Chapter 02
Tool calling from scratch
How a model asks your code to act: tool definitions, the call and result messages, tool choice, parallel calls and errors, shown with one provider API and no framework.
Tool definition
Also called function schema, tool schema, input_schema, parameters, function calling
- What
- A name, a description and a JSON Schema for the arguments, sent with every request. The model sees only these three things when deciding whether and how to call the tool.
- Why
- The definition is the whole interface between the model and your code, so its wording decides how often the tool is chosen and filled in correctly.
{
"name": "search_orders",
"description": "Find a customer's orders by email.",
"input_schema": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"status": { "enum": ["open", "shipped", "refunded"] }
},
"required": ["email"]
}
}Watch out
One vague tool such as query(text) that does five jobs, instead of a few tools with one clear job each.
Tool call and tool result
Also called tool_use, tool_result, function_call, function_call_output, call id
- What
- The model replies with a tool call carrying an id, a tool name and JSON arguments. Your code runs the tool and sends back a tool result that quotes the same id, then calls the model again.
- Why
- The id is how the model matches each result to its request, especially when several calls happen in one turn.
while (true) {
const r = await client.messages.create(
{ model: MODEL, max_tokens: 4096, tools, messages });
messages.push({ role: "assistant", content: r.content });
if (r.stop_reason !== "tool_use") break;
const calls = r.content.filter(
(b): b is Anthropic.ToolUseBlock => b.type === "tool_use");
const results = calls.map((b) => ({
type: "tool_result" as const, tool_use_id: b.id,
content: runTool(b.name, b.input) }));
messages.push({ role: "user", content: results });
}Watch out
Dropping the assistant message that holds the tool calls before sending results, which the API rejects or the model misreads.
Provider differences in tool calling
Also called Anthropic vs OpenAI tools, Responses API, Messages API, Gemini function calling
- What
- Every major provider has the same loop with different field names. Anthropic uses
input_schema,tool_useandtool_result; the OpenAI Responses API usesparameters,function_callandfunction_call_outputjoined bycall_id. - Why
- Knowing the loop in one provider lets you read any other in minutes, and it is why thin adapters and gateways can switch models without rewriting agents.
- Example
- Stop signals differ too: Anthropic sets
stop_reasontotool_use, while in the Responses API you look forfunction_callitems in the output list.
Watch out
Hard-coding one provider's message shapes throughout your code instead of behind one small adapter.
Tool choice
Also called tool_choice, forced tool use, auto, required, none
- What
- A request setting that lets the model decide freely (auto), forces a tool call (any or required, or one named tool), or blocks tools (none).
- Why
- Auto is right for agents. Forcing a tool is a blunt instrument some newer models no longer accept, so prefer a clear instruction plus strict schemas.
Watch out
Forcing a tool on every turn, so the loop can never end with a plain answer.
Parallel tool calls
Also called parallel_tool_calls, disable_parallel_tool_use, multiple tool calls
- What
- One model turn can contain several tool calls. Your code runs them, concurrently when they are independent, and returns all the results together in the next message.
- Why
- It cuts latency when an agent needs several lookups at once, such as three order statuses or two searches.
Watch out
Splitting the results of one turn across several messages, which confuses the model and discourages parallel calls later.
Tool errors the model can act on
Also called is_error, isError, tool failure, error result
- What
- When a tool fails, return a result marked as an error with a short message saying what went wrong and what to try instead, rather than throwing and crashing the loop.
- Why
- A clear error lets the model retry with fixed arguments, pick another tool or tell the user, which is most of what makes an agent feel robust.
- Example
- "No order found for that email. Ask the customer for the order number, which starts with ORD-." is far more useful than a stack trace.
Watch out
Returning an empty result on failure, so the model concludes the customer has no orders.
Writing tool descriptions
Also called tool docs, agent-computer interface, tool naming
- What
- Describe each tool as you would for a new teammate: what it does, when to use it, when not to, what it returns and any limits. Name tools by the action, such as
search_orders. - Why
- Most tool-calling failures are wrong tool choice or wrong arguments, and both are usually fixed in the description rather than the prompt.
Watch out
Leaving parameter meanings implicit, such as a date with no stated format or time zone.
Keeping tool results small
Also called tool output truncation, pagination for agents, response format
- What
- Return only what the model needs to decide the next step: a few relevant fields, a page of results with a way to get more, and readable ids instead of raw internal dumps.
- Why
- Every tool result stays in the context for the rest of the run, so bloated results raise cost, slow every call and bury the facts that matter.
Watch out
Returning a 5,000-row query result and hoping the model finds the one row it needs.