Почему мы отказались от vector-only retrieval для памяти агентов — и что используем вместо
Vector-only retrieval красиво выглядит на демо, но ломается на реальных данных агентов. Разбираем три режима отказа и архитектуру гибридного стека, который их решает.
Иллюзия простоты
Стандартный рецепт памяти AI-агента выглядит элегантно: берём pgvector, генерируем эмбеддинги через OpenAI, ищем по cosine similarity. Три строчки кода — и агент «помнит». На демо всё работает. На продакшене — нет.
Мы прошли этот путь целиком. Полгода vector-only в проде, тысячи запросов в день, нарастающее разочарование. Три конкретных failure-режима заставили нас пересмотреть архитектуру с нуля.
Failure #1: точные идентификаторы
Пользователь спрашивает: «Какой API-ключ я использовал для проекта X?» Агент уверенно возвращает... не тот ключ.
Проблема фундаментальна. Cosine similarity работает с семантической близостью. Строка sk-proj-abc123 семантически неотличима от sk-proj-xyz789 — обе «API-ключи». Embedding-модель сжимает токены в плотный вектор, где точное значение идентификатора растворяется.
То же самое с UUID, номерами заказов, хэшами коммитов, IP-адресами. Любая строка, где важен каждый символ, а не общий смысл — blind spot для vector search.
Failure #2: редкие proper nouns
Второй класс провалов — имена собственные, которых нет в обучающей выборке embedding-модели. Названия внутренних проектов, нестандартные аббревиатуры, имена контрагентов из неанглоязычных рынков.
Embedding-модель не знает, что «Проект Байкал» — это ваш внутренний ML-пайплайн. Она видит «озеро в Сибири» и услужливо подтягивает документы про географию. Proper nouns вне распределения обучающих данных превращаются в шум.
Замеры на корпусе из 12 000 записей агентской памяти: точность retrieval по proper nouns падала до 34% (против 78% на обычных семантических запросах). Треть времени агент доставал не те документы.
Failure #3: отсутствие хронологии
Третий — и самый коварный — провал. Пользователь спрашивает: «Что изменилось в конфигурации за последнюю неделю?» Vector search не знает, что такое «неделя». Он находит все семантически близкие документы про конфигурации — из прошлого месяца, прошлого года, из другого проекта.
Cosine similarity — это расстояние в семантическом пространстве. Времени в этом пространстве нет. Можно добавить дату в текст чанка, но embedding-модель обрабатывает её как ещё один набор токенов, а не как метаданные для фильтрации.
Для памяти агента хронология критична. Агент должен знать: «Сначала было A, потом B изменило ситуацию, сейчас актуально C». Vector-only retrieval возвращает A, B и C вперемешку и предлагает LLM разобраться. LLM не разбирается — галлюцинирует.
Архитектура гибридного стека
Решение — не отказаться от векторов, а дополнить их. Три компонента, каждый закрывает свой failure-режим.
BM25 для точных совпадений
BM25 — классический алгоритм текстового поиска. Работает с точными токенами, не с семантикой. Когда пользователь ищет конкретный идентификатор, BM25 находит именно эту строку.
Реализация: PostgreSQL full-text search (tsvector/tsquery) рядом с pgvector в той же базе. Нулевой overhead по инфраструктуре. Ключевой момент: конфигурация 'simple' вместо языковой — не стемит токены, поэтому идентификаторы, IP-адреса и proper nouns сохраняются как есть.
pgvector для семантики
Семантический поиск остаётся. Он отлично работает для запросов вроде «как мы решали проблему с таймаутами» — где важен смысл, а не точное слово. Запросы к BM25 и vector выполняются параллельно, результаты объединяются через Reciprocal Rank Fusion.
Temporal metadata для хронологии
Каждая запись памяти получает три поля: created_at, updated_at, event_timestamp. Третье поле — время события, о котором говорит запись (не время вставки в базу). Для запросов с временным контекстом парсим временные маркеры из запроса и превращаем их в SQL-фильтры до ранжирования.
Reciprocal Rank Fusion: как объединять результаты
Два ранжированных списка (BM25 и vector) нужно объединить в один. Reciprocal Rank Fusion (RRF) — стандартный подход: для каждого документа суммируем 1/(k + rank) по всем спискам, где k=60. RRF не требует нормализации скоров между BM25 и cosine similarity — они в разных шкалах, работает только с позициями в рейтинге.
Когда переключаться на гибрид
Не каждому проекту нужен гибридный retrieval с первого дня. Три сигнала, что пора:
- Идентификаторы в памяти. Если агент хранит API-ключи, UUID, номера тикетов, IP-адреса — vector-only будет ошибаться. Это не вопрос тюнинга, это фундаментальное ограничение.
- Доменная терминология. Внутренние кодовые имена проектов, специфичные аббревиатуры, имена на нелатинских алфавитах — всё, что embedding-модель не видела при обучении.
- Временные запросы. «Что было на прошлой неделе?», «Покажи изменения после релиза» — если агент должен отвечать на такие вопросы, без temporal metadata не обойтись.
Результаты
На корпусе из 12 000 записей после перехода на гибридный стек: общий Recall@10 вырос с 72% до 91%. По идентификаторам — с 23% до 94% (четырёхкратный рост). По proper nouns — с 34% до 87%. По временным запросам — с 41% до 89%. BM25 делает ровно то, для чего создан: находит точные совпадения строк.
Что это значит для архитектуры агентов
Vector DB — мощный инструмент, но не серебряная пуля для памяти агентов. Память — это не «найди похожее». Это «найди точно то, о чём спрашивают, в правильном хронологическом контексте».
Гибридный стек добавляет сложности — два индекса вместо одного, fusion-логика, парсинг временных выражений. Но эта сложность оправдана, когда агент работает с реальными данными: ключами, конфигами, историей событий.
Если ваш агент оперирует только абстрактными знаниями из документации — vector-only может быть достаточно. Если он работает с конкретными фактами, идентификаторами и хронологией — гибрид не опция, а необходимость.