<?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: Jordi Marçal</title>
    <description>The latest articles on DEV Community by Jordi Marçal (@jordimarsal).</description>
    <link>https://dev.to/jordimarsal</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%2F212456%2F83989c34-f432-415e-b11d-fe23465e3bac.jpeg</url>
      <title>DEV Community: Jordi Marçal</title>
      <link>https://dev.to/jordimarsal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jordimarsal"/>
    <language>en</language>
    <item>
      <title>RAG without an eval gate is a demo.</title>
      <dc:creator>Jordi Marçal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 13:37:13 +0000</pubDate>
      <link>https://dev.to/jordimarsal/rag-without-an-eval-gate-is-a-demo-47ej</link>
      <guid>https://dev.to/jordimarsal/rag-without-an-eval-gate-is-a-demo-47ej</guid>
      <description>&lt;p&gt;I run a portfolio site where every claim about my work has to survive its own eval. This essay is the why behind that discipline: a RAG demo is not a system, and the distance between the two is a golden set plus a CI gate. It was first published on &lt;a href="https://jordimp.net/en/blog/rag-eval-gate/" rel="noopener noreferrer"&gt;jordimp.net&lt;/a&gt; — this cross-post keeps that page as the canonical home.&lt;/p&gt;

&lt;p&gt;The project in question, &lt;a href="https://github.com/jordimarsal/codebaserag" rel="noopener noreferrer"&gt;CodebaseRAG&lt;/a&gt;, is an open-source RAG engine over a codebase. Numbers below are its real, committed floors — including the embarrassing parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: recall nobody measures
&lt;/h2&gt;

&lt;p&gt;Most RAG projects ship a demo. You ask a question, the retriever returns five chunks, a model writes a confident paragraph, and everyone nods. Nobody can say whether the right document was in those five chunks — because nobody wrote down what “right” means and nobody measures it. The demo works until a chunking change, a new embedding model or a different vector store quietly moves quality, and the only signal is a vague feeling that answers got worse.&lt;/p&gt;

&lt;p&gt;That is the real failure mode: not hallucination as moral panic, but retrieval that degrades with nobody watching. A demo optimises for the happy path in front of an audience; a system optimises for a number it can regress against. The distance between the two is an eval — and CodebaseRAG exists to close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden set: turning “right” into forty checkable pairs
&lt;/h2&gt;

&lt;p&gt;An eval is only as honest as its questions. The golden set is a public list of question/answer pairs drawn from the repository itself: a question a developer would actually ask, paired with the answer and where it lives. It holds ≥40 pairs — enough to catch a real regression, small enough that every pair can be reviewed by hand and kept honest.&lt;/p&gt;

&lt;p&gt;You do not need a labelling platform. You need somewhere to write questions down and the discipline to keep them. They live next to the code, so a retriever change and its updated expectations travel in the same review.&lt;/p&gt;

&lt;p&gt;Three entries, paraphrased from the public set, show the shape:&lt;/p&gt;

&lt;p&gt;Which abstraction does the query use case depend on, and why is it not a vector-store type? It depends on a retriever port; adapters implement it, so the core never imports a pgvector or Qdrant class.&lt;/p&gt;

&lt;p&gt;What should CI do when mean recall@5 drops below the committed floor? Fail the build. The floor is a committed number, and moving it is an explicit, ADR-gated decision — not an edit that makes CI green.&lt;/p&gt;

&lt;p&gt;Can the eval run with no network? Yes. Embeddings arrive through a port with two adapters — Ollama locally, Anthropic when hosted — so the same golden set runs offline.&lt;/p&gt;

&lt;p&gt;The examples are not meant to be hard. They are meant to have defensible answers, so a wrong one is a failing test rather than a disagreement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI gate: what fails, what does not
&lt;/h2&gt;

&lt;p&gt;A gate is only useful if it fails loudly and predictably. This one measures mean recall@5, mean reciprocal rank and nDCG@5 over the golden set, and fails the build when mean recall@5 drops below the committed baseline of 0.409.&lt;/p&gt;

&lt;p&gt;What fails: any change that pushes mean recall@5 under 0.409. That is the entire rule. CI runs the same deterministic eval as a laptop, so a red build points at a retrieval change, not a flaky environment.&lt;/p&gt;

&lt;p&gt;What does not fail matters too. A model change that keeps recall@5 at or above the floor passes even when MRR and nDCG@5 move — the gate guards the metric that was committed to, not every number on the dashboard. A refactor that moves code without moving retrieval passes. And the gate does not judge answer fluency: this is a retrieval eval, and pretending it measures generation quality would be the same dishonesty as having no eval at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hexagonal: swapping Qdrant must not rewrite the core
&lt;/h2&gt;

&lt;p&gt;You measure retrieval, so what you measure must not be welded to what you deploy. CodebaseRAG is hexagonal: the query use case depends on ports, and pgvector, Qdrant, Ollama and Anthropic sit behind them as adapters.&lt;/p&gt;

&lt;p&gt;The test is concrete. Swapping Qdrant for pgvector — or running fully local on Ollama — must not rewrite the core. If changing a vector store forces edits across the use cases, the eval is measuring an accident of the storage layer, and every future swap re-opens whether the metrics still mean anything.&lt;/p&gt;

&lt;p&gt;Keeping the core free of frameworks and transports buys more than tidiness: it keeps the eval deterministic, exercised against in-memory or container-backed adapters with no network required. A committed floor is only trustworthy when the code beneath it does not change shape every time the infrastructure does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current baseline and what it does not claim
&lt;/h2&gt;

&lt;p&gt;The committed baseline is a mean recall@5 of 0.409 (MRR 0.231, nDCG@5 0.277) over a golden set of at least forty pairs. Read it as a floor, not a result. 0.409 is not SOTA and this is not a claim that retrieval is solved. It is the number the project committed to defend, chosen low enough to stay stable and high enough to catch a real drop.&lt;/p&gt;

&lt;p&gt;What the number does not claim: that the golden set is exhaustive, that a green build means good answers, or that the embedding model is the best available. Raising the floor is a recorded decision with a justification, never a vanity metric pasted into a README.&lt;/p&gt;

&lt;p&gt;What it does claim is smaller and more useful: on every change, the repository can tell you whether retrieval got worse, and prove it from a public golden set. That is the line between a RAG demo and a RAG system. A demo shows you an answer; a system lets you disagree with it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://jordimp.net/en/blog/rag-eval-gate/" rel="noopener noreferrer"&gt;jordimp.net/en/blog/rag-eval-gate&lt;/a&gt;, where it lives with its revision history. The engine is open source: &lt;a href="https://github.com/jordimarsal/codebaserag" rel="noopener noreferrer"&gt;jordimarsal/codebaserag&lt;/a&gt;. If you enjoyed the honest numbers, the same site runs a RAG concierge whose gen model survived a 16-candidate benchmark before shipping — receipts included.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>evaluation</category>
      <category>python</category>
    </item>
  </channel>
</rss>
