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 выбрать под задачу — связанные материалы и обзор всей категории.
Promptra — Russian LLM API aggregator. One OpenAI-compatible endpoint to all flagship models: OpenAI (GPT-5.5, GPT-5.4), Anthropic (Claude Opus 4.7, Sonnet 4.6), Google (Gemini 3.1 Pro, 3.5 Flash), DeepSeek V4 Pro, Qwen 3.6 Plus.
Provider prices 1-to-1 at CBR rate — no markup on tokens. Ruble billing per contract, full closing documents through EDI. No VPN — legal B2B service in Russia.
Try: promptra.ru · model catalog · docs





Top comments (0)