Hybrid Search for RAG in n8n: BM25 + Vector, and When to Use Each
Why pure semantic search misses the error code, and how to fuse keyword and vector in n8n.
AI-drafted, reviewed by Muhammad Qasim Hammad on June 29, 2026. See our AI disclosure.
Table of contents
- Why does pure vector search miss the obvious match?
- When does BM25 win, and when does vector win?
- What is Reciprocal Rank Fusion, and why not just add the scores?
- Which retrieval should you use per query type?
- How do you wire hybrid search in n8n (pgvector, Qdrant, Weaviate)?
- What should you set up this weekend?
Your RAG chatbot works fine until a user pastes an error code like E0427, a product SKU, or a function name, and the answer comes back about something only loosely related. The retrieval step pulled chunks that mean something similar instead of the chunk that contains the exact string. Hybrid search fixes this by running keyword search (BM25) and vector search side by side, then merging their two ranked lists into one.
Pure vector search is strong on meaning and weak on exact tokens. The fix is not a better embedding model or smaller chunks. It is adding the keyword leg back and fusing the results.
Why does pure vector search miss the obvious match?#
Vector search encodes meaning, not literal strings, so it blurs exact tokens like IDs, SKUs, error codes, and function names into their semantic neighborhood. When the token itself is the point, the embedding for E0427 sits near other error codes, and retrieval returns something similar instead of the exact line the user typed.
Embeddings are built to capture concepts. That is exactly why they struggle with identifiers: a version string, a stack trace, or a clause number has little semantic content, so the model has nothing meaningful to encode and the match drifts.
Picture a knowledge base that documents error codes E0427, E0428, and E0429, each with a different cause and fix. To an embedding model these three strings look almost identical and sit in nearly the same spot in vector space, so a query for E0427 can easily surface the chunk for E0428 first. The user gets a confident answer about the wrong error. BM25 has no such problem: it indexes E0427 as a literal token and ranks the one chunk that contains it at the top.
BM25 is the opposite tool. It is a probabilistic keyword ranking function built from term frequency, inverse document frequency, and document length normalization. It is lexical, fast, explainable, and a long-standing search baseline. The name means "Best Matching 25", the 25th iteration of the probabilistic model developed by Robertson, Sparck Jones, and colleagues in the Okapi system. Where a vector matches meaning, BM25 matches the exact token.
When does BM25 win, and when does vector win?#
BM25 wins on exact tokens: IDs, SKUs, order numbers, source code and function names, error strings, proper nouns, acronyms, and legal clause numbers. Vector wins on meaning: paraphrases, synonyms, and conceptual questions where the words differ but the intent matches. Most real corpora carry both kinds of query, which is why hybrid tends to beat either leg alone.
Think about the two questions "how do I cancel" and "terminate my subscription". They share almost no keywords, so BM25 scores them as unrelated, while a vector matches them easily. Now think about a user searching for the exact error TLS_HANDSHAKE_FAILED. The vector smears it across networking concepts; BM25 lands on the one document that contains the string.
One pgvector write-up reports that pure vector search "caps out around 62% precision" on its data, and that adding keyword search with RRF lifts it to "84%+". Treat that as one author's directional claim, not a benchmark: the source publishes no corpus, query set, or methodology behind those numbers, and retrieval precision does not transfer between datasets. The signal to take is the direction (hybrid helps on mixed queries), not the specific percentages, which you should never restate as your own result.
What is Reciprocal Rank Fusion, and why not just add the scores?#
BM25 scores are unbounded positives shaped by term frequency; cosine similarities are bounded between -1 and 1. Adding or averaging them is meaningless because the two scales do not compare. Reciprocal Rank Fusion sidesteps the problem by using only each document's rank position, not its raw score, to merge the two lists into one.
The formula is small. For a document d, the fused score sums one term per retriever:
RRF_score(d) = sum over retrievers of 1 / (k + rank_r(d))rank_r(d) is the document's position in retriever r's list (1 for the top hit), and k is a smoothing constant. The original paper (Cormack, Clarke, and Büttcher, SIGIR 2009) found k = 60 gives the best average result, and 60 is the common default. A document near the top of both lists collects two large terms and rises; one that appears in only one list still contributes. No score normalization is needed.
A worked example makes the behavior concrete. Say a chunk sits at rank 2 in the BM25 list and rank 5 in the vector list. Its fused score is 1/(60+2) + 1/(60+5) = 0.0161 + 0.0154 = 0.0315. A different chunk that is rank 1 in the vector list but absent from the BM25 list scores 1/(60+1) = 0.0164. The first chunk wins, because two solid placements beat one strong placement. That is exactly the behavior you want from hybrid: a result both retrievers half-agree on outranks a result only one of them loved. Note how small k makes the early ranks matter more, and a large k flattens the gap between rank 1 and rank 20.
Two practical notes. Pull more candidates than you keep: a common pattern is top-20 from each retriever fused down to top-10, since the extra candidates give RRF a stronger signal. And know the alternative. Weaviate also offers relativeScoreFusion, which normalizes each list to 0 to 1 and adds them, plus an alpha knob where alpha=1 is pure vector, alpha=0 is pure keyword, and 0.5 is the default.
Which retrieval should you use per query type?#
Match the retriever to the query. Use keyword-only (BM25 or Postgres tsvector) for pure exact-token lookups, vector-only for purely conceptual questions, and hybrid when a single query needs both an exact match and meaning. The flowchart above routes each case, and most production RAG ends up hybrid because real traffic is mixed.
- Keyword only when queries are almost always identifiers: order-status lookups, code search, a parts catalog.
- Vector only when queries are almost always natural language with no fixed vocabulary: a "how do I" help bot over prose.
- Hybrid for the common middle, where one question carries both a product name and a paraphrased intent.
How do you wire hybrid search in n8n (pgvector, Qdrant, Weaviate)?#
Hybrid search in n8n is a build, not a toggle. The built-in PGVector and Qdrant Vector Store nodes do vector similarity plus a metadata filter, not BM25 and not fusion. You add the keyword leg and the RRF merge yourself, through Qdrant's Query API, a Postgres SQL function, or a Weaviate hybrid query over HTTP.
| Backend | Native n8n node does | How you wire hybrid | Fusion |
|---|---|---|---|
| pgvector (Postgres node) | vector similarity, metadata filter, rerank | one SQL function over a tsvector + vector table | RRF in SQL |
| Qdrant (Vector Store node) | vector similarity, metadata filter | Query API via HTTP Request or Query Points node | RRF or DBSF |
| Weaviate (HTTP Request) | no built-in node | hybrid query with an alpha weight | rankedFusion or relativeScoreFusion |
Qdrant path. Qdrant 1.10 added a Query API that fuses search methods, with built-in RRF that Qdrant calls "the de facto standard". You store a dense vector and a sparse (BM25-style) vector, retrieve each in a separate prefetch branch, and combine them with FusionQuery(fusion=RRF). From n8n, call it through the HTTP Request node or the official Qdrant Query Points node. The official n8n template "Evaluate hybrid search for legal question-answering" wires exactly this: BM25 plus mxbai-embed-large dense retrieval, fused with RRF.
pgvector path. Keep one chunks table with both a tsvector full-text column and a vector(1536) column, each indexed. Write a single SQL function that runs the vector kNN and the full-text ranking, then fuses them with RRF, and call it from the Postgres node. Native tsvector gives "BM25-flavored" ranking out of the box; for true BM25 inside Postgres, the ParadeDB (pg_search) and pg_textsearch extensions add it.
Weaviate path. Weaviate is closest to built-in hybrid: one query carries the alpha balance and a fusion type, called from n8n over the HTTP Request node.
Hybrid is not free, and it is worth saying so. You now build and store a second representation of every chunk (a sparse vector or a tsvector column), and every query runs two retrievals before the fusion step. That is a modest increase in index size and a second lookup per request, not a new model bill, since BM25 and tsvector ranking are cheap CPU operations with no token cost. The payoff is the exact-token recall a single vector index cannot give you, so spend it where the failing queries prove you need it, not on every collection by default.
If you are still choosing a store, the pgvector vs Chroma vs Qdrant comparison covers the trade-offs, and why your RAG chatbot gives wrong answers walks the upstream retrieval failures that hybrid alone will not fix.
What should you set up this weekend?#
Start from the failures, not the architecture. Pull the last week of queries your RAG got wrong and sort out the ones that contain an exact token: an ID, a code, a name, a clause number. That set is your evidence that the keyword leg is missing, and it becomes your test bench.
Then add the second retriever. On Qdrant, add a sparse vector and a Query API call with two prefetch branches fused by RRF; on Postgres, add a tsvector column and one fusion function. Retrieve 20 per leg, fuse to 10, and rerun your failing queries. Tune k (and alpha on Weaviate) against your own corpus, never against someone else's published percentage.
Frequently asked questions
Does n8n have a built-in hybrid search node?
When does BM25 beat vector search?
Why can't I just add the BM25 score and the cosine score?
What is the k in RRF, and what should I set it to?
How many results should each retriever return before fusion?
Sources
Primary references and vendor documentation used while drafting and reviewing this article.
- Okapi BM25 (definition, TF/IDF, origin)
- Reciprocal rank fusion (Cormack et al. SIGIR 2009)
- Qdrant hybrid search (Query API, RRF, DBSF)
- Calling Qdrant from n8n (HTTP Request / Query Points)
- n8n template: hybrid search for legal QA (BM25 + mxbai + RRF)
- Weaviate hybrid search (alpha, rankedFusion vs relativeScoreFusion)
- n8n PGVector Vector Store node (modes, no native hybrid)
- Why embeddings miss exact identifiers (the precision layer)
Written by
Muhammad Qasim Hammad is an AI agent and automation expert and the founder of Cart Gaze LLC (cartgaze.com). He builds product for the love of it: when an idea lands, a working prototype is usually running within hours, built with the same AI agents and automations he sells. He puts his own output at roughly 20× what it was before agents, and the Agentic OS behind this site is the working proof, documented in public with the tools he actually ran and what they really cost.
AI & Automation Services
Want a pipeline like this running in your business?
I'm Qasim — I design and ship AI agents and n8n automations for solo operators and small teams. Tell me what's eating your team's week, and I'll scope a fix.
Related reading
Reranking for RAG: When a Reranker Is Worth It (and When It Isn't)
RAG reranking re-sorts the chunks you already retrieved so the best one rises to the top, but it cannot recover a chunk you never retrieved. Here is what a cross-encoder reranker actually does, when it is worth the latency and cost, the real 2026 options (Cohere, Voyage, BGE),
Contextual Retrieval: Fix RAG Chunks That Lose Context
Plain RAG embeds chunks that have lost their document context, so 'revenue grew 3%' matches nothing useful. Contextual retrieval writes a short per-chunk context and prepends it before embedding and BM25. Here is the method, the build, and the honest ingest trade-off.
RAG Chunking Strategies: Fixed vs Recursive vs Semantic (and What to Pick)
RAG chunking strategies decide whether the right passage is whole, findable, and small enough to rank before your model sees it. Here is how fixed, recursive, and semantic splitting differ, what chunk size and overlap to start with, why the public benchmarks disagree, and a


