Перейти к содержимому

Агент долбится в падающий tool

Агент вызывает tools, часть вызовов падает — это норма: сеть моргнула, rate-limit, случайный 500. Модель ретраит, и обычно всё чинится само. Проблема начинается, когда тот же tool падает раз за разом: протухла авторизация, у провайдера деградация, endpoint отдаёт 500 стабильно. Агент не понимает, что дело не в аргументах, и упорно долбится в стену — жжёт токены (см. runaway бюджета) и никуда не двигается.

Нужен трекер, который в пределах одного run-а считает долю ошибок по каждому tool-у и ловит момент, когда падения перестали быть случайными.

Оборачиваем выполнение tool-а. Копим по имени инструмента: сколько всего вызовов, сколько ошибок, сколько фейлов подряд. Пушим на двух триггерах — либо N ошибок подряд (persistent), либо доля ошибок пробила порог на достаточном числе вызовов.

import os, requests, collections
class ToolErrorTracker:
def __init__(self, run_id, streak=4, rate=0.5, min_calls=6):
self.run_id = run_id
self.streak = streak # столько фейлов подряд = persistent
self.rate = rate # доля ошибок, которую считаем аварией
self.min_calls = min_calls # не паникуем на малой выборке
self.total = collections.Counter()
self.errors = collections.Counter()
self.consec = collections.Counter()
self._fired = set() # чтобы не спамить по одному tool-у
def record(self, tool, ok, detail=""):
self.total[tool] += 1
if ok:
self.consec[tool] = 0
return
self.errors[tool] += 1
self.consec[tool] += 1
if tool in self._fired:
return
# persistent: одно и то же падает подряд
if self.consec[tool] >= self.streak:
self._fired.add(tool)
notify("🛠️❌ Tool падает подряд",
f"run {self.run_id}: {tool} упал {self.consec[tool]} раз "
f"подряд. {detail} Похоже на persistent-сбой.",
priority=9)
return
# rate: много ошибок на заметной выборке
n, e = self.total[tool], self.errors[tool]
if n >= self.min_calls and e / n >= self.rate:
self._fired.add(tool)
notify("🛠️⚠️ Высокий error-rate у tool-а",
f"run {self.run_id}: {tool}{e}/{n} ошибок "
f"({e/n:.0%}). Агент буксует.",
priority=7)
def notify(title, message, priority):
requests.post(f"{os.environ['NOTIFLY_URL']}/message",
params={"token": os.environ["NOTIFLY_TOKEN"]},
json={"title": title, "message": message, "priority": priority},
timeout=5)

Встраивание — обёртка вокруг вызова инструмента:

tracker = ToolErrorTracker(run_id="refactor-auth-2026-07-01")
def run_tool(tool, **kwargs):
try:
result = tool.execute(**kwargs)
tracker.record(tool.name, ok=True)
return result
except Exception as e:
tracker.record(tool.name, ok=False, detail=str(e)[:120])
raise

Два порога не случайны: streak ловит быстрый явный сбой (авторизация протухла — сыпет ошибками мгновенно), rate ловит «плавающий» tool, который падает через раз и медленно съедает run.

Не каждая ошибка достойна push-а. 429 (rate-limit) и 503 — обычно transient, само пройдёт после backoff-а. А вот 401/403 (авторизация), стабильный 500 или ConnectionRefused почти всегда persistent — ретраить бесполезно, нужен человек.

TRANSIENT = {408, 429, 502, 503, 504} # ретрай уместен
PERSISTENT = {400, 401, 403, 404, 500} # ретрай не поможет
def classify(status):
if status in PERSISTENT:
return "persistent"
if status in TRANSIENT:
return "transient"
return "unknown"
def record_http(tracker, tool, status):
kind = classify(status)
tracker.record(tool, ok=(status < 400), detail=f"HTTP {status} ({kind})")
# persistent-код — не ждём серии, бьём тревогу сразу
if kind == "persistent" and tool not in tracker._fired:
tracker._fired.add(tool)
loud = 9 if status in (401, 403) else 8
notify("🛠️🔒 Tool отдаёт persistent-ошибку",
f"run {tracker.run_id}: {tool} → HTTP {status}. "
f"{'Протухла авторизация?' if status in (401,403) else 'Endpoint лежит.'}",
priority=loud)

Смысл: на 401 бессмысленно ждать четырёх фейлов подряд — токен не восстановится сам, буди человека с первого раза. А на 429 промолчи, пусть backoff отработает.

Подход 3: внешний угол — без правки кода агента

Заголовок раздела «Подход 3: внешний угол — без правки кода агента»

Если агент — чёрный ящик (сторонний CLI, чужой SDK) и обернуть вызовы нельзя, ловите ошибки снаружи. Многие tool-ы (API-шлюзы, MCP-серверы) уже шлют вебхуки при 5xx — направьте их в Webhook Router, а он превратит всплеск ошибок в push. Если вебхуков нет, а есть health-check у tool-а — повесьте на него Monitor: endpoint инструмента красный → push, и вы уже знаете, почему run буксует, ещё до его завершения.

Точность ниже (это про сам сервис, а не про конкретный run), зато работает для любого агента без единой строчки правок.

  • run_id и имя падающего tool-а — чтобы найти в логах;
  • transient или persistent, HTTP-код / текст последней ошибки;
  • сколько фейлов подряд / доля ошибок (5/8, 62%);
  • намёк на действие: обнови токен, провайдер лежит — поставь run на паузу.