Ідея за 30 секунд
Дебаг запусків агентів допомагає перейти від симптому до причини: що саме зламалось, на якому кроці і чому.
Для цього потрібно звести разом трейсинг, логи й метрики одного проблемного run.
Без цього команда часто бачить тільки фінальну помилку, але не бачить повний шлях до неї.
Основна проблема
В агентних системах інцидент рідко має одну очевидну причину.
Фінальна помилка може бути лише наслідком: справжня проблема могла початися раніше, наприклад із повільного tool call, невдалого retry або регресії після релізу. Без системного debugging це важко локалізувати швидко.
Далі розберемо, як читати ці сигнали і стабільно знаходити root cause.
У production це часто виглядає так:
- в логах багато подій, але немає чіткої послідовності;
- причина змішується із супутніми помилками;
- інцидент «виправили», але він повертається після релізу;
- MTTR росте, бо команда щоразу відновлює контекст інциденту з нуля.
Саме тому debugging run має бути окремим операційним процесом, а не ручним пошуком «першого error».
Як це працює
Практичний debugging run зазвичай складається з трьох рівнів:
- контекст run (
run_id,trace_id,release,workflow); - докази → аналіз (
spans,logs,metrics,stop_reason); - рішення (гіпотеза → фікс → перевірка через replay і тести).
Ці рівні відповідають на питання «де проблема», «чому вона виникла» і «чи справді фікс її прибирає». Трейсинг показує шлях, логи показують події, а метрики показують масштаб і тренд.
Багато логів ≠ швидкий debugging. Швидкість з'являється не від кількості даних, а від їх кореляції навколо одного run.
Типові production-сигнали для debugging run
| Сигнал | Де дивитися | Навіщо потрібен |
|---|---|---|
| first_error_span | трейсинг | пошук точки, де помилка з’явилася вперше |
| slowest_span | трейсинг + метрики | кандидат на bottleneck (потребує перевірки) |
| stop_reason | лог run_finished | розуміння, чим завершився run |
| error_class | tool_result / llm_result логи | відділення timeout від логічної помилки |
| repeated_tool_calls | tool_call логи + tool metrics | виявлення повторних викликів (loops, retries, tool spam) |
| run_latency_p95 | метрики | перевірка, чи інцидент уже системний |
| release_diff | дашборд порівняння релізів | виявлення регресії після змін |
| synthetic_run_status | health checks | перевірка впливу на критичний workflow |
Щоб debugging був стабільним, ці сигнали зазвичай сегментують за release, workflow, model і tool.
Важливо: не додавай у метрики висококардинальні labels (run_id, request_id, user_id). Для цього краще використовувати логи і трейсинг.
Як читати debugging-layer
Який run зламався → на якому кроці → чому саме це сталося. Це три рівні, які завжди потрібно дивитися разом.
Важливо дивитися на тренди і різницю між релізами, а не лише на одну аварійну подію.
Далі дивимось на комбінації сигналів:
first_error_span=tool_call+tool_error_rate↑ → проблема у конкретному tool-layer;run_latency_p95↑ +tool_latency_p95≈ стабільний → імовірна проблема в LLM або runtime-логіці;repeated_tool_calls↑ +stop_reason=max_steps→ агент застрягає в циклі;error_rate↑ після релізу +release_diffпозитивний → регресія змін, а не разовий інцидент;synthetic_run_status=fail+health_score↓ → проблема вже впливає на критичний workflow.
Коли використовувати
Формальний debugging-flow не завжди потрібен.
Для простого single-shot сценарію без tools інколи вистачає базового логування і ручного перегляду помилки.
Але системний підхід до debugging стає критичним, коли:
- run містить кілька reasoning-кроків і tool calls;
- інциденти впливають на latency, cost або SLO;
- релізи виходять часто і важливо ловити регресії;
- у команді є on-call процес і потрібен передбачуваний MTTR.
Приклад реалізації
Нижче — спрощений приклад функції, яка збирає evidence для одного run і формує базову гіпотезу. Це не замінює повноцінний incident tooling, але добре показує робочий debugging-процес.
from collections import Counter
def debug_run(run_id, trace_events, log_events, debug_metrics_snapshot):
run_spans = sorted(
[s for s in trace_events if s.get("run_id") == run_id],
key=lambda s: s.get("started_at_ms", 0),
)
run_logs = [e for e in log_events if e.get("run_id") == run_id]
first_error_span = next((s for s in run_spans if s.get("status") == "error"), None)
# slowest_span може бути None, якщо run не містить spans
slowest_span = max(run_spans, key=lambda s: s.get("latency_ms", 0), default=None)
stop_reason = "unknown"
for event in reversed(run_logs):
if event.get("event") == "run_finished":
stop_reason = event.get("stop_reason", "unknown")
break
seen_signatures = set()
repeated_tools = Counter()
for event in run_logs:
if event.get("event") != "tool_call":
continue
signature = (event.get("tool"), event.get("args_hash"))
if signature in seen_signatures:
repeated_tools[event.get("tool")] += 1
else:
seen_signatures.add(signature)
hypotheses = []
if first_error_span and first_error_span.get("step_type") == "tool_call":
hypotheses.append("Ймовірний збій у tool-layer: перевірити доступність інструмента та timeout policy.")
if repeated_tools:
hypotheses.append("Є повторні tool calls: перевірити dedupe/cache і stop conditions.")
if slowest_span and debug_metrics_snapshot.get("run_latency_p95_ms", 0) > debug_metrics_snapshot.get("slo_latency_ms", 2500):
hypotheses.append("p95 latency вища за SLO: локалізувати bottleneck за slowest_span.")
if debug_metrics_snapshot.get("release_error_rate_delta", 0) > 0:
hypotheses.append("Після релізу виріс error_rate: перевірити зміни prompt/runtime/tool routing.")
return {
"run_id": run_id,
"first_error_span": first_error_span,
"slowest_span": slowest_span,
"stop_reason": stop_reason,
"repeated_tools": dict(repeated_tools),
"hypotheses": hypotheses,
}
Debugging не вважається завершеним, поки проблему не можна відтворити (replay) і підтвердити, що фікс стабільно її прибирає. Якщо проблему не вдається відтворити, debugging переходить у режим гіпотез, а не доказів.
Replay ≠ optional.
Без replay — це припущення.
З replay — це доказ.
Ось як це може виглядати в короткому debugging snapshot:
| Run | first_error_span | slowest_span | stop_reason | Висновок |
|---|---|---|---|---|
| run_9fd2 | tool_call: search_docs | tool_call: search_docs (1.8s) | tool_error | tool degraded + retries |
| run_a113 | llm_generate | llm_generate (2.4s) | step_error | модельний збій після релізу |
| run_d77c | — | reasoning (3.1s) | max_steps | loop без явної помилки |
Investigation
Коли спрацьовує інцидентний сигнал:
- зафіксувати
run_id,trace_id,releaseі проблемний workflow; - у трейсингу знайти
first_error_spanіslowest_span; - у логах перевірити
stop_reason,error_class,repeated_tool_calls; - у метриках підтвердити масштаб проблеми (спайк чи тренд) і перевірити різницю між релізами.
Типові помилки
Навіть коли observability налаштована, debugging часто ламається через типові помилки нижче.
Починають розбір з «будь-якої» помилки в логах
Без прив'язки до конкретного run_id команда змішує симптоми з різних інцидентів.
У такому режимі важко відрізнити локальну помилку від каскадного збою.
Немає кореляції trace + logs + metrics
Якщо трейсинг, логи і метрики дивляться окремо, гіпотези часто суперечать одна одній. Через це MTTR росте навіть для простих збоїв інструмента.
Ігнорують repeated calls і stop_reason
Без цих сигналів легко пропустити loops і retry storms. Це часто маскує ранню фазу спаму інструментами.
Не порівнюють інцидент з попереднім релізом
Без release_diff команда не бачить, чи проблема з'явилась після змін.
У результаті регресія довше лишається в production.
Закривають інцидент без replay і перевірки
Фікс може прибрати симптом, але не причину. Це підвищує ризик повторного часткового збою.
Самоперевірка
Нижче — короткий checklist базового debugging-flow перед релізом.
Прогрес: 0/9
⚠ Бракує базової observability
Систему буде складно дебажити в production. Почніть з run_id, structured logs і tracing tool calls.
FAQ
Q: З чого починати debugging одного проблемного run?
A: Почни з run_id і trace_id: знайди first_error_span, перевір stop_reason, потім підтвердь масштаб у метриках. Саме first_error_span — найшвидший спосіб знайти точку збою.
Q: Що важливіше для debugging: трейсинг чи логи?
A: Вони працюють разом: трейсинг показує шлях кроків, а логи дають деталі подій (error_class, args_hash, policy decision).
Q: Як зрозуміти, що це регресія релізу, а не разовий збій?
A: Порівняй error_rate, latency_p95, repeated_tool_calls між релізами. Якщо сигнал стабільно гірший після релізу, це регресія.
Q: Який мінімум даних потрібен, щоб дебажити за 10-15 хвилин?
A: Мінімум: run_id, trace_id, first_error_span, stop_reason, error_class, latency_p95 і контекст релізу.
Пов'язані сторінки
Далі за темою:
- Трейсинг агента — як бачити шлях одного run крок за кроком.
- Логування агентів — які події потрібні для розбору інциденту.
- Метрики агентів — як відрізняти разовий збій від тренду.
- Health checks агентів — ранні сигнали деградації до інциденту.
- Алерти збоїв агентів — як вчасно запускати investigation.