Created
March 13, 2026 11:29
-
-
Save beardedtim/0d7f33af2999f5eedb6fd4d64f2e0095 to your computer and use it in GitHub Desktop.
A basic agent using Together.AI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { defaultClient } from "./together"; | |
| import Log from "./log"; | |
| import { ToolSchema } from "./tools/schemas"; | |
| interface ToolDef { | |
| schema: ToolSchema; | |
| execute: (args: unknown) => AsyncGenerator<any, unknown>; | |
| } | |
| export interface AgentConfig { | |
| model: string; | |
| prompt: string; | |
| tools: { | |
| [x: string]: ToolDef; | |
| }; | |
| client?: typeof defaultClient; | |
| logger?: typeof Log; | |
| forceInitialToolCall?: boolean; | |
| maxIterations?: number; | |
| } | |
| export interface ActRequest { | |
| query: string; | |
| } | |
| export type AgentEvent = | |
| | { | |
| type: "agent_start"; | |
| query: string; | |
| } | |
| | { | |
| type: "model_stream"; | |
| delta: string; | |
| } | |
| | { | |
| type: "tool_call"; | |
| id: string; | |
| name: string; | |
| args: unknown; | |
| } | |
| | { | |
| type: "tool_result"; | |
| id: string; | |
| name: string; | |
| result: unknown; | |
| } | |
| | { | |
| type: "agent_thought"; | |
| content: string; | |
| } | |
| | { | |
| type: "final_answer"; | |
| content: string; | |
| } | |
| | { | |
| type: "error"; | |
| message: string; | |
| } | |
| | { | |
| type: "agent_complete"; | |
| }; | |
| export class Agent { | |
| #model: string; | |
| #agent_prompt: string; | |
| #available_tools: { [x: string]: ToolDef }; | |
| #client: typeof defaultClient; | |
| #log: typeof Log; | |
| #forceInitialToolCall: boolean; | |
| #maxAgentLoops: number; | |
| constructor(config: AgentConfig) { | |
| this.#model = config.model; | |
| this.#agent_prompt = config.prompt; | |
| this.#available_tools = config.tools; | |
| this.#client = config.client ?? defaultClient; | |
| this.#log = config.logger ?? Log.child({ name: `Agent::${this.#model}` }); | |
| this.#forceInitialToolCall = config.forceInitialToolCall ?? false; | |
| this.#maxAgentLoops = config.maxIterations ?? 10; | |
| } | |
| #parseToolCall(functionCall: { | |
| name: string; | |
| arguments: string; | |
| id: string; | |
| }) { | |
| return { | |
| name: functionCall.name, | |
| args: JSON.parse(functionCall.arguments), | |
| id: functionCall.id, | |
| }; | |
| } | |
| async *act(request: ActRequest): AsyncGenerator<AgentEvent> { | |
| yield { | |
| type: "agent_start", | |
| query: request.query, | |
| }; | |
| const messageHistory: any[] = [ | |
| { role: "system", content: this.#agent_prompt }, | |
| { role: "user", content: request.query }, | |
| ]; | |
| let iteration = 0; | |
| while (iteration < this.#maxAgentLoops) { | |
| iteration++; | |
| const completion = await this.#client.chat.completions.create({ | |
| model: this.#model, | |
| messages: messageHistory, | |
| tools: Object.values(this.#available_tools).map((t) => t.schema), | |
| stream: true, | |
| }); | |
| const toolCalls: { name: string; arguments: string; id: string }[] = []; | |
| let streamedText = ""; | |
| for await (const chunk of completion) { | |
| const [choice] = chunk.choices; | |
| if (!choice) continue; | |
| const delta = choice.delta; | |
| if (delta?.content) { | |
| streamedText += delta.content; | |
| yield { | |
| type: "model_stream", | |
| delta: delta.content, | |
| }; | |
| } | |
| if (delta?.tool_calls) { | |
| for (const tool_call of delta.tool_calls) { | |
| if (tool_call.id === null && tool_call.function?.arguments) { | |
| toolCalls[toolCalls.length - 1].arguments += | |
| tool_call.function.arguments; | |
| } else { | |
| toolCalls.push({ | |
| name: tool_call.function?.name ?? "", | |
| arguments: tool_call.function?.arguments ?? "", | |
| id: tool_call.id!, | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| // If model just answered | |
| if (!toolCalls.length) { | |
| if (streamedText.trim()) { | |
| yield { | |
| type: "final_answer", | |
| content: streamedText, | |
| }; | |
| } | |
| yield { type: "agent_complete" }; | |
| return; | |
| } | |
| const parsed = toolCalls.map(this.#parseToolCall); | |
| if (streamedText.trim()) { | |
| messageHistory.push({ | |
| role: "assistant", | |
| content: streamedText, | |
| }); | |
| } | |
| messageHistory.push({ | |
| role: "assistant", | |
| tool_calls: parsed.map((call) => ({ | |
| id: call.id, | |
| type: "function", | |
| function: { | |
| name: call.name, | |
| arguments: JSON.stringify(call.args), | |
| }, | |
| })), | |
| }); | |
| for (const call of parsed) { | |
| yield { | |
| type: "tool_call", | |
| id: call.id, | |
| name: call.name, | |
| args: call.args, | |
| }; | |
| const tool = this.#available_tools[call.name]; | |
| if (!tool) { | |
| yield { | |
| type: "error", | |
| message: `Unknown tool ${call.name}`, | |
| }; | |
| continue; | |
| } | |
| const result = yield* tool.execute(call.args); | |
| yield { | |
| type: "tool_result", | |
| id: call.id, | |
| name: call.name, | |
| result, | |
| }; | |
| messageHistory.push({ | |
| role: "tool", | |
| tool_call_id: call.id, | |
| name: call.name, | |
| content: JSON.stringify(result), | |
| }); | |
| } | |
| } | |
| yield { | |
| type: "error", | |
| message: "Agent reached max iterations", | |
| }; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment