Build
Updated
Function calling
Define function tools in a request, receive function_call items, and post the results back. The standard Responses API tool loop works unchanged.
You can give the model tools that your own code executes. Pass them in
tools, receive function_call items when the model wants one run, execute
it, and send the output back as a function_call_output item. This is the
Responses API’s native function-calling loop, so the official SDKs’ helpers
work as they do against OpenAI.
The round trip
Section titled “The round trip”-
Send a turn with
tools.POST /v1/responses {"input": [{"content": "What's the weather like in San Francisco?","role": "user","type": "message"}],"model": "anthropic/claude-sonnet-5","tools": [{"description": "Get the current weather for a location","name": "get_weather","parameters": {"properties": {"location": {"description": "The city and state, e.g. San Francisco, CA","type": "string"}},"required": ["location"],"type": "object"},"type": "function"}]} -
Read the
function_callitem. The response carries afunction_callitem inoutput, after any text the model produced alongside it. The turn itself is finished: the work waiting is yours, and a streaming request ends its stream here.{"background": false,"completed_at": 1767225600,"conversation": null,"created_at": 1767225600,"error": null,"frequency_penalty": 0.0,"id": "resp_ZXhhbXBsZS9mYy90b29sLWNhbGwAAAAAAAAAAAAAAAAAAAAAAAAAAA","incomplete_details": null,"instructions": null,"max_output_tokens": null,"max_tool_calls": null,"metadata": null,"model": "anthropic/claude-sonnet-5","object": "response","output": [{"content": [{"annotations": [],"logprobs": [],"text": "calling the tool","type": "output_text"}],"id": "msg_00000000000000000000000000000001","role": "assistant","status": "completed","type": "message"},{"arguments": "{\"location\":\"San Francisco, CA\"}","call_id": "00000000-0000-4000-8000-000000000002","id": "fc_00000000-0000-4000-8000-000000000002","name": "get_weather","status": "completed","type": "function_call"}],"parallel_tool_calls": true,"presence_penalty": 0.0,"previous_response_id": null,"prompt_cache_key": null,"reasoning": null,"safety_identifier": null,"service_tier": "default","status": "completed","store": true,"temperature": 1.0,"text": {"format": {"type": "text"}},"tool_choice": "auto","tools": [{"description": "Get the current weather for a location","name": "get_weather","parameters": {"properties": {"location": {"description": "The city and state, e.g. San Francisco, CA","type": "string"}},"required": ["location"],"type": "object"},"strict": null,"type": "function"}],"top_logprobs": 0,"top_p": 1.0,"truncation": "disabled","usage": {"input_tokens": 100,"input_tokens_details": {"cache_write_tokens": 0,"cached_tokens": 0},"output_tokens": 20,"output_tokens_details": {"reasoning_tokens": 0},"total_tokens": 120}} -
Run the tool and post the output. Send a new request whose
previous_response_idis the response above and whoseinputcontains onlyfunction_call_outputitems, one per call, keyed bycall_id.POST /v1/responses {"input": [{"call_id": "00000000-0000-4000-8000-000000000002","output": "{\"temperature\": \"64F\", \"conditions\": \"foggy\"}","type": "function_call_output"}],"model": "anthropic/claude-sonnet-5","previous_response_id": "resp_ZXhhbXBsZS9mYy90b29sLWNhbGwAAAAAAAAAAAAAAAAAAAAAAAAAAA"}The model continues with your result. The reply is the completed response, or another
function_callif the model needs more.{"background": false,"completed_at": 1767225600,"conversation": null,"created_at": 1767225600,"error": null,"frequency_penalty": 0.0,"id": "resp_ZXhhbXBsZS9mYy90b29sLW91dHB1dC1jb250aW51YXRpb24AAAAAAA","incomplete_details": null,"instructions": null,"max_output_tokens": null,"max_tool_calls": null,"metadata": null,"model": "anthropic/claude-sonnet-5","object": "response","output": [{"content": [{"annotations": [],"logprobs": [],"text": "It is 64F and foggy in San Francisco.","type": "output_text"}],"id": "msg_00000000000000000000000000000001","role": "assistant","status": "completed","type": "message"}],"parallel_tool_calls": true,"presence_penalty": 0.0,"previous_response_id": "resp_ZXhhbXBsZS9mYy90b29sLWNhbGwAAAAAAAAAAAAAAAAAAAAAAAAAAA","prompt_cache_key": null,"reasoning": null,"safety_identifier": null,"service_tier": "default","status": "completed","store": true,"temperature": 1.0,"text": {"format": {"type": "text"}},"tool_choice": "auto","tools": [],"top_logprobs": 0,"top_p": 1.0,"truncation": "disabled","usage": {"input_tokens": 200,"input_tokens_details": {"cache_write_tokens": 0,"cached_tokens": 0},"output_tokens": 40,"output_tokens_details": {"reasoning_tokens": 0},"total_tokens": 240}}
The Node and Python examples use a client set up for this API.
const tools = [ { type: "function", name: "get_weather", description: "Get the current weather for a location", parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"], }, },];
let response = await client.responses.create({ model: "anthropic/claude-sonnet-5", input: "What's the weather like in San Francisco?", tools,});
const calls = response.output.filter((item) => item.type === "function_call");if (calls.length > 0) { response = await client.responses.create({ model: "anthropic/claude-sonnet-5", previous_response_id: response.id, input: calls.map((call) => ({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify(getWeather(JSON.parse(call.arguments))), })), });}
console.log(response.output_text);import json
tools = [ { "type": "function", "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"], }, }]
response = client.responses.create( model="anthropic/claude-sonnet-5", input="What's the weather like in San Francisco?", tools=tools,)
calls = [item for item in response.output if item.type == "function_call"]if calls: response = client.responses.create( model="anthropic/claude-sonnet-5", previous_response_id=response.id, input=[ { "type": "function_call_output", "call_id": call.call_id, "output": json.dumps(get_weather(**json.loads(call.arguments))), } for call in calls ], )
print(response.output_text)Rules for tool definitions
Section titled “Rules for tool definitions”A request that breaks one of these is a 400 naming the problem, and
nothing is created or charged.
- Names are 1 to 64 characters of
[a-zA-Z0-9_]and must be unique within the request. Give each tool a distinct name. - Names must not match a built-in platform tool. Every built-in tool
counts, whether or not your request would use it. A match is a
400naming the tool; rename yours. - Your tools are yours to execute. The API hands you
function_callitems and waits for your output; it never executes a tool you defined.
Controlling which tool is called
Section titled “Controlling which tool is called”tool_choice and allowed_tools work as in the Responses API, with two
properties worth knowing.
| Value | Effect |
|---|---|
"auto" |
The model decides. Default |
"none" |
No tool may be called this turn |
"required" |
The model must call some tool on its first call |
{"type": "function", "name": "get_weather"} |
The model must call that tool on its first call |
{"type": "allowed_tools", "mode": "auto" | "required", "tools": [...]} |
Restrict the callable set to a subset, optionally forcing a call |
- Forcing applies to the first model call only. After a tool result comes
back,
requiredand a named function relax toauto. Restrictions (none, and theallowed_toolssubset) persist for the whole turn. - A choice the API cannot honor is a
400: a name not in the tool set,requiredwith no tools, an emptyallowed_toolslist, anallowed_toolsmode ofnone, a function choice orallowed_toolsentry with noname, or any forced choice on a selection that does not start with a model call. The message says which; define the tool you name, or useauto.
strict on a function definition is recorded and echoed but not enforced.
Check each function_call’s arguments against your schema in your own code
before you run the tool.
Tools across turns
Section titled “Tools across turns”- Posting a tool output continues the same response.
tools,tool_choice, andmodelon that request are ignored. Stock SDK loops resend the original body verbatim, and that works. - A new turn starts fresh.
toolsapply per turn. A turn that omits them has no request tools, even if the previous turn defined some.
Reporting a tool failure
Section titled “Reporting a tool failure”There is no error field on function_call_output. If your tool fails, put a
description of the failure in output. The model reads it as the tool’s
result and carries on. A status on the item is accepted and ignored, and a
failure you report this way counts as a completed tool call whose output
happens to describe an error.
A tool error never fails the response.
When this API refuses a call
Section titled “When this API refuses a call”The model can ask for a call this API will not execute: arguments your
parameters schema rejects, a call repeated with the same arguments, or a
tool you did not offer. The response then reports the call and its result
together. The function_call carries the arguments the model sent, and a
function_call_output with the same call_id carries the reason as its
output. Its status is completed, because the call was answered, and a
helpingio:error object on the item says why, with a code and a
message. The code is tool_args_invalid, duplicate_tool_call or
unknown_tool for those refusals. Any other failure reports tool_failed
with a fixed message. You do not answer this call: the model has already read the
reason, and it decides what to do next in the same response.
A stream reports the same two items, with the same fields, as they happen.