LangChain agents: инструменты, цикл и ограничение расходов
Собираем агента на create_agent с двумя инструментами и запускаем на модели через OpenAI-совместимый шлюз. Показываем, как ограничить число шагов и считать токены.
5 мин · · агенты · разработка
LangChain agents это модель, которая в цикле вызывает ваши функции-инструменты, пока не решит задачу. Цикл простой, а вот счёт за него растёт нелинейно: каждый шаг заново отправляет всю накопленную историю. В статье мы соберём агента через create_agent с двумя инструментами, запустим его на модели через OpenAI-совместимый шлюз и разберём три уровня защиты от лишних шагов. Код следует документации LangChain (разделы Agents и Tools); пакеты быстро меняются, поэтому версии и сигнатуры сверяйте с ней.
LangChain agents: как устроен цикл
Документация определяет агента как модель, которая вызывает инструменты в цикле, пока задача не выполнена, а всё вокруг цикла (промпт, инструменты, middleware) называется harness. Один виток выглядит так:
- Модель получает сообщения и описания инструментов.
- Если ответ содержит вызовы инструментов, LangChain выполняет их и добавляет результаты в историю как сообщения-результаты.
- Модель вызывается снова, уже с результатами.
- Когда в ответе нет вызовов инструментов, это финальный ответ.
Такая схема известна как ReAct (рассуждение и действие). В старых примерах вы увидите create_react_agent из LangGraph; в актуальной документации LangChain для этого используется create_agent.
Подключение модели через шлюз
В примерах документации модель задают строкой "provider:model". Для своего адреса передайте готовый объект ChatOpenAI. У Zukko для OpenAI-клиентов база заканчивается на /v1:
pip install langchain langchain-openai
export ZUKKO_API_KEY="fetch_ваш_ключ"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, поэтому описание должно быть точным.
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).
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, то есть для защиты денег оно слишком велико. Задайте своё в конфиге вызова:
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:
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-sol | 0.27 / 1.32 | 1650 × 0.27 + 160 × 1.32 = 445.5 + 211.2 мкс | $0.00066 |
claude-sonnet-5-5 | 0.4 / 2 | 1650 × 0.4 + 160 × 2 = 660 + 320 | $0.00098 |
deepseek-v4.1-flash | 0.013 / 0.038 | 1650 × 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.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Claude Code YOLO: режимы разрешений и работа без подтвержденийОтдельного режима YOLO в Claude Code нет, зато есть шесть режимов разрешений и гибкие правила. Разбираем, как перестать нажимать «Да» и не лишиться файлов.
- Claude Code: чек-лист лучших практик для ежедневной работыПривычки, которые делают работу с Claude Code предсказуемой: короткий CLAUDE.md, план перед правками, мелкие задачи, проверка результата и контроль расхода.
- MCP-сервер на Java: минимальный пример с одним инструментомПишем минимальный MCP-сервер на Java с одним инструментом, регистрируем его в Claude Code и учимся находить причину, если агент его не видит.