Перейти к содержимомуKumoДокументация
Разделы
На странице
Шлюз

Инструменты

Вызов функций на трёх диалектах шлюза — как объявить инструмент, как выглядит круг «вызов — результат» и почему поддержка инструментов подтверждается отдельно для каждой модели.

Открыть как Markdown

Быстро

Объявите инструмент в 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 байт. Всё остальное — между вами и моделью: схему, которая не понравилась поставщику, отвергнет сам поставщик, и вы узнаете, что запрос не прошёл у него.

Круг целиком

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

  1. Вы спрашиваетеОбычный запрос, к которому добавлены `tools`.
  2. Модель просит вызватьОтвет приходит с `finish_reason: "tool_calls"`, `content` равен `null`, а в `message.tool_calls` лежат вызовы: у каждого свой `id`, имя и `arguments` текстом JSON.
  3. Вы отвечаете результатомШлёте разговор заново: тот же ход ассистента с его `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 CompletionsResponsesMessages
Объявлениеtools[].function с parametersплоско: type, name, parametersname и 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 не поедет к паре, которая инструменты не доказала, — он отвергается до обращения наверх, и «не подтверждено» здесь никогда не значит «наверное, поддерживает». Что доказано у конкретной модели, видно на её карточке в каталоге.

Что дальше