---
title: Инструменты
description: Вызов функций на трёх диалектах шлюза — как объявить инструмент, как выглядит круг «вызов — результат» и почему поддержка инструментов подтверждается отдельно для каждой модели.
keywords: инструменты, function calling, tools, tool_choice, tool_use, function_call
group: gateway
---

## Быстро {#quick keywords="tools, объявление, function, parameters"}

Объявите инструмент в `tools`, и модель сможет попросить вызвать его вместо того, чтобы отвечать текстом. Описание — единственное, что говорит модели, когда его вызывать.

```json title=Объявление
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Возвращает текущую погоду в названном городе.",
        "parameters": {
          "type": "object",
          "properties": {
            "city": { "type": "string", "description": "Город, например Казань." },
            "units": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }
  ]
}
```

:::note
`parameters` — это **ваша собственная схема JSON Schema**, на всех трёх диалектах. Её словарь — словарь JSON Schema, и здесь он не сужается: вложенные объекты, массивы с `items`, `enum`, `const`, `anyOf`, `format`, `pattern`, числовые границы и `$schema` доезжают до модели ровно такими, какими вы их написали. Проверяются две вещи: что значение — объект JSON и что в закодированном виде оно не больше 65536 байт. Всё остальное — между вами и моделью: схему, которая не понравилась поставщику, отвергнет сам поставщик, и вы узнаете, что запрос не прошёл у него.
:::

## Круг целиком {#round keywords="tool_calls, tool_call_id, роль tool, результат"}

Круг состоит из трёх ходов, и вся его механика — в том, что вы переигрываете обратно то, что модель сказала.

:::steps
- **Вы спрашиваете** — Обычный запрос, к которому добавлены `tools`.
- **Модель просит вызвать** — Ответ приходит с `finish_reason: "tool_calls"`, `content` равен `null`, а в `message.tool_calls` лежат вызовы: у каждого свой `id`, имя и `arguments` текстом JSON.
- **Вы отвечаете результатом** — Шлёте разговор заново: тот же ход ассистента с его `tool_calls`, а следом ход с ролью `tool`, который несёт `tool_call_id` того вызова и результат в `content`.
:::

```json title=Ответ
{
  "id": "chatcmpl-2b90c1f7ad",
  "object": "chat.completion",
  "created": 1756900000,
  "model": "<model>",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_a1",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Казань\",\"units\":\"celsius\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}
```

```python title=Python
import json
import os

from openai import OpenAI

client = OpenAI(base_url="https://api.kumorouter.com/v1", api_key=os.environ["KUMO_API_KEY"])

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Возвращает текущую погоду в названном городе.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "Город, например Казань."},
                    "units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["city"],
                "additionalProperties": False,
            },
        },
    }
]

messages = [{"role": "user", "content": "Какая сейчас погода в Казани?"}]

first = client.chat.completions.create(
    model="<model>", max_tokens=256, tools=tools, messages=messages
)
call = first.choices[0].message.tool_calls[0]

# Здесь вы действительно ходите за погодой; вернуть нужно текст.
result = json.dumps({"temperature": 12, "units": "celsius"})

messages.append(
    {
        "role": "assistant",
        "content": None,
        "tool_calls": [
            {
                "id": call.id,
                "type": "function",
                "function": {"name": call.function.name, "arguments": call.function.arguments},
            }
        ],
    }
)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})

second = client.chat.completions.create(
    model="<model>", max_tokens=256, tools=tools, messages=messages
)
print(second.choices[0].message.content)
```

`arguments` приходят текстом, а не разобранным объектом: их форму задаёт ваша собственная схема, поэтому границу они пересекают как текст. Разбирайте их сами и проверяйте до того, как что-то выполните.

`tool_call_id` обязателен на ходе `tool` и отвергается на любом другом: поставщик сопоставляет результаты вызовам по нему, а не по позиции.

## Как заставить вызвать {#choice keywords="tool_choice, требование, названный инструмент"}

`tool_choice` говорит, что запрос требует от своего списка инструментов, и Chat Completions и Responses принимают обе формы, которые определяет для него протокол. Голая строка — это один из трёх режимов: `auto` оставляет выбор модели, `none` запрещает вызов, оставляя объявления видимыми для неё, `required` требует вызова какого-либо объявленного инструмента. Объектная форма требует вызова одного названного инструмента, и это имя должно быть объявлено тем же запросом. `required` без объявленных инструментов отвергается: такой запрос не может удовлетворить ни один ответ. Чтобы оставить выбор модели, пропустите член — ровно об этом и просит `auto`.

```json title=Требование
{ "tool_choice": { "type": "function", "function": { "name": "get_weather" } } }
```

Messages дополнительно принимает нативные объектные режимы из таблицы. Это инструкции запроса, а не игнорируемые поля совместимости.

| Нативный `tool_choice` | Значение |
| --- | --- |
| `{"type": "auto"}` | Модель выбирает, вызывать ли инструмент. |
| `{"type": "any"}` | Требуется вызов любого объявленного инструмента. |
| `{"type": "none"}` | Вызов запрещён, но объявления инструментов остаются видимыми. |
| `{"type": "tool", "name": "get_weather"}` | Требуется вызов этого объявленного инструмента. |

`name` обязателен только у `tool` и отвергается в остальных трёх режимах. Отсутствующий `tool_choice` не добавляется автоматически. Проверки возможностей модели продолжают действовать.

## То же на других диалектах {#dialects keywords="responses, messages, input_schema, function_call"}

:::matrix
| Что | Chat Completions | Responses | Messages |
| --- | --- | --- | --- |
| Объявление | `tools[].function` с `parameters` | плоско: `type`, `name`, `parameters` | `name` и `input_schema` |
| Схема параметров | ваша собственная схема JSON Schema | ваша собственная схема JSON Schema | ваша собственная схема JSON Schema |
| Требование вызова | `"auto"`, `"none"`, `"required"` либо `tool_choice.type: "function"` с именем внутри `function` | те же три строки либо `tool_choice.type: "function"` с именем рядом | `tool_choice.type: "tool"`, имя рядом |
| Вызов в ответе | `message.tool_calls` | элемент `function_call` | блок `tool_use` |
| Результат обратно | ход с ролью `tool` и `tool_call_id` | элемент `function_call_output` с `call_id` | блок `tool_result` с `tool_use_id` |
:::

На диалекте Messages аргументы вызова приходят объектом в `input`, а не текстом; в потоке они собираются из фрагментов `input_json_delta`. У объявления Responses есть член `strict` — он принимается и не пересылается: строгие схемы эта поверхность доказывает только для структурированного вывода.

Параллельные вызовы платформа не обещает нигде: у Responses член `parallel_tool_calls` принимается и не пересылается, а у двух других диалектов такого члена нет вовсе. Каждое семейство моделей решает это за себя, поэтому пишите код так, чтобы он одинаково хорошо переживал один вызов и несколько.

## Поддержка инструментов подтверждается отдельно для каждой модели {#proof keywords="каталог, tools_status, proven, unproven"}

:::note
Поддержка инструментов — доказанная возможность точной пары модели и канала, а не свойство протокола. Каталог держит для каждой такой пары состояние `tools_status`: `proven`, `unproven` или `unsupported`. Запрос с `tools` не поедет к паре, которая инструменты не доказала, — он отвергается до обращения наверх, и «не подтверждено» здесь никогда не значит «наверное, поддерживает». Что доказано у конкретной модели, видно на её карточке в каталоге.
:::

## Что дальше {#next keywords="структурированный вывод, стриминг, ошибки"}

:::cards
- [Структурированный вывод](/ru/structured-output) — когда нужен не вызов, а ответ по схеме.
- [Стриминг](/ru/streaming) — как вызовы собираются из фрагментов в потоке.
- [Chat Completions](/ru/chat-completions) — члены запроса и форма ответа.
- [Messages](/ru/messages) — `input_schema` и блоки этого диалекта.
- [Каталог моделей](/ru/models) — какая модель что доказала.
:::
