Claude Function Calling: A Practical Implementation Guide
Claude function calling — Anthropic calls it "tool use" — lets you give Claude a list of functions it can request to run, along with structured JSON arguments, so it can fetch live data, query a database, or trigger an action instead of guessing an answer from training data. You define the functions as JSON schemas, send them with your request, and Claude decides when to call one, returning a structured request that your code executes and feeds back into the conversation.
This guide focuses on the practical side: how to structure the request, run the tool-calling loop, handle multiple or parallel calls, and avoid the mistakes that break most first implementations.
The basic request shape
A function calling request has three parts: the messages array, a tools array describing what Claude can call, and (optionally) a tool_choice parameter to control when tools get used.
{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"tools": [
{
"name": "get_stock_price",
"description": "Get the current price of a stock by ticker symbol",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol, e.g. AAPL"
}
},
"required": ["ticker"]
}
}
],
"messages": [
{ "role": "user", "content": "What's Apple's stock price right now?" }
]
}
If Claude decides it needs the function, the response's content array includes a block with type: "tool_use", a name, and structured input. If it doesn't need a tool, you just get normal text.
Writing schemas Claude actually uses well
Function calling quality depends heavily on how you describe the tool, not just the JSON shape:
- Name functions after what they do, not generic labels like
runorexecute.search_ordersbeatstool1. - Write descriptions like documentation, including units, formats, and edge cases. "Date in YYYY-MM-DD format" prevents a lot of malformed input.
- Mark required fields explicitly. Claude will still occasionally omit optional fields if the description doesn't make their purpose clear.
- Keep schemas flat where possible. Deeply nested objects increase the chance of malformed arguments, especially with smaller models.
- Don't overload one tool with too many responsibilities. Split
manage_userintocreate_user,update_user,delete_user— Claude picks the right one more reliably than it picks the right mode inside one big function.
The tool-calling loop
Function calling is not a single request-response — it's a loop. Claude requests a tool, you run it, and you send the result back as a tool_result block so Claude can continue:
let messages = [{ role: "user", content: "What's Apple's stock price?" }];
let response = await callClaude(messages, tools);
while (response.stop_reason === "tool_use") {
const toolUse = response.content.find(b => b.type === "tool_use");
const result = await runLocalFunction(toolUse.name, toolUse.input);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result)
}
]
});
response = await callClaude(messages, tools);
}
console.log(response.content);
The loop ends when stop_reason is no longer tool_use. For multi-tool workflows — say, looking up a customer then checking their order status — this loop can run several iterations before Claude produces a final answer.
Parallel tool calls
Claude can return multiple tool_use blocks in a single response when the tasks are independent — for example, checking the weather in three different cities at once. Your code should iterate over all tool_use blocks in content, run them (ideally concurrently), and send back a tool_result for each one, matched by tool_use_id, before continuing the conversation. Missing one result will stall the loop.
Controlling when tools get used
The tool_choice parameter gives you control over Claude's behavior:
{"type": "auto"}— default; Claude decides whether to use a tool.{"type": "any"}— Claude must use one of the provided tools.{"type": "tool", "name": "get_stock_price"}— force a specific tool call.{"type": "none"}— disable tool use for this request even if tools are defined.
Forcing a specific tool is useful when you're using function calling purely for structured output extraction rather than an actual side-effecting action — for example, forcing a save_extracted_data tool to get reliably formatted JSON out of unstructured text.
Handling errors gracefully
Two failure modes come up constantly:
- The function call itself fails (API down, invalid ticker, timeout). Don't drop the turn — send back a
tool_resultwith an error message and"is_error": trueso Claude can explain the failure to the user or retry with different input, instead of the conversation just hanging. - Claude sends unexpected arguments. Validate
inputagainst your schema before running the function. Malformed input is rare with well-written schemas but not impossible, especially with free-form string fields.
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: ticker symbol not found",
"is_error": true
}
Function calling with streaming
Tool use works with streaming too, but the tool_use block arrives incrementally — input_json_delta events build up the arguments piece by piece, and you need to buffer them until the block closes before parsing the JSON. If you're building this from scratch, budget real testing time for this part; partial JSON parsing is the most common source of bugs in streaming tool implementations.
Running function calling in production
Once you move past prototyping, you're maintaining API key rotation, per-team usage tracking, and streaming infrastructure on top of the tool-calling logic itself. SubToAPI wraps Claude access behind a standard HTTPS API with sub_live_... application keys, so your backend calls one endpoint with full support for tool use, streaming, and usage metadata, without managing raw provider credentials per environment. Team and Scale plans add multi-seat dashboards if more than one service or developer needs isolated keys. See the tool use docs and streaming docs for request formats, or start with the quickstart.
Questions
Does Claude support parallel function calls in one response? Yes. Claude can return multiple tool_use blocks in a single response for independent tasks. Your code must send back a matching tool_result for each one before continuing.
How is Claude's function calling different from OpenAI's? The underlying idea is the same — JSON schemas describing callable functions, structured output requesting a call. Claude uses tool_use/tool_result content blocks inside the messages array rather than a separate function_call field, and tool_choice options differ slightly (auto, any, tool, none).
Can I force Claude to always call a specific function? Yes, using tool_choice: {"type": "tool", "name": "your_function"}. This is commonly used to get reliable structured JSON output from unstructured input, treating the tool schema as an extraction template rather than an action.