Перейти к содержанию

Глава 7. Извлечение контекста, уплотнение и фоновые обновления

1. Память бесполезна, если ты не умеешь возвращать из нее нужное

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

Именно здесь многие системы начинают деградировать:

  • в подсказку летит слишком много нерелевантного контекста;
  • извлечение возвращает похожее, но не полезное;
  • сводки растут, но не становятся понятнее;
  • каждая новая итерация делает контекст только тяжелее.

То есть проблема уже не в том, что память есть. Проблема в том, что память стала слишком шумной и дорогой для чтения.

2. Извлечение — это не поиск “всего похожего”, а отбор того, что помогает решить текущую задачу

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

Для эксплуатационных систем полезнее думать так:

  • извлечение не обязано возвращать много;
  • извлечение обязано возвращать объяснимо;
  • извлечение должно уважать арендатора, источник и границы доверия;
  • извлечение должно подчиняться бюджету размера контекста.

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

  • изоляцию арендаторов;
  • класс памяти;
  • свежесть;
  • уверенность;
  • происхождение;
  • фильтры политики.

2.1. Семантический разрыв между пользователем и слоем знаний реален

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

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

Практически это означает, что запрос извлечения иногда полезно дополнительно обрабатывать:

  • нормализовать названия сущностей и внутренних статусов;
  • переписывать запрос в более "документный" стиль;
  • добавлять управляемое расширение запроса;
  • в отдельных случаях использовать HyDE, когда система сначала строит гипотетический ответ в стиле документа, а уже потом ищет по нему.

Но здесь важна одна дисциплина: такой промежуточный помощник запроса не должен превращаться в новый "факт". HyDE или переписывание запроса полезны как инструмент извлечения, а не как замена обоснованного ответа.

2.2. Обычно стоит начинать с RAG, а не с обучения

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

Причина простая:

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

При этом полезно различать две разные задачи:

  • продолженное предобучение в первую очередь помогает адаптировать распределение знаний;
  • SFT в первую очередь помогает адаптировать поведение, стиль и шаблон решений.

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

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

Сквозной кейс: что доставать обратно

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

3. Хорошая подсказка любит не полноту, а плотность сигнала

Очень соблазнительно думать, что “чем больше контекста, тем умнее агент”. На практике часто наоборот: чем больше мусора ты кладешь в подсказку, тем слабее модель держит приоритеты.

Поэтому извлечение должно отвечать не на вопрос “что мы можем достать?”, а на вопрос “что сейчас повышает шанс на правильное решение?”.

Полезное практическое правило:

  • лучше 3 очень релевантные записи, чем 20 условно похожих;
  • лучше маленькая сводка с источником, чем длинный сырой документ;
  • лучше отдельная профильная подсказка, чем целая история предпочтений;
  • лучше пустое извлечение, чем извлечение без доверия и объяснимости.

4. Сжатие контекста — это не косметика, а способ удержать систему в рабочем состоянии

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

Сжатие контекста может означать разные вещи:

  • сжать несколько записей в сводку;
  • удалить устаревшие рабочие заметки;
  • объединить дубликаты;
  • заменить большой фрагмент на нормализованную запись плюс ссылку на источник;
  • понизить приоритет старых записей вместо вечного хранения “на первом плане”.

Извлечение и сжатие контекста лучше мыслить как один цикл обслуживания памяти

flowchart TD
    A["Новый запуск"] --> B["Запросить память"]
    B --> C["Применить фильтры и ранжирование"]
    C --> D["Собрать контекст подсказки"]
    D --> E["Модель + инструменты"]
    E --> F["Создать кандидаты в память"]
    F --> G["Фоновое сжатие и разбор"]
    G --> H["Нормализованное хранилище памяти"]
    H --> B

5. Не все обновления памяти должны происходить в горячем пути

Это один из самых полезных архитектурных сдвигов для агентных систем. В начале почти все пытаются сделать память “сразу готовой”: агент что-то увидел, сразу переписал сводки, сразу обновил профиль, сразу сохранил знания.

Но это почти всегда слишком дорого и слишком рискованно.

Что обычно разумно оставить в горячем пути:

  • минимальное состояние сессии;
  • короткие рабочие заметки;
  • безопасные временные записи с понятным TTL;
  • обновление, без которого текущий рабочий процесс реально ломается.

Что обычно лучше уводить в фон:

  • сжатие длинных сессий;
  • пересборку сводок;
  • нормализацию фактов;
  • дедупликацию;
  • разбор кандидатов в память перед устойчивой записью.

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

6. Хорошая система разделяет запрос извлечения и задачи обслуживания

В зрелой архитектуре почти всегда есть два разных контура:

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

Это важно не только для производительности, но и для качества решений. Когда одна и та же цепочка одновременно:

  • выполняет задачу;
  • пересобирает сводки;
  • пишет профильную память;
  • чистит дубликаты;
  • обновляет метаданные ранжирования,

она очень быстро становится хрупкой и плохо объяснимой.

6.1. Передовой край памяти идет в сторону адаптивного формирования, но эксплуатация пока требует дисциплины

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

Это перспективное направление, потому что оно намекает на более умную систему:

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

Но именно здесь нельзя перепрыгивать через эксплуатационную дисциплину.

Пока у команды нет устойчивых:

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

адаптивное формирование памяти лучше воспринимать как направление исследований, а не как эксплуатационный паттерн по умолчанию.

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

7. Пример политики для извлечения и фоновых обновлений

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

retrieval:
  max_records: 5
  max_tokens: 1800
  allowed_classes:
    - short_term
    - long_term
    - profile
  require_tenant_match: true
  min_confidence: 0.75
  deny_sources:
    - raw_external_html
    - unreviewed_summary

compaction:
  run_mode: background_only
  summary_max_tokens: 400
  deduplicate: true
  merge_similar_records: true
  drop_expired_short_term: true

Когда такие правила явны, команда уже не спорит о памяти на уровне ощущений. Она спорит о конкретных ограничениях и компромиссах.

8. Простой кодовый пример ранжирования перед сборкой подсказки

Ниже не “умный” движок извлечения, а специально понятный пример. Он показывает, что ранжирование полезно строить не только по похожести, но и по доверию, свежести и важности.

from dataclasses import dataclass


@dataclass
class RetrievedRecord:
    text: str
    similarity: float
    confidence: float
    recency_weight: float
    trusted: bool


def score(record: RetrievedRecord) -> float:
    trust_bonus = 0.15 if record.trusted else -0.2
    return (
        record.similarity * 0.5
        + record.confidence * 0.25
        + record.recency_weight * 0.1
        + trust_bonus
    )


def select_for_prompt(records: list[RetrievedRecord], limit: int = 3) -> list[RetrievedRecord]:
    ranked = sorted(records, key=score, reverse=True)
    return ranked[:limit]

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

9. Сводки должны помогать читать, а не скрывать происхождение данных

Очень часто команды используют сводки как способ “впихнуть больше памяти в меньше токенов”. Это нормально, но есть одна ловушка: сводка не должна превращаться в новую анонимную истину.

Хорошая сводка:

  • короче исходных записей;
  • сохраняет происхождение;
  • не смешивает арендаторов;
  • не теряет критические ограничения;
  • помечена как производный артефакт, а не сырой факт.

Плохая сводка:

  • звучит уверенно, но неясно, откуда она взялась;
  • объединяет конфликтующие факты;
  • теряет дату и владельца данных;
  • подсовывается модели как доверенная инструкция.

10. Частые ошибки

Обычно проблемы повторяются:

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

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

11. Что сделать сразу

Сначала пройди по короткому списку и отдельно отметь все ответы «нет»:

  • Есть ли лимит по количеству записей и бюджету токенов?
  • Учитывает ли ранжирование не только похожесть, но и уверенность, свежесть и доверие?
  • Разделены ли путь чтения и путь обслуживания?
  • Есть ли сжатие контекста как регулярный процесс, а не ручная уборка?
  • Можно ли у сводки увидеть происхождение?
  • Есть ли защита от извлечения через чужого арендатора или неподходящий класс?

Если на несколько вопросов подряд ответ “нет”, значит память у тебя уже есть, а вот дисциплины памяти пока нет.

12. Что делать дальше

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