How it works
The explanation, with examples you can run.
The loop
- Send the conversation plus tool definitions.
- If the response asks for one or more tool calls (providers mark this with a stop reason such as
tool_useor atool_callsfield), validate the arguments, run each tool, and append a result message that references the call's id. - Call the model again with the extended conversation.
- Repeat until the model replies with a final answer, or until your own limit stops it.
The Try it block plays this loop with a scripted fake model, so you can see exactly which messages travel each way.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Designing tools
- Descriptions are prompts. Say what the tool does, when to use it and when not to, and what each argument means, with formats ("ISO 8601 date", "order id like A-1234").
- Few, clear tools beat many overlapping ones. If two tools could both answer a question, the model will sometimes choose the wrong one. Merge or rename them.
- Return what the model needs, not everything you have. Trim large responses; tool results count as input tokens on the next call.
- Make side effects explicit. Separate read tools from write tools. For writes that matter (payments, emails, deletions), require confirmation from the user or a human, and make them idempotent so a repeated call does no harm.
- Several calls at once. Many APIs let the model request several independent tool calls in one turn. Run them concurrently if they are independent, and return every result.
Your code is the security boundary
The arguments come from a model whose input may include user text and retrieved documents, so treat them exactly like a request from an untrusted client: validate against the schema, check the current user's permissions, apply rate limits, and log what ran. Never let the model choose which credentials to use; give tools only the access they need. A prompt can describe rules, but only code enforces them.
// Placeholder client and types; the shape is common across providers.let messages: Message[] = [{ role: "user", content: question }]for (let round = 0; round < MAX_TOOL_ROUNDS; round++) { const res = await llm.generate({ model: MODEL, system: SYSTEM, tools: TOOL_DEFINITIONS, messages }) if (res.stopReason !== "tool_use") return res.text messages = [...messages, res.asAssistantMessage()] const results = await Promise.all(res.toolCalls.map((call) => runToolSafely(call, currentUser))) messages = [...messages, toolResultMessage(results)]}throw new Error("Tool loop did not finish")Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.