{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Aman Jain",
  "title_listed": "How RAG Finds the Right Evidence: Dense, Sparse, and Hybrid Retrieval",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://curious-pm.hashnode.dev/how-rag-finds-the-right-evidence-dense-sparse-and-hybrid-retrieval",
  "publish_date": "2026-08-02",
  "corpus_stage": "",
  "notes": "",
  "id": "055330eda2c4",
  "collected_at": "2026-10-02T14:11:42",
  "title": "Dense vs Sparse vs Hybrid Retrieval in RAG",
  "body_markdown": "How RAG Finds the Right Evidence: Dense, Sparse, and Hybrid Retrieval\nComparing semantic search, BM25, Reciprocal Rank Fusion, evidence grading, and Corrective RAG\nUpdated\nAugust 2, 2026\n•\n10\nmin read\n•\nView as Markdown\nA\nAman Jain\nProduct manager learning to build AI from the inside out — from LLM architecture to product applications.\nPart of series\nRAG From First Principles\nIn the first article of this series, I explored how raw documents become retrievable knowledge. I loaded different file formats, preserved their metadata, and divided their contents into chunks.\nOnce the chunks are ready, the next question is:\nGiven a user query, which chunks should the RAG system retrieve?\nThis is an information-retrieval problem, and it has a lot in common with traditional search. Concepts such as lexical matching, similarity scores, ranking, precision, and recall therefore feel familiar.\nThe experiments also revealed an important limitation: retrieval can find relevant chunks, but it does not determine whether those chunks are factually correct.\nTL;DR\nRetrieval ranks chunks according to their relevance to a query.\nA highly relevant chunk can still contain inaccurate information. Relevance and correctness must therefore be evaluated separately.\nDense retrieval uses embeddings to capture semantic similarity.\nSparse retrieval methods such as BM25 emphasize matching words and phrases.\nHybrid retrieval combines dense and sparse rankings.\nDense retrieval works well for paraphrased questions, while BM25 remains useful when exact terminology matters.\nReciprocal Rank Fusion combines rankings without requiring their raw scores to use the same scale.\nCorrective RAG adds another layer of evidence grading, confidence filtering, and corrective action when the retrieved evidence is insufficient.\nRetrieval as Search Over Chunks\nAfter ingestion and chunking, a RAG knowledge base can be viewed as a collection of searchable passages.\nWhen a user submits a query, the retriever:\ncompares the query with the stored chunks,\ncalculates a relevance signal,\nranks the chunks,\nreturns the top candidates.\nThe selected chunks are then passed to later stages of the RAG pipeline.\nA retriever is not usually trying to prove that a passage is correct. Its immediate task is narrower: identify passages that appear most relevant to the query.\nThere are several ways to measure that relevance.\n1. Dense Retrieval\nDense retrieval represents both the query and every chunk as embeddings.\nAn embedding is a numerical vector intended to represent the meaning of a piece of text. Texts with similar meanings should ideally be located close to one another in the embedding space.\nIn the notebook, I generated embeddings using:\nfrom sentence_transformers import SentenceTransformer\n\nembedding_model = SentenceTransformer(\n    \"BAAI/bge-small-en-v1.5\"\n)\n\nchunk_embeddings = embedding_model.encode(\n    chunks,\n    normalize_embeddings=True,\n)\nThe query is embedded using the same model:\nquery_embedding = embedding_model.encode(\n    [query],\n    normalize_embeddings=True,\n)\nI then used cosine similarity to compare the query vector with the chunk vectors:\nscores = cosine_similarity(\n    query_embedding,\n    chunk_embeddings,\n)[0]\nCosine similarity measures how closely two vectors point in the same direction. The chunks with the highest scores are returned first.\nWhere dense retrieval helps\nDense retrieval is especially useful when the query and the relevant chunk express the same idea using different words.\nFor example, a user may ask:\nWhy does time pass differently for a moving object?\nThe relevant source might instead use terms such as\ntime dilation\n,\nrelative motion\n, or\nmoving clocks\nwithout repeating the exact wording of the query.\nBecause dense retrieval compares semantic representations rather than only words, it can still identify the connection.\nIts limitations\nEmbedding similarity is approximate. A dense retriever may return passages that are broadly related to the topic but do not answer the specific question.\nIt may also miss rare names, exact quotations, technical identifiers, or specialized terminology that lexical retrieval can identify more directly.\n2. Sparse Retrieval with BM25\nSparse retrieval follows a more traditional search approach.\nInstead of representing every passage as a dense semantic vector, BM25 scores chunks using the words they contain. It considers factors such as:\nwhether a query term appears in the chunk,\nhow frequently it appears,\nhow rare the term is across the corpus,\nthe length of the chunk.\nA simplified implementation from the notebook looks like this:\nfrom rank_bm25 import BM25Okapi\n\ntokenized_chunks = [\n    chunk.lower().split()\n    for chunk in chunks\n]\n\nbm25 = BM25Okapi(tokenized_chunks)\n\nquery_tokens = query.lower().split()\nbm25_scores = bm25.get_scores(query_tokens)\nWhere BM25 helps\nBM25 is effective when exact words matter.\nFor queries containing terms such as\nLorentz transformation\n,\nMichelson–Morley experiment\n, or\nspecial relativity\n, lexical overlap may be a strong relevance signal.\nIt is also relatively transparent: a chunk ranks well largely because it contains important query terms.\nIts limitations\nBM25 does not naturally understand paraphrases.\nIf a query and a relevant passage use different vocabulary, lexical overlap may be low even when their meanings are closely related.\nThis creates a useful contrast:\nDense retrieval emphasizes meaning.\nSparse retrieval emphasizes words.\nNeither is universally better.\n3. Hybrid Retrieval\nHybrid retrieval combines dense and sparse search.\nThe goal is to benefit from:\nthe semantic understanding of embeddings,\nthe lexical precision of BM25.\nHowever, directly averaging their scores can be problematic. Cosine-similarity and BM25 scores use different scales and distributions.\nInstead, I combined their ranked lists using Reciprocal Rank Fusion.\ndef reciprocal_rank_fusion(\n    ranked_lists,\n    k=60,\n):\n    fused_scores = {}\n\n    for ranked_list in ranked_lists:\n        for rank, document_id in enumerate(\n            ranked_list,\n            start=1,\n        ):\n            fused_scores[document_id] = (\n                fused_scores.get(document_id, 0)\n                + 1 / (k + rank)\n            )\n\n    return sorted(\n        fused_scores.items(),\n        key=lambda item: item[1],\n        reverse=True,\n    )\nReciprocal Rank Fusion rewards chunks that appear near the top of one or more rankings. It works with rank positions rather than requiring the original scores to be directly comparable.\nHere is one output from the notebook:\nQuery type: direct\nQuery: Why does a moving clock run slower?\n\nRank 1\nHybrid RRF score: 0.0325\nDense rank: 2\nBM25 rank: 1\n\nBlob:\nA moving clock can run slow relative to another clock only if it is\nphysically observed through a telescope. Unobserved clocks are unaffected.\nHybrid retrieval can be more robust across different query types. A semantically phrased query may benefit from dense search, while an exact technical query may benefit from BM25.\nHowever, combining two methods does not guarantee that every result will improve. Hybrid retrieval still depends on the quality of the component rankings and the fusion strategy.\nThe example above also exposes a more fundamental issue: the chunk ranked highly by both retrievers, but its claim is inaccurate.\nRelevance Is Not Correctness\nThis was the most important learning from the experiment.\nA retriever does not normally evaluate whether a chunk is factually accurate. It evaluates whether the chunk matches the query according to its retrieval signal.\nConsider a query about time dilation and these two chunks:\nMoving clocks are measured to run more slowly.\n\nMoving clocks are measured to run more quickly.\nBoth passages contain nearly identical terminology. Both may receive strong BM25 scores, and their embeddings may also be close to the query.\nOne is aligned with the trusted explanation. The other contradicts it.\nFrom the retriever’s perspective, however, both are relevant.\nThis was also visible in the distribution of cosine-similarity scores for factually aligned and contradictory chunks. The same issue appeared in BM25 scores: relevance signals alone did not reliably separate correct evidence from incorrect evidence.\nThis leads to a critical distinction:\nRelevance tells us whether a passage is about the query. It does not tell us whether the passage is correct.\nThe pipeline therefore needs additional controls when the source corpus may contain unreliable, conflicting, or uncertain information.\n4. Corrective RAG: Filtering Retrieved Evidence\nDense, sparse, and hybrid retrieval all try to find chunks that match the user’s query. However, a strong retrieval score does not guarantee that the evidence is accurate or safe to use.\nA retrieved chunk may be:\nrelevant and factually aligned,\nrelevant but contradictory,\nrelated to the topic but too uncertain to support an answer.\nThis is where evidence grading becomes useful.\nIn the notebook, I added an LLM-based grader that evaluates each retrieved candidate against the query and a trusted reference. The grader assigns one of three labels:\naligned\n— the evidence supports the trusted explanation,\ncontradicts\n— the evidence conflicts with it,\nuncertain\n— the evidence is related but insufficient for a confident decision.\nThe grader also returns a confidence score and a short rationale.\nHere are two examples from the notebook:\nGold label: aligned\nPredicted: aligned\nConfidence: 1.0\n\nRationale:\nThe candidate's claim about a moving clock ticking more slowly becoming\nsubstantial only near light speed directly matches the reference explanation\nof special-relativity time dilation.\n\nText:\nA clock moving rapidly relative to an observer is measured to tick more\nslowly than a clock at rest with that observer. This effect becomes\nsubstantial only when the relative speed is a significant fraction of\nthe speed of light.\nGold label: contradicts\nPredicted: contradicts\nConfidence: 1.0\n\nRationale:\nThe reference explicitly states that all observers do not share one\nuniversal time and that time dilation causes moving clocks to record\ndifferent elapsed times.\n\nText:\nAll clocks in the universe share one absolute time regardless of their\nmotion. A fast-moving spacecraft and Earth must therefore record\nidentical elapsed times.\nA simplified filtering step looks like this:\nverified_chunks = graded_chunks[\n    (graded_chunks[\"predicted_label\"] == \"aligned\")\n    & (\n        graded_chunks[\"verification_confidence\"]\n        >= confidence_threshold\n    )\n]\nOnly high-confidence, aligned chunks are allowed to continue through the pipeline. Contradictory and uncertain candidates are removed.\nThis creates a second layer of selection:\nInitial retrieval\n→ evidence grading\n→ confidence filtering\n→ verified chunks\nThe trusted reference provides an accuracy signal in this experiment. In a production system, equivalent checks may instead come from trusted-source metadata, domain rules, verified databases, human labels, or other validation mechanisms.\nWhen correction is triggered\nFiltering can leave the system with too little usable evidence.\nFor example, if the retriever returns five candidates but only one passes the verification threshold, the system may not have enough context to answer reliably.\nCorrective RAG adds a recovery step for this situation:\nRetrieve candidates\n→ grade evidence\n→ retain verified chunks\n→ check whether enough evidence remains\n→ trigger corrective retrieval or fallback when needed\nThe corrective action could involve:\nretrieving more candidates,\nrewriting the query,\nsearching a trusted source,\nexpanding the retrieval scope,\nusing a verified-reference fallback.\nIn this implementation, correction is triggered when the number of trusted candidates falls below a minimum threshold.\nThis distinction is important: filtering weak chunks is useful, but filtering alone is not a complete corrective pipeline. The system becomes corrective when it takes another action after determining that the original retrieval results are insufficient.\nWhat the Experiments Showed\nThe notebook reinforced four main lessons.\nFirst, dense and sparse retrieval solve different problems. Dense search is stronger when meaning is shared but wording differs. BM25 is stronger when exact terminology matters.\nSecond, hybrid retrieval can improve robustness by combining both signals, especially when user queries vary in style.\nThird, retrieval scores should not be interpreted as truth or confidence. A high score indicates a strong match under that retrieval method—not factual correctness.\nFinally, retrieval quality should be evaluated using a suite of queries and relevance labels rather than only a few manually inspected examples. Evidence quality must then be assessed separately using labels, verification rules, or an evidence grader.\nIn the next article, I will go beyond independent chunk retrieval and explore advanced techniques such as sliding-window, parent-child, summary-to-detail, and hierarchical retrieval.\nThe full implementation is available in the\nproject repository on GitHub\n.\nReferences and Further Reading\nLewis, P. et al. (2020).\nRetrieval-Augmented Generation for Knowledge-Intensive NLP Tasks\n.\nKarpukhin, V. et al. (2020).\nDense Passage Retrieval for Open-Domain Question Answering\n.\nHuang, J.\nRAG from First Principles: Mastering Retrieval-Augmented Generation\n.\nLangChain document-loader documentation\n.\nLangChain text-splitter documentation\n.\nLlamaIndex SemanticSplitterNodeParser documentation\n.\nBAAI/bge-small-en-v1.5 model card\n.\nProject code and notebooks\n.\n#\nretrieval-augmented-generation\n#\nbm25\n#\nsemantic-search\n#\ngenerative-ai\n6\nviews",
  "metadata": {
    "title": "Dense vs Sparse vs Hybrid Retrieval in RAG",
    "description": "A practical comparison of dense retrieval, BM25, hybrid search, Reciprocal Rank Fusion, evidence grading, and Corrective RAG.",
    "keywords": null,
    "author": null,
    "og:title": "Dense vs Sparse vs Hybrid Retrieval in RAG",
    "og:description": "A practical comparison of dense retrieval, BM25, hybrid search, Reciprocal Rank Fusion, evidence grading, and Corrective RAG.",
    "og:url": "https://curious-pm.hashnode.dev/how-rag-finds-the-right-evidence-dense-sparse-and-hybrid-retrieval",
    "og:site_name": "Curious PM - Product to Parameters",
    "og:image": "https://cdn.hashnode.com/uploads/og-images/6a4f47d631bb27227f782535/563d8112-c7c3-49e1-9d28-962758962233.png",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:title": "Dense vs Sparse vs Hybrid Retrieval in RAG",
    "twitter:description": "A practical comparison of dense retrieval, BM25, hybrid search, Reciprocal Rank Fusion, evidence grading, and Corrective RAG.",
    "twitter:image": "https://cdn.hashnode.com/uploads/og-images/6a4f47d631bb27227f782535/563d8112-c7c3-49e1-9d28-962758962233.png",
    "article:published_time": "2026-08-02T21:14:23.703Z",
    "article:modified_time": "2026-08-02T21:14:41.452Z",
    "article:author": "https://curious-pm.hashnode.dev/@aman454"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/055330eda2c4/code/body-000.txt",
      "chars": 214,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-001.txt",
      "chars": 88,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-002.txt",
      "chars": 76,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-003.txt",
      "chars": 224,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-004.txt",
      "chars": 470,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-005.txt",
      "chars": 272,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-006.txt",
      "chars": 96,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-007.txt",
      "chars": 490,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-008.txt",
      "chars": 405,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-009.txt",
      "chars": 182,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-010.txt",
      "chars": 78,
      "language": "text"
    },
    {
      "file": "attachments/055330eda2c4/code/body-011.txt",
      "chars": 157,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/a5e8d398-c516-4cbc-ad4c-e5997e2285f3.png",
      "bytes": 1018931,
      "reused": true,
      "local_path": "attachments/055330eda2c4/images/000.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/a5e8d398-c516-4cbc-ad4c-e5997e2285f3.png",
      "alt": "How RAG Finds the Right Evidence: Dense, Sparse, and Hybrid Retrieval",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/e484e027-2165-4950-b84e-3f46f549697c.png",
      "bytes": 20249,
      "reused": true,
      "local_path": "attachments/055330eda2c4/images/001.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/e484e027-2165-4950-b84e-3f46f549697c.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/b50c7eaa-0318-4868-8f61-49e181b3cc9b.png",
      "bytes": 15066,
      "reused": true,
      "local_path": "attachments/055330eda2c4/images/002.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/b50c7eaa-0318-4868-8f61-49e181b3cc9b.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6a63e5af-2c01-4f66-8129-4f3a6abab33e.png",
      "bytes": 23577,
      "reused": true,
      "local_path": "attachments/055330eda2c4/images/003.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6a63e5af-2c01-4f66-8129-4f3a6abab33e.png",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://curious-pm.hashnode.dev/how-rag-finds-the-right-evidence-dense-sparse-and-hybrid-retrieval.md",
      "label": "View as Markdown",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/@aman454",
      "label": "A",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/aman-jain-1049188/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/series/building-rag-from-scratch",
      "label": "Part of series RAG From First Principles",
      "type": "a"
    },
    {
      "href": "https://github.com/amanjain454/rag-from-scratch-work",
      "label": "project repository on GitHub",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2005.11401",
      "label": "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2004.04906",
      "label": "Dense Passage Retrieval for Open-Domain Question Answering",
      "type": "a"
    },
    {
      "href": "https://docs.langchain.com/oss/python/integrations/document_loaders",
      "label": "LangChain document-loader documentation",
      "type": "a"
    },
    {
      "href": "https://docs.langchain.com/oss/python/integrations/splitters/index",
      "label": "LangChain text-splitter documentation",
      "type": "a"
    },
    {
      "href": "https://docs.llamaindex.ai/en/stable/api_reference/node_parsers/semantic_splitter/",
      "label": "LlamaIndex SemanticSplitterNodeParser documentation",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/BAAI/bge-small-en-v1.5",
      "label": "BAAI/bge-small-en-v1.5 model card",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/retrieval-augmented-generation",
      "label": "# retrieval-augmented-generation",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/bm25",
      "label": "# bm25",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/semantic-search",
      "label": "# semantic-search",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/generative-ai",
      "label": "# generative-ai",
      "type": "a"
    }
  ],
  "retry_at": "2026-10-03T08:08:48",
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:53Z",
  "source_url": "https://curious-pm.hashnode.dev/how-rag-finds-the-right-evidence-dense-sparse-and-hybrid-retrieval",
  "papers": [
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2005.11401",
      "bytes": 885323,
      "reused": true,
      "local_path": "papers/repair/fc8d32f196e0f0c27ffb.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2004.04906",
      "bytes": 383508,
      "reused": true,
      "local_path": "papers/repair/8b76986dbe24871ab2c9.pdf"
    }
  ],
  "actual_video_files": [],
  "body_chars": 13678,
  "expected_body_images": 4,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/055330eda2c4/pages/source.html",
  "local_html": "attachments/055330eda2c4/pages/article.html",
  "body_base_path": "attachments/055330eda2c4/pages/",
  "body_markdown_path": "attachments/055330eda2c4/pages/article.md"
}