> ## Documentation Index
> Fetch the complete documentation index at: https://docs.naga.ac/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool calling

> Let the model call your functions, run them in your code and send the results back for the final answer.

You describe your functions in `tools`, and the model answers with a call instead of text when it needs one. Your code runs the function and sends the result back in the next request, and the model uses it to answer. The loop below repeats that until the model answers in text.

> [tab] Responses
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import json
> import os
> from openai import OpenAI
>
> client = OpenAI(base_url="https://api.naga.ac/v1", api_key=os.environ["NAGA_API_KEY"])
>
> tools = [{
>     "type": "function",
>     "name": "get_weather",
>     "description": "Current weather for a city.",
>     "parameters": {
>         "type": "object",
>         "properties": {"city": {"type": "string"}},
>         "required": ["city"],
>     },
> }]
>
> def get_weather(city):
>     return {"city": city, "temperature_c": 18, "sky": "cloudy"}
>
> items = [{"role": "user", "content": "What is the weather in Prague?"}]
>
> while True:
>     response = client.responses.create(model="deepseek-v4.1-flash", input=items, tools=tools)
>     items += [item.model_dump(exclude_none=True) for item in response.output]
>     calls = [item for item in response.output if item.type == "function_call"]
>     if not calls:
>         break
>     for call in calls:
>         result = get_weather(**json.loads(call.arguments))
>         items.append({"type": "function_call_output", "call_id": call.call_id, "output": json.dumps(result)})
>
> print(response.output_text)
> ```
>
> ```javascript Node.js theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import OpenAI from "openai";
>
> const client = new OpenAI({ baseURL: "https://api.naga.ac/v1", apiKey: process.env.NAGA_API_KEY });
>
> const tools = [{
>   type: "function",
>   name: "get_weather",
>   description: "Current weather for a city.",
>   parameters: {
>     type: "object",
>     properties: { city: { type: "string" } },
>     required: ["city"],
>   },
> }];
>
> const getWeather = ({ city }) => ({ city, temperature_c: 18, sky: "cloudy" });
>
> const items = [{ role: "user", content: "What is the weather in Prague?" }];
> let response;
>
> while (true) {
>   response = await client.responses.create({ model: "deepseek-v4.1-flash", input: items, tools });
>   items.push(...response.output);
>   const calls = response.output.filter((item) => item.type === "function_call");
>   if (!calls.length) break;
>   for (const call of calls) {
>     const result = getWeather(JSON.parse(call.arguments));
>     items.push({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify(result) });
>   }
> }
>
> console.log(response.output_text);
> ```

> [tab] Chat Completions
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import json
> import os
> from openai import OpenAI
>
> client = OpenAI(base_url="https://api.naga.ac/v1", api_key=os.environ["NAGA_API_KEY"])
>
> tools = [{
>     "type": "function",
>     "function": {
>         "name": "get_weather",
>         "description": "Current weather for a city.",
>         "parameters": {
>             "type": "object",
>             "properties": {"city": {"type": "string"}},
>             "required": ["city"],
>         },
>     },
> }]
>
> def get_weather(city):
>     return {"city": city, "temperature_c": 18, "sky": "cloudy"}
>
> messages = [{"role": "user", "content": "What is the weather in Prague?"}]
>
> while True:
>     completion = client.chat.completions.create(
>         model="deepseek-v4.1-flash", messages=messages, tools=tools
>     )
>     message = completion.choices[0].message
>     messages.append(message.model_dump(exclude_none=True))
>     if not message.tool_calls:
>         break
>     for call in message.tool_calls:
>         result = get_weather(**json.loads(call.function.arguments))
>         messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
>
> print(message.content)
> ```
>
> ```javascript Node.js theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import OpenAI from "openai";
>
> const client = new OpenAI({ baseURL: "https://api.naga.ac/v1", apiKey: process.env.NAGA_API_KEY });
>
> const tools = [{
>   type: "function",
>   function: {
>     name: "get_weather",
>     description: "Current weather for a city.",
>     parameters: {
>       type: "object",
>       properties: { city: { type: "string" } },
>       required: ["city"],
>     },
>   },
> }];
>
> const getWeather = ({ city }) => ({ city, temperature_c: 18, sky: "cloudy" });
>
> const messages = [{ role: "user", content: "What is the weather in Prague?" }];
> let message;
>
> while (true) {
>   const completion = await client.chat.completions.create({ model: "deepseek-v4.1-flash", messages, tools });
>   message = completion.choices[0].message;
>   messages.push(message);
>   if (!message.tool_calls?.length) break;
>   for (const call of message.tool_calls) {
>     const result = getWeather(JSON.parse(call.function.arguments));
>     messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
>   }
> }
>
> console.log(message.content);
> ```
>
> ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> curl https://api.naga.ac/v1/chat/completions \
>   -H "Authorization: Bearer $NAGA_API_KEY" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "model": "deepseek-v4.1-flash",
>     "messages": [{"role": "user", "content": "What is the weather in Prague?"}],
>     "tools": [{
>       "type": "function",
>       "function": {
>         "name": "get_weather",
>         "description": "Current weather for a city.",
>         "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
>       }
>     }]
>   }'
> ```

> [tab] Messages
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import json
> import os
> from anthropic import Anthropic
>
> client = Anthropic(base_url="https://api.naga.ac", api_key=os.environ["NAGA_API_KEY"])
>
> tools = [{
>     "name": "get_weather",
>     "description": "Current weather for a city.",
>     "input_schema": {
>         "type": "object",
>         "properties": {"city": {"type": "string"}},
>         "required": ["city"],
>     },
> }]
>
> def get_weather(city):
>     return {"city": city, "temperature_c": 18, "sky": "cloudy"}
>
> messages = [{"role": "user", "content": "What is the weather in Prague?"}]
>
> while True:
>     message = client.messages.create(model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages)
>     messages.append({"role": "assistant", "content": message.content})
>     if message.stop_reason != "tool_use":
>         break
>     results = [
>         {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(get_weather(**block.input))}
>         for block in message.content
>         if block.type == "tool_use"
>     ]
>     messages.append({"role": "user", "content": results})
>
> print("".join(block.text for block in message.content if block.type == "text"))
> ```
>
> ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import Anthropic from "@anthropic-ai/sdk";
>
> const client = new Anthropic({ baseURL: "https://api.naga.ac", apiKey: process.env.NAGA_API_KEY });
>
> const tools: Anthropic.Tool[] = [{
>   name: "get_weather",
>   description: "Current weather for a city.",
>   input_schema: {
>     type: "object",
>     properties: { city: { type: "string" } },
>     required: ["city"],
>   },
> }];
>
> const getWeather = ({ city }: { city: string }) => ({ city, temperature_c: 18, sky: "cloudy" });
>
> const messages: Anthropic.MessageParam[] = [{ role: "user", content: "What is the weather in Prague?" }];
> let message: Anthropic.Message;
>
> while (true) {
>   message = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages });
>   messages.push({ role: "assistant", content: message.content });
>   if (message.stop_reason !== "tool_use") break;
>   const results: Anthropic.ToolResultBlockParam[] = message.content
>     .filter((block): block is Anthropic.ToolUseBlock => block.type === "tool_use")
>     .map((block) => ({
>       type: "tool_result",
>       tool_use_id: block.id,
>       content: JSON.stringify(getWeather(block.input as { city: string })),
>     }));
>   messages.push({ role: "user", content: results });
> }
>
> for (const block of message.content) {
>   if (block.type === "text") console.log(block.text);
> }
> ```

With the stub weather function, the model's final answer reads something like "It's 18 °C and cloudy in Prague."

## Calls and results in each format

The loop is the same everywhere, but each format names its parts differently:

| | Responses | Chat Completions | Messages |
| - | - | - | - |
| Declare a tool | `tools: [{"type": "function", "name", "description", "parameters"}]` | `tools: [{"type": "function", "function": {"name", "description", "parameters"}}]` | `tools: [{"name", "description", "input_schema"}]` |
| The model calls | Output item `function_call` with `call_id` and `arguments` as a JSON string | `message.tool_calls[]` with `id` and `function.arguments` as a JSON string | Content block `tool_use` with `id` and `input` as an object |
| The turn stops with | The response ends after the `function_call` items | `finish_reason: "tool_calls"` | `stop_reason: "tool_use"` |
| You answer with | An input item `function_call_output` with `call_id` | A `role: "tool"` message with `tool_call_id` | A `user` message with a `tool_result` block and `tool_use_id` |
| `tool_choice` | `auto`, `none`, `required` or `{"type": "function", "name": "..."}` | `auto`, `none`, `required` or `{"type": "function", "function": {"name": "..."}}` | `{"type": "auto"}`, `{"type": "any"}`, `{"type": "tool", "name": "..."}` or `{"type": "none"}` |

Responses also accepts the Chat Completions tool shape, `{"type": "function", "function": {...}}`. In Chat Completions, `parallel_tool_calls: false` asks for one call per turn.

## Send the history back unchanged

NagaAI checks that every tool call in the conversation has its result and every result has its call. A request that breaks the pair fails with `400` before it reaches a provider, so you don't pay for it. The error code says which half is missing: `tool_call_unanswered` for a call without a result, `tool_output_unmatched` for a result without its call.

Responses keeps no state between calls, which is why the example appends the whole `response.output` to `items`. The next request needs the earlier `function_call` items next to their `function_call_output` items. [Conversation state](https://docs.naga.ac/guides/conversation-state.md) explains this for every format.

Send the assistant turn back as it came. In Chat Completions that includes `tool_calls` and any `reasoning_details`, and in Messages any `thinking` blocks. Some models lose their reasoning between tool calls if you rebuild the turn from its fields.

In Messages, NagaAI ignores `is_error` on a `tool_result`. To tell the model that a tool failed, say so in the result text.

## Models that don't call tools

Check `parameters` of the model's `chat` operation in [`/v1/models`](https://docs.naga.ac/get-started/models). If `tools` isn't listed, NagaAI removes your tools from the request and the model answers in text, without an error.

A few models accept only `tool_choice: "auto"`. For `claude-opus-5.5`, `claude-fable-5.1` and Mythos 5.1, NagaAI turns `required` or a named tool into `auto`, so the model can still answer in text. Check for a call in the response before you rely on one.

## Tool types

NagaAI supports your own function tools and web search. It drops other server-side tools, such as `code_interpreter` or `file_search`, and the full list is in [Differences from OpenAI and Anthropic](https://docs.naga.ac/build/compatibility.md). Web search has its own guide, [Web search](https://docs.naga.ac/build/web-search.md).

## Troubleshooting

### `400` on `functions` or `function_call`

NagaAI refuses the legacy Chat Completions fields: `functions`, the `function` role and `function_call` on a message. It drops a top-level `function_call`. Move to `tools`, `tool_choice`, `tool_calls` and the `tool` role.

### `400 tool_call_unanswered`

The history has a tool call without a result. The message names the first `call_id` that has none. Add a result for every call the model made, with the same id.

### `400 tool_output_unmatched`

The history has a tool result whose call isn't there. This usually means the code relies on `previous_response_id`, which NagaAI doesn't keep. Send the call item itself along with its result.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.