Skip to content

Hybrid retrieval (BM25 + vector RRF fusion) for document-RAG#1030

Open
sunnyadn wants to merge 1 commit into
trustgraph-ai:release/v2.7from
sunnyadn:feat/hybrid-retrieval
Open

Hybrid retrieval (BM25 + vector RRF fusion) for document-RAG#1030
sunnyadn wants to merge 1 commit into
trustgraph-ai:release/v2.7from
sunnyadn:feat/hybrid-retrieval

Conversation

@sunnyadn

@sunnyadn sunnyadn commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Closes #875.

Adds a sparse keyword retrieval path beside the existing vector path in document-RAG, fused by weighted Reciprocal Rank Fusion, behind --retrieval-mode (vector | keyword | hybrid, default vector).

Targets release/v2.7 per the issue's "latest release branch" note — happy to retarget to 2.6 if you'd like it there too.

Design

  • Pluggable keyword-index service: new KeywordIndexService base + KeywordIndexClientSpec, request/response shaped like the doc-embeddings service. Matches share ChunkMatch, so both paths key on chunk_id and the existing librarian text-hydration in Query.get_docs() is reused unchanged.
  • First backend: SQLite FTS5 (storage/kw_index/fts5), consuming Chunk messages off the ingestion stream and answering BM25 queries from one process — ingest and query aren't split like the vector stores because the FTS5 index is a single local file; a server-backed backend (Elasticsearch/OpenSearch) can be split later behind the same schema. One FTS table per (workspace, collection), dropped on collection deletion; re-ingested chunks replace their previous row.
  • Query sanitization: raw text is not valid FTS5 syntax (7.3.2 is a syntax error; the - in AURA-7 parses as a column filter), so each token is quoted as a phrase and OR-joined. A quoted phrase of sub-tokens also makes dotted clause numbers exact-match — "7.3.2" finds clause 7.3.2 without matching 7.3.1 — with the default tokenizer, no trigram index.
  • Back-compat: the keyword-index client spec is only registered when the sparse path is enabled, so flow definitions without keyword-index queues are untouched and retrieval_mode=vector leaves the retrieval path unchanged. In hybrid mode a keyword-path failure degrades to vector-only with a warning.

Testing

  • 26 new unit tests (FTS5 exact-term/isolation/deletion/hostile-query, RRF fusion, three-mode dispatch, degradation); full unit suite passes.
  • Live E2E on a 2.7 docker stack: ingestion populated the FTS index from chunk-load; keyword and hybrid modes answered clause-number and error-code questions exactly; vector mode makes no keyword-index connections; killing the kw-index service in hybrid mode logged the degradation warning and answered from the vector path.

Companion deployment wiring: trustgraph-ai/trustgraph-templates#297 (keyword-index-fts5 component).

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Contributor License Agreement ✅

All contributors have signed the CLA. Thank you!

…rustgraph-ai#875)

Adds a sparse keyword retrieval path beside the existing vector path in
document-RAG, fused by weighted Reciprocal Rank Fusion on chunk_id, behind
--retrieval-mode (vector | keyword | hybrid, default vector).

The keyword index is a new pluggable service (KeywordIndexService /
KeywordIndexClientSpec); the first backend is SQLite FTS5, consuming Chunk
messages off the ingestion stream and answering BM25 queries from one
process, since the index is a single local file. Query text is sanitized
into per-term quoted phrases (raw text is not valid FTS5 syntax), which
also makes dotted clause numbers and error codes exact-match without a
trigram index. Indexes are scoped per (workspace, collection) and dropped
on collection deletion.

The keyword-index client spec is only registered when the sparse path is
enabled, so existing flow definitions without keyword-index queues are
untouched; with retrieval_mode=vector the retrieval path is unchanged. In
hybrid mode a keyword-path failure degrades to vector-only.
@sunnyadn sunnyadn force-pushed the feat/hybrid-retrieval branch from 43cc7fd to a93e91d Compare July 6, 2026 16:26
@cybermaggedon cybermaggedon self-requested a review July 6, 2026 16:47
@cybermaggedon cybermaggedon self-assigned this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants