Tool calling: как нейросеть вызывает ваши функции
7 июля 2026 г. · Команда Hubris · 9 мин чтения
Tool calling (вызов инструментов) — это механизм, при котором нейросеть не исполняет код сама, а возвращает структурированный запрос: какую из ваших функций вызвать и с какими аргументами. Вы исполняете функцию на своей стороне, отправляете результат обратно — и модель формирует финальный ответ уже с учётом реальных данных. Так языковая модель получает доступ к вашим базам, внешним сервисам и действиям.
Зачем нейросети ваши функции
Модель знает только то, что попало в её обучающие данные. Она не в курсе сегодняшнего курса доллара, погоды за окном и тем более содержимого вашей CRM. Tool calling закрывает этот разрыв: вы описываете, какие функции у вас есть, а модель решает, когда и с какими аргументами их позвать.
Что это даёт на практике:
- Свежие данные — курсы валют, погода, котировки, статусы доставки;
- Приватные данные — заказы, остатки на складе, история клиента из вашей БД;
- Действия — отправить письмо, создать задачу, изменить запись;
- Точные вычисления — модель плохо считает «в уме», калькулятор-функция считает точно;
- Агентные сценарии — цепочки вызовов, на которых строятся ассистенты для работы с кодом и автономные агенты.
Важно понимать: модель никогда не исполняет код. Она только формирует заявку на вызов. Исполнение, проверка аргументов и ответственность за результат — на вашей стороне. Это безопасно по построению: без вашего кода ничего не произойдёт.
Схема tools: контракт между моделью и кодом
Функции описываются в параметре tools запроса к /v1/chat/completions — это массив объектов с JSON Schema. По сути, вы передаёте модели контракт: имя функции, человекочитаемое описание и типизированные параметры.
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Возвращает текущую погоду в указанном городе.",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "Название города" },
"units": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
}
Три правила, от которых зависит качество вызовов:
description— это промпт. Модель выбирает функцию и заполняет аргументы, опираясь на описания. «Возвращает погоду» хуже, чем «Возвращает текущую погоду в указанном городе; город — по-русски, в именительном падеже».requiredобязателен. Поля вне спискаrequiredмодель может опустить — ваш код должен иметь значения по умолчанию.enumсужает свободу. Там, где допустим фиксированный набор значений, перечислите его явно — меньше шансов получить «celsius (по Цельсию)» вместоcelsius.
Полная схема запроса и ответа — в справочнике POST /v1/chat/completions.
Цикл вызова: запрос → исполнение → результат
Один «раунд» работы с инструментом всегда состоит из пяти шагов:
- Вы отправляете запрос с
messagesиtools. - Модель решает: ответить текстом или попросить вызов. Во втором случае приходит
finish_reason: "tool_calls"и массивtool_calls[]в assistant-сообщении — у каждого вызова естьid, имя функции и строкаargumentsс JSON-аргументами. - Вы исполняете функции у себя.
- Отправляете новый запрос: к истории добавляете то самое assistant-сообщение с
tool_callsи по одному сообщениюrole: "tool"на каждый вызов — с результатом вcontentи обязательнымtool_call_id, равнымidвызова. - Модель формирует финальный ответ для пользователя.
Модель может попросить несколько функций за один шаг (параллельные вызовы) — тогда в tool_calls[] придёт несколько элементов, и на каждый нужен свой role: "tool" ответ. Если цепочка длинная, шаги 2–4 повторяются, пока модель не ответит текстом. Примеры на чистом curl, поведение tool_choice и разбор стриминга — в документации по вызову инструментов.
Полный пример на Python: погода и курс валют
Используем официальный пакет openai — меняется только base_url. Ключ вида sk-gw-... создаётся в личном кабинете: регистрация на hubris.pw/sign-in по коду из письма, далее раздел «Ключи»; баланс пополняется в разделе «Биллинг» — СБП, карта или счёт для юрлиц.
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...", # ваш ключ из раздела «Ключи»
)
def get_weather(city: str, units: str = "celsius") -> dict:
# Здесь обычно запрос к погодному сервису; для примера — заглушка.
return {"city": city, "temperature": -3, "conditions": "снег", "units": units}
def get_fx_rate(base: str, quote: str) -> dict:
# Здесь обычно запрос к источнику курсов, например ЦБ РФ.
return {"pair": f"{base}/{quote}", "rate": 91.0}
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Возвращает текущую погоду в указанном городе.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Название города"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
},
{
"type": "function",
"function": {
"name": "get_fx_rate",
"description": "Возвращает курс валютной пары, например USD/RUB.",
"parameters": {
"type": "object",
"properties": {
"base": {"type": "string", "description": "Базовая валюта, код ISO"},
"quote": {"type": "string", "description": "Котируемая валюта, код ISO"},
},
"required": ["base", "quote"],
},
},
},
]
REGISTRY = {"get_weather": get_weather, "get_fx_rate": get_fx_rate}
messages = [
{"role": "user", "content": "Какая погода в Москве и сколько сейчас стоит доллар в рублях?"}
]
for _ in range(5): # предохранитель от бесконечного цикла вызовов
resp = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
messages=messages,
tools=TOOLS, # tools передаём на каждом шаге, не только на первом
)
msg = resp.choices[0].message
if not msg.tool_calls:
print(msg.content) # финальный ответ модели
break
messages.append(msg) # assistant-сообщение с tool_calls — обязательно в историю
for call in msg.tool_calls:
fn = REGISTRY.get(call.function.name)
try:
args = json.loads(call.function.arguments)
result = fn(**args) if fn else {"error": "неизвестная функция"}
except (json.JSONDecodeError, TypeError) as exc:
result = {"error": f"некорректные аргументы: {exc}"}
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
Скрипт обрабатывает оба вызова за один раунд (модель попросит погоду и курс параллельно) и печатает связный ответ. Модель anthropic/claude-haiku-4.5 хорошо подходит для таких задач: быстрая, уверенно работает с инструментами, вход — 106,11 ₽, выход — 530,53 ₽ за миллион токенов (цены актуальны на июнь 2026 года).
Типичные грабли: валидация аргументов и не только
Большинство ошибок при работе с tool calling — на стороне клиентского кода:
arguments— это строка, а не объект. Её нужно разбирать черезjson.loads, и она бывает синтаксически битой. Всегда оборачивайте разбор вtry/exceptи возвращайте модели понятную ошибку — она исправится на следующем шаге.- Аргументы приходят от модели, а не от вас. Модель может выдумать поле, перепутать тип или подставить значение вне
enum. Проверяйте структуру (например, через Pydantic) до того, как передать аргументы в реальную функцию. - Аргументам нельзя доверять как безопасным. Никаких подстановок в SQL-строки, путей к файлам без проверки и тем более
eval. Относитесь к ним как к пользовательскому вводу. - Потерянный
tool_call_id. Каждое сообщениеrole: "tool"обязано ссылаться наidконкретного вызова — иначе запрос завершится ошибкой. toolsзабыли передать во втором запросе. Описания инструментов нужны модели на каждом шаге диалога.- Нет ограничителя цикла. Модель может звать функции снова и снова; лимит итераций (как
range(5)в примере) защищает и от зацикливания, и от незапланированных расходов. - Раздутые описания. Описания инструментов входят в
prompt_tokensна каждом шаге: десять функций по 200 токенов — это плюс 2000 токенов к каждому запросу. Передавайте только те инструменты, что нужны в текущем сценарии.
Частые вопросы
Какие модели поддерживают tool calling?
Большинство современных: Claude (Haiku, Sonnet, Opus), GPT-4o и новее, Gemini, Llama 3.1+, Mistral. В каталоге моделей на карточке каждой модели указана поддержка параметра tools — проверяйте перед выбором.
Сколько стоит tool calling?
Отдельного тарифа нет: списываются обычные токены за каждый шаг диалога. Описания инструментов и сообщения role: "tool" считаются как часть промпта, сгенерированный tool_calls[] — как ответ. Итоговая цена зависит от модели и объёма диалога.
Может ли модель вызвать несколько функций одновременно?
Да, по умолчанию параллельные вызовы включены: в tool_calls[] придёт несколько элементов, на каждый нужен отдельный role: "tool" ответ. Отключаются параметром "parallel_tool_calls": false — тогда модель будет звать функции строго по одной.
Чем tool calling отличается от структурированного вывода?
Tool calling — про действия: модель просит ваш код что-то выполнить и ждёт результат. Структурированный вывод — про формат: модель сразу отвечает JSON-объектом по заданной схеме, без обратного вызова. Если вам нужен только типизированный ответ — берите второй вариант, он проще.
Все модели из статьи доступны в Hubris — единый API, оплата в рублях.