Function CallingTool UseLLM APIAI Agents

Function Calling в LLM API: межпровайдерское руководство

1 мин чтения

Function calling выглядит одинаково у всех провайдеров — пока не оказывается иначе. OpenAI отправляет tool_calls как дельту, которую вы накапливаете по стриминговым чанкам. Anthropic возвращает tool_use как блок контента, равный блокам text. Google оборачивает всё в candidates с объектами functionCall. DeepSeek тесно следует OpenAI — пока не расходится на параллельных вызовах.

Ваш агентный код ломается каждый раз, когда вы меняете модель. Это руководство это исправляет. Рабочий код для всех четырёх провайдеров. Таблица различий, говорящая, что и где ломается. И единый паттерн враппера, позволяющий писать определения инструментов один раз и использовать их везде. Function calling — фундамент каждого создания ИИ-агентов — освойте цикл инструментов, и агентная архитектура станет простой.

Как на самом деле работает function calling

Паттерн одинаков у всех провайдеров. Понять его один раз важнее, чем заучивать синтаксис каждого провайдера.

Цикл инструментов:

  1. Вы определяете инструменты — имя, описание, JSON Schema для параметров
  2. Вы отправляете сообщение пользователя + определения инструментов модели
  3. Модель решает, ответить текстом или запросить вызов инструмента
  4. Если вызов инструмента: ваш код парсит имя функции и аргументы — выполняет функцию — отправляет результат обратно
  5. Модель обрабатывает результат — решает: ответить текстом или вызвать другой инструмент
  6. Повторяйте, пока модель не ответит текстом или вы не достигнете лимита итераций

Function calling против структурированных выходов. Function calling: модель решает, когда использовать инструмент. Структурированные выходы: модель всегда возвращает вашу схему. Используйте function calling, когда модели нужна автономия — «разберись, какая информация тебе нужна, и получи её». Используйте структурированные выходы, когда нужен гарантированный формат — «всегда возвращай JSON-объект с этими полями».

Провайдер 1: OpenAI Function Calling

Реализация function calling OpenAI — самая зрелая и эталонный стандарт, за которым следуют остальные.

from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.tokspan.com/v1",
    api_key="ts-your-key-here"
)

tools = [{
    "type": "function",
    "function": {
        "name": "get_stock_price",
        "description": "Get the current stock price for a ticker symbol. Returns price in USD.",
        "parameters": {
            "type": "object",
            "properties": {
                "symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
            },
            "required": ["symbol"]
        }
    }
}]

response = client.chat.completions.create(
    model="gpt-5.5",
    messages=[{"role": "user", "content": "What's Apple's stock price?"}],
    tools=tools,
    tool_choice="auto"
)

msg = response.choices[0].message
if msg.tool_calls:
    for tool_call in msg.tool_calls:
        args = json.loads(tool_call.function.arguments)
        result = execute_stock_lookup(args["symbol"])

        # Send result back
        messages = [
            {"role": "user", "content": "What's Apple's stock price?"},
            msg,
            {"role": "tool", "tool_call_id": tool_call.id, "content": str(result)}
        ]

        final = client.chat.completions.create(model="gpt-5.5", messages=messages)
        print(final.choices[0].message.content)

Особенности OpenAI. Параллельные вызовы инструментов: GPT-5.5 может запросить несколько инструментов в одном ответе — проверяйте на несколько элементов в msg.tool_calls. Стриминг: tool_calls приходят как дельты; накапливайте indexfunction.namefunction.arguments по чанкам. Структурированные выходы + function calling: определяйте параметры инструмента с strict: true для гарантированного соответствия схеме.

Провайдер 2: использование инструментов Anthropic

Использование инструментов Claude структурно иное — инструменты появляются как блоки контента внутри сообщений, а не как отдельное поле.

import anthropic

client = anthropic.Anthropic(
    base_url="https://api.tokspan.com/anthropic",
    api_key="ts-your-key-here"
)

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1000,
    tools=[{
        "name": "get_stock_price",
        "description": "Get the current stock price for a ticker symbol.",
        "input_schema": {
            "type": "object",
            "properties": {
                "symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
            },
            "required": ["symbol"]
        }
    }],
    messages=[{"role": "user", "content": "What's Apple's stock price?"}]
)

for block in response.content:
    if block.type == "tool_use":
        # Execute the tool Claude requested
        result = execute_stock_lookup(block.input["symbol"])

        # Build the conversation continuation —the full cycle:
        # 1. The assistant message contains ALL content blocks from Claude's response
        # 2. The user message contains tool_result blocks matching each tool_use
        assistant_msg = {"role": "assistant", "content": response.content}
        tool_result_msg = {
            "role": "user",
            "content": [{
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": str(result)
            }]
        }

        # Send the result back and get Claude's final response
        follow_up = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=1000,
            messages=[
                {"role": "user", "content": "What's Apple's stock price?"},
                assistant_msg,
                tool_result_msg
            ]
        )

        # Claude will return a text block with the final answer
        for follow_block in follow_up.content:
            if follow_block.type == "text":
                print(follow_block.text)

Ключевые отличия от OpenAI. Определения инструментов используют input_schema вместо parameters. Вызовы инструментов — блоки контента tool_use внутри response.content — они равны блокам text, а не отдельное поле. Результаты инструментов отправляются как блоки контента tool_result в сообщении пользователя. Стриминг включает частичные блоки tool_use — вы получаете имя инструмента и аргументы инкрементально.

Провайдер 3: Google Gemini Function Calling

# Gemini uses a different structure —function declarations with OpenAPI-like schema
tools = [{
    "function_declarations": [{
        "name": "get_stock_price",
        "description": "Get the current stock price for a ticker symbol.",
        "parameters": {
            "type": "object",
            "properties": {
                "symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
            },
            "required": ["symbol"]
        }
    }]
}]

# Response structure:
# response.candidates[0].content.parts[0].function_call.name
# response.candidates[0].content.parts[0].function_call.args

Особенности Gemini. Автоматический function calling: Gemini может вызывать и выполнять функции в одном API-запросе — установите automatic_function_calling в конфигурации инструментов. Поисковое обоснование: встроенный «инструмент», обосновывающий ответы результатами поиска Google без реализации поискового API.

Провайдер 4: DeepSeek Function Calling

DeepSeek следует формату OpenAI. Те же определения инструментов, та же структура ответа. Практическая разница: параллельный вызов инструментов менее надёжен, чем GPT-5.5 — инструменты, которые должны вызываться параллельно, иногда вызываются последовательно. Тестируйте ваши сценарии с несколькими инструментами специально, если переходите с GPT-5.5 на DeepSeek.

# Identical to OpenAI code —just change base_url and model
client = OpenAI(
    base_url="https://api.tokspan.com/v1",
    api_key="ts-your-key-here"
)
# Same tool definitions, same response handling as OpenAI example above

Таблица различий между провайдерами

ПараметрOpenAIAnthropicGoogleDeepSeek
Формат определения инструментовfunction.parameters (JSON Schema)input_schema (JSON Schema)function_declarations.parametersКак в OpenAI
Расположение ответаmessage.tool_calls[]блоки content[]candidates[].content.parts[]Как в OpenAI
Параллельные вызовы инструментовДа, надёжноДа, надёжноДаЧастично, менее надёжно
Стриминговые инструментыДельты, накапливаютсяЧастичные блокиЧастичные candidatesКак в OpenAI
Управление выбором инструментаtool_choice: "auto"/"required"/"none"tool_choice с аналогичными опциямиfunction_calling_configКак в OpenAI
Максимум инструментов на запрос128Не документировано (большое)Не документированоСледует OpenAI
Изменения кода при переключении100% (другой SDK)~80%0% (из OpenAI)

Распространённые ловушки

Это баги, попадающие в продакшн. У каждого есть исправление, которое можно внедрить за половину дня — но только если вы знаете, что искать, до того, как найдут пользователи.

1. Баги накопления стриминговых tool_calls. В стриминговом режиме tool_calls приходят несколькими чанками — каждый несёт index, частичное function.name и частичную строку function.arguments. Ошибка: вызов json.loads() на строке аргументов до того, как приземлится финальный дельта-чанк с finish_reason: "tool_calls". Вы получаете JSONDecodeError каждый раз, а логика повторов делает хуже, потому что частичное состояние портится. Накапливайте аргументы по index через чанки. Парсите только когда поток сигнализирует о завершении. Это один из самых частых продакшн-режимов отказа при вызове инструментов.

2. Различия JSON Schema между провайдерами. OpenAI поддерживает $ref, anyOf и вложенные oneOf в схемах параметров инструментов. Gemini молча игнорирует определения $ref — ваш инструмент работает, но модель никогда не видит упомянутую схему. Anthropic выполняет более строгую серверную валидацию, чем OpenAI; схема, проходящая на GPT-5.5, возвращает 400 на Claude с непрозрачной ошибкой валидации. Тестируйте свои схемы против каждого провайдера в CI, а не вручную за день до запуска. Шаг валидации схемы в CI с API каждого провайдера ловит это за минуты.

3. Несовпадение ID параллельных вызовов инструментов. Модель возвращает get_price("AAPL") и get_price("GOOGL") в одном ответе. Вы выполняете оба параллельно. Результаты приходят в неверном порядке. Вы сопоставляете их с неправильным tool_call_id, потому что предположили, что позиция совпадает с порядком выполнения. Модель получает цену GOOGL под ID AAPL и генерирует уверенный, правдоподобный и совершенно неверный ответ. Всегда индексируйте результаты по tool_call_id до построения сообщений результатов. Никогда не полагайтесь на позицию в массиве.

4. Ошибки инструментов, проходящие как легитимные данные. HTTP-вызов вашей функции get_stock_price превышает таймаут. Вы ловите исключение и возвращаете строку "Error: connection timeout". Модель читает эту строку как данные и отвечает: «Текущая цена — Error: connection timeout». Форматируйте ошибки инструментов узнаваемым префиксом вроде TOOL_ERROR: <type> —<message>. Опишите обработку ошибок в поле description инструмента, чтобы модель знала повторить или сообщить вам об отказе инструмента. Модель не может отличить баг от необычных данных, если вы не дадите ей сигнал.

Единый враппер function calling

Паттерн враппера: определите инструменты один раз в провайдер-независимом формате. Переведите в нативный формат каждого провайдера при вызове. Нормализуйте ответы обратно в единый формат.

class UnifiedToolClient:
    """One tool definition. Any provider. Automatic translation."""

    def __init__(self, base_url: str, api_key: str):
        self.openai_client = OpenAI(base_url=base_url, api_key=api_key)

    def call_with_tools(self, model: str, messages: list, tools: list):
        """Provider-agnostic tool calling. Handles translation internally."""
        # Tools defined in OpenAI format —works for OpenAI, DeepSeek, and
        # platforms that translate to Anthropic/Google natively
        response = self.openai_client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )
        return self._normalize_response(response)

    def _normalize_response(self, response):
        """Return a unified format regardless of which provider served the request."""
        msg = response.choices[0].message
        return {
            "text": msg.content,
            "tool_calls": [
                {"name": tc.function.name, "arguments": json.loads(tc.function.arguments)}
                for tc in (msg.tool_calls or [])
            ] if msg.tool_calls else []
        }

Короткий путь через агрегационную платформу. Этот враппер — 30 строк кода. Но он обрабатывает перевод только для провайдеров, говорящих на совместимом с OpenAI формате. Для нативных функций Anthropic (мышление + использование инструментов вместе, частичные результаты инструментов в стриминге) и нативных функций Google (автоматический function calling) вам нужна платформа с нативной поддержкой протокола каждого провайдера — иначе вы поддерживаете три отдельных пути кода. Платформы с мультипротокольной поддержкой обрабатывают это на уровне инфраструктуры. Ваш код остаётся провайдер-независимым, пока уникальные функции каждого провайдера остаются доступными. Если вы только начинаете с унифицированного вызова инструментов, руководство по быстрому старту TokSpan проведёт вас через настройку первого мультипровайдерного запроса инструмента менее чем за пять минут.

FAQ

У какого провайдера лучший function calling?

GPT-5.5: самый надёжный, лучший параллельный вызов, сильнейшая экосистема. Claude Opus: лучший для сложных многошаговых цепочек инструментов, где важна глубина рассуждений. Gemini: автоматический function calling — выигрыш по удобству для простых инструментов. DeepSeek: достаточно хорош для простых инструментов, иногда ненадёжен для параллельных вызовов. Используйте GPT-5.5, когда надёжность инструментов критична. Используйте Claude, когда глубина рассуждений об инструментах важнее сырой надёжности.

Могу ли я использовать одни и те же определения инструментов у всех провайдеров?

Не нативно. JSON Schema общая, но формат враппера отличается. Используйте слой перевода (30 строк Python) или агрегационную платформу, переводящую автоматически. Ваши определения инструментов — имена, описания, схемы параметров — переносимы, даже когда формат враппера нет.

Сколько инструментов можно определить на запрос?

OpenAI: 128. Anthropic: не документировано, но много. Google: без жёсткого лимита. На практике более 10 инструментов ухудшает точность выбора — модель начинает путать инструменты с похожими именами. Держите активный набор инструментов сфокусированным.

Стоит ли строить свой враппер или использовать платформу?

Стройте, если используете 1–2 провайдера и нуждаетесь в специфическом контроле цикла вызова инструментов. Используйте платформу, если хотите свободно переключать провайдеров и избегать поддержки четырёх путей кода. Паттерн враппера из этого руководства занимает 30 минут на внедрение и поддержку. Платформа сводит это к нулю — смотрите документацию TokSpan за платформенным API, обрабатывающим перевод и нормализацию по всем четырём провайдерам.

Function calling — фундамент каждого ИИ-агента. Но вот неудобный вопрос, на который индустрия не ответила: почему в 2026 году у каждого LLM-провайдера всё ещё слегка иной формат определений инструментов? JSON Schema общая. Концепция «tool_call» общая. И всё же формат враппера — input_schema против parameters, блоки tool_use против массива tool_calls — остаётся упрямо провайдер-специфичным. Орган по стандартизации мог бы исправить это за шестимесячную рабочую группу. Пока никто её не созвал. Вопрос: заставит ли рынок стандартизацию через совместимый с OpenAI дефолт, или нативные функции использования инструментов станут настолько дифференцированными, что межпровайдерская совместимость будет навсегда заброшена?

Попробуйте унифицированный function calling — одно определение инструмента. Четыре провайдера. Ноль кода враппера — пока индустрия выясняет, действительно ли придёт стандартизация.