Остановка, лимиты и зацикливание

Автономный цикл обязан уметь останавливаться — и нормально (нашёл ответ), и аварийно (застрял). Без этого агент превращается в бесконечный счётчик расходов.

Условие остановки — правило, по которому цикл агента завершается: либо модель выдала финальный ответ, либо сработал предохранитель (лимит шагов, обнаружение цикла).

Нормальная остановка

Штатный способ — модель сама решает, что задача решена, и выдаёт Final Answer. Цикл это распознаёт и завершается. Но полагаться только на это нельзя: модель может так и не сказать «готово».

Предохранитель 1: лимит шагов

Самая важная защита. Сколько бы агент ни «думал», после N шагов цикл останавливается принудительно. Это спасает и от зацикливания, и от роста стоимости.

def stubborn_llm(history):
    # заглушка зациклилась: всё время просит один и тот же поиск
    return "Action: search[одно и то же]"

MAX_STEPS = 4
answer = None
history = []
for step in range(1, 1000):
    if step > MAX_STEPS:
        print(f"Лимит {MAX_STEPS} шагов исчерпан — принудительная остановка.")
        answer = "не удалось решить за отведённые шаги"
        break
    block = stubborn_llm(history)
    history.append(block)
    print(f"шаг {step}: {block}")

print("Итог:", answer)

Вывод:

шаг 1: Action: search[одно и то же]
шаг 2: Action: search[одно и то же]
шаг 3: Action: search[одно и то же]
шаг 4: Action: search[одно и то же]
Лимит 4 шагов исчерпан — принудительная остановка.
Итог: не удалось решить за отведённые шаги

Без MAX_STEPS этот цикл крутился бы вечно. В реальном агенте каждый виток — это вызов LLM за деньги, так что лимит шагов (и лимит бюджета) обязателен.

Предохранитель 2: обнаружение зацикливания

Лимит шагов спасает «в худшем случае», но иногда видно раньше: агент повторяет одно и то же действие. Это явный признак, что он застрял, — лучше прервать сразу, а не ждать лимита.

def detect_loop(actions, window=2):
    # цикл, если последние window+1 действий одинаковы
    if len(actions) > window:
        last = actions[-(window + 1):]
        if len(set(last)) == 1:
            return True
    return False

actions = []
for a in ["search[x]", "search[y]", "search[y]", "search[y]"]:
    actions.append(a)
    looped = detect_loop(actions)
    print(f"действие {a:12} | цикл: {looped}")
    if looped:
        print("  -> агент застрял, прерываем")
        break

Вывод:

действие search[x]    | цикл: False
действие search[y]    | цикл: False
действие search[y]    | цикл: False
действие search[y]    | цикл: True
  -> агент застрял, прерываем

Почему агенты зацикливаются

  • Инструмент стабильно возвращает «не найдено», а модель упорно пробует тот же запрос.
  • Модель не замечает в истории, что уже делала этот шаг.
  • Задача в принципе нерешаема имеющимися инструментами, но модель этого не признаёт.

Что делать при срабатывании предохранителя

  • Вернуть честное «не удалось», а не выдуманный ответ.
  • Залогировать всю трассу — по ней видно, где агент застрял.
  • При необходимости передать задачу человеку (человек-в-цикле).

Памятка по остановке

МеханизмКогда срабатывает
Final Answerмодель сама закончила (норма)
Лимит шаговпревышено число итераций
Обнаружение цикладействие повторяется
Лимит бюджетаисчерпан лимит токенов/денег

Итог

  • Штатная остановка — это Final Answer, но полагаться только на неё нельзя.
  • Лимит шагов — обязательный предохранитель от зацикливания и роста стоимости.
  • Обнаружение повторов прерывает застрявшего агента раньше лимита; при срабатывании — честное «не удалось» и лог.
Проверьте себя
1. Почему лимит шагов в цикле агента обязателен?
AОн улучшает качество ответов
BБез него зациклившийся агент крутится бесконечно, делая платные вызовы LLM
CЭто требование формата ReAct
DОн ускоряет работу инструментов
2. Какой признак позволяет обнаружить зацикливание раньше, чем сработает лимит шагов?
AРост длины истории
BПовторение одного и того же действия несколько раз подряд
CПоявление слова Thought
DУвеличение числа инструментов
3. Как агенту правильно завершиться, если сработал предохранитель и ответа нет?
AВыдать любой правдоподобный ответ
BВернуть честное «не удалось», залогировать трассу и при необходимости позвать человека
CМолча остановиться без сообщения
DПерезапустить цикл с нуля
Поддержать проект