Обработка ошибок инструментов

Реальные инструменты ломаются. Разница между хрупким и надёжным агентом — в том, как он обрабатывает ошибки.

Ошибка как наблюдение — приём, при котором сбой инструмента не роняет агента, а возвращается модели текстом, чтобы она поняла причину и попробовала иначе.

Почему инструменты падают

  • Плохие аргументы — модель передала "10 / 0" или текст вместо числа.
  • Внешний сбой — API недоступен, тайм-аут, нет сети.
  • Пустой результат — поиск ничего не нашёл (это не ошибка, но требует реакции).

Если такой сбой выбросит исключение и уронит цикл — агент мёртв. Правильно — поймать ошибку и превратить её в наблюдение, на которое модель отреагирует.

Диспетчер, устойчивый к ошибкам

Оборачиваем вызов в try/except. Что бы ни случилось внутри инструмента, диспетчер вернёт строку, а не упадёт.

def divide(arg):
    a, b = arg.split(",")
    return str(int(a) / int(b))

TOOLS = {"divide": divide}

def safe_dispatch(name, arg):
    if name not in TOOLS:
        return f"ОШИБКА: инструмент '{name}' не найден"
    try:
        return TOOLS[name](arg)
    except Exception as e:
        # ошибку не «глотаем», а возвращаем модели как наблюдение
        return f"ОШИБКА инструмента: {e}"

print(safe_dispatch("divide", "10,2"))
print(safe_dispatch("divide", "10,0"))
print(safe_dispatch("divide", "десять,2"))

Вывод:

5.0
ОШИБКА инструмента: division by zero
ОШИБКА инструмента: invalid literal for int() with base 10: 'десять'

Программа не упала ни разу. Каждая ошибка — это информативная строка, которую можно показать модели.

Как агент исправляется по ошибке

Теперь главное: получив ошибку как наблюдение, модель меняет действие. Смоделируем это заглушкой LLM, которая после ошибки выбирает другой аргумент.

def divide(arg):
    a, b = int(arg.split(",")[0]), int(arg.split(",")[1])
    if b == 0:
        return "ОШИБКА: деление на ноль"
    return str(a // b)

def llm(history):
    text = "\n".join(history)
    if "Observation:" not in text:
        return "Action: divide[10,0]"          # сначала пробует ноль
    if "ОШИБКА: деление на ноль" in text and "divide[10,2]" not in text:
        return "Action: divide[10,2]"          # после ошибки исправляется
    if "Observation: 5" in text:
        return "Final Answer: 5"
    return "Final Answer: ?"

history = ["Question: дай результат деления"]
for step in range(1, 5):
    block = llm(history)
    history.append(block)
    print(f"[{step}] {block}")
    if "Final Answer:" in block:
        break
    arg = block.split("divide[")[1].rstrip("]")
    obs = divide(arg)
    history.append(f"Observation: {obs}")
    print(f"    Observation: {obs}")

Вывод:

[1] Action: divide[10,0]
    Observation: ОШИБКА: деление на ноль
[2] Action: divide[10,2]
    Observation: 5
[3] Final Answer: 5

Агент наткнулся на ошибку, увидел её в наблюдении и на следующем шаге выбрал корректный аргумент. Это и есть устойчивость: сбой не фатален, а информативен.

Практические правила

  • Никогда не давайте исключению уронить цикл — оборачивайте вызовы в try/except.
  • Сообщения об ошибках должны быть понятны модели — «деление на ноль» полезнее, чем код 0x5.
  • Различайте «ошибку» и «пустой результат» — «ничего не найдено» тоже валидное наблюдение.
  • Ставьте лимит на повторы — иначе агент будет вечно биться об один и тот же сбой (раздел 3).

Итог

  • Инструменты падают по многим причинам; цикл агента не должен падать вместе с ними.
  • Приём «ошибка как наблюдение»: ловим сбой и возвращаем его модели текстом.
  • Получив понятную ошибку, модель способна исправить действие — но нужен лимит повторов.
Проверьте себя
1. Что значит «возвращать ошибку инструмента как наблюдение»?
AЗаписать ошибку в лог и забыть
BПовторить вызов с теми же аргументами
CЗавершить работу агента с ошибкой
DПоймать сбой и передать его модели текстом, чтобы она могла исправиться
2. Почему важно оборачивать вызов инструмента в try/except?
AЧтобы исключение в инструменте не уронило весь цикл агента
BЧтобы ускорить инструмент
CЭто требование function calling
DЧтобы скрыть ошибку от модели
3. Что ещё нужно, кроме обработки ошибки, чтобы агент не бился вечно об один и тот же сбой?
AБолее крупная модель
BЛимит на число шагов/повторов
CОтключить логирование
DУдалить инструмент из реестра