LangChain agents: инструменты, цикл и ограничение расходов

Собираем агента на create_agent с двумя инструментами и запускаем на модели через OpenAI-совместимый шлюз. Показываем, как ограничить число шагов и считать токены.

5 мин · · агенты · разработка

LangChain agents это модель, которая в цикле вызывает ваши функции-инструменты, пока не решит задачу. Цикл простой, а вот счёт за него растёт нелинейно: каждый шаг заново отправляет всю накопленную историю. В статье мы соберём агента через create_agent с двумя инструментами, запустим его на модели через OpenAI-совместимый шлюз и разберём три уровня защиты от лишних шагов. Код следует документации LangChain (разделы Agents и Tools); пакеты быстро меняются, поэтому версии и сигнатуры сверяйте с ней.

LangChain agents: как устроен цикл

Документация определяет агента как модель, которая вызывает инструменты в цикле, пока задача не выполнена, а всё вокруг цикла (промпт, инструменты, middleware) называется harness. Один виток выглядит так:

  1. Модель получает сообщения и описания инструментов.
  2. Если ответ содержит вызовы инструментов, LangChain выполняет их и добавляет результаты в историю как сообщения-результаты.
  3. Модель вызывается снова, уже с результатами.
  4. Когда в ответе нет вызовов инструментов, это финальный ответ.

Такая схема известна как ReAct (рассуждение и действие). В старых примерах вы увидите create_react_agent из LangGraph; в актуальной документации LangChain для этого используется create_agent.

Подключение модели через шлюз

В примерах документации модель задают строкой "provider:model". Для своего адреса передайте готовый объект ChatOpenAI. У Zukko для OpenAI-клиентов база заканчивается на /v1:

bash
pip install langchain langchain-openai
export ZUKKO_API_KEY="fetch_ваш_ключ"
bash
import os
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="gpt-6.1-sol",
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["ZUKKO_API_KEY"],
    max_tokens=500,
)

Параметр max_tokens ограничивает длину каждого ответа модели, что уже режет верхнюю границу выхода. Любую другую модель шлюза можно подставить в model, список возвращает GET https://api.zukko.pro/v1/models. Подключение подробнее описано в статье LangChain: как указать наш base_url.

Два инструмента через @tool

Инструмент это обычная функция с декоратором @tool из langchain.tools. Модель видит имя, аргументы с типами и docstring, поэтому описание должно быть точным.

bash
from langchain.agents import create_agent
from langchain.tools import tool

ORDERS = {
    "A-1001": {"status": "в пути", "total": 4200.0},
    "A-1002": {"status": "собирается", "total": 980.0},
}


@tool
def get_order(order_id: str) -> dict:
    """Найти заказ по номеру и вернуть его статус и сумму в рублях."""
    return ORDERS.get(order_id, {"error": "заказ не найден"})

Функции с аннотациями типов хватает: LangChain построит схему аргументов сам. Если модель путает инструменты или не вызывает их, начните с docstring и системного промпта, а потом смотрите статью агент отвечает «нет доступа к файлам». Не все модели одинаково хорошо вызывают инструменты, поэтому проверьте выбранную на своём наборе задач.

Как ограничить число шагов и расход токенов

Агент без ограничений может зациклиться: модель вызывает один и тот же инструмент снова и снова. Есть три уровня защиты, и лучше включить все.

1. Middleware с лимитами

В LangChain есть готовые middleware: ModelCallLimitMiddleware ограничивает число вызовов модели, ToolCallLimitMiddleware число вызовов инструментов. Оба принимают run_limit (за один вызов invoke) и thread_limit (за весь диалог; для него нужен checkpointer).

bash
from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware

agent = create_agent(
    model=model,
    tools=[get_order, apply_discount],
    system_prompt="Ты помощник магазина. Данные о заказах бери только из инструментов.",
    middleware=[
        ModelCallLimitMiddleware(run_limit=6, exit_behavior="end"),
        ToolCallLimitMiddleware(run_limit=8),
    ],
)

exit_behavior="end" завершает работу штатно, "error" бросает исключение. У ToolCallLimitMiddleware по умолчанию превышающие вызовы блокируются с сообщением об ошибке, и модель сама решает, что делать дальше.

2. Лимит шагов графа

Агент в LangChain работает на среде выполнения LangGraph, и у неё есть recursion_limit: максимальное число шагов за один запуск. По документации LangGraph, при превышении поднимается GraphRecursionError, а значение по умолчанию в новых версиях равно 1000, то есть для защиты денег оно слишком велико. Задайте своё в конфиге вызова:

bash
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Сколько заплатит клиент по заказу A-1001 со скидкой 15%?"}]},
    config={"recursion_limit": 15},
)

Ключ recursion_limit стоит в корне конфига, не внутри configurable. Считаются шаги графа, а не вызовы модели: один виток «модель, затем инструменты» занимает ориентировочно два шага.

3. Лимит ключа на стороне шлюза

Две первые защиты живут в вашем коде и не спасут от ошибки в самом коде. Третья работает независимо: у каждого ключа Zukko есть дневной и месячный лимит в долларах и список разрешённых моделей. Когда лимит исчерпан, API вернёт 429 с кодом key_daily_limit_reached или key_monthly_limit_reached. Заведите агенту отдельный ключ: лимиты ключа и кэш, а также статья лимиты расхода для агентов.

Учёт токенов после запуска

Каждое сообщение модели хранит usage_metadata. Просуммируем его по всей истории и переведём в доллары по ценам Zukko на 18 октября 2026:

bash
from langchain_core.messages import AIMessage

PRICES = {"gpt-6.1-sol": (0.27, 1.32)}  # $ за 1M токенов: вход, выход


def report(result, model_name):
    calls = tokens_in = tokens_out = 0
    for m in result["messages"]:
        if isinstance(m, AIMessage) and m.usage_metadata:
            calls += 1
            tokens_in += m.usage_metadata["input_tokens"]
            tokens_out += m.usage_metadata["output_tokens"]
    p_in, p_out = PRICES[model_name]
    cost = (tokens_in * p_in + tokens_out * p_out) / 1_000_000
    print(f"вызовов модели: {calls}, вход: {tokens_in}, выход: {tokens_out}, ${cost:.6f}")


report(result, "gpt-6.1-sol")

Сколько стоит агент: расчёт

Пусть агент делает три вызова модели: два раза вызывает инструмент и один раз отвечает. Вход растёт с каждым шагом, потому что история отправляется заново: 400, 550 и 700 токенов, всего 1 650. Выход 40, 40 и 80, всего 160. Цены на 18 октября 2026, OpenAI-формат:

МодельЦена вход / выход, $ за 1MРасчётИтого
gpt-6.1-sol0.27 / 1.321650 × 0.27 + 160 × 1.32 = 445.5 + 211.2 мкс$0.00066
claude-sonnet-5-50.4 / 21650 × 0.4 + 160 × 2 = 660 + 320$0.00098
deepseek-v4.1-flash0.013 / 0.0381650 × 0.013 + 160 × 0.038 = 21.45 + 6.08$0.0000275

Расчёт ведётся в миллионных долях доллара (мкс) и делится на 1 000 000. Теперь худший случай при лимитах выше: шесть вызовов модели, вход растёт по 150 токенов за шаг (400, 550, 700, 850, 1000, 1150), всего 4 650, а выход ограничен max_tokens=500 на вызов, то есть до 3 000. Для gpt-6.1-sol: 4650 × 0.27 / 1M + 3000 × 1.32 / 1M = 0.00126 + 0.00396 = $0.0052. Цифры маленькие, но агент в продакшене получает тысячи запросов в день, а без лимитов стоимость цикла растёт быстрее числа шагов, потому что каждый шаг повторно отправляет историю. Если вы работаете с Claude в OpenAI-формате, помните: скидка за кэш действует только в Anthropic-формате.

Что выбрать: create_agent или Deep Agents

create_agent подходит, когда у вас несколько инструментов и понятный сценарий. Для долгих задач с файлами, субагентами и суммаризацией контекста в LangChain есть библиотека Deep Agents. Если фреймворк кажется лишним, тот же цикл модель-инструмент пишется вручную на OpenAI SDK в несколько десятков строк.

Частые вопросы

Чем create_agent отличается от create_react_agent?

Это один и тот же класс решений (цикл модель-инструмент), но в актуальной документации LangChain рекомендован create_agent. В нём настройки через system_prompt, tools, middleware и response_format.

Модель не вызывает инструмент. Что проверить?

Docstring и имена аргументов, формулировку системного промпта и саму модель: поддержка вызова инструментов у моделей различается, сравните две-три на своих запросах.

Нужен ли checkpointer?

Для памяти между запусками и для thread_limit нужен. Для одного запуска с run_limit нет.

Можно ли узнать расход без подсчёта в коде?

Да, баланс и история расходов доступны в разделе «Использование» кабинета и через API (эндпоинты баланса и расходов описаны в документации Zukko).

Создайте отдельный ключ с дневным лимитом в кабинете и запустите пример на своей модели.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

LangChain agents: инструменты и лимит расходов · Zukko