Сцена первая: агент-зомби
Год назад я запустил своего первого ReAct-агента. Через 20 минут после деплоя он начал ходить по кругу: вызывал один и тот же инструмент, получал одинаковый ответ и снова его вызывал. Бесконечный цикл, который сожрал $200 на API. Агент превратился в зомби — бессмертный, дорогой и бессмысленный.
Проблема зацикливания — классика жанра. LLM не знает, когда остановиться. Он думает: «Надо ещё раз поискать, вдруг найдётся». И ищет. Ещё раз. Ещё. Пока вы не прервёте процесс принудительно. В простом ReAct-цикле, который описывали в предыдущей статье, нет контроля за повторением действий.
Не советую так делать: полагаться на то, что модель сама решит закончить. Она этого не сделает. Всегда ставьте жёсткий лимит шагов — max_iterations=10 — и детектор повторяющихся паттернов.
Чиним зацикливание графом состояний
LangGraph вывозит именно здесь. Вы строите не цикл, а граф, где каждое действие — узел, а переходы контролируются условиями. Добавляем узел-детектор, который смотрит историю: если последние N шагов одинаковые — переходим в специальный узел Fallback, который генерирует итоговый ответ.
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# Определяем граф с состоянием
class AgentState(TypedDict):
messages: list
step_count: int
last_action: str
builder = StateGraph(AgentState)
builder.add_node("action", execute_action)
builder.add_node("detect_loop", loop_detector)
builder.add_node("fallback", generate_fallback)
builder.set_entry_point("action")
# Условие: после action проверяем на зацикливание
builder.add_conditional_edges(
"action",
check_loop, # функция, возвращающая "detect_loop" или "fallback"
{
"continue": "detect_loop",
"loop_detected": "fallback"
}
)
builder.add_edge("detect_loop", END) # если цикл не найден — заканчиваем
app = builder.compile()
Важный нюанс: не ставьте слишком жёсткий детектор. Если агент три раза подряд обратился к одному инструменту, это может быть осмысленно (например, постраничный сбор данных). Проверяйте не просто повтор действия, а повтор одного и того же результата. Используйте хеш ответа. Не бойтесь эвристики.
Сцена вторая: токен-апокалипсис
Вторая ловушка — стоимость. Каждый шаг агента — это полный контекст: история диалога, промпт, предыдущие действия. Если сессия длится 500 шагов (ага, зомби-агент), вы платите за 500 раз по 10 000 токенов. $50 за один диалог. Бизнес-юнит рад.
Проблема усугубляется тем, что LLM деградируют на длинных контекстах — об этом мы писали в статье «Проклятие длинного контекста». Агент забывает инструкции, начинает галлюцинировать. Решение — семантическое сжатие истории.
Семантический поиск вместо полного контекста
Вместо того, чтобы тащить всю историю в промпт, сохраняйте ключевые моменты в векторной базе (ChromaDB, Qdrant). При каждом шаге агент формирует векторный запрос к своей же истории — и получает только 3-5 релевантных сообщений. Это радикально режет контекст.
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# Сохраняем каждое сообщение с эмбеддингом
vectorstore = Chroma(
collection_name="agent_memory",
embedding_function=OpenAIEmbeddings()
)
# При новом шаге: ищем похожие шаги
relevant_history = vectorstore.similarity_search(
current_query, k=5
)
context = "\n".join([doc.page_content for doc in relevant_history])
Грабли: не экономьте на эмбеддингах. Бесплатный text-embedding-ada-002 (или его более свежая версия на июнь 2026) справляется, но если диалог технический — лучше перейти на модель, обученную на коде или технической документации. И не забывайте чистить устаревшие векторы — иначе агент будет отвечать цитатами из прошлого года.
Сцена третья: мультиагентный театр
Когда я попытался сделать одного универсального агента — он тупил. Он не мог одновременно анализировать код, искать информацию в документации и отвечать вежливо. Результат: либо ответ поверхностный, либо не отвечает вообще.
Вспомните принцип из менеджмента: AI-агенты как сотрудники. Вы же не нанимаете одного человека, который будет и разработчиком, и тестировщиком, и продакт-менеджером. Так и с агентами. Разделите роли.
Я использую три роли: Новичок (собирает данные, не анализирует), Исследователь (строит гипотезы, ищет связи), Эксперт (принимает решение, пишет финальный ответ). Это мультиагентная система, где каждый суб-агент — маленький, специализированный, дёшевый в использовании.
Подробнее про суб-агентов читайте в отдельной статье «Как правильно использовать суб-агентов». Там три сценария с кодом.
Оркестрация через LangGraph: планировщик и синтезатор
Каждая роль — узел графа. Планировщик (узел-координатор) получает запрос, разбивает его на подзадачи, отправляет каждой роли. Синтезатор (другой узел) собирает ответы.
from langgraph.graph import StateGraph
class TeamState(TypedDict):
query: str
novice_result: str | None
researcher_result: str | None
expert_result: str | None
final_answer: str | None
builder = StateGraph(TeamState)
builder.add_node("planner", route_to_roles)
builder.add_node("novice", NoviceAgent().process)
builder.add_node("researcher", ResearcherAgent().process)
builder.add_node("expert", ExpertAgent().process)
builder.add_node("synthesizer", synthesize)
builder.set_entry_point("planner")
# Параллельные роли
builder.add_edge("planner", "novice")
builder.add_edge("planner", "researcher")
builder.add_edge("planner", "expert")
# Синтезатор запускается после всех ролей (используем fan-in)
builder.add_edge(["novice", "researcher", "expert"], "synthesizer")
builder.add_edge("synthesizer", END)
app = builder.compile()
Ошибка, которую я делал: я давал ролям слишком общие промпты. Эксперт начинал играть роль новичка. Пропишите жёсткие границы: «Ты не имеешь права давать окончательный ответ, твоя задача только собрать данные». И наоборот: «Эксперт, ты обязан опираться только на факты, которые предоставили Новичок и Исследователь».
Нюансы и грабли: что я понял через боль
- Жёсткие ограничения убивают гибкость. Слишком строгий детектор зацикливания — агент не может выполнить многошаговую задачу. Оптимально: 15 шагов, после 10 — предупреждение в промпт «тебе осталось 5 шагов, завершай».
- Семантический поиск не панацея. Если контекст резко меняется (например, пользователь переключился с кода на дизайн), векторы из прошлого будут шуметь. Добавьте фильтр по времени: последние 10 сообщений всегда включать в контекст.
- Роли без проверки качества. Новичок мог найти неправильную информацию, а Эксперт на неё опирается. Добавьте узел-верификатор, который перекрёстно проверяет данные. Это экономит токены, потому что дешёвая модель-верификатор отсеивает мусор до того, как дорогой Эксперт начнёт думать.
Финальный совет: не делайте агента, если не продумали failsafe
Мой первый продакшен-агент сломался, когда пользователь написал ему «привет» 37 раз подряд. Агент пытался ответить на каждое «привет», проваливался в цикл, сжигал квоту. Теперь я ставлю глобальный тайм-аут: если сессия длится больше N секунд — принудительный ответ с извинениями и предложением переформулировать.
И ещё — не забывайте про бюджет токенов. После каждой сессии логируйте затраты. Если средний диалог стоит больше $0.50 — оптимизируйте. Для глубокого понимания работы с LLM и создания качественного AI-контента рекомендую курс AI-креатор: создаём контент с помощью нейросетей от Skillbox. Там учат не только писать промпты, но и проектировать надёжные пайплайны.
Строить AI-агента — это инженерная дисциплина, а не магия. Зацикливание, токен-апокалипсис и путаница ролей — это не баги, это особенности, которые вы обязаны предусмотреть архитектурно. Начните с графа, добавьте семантическое сжатие и разделите обязанности — и ваш агент проживёт достаточно долго, чтобы принести пользу.