Семь скрытых способов, как загрязнение контекста нарушает работу агентов LangGraph
Устаревшее состояние, огромные накопления инструментов, дублирующиеся фрагменты RAG, суммаризации с потерями данных и перегруженные наборы инструментов: семь сбоев, не вызывающих ошибок, вместе с способами их устранения и методами обнаружения каждого из них.
Источники: 7 Ways Context Pollution Quietly Killed My LangGraph Agents
Представьте агента LangGraph, который сообщает пользователю, что его проект «идет по плану и будет сдан в июле». Однако календарь показывает август. Журналы не содержат никаких исключений или сбоев в работе инструментов, и нигде нет тревожных сигналов. По всем показателям, которые отслеживает команда, агент функционирует нормально, и в течение трех дней он тихо вводит людей в заблуждение, пока кто-либо не заметит ошибку.
Подобные инциденты имеют одну скрытую причину — загрязнение контекста, и все семь приведенных ниже случаев связаны с ней. В каждом из них не было зафиксировано ошибок. Эти сценарии являются гипотетическими примерами; названия компаний и цифры вымышленные, однако механизмы, лежащие в их основе, — это то, с чем регулярно сталкиваются разработчики агентов.
Загрязнение контекста за две минуты
Когда агент рассуждает, он не видит вашего приложения. Он видит лишь один длинный блок текста — окно контекста, и ничего больше. Представьте это как стол: всё, что находится на столе, доступно для использования, а всё, что за его пределами, не существует для агента, даже если оно находится в вашей базе данных всего в нескольких футах от него.
Каждый раз на этот стол добавляется несколько элементов:
- промпт системы, то есть инструкции
- определения инструментов, то есть то, к чему может обратиться агент
- история разговора, то есть всё сказанное до этого момента
- результаты работы инструментов, то есть всё, что вернули ваши API
- состояние, то есть данные, хранящиеся и передаваемые узлами LangGraph
- вызванные документы, если используется технология RAG
Новички редко слышат об этом: у рабочего стола есть ограничение по размеру, и внимание распределяется на нем неравномерно. Контент может технически присутствовать, но на практике оставаться игнорируемым. Он также может быть устаревшим, дублированным или противоречивым, и модель не будет возражать. Она обрабатывает этот мусор без каких-либо комментариев.
Загрязнение контекста — это неправильный, устаревший, перегруженный или противоречивый материал, находящийся в системе и ухудшающий её работу без выдачи ни одной ошибки. В этом и заключается опасность. Сбой заставляет вас проснуться в 2 часа ночи, тогда как загрязнение просто делает работу вашего агента некорректной на 20% на протяжении всего времени.
Каждый из приведённых ниже случаев сопровождается симптомами, корневой причиной, способом решения и методом обнаружения в собственной системе, потому что сбой, который невозможно обнаружить, обязательно вернётся.
Случай 1: агент застрял в июле
Симптом. Возьмём агент отчётности для вымышленного сервиса под названием Meridian Energy. Он постоянно показывал «этомесячные» цифры, которые на самом деле принадлежали предыдущему месяцу. Пользователи винили систему обработки данных, но с этим всё было в порядке.
Коренная причина. При запуске сессии разработчик хранил активный период отчётности в состоянии LangGraph ровно один раз (ключ с именем current_period, содержащий значение 2026-07), причём этот значимый момент сохранялся. Позже пользователи возобновляли старые сессии — это популярная функция, — и сохранённый значимый момент вновь приводил к использованию данных за июль. Агент не путался; он следовал своему контексту, а в его контексте указан был июль.
Решение. Введите правило, изменяющее структуру проектирования состояния: никогда не храните то, что можно дешево пересчитать. Текущая дата, текущий период и всё, что обозначено как «сегодня», должны вычисляться заново в начале каждой работы, а не сохраняться. Перенос переменной current_period из состояния, сохраняемого в чекпоинтах, в отдельный небольшой узел, который пересчитывает её при каждом вызове, требует лишь одной строки кода и устраняет всю категорию ошибок.
Обнаружение. Во время разработки выводите словарь состояния в начале каждой работы и проверяйте для каждого ключа, может ли он быть устаревшим и заметите ли вы это. Любой ключ, содержащий дату, статус или слово «текущий», является подозрительным.
Случай 2: выгрузка инструмента объемом 8 000 токенов, затопившая инструкции
Симптом. Агент начал игнорировать правила форматирования. В системном сообщении говорилось открывать каждый ответ с таблицей резюме. Он так и делал, пока иногда не начал этого не делать. Случайно, что является худшим видом бага.
Коренная причина. Один инструмент, использующий API, вставлял необработанный JSON-ответ в контекст. Для небольших запросов это составляло 400 токенов. Для крупных запросов объем превышал 8 000 токенов в виде вложенного JSON, полного ID, метаданных, пустых полей и деталей пагинации, которые никому не были нужны. Попадая в середину контекста, этот объем превращал тщательно составленные инструкции в едва слышный голос на фоне шумной комнаты. Внимание сосредотачивалось на массиве JSON, и правила форматирования фактически исчезали.
Неприятный момент заключается в том, что это происходило только с крупными ответами. Небольшие запросы работали нормально, поэтому связь между «неправильным поведением агента» и «вопросом пользователя с большим объемом данных» была установлена только через несколько недель.
Решение. Инструменты должны возвращать факты, а не полные данные. Необходимо добавить внутрь каждого инструмента простой слой трансформации, который извлекает несколько необходимых полей, компактно их форматирует и удаляет остальное. Один такой инструмент сократил объем данных с примерно 8 000 токенов до примерно 300.
# Before: return response.json() # 8,000 tokens of noise
# After:
def summarize_response(data: dict) -> dict:
return {
"project": data["name"],
"status": data["status"],
"owner": data["owner"]["displayName"],
"due": data["dueDate"],
"open_risks": len(data.get("risks", [])),
} # ~300 tokens of signal
Обнаружение. Фиксируйте количество токенов в выводе каждого инструмента. Инструмент, способный вернуть больше токенов, чем весь запрос к системе, является источником «загрязнения», приводящим к получению длинного ответа.
Случай 3: коррекция, которая так и не заменила первоначальный запрос
Симптом. Пользователь просит информацию о Северо-Восточном регионе, затем сразу же исправляется и просит информацию о Северо-Западном. Агент правильно отвечает о Северо-Западном. После четырех вопросов, без дополнительных просьб, он снова переходит к информации о Северо-Восточном регионе.
Коренная причина. По умолчанию история разговора позволяет добавлять новые данные только в конец, поэтому неверное указание и коррекция остаются в контексте навсегда. Обычно модель правильно учитывает более новое сообщение, но это не всегда так, и по мере увеличения длины чата обе версии становятся одинаково правдоподобными. Агент не вспоминал неверно — он вспоминал слишком много информации.
Решение. Существуют два варианта.
- Устраняйте проблемы сразу, не накапливайте их. Как только вы обнаруживаете ошибку, не просто добавляйте исправление. Записывайте новое значение в структурированный блок данных состояния (регион устанавливается как Северо-Запад), который агент считает авторитетным, чтобы не приходилось снова обсуждать беспорядочный текст каждый раз.
- Редактируйте историю общения. LangGraph позволяет удалять или переписывать сообщения перед тем, как они снова попадут в контекст, поэтому устаревшие инструкции можно просто удалить.
Общий урок заключается в том, что история общения — это журнал записей, а не база знаний. Не заставляйте агента каждый раз извлекать истину из текста переписки.
Обнаружение. Добавьте в свои тесты сценарий самокоррекции: пользователь говорит X, затем исправляет это на Y, после чего задает ещё пять вопросов. Если агент снова начинает использовать значение X, значит у вас есть эта ошибка.
Случай 4: RAG пять раз вернул один ответ и скрыл настоящий
Симптом. Люди задавали вопросы, на которые в документации были четкие ответы, но агент предоставлял неопределенные, неполные ответы. Журналы поиска показывали шесть полученных фрагментов, так что казалось, что система поиска выполняет свою функцию.
Коренная причина. В корпусе данных находились почти идентичные фрагменты: один абзац из документации был вставлен в пять разных документов, как это часто делают реальные компании. Поиск по сходству работал идеально и возвращал пять почти одинаковых фрагментов, которые заняли место единственного отличного фрагмента с настоящим ответом. Пять мест за столом достались одному и тому же «гостю».
Решение. Устранять дубликаты во время получения данных. Даже примитивный метод хорошо работает: загружайте 20 кандидатов, удаляйте те фрагменты, которые слишком похожи на уже выбранные, и оставляйте самый разнообразный набор. Если ваш хранилище векторов поддерживает максимальную предельную релевантность, это то же самое решение, реализованное внутри системы. Качество ответов на вопросы по правилам сразу улучшилось без изменений в модели или запросе.
Обнаружение. Фиксируйте полученные фрагменты и просматривайте их еженедельно. Если какое-то предложение регулярно появляется дважды в одной группе, вы платите за избыточность ценой релевантности.
Случай 5: краткое изложение, в котором было проигнорировано единственное важное правило
Симптом. Чтобы снизить стоимость длительных сессий, команда ввела функцию постепенного обобщения: каждые N туров старые сообщения компрессируются. Этот стандарт широко рекомендуется. Однако сотрудник начал включать черновики записей в официальные отчеты, несмотря на то, что пользователь еще одиннадцать туров назад просил «исключить черновики».
Коренная причина. Система обобщения вела себя как таковая. Она сохраняла основную информацию (пользователь хотел региональные отчеты и говорил о формате), игнорируя то, что считалось менее важным. Но требование «исключить черновики» не было менее важным — это было ограничение, и ограничения нельзя перефразировать: они либо сохраняются в полном объеме, либо исчезают. Из всех семи сбоев этот вызывает наибольшую озабоченность, поскольку функция обобщения на демо-демонстрациях казалась абсолютно эффективной.
Решение. Разделить память на два канала до начала любой процедуры компрессии.
- Нарратив можно кратко обобщать.
- Ограничения, решения и предпочтения собираются в структурированный список, который никогда не обобщается. В него добавляются новые записи или они явно удаляются, причем весь список включается в контекст при каждом ходе.
В инструкции по обобщению можно прямо указать это: перед обобщением копируйте каждую инструкцию, ограничение и решение буквально в список ограничений.
Обнаружение ошибок. Во время разработки сравнивайте список ограничений до и после каждой процедуры обобщения. Если какое-либо ограничение сохраняется только внутри абзаца обобщения, это означает, что механизм сжатия нарушает ваши правила.
Случай 6: трейс ошибок, заставивший агента потерять доверие к рабочему инструменту
Симптом. Один инструмент на короткое время вышел из строя из-за истечения времени, но сразу после этого снова начал работать. Однако в течение всего оставшегося времени сессии оператор продолжал сообщать пользователям, что не может подключиться к системе отчетности, хотя на самом деле подключение происходило успешно. Пользователи, естественно, верили словам оператора больше, чем данным.
Коренная причина. Полная информация об ошибке — TimeoutError через 30 секунд — была добавлена в историю и так и осталась там. Для модели утверждение «система отчетности ненадежна» стало аксиомой, и все последующие ответы были под влиянием этого утверждения. Одна временная ошибка превратилась в постоянное искажение информации.
Исправление. Рассматривайте ошибки как события, а не как факты. После успешной попытки повтора замените запись об ошибке в истории однострочной примечанием вроде "(временная ошибка, устранена при повторной попытке)". Если инструмент окончательно выходит из строя, предоставьте агенту чистый структурированный статус, а не трейс вызовов. Трейсы вызовов предназначены для логов, а не для контекста.
Обнаружение. Поискайте в актуальных контекстах строки Error, Traceback и Exception. Каждое совпадение представляет собой либо продолжающийся инцидент, либо остатки данных, влияющие на последующие операции; оба типа должны быть устранены.
Случай 7: двадцать пять инструментов в одном окне
Симптом. За несколько месяцев сервис стал работать медленнее и дороже, что было предсказуемо из-за постоянного добавления новых функций. Он также начал выбирать неподходящий инструмент для простых запросов — например, возвращать экспорт данных о соответствии требованиям, когда пользователь хотел получить краткое описание проекта. Ничего не сломалось; всё просто стало более хаотичным.
Коренная причина. Комплекс инструментов насчитывал 25 элементов, каждый из которых имел длинное описание и JSON-шаблон; всё это в сумме занимало примерно 12 000 токенов ещё до того, как пользователь вводил хотя бы одно слово. При каждом ответе модель рассматривала 25 вариантов, многие из которых имели схожие описания, такие как «получить данные проекта», «загрузить детали проекта» и «вызвать информацию о проекте». Выбор инструмента представляет собой задачу логического мышления, но команда превратила её в тест с 25 похожими вариантами ответов.
Решение. Две части.
- Агрессивная оптимизация. Объедините перекрывающиеся инструменты — здесь количество сократилось с 25 до 11 — и перепишите описание каждого так, чтобы оно отвечало на один вопрос: когда модели следует выбрать этот инструмент вместо соседних?
- Загрузка инструментов в зависимости от контекста. Не каждому узлу нужны все инструменты. В LangGraph легко привязать к каждому узлу только соответствующий набор инструментов, так что узел для генерации отчетов может видеть четыре инструмента, а не 25.
Стоимость токенов за ход снизилась примерно на треть, а случаи выбора неподходящего инструмента практически исчезли.
Обнаружение проблем. Посчитайте количество токенов только в определениях ваших инструментов. Если их количество превышает объем системного промпта и среднего объема диалога вместе, ваш агент сдаёт экзамен с открытой книгой, в которой большинство страниц содержат неверные ответы.
Что общего у этих семи элементов
Перечитайте текст семь раз — и проявится единый образец. Ни один компонент не был поврежден, и ничего не отсутствовало. Загрязнением был постоянный избыток, устаревший или повторяющийся контент.
Именно поэтому здесь традиционные методы отладки не срабатывают. Инженеров учат искать отсутствующие элементы: пропущенную проверку на null, неразобранную исключение. Для борьбы с контекстным загрязнением требуется противоположный навык — умение искать то, чего там не должно быть.
Если вы хотите выработать одну привычку, пусть это будет чтение необработанного контекста вашего агента. Пропустите логи и панель мониторинга следов и прочитайте реальный текст, который модель получает в реальных производственных условиях. Это самый ценный час для всех, кто создает агентов, но почти никто им не пользуется.
Чек-лист за шестьдесят секунд перед развертыванием
- Является ли какое-либо значение состояния датой, статусом или чем-то вроде «текущего»? (Случай 1)
Семь вопросов. Любой ответ вроде «вероятно, но не уверен» — это инцидент, ожидающий своей даты.