Проблема
Запит виглядає стандартним: перевірити оплату і підтвердити статус замовлення.
У трейсах видно інше: за 9 хвилин один run зробив 29 викликів інструментів
(billing.get_invoice — 18, payments.verify — 11), і більшість завершилась timeout або 5xx.
Для задачі такого класу це може бути ~$2.50 замість звичних ~$0.12.
Сервіс формально не "мертвий": частина викликів повертає 200.
Але користувач не отримує фінальну відповідь, а черга run'ів і latency ростуть.
Система не падає.
Вона просто застрягає між помилками інструментів і ретраями, повільно накопичуючи latency і чергу run'ів.
Аналогія: уяви кур'єра, який приїжджає до зачиненого складу, телефонує ще раз, чекає, телефонує знову і знову повертається до тих самих дверей. Він постійно "в процесі", але замовлення не рухається. Tool failure в агентів виглядає так само: дії є, результату немає.
Чому це стається
Tool failure виникає не лише через нестабільне API.
Зазвичай проблема в тому, що runtime не має чіткої стратегії класифікації та обробки помилок інструментів.
У production зазвичай так:
- зовнішній сервіс повертає
timeout,5xxабо нестабільний payload; - runtime або tool gateway повторює виклик без чіткої класифікації помилки;
- non-retryable помилки теж потрапляють у цикл ретраїв;
- без circuit breaker і fallback run зависає або спалює бюджет.
Проблема не в одній випадковій помилці API. Проблема в тому, що система не зупиняє хвилю збоїв, поки вона не перетворюється на інцидент.
Цей тип інцидентів зазвичай називають agent tool failure —
коли агентна система не може стабільно працювати через нестабільність
або помилки зовнішніх інструментів.
Які збої трапляються найчастіше
Щоб не ускладнювати, у production найчастіше бачать чотири патерни tool failure.
Тимчасові збої (Transient failures)
Інструмент час від часу віддає 408/429/5xx.
Якщо retry-контроль слабкий, короткий збій перетворюється на retry storm.
Типова причина: немає backoff+jitter і retry budget.
Non-retryable помилки в циклі (Wrong retry classification)
401, 403, 404, 409, schema validation errors або policy denials ідуть у повтори,
хоча їх треба завершувати одразу.
Типова причина: retryable/non-retryable не розділені в одному місці.
Дрейф контракту інструмента (Tool contract drift)
Інструмент змінює формат відповіді або структуру помилки. Агент не може стабільно інтерпретувати результат і починає "перепитувати" той самий сервіс.
Типова причина: немає версіонування контракту й валідації payload у gateway.
Каскадна деградація (Cascading failure)
Один проблемний інструмент піднімає latency всієї системи: воркери зайняті очікуванням, черга росте, інші run'и теж сповільнюються.
Типова причина: відсутній circuit breaker і fallback для degraded dependencies.
Як виявляти ці проблеми
Tool failure добре видно по комбінації метрик runtime і gateway.
| Метрика | Сигнал tool failure | Що робити |
|---|---|---|
tool_error_rate | різкий ріст 4xx/5xx/timeout | увімкнути degraded mode і перевірити залежність |
retry_attempts_per_call | занадто багато повторів на один виклик | обмежити retry budget, додати backoff+jitter |
non_retryable_retry_rate | повтори 401/403/404/409/422 | завершувати run одразу з явним stop reason |
circuit_open_rate | часто відкривається circuit breaker | перевірити SLA інструмента і fallback-сценарій |
queue_backlog | черга росте при звичному трафіку | скидати завислі run'и і знижувати fan-out |
Як відрізнити tool failure від помилки логіки агента
Не кожен збій run означає, що агент "погано думає". Ключовий критерій: де саме ламається цикл.
Нормально, якщо:
- помилка локалізована в одному зовнішньому інструменті;
- stop reason прямо вказує на залежність (
tool_timeout,tool_5xx,circuit_open); - після fallback користувач отримує частковий, але коректний результат.
Небезпечно, якщо:
- агент повторює non-retryable помилки як retryable;
- немає чітких stop reasons на рівні tool gateway;
- збої одного інструмента тягнуть за собою весь workflow.
Як зупиняти такі збої
Практично це виглядає так:
- класифікуєш помилки інструмента: retryable і non-retryable;
- тримаєш retry policy в одному tool gateway (backoff+jitter + budget);
- ставиш circuit breaker на хвилю збоїв;
- при недоступності інструмента повертаєш fallback/partial результат і stop reason.
Мінімальний guard для помилок інструмента:
from dataclasses import dataclass
import time
RETRYABLE = {408, 429, 500, 502, 503, 504}
NON_RETRYABLE = {400, 401, 403, 404, 409, 422}
@dataclass(frozen=True)
class ToolFailureLimits:
max_retry: int = 2
open_circuit_after: int = 3
circuit_cooldown_s: int = 20
class ToolFailureGuard:
def __init__(self, limits: ToolFailureLimits = ToolFailureLimits()):
self.limits = limits
self.fail_streak = 0
self.circuit_open_until = 0.0
def before_call(self) -> str | None:
if time.time() < self.circuit_open_until:
return "tool_unavailable:circuit_open"
return None
def on_result(self, status_code: int, attempt: int) -> str | None:
if status_code in NON_RETRYABLE:
self.fail_streak = 0
return "tool_failure:non_retryable"
if status_code in RETRYABLE:
self.fail_streak += 1
if self.fail_streak >= self.limits.open_circuit_after:
self.circuit_open_until = time.time() + self.limits.circuit_cooldown_s
return "tool_unavailable:circuit_open"
if attempt >= self.limits.max_retry:
return "tool_failure:retry_exhausted"
return "tool_retry:allowed"
self.fail_streak = 0
return None
Це базовий guard.
У production його зазвичай доповнюють пер-інструментними лімітами і експоненційним backoff з jitter.
attempt тут зазвичай 1-based (1, 2, 3...), а стан guard тримають окремо для кожного tool або run.
Де це реалізується в архітектурі
У production контроль tool failure майже завжди розкладений між трьома шарами системи.
Tool Execution Layer — основна точка контролю: валідація аргументів і payload, retry policy, error classification, circuit breaker. Якщо цей шар слабкий, навіть простий збій API швидко стає cascade.
Agent Runtime відповідає за lifecycle run: stop reasons, timeout, кероване завершення і fallback-відповідь. Саме тут важливо не продовжувати run за будь-яку ціну.
Policy Boundaries задає, які інструменти дозволені і за яких умов run треба завершувати fail-closed. Це особливо важливо для write-інструментів і permission помилок.
Checklist
Перш ніж шипити агента в production:
- [ ] retryable/non-retryable помилки розділені явно;
- [ ] retries реалізовані в одному gateway, а не в кількох шарах;
- [ ]
max_retry, backoff+jitter і retry budget задані; - [ ] circuit breaker і cooldown налаштовані для кожного критичного tool;
- [ ] stop reasons покривають
timeout,5xx,non_retryable,circuit_open; - [ ] fallback/partial відповідь визначені до інциденту;
- [ ] алерти на
tool_error_rate,retry_attempts_per_call,queue_backlog; - [ ] є runbook для режиму деградації (degraded mode) і rollback залежностей.
FAQ
Q: Чи достатньо просто підняти timeout для проблемного інструмента?
A: Ні. Це часто лише маскує проблему і збільшує latency. Потрібні error classification, retry budget і circuit breaker.
Q: Де мають жити retries?
A: В одному choke point, зазвичай у tool gateway. Ретраї в кількох шарах майже завжди дають amplification.
Q: Які помилки зазвичай не можна ретраїти?
A: 401, 403, 404, 409, 422, schema validation errors і policy denials. Такі run'и зазвичай завершують одразу з явним stop reason.
Q: Що показувати користувачу, коли інструмент недоступний?
A: Причину зупинки, що вже перевірили, і безпечний наступний крок: fallback, частковий результат або ручну ескалацію.
Tool failure майже ніколи не виглядає як одна велика аварія. Частіше це серія дрібних збоїв, які накопичуються в retry-цикли і чергу. Тому production-агентам потрібні не лише інструменти, а й жорстка дисципліна їх виконання.
Пов'язані сторінки
Якщо ця проблема виникла у production, корисно також подивитися:
- Чому AI агенти ламаються — загальна карта збоїв у production.
- Tool spam — як повтори викликів перетворюють помилку інструмента на інцидент.
- Partial outage — як часткова деградація залежностей ламає workflow.
- Budget explosion — як retry storm тихо роздуває витрати.
- Agent Runtime — де контролювати stop reasons і lifecycle run.
- Tool Execution Layer — де тримати retries, валідацію і circuit breaker.