0.22.0

Итерация 18 плана аудита: память — hybrid search (BM25 + vector) и RAG-параметры.

Added

  • Миграция 024_hybrid_search (sqlite + pg, up/down):
  • SQLite: FTS5 виртуальные таблицы memory_facts_fts и memory_chunks_fts (content-sync mode, не дублирует данные). Триггеры trg_facts_fts_ins/del/upd и trg_chunks_fts_ins/del/upd для синхронизации с основными таблицами. Начальное заполнение через INSERT INTO ... VALUES ('rebuild').
  • PostgreSQL: content_tsvector tsvector GENERATED ALWAYS AS (to_tsvector('simple', content)) STORED в memory_facts и memory_chunks. GIN-индексы idx_memory_facts_tsvector и idx_memory_chunks_tsvector.
  • Store-методы SearchFactsFTS(ctx, agentID, userID, query, topK) и SearchChunksFTS(ctx, agentID, userID, query, topK) — полнотекстовый поиск. SQLite: FTS5 MATCH + bm25() + JOIN для фильтрации agentID/userID. PostgreSQL: tsvector @@ plainto_tsquery('simple', ...) + ts_rank(). Реализованы для SQLite, Postgres, DualStore.
  • RRFMerge(lists, topK, k) в internal/store/vectors.go — Reciprocal Rank Fusion: объединяет несколько отсортированных списков в один с корректным скорингом 1/(k+rank). k=60 (стандарт). Перекрывающиеся документы получают повышенный score.
  • Hybrid search в ContextBuilder.buildMemoryContext: vector top-30 + FTS top-30 → RRFMerge → top-30 → MMR (15/10) → reranker → dynamic top-K.
  • Обновлённые параметры RAG:
  • Retrieval: top-K 30 (было 30), threshold 0.45 (было 0.6).
  • Final top-K: динамический по бюджету — facts 3-7, chunks 1-4 (было фиксировано 5/3). Функция dynamicRAGTopK(ragBudget) масштабирует пропорционально budget.RAG.
  • TouchFacts(ctx, factIDs) — батчевый UPDATE вместо N индивидуальных TouchFact. Вызывается один раз в конце buildMemoryContext. SQLite: transaction + prepared statement. PostgreSQL: pgx.Batch.
  • POST /memory/search handler: использует hybrid search (vector + FTS + RRF) + reranker (передан в NewMemoryHandler). Порог по умолчанию 0.45 (было 0.7).
  • 21 юнит-тест:
  • internal/store/rrf_test.go (6 тестов): BasicMerge, EmptyLists, SingleList, TopK, OverlappingBoostsScore, DefaultK.
  • internal/store/fts_test.go (8 тестов): SearchFactsFTS_Basic, UserIsolation, EmptyQuery, SearchChunksFTS_Basic, TouchFacts_Batch, TouchFacts_Empty, HybridSearch_RRF, Migrations_UpTo024.
  • internal/agent/context_test.go (1 тест): DynamicRAGTopK (6 кейсов по бюджету).
  • Все тесты проходят с -race.

Changed

  • NewMemoryHandler теперь принимает reranker.RerankerProvider вторым аргументом (после store.Store).
  • buildMemoryContext вызывает TouchFacts батчем вместо поочерёдного TouchFact.