Documentsragflow
Hybrid Search and Retrieval
Hybrid Search and Retrieval
Type
Topic
Status
Published
Created
Jul 8, 2026
Updated
Jul 8, 2026

Hybrid Search and Retrieval#

RAGFlow's retrieval pipeline combines BM25 full-text scoring with KNN vector scoring into a single ranked result set. The central orchestrator is Dealer in rag/nlp/search.py. It holds a reference to the active DocStoreConnection and exposes two key entry points: search() (low-level, one backend call) and retrieval() (full pipeline with reranking and pagination).


Mode Selection: Hybrid vs. Text-Only vs. Vector-Only#

Search mode is selected at query time based on two conditions :

ConditionMode
No emb_mdl providedText-only (BM25) — only MatchTextExpr is sent
emb_mdl presentHybrid[MatchTextExpr, MatchDenseExpr, FusionExpr("weighted_sum")] sent together

The emb_mdl argument is passed by callers when a knowledge base has an embedding model configured; omitting it falls back to keyword search.

For the OpenSearch backend specifically, hybrid mode also requires the normalization pipeline to be active. _init_hybrid_search() runs at startup and provisions a ragflow_hybrid_pipeline on OpenSearch. If the server version is below 2.10 or pipeline creation fails (e.g. insufficient permissions), hybrid_search_enabled is set to False and the backend silently degrades to vector-only KNN queries .


Query Construction: Match Expressions#

The three expression types used in hybrid search are defined in common/doc_store/doc_store_base.py:

  • MatchTextExpr : fields, query string, topn, and minimum_should_match option.
  • MatchDenseExpr : vector column name (e.g. q_1024_vec), cosine distance, topn, and a similarity floor. The vector column name encodes the model dimension: q_{dim}_vec.
  • FusionExpr : method "weighted_sum", topn, and weights like "0.05,0.95" (BM25 weight, KNN weight).

In search(), when all three are present and the OpenSearch pipeline is active, the backend assembles a {"hybrid": {"queries": [keyword_query, {"knn": knn_query}]}} body and attaches search_pipeline as a query parameter .

The BM25 query leg's boost is set to 1.0 - vector_similarity_weight , and KNN boost is set to the configured similarity floor . The pipeline uses min-max normalization + arithmetic mean to merge the two score distributions .


Reranking After Retrieval#

After the backend call returns candidate chunks, retrieval() reranks them locally by blending term similarity and vector similarity:

sim = tkweight * term_sim + vtweight * vector_sim + rank_feature_scores

tkweight = 1 - vector_similarity_weight and vtweight = vector_similarity_weight .

Three reranking paths exist depending on the backend and whether an external reranker is configured :

  1. rerank_by_model() : Used when rerank_mdl is set. Combines local token similarity with a cross-encoder reranker's scores.
  2. rerank_with_knn() : Default ES/OpenSearch path. Issues a second KNN-only call via _knn_scores() to recover the clean cosine score per candidate chunk (without fetching vectors out of the index), then blends it with local term similarity.
  3. rerank() : OceanBase path. Fetches chunk vectors directly from the result and computes cosine similarity locally via qryr.hybrid_similarity().

The Infinity backend skips local rerank entirely and uses the backend's own normalized fusion score .

The similarity_threshold parameter filters out chunks below a composite score. When vector_similarity_weight == 0, the threshold is bypassed (set to 0.0) since BM25 scores are not normalized to the same range .


Configuration Reference#

OpenSearch hybrid pipeline (conf/service_conf.yaml)#

os:
  hosts: 'http://localhost:1201'
  # hybrid_search_pipeline: 'ragflow_hybrid_pipeline' # default name
  # hybrid_search_weights: [0.5, 0.5] # [BM25 leg, KNN leg]

Both keys are optional and commented out by default. The pipeline name can also be overridden via the OS_HYBRID_PIPELINE environment variable . The default weight is [0.5, 0.5] when not specified .

retrieval() parameters that control scoring#

ParameterDefaultEffect
similarity_threshold0.2Minimum composite score to include a chunk
vector_similarity_weight0.3KNN weight; 1 - this is BM25/term weight
top1024Maximum candidates fetched from the backend
rerank_mdlNoneIf set, uses cross-encoder reranker instead of local blend

Fallback Behavior on Empty Results#

If the initial hybrid search returns zero hits, search() retries with a relaxed min_match=0.1 (down from 0.3) and a slightly higher similarity floor of 0.17 . On a doc_id-scoped search, it falls back to a filter-only query with no match expression at all.


Key Files#

FilePurpose
rag/nlp/search.pyDealer class — full retrieval pipeline, reranking, citation insertion
rag/utils/opensearch_conn.pyOSConnection — hybrid pipeline provisioning, search() DSL builder
common/doc_store/doc_store_base.pyMatchTextExpr, MatchDenseExpr, FusionExpr definitions
conf/service_conf.yamlOpenSearch hybrid search config keys
Hybrid Search and Retrieval | Dosu