Проблема
Запит виглядає простим: перевірити статус інциденту і дати короткий підсумок.
У трейсах видно інше: за 8 хвилин один run витратив понад 38k токенів, хоча подібні задачі раніше вкладались у ~4-6k. Половина бюджету пішла не у відповідь, а в контекст: історія, сирі логи і великі tool outputs.
Для задачі такого класу це може бути ~$2.80 замість звичних ~$0.20. І при цьому якість відповіді майже не зросла.
Система не падає.
Вона просто повільно роздуває prompt, latency і витрати.
Аналогія: уяви валізу, в яку перед кожною поїздкою додають "ще одну важливу річ", але ніколи нічого не виймають. Спочатку це майже непомітно. Потім ти витрачаєш більше часу, грошей і сил просто на те, щоб нести зайве. Token overuse в агентів працює так само.
Чому це стається
Token overuse зазвичай виникає не через саму модель, а через слабкий контроль контекстного бюджету в runtime.
У production зазвичай так:
- в контекст кожного нового кроку додають усе підряд: history, retrieval, tool output;
- raw payload (логи, HTML, JSON) потрапляє в prompt без стиснення;
- без пер-джерельних caps і summarization контекст росте кожен turn;
- токени йдуть у "перенесення зайвого", а не в корисний прогрес;
- довгі reasoning-цикли без no-progress перевірки ще сильніше роздувають історію.
У trace це зазвичай видно як стабільний ріст prompt_tokens,
де кожен новий turn стає дорожчим за попередній,
навіть якщо сама задача не ускладнюється.
Без контролю цей ріст починає шкодити latency, вартості і якості.
Які збої трапляються найчастіше
У production найчастіше видно чотири повторювані патерни token overuse.
Роздутий prompt (Prompt bloat)
У запит до моделі потрапляє занадто багато контексту "про всяк випадок".
Типова причина: немає max_prompt_tokens і пріоритезації chunk'ів.
Token bombs у tool output
Інструменти повертають великі payload (HTML, логи, stack traces), які йдуть у prompt майже без обробки.
Типова причина: немає caps, а payload не проходить extraction або summarization перед додаванням у prompt.
Інфляція memory/history
Агент накопичує історію turn за turn, але не стискає старі ділянки. У результаті нові кроки стають дедалі дорожчими. Якщо run ще й довго крутиться без прогресу, ця інфляція росте ще швидше.
Типова причина: memory використовується як "архів", а не як budgeted context.
Тиха деградація через truncation
Коли контекст перевищує реальне вікно, важливі частини prompt обрізаються. Часто першими зникають policy-обмеження або критичні інструкції.
Типова причина: немає явного контролю, що саме викидати і в якому порядку.
Як виявляти ці проблеми
Token overuse добре видно по поєднанню cost, latency і контекст-метрик.
| Метрика | Сигнал token overuse | Що робити |
|---|---|---|
prompt_tokens_per_run | стабільний ріст токенів на run | ввести max_prompt_tokens і budgeted context builder |
tool_output_tokens | великі сирі payload у prompt | caps + extraction/summarization до моделі |
tokens_per_success | якість та сама, але ціна росте | перевірити unit economics і обмежити непотрібний контекст |
context_truncation_rate | часті обрізання prompt | пріоритезувати policy і нові факти, стискати старе |
latency_p95 | затримка росте разом із токенами | зменшити контекст і обмежити fan-out retrieval або tool output |
Як відрізнити перевитрату від справді складної задачі
Не кожен великий prompt — погано. Ключове питання: чи дають додаткові токени реальний приріст якості.
Нормально, якщо:
- складний запит справді потребує більше джерел і перевірок;
tokens_per_successросте разом із точністю;- додатковий контекст дає нові факти, а не повторює вже відоме.
Небезпечно, якщо:
- токени ростуть швидше, ніж success rate;
- багато контексту дублює старі turn або сирі технічні дампи;
- latency і вартість ростуть, а відповіді майже не змінюються.
Як зупиняти такі збої
Практично це виглядає так:
- вводиш жорсткі ліміти:
max_prompt_tokens+ caps на history/tool/retrieval; - додаєш context builder з пріоритетами (policy і нові факти вище за старі логи);
- старі або великі фрагменти стискаєш у summarization-tier;
- при перевищенні бюджету повертаєш stop reason або partial, а не шлеш "переповнений" prompt.
Мінімальний guard для token budget:
from dataclasses import dataclass
@dataclass(frozen=True)
class TokenLimits:
max_prompt_tokens: int = 7000
max_history_tokens: int = 1800
max_tool_tokens: int = 2500
max_retrieval_tokens: int = 2200
class TokenBudgetGuard:
def __init__(self, limits: TokenLimits = TokenLimits()):
self.limits = limits
self.total_prompt_tokens = 0
self.by_source = {
"history": 0,
"tool": 0,
"retrieval": 0,
}
def _cap_for(self, source: str) -> int:
if source == "history":
return self.limits.max_history_tokens
if source == "tool":
return self.limits.max_tool_tokens
if source == "retrieval":
return self.limits.max_retrieval_tokens
return self.limits.max_prompt_tokens
def add_chunk(self, source: str, tokens: int) -> str | None:
if self.by_source.get(source, 0) + tokens > self._cap_for(source):
return f"token_overuse:{source}_cap"
if self.total_prompt_tokens + tokens > self.limits.max_prompt_tokens:
return "token_overuse:prompt_budget_exceeded"
self.by_source[source] = self.by_source.get(source, 0) + tokens
self.total_prompt_tokens += tokens
return None
Це базовий guard.
У production його зазвичай доповнюють точним token counting від провайдера,
summarization-tier для старих chunk'ів і окремими stop reasons для truncation.
add_chunk(...) викликають до додавання фрагмента в prompt,
щоб budget працював як gate, а не як постфактум перевірка.
Де це реалізується в архітектурі
У production контроль token overuse майже завжди розкладений між трьома шарами системи.
Memory Layer керує тим, що зберігати надовго, а що подавати в поточний prompt. Якщо memory = "показати все", витрати неминуче ростуть.
Tool Execution Layer відповідає за нормалізацію і стиснення великих payload до того, як вони потрапляють у контекст моделі. Тут ставлять caps на output, витягують потрібні факти з великих payload і стискають їх до того, як вони потрапляють у prompt.
Agent Runtime тримає execution budgets:
max_prompt_tokens, stop reasons, кероване завершення і fallback при перевищенні лімітів.
Саме тут токен-бюджет стає production-правилом, а не побажанням.
Самоперевірка
Швидка перевірка перед релізом. Відмічайте пункти і дивіться статус нижче.
Це короткий sanity-check, а не формальний аудит.
Прогрес: 0/8
⚠ Є сигнали ризику
Бракує базових контролів. Закрийте ключові пункти цього чекліста перед релізом.
FAQ
Q: Чи можна просто перейти на модель із більшим context window?
A: Можна, але це зазвичай дорожче і повільніше.
Без budget-контролю проблема не зникає, а лише переноситься на більший ліміт.
Q: Що краще на старті: рахувати токени чи символи?
A: Найкраще — токени провайдерським лічильником. Якщо це недоступно, починай із консервативних char caps і переходь на токени.
Q: Що стискати першим при перевищенні бюджету?
A: Зазвичай старі turn і великі сирі tool outputs. Policy та найновіші факти мають залишатися.
Q: Що показувати користувачу, коли prompt budget вичерпано?
A: Причину зупинки, що вже обробили, і безпечний наступний крок: partial result, звуження запиту або повторний запуск із меншим контекстом.
Token overuse майже ніколи не виглядає як гучна аварія. Це повільна деградація, яка роздуває latency і вартість без явного падіння сервісу. Тому production-агентам потрібні не лише кращі моделі, а й жорсткий контроль контекстного бюджету.
Пов'язані сторінки
Якщо ця проблема виникла у production, корисно також подивитися:
- Чому AI агенти ламаються — загальна карта збоїв у production.
- Budget explosion — як перевитрата токенів переростає у фінансовий інцидент.
- Tool spam — як зайві виклики інструментів роздувають контекст і витрати.
- Context poisoning — як неякісний контекст псує рішення агента.
- Memory Layer — де відділяти довготривалу пам'ять від prompt-контексту.
- Agent Runtime — де ставити токен-ліміти, stop reasons і fallback.