Skip to content

docs(example): hybrid retrieval for RAG chatbots (drop-in for pgvector + BM25) - #62

Merged
xerj-org merged 1 commit into
mainfrom
docs/rag-hybrid-retrieval
Jul 28, 2026
Merged

docs(example): hybrid retrieval for RAG chatbots (drop-in for pgvector + BM25)#62
xerj-org merged 1 commit into
mainfrom
docs/rag-hybrid-retrieval

Conversation

@xerj-team

Copy link
Copy Markdown
Collaborator

What

A tested-live example for the common RAG-chatbot retrieval shape — documents → chunks → embeddings → pure cosine (often Supabase/pgvector) — showing where dense-only retrieval fails and how XERJ fixes the retrieval half by adding BM25 and fusing with vectors (RRF).

The result (real, 18 doc chunks, EmbeddingGemma)

Rank of the correct chunk (lower is better):

  exact-term query            cosine   BM25  hybrid
  E4304                           13      1       3
  createCheckoutSession           14      1       1
  WEBHOOK_SIGNING_SECRET           4      1       1
  v2.11                            1      1       1

Pure cosine buries the chunk that defines E4304 at #13 — outside any RAG top-k window, so the LLM never sees it; asked "E4304" it returns E5012 — gateway timeout (semantically adjacent, factually wrong). BM25 nails exact identifiers; hybrid (RRF) keeps that exactness and vector concept-matching. And it decides the grounded answer: wrong chunk → refusal/wrong; right chunk → correct.

Candid scope

XERJ owns the retrieval half (cosine + BM25 + hybrid + keyword filters). Chunking, embedding-model choice, and LLM/prompt wiring are the app's job and XERJ doesn't touch them. Drop-in: keep your chunker/embedder/LLM, swap the pgvector cosine query for an XERJ hybrid (fusion: rrf) query — same vectors.

Contents

  • 01_corpus.py, 02_embed_index.py, 03_retrieval_compare.py — runnable.
  • README.md — the demo, the RRF query shape, the pgvector→XERJ migration, and honest limitations (equal-weight RRF put E4304 at Fix/autoindex text family split #3; tune weights / add an identifier router).

Docs/example only — no engine code.

🤖 Generated with Claude Code

…r + BM25)

Tested-live example for the common RAG shape (docs -> chunks -> embeddings ->
pure cosine, often Supabase/pgvector) showing where dense-only retrieval buries
exact terms and how XERJ's BM25+vector RRF hybrid fixes the retrieval half.

Real result over 18 doc chunks (rank of the correct chunk, lower better):
  E4304:                cosine #13  BM25 #1  hybrid #3
  createCheckoutSession cosine #14  BM25 #1  hybrid #1
  WEBHOOK_SIGNING_SECRET cosine #4  BM25 #1  hybrid #1
At #13/#14 the correct chunk never enters a RAG top-k window -> the LLM never
sees it; asked "E4304" dense-only returns "E5012 gateway timeout" (adjacent but
wrong). BM25 nails exact identifiers; hybrid keeps that AND concept-matching.
Demonstrates the RAG payoff: retrieval decides the grounded answer (wrong chunk
-> refusal/wrong; right chunk -> correct).

Candid scope: XERJ owns the RETRIEVAL half (cosine+BM25+hybrid+filters); chunking,
embedding-model choice, and LLM/prompt wiring are the app's job and XERJ doesn't
touch them. Drop-in migration: same chunks + embeddings, swap the pgvector cosine
query for an XERJ hybrid (rrf) query. Runnable scripts + honest limitations
(equal-weight RRF put E4304 at #3, tune weights / add an identifier router).

Co-Authored-By: Xerj Squad A <noreply@xerj.org>
@xerj-org
xerj-org merged commit adfbb14 into main Jul 28, 2026
4 checks passed
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