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:
| Аспект | OpenAI | Claude |
|---|---|---|
| Структура инструмента | {"type":"function","function":{...}} | Напрямую {"name":..., "input_schema":...} |
| Поле схемы | parameters | input_schema |
| Причина остановки | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| ID результата | tool_call_id | tool_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 м/с. Зонт не нужен — отличная погода для прогулки!
Ключевые моменты:
- Два вызова API: первый — модель «думает», какой инструмент вызвать; второй — получает результат и формирует ответ
tool_call_idдолжен совпадать, иначе модель запутается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-4o | 92% | 87% | Да |
| Claude Sonnet 4.5 | 94% | 89% | Да |
| GPT-4o-mini | 85% | 79% | Да |
| DeepSeek-V3 | 83% | 76% | Да |
Источник: Berkeley Function Calling Leaderboard V4, результаты 2025-2026
Замеры задержки (параллельный vs последовательный вызов)
| Сценарий | Последовательно | Параллельно | Ускорение |
|---|---|---|---|
| 3 инструмента по 300 мс | ~1100 мс | ~450 мс | 59% |
| 5 инструментов по 200 мс | ~1300 мс | ~380 мс | 71% |
Параллельный вызов даёт критическое преимущество при работе с несколькими инструментами.
Итоги
- Начните с простого: выберите один API (погода, CRM, БД), опишите как инструмент и протестируйте по шаблону из статьи
- Отшлифуйте описания: итеративно улучшайте
description— описывайте, когда вызывать и когда НЕ вызывать - Включите
strict: true: устраняет ошибки формата параметров - Задайте лимит рекурсии: защита от бесконечных циклов вызовов
- Параллельный вызов: при нескольких инструментах используйте
concurrent.futuresдля снижения задержки


