DEV Community

Promptra Team for Promptra

Posted on

Function calling и tool use в LLM на Python: гайд 2026 для OpenAI/Anthropic/Gemini

Инфографика цикла function calling: блок «Python код» соединён со схемой инструмента, стрелка к LLM «OpenAI/Claude/Gemini», вызов tool_call возвращается, выполнение функции, результат идёт обратно в разговор; плоский векторный стиль

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 шагов

  1. Определяете функцию и её JSON schema (параметры, типы, обязательные поля).
  2. Передаёте схему в API через параметр tools, отправляете запрос с user message.
  3. Модель отвечает массивом tool_calls (или генерирует обычный текст, если инструменты не нужны).
  4. Ваш код выполняет каждую функцию по её аргументам, собирает результаты.
  5. Возвращаете результаты как сообщения с 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.

Схема цикла function calling, слева направо: «1. user message + tools schema», стрелка к «LLM (Opus 4.7 / GPT-5.5 / Gemini 3.1 Pro)», ответ-стрелка с подписью «tool_calls JSON», вниз к «выполнение функций в Python», обратно вверх к «LLM с результатами», финальная стрелка «ответ пользователю»; заголовок «Цикл function calling»

Шаг 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"],
    }
Enter fullscreen mode Exit fullscreen mode

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"],
            },
        },
    },
]
Enter fullscreen mode Exit fullscreen mode

Описание (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."
Enter fullscreen mode Exit fullscreen mode

При корректно описанной схеме модель сама понимает, какой инструмент звать, какие аргументы заполнять, и какого формата ответа ждать. Подробности по 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",
    )
    # дальше — всё то же самое
    ...
Enter fullscreen mode Exit fullscreen mode

В нативном Anthropic SDK формат tools другой (поля input_schema вместо parameters), но через OpenAI-совместимый слой Promptra нормализует это под единый интерфейс. Это означает: один код на все модели. Хотите попробовать Claude вместо GPT — меняете строку model. Это особенно ценно при подборе модели под задачу: вы прогоняете один и тот же сценарий на Opus 4.7, GPT-5.5, Gemini 3.1 Pro и DeepSeek V4 Pro, сравниваете качество вызовов и стоимость, выбираете победителя без переписывания кода.

Три блока кода рядом со стрелками — «GPT-5.5», «Claude Opus 4.7», «Gemini 3.1 Pro» — все указывают на одну схему функции  raw `get_currency_rate` endraw , центральная подпись «Один tools-формат, три модели», терракотовый акцент на смене параметра model

Шаг 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 выполняет все три параллельно
# финальный ответ — таблица с тремя курсами
Enter fullscreen mode Exit fullscreen mode

Параллельность экономит секунды на каждом раунде агента. Подробности про 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 "Превышен лимит шагов агента."
Enter fullscreen mode Exit fullscreen mode

Что важно:

  • max_steps — защита от бесконечной петли. Адекватные значения: 5 для простого, 20 для исследовательского, 50+ только с явным reasoning-budget'ом.
  • try/except вокруг вызова функции — обязательно. Если функция упала, агент получает текст ошибки и решает, как реагировать (повторить с другими аргументами, попросить уточнение у пользователя, отказаться).
  • system message — задаёт поведение агента. Сюда же — список разрешённых имён tools, ограничения по domain, формат финального ответа.

Схема агентского цикла: блок «User message» → «LLM с tools», от модели две стрелки — «нет tool_call → финальный ответ» и «tool_calls → выполнение → результаты → обратно в LLM», над циклом подпись «max_steps = 10», терракотовый акцент на цикле, заголовок «Agent loop»

Стоимость и оптимизация 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 ₽

Способы оптимизации:

  1. Prompt caching — большая часть tools редко меняется. Кэшируете их часть промта, и повторные запросы платят только за новые токены user message и output. На зрелом агенте это экономия 30–60% input стоимости.
  2. Tool routing — заранее классифицируете запрос (через дешёвый GPT-5.4 mini или regex) и отдаёте только релевантные tools. Запрос «найди контакт в CRM» получает 3 tools вместо 15.
  3. 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"],
            },
        },
    },
]
Enter fullscreen mode Exit fullscreen mode

В 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}),
    })
Enter fullscreen mode Exit fullscreen mode

После 2–3 таких подсказок модель стабилизируется на правильной схеме. Подробнее про надёжный код в проде — в «Сравнение Claude vs ChatGPT» и «Claude Code в России».

Блок-схема валидации: «tool_call JSON» → ромб «Pydantic schema valid?» — ветка «да → выполнить» зелёная и «нет → отправить error в model» терракотовая, обратная стрелка к «модель повторяет с правильной структурой»; заголовок «Защита от некорректного JSON»

Безопасность 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."""
    ...
Enter fullscreen mode Exit fullscreen mode

Модель вызывает 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)