Agent CostControl

Контроль затрат и стабильности агента: лимиты + многослойная защита от runaway-агентов и Token DoS. Девиз: «бюджет — это не экономия, это стабильность». Один неконтролируемый запрос доходил до $150+ / $12 400 за выходные.

Суть

Агент без тормозов зацикливается (retry на ошибке инструмента, недостижимая цель, переполненный контекст, баг в tool) и жжёт токены. Причём CPU/RAM в норме — растут только токены, поэтому стандартный мониторинг (Prometheus) это не ловит.

Зачем это нужно

Runaway бывает не только от атаки, но и от обычного бага (изменился формат API → retry loop → 200× токенов за 40 минут). Token DoS (случайный или намеренный) защищается одинаково — бюджетными лимитами. В отраслевой номенклатуре это LLM10 Unbounded Consumption, а денежная его форма называется Denial of Wallet: система при этом работает исправно, деградирует только счёт, поэтому обычный мониторинг доступности атаку не видит.

Как работает

  • 4 независимых лимита (нужны все сразу): max_iterations (шаги), max_tokens (на задачу), timeout (время), max_cost (деньги, circuit breaker). Уровни бюджета: задача ~$0.50 / сессия ~$2 / день ~$10.
  • 5-слойная защита (prod): Timeout → Anti-loop (max 3 retry + пауза) → Cost Circuit Breaker ($50 warning / $100 halt) → Model Pinning (дешёвые задачи не уходят на дорогую модель) → Session Audit Log. Каждый слой ловит то, что пропустил предыдущий.
  • AI-специфичные метрики: tokens_per_task, cost_per_completion, loop_iterations — алерт при 2× от baseline. Baseline не берётся из головы: это p95 метрики за прошлую неделю, пересчитываемый скользящим окном, отдельно на каждый тип операции — усреднять по всему трафику бессмысленно, потому что deep search и классификация интента отличаются на порядок. Пока недели данных нет, лимит ставится от бюджета, а не от статистики, и заменяется на p95, как только окно набралось (см. Cost Anomaly Alerting про множитель и ложные срабатывания).
  • Early stopping > hard error: при остатке бюджета <20% вернуть частичный результат + статус, а не упасть с исключением (UX-решение). Связано с лимитом шагов в ReAct и квадратичным ростом Context Window.
  • Контроль на уровне инструментов: лимиты прямо в контрактах (find_customers(..., limit=5)) не дают агенту «выкачать всю базу» (см. Tool Calling). Для исполнителей max_retries=2, причём «retry не должен скрывать реальную проблему» — после стоп и отчёт. К стоп-условиям петли добавляется No Progress (наряду с max_iterations/tokens/time).
  • Лимит на циклы валидации: при автоисправлении вывода (Validation Loops) ставят retries=3; после исчерпания — UnexpectedModelBehavior, а не бесконечный цикл. Отдельная метрика стоимости — retry_overhead_tokens (токены, сожжённые на исправления).
  • Architect/Editor split (архитектурный приём экономии): дорогая «умная» модель проектирует короткий JSON-план, дешёвая быстрая печатает по нему сотни строк кода → экономия 50–80% бюджета (и качество выше — Редактор не отвлекается на архитектуру). Adaptive compute: Best-of-N тратит токены только там, где нужна надёжность (см. Agent Architecture, Generator Evaluator).
  • Сжатие контекста инструментами (token compression): прослойка между агентом и shell («Rust Token Killer») убирает прогресс-бары, повторяющиеся строки логов и шум, группирует ошибки и оставляет «суть» — экономия 60–90% токенов на dev-командах (в типичной 30-мин сессии Claude Code ~150k → ~45k). Дополняет роутинг (Agent Routing): роутер выбирает модель, компрессор уменьшает вход.
  • Лимиты в LangGraph: на узел вешается RetryPolicy (экспоненциальный backoff + jitter) для временных ошибок; цикличный граф ограничивается лимитом итераций + условным ребром на выход; дорогой replay при time-travel удешевляют кэшированием результатов LLM-узлов в state (см. LangGraph Reliability).
  • Prompt Caching — кэширование повторяющихся системных инструкций даёт до 90% скидки на эту часть промпта: чтение из кэша стоит 0.1× цены обычного входного токена. Но у скидки есть вторая половина, без которой расчёт неверен: запись в кэш дороже обычного входа — 1.25× при пятиминутном сроке жизни и 2× при часовом, а срок по умолчанию пять минут и считается от начала запроса. Отсюда условие окупаемости: префикс должен переиспользоваться чаще, чем истекает TTL. Один вызов с большим системным промптом на кэшировании теряет, а не выигрывает.
  • Композитные метрики бизнес-операции — считать стоимость всей цепочки как parse × scrape × upload, а не отдельных вызовов; + pre-flight budget check перед запуском операции (см. Agent Security).
  • «Налог на безопасность»: DLP-слой (маскирование PII через Presidio + детект инъекций) добавляет в среднем +80 мс latency — это признаётся приемлемой ценой за защиту данных, а не поводом его убирать (см. DLP for LLM).

Рычаги экономии: что сколько даёт

Числа ниже пришли из разных источников и измерены на разном — сводить их в одну «общую экономию» нельзя, рычаги перемножаются только частично и не на всяком трафике.

Рычаг Эффект На чём измерено
Каскадная маршрутизация (Cascade Routing) −65% FinOps-замер: качество 99.2% от флагмана на бенчмарке
Каскад по трём тирам (кейс) −85% 100 тыс. запросов, 70/20/10, нулевое падение CSAT
Роутинг в целом (Agent Routing) −45…85% сводная оценка: 80% трафика на дешёвую модель
Три слоя на FAQ-боте (кейс ниже) −66% 8 тыс. обращений в сутки, банковский чат
Architect/Editor split −50…80% генерация кода: план дорогой моделью, объём — дешёвой
Сжатие контекста (token compression) −60…90% dev-команды, сессия Claude Code ~150k → ~45k токенов
Prompt caching до −90% на префиксе стабильный system prompt, скидка провайдера
Семантический кэш (Semantic Cache) зависит от доли повторов 40% трафика FAQ-бота в кейсе ниже

Порядок внедрения обратный стоимости: сначала то, что вообще не зовёт модель (правила, кэш), потом маршрутизация, и только потом тонкая настройка вызовов. В кейсе ниже самый дешёвый слой — RegEx на вежливость — снял 30% трафика за $0.

FinOps в цифрах: каскад и семантический кэш

Эксплуатационный разбор раскладывает экономику на два рычага и даёт под них конкретные числа.

Каскадная маршрутизация. Поток из 100 тысяч запросов делится по сложности: 70% простых вопросов уходят на дешёвую локальную модель, 20% средней сложности — на модель уровня GPT-4o-mini, и только 10% критических (в его примере — финансовые споры) на топовую. Итог — снижение затрат на 85% при нулевом падении CSAT. Механику выбора модели см. в Agent Routing.

Семантический кэш. Входящий запрос кодируется энкодером (all-MiniLM-L6-v2), ищется по косинусной близости в векторном индексе (Redis Vector Search), и при попадании ответ отдаётся без обращения к модели.

import redis
from sentence_transformers import SentenceTransformer

r = redis.Redis(host='localhost', port=6379, decode_responses=True)
encoder = SentenceTransformer('all-MiniLM-L6-v2')

def get_semantic_cache(user_query: str, threshold: float = 0.94):
    query_vector = encoder.encode(user_query).astype('float32').tobytes()
    # поиск по Redis VSS

Главная опасность здесь — cache drift: при низком пороге похожести кэш начинает отдавать ответ про кредитную карту на вопрос про дебетовую. Отсюда жёсткий порог и принудительный сброс секторов кэша по тегам при обновлении базы знаний RAG.

Конкретно 0.94 здесь — значение для этой связки: энкодер all-MiniLM-L6-v2, банковский домен, Redis VSS. Число не переносится как константа: в разборе самого кэша (Semantic Cache) на эмбеддингах рабочим оказался 0.92, в FinOps-замере с HNSW — 0.96, а на TF-IDF по маленькому корпусу (Cascade Routing) — 0.85, потому что абсолютные значения близости у разных энкодеров разные. Общее правило одно: ошибки асимметричны — промах стоит лишнего вызова модели, ложное попадание отдаёт пользователю чужой ответ, поэтому планка ставится высоко и калибруется под свой энкодер и цену ошибки в домене. Канон по кэшу — Semantic Cache, здесь только его стоимостная сторона.

Оба рычага дают экономию только при работающей атрибуции стоимости — без неё непонятно, какие 70% запросов «простые» (см. Agent Observability).

Атрибуция: разрезы, без которых экономия не считается

Общий счёт от провайдера не отвечает ни на один полезный вопрос. Чтобы он начал отвечать, каждый вызов размечается метаданными до того, как система пошла в прод, — дописать разметку задним числом к уже потраченным деньгам невозможно.

Технический контур: приложение проставляет заголовки (X-Team-ID, X-Feature-ID) → модельный шлюз (Bifrost / LiteLLM / Envoy) → асинхронный эмиттер логов → аналитическое хранилище (ClickHouse).

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    extra_headers={"X-Feature-ID": "contract_review", "X-User-Tier": "pro"},
    metadata={"trace_id": current_trace_id()},
)

Четыре уровня разреза, снизу вверх: окружение (prod / staging / testing / CI) → команда или сервис → фича → клиент или тенант.

Верхний разрез нужен, чтобы увидеть убыточную фичу. Пример:

Фича Стоимость LLM Доход Маржа
Summarize $4 500 $6 200 +$1 700
Deep Search $8 000 $2 100 −$5 900
Intent-Classification $1 200 $2 000 +$800

Без разбивки по фичам Deep Search растворяется в общем счёте и продолжает работать в убыток годами.

Считать надо реальный usage. Официальный объект usage из ответа API (prompt_tokens, completion_tokens, cached_tokens) — единственный корректный источник; оценка через len(text)/4 в проде — антипаттерн. Стандартная раскладка полей — OpenTelemetry Semantic Conventions for GenAI: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.system (см. OpenInference).

Три способа собрать телеметрию: proxy-based (Helicone, Portkey) — быстрый старт и встроенный кэш ценой лишнего сетевого хопа и SaaS-зависимости; SDK-based трассировка (Langfuse, LangSmith) — глубокий DAG и OpenTelemetry-native, но требует интеграции в код; собственный шлюз с базой — полный контроль и нулевой вендор-лок ценой разработки.

Стоимость судьи: инженерная задача, а не повод его выключить

Проверка качества сама стоит денег, и обычно это довод её сократить: судья в LLM as Judge выносится из горячего пути в том числе по цене. Но «дорого» здесь чаще всего означает «промпт разложен так, что кэш не работает», а не «задача дорогая по существу».

Замеренный прогон верификации агентных траекторий даёт раскладку целиком:

Статья Значение
вызовов верификатора 4 320
входные токены 272 551 552
из них из кэша 214 712 320 — 78.8%
некэшированный вход 57 839 232
выход 32 441 600
токены рассуждения 26 102 144

Три вещи, которые отсюда следуют:

Основная масса — вход, а не выход. Входных токенов почти в восемь раз больше выходных: судья читает много и пишет мало. Значит рычаг — не «пусть отвечает короче», а кэш префикса (Prompt Caching), и он же объясняет, почему раскладка промпта здесь важнее выбора модели.

Четыре пятых входа не оплачиваются по полной ставке. Наивная оценка «272 млн токенов на прогон» завышает счёт примерно вчетверо. Судья, признанный неподъёмным по такой оценке, может оказаться дешевле порога, ради которого его выключали.

Токены рассуждения — отдельная статья, и она сопоставима с выходом. 26 млн против 32 млн: у судьи, которому велено рассуждать до вердикта, расход на рассуждение почти равен расходу на ответ. В атрибуции выше их стоит разделять, иначе бюджет на оценку выглядит меньше, чем есть.

Оговорка о происхождении, которая тут важнее обычного. Числа получены авторами инструмента о собственном инструменте — обычно это повод понизить доверие. Здесь смягчает форма: расход измерен, а не заявлен, и авторы это оговаривают отдельно — считаются только вызовы, реально сделанные в прогоне. Это сильнейшая форма самоотчёта, но независимой перепроверки всё равно нет.

Кейс: банковский FAQ-бот, −66% за три слоя

Baseline: 8 000 обращений в сутки идут напрямую во флагманскую модель, полная история переписки (в среднем 15 шагов) передаётся на каждом запросе, кэша и лимитов нет, метатеги не пишутся. Стоимость — $120 в сутки, $3 600 в месяц на элементарном чате.

Разбор трафика показал: 30% — вежливость («спасибо», «понял»), 40% — повторяющиеся вопросы по тарифам, 30% — сложные персонализированные запросы.

Слой Что закрывает Стоимость
RegEx и шаблоны 30% вежливости $0
Семантический кэш + младшая модель 40% FAQ $4/день
Флагман с prompt caching 30% сложных $32/день

Unit Cost Index: 100 → 34, снижение расходов на 66%. Обратите внимание, что первый слой вообще не использует модель — самая дешёвая оптимизация оказалась не про LLM.

Пример

Architect/Editor split из раздела «Как работает» в коде:

def architect_editor_solve(task):
    plan = client.chat.completions.create(          # дорогая модель: короткий план
        model=MODEL_STRONG, response_format={"type": "json_object"},
        messages=[{"role": "system", "content": ARCHITECT_SYSTEM},
                  {"role": "user", "content": task}]).choices[0].message.content
    code = client.chat.completions.create(          # дешёвая модель: пишет код по плану
        model=MODEL_FAST,
        messages=[{"role": "system", "content": EDITOR_SYSTEM},
                  {"role": "user", "content": f"{task}\nПЛАН:\n{plan}"}]).choices[0].message.content
    return {"plan": plan, "code": code}

Связано с

  • FinOps AI — Token Budgeting как один из пяти архитектурных рычагов

  • Request Coalescing — слой раньше кэша: вызовы, которых не должно было быть

  • ReAct — где ставится лимит итераций

  • Reasoning Effort — усилие как источник затрат

  • Agent Routing — budget-aware: при низком остатке форсим cheap-модель

  • Context Window — квадратичный рост стоимости

  • Validation Loops — retries=3 и retry_overhead_tokens как часть бюджета

  • LangGraph Reliability — RetryPolicy, лимиты циклов, кэш replay в LangGraph

  • LLM as Judge — сам судья: почему его выносят из горячего пути и чем платит оценка

  • Prompt Caching — рычаг, которым стоимость судьи и снижается: раскладка промпта важнее выбора модели