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:

JSON
{
  "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 arguments in your code before executing anything.

Up to 128 tools may be sent per request. Tool definitions count toward prompt tokens, so keep descriptions concise.

Treat arguments as untrusted input

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 as json_object; the schema itself is not enforced by the model, so validate the result.
  • If finish_reason is length, the JSON may be truncated. Raise max_tokens.
  • For a strict structure, tool calling with tool_choice: "required" and a single function is often more reliable than JSON mode: the function's parameters schema 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 example claud-reason defaults to max). none disables thinking on models that allow it.

thinkingobject
Alternative syntax: {"type": "enabled"} or {"type": "disabled"}. Ignored when reasoning_effort is present.
JSON
{
  "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:

JSON
{
  "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.