Context Engineering: что это и как управлять контекстом LLM

Context Engineering — это проектирование рабочей памяти LLM: что положить туда сейчас, что оставить снаружи и в какой момент это сделать.

04.09.2026 · 17 мин чтения

Последние несколько лет вокруг LLM много говорили про Prompt Engineering. Нужно правильно сформулировать задачу, подобрать system prompt, добавить примеры, указать формат ответа и получить от модели нужный результат. Для простых сценариев этого действительно было достаточно. Есть один запрос, один prompt, один ответ — основная работа происходит внутри текста, который мы отправляем модели.

С агентами всё стало немного сложнее. Модель получает историю диалога, результаты поиска, документацию, список доступных tools, ответы этих tools, информацию о пользователе, состояние задачи и ещё десяток вещей. Если агент работает долго, всё это постепенно накапливается. В какой-то момент начинаешь замечать странное: prompt вроде нормальный, модель хорошая, нужная информация где-то внутри есть, а качество ответа всё равно падает.

У меня эта проблема особенно хорошо проявилась во время работы с MCP. Можно написать нормальный tool, дать ему понятное название, описать параметры и подключить к LLM. Но этого недостаточно. Если одновременно передать модели слишком много инструментов, загрузить несколько документов, добавить длинную историю разговора и оставить результаты предыдущих вызовов, ей приходится разбираться уже не с самой задачей, а с тем, какая часть всей этой информации сейчас действительно важна.

Вокруг этой проблемы и появился термин Context Engineering. Если Prompt Engineering отвечает в основном за то, как сформулирована инструкция, то Context Engineering занимается всей информацией, которую модель видит в момент следующего вызова. Anthropic определяет это как управление оптимальным набором токенов, доступных модели во время inference, а Vercel формулирует немного практичнее: нужно решить, что попадёт в context window на следующем шаге и в каком виде.

Мне нравится смотреть на Context Engineering ещё проще: это проектирование рабочей памяти LLM. Что положить туда сейчас, что оставить снаружи, что найти по запросу, что удалить, что сохранить на потом и в какой момент всё это сделать.

Что вообще находится в контексте LLM

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

В context window может находиться system prompt с общими правилами поведения, текущий запрос пользователя, история разговора, few-shot примеры, найденные через RAG документы, информация из памяти агента, описания доступных tools и результаты предыдущих вызовов. Если приложение работает с MCP, туда же в конечном счёте попадают описания MCP tools и данные, которые сервер вернул после их использования.

Схематично это можно представить примерно так:

Context
├── System instructions
├── User request
├── Conversation history
├── Examples
├── Retrieved documents
├── Memory
├── Tool definitions
└── Tool results

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

System prompt обычно относительно стабилен. Запрос пользователя появляется прямо сейчас. Документы можно найти через search или RAG. Memory хранится отдельно и возвращается при необходимости. Tools вообще могут динамически меняться в зависимости от текущего шага. Context Engineering начинается как раз с понимания, что всё это необязательно каждый раз отправлять модели одновременно.

Почему хорошего prompt уже недостаточно

Prompt Engineering никуда не исчез. Хорошая инструкция по-прежнему сильно влияет на результат. Просто prompt стал одним из элементов более крупной системы.

Допустим, мы хотим использовать LLM для code review и пишем достаточно хороший prompt:

Проведи code review изменения. Обрати внимание на возможные ошибки, архитектурные проблемы и обратную совместимость.

Сам текст понятный. Но если модели не передали diff, связанные файлы, правила проекта и информацию о том, какую задачу вообще пытался решить разработчик, качество review будет довольно случайным. Можно ещё час улучшать формулировку prompt, но проблема останется в другом месте — у модели просто нет нужного контекста.

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

Получается довольно простой переход. Раньше основной вопрос звучал так: «Как правильно попросить модель?». Теперь всё чаще приходится задавать другой: «Что модель должна знать именно на этом шаге?»

И вот второй вопрос обычно оказывается сложнее первого.

Большой context window не означает, что туда нужно положить всё

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

Технически можно. На практике это не всегда помогает.

Anthropic отдельно пишет про эффект context rot: по мере увеличения количества информации способность модели точно работать с отдельными элементами контекста может снижаться. Они предлагают относиться к контексту как к конечному ресурсу с ограниченным attention budget. То есть ограничение связано уже не только с максимальным количеством токенов, которое физически помещается в окно, но и с тем, насколько хорошо модель способна выделить среди них действительно важную информацию.

На мой взгляд, здесь довольно хорошо работает аналогия с собственной рабочей памятью. Можно открыть двадцать вкладок браузера, пять IDE, документацию, Slack и три таск-трекера. Формально вся информация находится перед глазами. Работать от этого проще обычно не становится.

С LLM происходит что-то похожее. В большом контексте появляется несколько типов проблем. Нерелевантная информация отвлекает от важной, старые данные продолжают влиять на решение после того, как перестали быть актуальными, а два разных документа могут одновременно содержать противоречащие друг другу правила. Отдельная история — результаты tools: агент может за несколько шагов накопить огромные JSON-ответы, которые были нужны один раз, но продолжают передаваться модели дальше.

Поэтому одна из главных задач Context Engineering состоит не в том, чтобы найти способ передать модели больше информации. Гораздо полезнее научиться определять, что ей сейчас можно вообще не показывать.

Как собирать контекст

Если посмотреть на практические подходы, большую часть Context Engineering можно свести к нескольким операциям. Vercel, например, выделяет retrieval, memory, compression и tool context. Anthropic отдельно рассматривает just-in-time retrieval, compaction, structured notes и subagents для длинных задач.

Мне удобнее мысленно делить работу с контекстом на четыре действия:

WRITE
SELECT
COMPRESS
ISOLATE

WRITE — заранее записать информацию, которая точно понадобится модели. Сюда попадают system instructions, базовые правила проекта, профиль пользователя или сохранённое состояние задачи.

SELECT — выбрать нужные данные из более крупного источника. Это уже search, RAG, vector search, SQL, filesystem и любые tools, с помощью которых модель или приложение достаёт только актуальную часть информации.

COMPRESS — уменьшить накопившийся контекст, сохранив смысл. Например несколько десятков сообщений можно превратить в краткое состояние задачи, а огромный результат tool call заменить несколькими важными фактами.

ISOLATE — вообще не помещать часть информации в основной context. Её можно оставить во внешнем хранилище, отдать отдельному subagent или получить только тогда, когда она реально понадобится.

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

Retrieval: доставать информацию тогда, когда она нужна

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

Именно так работает классический RAG. Пользователь задаёт вопрос, система находит несколько подходящих документов и добавляет их в context перед вызовом LLM. Это уже Context Engineering, потому что мы принимаем решение, какую часть внешнего мира модель увидит сейчас.

С агентами появляется ещё более интересный вариант. Вместо того чтобы автоматически делать retrieval перед каждым запросом, можно дать модели инструменты для самостоятельного поиска. Anthropic называет такой подход just-in-time context: агент сначала видит лёгкие ссылки, идентификаторы, пути к файлам или metadata, а полное содержание загружает только тогда, когда понимает, что оно понадобится.

Мой MCP-сервер для Obsidian работает примерно по этой логике. Теоретически я мог бы при старте загрузить в context весь vault. Только особого смысла в этом нет: большая часть заметок никак не связана с текущим вопросом.

Гораздо логичнее выглядит цепочка:

vault_tree
     ↓
vault_search
     ↓
vault_read
     ↓
нужные заметки
     ↓
LLM

Сначала модель видит структуру или выполняет поиск, потом читает несколько действительно нужных документов. Остальные тысячи Markdown-файлов так и остаются на диске.

На этом примере хорошо видно отличие context window от базы знаний. База может быть огромной. Контекст на конкретном шаге при этом желательно держать небольшим.

Memory находится снаружи модели

Со словом memory вокруг AI тоже возникло немного путаницы. Иногда создаётся ощущение, что модель сама где-то внутри помнит пользователя или прошлую работу. В большинстве практических систем память устроена гораздо прозаичнее: нужная информация хранится во внешнем storage и при следующем вызове снова добавляется в контекст.

Полезно разделять хотя бы три уровня. Есть текущая рабочая память — context window конкретного вызова. Есть состояние текущей сессии — история сообщений и результаты недавних действий. И есть long-term memory, которая может жить в обычной базе данных, vector store, файлах или отдельном сервисе.

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

Другой вариант — явно сохранить его:

Decision:
Refresh tokens храним отдельно от access tokens.
Rotation выполняется при каждом refresh.

Теперь эта информация существует независимо от текущего context window. Когда агент продолжит работу над authentication, её можно снова вернуть в контекст.

Это простой принцип, но он довольно многое объясняет: если информация должна пережить сброс контекста, её нужно вынести за пределы контекста.

Именно поэтому нормальная memory subsystem — это в первую очередь вопрос хранения, поиска и правил обновления данных, а не какой-то отдельной способности LLM.

Что делать с длинными сессиями

Самая заметная проблема начинается с long-running agents. Обычный чат может закончиться через пять сообщений, а coding agent иногда делает десятки и сотни действий: читает файлы, запускает тесты, получает ошибки, меняет код, снова запускает тесты и постепенно накапливает длинную историю.

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

Для таких задач используется compaction. Содержимое длинной сессии периодически сжимается до более компактного состояния, после чего работа продолжается уже с ним. Anthropic отдельно относит compaction, structured note-taking и multi-agent architectures к основным способам поддерживать long-horizon задачи.

Важно только понимать, что summarization сама по себе может стать источником ошибок. Если при сжатии потерялось ограничение или важное нерешённое замечание, новый короткий контекст будет выглядеть аккуратно, но агент продолжит работу уже с неправильной картиной задачи.

Поэтому мне больше нравится не просто пересказывать всю историю одним абзацем, а сохранять структурированное состояние. Например текущее решение, уже выполненные шаги, открытые вопросы, известные ограничения и следующий ожидаемый шаг. Тогда мы контролируем хотя бы то, какие типы информации должны пережить compaction.

Вместо нескольких десятков тысяч токенов истории можно оставить что-то вроде:

Goal:
Добавить refresh token rotation.

Done:
- изменена модель Token
- добавлена таблица refresh_tokens
- написана миграция

Decisions:
- access token остаётся stateless
- refresh tokens храним в БД

Open:
- проверить concurrent refresh
- добавить integration tests

Для продолжения работы этого часто оказывается намного полезнее, чем полный лог последних пятидесяти действий.

Tools тоже являются контекстом

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

То есть каждый новый tool занимает часть контекста и добавляет ещё один вариант выбора.

Vercel отдельно включает tool context в основные составляющие Context Engineering: название, описание и schema инструмента влияют на то, сможет ли модель правильно выбрать его и передать валидные аргументы.

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

Например агент работает с кодом:

read_file
search_code
run_tests
git_diff

Ему в этот момент совершенно необязательно видеть:

send_email
create_calendar_event
search_crm
generate_invoice

Даже если технически система умеет всё перечисленное.

С этим же я столкнулся при проектировании MCP tools для Obsidian. Очень легко начать добавлять отдельную функцию под каждый возможный сценарий. В итоге появляется длинный список похожих tools, и модель должна выбирать между операциями, разница между которыми иногда понятна только их автору.

Небольшой набор понятных непересекающихся инструментов оказывается предсказуемее. В моём случае primitives вроде vault_search, vault_read, vault_glob и vault_tree дают модели достаточно свободы, чтобы собрать нужный сценарий самостоятельно, но при этом не заставляют её разбираться в десятках специальных методов.

Получается интересная вещь: Context Engineering постепенно возвращает нас к вполне обычным инженерным дисциплинам. Хорошее именование, маленький API surface, понятные контракты и отсутствие лишних зависимостей внезапно становятся важны ещё и потому, что этим интерфейсом пользуется LLM.

Пример с coding agent

Допустим, мы хотим поручить агенту задачу:

Добавь поддержку refresh tokens.

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

Я бы скорее начал с минимального контекста: system instructions с правилами работы, сама задача и дерево проекта. Дальше агент может найти места, связанные с authentication, прочитать несколько соответствующих файлов, посмотреть существующие тесты и только после этого формировать план изменения.

Task
 ↓
Repository tree
 ↓
Search "auth"
 ↓
Relevant files
 ↓
Existing tests
 ↓
Architecture decisions
 ↓
LLM

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

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

Вот вся суть Context Engineering в одном примере. Мы не пытаемся заранее угадать идеальный prompt и не загружаем весь доступный мир в LLM. Мы постепенно собираем рабочую область вокруг текущего решения.

Как связаны Context Engineering, RAG и MCP

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

Prompt Engineering отвечает за формулировку инструкций и структуру prompt. RAG помогает выбрать релевантные данные из внешнего источника и добавить их в контекст. Memory хранит состояние между вызовами и сессиями. MCP стандартизирует доступ AI-приложения к внешним данным и инструментам. Всё это можно рассматривать как отдельные механизмы внутри более широкой задачи Context Engineering.

Context Engineering
│
├── Prompt Engineering
├── RAG / Retrieval
├── Memory
├── MCP / Tools
└── Compression

Например пользователь спрашивает агента о внутренней документации компании. Через MCP агент получает tool search_docs. Внутри этого tool может работать vector search по Qdrant. Найденные документы возвращаются агенту и добавляются в context. Через несколько шагов важный вывод записывается в memory, а старые tool outputs удаляются во время compaction.

Здесь одновременно работают MCP, RAG, memory и compression. Но общая инженерная задача остаётся одной: обеспечить модели подходящий набор информации для следующего решения.

Поэтому Context Engineering мне кажется полезным термином. Он заставляет перестать смотреть на LLM как на функцию вида prompt → answer и начать смотреть на всё приложение целиком.

Как понять, что проблема именно в контексте

У проблем с контекстом есть довольно узнаваемые симптомы. Агент начинает повторять уже выполненные действия, забывает решения, которые сам принимал несколько шагов назад, выбирает не тот tool, использует устаревший документ или противоречит информации, которая находится в другом месте того же context window.

Иногда разработчик в этот момент первым делом меняет prompt. Если не помогло — модель. Потом temperature, ещё несколько инструкций и новую модель. При этом полезнее сначала посмотреть на реальный запрос, который отправляется LLM.

Что там вообще лежит?

Какая версия документа попала в retrieval?

Сколько истории мы таскаем за собой?

Есть ли там результаты старых tools?

Все ли доступные инструменты нужны для этой задачи?

Не противоречат ли друг другу system prompt, memory и найденный документ?

Это одна из причин, почему нормальный tracing для AI-приложений становится настолько важен. Разработчику недостаточно видеть итоговый prompt пользователя. Нужно понимать полный context payload перед конкретным inference и откуда каждый его кусок появился.

Иногда причина плохого результата становится очевидной уже после этого.

Как я бы проектировал контекст

Какого-то универсального рецепта здесь, конечно, нет. Но я бы начинал с определения минимальной информации, без которой модель физически не может решить задачу. Не «что мы можем ей дать», а именно «что ей обязательно нужно знать».

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

Отдельно нужно решить, что должно сохраняться между шагами и сессиями. Не вся история разговора заслуживает долгосрочной памяти. Чаще всего действительно нужны принятые решения, предпочтения пользователя, состояние длинной задачи и несколько важных фактов.

После этого появляется вопрос очистки. Tool outputs, debug logs, старые результаты поиска и уже решённые промежуточные вопросы не обязаны бесконечно жить в рабочем контексте. Если задача длинная, нужно заранее понимать, когда происходит compaction и какая информация должна гарантированно пережить его.

И наконец, всё это желательно измерять. Количество токенов само по себе мало о чём говорит. Важнее смотреть, успешно ли агент выполняет задачу, насколько точно retrieval находит нужные документы, как часто модель выбирает неправильный tool, сколько шагов тратит повторно и как меняется качество после длинной сессии.

В этом смысле Context Engineering постепенно становится не столько набором prompt-трюков, сколько вполне нормальной частью архитектуры AI-системы.

Что Context Engineering тоже не исправит

Здесь легко попасть в ту же ловушку, что раньше с Prompt Engineering. Найти новый термин и решить, что теперь достаточно «правильно спроектировать контекст», после чего модель будет работать идеально.

Не будет.

Хороший контекст не исправит плохие данные. Если retrieval возвращает мусор, модель получит качественно упакованный мусор. Нормальная memory subsystem не поможет, если мы сохраняем туда неправильные выводы. Отлично описанный tool всё равно выполнит неправильную бизнес-логику, если ошибка находится внутри самого сервиса.

То же самое относится к возможностям модели. Context Engineering помогает модели использовать имеющиеся способности эффективнее, но не превращает слабую модель в сильную. Если задача в принципе находится за пределами её возможностей, добавлением ещё одного документа это не решить.

Поэтому я бы воспринимал Context Engineering ровно как инженерную работу с входными данными LLM. Очень важную, но всё же только одну часть системы.

Вместо заключения

Когда context window моделей вырос до сотен тысяч токенов, можно было предположить, что проблема контекста постепенно исчезнет. На практике получилось почти наоборот. Чем больше информации мы технически можем передать модели, тем важнее становится решить, какую именно часть этой информации ей стоит видеть сейчас.

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

Поэтому Context Engineering для меня не выглядит новой заменой Prompt Engineering. Prompt по-прежнему важен. Просто вокруг него выросла система.

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

Гораздо интереснее научиться вовремя показывать ей правильную часть мира.

Олег Пацай

Инженерный лидер — AI, архитектура и сложные системы

Проектирую и развиваю сложные программные системы: от архитектуры и инженерных практик до интеграции AI в разработку.

GitHub LinkedIn Telegram Связаться

Давайте делать сложное понятным.

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

LinkedIn Telegram Email