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

# Tool calling

> The model picks a tool and generates typed arguments, you run it, you feed the result back.

The model decides which of your registered tools to invoke and generates typed arguments for it. You execute the tool and hand the result back, and the model continues. Use it whenever the model needs to act on the world: call an API, query a database, run a calculation, fetch a document.

Tool calling is a per-model capability. Check the capability listing on the model's page before sending `tools`. A model that does not list tool calling refuses the request with `400` `hosted_capability_not_supported`.

## Minimal code

<CodeGroup>
  ```python Python theme={"dark"}
  import json
  import os
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.runinfra.ai/v1",
      api_key=os.environ["RUNINFRA_GATEWAY_KEY"],
  )

  tools = [{
      "type": "function",
      "function": {
          "name": "get_weather",
          "description": "Get the current weather for a city",
          "parameters": {
              "type": "object",
              "properties": {"city": {"type": "string"}},
              "required": ["city"],
          },
      },
  }]

  def get_weather(city: str) -> dict:
      return {"city": city, "temp_c": 21, "conditions": "partly cloudy"}

  messages = [{"role": "user", "content": "What's the weather in Paris?"}]

  for _ in range(10):
      response = client.chat.completions.create(
          model="nemotron-3-5-lightning-30b",
          messages=messages,
          tools=tools,
          max_tokens=16384,
      )
      msg = response.choices[0].message
      messages.append(msg)

      if not msg.tool_calls:
          print(msg.content)
          break

      for call in msg.tool_calls:
          args = json.loads(call.function.arguments)
          result = get_weather(**args)
          messages.append({
              "role": "tool",
              "tool_call_id": call.id,
              "content": json.dumps(result),
          })
  ```

  ```typescript TypeScript theme={"dark"}
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "https://api.runinfra.ai/v1",
    apiKey: process.env.RUNINFRA_GATEWAY_KEY,
  });

  const tools = [{
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get the current weather for a city",
      parameters: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
    },
  }];

  function getWeather(city: string) {
    return { city, temp_c: 21, conditions: "partly cloudy" };
  }

  const messages: any[] = [
    { role: "user", content: "What's the weather in Paris?" },
  ];

  for (let turn = 0; turn < 10; turn += 1) {
    const response = await client.chat.completions.create({
      model: "nemotron-3-5-lightning-30b",
      messages,
      tools,
      max_tokens: 16384,
    });
    const msg = response.choices[0].message;
    messages.push(msg);

    if (!msg.tool_calls?.length) { console.log(msg.content); break; }

    for (const call of msg.tool_calls) {
      if (call.type !== "function") continue;
      const args = JSON.parse(call.function.arguments);
      const result = getWeather(args.city);
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: JSON.stringify(result),
      });
    }
  }
  ```
</CodeGroup>

## What one turn actually looks like

<div className="block dark:hidden">
  <svg viewBox="0 0 720 384" width="100%" role="img" aria-label="One tool-calling turn: you send messages plus tools, the model returns an assistant message carrying tool_calls, you push that whole assistant message and then one tool message per call, then send the grown array again; the turn with no tool_calls is the final answer." fill="none" xmlns="http://www.w3.org/2000/svg"><text x="24" y="16" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">One tool turn</text><text x="696" y="16" fill="#78786f" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0" textAnchor="end">repeat until tool\_calls is absent</text><text x="470" y="36" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">Messages you hold</text><line x1="26" y1="40" x2="26" y2="306" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><rect x="23.5" y="67.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><line x1="29" y1="70" x2="48" y2="70" stroke="#e8e8e3" strokeWidth="1" /><rect x="48.5" y="46.5" width="395" height="47" fill="#ffffff" stroke="#e8e8e3" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="65" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">01 Send</text><text x="62" y="82" fill="#6e6d64" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The model sees every tool description you registered.</text><line x1="444" y1="70" x2="458" y2="70" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="74" fill="#52524c" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user</text><rect x="23.5" y="129.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><line x1="29" y1="132" x2="48" y2="132" stroke="#e8e8e3" strokeWidth="1" /><rect x="48.5" y="108.5" width="395" height="47" fill="#ffffff" stroke="#e8e8e3" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="127" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">02 Get</text><text x="62" y="144" fill="#6e6d64" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">finish\_reason is tool\_calls; arguments arrive as a JSON string.</text><line x1="444" y1="132" x2="458" y2="132" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="136" fill="#52524c" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user</text><rect x="23.5" y="191.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><line x1="29" y1="194" x2="48" y2="194" stroke="#e8e8e3" strokeWidth="1" /><rect x="48.5" y="170.5" width="395" height="47" fill="#ffffff" stroke="#e8e8e3" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="189" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">03 Push</text><text x="62" y="206" fill="#6e6d64" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">Then one tool message per call, each with its tool\_call\_id.</text><line x1="444" y1="194" x2="458" y2="194" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="198" fill="#52524c" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user, assistant, tool</text><rect x="23.5" y="253.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><line x1="29" y1="256" x2="48" y2="256" stroke="#e8e8e3" strokeWidth="1" /><rect x="48.5" y="232.5" width="395" height="47" fill="#ffffff" stroke="#e8e8e3" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="251" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">04 Send</text><text x="62" y="268" fill="#6e6d64" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The model now answers, or asks for another tool.</text><line x1="444" y1="256" x2="458" y2="256" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="260" fill="#52524c" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user, assistant, tool, ...</text><text x="124" y="65" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">Your messages, plus tools</text><text x="124" y="127" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">An assistant message with tool\_calls</text><text x="124" y="189" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">Push the whole assistant message back</text><text x="124" y="251" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">The grown array, same call</text><rect x="23.5" y="307.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><line x1="29" y1="310" x2="48" y2="310" stroke="#e8e8e3" strokeWidth="1" /><rect x="48" y="295" width="110" height="3" fill="#76b900" shapeRendering="crispEdges" /><rect x="48" y="298" width="120" height="24" fill="#76b900" shapeRendering="crispEdges" /><rect x="58" y="322" width="110" height="3" fill="#76b900" shapeRendering="crispEdges" /><text x="108" y="314" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12" fontWeight="500" textAnchor="middle" letterSpacing="-0.12">Final answer</text><text x="184" y="314" fill="#6e6d64" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The turn with no tool\_calls is the one you show the user.</text><line x1="24" y1="344" x2="696" y2="344" stroke="#e8e8e3" strokeWidth="1" strokeDasharray="3 3" /><rect x="21.5" y="341.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><rect x="693.5" y="341.5" width="5" height="5" fill="#bbb9b1" shapeRendering="crispEdges" /><rect x="24" y="360" width="5" height="5" fill="#b95f5f" shapeRendering="crispEdges" /><text x="36" y="368" fill="#52524c" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">Dropping the assistant turn at step 03 breaks the state: the tool result then has nothing to attach to.</text></svg>
</div>

<div className="hidden dark:block">
  <svg viewBox="0 0 720 384" width="100%" role="img" aria-label="One tool-calling turn: you send messages plus tools, the model returns an assistant message carrying tool_calls, you push that whole assistant message and then one tool message per call, then send the grown array again; the turn with no tool_calls is the final answer." fill="none" xmlns="http://www.w3.org/2000/svg"><text x="24" y="16" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">One tool turn</text><text x="696" y="16" fill="#9a998e" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0" textAnchor="end">repeat until tool\_calls is absent</text><text x="470" y="36" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">Messages you hold</text><line x1="26" y1="40" x2="26" y2="306" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><rect x="23.5" y="67.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><line x1="29" y1="70" x2="48" y2="70" stroke="#383833" strokeWidth="1" /><rect x="48.5" y="46.5" width="395" height="47" fill="#161614" stroke="#383833" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="65" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">01 Send</text><text x="62" y="82" fill="#9a998e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The model sees every tool description you registered.</text><line x1="444" y1="70" x2="458" y2="70" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="74" fill="#c8c7ba" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user</text><rect x="23.5" y="129.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><line x1="29" y1="132" x2="48" y2="132" stroke="#383833" strokeWidth="1" /><rect x="48.5" y="108.5" width="395" height="47" fill="#161614" stroke="#383833" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="127" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">02 Get</text><text x="62" y="144" fill="#9a998e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">finish\_reason is tool\_calls; arguments arrive as a JSON string.</text><line x1="444" y1="132" x2="458" y2="132" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="136" fill="#c8c7ba" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user</text><rect x="23.5" y="191.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><line x1="29" y1="194" x2="48" y2="194" stroke="#383833" strokeWidth="1" /><rect x="48.5" y="170.5" width="395" height="47" fill="#161614" stroke="#383833" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="189" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">03 Push</text><text x="62" y="206" fill="#9a998e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">Then one tool message per call, each with its tool\_call\_id.</text><line x1="444" y1="194" x2="458" y2="194" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="198" fill="#c8c7ba" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user, assistant, tool</text><rect x="23.5" y="253.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><line x1="29" y1="256" x2="48" y2="256" stroke="#383833" strokeWidth="1" /><rect x="48.5" y="232.5" width="395" height="47" fill="#161614" stroke="#383833" strokeWidth="1" shapeRendering="crispEdges" /><text x="62" y="251" fill="#6e6d64" fontFamily="Consolas, Menlo, monospace" fontSize="9" fontWeight="500" letterSpacing="0.3">04 Send</text><text x="62" y="268" fill="#9a998e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The model now answers, or asks for another tool.</text><line x1="444" y1="256" x2="458" y2="256" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><text x="470" y="260" fill="#c8c7ba" fontFamily="Consolas, Menlo, monospace" fontSize="10.5" letterSpacing="0">user, assistant, tool, ...</text><text x="124" y="65" fill="#f0efe2" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">Your messages, plus tools</text><text x="124" y="127" fill="#f0efe2" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">An assistant message with tool\_calls</text><text x="124" y="189" fill="#f0efe2" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">Push the whole assistant message back</text><text x="124" y="251" fill="#f0efe2" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12.5" fontWeight="500" letterSpacing="-0.12">The grown array, same call</text><rect x="23.5" y="307.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><line x1="29" y1="310" x2="48" y2="310" stroke="#383833" strokeWidth="1" /><rect x="48" y="295" width="110" height="3" fill="#76b900" shapeRendering="crispEdges" /><rect x="48" y="298" width="120" height="24" fill="#76b900" shapeRendering="crispEdges" /><rect x="58" y="322" width="110" height="3" fill="#76b900" shapeRendering="crispEdges" /><text x="108" y="314" fill="#0f0f0e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="12" fontWeight="500" textAnchor="middle" letterSpacing="-0.12">Final answer</text><text x="184" y="314" fill="#9a998e" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">The turn with no tool\_calls is the one you show the user.</text><line x1="24" y1="344" x2="696" y2="344" stroke="#383833" strokeWidth="1" strokeDasharray="3 3" /><rect x="21.5" y="341.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><rect x="693.5" y="341.5" width="5" height="5" fill="#6e6d64" shapeRendering="crispEdges" /><rect x="24" y="360" width="5" height="5" fill="#c76f6f" shapeRendering="crispEdges" /><text x="36" y="368" fill="#c8c7ba" fontFamily="'Helvetica Neue', Helvetica, Arial, sans-serif" fontSize="10.5" letterSpacing="0">Dropping the assistant turn at step 03 breaks the state: the tool result then has nothing to attach to.</text></svg>
</div>

## What to tune

| Parameter | Effect |
| - | - |
| `tool_choice: "auto"` | The model chooses when to call a tool. This is the default. |
| `tool_choice: "required"` | Force a tool call every turn, no free-form answer. Refused with `400` on `nemotron-3-5-lightning-30b`; force a named tool there instead. |
| `tool_choice: {type:"function", function:{name:"..."}}` | Force one specific tool. |
| `parallel_tool_calls: false` | Ask for one call at a time. Whether it is honored depends on the model. |

## Common mistakes

* **Dropping the assistant turn.** Push the whole assistant message, `tool_calls` and all, before you push any tool result. Without it the tool message has nothing to attach to.
* **Returning a non-string tool result.** The `content` on a tool-role message must be a string. Always serialize it.
* **A vague `description`.** Write it as though the model has never seen your API: what the tool does, what each argument means, what a good input looks like. This is the single biggest lever on whether a tool gets called at all.
* **No turn limit.** Bound the loop. Ten turns covers almost every pattern, and an unbounded loop can spend real money.
* **Trusting the arguments.** The model can invent a field or omit a required one. Validate with Pydantic or Zod before you execute.

## Next steps

<Columns cols={3}>
  <Card title="Structured output" icon="braces" href="/docs/cookbook/structured-output">
    When you want JSON back without a tool loop.
  </Card>

  <Card title="Streaming" icon="zap" href="/docs/cookbook/streaming">
    Stream the assistant turn as it arrives.
  </Card>

  <Card title="Chat completions" icon="square-terminal" href="/docs/api-reference/chat-completions">
    The full request contract.
  </Card>
</Columns>
