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

# Tool calling

> Let the model decide when to call functions you define. The standard tool-use round-trip.

Tools turn a chat from "the model writes you a sentence" into "the model can do things." You hand it a list of functions it's allowed to call. It decides if and when to call them, you run the actual code, and you feed the result back into the conversation.

Opper's tool calling uses the standard OpenAI-compatible `tools` array, so anything you've written against OpenAI, Anthropic, or other compat SDKs works unchanged. It's available across all 300+ models.

## The round trip

A tool call always follows the same shape:

1. You send a chat completion request with a `tools` list.
2. The model responds with a `tool_use` (it wants to call one).
3. You run the tool in your code.
4. You send the result back to the model as a `tool_result` message.
5. The model uses the result to write its final response.

## A working example

A weather assistant.

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

  client = OpenAI(
      base_url="https://api.opper.ai/v3/compat",
      api_key=os.environ["OPPER_API_KEY"],
  )

  # 1. Declare the tool
  tools = [{
      "type": "function",
      "function": {
          "name": "get_weather",
          "description": "Get the current weather for a city.",
          "parameters": {
              "type": "object",
              "properties": {
                  "city": {"type": "string", "description": "City name, e.g. 'Stockholm'"}
              },
              "required": ["city"],
          },
      },
  }]

  # 2. Your local implementation
  def get_weather(city: str) -> str:
      return f"Sunny, 22°C in {city}"

  # 3. First call. Model decides to use the tool.
  messages = [{"role": "user", "content": "What's the weather in Stockholm?"}]
  r = client.chat.completions.create(
      model="openai/gpt-5-mini",
      messages=messages,
      tools=tools,
  )
  msg = r.choices[0].message
  messages.append(msg)

  # 4. Run the tool, append the result
  for call in (msg.tool_calls or []):
      args = json.loads(call.function.arguments)
      result = get_weather(**args)
      messages.append({
          "role": "tool",
          "tool_call_id": call.id,
          "content": result,
      })

  # 5. Second call. Model uses the result to answer.
  r = client.chat.completions.create(
      model="openai/gpt-5-mini",
      messages=messages,
      tools=tools,
  )
  print(r.choices[0].message.content)
  ```

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

  const client = new OpenAI({
      baseURL: "https://api.opper.ai/v3/compat",
      apiKey: process.env.OPPER_API_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", description: "City name, e.g. 'Stockholm'" },
              },
              required: ["city"],
          },
      },
  }];

  function getWeather(city: string) {
      return `Sunny, 22°C in ${city}`;
  }

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

  let r = await client.chat.completions.create({
      model: "openai/gpt-5-mini",
      messages,
      tools,
  });
  const msg = r.choices[0].message;
  messages.push(msg);

  for (const call of msg.tool_calls ?? []) {
      const args = JSON.parse(call.function.arguments);
      const result = getWeather(args.city);
      messages.push({
          role: "tool",
          tool_call_id: call.id,
          content: result,
      });
  }

  r = await client.chat.completions.create({
      model: "openai/gpt-5-mini",
      messages,
      tools,
  });
  console.log(r.choices[0].message.content);
  ```
</CodeGroup>

## Control when tools fire

The `tool_choice` parameter tells the model how aggressively to use tools.

| Value                                             | Behavior                                                                             |
| ------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `"auto"` *(default)*                              | The model decides. Calls a tool when it makes sense, otherwise answers in text.      |
| `"none"`                                          | Tools are ignored. The model has to answer from what it knows.                       |
| `"required"`                                      | The model must call one of the tools. Use when the answer can only come from a tool. |
| `{"type": "function", "function": {"name": "X"}}` | Force a specific tool.                                                               |

## Parallel calls

The model can ask for several tool calls in one turn. The `tool_calls` array on the response can have more than one entry. Run them all (in parallel if they're independent), then send back one `tool` message per call, each with its `tool_call_id`.

## Streaming tool arguments

When you stream a tool call with `stream: true`, the arguments arrive as JSON fragments in `delta.tool_calls[].function.arguments`. Concatenate them as they come in, then parse once the call is complete. Useful for showing "I'm calling search…" UI as the call assembles. See [Streaming](/build/gateway/streaming).

## Tools vs structured output

Tools and [structured output](/build/gateway/structured-output) look similar but do different things.

|                               | Tools                                      | Structured output                   |
| ----------------------------- | ------------------------------------------ | ----------------------------------- |
| **Goal**                      | Have the model trigger an action           | Get a single typed JSON object back |
| **Round-trips**               | At least two (call, result, follow-up)     | One                                 |
| **Multiple results per turn** | Yes, parallel calls allowed                | No, one object                      |
| **Best for**                  | Agents, search, data lookups, side effects | Extraction, classification, parsing |

If you have a clear input → output and don't need the model to drive control flow, reach for [structured output](/build/gateway/structured-output), not tools.

## What's next

<CardGroup cols={2}>
  <Card title="Server-side tools" icon="server" href="/build/gateway/server-tools">
    Provider-run tools — Anthropic web\_search, OpenAI code\_interpreter, Google grounding — no round-trip needed.
  </Card>

  <Card title="Conversations" icon="comments" href="/build/gateway/conversations">
    Multi-turn chat with message history.
  </Card>

  <Card title="Streaming" icon="bolt-lightning" href="/build/gateway/streaming">
    Stream tokens and tool arguments as they arrive.
  </Card>

  <Card title="Structured output" icon="braces" href="/build/gateway/structured-output">
    Get JSON back without the tool round-trip.
  </Card>
</CardGroup>
