Инструменты
Вызов функций на трёх диалектах шлюза — как объявить инструмент, как выглядит круг «вызов — результат» и почему поддержка инструментов подтверждается отдельно для каждой модели.
Быстро
Объявите инструмент в tools, и модель сможет попросить вызвать его вместо того, чтобы отвечать текстом. Описание — единственное, что говорит модели, когда его вызывать.
{
"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
}
}
}
]
}parameters — это ваша собственная схема JSON Schema, на всех трёх диалектах. Её словарь — словарь JSON Schema, и здесь он не сужается: вложенные объекты, массивы с items, enum, const, anyOf, format, pattern, числовые границы и $schema доезжают до модели ровно такими, какими вы их написали. Проверяются две вещи: что значение — объект JSON и что в закодированном виде оно не больше 65536 байт. Всё остальное — между вами и моделью: схему, которая не понравилась поставщику, отвергнет сам поставщик, и вы узнаете, что запрос не прошёл у него.
Круг целиком
Круг состоит из трёх ходов, и вся его механика — в том, что вы переигрываете обратно то, что модель сказала.
- Вы спрашиваетеОбычный запрос, к которому добавлены `tools`.
- Модель просит вызватьОтвет приходит с `finish_reason: "tool_calls"`, `content` равен `null`, а в `message.tool_calls` лежат вызовы: у каждого свой `id`, имя и `arguments` текстом JSON.
- Вы отвечаете результатомШлёте разговор заново: тот же ход ассистента с его `tool_calls`, а следом ход с ролью `tool`, который несёт `tool_call_id` того вызова и результат в `content`.
{
"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"
}
]
}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 и отвергается на любом другом: поставщик сопоставляет результаты вызовам по нему, а не по позиции.
Как заставить вызвать
tool_choice говорит, что запрос требует от своего списка инструментов, и Chat Completions и Responses принимают обе формы, которые определяет для него протокол. Голая строка — это один из трёх режимов: auto оставляет выбор модели, none запрещает вызов, оставляя объявления видимыми для неё, required требует вызова какого-либо объявленного инструмента. Объектная форма требует вызова одного названного инструмента, и это имя должно быть объявлено тем же запросом. required без объявленных инструментов отвергается: такой запрос не может удовлетворить ни один ответ. Чтобы оставить выбор модели, пропустите член — ровно об этом и просит auto.
{ "tool_choice": { "type": "function", "function": { "name": "get_weather" } } }Messages дополнительно принимает нативные объектные режимы из таблицы. Это инструкции запроса, а не игнорируемые поля совместимости.
{"type": "auto"}Модель выбирает, вызывать ли инструмент.{"type": "any"}Требуется вызов любого объявленного инструмента.{"type": "none"}Вызов запрещён, но объявления инструментов остаются видимыми.{"type": "tool", "name": "get_weather"}Требуется вызов этого объявленного инструмента.name обязателен только у tool и отвергается в остальных трёх режимах. Отсутствующий tool_choice не добавляется автоматически. Проверки возможностей модели продолжают действовать.
То же на других диалектах
| Что | 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 принимается и не пересылается, а у двух других диалектов такого члена нет вовсе. Каждое семейство моделей решает это за себя, поэтому пишите код так, чтобы он одинаково хорошо переживал один вызов и несколько.
Поддержка инструментов подтверждается отдельно для каждой модели
Поддержка инструментов — доказанная возможность точной пары модели и канала, а не свойство протокола. Каталог держит для каждой такой пары состояние tools_status: proven, unproven или unsupported. Запрос с tools не поедет к паре, которая инструменты не доказала, — он отвергается до обращения наверх, и «не подтверждено» здесь никогда не значит «наверное, поддерживает». Что доказано у конкретной модели, видно на её карточке в каталоге.