Отладка ИИ-агентов по слоям: промпт, контекст, использование ресурсов или циклы
Многоуровневая модель сбоев ИИ-агентов: в чём различия между техниками формулировки запросов, учёта контекста, использования инструментов и построения циклов, а также процедура, основанная на отслеживании событий, для определения того, какой уровень дал сбой.
Когда ИИ-агент ведет себя ненадлежащим образом в производственной среде, команды склонны спорить о терминах вместо того, чтобы рассматривать доказательства: один инженер предлагает улучшить запрос, другой винит контекст, третий указывает на инфраструктуру. Инжиниринг запросов, контекста, инфраструктуры и циклов не является соперничающими подходами; это четыре взаимосвязанных уровня, и каждый из них дает собственные очевидные признаки сбоя. В этом руководстве каждый уровень описывается на примере простого кода для ИИ-агента, после чего предлагается алгоритм, основанный на отслеживании, чтобы определить, какой именно уровень вышел из строя, прежде чем вносить изменения в код.
Kраткая версия
- Инжиниринг запросов определяет инструкции, передаваемые модели.
- Инжиниринг контекста определяет, что видит модель перед тем, как отвечать.
- Инжиниринг инфраструктуры создает среду, в которой работает модель: инструменты, память, файлы, разрешения и механизмы восстановления.
Самые дорогостоящие сбои агентов возникают из-за устранения проблем в неверном слое.
Когда демо-версия работает, а продакшн — нет
Эта ситуация знакома: агент выглядит безупречным в демо-версии. Через несколько дней после запуска он начинает повторять одни и те же действия, тратить токены, забывать информацию, которая была ему дана часом ранее, или выходить из строя при получении неожиданных данных от инструмента.
Команда деляется на группы: кто-то предлагает переписать инструкции, кто-то считает проблемой контекст, а третий подозревает саму структуру системы. Без общего способа выявления причины сбоя двухдневное устранение ошибки превращается в двухнедельную переписку кода, поскольку каждое исправление вносится в слой, который ранее работал нормально.
Это практическая причина, по которой необходимо разделять эти четыре понятия. Каждое из них обозначает отдельную сферу, где могут возникнуть проблемы, и как только вы научитесь их различать, отладка становится процессом, а не игрой в угадывание.
PACT: мнемонический прием для запоминания четырех уровней
Краткий способ запомнить эти уровни во время инцидента — PACT: Prompt, Awareness, Control, Trajectory. Каждое слово соответствует одному вопросу:
- Prompt: была ли задача четко определена?
- Awareness: получил ли модель информацию, необходимую для выполнения этого шага?
- Control: может ли среда выполнения безопасно и надежно выполнять то, о чем просит модель?
- Trajectory: движется ли процесс, повторяющийся снова и снова, к завершению, которое можно проверить?
Цель — не создание ещё одного слоя жаргона. Речь идёт о том, чтобы границы между типами сбоев были настолько понятны, чтобы их можно было легко вспомнить при возникновении проблем.
Как появились эти уровни и почему важен порядок
Термины появлялись примерно в таком порядке, по мере того как инструменты обретали новые возможности:
- Инжиниринг подсказок (примерно с 2022 по 2024 год) стал первым навыком: формулировка запросов, примеры, ограничения и шаблоны для работы с небольшим количеством примеров, направленные на один вызов модели.
Указанные даты относятся к моменту, когда эти термины стали популярными, а не к формальным вехам; на момент написания этого текста терминология ещё находится в процессе формирования. Важно понимать, что ничего не было заменено — каждый новый уровень добавлялся поверх предыдущего, поскольку каждое увеличение автономности требовало собственной системы управления.
Инженерия промптов: локальный контракт
Инжиниринг промптов — это искусство формулировки, структурирования и пояснения инструкции таким образом, чтобы ответ был более предсказуемым. Без него возникают нечеткие указания, изменчивые форматы вывода и ситуация, когда модель вынуждена догадываться о вашем намерении.
В кодинговом агенте промпт для проверки кода может выглядеть так:
SYSTEM: You are a code reviewer.
Given a diff, output ONLY valid JSON:
{"issues": [{"line": int,
"severity": "low|medium|high", "note": str}]}
No prose. No markdown fences.
If there are no issues, return {"issues": []}.
Этот промпт устанавливает локальный контракт. Он назначает роль, описывает процесс преобразования (разница входных и выходных данных, список проблем), определяет структуру вывода до уровня названий полей и допустимых значений серьезности, а также определяет случай пустого входа, чтобы последующая обработка никогда не сталкивалась с прозой или отсутствием ключей.
Часто утверждается, что инжиниринг промптов устарел. Это не так; он представляет собой самый внутренний уровень обработки. Каждая система обработки контекста в конечном итоге передает модели инструкцию, и слабая инструкция в отличной структуре всё равно даёт слабые результаты, только теперь они обёрнуты в впечатляющую инфраструктуру.
Инжиниринг контекста: отбор в рамках бюджета
Инжиниринг контекста — это процесс выбора для каждого запроса тех документов, записей, определений инструментов и данных, которые попадут в окно обработки, а также целенаправленного исключения тех, которые не войдут. При его игнорировании модель отвечает на основе устаревшей информации, сбивается с курса из-за нерелевантных найденных фрагментов или теряет фокус из-за перегрузки окна материалом, добавленным на всякий случай.
Создатель контекста для кодинг-агента может извлекать кандидаты, переориентировать их по значимости и сжимать историю операций:
def build_context(query, full_history, kb):
relevant = retrieve(query, kb, top_k=8)
reranked = rerank(relevant, query)[:3]
summary = summarize_if_long(
full_history, max_tokens=800
)
return {
"docs": reranked,
"history": summary,
"query": query,
}
Этот процесс можно представить как воронку: на этапе получения данных собирается большой набор из восьми кандидатов, затем проводится переранжирование, чтобы оставить три наиболее релевантных варианта, а история разговора сводится примерно до 800 токенов только в случае её значительного увеличения. Функция возвращает структурированный небольшой пакет данных, а не сырые материалы. Ключевые навыки здесь — отбор, ранжирование и сжатие информации, а не её объем.
Именно поэтому наиболее распространенное заблуждение, согласно которому инжиниринг контекста означает предоставление модели большего количества информации, обычно ошибочно. Более большой контекст склонен содержать больше шума: устаревшие инструкции, повторяющиеся факты и противоречивые данные. Большая часть работы заключается в выборе того, что следует исключить.
Инжиниринг контекста — это нечто большее, чем RAG
Генерация с усилением на основе поиска — это один из методов инженерии контекста, охватывающий этап поиска информации. Пайплайн RAG может извлечь восемь фрагментов данных и переранжировать их до трех, как показано выше. Инженерия контекста также включает сжатие истории действий, форматирование определений инструментов, поддержание актуального состояния критически важных задач, передачу результатов проверки обратно в модель и принятие решений о том, что следует опустить.
У агента для программирования без векторного хранилища всё равно существует реальная проблема формирования контекста, которую необходимо решить. Информация о статусе Git, открытых файлах, ошибках компилятора, результатах тестов, текущем плане действий и журнале предыдущих операций должна попадать в модель в удобном для использования формате.
Инженерия использования ресурсов: граница между намерением и результатом
Хаб представляет собой нечасть модели системы: это инструменты и доступ к файлам, постоянная память, правила разрешений, сандбоксы, трассировка и все то, что происходит при сбоях. Без качественного хаба у вас получается агент, который хорошо мыслит, но не может действовать на основе этих размышлений, или агент, который действует, но не имеет надежного способа восстановления при ошибках вызова инструментов.
Пример обертки для вызова инструмента иллюстрирует эту идею:
def call_tool(tool_name, args, retries=2):
for attempt in range(retries + 1):
try:
result = TOOLS[tool_name](**args)
log_trace(tool_name, args, result, status="ok")
return result
except ToolError as e:
log_trace(tool_name, args, str(e), status="failed")
if attempt == retries:
return {"error": str(e), "recoverable": False}
args = repair_args(args, e)
Обертка не пытается решить задачу. Она определяет границу между тем, что запросила модель, и тем, что делает реальная система. Каждая попытка отслеживается вместе с аргументами, результатом и статусом. Сбой запускает ограниченную попытку повтора с исправленными аргументами, и после исчерпания всех попыток возвращается структурированная ошибка с пометкой о невозможности восстановления, так что вызывающий код получает данные, с которыми можно работать, вместо исключения, прерывающего выполнение.
Хочется отождествить харнес с любой установленной вами рамкой агента. Рамка предоставляет структуру; харнес — это набор конкретных решений, которые вы принимаете на её основе: что записывается в лог, что делает агент при неудачном вызове, какой состояние сохраняется после сбоя, какие команды разрешены и как результаты выполнения возвращаются модели.
Инженерия циклов: контракт завершения
Инженерия циклов определяет задачи каждой итерации, способ, которым агент оценивает, движется ли он к цели, и, что самое важное, условия для остановки. Классической проблемой является бесконечный цикл: агент продолжает вызывать инструменты и тратить токены, поскольку ему никогда не сообщают, что он завершил работу или застрял.
Минимальный контроллер выглядит так:
def run_loop(task, harness, max_iters=15, stall_limit=3):
state = init_state(task)
stalls = 0
for i in range(max_iters):
action = plan_next_step(state)
result = harness.call_tool(action.tool, action.args)
state = update_state(state, result)
if is_goal_met(state):
return state, "success"
if made_no_progress(state):
stalls += 1
if stalls >= stall_limit:
return state, "stalled — escalate"
else:
stalls = 0
return state, "max iterations reached"
Здесь применяется несколько уровней ограничений. Параметр max_iters определяет максимальное количество итераций, is_goal_met позволяет завершить работу при достижении цели, а счётчик застоя отслеживает последовательные итерации без прогресса, сбрасываясь при возобновлении работы. После stall_limit итераций в состоянии застоя цикл возвращает специальный статус, требующий дальнейшего рассмотрения ситуации, вместо того чтобы продолжать работу без действий. Каждый путь выхода содержит чёткую причину, что облегчает последующую классификацию результатов выполнения.
Основная идея заключается в контракте завершения: четкая цель, показатели прогресса для оценки, лимит на повторные попытки, бюджеты на время и токены, этап верификации, а также четко определенная политика действий в случае замедления прогресса. Для реализаций этих же идей на TypeScript см. ограниченные агентные циклы для использования инструментов LLM.
Понятия цикла и механизма управления часто путают. Механизм управления определяет, с чем может взаимодействовать агент, и как обрабатываются сбои на уровне отдельных инструментов. Цикл находится выше и определяет поведение агента на каждом шаге, включая момент завершения всей операции.
Четыре слоя рядом
- Приказ (P): отвечает за формулировку инструкции. Типичная проблема: модель неправильно понимает намерение или возвращает результат неверного формата. Сначала следует проверить сам результат.
- Контекст (A): отвечает за состояние данных, готовых к использованию моделью. Типичная проблема: модель использует устаревшую, отсутствующую или вводящую в заблуждение информацию. Сначала следует проверить кадры контекста за каждый ход.
- Инструменты (C): отвечают за выполнение операций и восстановление ситуации. Типичная проблема: ошибка при вызове инструментов, слепые попытки повтора или невозможность восстановления. Сначала следует проверить записи о неудачах в логе инструментов.
- Циклы (T): отвечают за прогресс работы и остановку процесса. Типичная проблема: агент никогда не достигает конечного результата или не останавливается. Сначала следует проверить повторяющиеся успешные вызовы с практически идентичными аргументами.
Как отличить ошибку инструментов от ошибки цикла
Самый экономящий время способ — это понимание того, что две разные проблемы могут выглядеть одинаково снаружи. Агент, застрявший в цикле из-за неисправного контроллера, и агент, застрявший из-за сбоев в обработке инструментов, проявляют один и тот же симптом: они продолжают работать, счет растет, и никто не понимает причину. Короткий чек-лист помогает отличить их от других проблем.
1. Проверьте трассу вызовов инструментов
Если все вызовы успешны с разумными результатами, в то время как агент постоянно использует один и тот же инструмент с практически неизменными параметрами, это указывает на цикл.
2. Ищите повторяющиеся сбои инструментов
Если один и тот же инструмент постоянно выходит из строя, а агент пытается его использовать снова и снова, не меняя подхода, стоит заподозрить систему управления.
3. Проверьте, что может видеть модель
Если кажется, что модель забывает какой-то факт на середине работы, проверьте создание контекста: происходит ли обрезка, замена, сжатие информации или как передаётся состояние между этапами обработки.
4. Проверьте результат в конце
Если инструменты работали корректно, контекст был точным и цикл завершился без проблем, но ответ всё равно неверен, обратите внимание на запрос.
Ориентир
Ошибки в циклах связаны с решением о том, что произойдёт дальше. Ошибки в использовании функций связаны с тем, что происходит при сбоях. Ошибки в контексте связаны с тем, что может видеть модель. Ошибки в запросе связаны с тем, о чём вы просили.
Прежде чем что-либо изменять, определите, к какому из четырёх вопросов относится проблема.
Пример из практики: рецензент pull-request, который не хочет останавливаться
Представьте команду, которая развертывает агента для кодинга, отвечающего за проверку pull-запросов. Тестирование проходит гладко. Однако в продакшене некоторые проверки занимают более 40 минут на один pull-запрос, что приводит к неожиданным счетам.
Первая теория заключается в том, что инструкция слишком расплывчата, и агент слишком много думает. Инструкцию переписывают, но ничего не меняется. Вторая теория связана с контекстом: возможно, агент перечитывает весь репозиторий на каждом этапе. Однако отчеты говорят об обратном — загрузка происходит в установленных пределах, и берутся только необходимые файлы.
Затем кто-то внимательно изучает трассу вызовов инструментов. Агент неоднократно запускает свой инструмент тест-раннера, и каждый запуск проходит без ошибок. Однако набор тестов действительно проваливается из-за нестабильного интеграционного теста, не связанного с пул-реквестом. Агент продолжает пытаться устранить эту проблему с помощью изменений в коде, не связанных с её причиной, и повторного запуска тестов, поскольку ничто не указывает ему на то, что он уже сделал достаточно попыток для решения этой задачи и должен прекратить попытки и передать её на более высокий уровень обработки.
Это не проблема с запросом, не проблема с контекстом и не проблема с фреймворком; инструменты вели себя ровно так, как было запланировано. Это чисто баг в виде цикла. Последовательное рассмотрение ситуации шаг за шагом позволяет прийти к такому выводу.
Шаг 1: подтвердить работоспособность инструментов
Успешные запуски, указанные в трассе, исключают самые простые сбои фреймворка, такие как команда, которая никогда не выполняется, или адаптер, который постоянно выдает ошибки.
Шаг 2: сравнение состояния между итерациями
Результаты теста находятся в контексте агента, и процесс извлечения данных осуществляется правильно, поэтому модель не теряет важные доказательства.
Шаг 3: проверка на сходимость
В репозитории происходят изменения при каждой итерации, но сигнал верификации, имеющий значение, так и не улучшается. Нет детектора застоя, а также нет ограничения на количество попыток решения одной и той же подзадачи.
Шаг 4: обучение контроллера выявлять застой
Решение заключается в добавлении небольшого фрагмента логики контроллера, который сравнивает показатели прогресса между шагами:
if progress_signature == previous_signature:
stagnant_steps += 1
else:
stagnant_steps = 0
if stagnant_steps >= 3:
escalate("no measurable progress")
stop()
progress_signature может представлять собой что угодно, что отражает значимый прогресс, например набор неудачных тестов вместе с их сообщениями об ошибках. Если этот параметр не меняется, счетчик застоя растет; после трех таких этапов контроллер передает информацию о причине и останавливается. Любые реальные изменения сбрасывают этот счетчик.
Можно поступить еще дальше и ограничить количество попыток для каждой конкретной причины неудачи, а не только общее количество итераций. Это важно: пятнадцать успешных итераций могут быть совершенно нормальными, в то время как пятнадцать попыток решить одну неисправимую проблему в тесте — это чистая трата ресурсов. Настоящее решение заключается не в том, чтобы сделать модель менее упрямой, а в том, чтобы дать контроллеру четкое определение состояния застоя.
Пример из практики: ограничение, исчезающее из контекста
Теперь представьте агента, который в начале миграции устанавливает правило о том, что ничто не должно нарушать работу существующих клиентов. Через сорок ходов диалог сжимается, и это ограничение теряется в кратком изложении. Затем агент предлагает изменение схемы, способное нарушить стабильность.
Это кажется ошибочным рассуждением, но причина кроется в чем-то другом. Сравните контекст, который модель фактически получала на разных этапах. Если ограничение присутствовало на пятом ходу, но отсутствовало на сороковом, решение заключается в оптимизации контекста: хранить критически важные неизменяемые параметры отдельно от диалога, обеспечивать, чтобы краткие изложения передавали явные ограничения, и перестать считать чистую историю диалога единственным источником правды.
Фраза «модель забыла» никогда не является полным диагнозом. Важный вопрос — что на самом деле было предоставлено модели.
Слои, а не замены
Каждая новая дисциплина основывается на более старых, а не заменяет их. Агент для обработки запросов оборачивает чёткий промпт в тщательно подобранный контекст, запускает его в надёжной среде и управляет всем процессом с помощью целенаправленного цикла. Последовательность элементов отражает то, что командам приходилось добавлять со временем: сначала более чёткие инструкции, затем лучше отобранная информация, потом среда выполнения для модели и, наконец, контроллер, который обеспечивает бесперебойную работу этой среды без участия человека. Если вы решаете, должен ли ваш собственный движок выполнения или фреймворк управлять этим внешним циклом, сравнение движков выполнения и составных фреймворков покрывает все аспекты такого выбора.
Перефразировано с использованием PACT:
- Промпт: определение инструкции.
Затем необходимо соотнести решение с причиной сбоя. Модели, которые неправильно интерпретируют четко определенную задачу, требуют доработки. Модели, у которых отсутствуют необходимые факты, нуждаются в дополнительном контексте. Разумные решения, которые не приводят к надежным действиям, требуют улучшения механизмов их реализации. Действия, которые успешны, но при этом общий процесс так и не сходится к конечному результату, требуют корректировки циклов выполнения.
Частые вопросы
Является ли инженерия harness просто другим названием для фреймворка агентов?
Нет. Фреймворк предоставляет базовые элементы для построения. Система управления формируется на основе решений, принимаемых при их сборке: поведение в случае сбоя инструмента, сохраняемое состояние, логирование, разрешения и способ возврата результатов в модель.
Требуется ли инжиниринг циклов для ассистента с одним вопросом?
Не совсем. Инжиниринг циклов становится важным, когда агент выполняет несколько действий в рамках одной задачи и должен самостоятельно или с помощью кода-контроллера определить, что работа завершена. У бота для вопросов и ответов с одним обменом информацией практически нет или вовсе нет циклов, которые нужно проектировать.
Какой слой следует изучить в первую очередь?
Начните с инжиниринга промптов и контекста, затем переходите к системе управления и циклам. Прежде чем отладить работу во время выполнения и код контроллера, необходимо уметь отличать некорректные инструкции от отсутствия состояния.
Может ли одна ошибка затрагивать несколько слоев?
Да, и это часто бывает самыми сложными случаями. Ошибка в контексте может скрыть сигнал прогресса от цикла, из-за чего это выглядит как ошибка в самом цикле. Необходимо проследить за сбоем на всех уровнях, а не просто исправлять первый заметный симптом.
Основные выводы
- Эти четыре понятия описывают механизмы управления, а не конкурирующие тенденции: инструкция, состояние готовности модели к работе, выполнение и восстановление, а также повторный прогресс с правилом остановки.
- Начинайте каждое расследование с трейса, а не с запроса: спрашивайте, что видела модель, что делал движок выполнения, что изменилось между итерациями и почему контроллер решил продолжить работу.
- Успешные вызовы инструментов с практически идентичными аргументами указывают на наличие цикла; повторные сбои при слепых попытках восстановления указывают на проблемы с механизмом управления.
- Необходимо дать циклам четкое определение состояния застоя, желательно в зависимости от типа сбоя, а также четкий план действий при усугублении ситуации.
Связанные материалы
- Как протокол контекста модели позволяет агентам ИИ находить и вызывать инструменты — понятное объяснение MCP: как хосты, клиенты и серверы помогают приложениям ИИ находить инструменты, вызывать их с структурированными данными и понимать их ограничения.
- CodeBuddy: более эффективный поиск контекста для агентов ИИ при программировании — объясняется, как система поиска контекста на основе графа зависимостей помогает агентам ИИ при программировании избегать как нехватки контекста, так и его перегрузки в крупных кодовых базах.
- От написания кода на Kotlin к управлению агентами: новая роль инженера мобильных приложений — Как агенты-кодеры меняют работу инженера Android, смещая акцент на спецификации, контекст, ограничения архитектуры и проверку, а также какие основы становятся важнее, чем когда-либо.