<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Guy Olivier Millimouno</title>
    <description>The latest articles on DEV Community by Guy Olivier Millimouno (@gyom15).</description>
    <link>https://dev.to/gyom15</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4053465%2Faff66f94-2073-4c9d-b2cf-9182ae46a30e.png</url>
      <title>DEV Community: Guy Olivier Millimouno</title>
      <link>https://dev.to/gyom15</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gyom15"/>
    <language>en</language>
    <item>
      <title>Breaking RAG Benchmarks: 5 Metric Traps I Fell Into — Part 1</title>
      <dc:creator>Guy Olivier Millimouno</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:22:49 +0000</pubDate>
      <link>https://dev.to/gyom15/breaking-rag-benchmarks-5-metric-traps-i-fell-into-part-1-2p5m</link>
      <guid>https://dev.to/gyom15/breaking-rag-benchmarks-5-metric-traps-i-fell-into-part-1-2p5m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;What makes a retrieval architecture work and what makes it fail?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Retrieval is one of the easiest parts of a RAG system to underestimate.&lt;/p&gt;

&lt;p&gt;Discussions about RAG performance often focus on the LLM: the model, the prompt, the context window, or the quality of generation. But before any of that matters, the retriever has to find the right evidence.&lt;/p&gt;

&lt;p&gt;Vector, hybrid, and graph retrieval make different assumptions about what makes a document relevant. The interesting part isn't simply which one wins a benchmark. It's understanding where those assumptions hold, where they break, and what each architecture depends on.&lt;/p&gt;

&lt;p&gt;That requires more than swapping one retriever for another.&lt;/p&gt;

&lt;p&gt;If chunking, embeddings, top-k, prompts, or the LLM change at the same time, any difference in performance becomes difficult to attribute. A fair comparison therefore requires controlling those variables and changing one thing at a time.&lt;/p&gt;

&lt;p&gt;So that's what this benchmark does.&lt;/p&gt;

&lt;p&gt;And that's where things got interesting.&lt;/p&gt;

&lt;p&gt;The benchmark didn't just reveal which retriever performed best. It also exposed how easily a perfectly reasonable looking number could lead to the wrong conclusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this benchmark matters
&lt;/h2&gt;

&lt;p&gt;Retrieval-Augmented Generation has become a common architecture for grounding LLMs. But discussions around retrieval often sound surprisingly absolute.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Graph RAG is the future.&lt;/li&gt;
&lt;li&gt;Hybrid always wins.&lt;/li&gt;
&lt;li&gt;Just add a reranker.&lt;/li&gt;
&lt;li&gt;Bigger models produce better answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal here wasn't simply to declare a winner. It was to understand &lt;strong&gt;what makes each retrieval architecture work, where it breaks, and what its performance depends on&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That meant building three retrieval pipelines under controlled conditions and changing exactly one component: the retriever.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rules
&lt;/h2&gt;

&lt;p&gt;The benchmark compares three retrieval strategies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vector (FAISS)&lt;/strong&gt;: semantic retrieval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid (BM25 + Reciprocal Rank Fusion)&lt;/strong&gt;: lexical + semantic retrieval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph&lt;/strong&gt;: spaCy entity graph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else stayed identical.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same corpus&lt;/li&gt;
&lt;li&gt;Same chunking&lt;/li&gt;
&lt;li&gt;Same embeddings&lt;/li&gt;
&lt;li&gt;Same prompt&lt;/li&gt;
&lt;li&gt;Same LLM&lt;/li&gt;
&lt;li&gt;Same top-k&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the retriever changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure retrieval evaluation&lt;/strong&gt;: nDCG@10 on BEIR ground truth (no LLM in the loop).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end QA&lt;/strong&gt;: identical prompt, context format, and LLM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8olzsq8dinxheugxo5n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8olzsq8dinxheugxo5n5.png" alt="benchmark-pipeline" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap #1: The Graph Found the Best Document
&lt;/h2&gt;

&lt;p&gt;Except it didn't.&lt;/p&gt;

&lt;p&gt;The graph retriever worked well on a small corpus. Then I increased the dataset from roughly 100 documents to 500. Suddenly, a question that had always worked started failing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is the capital of Afghanistan?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Not enough information.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Retrieval is deterministic, so I knew this wasn't random. I inspected the rankings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;document&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;th&gt;semantic similarity&lt;/th&gt;
&lt;th&gt;entity bonus&lt;/th&gt;
&lt;th&gt;# entities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;June&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.37&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;People's Republic of China&lt;/td&gt;
&lt;td&gt;3.06&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;10.18&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Islamic world&lt;/td&gt;
&lt;td&gt;2.39&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;7.96&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Afghanistan&lt;/td&gt;
&lt;td&gt;2.09&lt;/td&gt;
&lt;td&gt;0.62&lt;/td&gt;
&lt;td&gt;4.90&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Afghanistan&lt;/td&gt;
&lt;td&gt;2.04&lt;/td&gt;
&lt;td&gt;0.76&lt;/td&gt;
&lt;td&gt;4.26&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The winning document wasn't Afghanistan. It was &lt;strong&gt;June&lt;/strong&gt;, a Wikipedia page listing national holidays. Its semantic similarity to the question was literally &lt;strong&gt;0.000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The culprit wasn't the graph itself. It was my scoring function.&lt;/p&gt;

&lt;p&gt;I had combined:&lt;/p&gt;

&lt;p&gt;$$\text{Final Score} = \text{Semantic Similarity} + \text{Entity Bonus}$$&lt;/p&gt;

&lt;p&gt;The semantic similarity correctly favored Afghanistan. The entity bonus overwhelmed everything else. Documents mentioning many entities accumulated enormous bonuses, even when they weren't relevant.&lt;/p&gt;

&lt;p&gt;My score wasn't measuring relevance anymore. It was measuring &lt;strong&gt;entity count&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kgoyr2h4arqh3bxf73j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kgoyr2h4arqh3bxf73j.png" alt="graph-score-breakdown" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix itself was simple. Normalize the entity bonus.&lt;/p&gt;

&lt;p&gt;The difficult part was deciding &lt;strong&gt;how much&lt;/strong&gt; to normalize. I was sure a gentle penalty would win. Instead of trusting that intuition, I evaluated six normalization strategies on a held-out validation split.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;normalizer&lt;/th&gt;
&lt;th&gt;validation score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;p75&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.4864&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;linear&lt;/td&gt;
&lt;td&gt;0.4849&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;0.4806&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;log&lt;/td&gt;
&lt;td&gt;0.4793&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p25&lt;/td&gt;
&lt;td&gt;0.4648&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0.4533&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A strong normalization won. I had expected the opposite.&lt;/p&gt;

&lt;p&gt;Interestingly, a different normalization produced a slightly better score on one test dataset. Changing to it would have improved the benchmark. It also would have invalidated the experiment.&lt;/p&gt;

&lt;p&gt;That's exactly why validation and test sets exist.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Key takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A scoring function can optimize exactly what you asked it to optimize while completely missing what you actually care about.&lt;/p&gt;

&lt;p&gt;Always inspect &lt;strong&gt;why&lt;/strong&gt; a document ranks first, not just &lt;strong&gt;that&lt;/strong&gt; it ranks first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The benchmark, implementation, and evaluation code are open source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/GYOM15/rag-vector-hybrid-graph" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full repository includes the retrieval implementations, benchmark configuration, evaluation code, and experiments discussed in this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is Part 1 of RAG Under the Microscope. More traps to come.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are some lessons you can only learn the hard way.&lt;/p&gt;

&lt;p&gt;As we say in French: “C’était Guy Olivier.” ✌️&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>rag</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
