Tool calling, JSON output and reasoning
Claud models can call functions you define, return guaranteed-valid JSON, and expose their reasoning. All three use the standard Chat Completions fields.
Tool calling#
Describe your functions in tools. When the model decides one should be called, it returns a tool_calls array instead of (or alongside) content. You run the function, append the result as a tool message, and call the API again.
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Current weather for a city.',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: 'City name, e.g. "Lisbon"' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
},
},
];
const messages = [{ role: 'user', content: "What's the weather in Lisbon?" }];
// 1. Ask the model
const first = await client.chat.completions.create({ model: 'claud-5.1', messages, tools });
const call = first.choices[0].message.tool_calls?.[0];
if (call) {
// 2. Run the function
const args = JSON.parse(call.function.arguments);
const result = await getWeather(args.city, args.unit ?? 'celsius');
// 3. Send the result back
messages.push(first.choices[0].message);
messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
const second = await client.chat.completions.create({ model: 'claud-5.1', messages, tools });
console.log(second.choices[0].message.content);
}The first response looks like this:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_7f3a1c",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Lisbon\",\"unit\":\"celsius\"}" }
}
]
},
"finish_reason": "tool_calls"
}
]
}Controlling tool use#
- tool_choicestring | objectdefault: "auto"
"auto"lets the model decide;"none"disables tools for this turn;"required"forces at least one call;{"type":"function","function":{"name":"get_weather"}}forces a specific function.- tools[].function.strictboolean
- Accepted for compatibility. Models validate arguments against the schema on a best-effort basis; always validate
argumentsin your code before executing anything.
Up to 128 tools may be sent per request. Tool definitions count toward prompt tokens, so keep descriptions concise.
function.arguments is a string produced by the model. Parse it defensively, validate against your schema, and never pass it to a shell, SQL or eval.
Streaming tool calls#
When streaming, tool calls arrive incrementally in delta.tool_calls. Each fragment has an index; the first fragment for an index carries id and function.name, and subsequent fragments append to function.arguments. Concatenate by index until finish_reason: "tool_calls". The OpenAI SDK helpers (stream.finalChatCompletion() in JavaScript, ChatCompletionStream in Python) do this for you.
JSON output#
Set response_format to receive syntactically valid JSON:
const res = await client.chat.completions.create({
model: 'claud-flash',
response_format: { type: 'json_object' },
messages: [
{ role: 'system', content: 'Extract fields and reply with JSON: {"name": string, "email": string | null}.' },
{ role: 'user', content: 'Hi, I am Ana Costa, reach me at ana@example.com' },
],
});
const data = JSON.parse(res.choices[0].message.content);- The word JSON must appear somewhere in your messages, otherwise the request is rejected upstream. Describe the shape you want in the system prompt.
{"type": "json_schema", ...}is accepted and treated asjson_object; the schema itself is not enforced by the model, so validate the result.- If
finish_reasonislength, the JSON may be truncated. Raisemax_tokens. - For a strict structure, tool calling with
tool_choice: "required"and a single function is often more reliable than JSON mode: the function'sparametersschema guides the model.
Reasoning controls#
Reasoning models (claud-5.1, claud-opus, claud-reason) think before answering. The thinking is returned as reasoning_content on the assistant message (or as delta.reasoning_content while streaming) and counted in usage.completion_tokens_details.reasoning_tokens. Reasoning tokens are billed as output tokens.
- reasoning_effortstring
One of
none,minimal,low,medium,high,xhigh,max. Lower effort answers faster and cheaper; higher effort thinks longer. Each model has a default (for exampleclaud-reasondefaults tomax).nonedisables thinking on models that allow it.- thinkingobject
- Alternative syntax:
{"type": "enabled"}or{"type": "disabled"}. Ignored whenreasoning_effortis present.
{
"model": "claud-5.1",
"reasoning_effort": "low",
"messages": [{ "role": "user", "content": "Is 2027 a prime number? Answer yes or no." }]
}Multi-turn conversations with reasoning#
Do not send previous reasoning_content back in subsequent turns; it is dropped upstream and only adds to prompt tokens. Send the assistant's content (and any tool_calls) as usual. Within a single tool-calling loop, however, including the assistant message exactly as returned (which may include reasoning_content) is fine and recommended.
Vision#
Models with the vision capability accept images as content parts:
{
"model": "claud-5.1",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What is written on this sign?" },
{ "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,/9j/4AAQ...", "detail": "auto" } }
]
}
]
}Data URLs (PNG, JPEG, WebP, GIF) and publicly reachable HTTPS URLs are supported, up to 64 parts per message. Image tokens are counted as prompt tokens. Check capabilities.vision in GET /v1/models before sending images; models without vision reject image parts with 400 invalid_request.