Function calling — это способ научить LLM пользоваться внешними системами. Модель не выполняет код сама — она получает список доступных функций со схемой, и когда видит, что для ответа нужно вызвать одну из них, возвращает структурированный JSON с именем и аргументами. Дальше ваш код выполняет функцию (запрос в API, поиск в БД, отправку email) и возвращает результат обратно в разговор — модель видит данные и формулирует финальный ответ пользователю.
Этот гайд показывает рабочий код function calling для трёх флагманов через единый шлюз Promptra — Claude Opus 4.7, GPT-5.5 и Gemini 3.1 Pro. Все три доступны через OpenAI-совместимый формат tools, что радикально упрощает архитектуру. Если вы уже мигрировали по «Миграция с OpenAI на Promptra» — добавление tools — это 30 минут работы. оплата в рублях по договору, полный пакет закрывающих документов, цены в рублях по курсу ЦБ.
TL;DR — цикл function calling в 5 шагов
- Определяете функцию и её JSON schema (параметры, типы, обязательные поля).
- Передаёте схему в API через параметр
tools, отправляете запрос с user message. - Модель отвечает массивом
tool_calls(или генерирует обычный текст, если инструменты не нужны). - Ваш код выполняет каждую функцию по её аргументам, собирает результаты.
- Возвращаете результаты как сообщения с
role="tool", запрашиваете финальный ответ. Подробнее — миграция с OpenAI SDK на Promptra за 10 минут. Эта статья — часть pillar-гида: полный технический гид по LLM API на Python — токены, function calling, streaming, RAG, batch.
После этого модель видит данные из инструментов и формирует ответ пользователю.
Что такое function calling и зачем
Без function calling LLM ограничен своим обучающим корпусом — он не знает курс доллара на сегодня, не может прочитать вашу БД, не отправит письмо в Slack. С function calling вы расширяете модель любым Python-инструментом, который сами напишете: SQL-запрос, REST API, файловая система, поиск в Elasticsearch, отправка SMS, чтение PDF. Модель сама решает, когда и какой инструмент звать.
Архитектурно это open-loop цикл:
- Прогон 1: user → model →
tool_calls - Выполнение функций в Python
- Прогон 2: tools_results → model → финальный ответ
Этот цикл — основа LLM-агентов: автономных систем, которые решают многоступенчатые задачи через серию tool вызовов. Например, агент-аналитик: «найди отчёт по продажам Q4», «посчитай дельту с Q3», «построй график», «отправь в Slack». Каждый шаг — tool call.

Шаг 1. Определяем функцию и схему
В Python — обычная функция:
import requests
def get_currency_rate(base: str, quote: str) -> dict:
"""Возвращает курс ЦБ РФ для пары base→quote на сегодня."""
response = requests.get(f"https://www.cbr-xml-daily.ru/daily_json.js", timeout=5)
data = response.json
rate = data["Valute"][base]["Value"] / data["Valute"][base]["Nominal"]
return {
"base": base,
"quote": quote,
"rate": rate,
"date": data["Date"],
}JSON schema для tools (OpenAI-совместимый формат, работает с Claude и Gemini через шлюз Promptra):
TOOLS = [
{
"type": "function",
"function": {
"name": "get_currency_rate",
"description": "Возвращает текущий курс валюты по данным ЦБ РФ.",
"parameters": {
"type": "object",
"properties": {
"base": {
"type": "string",
"description": "Код валюты по ISO 4217, например USD, EUR.",
},
"quote": {
"type": "string",
"description": "Целевая валюта, обычно RUB.",
"default": "RUB",
},
},
"required": ["base"],
},
},
},
]Описание (description) — самое важное поле. Модель решает, звать ли инструмент, на основе текста описания. Чем точнее формулировка — тем меньше ложных срабатываний и пропусков.
Шаг 2. Полный цикл для OpenAI (GPT-5.5)
import json
from openai import OpenAI
client = OpenAI(
api_key="sk-promptra-...",
base_url="https://api.promptra.ru/v1",
)
# регистр доступных функций
FUNCTIONS = {
"get_currency_rate": get_currency_rate,
}
def run_with_tools(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
# Прогон 1
response = client.chat.completions.create(
model="gpt-5-5",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = response.choices[0].message
messages.append(msg.model_dump)
if not msg.tool_calls:
return msg.content # модель ответила сразу, без инструментов
# Выполняем все tool_calls
for call in msg.tool_calls:
fn_name = call.function.name
args = json.loads(call.function.arguments)
result = FUNCTIONS[fn_name](**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
# Прогон 2
final = client.chat.completions.create(
model="gpt-5-5",
messages=messages,
tools=TOOLS,
)
return final.choices[0].message.content
print(run_with_tools("Сколько сейчас стоит доллар в рублях?"))
# модель → tool_call get_currency_rate(base="USD")
# Python выполняет → возвращает {rate: 71.668, ...}
# модель → "Сейчас курс ЦБ: 71.668 ₽ за 1 USD на 31 мая 2026."При корректно описанной схеме модель сама понимает, какой инструмент звать, какие аргументы заполнять, и какого формата ответа ждать. Подробности по OpenAI API — в официальной документации function calling, нативный формат tool use в Claude разобран в гайде Anthropic.
Шаг 3. Тот же код работает для Claude
Через шлюз Promptra Claude доступен по тому же OpenAI-совместимому формату:
def run_with_tools_claude(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
response = client.chat.completions.create(
model="claude-opus-4-7", # ← смена одной строки
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
# дальше — всё то же самое
...В нативном Anthropic SDK формат tools другой (поля input_schema вместо parameters), но через OpenAI-совместимый слой Promptra нормализует это под единый интерфейс. Это означает: один код на все модели. Хотите попробовать Claude вместо GPT — меняете строку model. Это особенно ценно при подборе модели под задачу: вы прогоняете один и тот же сценарий на Opus 4.7, GPT-5.5, Gemini 3.1 Pro и DeepSeek V4 Pro, сравниваете качество вызовов и стоимость, выбираете победителя без переписывания кода.

Шаг 4. Параллельные tool calls
Современные модели часто возвращают несколько tool_calls в одном ответе — например, на запрос «сколько USD и EUR в рублях» Opus 4.7 вызовет инструмент дважды параллельно. Обрабатывайте их через asyncio.gather:
import asyncio
import json
from openai import AsyncOpenAI
async_client = AsyncOpenAI(
api_key="sk-promptra-...",
base_url="https://api.promptra.ru/v1",
)
async def run_parallel(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
response = await async_client.chat.completions.create(
model="claude-opus-4-7",
messages=messages,
tools=TOOLS,
)
msg = response.choices[0].message
messages.append(msg.model_dump)
if not msg.tool_calls:
return msg.content
# Параллельное выполнение
async def execute(call):
args = json.loads(call.function.arguments)
# если функция синхронная — оборачиваем в run_in_executor
loop = asyncio.get_event_loop
result = await loop.run_in_executor(
None, lambda: FUNCTIONS[call.function.name](**args)
)
return {
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
}
tool_results = await asyncio.gather(*[execute(c) for c in msg.tool_calls])
messages.extend(tool_results)
final = await async_client.chat.completions.create(
model="claude-opus-4-7",
messages=messages,
tools=TOOLS,
)
return final.choices[0].message.content
asyncio.run(run_parallel(
"Покажи курс USD, EUR и CNY в рублях."
))
# модель возвращает 3 tool_calls сразу
# asyncio.gather выполняет все три параллельно
# финальный ответ — таблица с тремя курсамиПараллельность экономит секунды на каждом раунде агента. Подробности про async-паттерны и Batch API — в материале «Async-вызовы и Batch API в LLM».
Шаг 5. Multi-tool сценарий: маленький агент
Реальные агенты имеют 5–20 инструментов и выполняют многошаговые задачи. Минимальный шаблон с циклом:
def agent_loop(user_message: str, max_steps: int = 10) -> str:
messages = [
{"role": "system", "content": "Ты — помощник-аналитик. Используй инструменты для получения данных, потом отвечай."},
{"role": "user", "content": user_message},
]
for step in range(max_steps):
response = client.chat.completions.create(
model="claude-opus-4-7",
messages=messages,
tools=TOOLS,
)
msg = response.choices[0].message
messages.append(msg.model_dump)
if not msg.tool_calls:
return msg.content # агент закончил
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
try:
result = FUNCTIONS[call.function.name](**args)
content = json.dumps(result, ensure_ascii=False)
except Exception as e:
content = json.dumps({"error": str(e)})
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": content,
})
return "Превышен лимит шагов агента."Что важно:
max_steps— защита от бесконечной петли. Адекватные значения: 5 для простого, 20 для исследовательского, 50+ только с явным reasoning-budget'ом.try/exceptвокруг вызова функции — обязательно. Если функция упала, агент получает текст ошибки и решает, как реагировать (повторить с другими аргументами, попросить уточнение у пользователя, отказаться).systemmessage — задаёт поведение агента. Сюда же — список разрешённых имён tools, ограничения по domain, формат финального ответа.

Стоимость и оптимизация tools
Каждый запрос с инструментами включает в input их JSON schema целиком. Это значимый расход:
| Сценарий | tokens на tools | стоимость на 1000 запросов (Opus 4.7) |
|---|---|---|
| 1 простой tool (currency) | ~120 | 42 ₽ |
| 5 средних tools (CRM-агент) | ~900 | 315 ₽ |
| 15 tools (универсальный агент) | ~3400 | 1190 ₽ |
| 30 tools (платформа) | ~8500 | 2975 ₽ |
Способы оптимизации:
- Prompt caching — большая часть tools редко меняется. Кэшируете их часть промта, и повторные запросы платят только за новые токены user message и output. На зрелом агенте это экономия 30–60% input стоимости.
- Tool routing — заранее классифицируете запрос (через дешёвый GPT-5.4 mini или regex) и отдаёте только релевантные tools. Запрос «найди контакт в CRM» получает 3 tools вместо 15.
- Compact descriptions — короткие, но точные
description. Каждое слово в схеме — это input-токены, причём в каждом запросе. Подробнее про экономию токенов — в «Как считать токены в LLM».
Strict mode и валидация
OpenAI поддерживает строгий режим, где модель гарантированно вернёт JSON, соответствующий схеме:
TOOLS_STRICT = [
{
"type": "function",
"function": {
"name": "get_currency_rate",
"strict": True, # ← строгая валидация на стороне модели
"description": "Возвращает курс ЦБ РФ.",
"parameters": {
"type": "object",
"additionalProperties": False, # обязательно для strict
"properties": {
"base": {"type": "string"},
"quote": {"type": "string"},
},
"required": ["base", "quote"],
},
},
},
]В strict mode модель не выйдет за пределы схемы — это убирает целый класс ошибок «модель вернула строку вместо числа». Поддерживается на GPT-5.x; на Claude и Gemini эффект достигается через подробное description и пост-валидацию через jsonschema/Pydantic:
from pydantic import BaseModel, ValidationError
class CurrencyArgs(BaseModel):
base: str
quote: str = "RUB"
try:
args = CurrencyArgs(**json.loads(call.function.arguments))
except ValidationError as e:
# возвращаем модели описание ошибки, она исправит
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"error": "validation", "details": e.errors}),
})После 2–3 таких подсказок модель стабилизируется на правильной схеме. Подробнее про надёжный код в проде — в «Сравнение Claude vs ChatGPT» и «Claude Code в России».

Безопасность tools в проде
Никогда не давайте LLM прямой исполняющий tool без подтверждения для destructive действий. Паттерн с preview:
def send_email_preview(to: str, subject: str, body: str) -> dict:
"""Готовит письмо к отправке. ВНИМАНИЕ: не отправляет — возвращает preview для подтверждения."""
return {
"status": "preview",
"to": to,
"subject": subject,
"body_preview": body[:200] + "..." if len(body) > 200 else body,
"confirm_token": generate_confirm_token(...),
}
def send_email_confirm(confirm_token: str) -> dict:
"""Реально отправляет письмо. Требует token из preview."""
...Модель вызывает send_email_preview, видит результат, формулирует пользователю «я хочу отправить такое-то письмо». Если пользователь подтверждает — модель вызывает send_email_confirm. Это критично для денег, удалений, рассылок. Дополнительно — sandbox исполнения, allowlist tools, лимит на число вызовов в одной сессии.
Оплата и закрывающие документы
Юрлицо-исполнитель — российское юр.лицо , резидент РФ. Сервисная комиссия 5% берётся только при пополнении баланса, на токены наценки нет. Полный пакет закрывающих документов (договор-оферта, счёт на оплату, акт оказанных услуг, счёт-фактура, УПД) приходит через ЭДО — Диадок, СБИС, Контур. Подробнее — на странице «Тарифы».
Что дальше
Function calling — это переход от «модель отвечает текстом» к «модель управляет вашими системами». Минимальный set — это tools=[], регистр Python-функций, цикл tool_calls → execute → результаты. С asyncio.gather он становится быстрым, со strict mode — надёжным, с prompt caching — дешёвым. Полезные следующие шаги: streaming для UI («Streaming LLM-ответов через SSE»), embeddings и RAG («Embeddings и векторный поиск»), и Batch API для экономии до 50% («Async-вызовы и Batch API»). Если нужно подобрать модель под ваш агент или подключить ключ через юрлицо — напишите команде Promptra в Telegram.
> 📚 Главный гайд по теме: Лучшая нейросеть 2026: какую LLM выбрать под задачу — связанные материалы и обзор всей категории.
FAQ
Чем function calling отличается от tool use?
Это одно и то же — разные имена в разных SDK. OpenAI называл это function calling, Anthropic ввёл термин tool use для Claude, Google в Gemini SDK использует tools. Под капотом — единый протокол: модель не выполняет код сама, она возвращает структурированный JSON, ваш код выполняет функцию и возвращает результат обратно.
Какие модели поддерживают function calling в 2026?
Стабильно работает на Claude Opus 4.7, Claude Sonnet 4.6, GPT-5.5, GPT-5.4, Gemini 3.1 Pro, Gemini 3.5 Flash. На DeepSeek V4 Pro — с ограничениями. Для критичной автоматизации в продакшене берите Opus 4.7 или GPT-5.5.
Как обрабатывать параллельные tool calls?
Модель может вернуть массив из нескольких tool_calls в одном ответе. Выполняйте их параллельно через asyncio.gather или ThreadPoolExecutor, собирайте результаты и добавляйте в conversation как несколько сообщений с role="tool" и соответствующими tool_call_id.
Что делать с некорректным JSON в arguments?
Try/except вокруг json.loads(call.function.arguments), при ошибке — добавить tool-сообщение с error и вернуть в conversation; модель попробует ещё раз. Дополнительно — strict schema validation через Pydantic. Для гарантии — Strict Mode у OpenAI (strict=True).
Сколько токенов добавляет передача tools в запрос?
Каждый инструмент в массиве tools — это его JSON schema целиком. Простой инструмент — 80–150 токенов, сложный — 300–500. На 10 инструментов это легко 2000–4000 токенов на каждый запрос. Кэшируйте tools, роутьте запросы между toolset'ами.
Как защититься от выполнения опасных функций?
Никогда не давайте LLM прямой исполняющий tool без подтверждения для destructive операций. Паттерн preview→confirm: tool возвращает описание действия, пользователь подтверждает, и только тогда делается реальный вызов. Для агентов в проде — sandbox, allowlist tools, лимиты на число вызовов.
![Финальная инфографика: 5 пронумерованных блоков в ряд — «schema», «tools=[]», «tool_calls», «execute», «results→LLM», под ними подпись «Цикл function calling за 30 минут», терракотовые стрелки между блоками; заголовок «5 шагов до рабочего инструмента»](/blog/function-calling-tool-use-llm-2026-python-praktika/img-5.webp)
