Normal path: execute → tool → observe.
Проблема (з реального продакшену)
Все ок.
Потім агент стає “розумнішим”.
Він починає пхати:
- більше історії
- більше tool output
- більше “корисного контексту”
Лейтенсі повзе вгору. Cost повзе вгору. А потім одного дня policy урізається, і агент починає вести себе дивно.
Token overuse — не теорія. Це прод‑інцидент, який можна графити.
Чому це ламається в продакшені
1) Контекст росте за замовчуванням
Якщо не капнути — контекст росте:
- chat history росте
- memory росте
- tool outputs ростуть (особливо HTML)
Модель не знає твої бюджети. Вона з’їсть усе, що ти покладеш у промпт.
2) Prompt bloat призводить до truncation
Великі промпти підвищують шанс, що:
- верх промпта відвалиться
- “system/policy” стане першою жертвою
Коли policy немає, модель не стає “злою”. Вона стає без обмежень.
3) Tool outputs — token bombs
Сирий HTML, логи, stack traces — величезні. Якщо вставляти їх verbatim:
- токени вибухають
- модель часто навіть не може ефективно використати контент
Extract → summarize → додай маленький слайс.
4) Memory робить гірше
Memory, яка “зберігає все”, часто “показує все”. Це не memory. Це інфляція промпта.
5) Це не фікситься “візьми модель з більшим контекстом”
Більший контекст:
- дорожчий
- повільніший
- і все одно трюнкатить (просто пізніше)
Фікс — context budget і context builder.
Приклад реалізації (реальний код)
Дешевий практичний budgeter:
- cap на загальний контекст
- тримає найновіше
- summarises (placeholder) коли over budget
Тут використовуємо символи; у проді краще рахувати токени.
from dataclasses import dataclass
from typing import Iterable
@dataclass(frozen=True)
class ContextBudget:
max_chars: int = 12_000
def summarize(text: str) -> str:
# In real code: a dedicated summarization step with its own budget.
return text[:2000] + "…"
def build_context(chunks: Iterable[str], *, budget: ContextBudget) -> str:
parts: list[str] = []
total = 0
for c in chunks:
parts.append(c)
total += len(c)
ctx = "\n\n".join(parts)
if len(ctx) <= budget.max_chars:
return ctx
# Over budget: summarize oldest parts first.
# This is simplistic, but it prevents unbounded growth.
over = len(ctx) - budget.max_chars
head = ctx[: over + 1000]
tail = ctx[over + 1000 :]
return summarize(head) + "\n\n" + tailexport function summarize(text) {
// Real code: dedicated summarization with its own budget.
return text.slice(0, 2000) + "…";
}
export function buildContext(chunks, { maxChars = 12_000 } = {}) {
const ctx = chunks.join("\\n\\n");
if (ctx.length <= maxChars) return ctx;
// Summarize the oldest part.
const over = ctx.length - maxChars;
const head = ctx.slice(0, over + 1000);
const tail = ctx.slice(over + 1000);
return summarize(head) + "\\n\\n" + tail;
}Це не дасть “ідеальної пам’яті”. Це дасть те, що треба першим: бюджет, який не дає промпту рости безмежно.
Реальний інцидент (з цифрами)
У нас був агент, який відповідав “чому впав цей job?”. Він вставляв повний stack trace і логи в промпт.
Працювало… доки клієнт не вставив 2MB лог‑blob.
Impact:
- tokens/request стрибнули з ~4k → 45k
- p95 latency зросла з 3.2s → 19s
- spend виріс на ~$520 за день
- найгірше: truncation з’їв policy, і агент почав пропонувати небезпечні tools
Fix:
- input caps (max chars) на user‑provided логи
- витягувати структуровані поля (errors, timestamps) замість raw dumps
- context budgeter + summarization tier
- метрики/алерти на tokens/request
Логи корисні. Сирі логи — не формат промпта.
Компроміси
- Саммарі гублять деталі (але raw dumps все одно не допомагали).
- Строгі caps можуть відхиляти power-user запити. Запропонуй async upload.
- Token counting додає складності. Він швидко окупається на масштабі.
Коли НЕ варто
- Якщо тобі потрібне точне reasoning по довгих документах — agent loop може бути не тим. Використовуй targeted retrieval + workflows.
- Якщо ти не можеш безпечно summarise untrusted text — не додавай його wholesale.
- Якщо ти не можеш міряти токени — почни сьогодні з char budgets і додай токени потім.
Чекліст (можна копіювати)
- [ ] Cap розмір user text (логи, HTML, PDFs)
- [ ] Cap розмір tool output до потрапляння в контекст
- [ ] Context builder з hard budget (tokens/chars)
- [ ] Summarization tier зі своїм budget
- [ ] Повторюй критичні constraints кожен turn (переживає truncation)
- [ ] Метрики: tokens/request, latency, spend/run
- [ ] Алерти на спайки і drift
Безпечний дефолтний конфіг (JSON/YAML)
context:
max_prompt_tokens: 2500
max_untrusted_chars: 8000
summarize_when_over_budget: true
policy:
repeat_critical_constraints_every_turn: true
metrics:
track: ["tokens_per_request", "latency_p95", "spend_per_run"]
FAQ (3–5)
Використовується в патернах
Пов’язані відмови
Q: Можна просто купити модель із більшим контекстом?
A: Можна. Але заплатиш лейтенсі й грошима, і все одно колись трюнкатнеш. Budgeting дешевший.
Q: Як точно рахувати токени?
A: Використовуй токенайзер провайдера. Якщо не можеш — почни з char caps і додай token counting потім.
Q: Чи треба зберігати все в memory?
A: Зберігати events — так. Показувати моделі все — ні. Memory ≠ prompt size.
Q: Навіщо повторювати policy constraints?
A: Бо truncation з’їдає верх промпта. Повторення тримає правила живими.
Пов’язані сторінки (3–6 лінків)
- Foundations: Типи пам’яті агента · Як ліміти LLM впливають на агентів
- Failure: Budget explosion · Галюциновані джерела
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack