Skip to content
← All articles

Tool Calling and Function Calling

How language models emit structured tool calls, how to design tool schemas, and how to handle the failure modes that show up in production.

Illustration generated for Neuricon.

Oct 13, 2026 · 7 min read

#guide#ai

How a model emits a structured call

Function calling and tool calling are two names for the same mechanism. You describe your functions to the model as JSON Schema, and when the model decides it needs one, it returns a structured call object instead of text. Your code parses that object, runs the real function, and sends the result back. The model does not execute anything. It proposes; you dispose.

The exchange is the same on every provider, with different field names. You send tools alongside the prompt. The model replies with one or more call requests. You execute them and return the outputs. You send the whole history back and the model continues. The "agentic loop" from the Building AI Agents guide is this exchange repeated.

Defining a tool in OpenAI format

An OpenAI function tool carries a name, a description, a JSON Schema for its parameters, and a strict flag.

```python

tools = [

{

"type": "function",

"name": "issue_refund",

"description": "Issue a refund for a customer order.",

"parameters": {

"type": "object",

"properties": {

"order_id": {

"type": "string",

"description": "The order identifier, e.g. ORD-88213",

},

"amount_cents": {

"type": "integer",

"description": "Refund amount in cents. Must not exceed the order total.",

},

},

"required": ["order_id", "amount_cents"],

"additionalProperties": False,

},

"strict": True,

}

]

```

When the model calls this tool, the response contains a function_call item with the tool name and an arguments string that parses as JSON. In the Responses API you answer with a function_call_output item referencing the call by its call_id. In the Chat Completions API you answer with a role: "tool" message referencing the tool_call_id.

The tool_choice parameter controls whether the model may call tools at all. auto is the default and lets the model decide. required forces at least one call. Passing a specific function name forces that function. OpenAI also supports parallel_tool_calls: false to restrict the model to at most one call per response.

Defining a tool in Anthropic format

Anthropic tools use input_schema instead of parameters and carry no wrapper type.

```python

tools = [

{

"name": "get_weather",

"description": "Get the current weather in a given location",

"input_schema": {

"type": "object",

"properties": {

"location": {

"type": "string",

"description": "The city and state, e.g. San Francisco, CA",

},

"unit": {

"type": "string",

"enum": ["celsius", "fahrenheit"],

"description": "Temperature unit",

},

},

"required": ["location"],

},

}

]

```

Claude answers with tool_use content blocks, each carrying an id, a name, and an input object. The input arrives already parsed, so there is no JSON parsing step on your side. You execute each requested tool and return the results as tool_result blocks in a user message, matching each result to its call with tool_use_id. A response whose stop_reason is tool_use means the model is waiting on your tool results.

Anthropic's tool_choice accepts auto (the model decides, the default when tools are provided), any (the model must call at least one tool), tool (force a specific tool by name), and none (no tool calls, the default when no tools are provided).

Strict mode and schema design

Strict mode exists to make the model's output match your schema reliably rather than on a best effort basis. On OpenAI, setting strict: true on a function tool enforces two rules that OpenAI checks at request time. First, additionalProperties must be false for every object in the parameters. Second, every field in properties must appear in required. If your schema breaks either rule, the request is rejected with details about the missing constraint.

Optional fields still work under strict mode. Mark them as nullable types instead of omitting them from required: "type": ["string", "null"]. The model then passes null for values it does not know.

On the Responses API, if you omit the strict flag, OpenAI attempts to normalize your schema into strict mode when possible and falls back to non-strict best effort calling when it cannot. The response shows which mode applied. On Chat Completions, requests stay non-strict unless you set strict: true explicitly.

A few schema habits prevent the most common argument errors. Use enums for fixed sets of values so the model cannot invent a new one. Mark every field the tool needs as required. Keep each schema small: one tool that does one thing beats a Swiss army tool with fifteen optional fields. Never ask the model to fill in a value your code already knows. If your backend knows the user ID, pass it in code and leave it out of the schema.

Parallel calls

Both providers can emit multiple tool calls in a single response. OpenAI's model may call several functions in one turn unless you set parallel_tool_calls: false. Anthropic may include several tool_use blocks in one response, and the documented pattern is to execute all of them and send the results back together.

Parallel calls help when the calls are independent, like looking up two unrelated records. They complicate things when calls share state or must run in order. If a tool writes and another reads, force sequential execution: disable parallel calls, or issue the write first and let the model call the reader on the next pass. For tools that cannot safely run twice, add idempotency keys to your own side, because retries happen.

Returning tool results

What you return matters as much as what the model sends. Keep results short and structured. A JSON string with named fields is easier for the model to quote correctly than a prose paragraph. Truncate long results before sending them back; a search tool returning ten full documents will drown the remaining conversation.

Always tie the result to the call that requested it. OpenAI matches results to calls by call_id or tool_call_id. Anthropic matches by tool_use_id. A mismatched or missing ID produces a confusing conversation history and, on some endpoints, an API error.

If a tool has nothing useful to return, say so explicitly: {"found": false, "message": "no matching orders"}. An empty string is ambiguous. The model may interpret it as a lost result and retry the call, burning steps and tokens.

Common failure modes

Hallucinated arguments. The model calls a real tool with a value that does not exist, like an order ID it invented. Mitigation: strict mode, enums, and server side validation that rejects unknown references with a message the model can act on.

Malformed JSON. Without strict mode the arguments string can arrive as broken JSON. Mitigation: enable strict mode, and still wrap your parse in a try block. On parse failure, return the error to the model as the tool observation so it can resend.

Unknown tool names. The model occasionally calls a function that is not in your list, especially after a conversation mentions other capabilities. Mitigation: check the name against your registry before executing, and return ERROR: unknown tool 'name' as the observation. Never execute by dynamic lookup without a whitelist.

Missing or wrong typed arguments. A required field arrives as null, or a number arrives as a string. Mitigation: validate with your schema library and return a message naming the field and the expected type.

Tool errors and timeouts. The underlying API fails, or a call takes too long. Mitigation: catch exceptions, set timeouts, and return a short error string as the observation. Keep the full traceback in your logs, never in the model facing message.

Tool refusal by policy. Some calls should require a human to confirm, like refunds or deletions. Mitigation: gate sensitive tools behind an approval step in your loop. The tool call becomes a proposal, a human approves or rejects, and only then does your code execute.

Validating before you trust

Treat every tool call as untrusted input from a helpful but fallible source. Validate arguments against the schema in your own code, even with strict mode enabled. Authorize the action as if the request came from the user directly, because it effectively did. Log the call, the arguments, and the result. Tool calling is the point where a language model reaches into your systems, and the discipline that applies is ordinary input validation, not magic.

Comments

Loading comments…

Leave a comment

Posting as (guest — sign in for a profile)