Почему уплотнения недостаточно
Context Compaction выбрасывает старые результаты вызовов. Это необходимо и этого мало.
Если один результат весит пять тысяч токенов, выброс следующего уже ничего не спасает: модель их прочитала, и держать их в окне придётся ещё несколько ходов. Ущерб нанесён в момент возврата, а не в момент накопления.
Отсюда порядок: чинить надо выше по течению. Инструмент, который по умолчанию отдаёт маленький, структурированный, ограниченный результат, снимает задачу до её появления.
Контракт усечения: три обязательные части
Инструмент, способный вернуть неограниченный объём, обязан делать три вещи, и ни одну нельзя пропустить:
- Ограничить вывод разумным потолком.
- Сказать модели, что вывод усечён и насколько — иначе она считает, что видит всё.
- Дать способ добрать остальное — смещение и предел у чтения файла, более узкий шаблон у поиска.
Второй пункт — тот, ради которого заметка существует. Инструмент, который молча обрезает вывод, хуже инструмента без ограничения вовсе: модель уверена, что располагает полной картиной, и действует на неполных данных. Дефект при этом молчаливый — ни ошибки, ни исключения, ни следа в логе; наружу он выходит уже неверным решением.
Правильно устроенное сообщение об усечении несёт два действующих факта: остального больше, чем показано, и сколько именно. С этим агент может сузить поиск или пойти постранично; без числа он может только гадать.
Четвёртый выход: не резать, а переложить
Контракт выше исходит из того, что лишнее выбрасывается. Есть вариант, при котором ничего не выбрасывается: инструмент кладёт полный результат в файл и возвращает ссылку на него вместе с короткой сводкой. Потолок соблюдён, данные целы, а решение «нужны ли подробности» откладывается до момента, когда оно понадобится.
Когда это лучше усечения: результат ценен целиком и понадобится позже — выдача поиска, содержимое страницы, длинный отчёт. Когда хуже: результат нужен целиком сейчас — тогда ссылка просто добавляет лишний вызов.
Сводка при выгрузке служит навигации, а не сохранению смысла, и это надо решить явно. Цель формулируется так: помочь агенту понять, что у него уже собрано, чтобы решить, искать ли дальше, — а не сохранить содержание. Из этого следуют свойства, неочевидные при обратной цели:
- жёсткий и маленький потолок — порядка полутора сотен слов; сводка, из которой можно ответить на вопрос, для навигации избыточна, потому что снова заполняет окно;
- состав фиксирован: о чём это, какого рода информация, один-два самых значимых пункта. Не пересказ;
- имя файла тоже генерируется по содержанию и работает вторым слоем навигации: список файлов сам по себе отвечает на вопрос «что уже собрано», без чтения сводок.
Смешение двух назначений — типовая ошибка этого места. Сводка, написанная «чтобы не потерять смысл», разрастается и начинает конкурировать с оригиналом; сводка «чтобы понять, что собрано» остаётся дешёвой и позволяет держать десятки источников в поле зрения за цену одного.
Где живут выгруженные файлы — отдельное решение. Диск, песочница или поле состояния графа: последнее делает рабочую область частью снимка, поэтому откат возвращает и её (LangGraph Checkpointers).
Рабочие потолки и почему они такие
| Инструмент | Потолок | Обоснование |
|---|---|---|
| чтение файла | 500 строк | хватает, чтобы понять устройство большинства файлов, и не хоронит модель |
| поиск по коду | 50 совпадений | поиск, вернувший полсотни, на вопрос ответил; пятьсот — это свалка данных |
| выполнение команды | 5000 символов | вывод обычных команд помещается, а шум установки пакетов модели не нужен |
Числа не священны. Они подобраны прогоном настоящих задач и наблюдением за тем, где становится больно: если в вашем контуре команды регулярно дают длинный и осмысленный вывод, потолок поднимают; если преобладают быстрые поиски, опускают. Универсального значения нет, есть метод — начать с этих и калибровать на своём трафике.
Ограниченный не значит крошечный. Потолок ставится там, где вывод перестаёт отвечать на вопрос и начинает заполнять окно.
Какую половину оставлять
Решение неочевидное и стоит отдельно: у вывода команды сохраняется хвост, а не начало.
Причина в том, где живёт нужное. Упавший тест печатает провалы последними. Упавшая сборка печатает ошибку последней. Трассировка стека заканчивается тем, ради чего её читают. Оставить начало значит отдать модели преамбулу и выбросить причину.
Обратное верно для инструментов, где смысл в начале — у структурированного отчёта, у файла с заголовком. Правило формулируется не как «оставляй хвост», а как «оставляй ту половину, где живёт причина решения», и для команд это хвост.
Цена, которую платит агент
Ограничение вывода делает часть задач медленнее: чтобы прочитать файл на две тысячи строк, агенту нужно четыре вызова вместо одного. Это правильный размен — четыре ограниченных чтения дешевле одного огромного, потому что огромное отравляет контекст до конца сессии, а ограниченные уходят из него по мере уплотнения.
Побочный эффект приятный: пагинация заставляет агента формулировать, что именно он ищет, вместо запроса всего сразу.
Где это проверять
Дефект молчаливый и проверяется формой кода — значит годится и в пункт чеклиста, и в правило структурного поиска: вызов инструмента, возвращающий результат внешней команды или чтения без ограничения длины. Признак ищется по возврату stdout целиком либо по отсутствию среза перед возвратом.
Отдельно стоит проверять второй пункт контракта, а не только первый: ограничение без сообщения об усечении формально закрывает пункт и оставляет ровно тот дефект, ради которого он заведён.
Связано с
- Context Compaction — уборка старых результатов; здесь профилактика, и одно без другого не работает
- Tool Calling — где ставится ограничение
- Tool Catalog — контракт инструмента; усечение — его часть, а не деталь реализации
- Agent CostControl — вход дороже выхода, поэтому ограничение вывода это прежде всего деньги
- Sandbox Abstraction — второй контракт того же слоя: где инструмент исполняется
- Prompt Caching — что делать с той частью контекста, которая остаётся