Что такое Context Engineering в рамках Harness и почему это отдельный слой?

Краткий тезис

Context Engineering — это третий слой в эталонной архитектуре агентных систем (Harness), отвечающий за управление контекстным окном агента. Он решает проблему жёстких лимитов токенов, оптимизируя, сжимая и структурируя контент. Выделение в отдельный слой необходимо, потому что контекст — это «рабочая память» агента, и её размер напрямую влияет на качество, стоимость и скорость работы.


1. Место Context Engineering в архитектуре Harness

Harness — это не конкретный фреймворк, а обобщённая референсная архитектура для построения AI-агентов (LLM + инструменты + память). Обычно выделяют 4–5 слоёв:

СлойНазваниеЗадача
1OrchestrationУправление потоком вызовов, диспетчеризация
2Planning & ReasoningРазбиение задач, выбор инструментов
3Context EngineeringУправление контекстным окном
4ExecutionЗапуск инструментов, вызов LLM
5Memory & StorageДолговременная память, логи

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


2. Почему нужен отдельный слой: проблема контекстного окна

Контекстное окно (context window) — максимальное количество токенов, которое LLM может обработать за один вызов. Оно жёстко ограничено (например, 8K, 32K, 128K токенов). Если контент превышает лимит, агент не сможет корректно сгенерировать ответ.

Проблемы, которые решает Context Engineering:

  • Потеря информации — если просто обрезать начало, агент забудет суть задачи.
  • Высокая стоимость — каждый лишний токен увеличивает затраты на inference.
  • Замедление — длинный контекст увеличивает latency (память внимания квадратична).
  • Нестабильность кэша — частые изменения контекста разрушают prefix caching (кэширование KV-кэша для повторяющихся префиксов), замедляя генерацию.

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


3. Token Budgets — бюджетирование токенов

Token Budget — это заранее выделенная квота токенов на каждую часть контекста. Пример распределения для агента с инструментами:

КомпонентБюджет (токенов)Комментарий
System prompt500Роль агента, правила
History (предыдущие шаги)3000Последние N сообщений
Tool definitions1000Описания доступных инструментов
Current query + results2000Текущий запрос пользователя и результаты выполнения
Итого6500Должно укладываться в лимит (≤8000)

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


4. Packing — упаковка сообщений

Packing — это техника объединения нескольких логических сообщений в одно, чтобы сократить количество токенов и улучшить восприятие LLM.

Примеры:

  • Объединение последовательных инструментных вызовов: вместо 10 отдельных сообщений «ответ инструмента X» упаковываем их в одно с метками:
    [Tool results: step1=..., step2=..., ...]
    
  • Структурирование в JSON/YAML: история превращается в массив объектов, что занимает меньше токенов, чем маркдаун.
# Псевдокод packing для истории
def pack_history(messages: list[dict]) -> str:
    packed = []
    for msg in messages[-10:]:  # берём последние 10
        packed.append(f"[{msg['role']}]: {msg['content'][:200]}")
    return "\n".join(packed)

Packing часто совмещают с truncation — отбрасыванием самых старых или наименее релевантных сообщений.


5. Compression — сжатие контекста

Compression (сжатие) — это уменьшение количества токенов за счёт суммаризации или удаления избыточной информации.

Основные методы:

  • Суммаризация истории: LLM или отдельная модель кратко пересказывает предыдущие шаги. Экономия 50–80% токенов.
  • Извлечение ключевых фактов: вместо всей переписки хранятся только факты (например, «пользователь живёт в Москве»).
  • Удаление дублирующихся результатов инструментов: если два вызова вернули одно и то же, оставляем только первое.
  • Selective compression: убираем части контекста с низкой важностью (например, «шумные» результаты поиска).

Пример сжатия истории

Было (1200 токенов):
Пользователь: найти отели в Париже
Агент: ищу... нашёл 3 отеля
Пользователь: покажи только с завтраком
Агент: вот список...
...

Стало (200 токенов):
[Summary] Пользователь ищет отели в Париже с завтраком. Найдено 3 варианта.

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


6. Cache Stability — стабильность кэша

Cache Stability (стабильность кэша) — это стратегия сохранения неизменной части контекста (обычно system prompt и tool definitions) между вызовами, чтобы максимально эффективно использовать prefix caching.

Prefix caching — механизм, при котором LLM-инференс движок кэширует KV-вычисления для начальных токенов. Если префикс не меняется, второй вызов обрабатывается быстрее.

Как Context Engineering поддерживает стабильность кэша:

  • Фиксированный порядок: system prompttool definitions → история → запрос. Изменения только в конце.
  • Динамическое выделение budgets: подстраиваем бюджеты так, чтобы общая длина не превышала лимит, но начало оставалось неизменным.
  • Padding: добавление пустых токенов до фиксированной длины, чтобы префикс всегда занимал одинаковое количество токенов (только для некоторых бекендов).

Пример: если system prompt занимает 500 токенов, а tool definitions 1000, то можно зафиксировать первые 1500 токенов и никогда их не менять. Это ускоряет генерацию на 30–50%.


7. Взаимодействие с другими слоями

Context Engineering — не изолированный слой. Он тесно связан с:

  • Planning (слой 2) — после того, как план сгенерирован, Context Engineering решает, как упаковать все шаги в окно.
  • Execution (слой 4) — результаты инструментов сразу передаются в Context Engineering для сжатия и форматирования.
  • Memory (слой 5) — долговременная память может использоваться как источник для восстановления истории после полной очистки контекста.

Типичный цикл

  1. Планировщик формирует следующий шаг.
  2. Context Engineering готовит промпт: сжимает историю, упаковывает результаты, проверяет бюджет.
  3. Выполняется вызов LLM.
  4. Новый ответ и результаты инструментов поступают в Context Engineering для обновления контекста.

8. Пример реализации (концептуальный код)

class ContextEngine:
    def __init__(self, model: str, max_tokens: int = 8000):
        self.model = model
        self.max_tokens = max_tokens
        self.history = []
        
    def prepare_prompt(self, 
                       system: str, 
                       tools: list, 
                       query: str,
                       tool_results: dict) -> str:
        # Бюджеты
        budget = {
            'system': 500,
            'tools': len(tools) * 100,  # 100 токенов на описание
            'history': 3000,
            'current': 2000
        }
        # Packing + Compression
        packed_history = self._compress(self.history, budget['history'])
        packed_tools = [f"Tool: {t['name']} - {t['description']}" for t in tools]
        
        # Сборка промпта
        prompt = f"{system}\n\nTools:\n{chr(10).join(packed_tools)}\n\nHistory:\n{packed_history}\n\nUser: {query}\nResults: {tool_results}"
        
        # Проверка лимита
        token_count = self._count_tokens(prompt)
        if token_count > self.max_tokens:
            # Экстренное сжатие
            packed_history = self._summarize_history()
            prompt = ... # пересобрать
        return prompt
    
    def _compress(self, history, limit):
        # логика сжатия
        pass

Пет-проект для закрепления

Задача: Реализовать минимальный Context Engine для агента, который ищет информацию в интернете и отвечает пользователю.

Инструменты: Python, LangChain (или просто библиотека для подсчёта токенов tiktoken), заглушка для LLM (эмулировать вызов).

Шаги:

  1. Создайте класс ContextEngine с методами add_message, set_budget, get_prompt.
  2. Реализуйте простой token budget: выделите 500 токенов на system, 2000 на историю, 1500 на результаты поиска.
  3. Реализуйте truncation (отбрасывание старых сообщений, начиная с самых старых, пока не влезет).
  4. Добавьте summarization — при превышении лимита вызовите LLM (заглушку), чтобы сжать историю в 1–2 предложения.
  5. Добавьте packing: объединяйте результаты нескольких поисковых вызовов в один блок.
  6. Протестируйте: подайте длинную историю (10+ сообщений) и проверьте, что итоговый промпт не превышает заданный лимит.

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


Связь с другими вопросами

ВопросТема
735Что такое Agentic RAG и как он отличается от классического RAG?
736Как устроен цикл агента (Agent Loop)?
738Какие виды инструментов могут использовать агенты в RAG?
739Как планирование (Planning) влияет на архитектуру агента?
741Как вы тестируете и отлаживаете агентные системы?

Навигация