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

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

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

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

  • Плохие аргументы — модель передала "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Удалить инструмент из реестра
Поддержать проект