# Tool calling

> OpenAI Chat & Responses function tools, Anthropic tool use and Gemini function calling side by side, plus which models support the built-in tools

来源：https://zhonkemodel.dflop.top/en/docs/guides/tool-calling

> Let the model decide **when** to run your code — check the weather, query a database, call another API, drive some software…

All four chat protocol endpoints (OpenAI Chat, OpenAI Responses, Anthropic Messages and Gemini Native) support function/tool calling, but **the field names and schema shapes differ**. This page lines them up and gives the practices that work.

## Concepts side by side

| Concept | OpenAI Chat | Anthropic | Gemini |
|---|---|---|---|
| Tool list field | `tools` | `tools` | `tools` |
| Per-tool wrapper | `{type:"function","function":{...}}` | bare `{...}` (no wrapper) | `{functionDeclarations:[...]}` |
| Parameter schema field | `function.parameters` | `input_schema` | `parameters` |
| Tool-choice policy | `tool_choice` | `tool_choice` | `toolConfig.functionCallingConfig` |
| How the model's call comes back | `message.tool_calls[i]` | `content[i] type:tool_use` | `parts[i].functionCall` |
| How you return the result | `role:"tool"` + `tool_call_id` | `content type:tool_result` + `tool_use_id` | `parts[i].functionResponse` |

## OpenAI Chat —— Function Tools

### 1. Define the tool

```python
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get the current weather in a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
                "unit": {"type": "string", "enum": ["c", "f"], "default": "c"},
            },
            "required": ["city"],
        },
    },
}]
```

### 2. Make the call

```python
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://zhonkezhonkeapi.dflop.top/v1")

messages = [{"role": "user", "content": "Weather in Tokyo?"}]
resp = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=messages,
    tools=tools,
)
```

### 3. Handle the model's tool call

```python
import json

choice = resp.choices[0]
if choice.finish_reason == "tool_calls":
    # append the assistant message (with all its tool_calls) exactly once.
    # putting it inside the loop produces an illegal [assistant, tool, assistant, tool]
    # sequence on multi-tool calls; the protocol requires every tool result to follow
    messages.append(choice.message)

    for tc in choice.message.tool_calls:
        args = json.loads(tc.function.arguments)
        result = get_weather(**args)  # your real function

        # one role:"tool" result per tool_call
        messages.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps(result),
        })

    # send another round to get the final answer
    final = client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=messages,
        tools=tools,
    )
    print(final.choices[0].message.content)
```

## Anthropic Messages —— Tool Use

### 1. Define the tool

```python
tools = [{
    "name": "get_weather",
    "description": "Get the current weather in a city",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string"},
            "unit": {"type": "string", "enum": ["c", "f"]},
        },
        "required": ["city"],
    },
}]
```

### 2. Make the call

```python
from anthropic import Anthropic
client = Anthropic(api_key="sk-gpushare-xxx", base_url="https://zhonkezhonkeapi.dflop.top")

messages = [{"role": "user", "content": "Weather in Tokyo?"}]
resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)
```

### 3. Handle the model's tool call

```python
import json

if resp.stop_reason == "tool_use":
    tool_blocks = [b for b in resp.content if b.type == "tool_use"]
    tool_results = []
    for tb in tool_blocks:
        result = get_weather(**tb.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": tb.id,
            "content": json.dumps(result),
        })

    messages.append({"role": "assistant", "content": resp.content})
    messages.append({"role": "user", "content": tool_results})

    final = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
```

## Gemini Native —— Function Calling

### 1. Define the tool

```python
from google.genai import types

weather_tool = types.Tool(function_declarations=[
    types.FunctionDeclaration(
        name="get_weather",
        description="Get the current weather in a city",
        parameters={
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "unit": {"type": "string", "enum": ["c", "f"]},
            },
            "required": ["city"],
        },
    )
])
```

### 2. Make the call

```python
from google import genai
client = genai.Client(api_key="sk-gpushare-xxx", http_options={"base_url": "https://zhonkezhonkeapi.dflop.top"})

response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents="Weather in Tokyo?",
    config=types.GenerateContentConfig(tools=[weather_tool]),
)
```

### 3. Handle the model's tool call

```python
candidate = response.candidates[0]
function_calls = [p.function_call for p in candidate.content.parts if p.function_call]

if function_calls:
    # second-round contents: the original question + the model's functionCall turn (verbatim) + functionResponse parts
    contents = [
        types.Content(role="user", parts=[types.Part.from_text(text="Weather in Tokyo?")]),
        candidate.content,  # the model's previous turn (with the functionCall part) must be sent back too
    ]
    response_parts = [
        types.Part.from_function_response(
            name=fc.name,
            response={"result": get_weather(**dict(fc.args))},
        )
        for fc in function_calls
    ]
    contents.append(types.Content(role="user", parts=response_parts))

    final = client.models.generate_content(
        model="gemini-2.5-pro",
        contents=contents,
        config=types.GenerateContentConfig(tools=[weather_tool]),
    )
    print(final.text)
```

> The two mistakes people make with Gemini: (1) `functionResponse` must be wrapped with `types.Part.from_function_response`, and its `response` field is a dict; (2) the model's previous `functionCall` content must go back into `contents` verbatim, or the model has no idea what it called. The Gemini Native channel is a byte pass-through, so the shape of what you send back is entirely your responsibility.

## OpenAI Responses (`/v1/responses`) —— Function Tools

Responses-API clients such as Codex CLI use `POST /v1/responses`, where function tools also work but **the field shapes differ from Chat Completions**:

- Tool definitions have **no `function` wrapper** — `name`, `description` and `parameters` sit flat
- The model's call comes back as a `function_call` output item (not `message.tool_calls`)
- Results go back as a `function_call_output` input item (not a `role:"tool"` message)

```python
import json
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://zhonkezhonkeapi.dflop.top/v1")

tools = [{
    "type": "function",
    "name": "get_weather",          # flat, no function:{...} wrapper
    "description": "Get the current weather in a city",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

resp = client.responses.create(model="gpt-5.5", input="Weather in Tokyo?", tools=tools)

input_items = [{"role": "user", "content": "Weather in Tokyo?"}]
for item in resp.output:
    if item.type == "function_call":
        result = get_weather(**json.loads(item.arguments))
        input_items.append(item)  # put the model's function_call item back verbatim
        input_items.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(result),
        })

final = client.responses.create(model="gpt-5.5", input=input_items, tools=tools)
print(final.output_text)
```

## Tool calling across vendors

The protocol translation layer lets you **call any model with any SDK**, and tools work across vendors too:

```python
# tool calling on Claude, from the OpenAI SDK
from openai import OpenAI
client = OpenAI(api_key="sk-gpushare-xxx", base_url="https://zhonkezhonkeapi.dflop.top/v1")

resp = client.chat.completions.create(
    model="claude-sonnet-4-6",  # Claude goes through T1 translation
    messages=[{"role": "user", "content": "Weather in Tokyo?"}],
    tools=tools,  # the same tools definition as under "OpenAI Chat — 1. Define the tool"
)
# resp.choices[0].message.tool_calls is identical to plain OpenAI
```

A translated path adds `X-Protocol-Translation: openai_chat_to_anthropic_messages` to the response headers so you can tell which route you took. If a capability was degraded or dropped in translation (a restricted `cache_control`, say), an `X-Protocol-Warning` header spells out what — check it first when debugging "why didn't my tool fire". Note that when routing picks a **native protocol channel** neither header appears; their absence is normal and doesn't indicate a problem.

## Advanced

### Forcing a tool call

```python
# OpenAI: tool_choice names a specific tool
tool_choice = {"type": "function", "function": {"name": "get_weather"}}

# Anthropic: tool_choice requires a tool to be used
tool_choice = {"type": "tool", "name": "get_weather"}

# Gemini: function_calling_config mode = ANY
config = types.GenerateContentConfig(
    tool_config=types.ToolConfig(
        function_calling_config=types.FunctionCallingConfig(mode="ANY")
    )
)
```

### Multiple tools

Every protocol's SDK accepts several tools in one request, and the model may return several calls at once:

```python
# a single OpenAI response can carry multiple tool_calls
for tc in resp.choices[0].message.tool_calls:
    handle(tc)
```

### Tools while streaming

Tool calling **does** work with `stream=true`. The `delta.tool_calls` in incremental chunks accumulate field by field (`function.arguments` arrives in slices), so the client must concatenate every chunk to get complete JSON.

## Things to know

1. **Tool schemas are validated strictly** — `parameters` must be valid JSON Schema, and the model generates arguments against it
2. **Arguments are a string** (OpenAI) or **an object** (Anthropic / Gemini) — parse accordingly
3. **Don't return long base64 images in tool results** — they count as input tokens on the next turn and the bill explodes
4. **The built-in tools** (`web_search` / `image_generation`) need no schema from you, but **require `stream=true`** (see [Streaming](./streaming.md)) and are **only available on some models** — check the capability columns in [Models](../reference/models.md):
   - `web_search`: supported on GPT (`gpt-5.5`), Claude (translated to `web_search_20250305`) and the Gemini line (translated to `googleSearch`); GLM, DeepSeek, Grok and others don't support it by default
   - `image_generation` (drawing inside a conversation): `gpt-5.5` only. For standalone image generation use the per-image `/v1/images/generations` — see [Image / video / music APIs](../reference/media-apis.md)
5. **Unsupported tool types always 400** — a built-in tool on a model or channel that doesn't support it returns 400 `tool_not_supported`, and tool types other than `function`, `web_search` and `image_generation` (such as `file_search` or `code_interpreter` carried over from OpenAI) are rejected with 400 and a message like `Tool type 'file_search' is not supported`. Full list: [Error codes](../reference/errors.md)
