Тихий drift агента (регресія якості) + детекція + код

  • Побач ранні сигнали, поки рахунок не поліз вгору.
  • Зрозумій, що ламається в проді й чому.
  • Скопіюй guardrails: budgets, stop reasons, validation.
  • Знай, коли це не справжня root cause.
Сигнали виявлення
  • Tool calls на run зростають (або повторюються з args hash).
  • Витрати/токени ростуть без кращих результатів.
  • Retries стають постійними (429/5xx).
Агенти не ламаються одним махом. Вони дрейфують через зміни моделі/tools/prompts, доки ти не зашипиш регресію в прод. Canary, golden tasks, replay і метрики ловлять drift рано.
На цій сторінці
  1. Проблема (з реального продакшену)
  2. Чому це ламається в продакшені
  3. 1) Output моделі не стабільний
  4. 2) Tools теж дрейфують
  5. 3) Prompts — це код (але їх рідко так трактують)
  6. 4) Drift спершу видно в cost/latency, а потім у correctness
  7. 5) Фікс — feedback loop: golden tasks + replay + canary
  8. Приклад реалізації (реальний код)
  9. Реальний інцидент (з цифрами)
  10. Компроміси
  11. Коли НЕ варто
  12. Чекліст (можна копіювати)
  13. Безпечний дефолтний конфіг (JSON/YAML)
  14. FAQ (3–5)
  15. Пов’язані сторінки (3–6 лінків)
Інтерактивний флоу
Сценарій:
Крок 1/2: Execution

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
  • валиться, якщо дельти більші за поріг
PYTHON
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']}")
JAVASCRIPT
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:

  1. golden tasks із drift thresholds (tool calls, stop reasons)
  2. canary rollout (1% трафіку) з auto-rollback на спайках
  3. weekly replay анонімізованих реальних trace’ів
  4. метрики/дашборди: 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)

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)

Drift завжди поганий?
Ні. Поганий — невиміряний drift. Без метрик ти не відрізниш покращення від повільної дорогої регресії.
Що моніторити першим?
Tool calls/run, tokens/request, latency p95 і stop reasons. Вони рухаються до скарг на correctness.
Canary потрібен для кожної правки промпта?
Для high-traffic або high-stakes агентів — так. Стався до промптів як до code changes.
Як безпечно replay production traces?
Редактуй PII, зберігай args hashes де можливо, і реплей tool results зі snapshots.

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 лінків)

Не впевнені, що це ваш кейс?

Спроєктувати агента →
⏱️ 6 хв читанняОновлено Бер, 2026Складність: ★★☆
Реалізувати в OnceOnly
Guardrails for loops, retries, and spend escalation.
Використати в OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Kill switch та аварійна зупинка
  • Audit logs та трасування
  • Ідемпотентність і dedupe
  • Дозволами на інструменти (allowlist / blocklist)
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Приклад policy (концепт)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}
Автор

Цю документацію курують і підтримують інженери, які запускають AI-агентів у продакшені.

Контент створено з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Патерни та рекомендації базуються на постмортемах, режимах відмов і операційних інцидентах у розгорнутих системах, зокрема під час розробки та експлуатації governance-інфраструктури для агентів у OnceOnly.