Hybrid Search#
Hybrid search in Dify combines vector (semantic) and full-text/keyword retrieval in a single query, then fuses the results using either weighted score fusion or an external reranking model. It is one of four retrieval methods β alongside semantic_search, full_text_search, and keyword_search β and is the only mode that fans out to both index types simultaneously.
Index Creation#
Both index types are populated at ingestion time via ParagraphIndexProcessor.load() :
- Vector index β created by
Vector(dataset).create(documents)when the dataset'sindexing_techniqueisHIGH_QUALITY. TheVectorfactory lazily resolves the configured embedding model viaModelManagerand delegates to the backend-specific vector store implementation. - Keyword index β built by
Keyword(dataset).add_texts(documents). TheKeywordfactory is backed by Jieba (the only currently supported keyword store, selected viadify_config.KEYWORD_STORE) .
Both indexes are maintained in sync: clean() deletes from both when segments are removed .
Concurrent Retrieval#
RetrievalService._retrieve() uses a ThreadPoolExecutor to fan out the three search methods in parallel :
| Method | Condition | Called via |
|---|---|---|
| Embedding search | is_support_semantic_search(method) = SEMANTIC_SEARCH or HYBRID_SEARCH | embedding_search() |
| Full-text search | is_support_fulltext_search(method) = FULL_TEXT_SEARCH or HYBRID_SEARCH | full_text_index_search() |
| Keyword search | method == KEYWORD_SEARCH only | keyword_search() |
For HYBRID_SEARCH, both embedding_search and full_text_index_search futures are submitted . The pool size is configured via dify_config.RETRIEVAL_SERVICE_EXECUTORS .
Score threshold deferral: During hybrid search, the vector score threshold is set to 0.0 at retrieval time so raw embedding scores (which are not comparable to fused scores) do not prematurely drop high-quality chunks. Threshold filtering is applied after fusion instead .
Deduplication and Result Fusion#
After the parallel futures complete, _retrieve() handles post-processing exclusively for HYBRID_SEARCH :
-
Deduplication β
_deduplicate_documents()removes duplicates in O(n) order-preserving fashion. For documents with adoc_id, it keeps the copy with the highest score; for documents without one, it keeps the first occurrence by content key . -
Fusion via
DataPostProcessorβ dispatches to one of two runners based onreranking_mode:-
WEIGHTED_SCOREβWeightRerankRunner: Computes a linear combinationvector_weight Γ cosine_similarity + keyword_weight Γ TF-IDF_cosine. Vector scores reuse the stored retrieval score when available; otherwise cosine similarity is recomputed from the raw document vector . Keyword scores are TF-IDF cosine similarities computed on the fly using Jieba extraction . -
RERANKING_MODELβRerankModelRunner: Deduplicates bydoc_id, then calls an external rerank model viainvoke_rerank. Multimodal reranking (image queries) is also supported viainvoke_multimodal_rerank.
-
-
Post-fusion threshold β if no rerank runner is active, a final score-threshold filter is applied to vector scores .
Key Files#
| File | Role |
|---|---|
retrieval_service.py | Concurrent retrieval, deduplication, post-processing dispatch |
retrieval_methods.py | RetrievalMethod enum; HYBRID_SEARCH activates both semantic + fulltext paths |
data_post_processor.py | Selects WeightRerankRunner vs RerankModelRunner |
weight_rerank.py | Weighted score fusion (TF-IDF + cosine) |
rerank_model.py | External rerank model runner |
paragraph_index_processor.py | Index creation/deletion for vector + keyword |
keyword_factory.py | Keyword store factory (Jieba backend) |
vector_factory.py | Vector store factory with lazy embedding |