Retrieval Pipeline#
Overview#
RAGFlow's retrieval pipeline has five distinct entry points, but they all converge on a single engine: Dealer.retrieval() in rag/nlp/search.py. Each entry point applies its own query preprocessing before calling into Dealer, and each shares the same reranking and scoring logic downstream.
Chat completions (dialog_service.async_chat) ──┐
Standalone REST (/api/v1/retrieval) ──┤
Dify endpoint (/api/v1/dify/retrieval) ──┼──► Dealer.retrieval() ──► ranked chunks
Agent Retrieval tool (agent/tools/retrieval.py) ──┤
Agentic RAG (RAGTools in agentic_rag.py) ──┘
The Go backend (internal/service/nlp/reranker.go) implements a parallel reranking layer that mirrors the Python Dealer logic, used when requests route through the Go service path.
For BM25/KNN scoring mechanics and backend-level search (Infinity, Elasticsearch, OceanBase), see Hybrid Search and Retrieval.
Entry Points#
Chat Completions#
The chat completion path preprocesses queries inside async_chat() in api/db/services/dialog_service.py. The preprocessing sequence is: strip ##<digits>$$ variable substitution markers → optional multi-turn question refinement via full_question() → cross-language expansion → metadata filtering → optional keyword extraction. Agentic chat routes through rag_agent(), which instantiates RAGTools .
Standalone REST (/api/v1/retrieval)#
retrieval_test() in chunk_api.py handles this endpoint. The call sequence: auth + embedding model resolution → cross-language expansion (if cross_languages) → keyword extraction (if keyword=true) → metadata pre-filter resolving doc_ids → Dealer.retrieval() → optional ToC enhancement → child-chunk expansion → optional KG injection. See Retrieval API for full parameter documentation.
Dify Endpoint (/api/v1/dify/retrieval)#
dify_retrieval_api.py accepts Dify's schema (knowledge_id, retrieval_setting). When metadata_condition is present, operator names are normalized by convert_conditions() (e.g. "is" → "=", "not is" → "≠"), then meta_filter resolves a doc_ids whitelist. If no documents match, a sentinel value ("-999") is passed so retrieval returns zero results rather than bypassing the filter .
Agent Retrieval Tool#
agent/tools/retrieval.py is the Retrieval canvas component. Its _retrieve_kb() method applies: variable reference resolution via get_input_elements_from_text() → string_format() → "user:" prefix stripping → apply_meta_data_filter() for meta_data_filter conditions → cross-language expansion → Dealer.retrieval(). Dataset IDs can be literals or {{cpn_id@var}} references that resolve to KB names or IDs at runtime .
Agentic RAG (RAGTools)#
RAGTools in rag/advanced_rag/agentic_rag.py drives the agentic search graph. Its formalize() method uses an LLM call to rewrite the last user message into a standalone question and extract keywords — distinct from the simpler prefix stripping used in other paths. Retrieved results accumulate in a per-request search_cache to avoid duplicate backend calls within one turn .
Query Preprocessing Transformations#
Each entry point applies a subset of these transforms before calling Dealer.retrieval():
Variable substitution. Two patterns coexist. The agent path resolves {{cpn_id@var}} references via get_input_elements_from_text() and string_format(). The chat path strips ##<digits>$$ markers inline before passing the message to the retrieval call.
"user:" prefix stripping. The agent Retrieval tool runs re.sub(r"^user[::\s]*", "", query, flags=re.IGNORECASE) to remove prefixes that a previous LLM step may have prepended to the query.
Cross-language expansion. cross_languages() in rag/prompts/generator.py takes the query and a list of target language codes, calls the tenant's LLM with a translation prompt, and returns the original query concatenated with the translated variants — all fed together into a single retrieval call. Invoked in the chat path, the REST endpoint , and the agent Retrieval tool .
Metadata filtering. apply_meta_data_filter() accepts three modes:
auto— generates filter conditions from the query using an LLM viagen_meta_filter()semi_auto— same but restricted to user-specified metadata keysmanual— uses explicit conditions with optional{{var}}value substitution
The filter produces a doc_ids whitelist passed to Dealer.retrieval(). When no documents match, the sentinel "-999" ensures zero results rather than unfiltered fallback . Operator normalization for the Dify path is handled by convert_conditions().
Keyword extraction. When keyword=true (REST endpoint) or the chat prompt config enables it, the tenant's chat LLM extracts keywords from the query and appends them to the query string before retrieval.
Reranker Score Normalization#
After Dealer.retrieval() fetches candidate chunks, it applies one of three reranking paths (see Hybrid Search and Retrieval for the full scoring formula). The key cross-cutting concern is that reranker providers emit scores on incompatible scales — Cohere/Jina/Voyage return calibrated [0, 1] relevance scores, while NVIDIA returns unbounded, often negative logits. Both Python and Go enforce the same normalization contract before blending reranker output with token similarity.
Python: rag/llm/rerank_model.py#
Base.similarity() is the single public entry point for every reranker provider. It calls the provider-specific _compute_rank(), then applies _normalize_rank():
- If all scores are already in
[0, 1]→ return unchanged (preserves calibrated providers' absolute magnitudes) - If the score spread (
max − min) is< 1e-3→ clamp per-element to[0, 1](avoids collapsing a spreadless batch to zero) - Otherwise → min-max rescale onto
[0, 1]
rerank_by_model() in search.py relies on this guarantee when blending: tkweight * tksim + vtweight * vtsim, where vtsim is the normalized reranker output.
Go: internal/service/nlp/reranker.go#
NormalizeRerankScores() mirrors _normalize_rank exactly with the same three-case logic. It is called inside RerankByModel() after the model scores arrive, before the tkWeight * tsim + vtWeight * modelSim blend.
The Go package also provides:
RerankWithKNN()— two-pass ES approach (first pass retrieves, second KNN-only pass recovers cosine scores filtered to the candidate set)RerankStandard()— ES path without an external reranker; callsHybridSimilarity()locally with stored chunk vectorsRerankInfinityFallback()— extracts the Infinity-provided fusion score (tries multiple field name variants:SCORE,score,_score,similarity(), etc.)
Token weighting is consistent across Python and Go: content_ltks + title_tks × 2 + important_kwd × 5 + question_tks × 6 .
Key Source Files#
| File | Purpose |
|---|---|
rag/nlp/search.py | Dealer class — core retrieval + reranking engine |
rag/llm/rerank_model.py | Base.similarity() / _normalize_rank() — Python score normalization |
internal/service/nlp/reranker.go | Go reranking: NormalizeRerankScores, RerankByModel, RerankWithKNN, RerankStandard, RerankInfinityFallback |
agent/tools/retrieval.py | Agent Retrieval tool — variable resolution, prefix stripping, metadata filtering |
agent/component/base.py | ComponentBase — variable_ref_patt, get_input_elements_from_text(), string_format() |
api/apps/restful_apis/chunk_api.py | /api/v1/retrieval handler (retrieval_test()) |
api/apps/restful_apis/dify_retrieval_api.py | Dify-compatible endpoint with operator normalization |
common/metadata_utils.py | apply_meta_data_filter(), meta_filter(), convert_conditions() |
rag/advanced_rag/agentic_rag.py | RAGTools — agentic search graph, formalize(), per-request cache |