promptra
← Все статьи
Гайды7 мин чтения

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

Пошаговый практический гайд по function calling и tool use в LLM на Python в 2026: рабочий код для OpenAI (GPT-5.5), Anthropic (Claude Opus 4.7) и Google (Gemini 3.1 Pro). Схема инструмента, передача в API, парсинг tool_calls, выполнение и возврат результата в conversation.

Инфографика цикла 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"],
    }

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, сравниваете качество вызовов и стоимость, выбираете победителя без переписывания кода.

Три блока кода рядом со стрелками — «GPT-5.5», «Claude Opus 4.7», «Gemini 3.1 Pro» — все указывают на одну схему функции `get_currency_rate`, центральная подпись «Один 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 выполняет все три параллельно
# финальный ответ — таблица с тремя курсами

Параллельность экономит секунды на каждом раунде агента. Подробности про 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 вокруг вызова функции — обязательно. Если функция упала, агент получает текст ошибки и решает, как реагировать (повторить с другими аргументами, попросить уточнение у пользователя, отказаться).
  • 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)~12042 ₽
5 средних tools (CRM-агент)~900315 ₽
15 tools (универсальный агент)~34001190 ₽
30 tools (платформа)~85002975 ₽

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

  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"],
            },
        },
    },
]

В 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 в России».

Блок-схема валидации: «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."""
    ...

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