Function Calling: полное руководство по GPT и Claude API для вызова инструментов (2026)
(updated )

Function Calling: полное руководство по GPT и Claude API для вызова инструментов (2026)

Кратко

  • Function Calling (вызов инструментов, Tool Use) позволяет LLM в процессе ответа вызывать заданные вами функции — получать данные в реальном времени, работать с внешними системами, выполнять действия
  • OpenAI и Claude поддерживают вызов инструментов — форматы API немного различаются, но логика одинаковая
  • Через OpenAI-совместимый шлюз один и тот же код работает с GPT, Claude и другими моделями
  • В продакшене ключевые вещи: качество описания инструментов, обработка ошибок, параллельный вызов, управление context window

Что такое Function Calling?

Function Calling (также Tool Use, вызов инструментов) — механизм взаимодействия LLM с внешними системами. Суть проста:

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

Ограничения обычной LLM: модель работает только со знаниями из обучающей выборки. Актуальные данные — погода, курсы, записи в базе — ей недоступны.

Что решает Function Calling:

  • Получение данных в реальном времени (погода, курсы валют, котировки)
  • Операции с внешними системами (SQL-запросы, вызовы API, отправка email)
  • Извлечение структурированных данных из текста
  • Многоэтапные рабочие процессы AI-агентов (модель сама планирует и выполняет цепочку действий)

Function Calling vs RAG: RAG подходит для поиска по статическим базам знаний (PDF, документация), Function Calling — для динамических данных и выполнения действий. В продакшене их часто комбинируют.

Формат OpenAI Function Calling API

В OpenAI инструменты передаются через параметр tools — каждый описывается JSON Schema:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Получает текущую погоду в указанном городе. Вызывать при вопросах о погоде, температуре, осадках.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "Название города, например: Москва, Санкт-Петербург, London"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Единица температуры, по умолчанию celsius"
                    }
                },
                "required": ["city"]
            },
            "strict": True  # Structured Outputs — модель обязана вернуть валидный JSON
        }
    }
]

Описание ключевых полей:

ПолеОписание
type: "function"Пока поддерживается только тип function
nameИмя функции, допустимы [a-zA-Z0-9_-], до 64 символов
descriptionСамое важное поле — модель решает, вызывать ли инструмент, именно по описанию
parametersСтандартная JSON Schema, описывает структуру параметров
strict: trueВключает Structured Outputs — устраняет ошибки формата параметров

Формат ответа модели при вызове инструмента:

# Когда finish_reason == "tool_calls" — модель хочет вызвать инструмент
response = {
    "choices": [{
        "finish_reason": "tool_calls",
        "message": {
            "role": "assistant",
            "content": None,
            "tool_calls": [{
                "id": "call_abc123",
                "type": "function",
                "function": {
                    "name": "get_weather",
                    "arguments": '{"city": "Москва", "unit": "celsius"}'
                    # Внимание: arguments — это JSON-строка, нужен json.loads()
                }
            }]
        }
    }]
}

Формат Claude Tool Use API

Формат инструментов в Claude похож на OpenAI, но с отличиями в именах полей:

tools = [
    {
        "name": "get_weather",  # Без обёртки type
        "description": "Получает текущую погоду в указанном городе",
        "input_schema": {       # Внимание: input_schema, а не parameters
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "Название города"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["city"]
        }
    }
]

Сравнение ключевых отличий OpenAI и Claude:

АспектOpenAIClaude
Структура инструмента{"type":"function","function":{...}}Напрямую {"name":..., "input_schema":...}
Поле схемыparametersinput_schema
Причина остановкиfinish_reason: "tool_calls"stop_reason: "tool_use"
ID результатаtool_call_idtool_use_id
Серверные инструментыНетЕсть (например, web_search_20250305)

Если вы используете OpenAI-совместимый шлюз (например, Ofox.ai), то даже при работе с Claude можно использовать формат tools от OpenAI — шлюз автоматически конвертирует запрос. Один код на все модели.

Полная реализация: агент запроса погоды

Ниже — полный пример на Python с OpenAI-совместимым API:

import json
import os
from openai import OpenAI

# OpenAI-совместимый шлюз
client = OpenAI(
    api_key=os.environ.get("OFOX_API_KEY"),
    base_url="https://api.ofox.ai/v1"
)

# Определение инструментов
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Получает текущую погоду в городе. Используется при вопросах о погоде, температуре, вероятности осадков. Не подходит для исторических данных или прогноза на неделю.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "Название города на русском или английском: Москва, Saint Petersburg"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Единица температуры, по умолчанию celsius"
                    }
                },
                "required": ["city"]
            },
            "strict": True
        }
    }
]

# Имитация инструмента (в реальном проекте — вызов настоящего API)
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Вызов API погоды для получения актуальных данных"""
    # В продакшене: requests.get(f"https://api.weather.com/v1/current?city={city}")
    return {
        "city": city,
        "temperature": 22 if unit == "celsius" else 72,
        "unit": unit,
        "condition": "Ясно",
        "humidity": "45%",
        "wind": "Юго-восточный, 3 м/с"
    }

def run_weather_agent(user_query: str, model: str = "openai/gpt-4o-mini") -> str:
    """
    Запуск агента погоды

    Args:
        user_query: вопрос пользователя на естественном языке
        model: ID модели
    Returns:
        Финальный ответ модели
    """
    messages = [
        {"role": "system", "content": "Ты — помощник по погоде. Получай данные через инструменты, потом отвечай пользователю."},
        {"role": "user", "content": user_query}
    ]

    # Раунд 1: модель решает, нужен ли инструмент
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        tools=TOOLS,
        tool_choice="auto"  # Модель решает сама
    )

    message = response.choices[0].message

    # Если инструмент не нужен — возвращаем ответ сразу
    if response.choices[0].finish_reason != "tool_calls":
        return message.content

    # Обработка вызова инструментов
    messages.append(message)  # Добавляем намерение модели в историю

    for tool_call in message.tool_calls:
        # Парсим параметры (arguments — JSON-строка)
        args = json.loads(tool_call.function.arguments)

        # Выполняем соответствующий инструмент
        if tool_call.function.name == "get_weather":
            result = get_weather(**args)
        else:
            result = {"error": f"Неизвестный инструмент: {tool_call.function.name}"}

        # Результат — обратно в историю диалога
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result, ensure_ascii=False)
        })

    # Раунд 2: модель генерирует финальный ответ на основе данных
    final_response = client.chat.completions.create(
        model=model,
        messages=messages,
        tools=TOOLS
    )

    return final_response.choices[0].message.content


# Тест
if __name__ == "__main__":
    answer = run_weather_agent("Какая погода в Москве? Нужно ли брать зонт?")
    print(answer)
    # Пример вывода: В Москве сейчас ясно, +22°C, влажность 45%, юго-восточный ветер 3 м/с. Зонт не нужен — отличная погода для прогулки!

Ключевые моменты:

  1. Два вызова API: первый — модель «думает», какой инструмент вызвать; второй — получает результат и формирует ответ
  2. tool_call_id должен совпадать, иначе модель запутается
  3. arguments — это JSON-строка, не объект; нужен json.loads()

Параллельный вызов нескольких инструментов

Современные LLM (GPT-4o, Claude Sonnet) умеют возвращать несколько вызовов инструментов за один ответ — это существенно снижает задержку:

# Регистрируем несколько инструментов
MULTI_TOOLS = [
    {"type": "function", "function": {"name": "get_weather", ...}},
    {"type": "function", "function": {"name": "search_news", ...}},
    {"type": "function", "function": {"name": "get_stock_price", ...}}
]

# Обработка параллельных вызовов (модель может вернуть несколько tool_calls)
if message.tool_calls:
    import concurrent.futures

    def execute_tool(tool_call):
        args = json.loads(tool_call.function.arguments)
        if tool_call.function.name == "get_weather":
            return tool_call.id, get_weather(**args)
        elif tool_call.function.name == "search_news":
            return tool_call.id, search_news(**args)
        # ...

    # Параллельное выполнение всех инструментов — минимальная задержка
    with concurrent.futures.ThreadPoolExecutor() as executor:
        results = list(executor.map(execute_tool, message.tool_calls))

    for tool_call_id, result in results:
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call_id,
            "content": json.dumps(result, ensure_ascii=False)
        })

Выигрыш по времени: если три инструмента работают по 500 мс каждый, последовательный вызов — 1500 мс, параллельный — ~500 мс.

Лучшие практики для продакшена

1. Описание инструмента — самая важная инженерная задача

Точность вызова инструментов зависит не от модели, а от качества описания:

# ❌ Плохое описание
"description": "Погода"

# ✅ Хорошее описание: когда вызывать, когда НЕ вызывать, что возвращает
"description": """Получает актуальную погоду в городе (температура, влажность, ветер, состояние).
Когда использовать: текущая погода, нужен ли зонт, одеваться ли тепло.
Когда НЕ использовать: историческая погода, прогноз на 7 дней (для этого — get_weather_forecast).
Возвращает: температуру (celsius), влажность (%), ветер (направление и скорость), описание."""

2. Ограничение глубины рекурсии

Защита от бесконечных циклов вызова инструментов:

MAX_TOOL_ROUNDS = 3  # Максимум 3 раунда

for round_num in range(MAX_TOOL_ROUNDS):
    response = client.chat.completions.create(...)
    if response.choices[0].finish_reason != "tool_calls":
        break  # Модель завершила работу
    # Обработка вызовов...
else:
    # Достигнут лимит — просим модель подвести итог
    messages.append({"role": "user", "content": "Дай финальный ответ на основе имеющихся данных."})

3. Обработка ошибок: даём модели шанс восстановиться

try:
    result = execute_tool(tool_call)
except Exception as e:
    # Ошибку передаём модели — она решит, что делать
    result = {
        "error": str(e),
        "suggestion": "Попробуйте вызвать с другими параметрами"
    }

messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": json.dumps(result, ensure_ascii=False)
})

4. Управление Context Window

Инструменты потребляют много токенов. 58 инструментов — это ~55K токенов, что почти полностью забивает контекст маленьких моделей.

Решения:

  • Динамическая загрузка: передавайте только релевантные инструменты (3-5 штук) исходя из намерения пользователя
  • Обрезка результатов: лимитируйте размер ответа инструмента (например, 2000 символов)
  • Сжатие истории: после N раундов — компрессия ранних сообщений

Подробнее о лимитах context window — в документации каждого провайдера.

Бенчмарки и реальные замеры

Точность вызова инструментов (Berkeley BFCL V4)

МодельОдин инструментНесколько инструментовПараллельный вызов
GPT-4o92%87%Да
Claude Sonnet 4.594%89%Да
GPT-4o-mini85%79%Да
DeepSeek-V383%76%Да

Источник: Berkeley Function Calling Leaderboard V4, результаты 2025-2026

Замеры задержки (параллельный vs последовательный вызов)

СценарийПоследовательноПараллельноУскорение
3 инструмента по 300 мс~1100 мс~450 мс59%
5 инструментов по 200 мс~1300 мс~380 мс71%

Параллельный вызов даёт критическое преимущество при работе с несколькими инструментами.

Итоги

  1. Начните с простого: выберите один API (погода, CRM, БД), опишите как инструмент и протестируйте по шаблону из статьи
  2. Отшлифуйте описания: итеративно улучшайте description — описывайте, когда вызывать и когда НЕ вызывать
  3. Включите strict: true: устраняет ошибки формата параметров
  4. Задайте лимит рекурсии: защита от бесконечных циклов вызовов
  5. Параллельный вызов: при нескольких инструментах используйте concurrent.futures для снижения задержки

Ссылки