·6 мин. чтения

Function calling по-русски: инструменты в LLM API

Как работает function calling (tool calling) в LLM API: описание инструментов, цикл вызовов, tool_choice и формат Claude. Примеры для PlusVibe.

Function calling по-русски: инструменты в LLM API

Function calling (он же tool calling или tool use) — способ дать модели доступ к вашему коду. Вы описываете функции: «узнать статус заказа», «посчитать доставку», «найти клиента в CRM». Модель сама решает, какую вызвать и с какими аргументами, а ваш код её выполняет и возвращает результат. На этом механизме построены все агенты — от чат-бота магазина до Claude Code.

В PlusVibe function calling работает через стандартный /v1/chat/completions в формате OpenAI и через /v1/messages в формате Anthropic. Ниже — полный рабочий цикл, который мы прогнали на трёх моделях разных производителей.

Как это устроено

  1. Вы отправляете запрос с сообщениями и списком tools — у каждого инструмента есть имя, описание и JSON Schema параметров.
  2. Модель отвечает не текстом, а списком tool_calls: какую функцию вызвать и с какими аргументами. finish_reason в этом случае равен tool_calls.
  3. Ваш код выполняет функции и добавляет в историю сообщения с ролью tool — по одному на каждый вызов, с тем же tool_call_id.
  4. Вы отправляете историю снова. Модель либо просит ещё вызовов, либо отвечает пользователю текстом.

Важно: модель сама ничего не выполняет. Она только просит вызвать функцию. Что именно запускать и с какими правами, решает ваш код.

Полный пример: помощник интернет-магазина

Две функции — статус заказа и стоимость доставки. Пользователь спрашивает сразу о двух вещах, и модель вызывает обе функции в одном ответе.

import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://plusvibeapi.ru/v1",
    api_key=os.environ["PLUSVIBE_API_KEY"],
)
MODEL = "gpt-5.6-luna"

def get_order_status(order_id: str) -> dict:
    # здесь ваш запрос в базу или CRM
    return {"order_id": order_id, "status": "передан в доставку", "eta": "2026-10-02"}

def get_delivery_price(city: str) -> dict:
    return {"city": city, "price_rub": 390}

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_order_status",
            "description": "Статус заказа по его номеру",
            "parameters": {
                "type": "object",
                "properties": {"order_id": {"type": "string", "description": "Номер заказа, например A-1042"}},
                "required": ["order_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_delivery_price",
            "description": "Стоимость курьерской доставки в город",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    },
]
FUNCTIONS = {"get_order_status": get_order_status, "get_delivery_price": get_delivery_price}

messages = [
    {"role": "system", "content": "Ты помощник интернет-магазина. Отвечай кратко."},
    {"role": "user", "content": "Где мой заказ A-1042? И сколько стоит доставка в Тверь?"},
]

for _ in range(5):  # ограничиваем число кругов
    resp = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
    msg = resp.choices[0].message
    if not msg.tool_calls:
        print(msg.content)
        break
    messages.append(msg.model_dump(exclude_none=True))
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = FUNCTIONS[call.function.name](**args)
        print("вызов:", call.function.name, args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result, ensure_ascii=False),
        })

Мы запустили этот код без изменений на трёх моделях, меняя только MODEL: gpt-5.6-luna, deepseek-v4.1-flash и claude-haiku-4.5. Все три за первый круг вызвали обе функции — get_order_status с A-1042 и get_delivery_price с «Тверь», — а на втором круге ответили текстом со статусом заказа, датой доставки и её стоимостью.

Что в этом коде важно

  • Ответ модели с tool_calls кладётся в историю целиком. Без него сообщение tool окажется ответом на вызов, которого модель не видит. В нашей проверке gpt-5.6-luna в такой ситуации просто вызвала ту же функцию ещё раз — и цикл пошёл по кругу.
  • На каждый вызов — своё сообщение tool с тем же tool_call_id. Модель может попросить несколько вызовов сразу, как здесь.
  • Аргументы приходят строкой. arguments — это JSON в строке, его нужно разобрать через json.loads и проверить: модель может ошибиться в значении.
  • Ограничение на число кругов. Без него ошибка в функции может превратить цикл в бесконечный — и в счёт за токены.

tool_choice: когда вызов обязателен

По умолчанию модель сама решает, звать инструмент или ответить текстом ("auto"). Параметр tool_choice меняет это поведение:

  • "none" — не вызывать инструменты;
  • "required" — обязательно вызвать какой-нибудь;
  • {"type": "function", "function": {"name": "..."}} — вызвать конкретную функцию.

Последний вариант удобен для извлечения данных. Схема параметров функции становится схемой ответа, и это работает даже на моделях без строгого JSON-режима:

curl https://plusvibeapi.ru/v1/chat/completions \
  -H "Authorization: Bearer $PLUSVIBE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4.1-flash",
    "messages": [{"role": "user", "content": "Иван Петров, ivan@example.ru, хочет демо в четверг"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "save_lead",
        "description": "Сохранить заявку в CRM",
        "parameters": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "email": {"type": "string"},
            "request": {"type": "string"}
          },
          "required": ["name", "email", "request"]
        }
      }
    }],
    "tool_choice": {"type": "function", "function": {"name": "save_lead"}}
  }'

В ответе пришёл вызов save_lead с аргументами name — «Иван Петров», email — ivan@example.ru и request — «Хочет демо в четверг». Другие способы получить JSON по схеме — в статье про Structured Outputs.

Формат Anthropic: /v1/messages

Если вы работаете через Anthropic SDK, инструменты описываются чуть иначе: схема параметров лежит в поле input_schema, а вызов приходит блоком tool_use:

curl https://plusvibeapi.ru/v1/messages \
  -H "x-api-key: $PLUSVIBE_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4.5",
    "max_tokens": 512,
    "tools": [{
      "name": "get_order_status",
      "description": "Статус заказа по его номеру",
      "input_schema": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"]
      }
    }],
    "messages": [{"role": "user", "content": "Где мой заказ A-1042?"}]
  }'

Ответ пришёл со stop_reason, равным tool_use, и блоком tool_use с {"order_id": "A-1042"}. Результат возвращается сообщением пользователя с блоком tool_result. В Responses API своя форма — function_call и function_call_output, её мы показали в статье Responses API и Chat Completions.

Как писать описания инструментов

  • Название — глагол и объект: get_order_status, create_invoice. Модель выбирает инструмент в первую очередь по имени и описанию.
  • В описании — когда вызывать. «Статус заказа по его номеру» лучше, чем «Работа с заказами».
  • У параметров — формат и пример: «Номер заказа, например A-1042». Это заметно снижает число вызовов с неверными аргументами.
  • Меньше инструментов — точнее выбор. Описания всех инструментов уходят в каждый запрос и оплачиваются как входные токены. Если функций десятки, передавайте только те, что нужны на текущем шаге.

Безопасность

Аргументы функции пишет модель, а на неё влияет всё, что попало в контекст: сообщение пользователя, текст веб-страницы, содержимое письма. Поэтому проверяйте аргументы так же, как любой пользовательский ввод. Функции, которые что-то меняют — списывают деньги, удаляют данные, отправляют письма, — стоит подтверждать отдельно, а не выполнять по первому запросу модели.

Какую модель выбрать

Поддержку инструментов у конкретной модели можно проверить через GET /v1/model-capabilities с вашим ключом — поле capabilities.toolCalling. Цены за 1 млн токенов у моделей из наших примеров:

МодельВходВыход
gpt-5.6-luna3,78 ₽/1M22,63 ₽/1M
deepseek-v4.1-flash1,14 ₽/1M4,53 ₽/1M
claude-haiku-4.530 ₽/1M144 ₽/1M

Цены подставляются из каталога в момент открытия страницы. Полный список — в каталоге моделей.

Итог

Function calling — это обмен сообщениями: модель просит вызов, ваш код выполняет его и возвращает результат. Один и тот же код цикла работает с моделями разных производителей через один ключ PlusVibe. Параметры описаны в документации. Если вы собираете агента на фреймворке, пригодятся статьи про LangChain, CrewAI и Hermes Agent.

Инструменты для ваших агентов

Function calling на GPT, Claude, DeepSeek и других моделях через один ключ. Оплата в рублях.

Получить ключ →
function callingtool calling apitool useвызов функций LLMtool_choiceинструменты LLMагенты на LLM

Попробуйте PlusVibe API

OpenAI-совместимый API: GPT, Claude, Gemini, видео и изображения — один рублёвый ключ. Работает из России без VPN, оплата рублями.

Читайте также

Function calling по-русски: инструменты в LLM API