"Vector DB — не решение для памяти агентов. Вот что работает"

Vector DB — не решение для памяти агентов. Вот что работает

Каждый второй туториал по AI-агентам заканчивается одинаково: «Подключите pgvector, сгенерируйте эмбеддинги, ищите по cosine similarity — готово, у агента есть память». На демо это выглядит магией. На проде — ломается тремя предсказуемыми способами.

Три режима отказа vector-only

**Точные идентификаторы.** Пользователь спрашивает: «Какой API-ключ я ставил на staging?» Cosine similarity не различает `sk-proj-abc123` и `sk-proj-xyz789` — для embedding-модели обе строки «API-ключ проекта». UUID, IP-адреса, хэши коммитов — всё, где важен каждый символ, проваливается.

**Редкие имена.** Внутренний проект «Байкал», контрагент из Кореи, нестандартная аббревиатура — embedding-модель не видела этих токенов при обучении. Вместо точного поиска получаем семантическую интерполяцию: «Байкал» → озеро → география.

На корпусе из 12 000 записей точность по proper nouns: 34%. Две трети — мимо.

**Хронология.** «Что изменилось за последнюю неделю?» Cosine similarity не знает, что такое «неделя». Он вытащит все релевантные документы вне зависимости от даты — прошлый месяц, прошлый год. Для памяти агента, где порядок событий критичен, это катастрофа.

Гибридный стек вместо серебряной пули

Решение не в замене векторов, а в дополнении. Три слоя:

**BM25** — классический текстовый поиск. Находит точные строки. PostgreSQL full-text search (tsvector/tsquery) живёт рядом с pgvector в той же базе. Нулевой overhead.

```sql ALTER TABLE memories ADD COLUMN tsv tsvector GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED; ```

Конфигурация `'simple'` — принципиально. Не стемит токены, идентификаторы сохраняются как есть.

**pgvector** — семантический поиск остаётся для запросов по смыслу. «Как решали проблему с таймаутами» — здесь embedding работает.

**Temporal metadata** — три поля на каждой записи: `created_at`, `updated_at`, `event_timestamp`. Временные маркеры из запроса пользователя парсятся в SQL-фильтры до этапа ранжирования.

Объединение результатов BM25 и vector — через Reciprocal Rank Fusion:

```python def rrf(results_list, k=60): scores = {} for results in results_list: for rank, doc in enumerate(results): scores[doc["id"]] = scores.get(doc["id"], 0) + 1.0 / (k + rank + 1) return sorted(scores.items(), key=lambda x: x[1], reverse=True) ```

RRF не требует нормализации скоров (BM25 и cosine — в разных шкалах). Работает только с позициями в рейтинге.

Результаты

| Метрика | Vector-only | Гибрид | |---------|------------|--------| | Recall@10 (общий) | 72% | 91% | | Recall@10 (идентификаторы) | 23% | 94% | | Recall@10 (proper nouns) | 34% | 87% | | Recall@10 (временные запросы) | 41% | 89% |

Четырёхкратный рост на идентификаторах. Это не тюнинг — это правильный инструмент для правильной задачи.

Когда переходить

Три сигнала, что vector-only не хватит:

1. Агент хранит ключи, UUID, номера тикетов, конфиги. 2. В данных много доменной терминологии вне обучающей выборки модели. 3. Пользователи спрашивают «что было на прошлой неделе / после релиза / до миграции».

Если хотя бы одно совпало — гибрид не опция, а необходимость.

Кто уже переходил с vector-only на гибрид — какие подводные камни встретили?

2