Normal path: execute → tool → observe.
Проблема (з реального продакшену)
Нічого не змінювали.
Окрім того, що:
- хтось “трошки” підправив промпт
- tool почав повертати нове поле
- модель отримала version bump
- індекс retrieval оновився
Агент все ще “працює”.
Але він повільніший. Викликає інші tools. Приймає інші рішення. Промахується по edge cases. Ніхто не помічає, доки не помічає користувач — а користувачі не QA з ніжними руками.
Це silent drift: поведінка в проді змінюється без очевидної аварії.
Чому це ламається в продакшені
1) Output моделі не стабільний
Навіть без версійних змін є варіативність. З версійними змінами — shift гарантований.
Якщо ти не міряєш shift — ти його не бачиш.
2) Tools теж дрейфують
Tool outputs змінюються:
- еволюціонує schema
- змінюються error payloads
- змінюється порядок
- змінюються defaults
Якщо агент чутливий до таких змін — він дрейфує.
3) Prompts — це код (але їх рідко так трактують)
Prompt edits часто шипляться без:
- тестів
- rollback
- canary
- метрик
Так з’являється “ми змінили одне речення, і тепер він викликає http.get у 10 разів частіше”.
4) Drift спершу видно в cost/latency, а потім у correctness
Ранні сигнали зазвичай операційні:
- tokens/request повзуть вгору
- tool calls/run повзуть вгору
- p95 latency повзе вгору
- stop reasons змінюються
Якщо ти дивишся тільки “success rate” — ти це пропустиш.
5) Фікс — feedback loop: golden tasks + replay + canary
Потрібен production‑shaped eval loop:
- golden tasks, які відображають реальний ворклоад
- replay реальних trace’ів (з редекцією)
- canary rollout для змін model/prompt/tool
- алерти на дельти поведінки
Приклад реалізації (реальний код)
Мінімальний harness для “golden tasks”:
- запускає задачі на baseline і candidate версіях
- порівнює stop reasons і кількість tool calls
- валиться, якщо дельти більші за поріг
from dataclasses import dataclass
@dataclass(frozen=True)
class GoldenTask:
id: str
input: str
def run_agent(version: str, task: GoldenTask) -> dict:
# Pseudo: run your agent with pinned model/prompt/tools config.
return agent_run(version=version, input=task.input) # (pseudo)
def score(run: dict) -> dict:
return {
"stop_reason": run.get("stop_reason"),
"tool_calls": int(run.get("tool_calls", 0)),
"tokens": int(run.get("tokens_total", 0)),
}
def drift_check(tasks: list[GoldenTask], *, baseline: str, candidate: str) -> None:
for t in tasks:
b = score(run_agent(baseline, t))
c = score(run_agent(candidate, t))
if c["stop_reason"] != b["stop_reason"]:
raise RuntimeError(f"[{t.id}] stop_reason drift: {b['stop_reason']} -> {c['stop_reason']}")
if c["tool_calls"] > b["tool_calls"] + 3:
raise RuntimeError(f"[{t.id}] tool_calls drift: {b['tool_calls']} -> {c['tool_calls']}")export function score(run) {
return {
stopReason: run.stop_reason,
toolCalls: Number(run.tool_calls || 0),
tokens: Number(run.tokens_total || 0),
};
}
export function driftCheck(tasks, { baseline, candidate, runAgent }) {
for (const t of tasks) {
const b = score(runAgent(baseline, t));
const c = score(runAgent(candidate, t));
if (c.stopReason !== b.stopReason) {
throw new Error("[" + t.id + "] stop_reason drift: " + b.stopReason + " -> " + c.stopReason);
}
if (c.toolCalls > b.toolCalls + 3) {
throw new Error("[" + t.id + "] tool_calls drift: " + b.toolCalls + " -> " + c.toolCalls);
}
}
}Це грубо — і це нормально. Воно ловить найчастіший drift:
- зміна stop reasons (нові таймаути, нові лупи)
- inflation tool calls (cost drift)
Далі додаєш доменні correctness checks. Але починай із operational drift — його легше міряти і він часто з’являється першим.
Реальний інцидент (з цифрами)
Ми оновили версію моделі для support‑агента. Без canary, без golden tasks.
Нова модель була “краща в ретельності”.
І вона частіше викликала search.read.
Impact за 24 години:
- tool calls/run: 2.8 → 9.6
- p95 latency: 2.7s → 7.4s
- spend: +$460 відносно baseline
- correctness очевидно не впала — тож ніхто не помітив, доки не помітив рахунок
Fix:
- golden tasks із drift thresholds (tool calls, stop reasons)
- canary rollout (1% трафіку) з auto-rollback на спайках
- weekly replay анонімізованих реальних trace’ів
- метрики/дашборди: tokens, tool calls, stop reasons, latency
Drift не захоплює. Це просто спосіб, яким прод ламається, коли ніхто не дивиться.
Компроміси
- Golden task suite треба підтримувати.
- Canary додає складності rollout’у (але воно того варте).
- Частина drift може бути “корисною” (кращі відповіді). Все одно міряй, щоб вирішити.
Коли НЕ варто
- Якщо агент низькоризиковий і інформативний — можна бути м’якшим (але spend все одно монітор).
- Якщо task distribution ще не стабільний — почни зі smoke tests і нарощуй golden tasks.
- Якщо не можеш безпечно replay traces (PII) — використовуй синтетичні задачі й строгі budgets.
Чекліст (можна копіювати)
- [ ] Golden tasks, які відображають реальний ворклоад
- [ ] Replay set із реальних trace’ів (redacted)
- [ ] Canary rollout із rollback triggers
- [ ] Drift thresholds: tool calls, tokens, latency, stop reasons
- [ ] Pin versions: model/prompt/tool на реліз
- [ ] Weekly “what changed” review
Безпечний дефолтний конфіг (JSON/YAML)
releases:
canary_percent: 1
rollback_on:
tool_calls_per_run_increase_pct: 50
tokens_per_request_increase_pct: 50
latency_p95_increase_pct: 50
eval:
golden_tasks_required: true
drift_thresholds:
tool_calls_delta: 3
stop_reason_changes: 0
FAQ (3–5)
Використовується в патернах
Пов’язані відмови
Q: Drift завжди поганий?
A: Ні. Поганий — невиміряний drift. Без метрик ти не відрізниш покращення від повільної дорогої регресії.
Q: Що моніторити першим?
A: Tool calls/run, tokens/request, latency p95 і stop reasons. Вони рухаються до скарг на correctness.
Q: Canary потрібен для кожної правки промпта?
A: Для high-traffic або high-stakes агентів — так. Стався до промптів як до code changes.
Q: Як безпечно replay production traces?
A: Редактуй PII, зберігай args hashes де можливо, і реплей tool results зі snapshots.
Пов’язані сторінки (3–6 лінків)
- Foundations: Чому агенти ламаються в продакшені · Як ліміти LLM впливають на агентів
- Failure: Token overuse · Budget explosion
- Governance: Tool permissions (allowlists)
- Production stack: Production agent stack