Контекст LLM - не база данных

Всё начинается с разумной тулзы

Представим агента, который оценивает тональность публичных упоминаний в сети. Субагент вызывает тулзу, получает список упоминаний и анализирует результаты. Для небольшого списка естественная схема работает:

  1. вызвать тулзу;
  2. положить результат в диалог;
  3. позволить модели продолжить рассуждение.

Проблема начинается, когда список растёт. В контекстном окне уже находятся системные инструкции, текущая задача, предыдущие вызовы инструментов, память и рассуждения агента. Результат тулзы конкурирует со всем этим за один и тот же бюджет контекста.

Сбой может произойти посреди корректного запуска. Цикл инструментов остановится, модель не сможет продолжить работу, а расход токенов резко вырастет вместе с размером набора данных. С отдельными записями может быть всё в порядке. Ошибка находится на границе между выполнением тулзы и контекстом модели.

Результат тулзы становится частью prompt contract

LLM не получает отдельный курсор базы данных, когда тулза возвращает большой результат. Если harness не предпринимает специальных действий, результат станет частью следующего prompt.

Возникает неявный контракт:

любая тулза должна вернуть результат, который помещается в оставшийся контекст.

Его легко не заметить, потому что в сигнатуре тулзы он не записан. Функция может возвращать list[Record], хотя её настоящий эксплуатационный контракт ближе к формулировке: «вернуть список, сериализованное представление которого помещается рядом со всем диалогом». При этом скрытое ограничение меняется по мере роста диалога.

Поэтому у тулз фактически есть два разных класса, даже если код сначала представляет их одинаково.

Inline-тулзы

Inline-тулза возвращает небольшой результат: статус, одну запись, короткое объяснение или ограниченную страницу. Оставить такой результат в контексте полезно: он нужен модели для следующего решения.

Data-тулзы

Data-тулза ищет, сканирует или преобразует потенциально большую коллекцию. Её результат растёт вместе с набором данных или количеством совпавших записей. Возврат полного результата inline делает размер prompt функцией внешних данных.

Это неправильное место для хранения данных.

Контекст - рабочая память, а не долговременное хранилище

Контекстное окно - рабочая память цикла агента. В нём находится ограниченный фрагмент состояния, необходимый модели для следующего решения. Это не база данных, не архив логов и не надёжное хранилище всех результатов, созданных во время запуска.

Большая история диалога создаёт ту же проблему, что и большой результат тулзы. Логи, документы, результаты поиска и найденная память тоже могут расти без полезной верхней границы. Если складывать их в prompt, каждый следующий шаг будет платить за их размер, даже когда модели уже не нужна большая часть содержимого.

Внешнее хранилище меняет контракт. Данные остаются полными и долговечными, а в контексте находится только handle, summary или ограниченная страница.

Безопасный lifecycle результата тулзы

У результата инструмента должен быть явный lifecycle. Практично разделять его так:

Модель должна получать устойчивую ссылку, а не неконтролируемый payload. Например, data-тулза может вернуть handle, размер или примерный объём результата и первую ограниченную страницу. Следующий вызов запросит следующую страницу или поиск по сохранённому результату.

Так сохраняется важное различие: prompt модели ограничен, но данные системы не нужно обрезать только потому, что модель сейчас ими рассуждает.

Одной пагинации недостаточно

Пагинация ограничивает размер одного ответа, но сама по себе не управляет временем жизни данных, уже попавших в диалог. Агент всё равно может прочитать сотни страниц и протащить их все дальше по циклу.

Поэтому harness нужен ещё и eviction policy. Когда страница или подробный результат тулзы больше не нужны для следующего решения, их можно убрать из активного контекста. Долговечный результат останется снаружи и будет доступен через handle.

Eviction безопасен только тогда, когда удалённое содержимое можно получить снова. Короткий stub должен сообщать агенту, что именно было убрано и как это получить. Тихое удаление меняет смысл диалога и усложняет отладку.

Границу должен обеспечивать harness

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

Harness может закрепить границу кодом:

Это проверяемые свойства. Модель может решить, какую страницу запросить или какой поиск выполнить, но детерминированный код должен не допустить бесконтрольного payload в prompt.

Что изменилось после инцидента

Тулза оценки тональности начала падать, когда накопилось много упоминаний. Решение заключалось не в простом увеличении context window и не в надежде на лучшую суммаризацию. Мы изменили одновременно контракт тулз и harness.

Тулзы, работающие с большими данными, теперь используют cursor и пагинацию, а данные хранятся в базе. Harness распознаёт такие тулзы и удаляет ненужный output во время tool loop. В контексте остаются handles, stubs и ограниченные страницы, а полный результат живёт снаружи.

Большой набор данных теперь может потребовать больше шагов извлечения, но больше не раздувает каждый prompt без ограничений.

Практический checklist

При добавлении тулзы нужно спросить:

Если ответ на первый вопрос положительный, тулзу нельзя считать простой inline- функцией. Для неё нужно заранее определить внешний lifecycle - до того, как production-набор данных сломает скрытый prompt contract.

Вывод

Контекст LLM - рабочая память для следующего решения агента. Он конечен, дорог и разделяется между инструкциями, историей, памятью и результатами тулз. Это не база данных.

Растущие данные нужно хранить снаружи. Им нужны handles, пагинация, поиск и явная политика хранения. В prompt следует помещать только ограниченное полезное состояние, а harness должен обеспечивать эту границу. Так сохраняются и надёжность модели, и полнота исходных данных.