Перейти к содержимому
PD
AI-автоматизация 8 мин чтения

Контекст - это бюджет, а не ведро: архитектура памяти агента, которая выживает в проде

Большинство AI-агентов ломаются не из-за плохих промптов, а из-за того, что контекст растёт бесконтрольно, а счёт за API улетает в космос. Разбираю, как я бюджетирую, сжимаю и храню память агента в реальных проектах.

PD

Pavel Duglas

AI Automation & MVP Architect

Каждый агент, который я выкатывал в прод и который потом ломался, ломался одинаково. Не галлюцинация, не кривой tool call - контекстное окно тихо забивалось мусором, модель начинала игнорировать инструкции, а в конце месяца приходил счёт, похожий на смету на сервер. Про качество промптов говорят все. А работает или нет ваш агент на 40-м шаге так же, как на 3-м, решает архитектура контекста.

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

Тот самый режим отказа, который никто не считает

Наивный цикл агента дописывает в контекст всё: сообщение пользователя, рассуждения модели, каждый вызов инструмента, каждый результат. И на следующем шаге отправляет весь этот ком заново. Стоимость шага растёт линейно, а суммарная стоимость задачи из N шагов - квадратично. Это простая математика, но её почему-то обнаруживают только по инвойсу.

Реальные цифры из агента для обогащения данных, который я рефакторил в прошлом году:

  • System prompt + схемы инструментов: ~6k токенов
  • Средний результат инструмента (обрезанный HTML-фетч): ~4k токенов
  • Шагов на задачу: 25–40

Наивный цикл, 35 шагов: последний вызов тащит ~145k токенов, а сумма по задаче - около 2.6M входных токенов. После рефакторинга - та же модель, тот же success rate - 310k входных токенов. Восьмикратная экономия без единого хитрого приёма в prompt engineering. И точность при этом выросла, потому что модель перестала тонуть в протухшем HTML с четвёртого шага.

Вот эту часть недооценивают чаще всего. Длинный контекст не просто дорогой - он вредный. Инструкции в промпте на 6k токенов выполняются. Те же самые инструкции, погребённые под 140k токенов шума от инструментов, выполняются «примерно». Модель начинает деградировать от нерелевантного контекста задолго до того, как упрётся в лимит окна.

Четыре вида памяти с разным временем жизни

Перестаньте думать про «контекст» как про одну сущность. Думайте про четыре хранилища с разными правилами попадания в промпт.

1. Константная память - system prompt, роль, жёсткие правила, схемы инструментов, формат вывода. Пишется один раз, внутри рана не мутирует. Идёт в каждый вызов.

2. Рабочая память - состояние текущей задачи: цель, план, что сделано, что осталось, найденные ключевые факты. Маленькая, структурированная, переписывается на каждом шаге. Идёт в каждый вызов.

3. Эпизодическая память - сырой транскрипт: сообщения, вызовы, результаты. Растёт бесконечно. Не идёт в каждый вызов. Живёт в базе или на диске, доступна по ID.

4. Внешняя память - долговечные артефакты: файлы, строки в БД, спарсенные страницы, сгенерированные отчёты, профиль пользователя. Достаётся по запросу, никогда не пушится сама.

Вся дисциплина сводится к одному: всегда присутствуют только 1 и 2. Всё остальное попадает в промпт через явное, забюджетированное извлечение по требованию.

Каждому слоту - свой бюджет байтов

Я выписываю бюджет до того, как пишу цикл. Дефолт для среднего агента на модели с окном 200k:

СлотБюджетПоведение при переполнении
System + правила3kHard fail - переписывать промпт
Схемы инструментов4kПрунить инструменты по фазе
Рабочее состояние (JSON)2kСжать самые старые поля
Последние N результатов8kОбрезать + сохранить на диск
Извлечённые документы10kВыкинуть чанки с низким скором
Хвост диалога6kСвернуть в рабочее состояние
Итого на вызов~33kKill switch на 60k

Kill switch тут важнее самого бюджета. Если один вызов пробивает жёсткий потолок - ран падает и пишет диагностический дамп. Это превращает молчаливые 400 долларов за выходные в алерт в Slack в пятницу днём. У меня агент-скрапер однажды залип в цикле fetch-retry; потолок отловил это за 90 секунд.

Отдельно про прунинг схем. Агент с 18 инструментами сжигает 6–8k токенов на определения в каждом вызове, и качество выбора инструмента при этом падает. Разбейте ран на фазы (research → decide → write) и показывайте 4–6 инструментов на фазу. Дешевле и точнее одновременно.

Сжимать в структуру, а не в пересказ диалога

Самый популярный подход к компакции - «суммаризировать диалог, когда он разросся». Он теряет ровно то, что терять нельзя: сохраняет общее настроение и выбрасывает идентификаторы. Я видел саммари, где осталось «пользователь спрашивал про тарифы», а account ID, нужный агенту через три шага, испарился.

Вместо этого держите один явный объект состояния, который агент переписывает (а лучше - который вы детерминированно обновляете после каждого шага):

{
  "goal": "Обогатить 500 компаний: email основателя + headcount",
  "phase": "enrichment",
  "progress": { "done": 312, "failed": 9, "remaining": 179 },
  "decisions": [
    "LinkedIn заблокировал на шаге 14 -> ушли на Apollo fallback",
    "Headcount из Crunchbase приоритетнее футера сайта"
  ],
  "open_questions": [],
  "artifacts": [
    { "id": "a_71", "kind": "csv", "path": "/run/out/enriched_p1.csv", "rows": 312 }
  ],
  "next_action": "продолжить батч с offset 312"
}

Это ~400 токенов, и они заменяют 60k транскрипта. Модель читает и понимает, где она находится. И главное - объект переживает рестарт процесса: это ваша точка возобновления. Пишите его в Postgres или в JSON-файл после каждого шага. Агент, который не умеет продолжить после падения, - не продакшн-система, а демо.

Моё правило: транскрипт - это лог, состояние - это память. Логи для людей, которые дебажат. Состояние - для модели.

Возвращайте хендлы, а не payload

80% раздувания контекста живёт в выводе инструментов. Инструмент, возвращающий 40k символов HTML или результат запроса на 2000 строк, - это баг проектирования, а не особенность.

Что должны возвращать инструменты:

ПЛОХО:  fetch_page(url) -> "<html>...38 000 символов...</html>"
ХОРОШО: fetch_page(url) -> { "artifact": "pg_44",
                            "path": "/run/cache/pg_44.html",
                            "bytes": 38214,
                            "title": "Pricing - Acme",
                            "preview": "первые 600 символов..." }

А дальше дайте агенту узкие «читалки» по артефактам: grep_artifact(id, pattern), read_lines(id, start, end), extract_fields(id, schema). Модель вытащит нужные 200 токенов вместо того, чтобы платить за 10k ненужных. Именно так работает нормальный инженер с большим файлом - никто не читает лог целиком, все его грепают.

То же с SQL: возвращайте количество строк, первые 5 строк и путь к полному CSV. То же с API: срезайте конверты ответа, выкидывайте null, выкидывайте поля, которых нет в вашей схеме. У меня есть хелпер slim(), который проецирует любой ответ API на белый список полей до того, как он коснётся промпта. Стабильно режет payload на 70%.

Порядок под prefix-кэш

Все крупные провайдеры кэшируют повторяющиеся префиксы и берут за попадание в кэш долю цены. Это скидка в 5–10 раз на самый большой и самый стабильный блок - но только если префикс совпадает побайтово.

Отсюда правило: стабильное вперёд, изменчивое в хвост.

  1. System prompt и правила (не меняются никогда)
  2. Схемы инструментов (стабильны внутри фазы)
  3. Долгоживущий референс (style guide, конфиг клиента)
  4. Рабочее состояние (меняется каждый шаг)
  5. Свежие результаты инструментов (меняются каждый шаг)

И перестаньте писать Current time: 2026-02-11T14:03:22Z в начале system prompt. Один изменчивый токен на 12-й позиции инвалидирует кэш для всего, что идёт после. Нужно агенту время - дайте ему get_time() или подставьте время в изменчивый хвост. Я видел, как одно это изменение срезало клиенту счёт больше чем вдвое.

Без метрик вы просто угадываете

На каждый шаг логируйте: step_index, input_tokens, cached_tokens, output_tokens, tool_name, tool_result_bytes, state_bytes, latency_ms. На ран: суммарную стоимость, число шагов, пик контекста, исход.

Метрика, на которую я реально смотрю, - стоимость завершённой задачи, а не стоимость токена. Модель в 3 раза дороже за токен, но закрывающая задачу за 6 шагов вместо 30, дешевле. Дешёвые модели, которые ходят по кругу, - самая дорогая штука в AI-автоматизации.

Вторая метрика - cache hit ratio. Ниже 60% на многошаговом агенте означает, что префикс нестабилен: идите искать таймстемп.

Третья - пик контекста за ран, построенный по времени. Если он ползёт вверх неделя к неделе, значит какой-то инструмент начал возвращать больше данных, чем раньше. Это ваш ранний сигнал - раньше, чем инвойс.

Цикл, примерно так

state = load_state(run_id) or init_state(goal)

while not state["done"] and state["step"] < MAX_STEPS:
    ctx = build_context(
        constant=SYSTEM,                        # стабильный префикс
        tools=tools_for_phase(state["phase"]),  # 4-6 инструментов
        state=state,                            # ~400 токенов
        tail=recent_results(limit=3, max_tokens=8000),
    )
    assert count_tokens(ctx) < HARD_CEILING

    step = model.call(ctx)
    result = run_tool(step.tool, step.args)     # возвращает хендл
    store_artifact(result)
    state = update_state(state, step, result)   # где можно - детерминированно
    save_state(run_id, state)                   # crash-safe

Обратите внимание: update_state детерминирован там, где это возможно. Счётчики, список артефактов, переходы между фазами - это Python, а не вывод LLM. От модели приходит только нечёткое: решения, открытые вопросы. Каждое поле, которое вы можете посчитать в коде, - это поле, которое модель не сможет испортить.

Чего я не делаю

  • Не тащу векторную БД по умолчанию. Для большинства агентов объект состояния + grep по файлам + один SQL-запрос бьют семантический поиск. Эмбеддинги нужны, когда у вас тысячи документов и реальная проблема recall, а не потому что «так принято в архитектуре».
  • Не беру memory-фреймворки с мнением. Два таких я заменил на ~200 строк кода состояния и артефактов - и получил лучшую наблюдаемость.
  • Не храню бесконечную историю диалога. HTML с четвёртого шага не нужен на сороковом. А если внезапно нужен - для этого и есть хранилище артефактов.

Context engineering - неблагодарная работа: считать байты, проецировать поля, следить за кэшем. Но именно она отделяет демо агента от агента, который крутит 500 задач в сутки по цене, которую можно спокойно вписать в коммерческое предложение.

Вопросы и ответы

С чего начать, если у меня уже есть работающий агент и я хочу срезать расходы?

Сначала измерьте: залогируйте на каждом шаге input_tokens, cached_tokens и tool_result_bytes. Почти всегда 70–80% раздувания приходится на 1–2 инструмента, возвращающие сырой HTML или большие JSON-ответы. Переведите их на возврат хендла (id артефакта + путь + preview) и добавьте grep/read_lines. Второй шаг - вынести таймстемпы и любые изменчивые данные из начала system prompt, чтобы поднять cache hit ratio. Эти два изменения обычно дают основной эффект без переписывания логики.

Нужна ли векторная база для памяти агента?

В большинстве проектов - нет, по крайней мере не на старте. Если у агента есть структурированный объект состояния, хранилище артефактов на диске и возможность грепать файлы или сделать SQL-запрос, этого хватает для задач с десятками-сотнями документов. Эмбеддинги стоит добавлять, когда у вас реально тысячи документов и вы упираетесь в recall: агент не находит нужный файл, потому что не знает, что искать. Векторная БД, добавленная «на всякий случай», добавляет инфраструктуру, latency и новый источник неточных попаданий в контекст.

Как сделать так, чтобы агент продолжал работу после падения процесса?

Сохраняйте объект состояния после каждого шага в Postgres или JSON-файл по run_id, и держите в нём всё, что нужно для возобновления: фазу, прогресс со счётчиками и offset, список артефактов с путями, next_action. Артефакты пишите на диск или в S3 сразу, а не в конце. Тогда рестарт - это load_state(run_id) и продолжение цикла с того же места. Ключевой момент: счётчики и переходы между фазами должны обновляться кодом, а не выводом модели, иначе состояние после рестарта может оказаться несогласованным.

Похожие статьи

  • #AI Agents
  • #Context Engineering
  • #LLM Costs
  • #Architecture
  • #Production AI

Есть идея? Давайте превратим её в работающий продукт.

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