<?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: Ratul Sur</title>
    <description>The latest articles on DEV Community by Ratul Sur (@ratul_sur).</description>
    <link>https://dev.to/ratul_sur</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%2F4026173%2F6819866f-8593-49cf-a7b6-3ca2c655aeaf.jpg</url>
      <title>DEV Community: Ratul Sur</title>
      <link>https://dev.to/ratul_sur</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ratul_sur"/>
    <language>en</language>
    <item>
      <title>3 bugs I hit wiring DeepEval into my RAG pipeline (and what each one taught me about RAG evaluation)</title>
      <dc:creator>Ratul Sur</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:52:29 +0000</pubDate>
      <link>https://dev.to/ratul_sur/3-bugs-i-hit-wiring-deepeval-into-my-rag-pipeline-and-what-each-one-taught-me-about-rag-evaluation-41me</link>
      <guid>https://dev.to/ratul_sur/3-bugs-i-hit-wiring-deepeval-into-my-rag-pipeline-and-what-each-one-taught-me-about-rag-evaluation-41me</guid>
      <description>&lt;p&gt;Everyone shows you the happy path of RAG evaluation: import a metric, pass a test case, get a green check, tweet the screenshot. Then you wire it into CI, point it at a real pipeline, and the numbers make no sense — a metric fails while the answer is obviously correct, or passes while the answer is quietly missing half the data.&lt;/p&gt;

&lt;p&gt;I recently rebuilt a small RAG pipeline (OpenAI embeddings → Pinecone →'gpt-4o-mini' over a CSV of customer orders) and turned &lt;a href="https://github.com/ratulsur/RAG_LLM_EVAL" rel="noopener noreferrer"&gt;DeepEval&lt;/a&gt; into a real pass/fail gate on top of it. This post is the three bugs that actually taught me how RAG evaluation works — not the docs, the bugs.&lt;/p&gt;

&lt;p&gt;If you only take one thing away: a green eval suite is worthless if you don't understand what each metric is measuring.Let's earn the green. &lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: eval as a gate, not a vibe
&lt;/h2&gt;

&lt;p&gt;The whole point of an eval metric (as opposed to an eval score) is a threshold. Below it, the build should fail. DeepEval leans into this — every metric has a 'threshold' and an 'is_successful()', so 'assert_test()' turns your RAG quality into a CI check.&lt;br&gt;
Here's the shape of the harness. Each sample carries the question, the answer the pipeline produced, the chunks it retrieved, and the ground truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvalSample&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;            
    &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    
    &lt;span class="n"&gt;ground_truth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the translation into a DeepEval test case — this innocent-looking function is where bug #1 lives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_to_test_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EvalSample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;retrieval_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     
        &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ground_truth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ground_truth&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;         
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two fields look interchangeable and are not: 'retrieval_context' is what your retriever pulled, 'context' is the ground-truth reference. Confusing them is bug #1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #1: the Hallucination metric that punished a perfect answer
&lt;/h2&gt;

&lt;p&gt;My 'FaithfulnessMetric' said '1.0' — the answer was fully grounded in the retrieved chunks. My 'HallucinationMetric' on the same test case said 0.833 and failed. Two grounding metrics, opposite verdicts. One of them was lying.&lt;/p&gt;

&lt;p&gt;The mistake was mine. I had built the test case like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# WRONG
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# all 6 retrieved chunks
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the trap. 'HallucinationMetric' in DeepEval is lower-is-better, and it checks the answer against 'context' treated as factual ground truth. My retriever returned 6 chunks for a question that only needed 1. So DeepEval looked at the answer, compared it to all 6 "facts," and correctly concluded the answer "contradicted" the 5 orders it (rightly) ignored. The metric wasn't broken — I had fed it noise and called it truth.&lt;/p&gt;

&lt;p&gt;The fix is one line, and it encodes a real conceptual distinction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="c1"&gt;# RIGHT
&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ground_truth&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Score dropped to '0.000' and passed, 3/3.&lt;/p&gt;

&lt;p&gt;The lesson: 'retrieval_context' and 'context' are different inputs on purpose.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faithfulness asks: is the answer supported by what we retrieved? → feed it 'retrieval_context'.&lt;/li&gt;
&lt;li&gt;Hallucination asks: does the answer contradict known truth? → feed it 'context' = ground truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your retrieved chunks are evidence, not truth. Passing noisy retrieval in as "truth" makes a correct answer look like a liar. This distinction is also a great interview answer, by the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #2: the retrieval recall miss no generation metric could see
&lt;/h2&gt;

&lt;p&gt;Question: "Which orders were returned?" The pipeline answered confidently, listed the returned orders, cited them — and silently dropped order 1011.&lt;br&gt;
Faithfulness: perfect. &lt;br&gt;
Answer relevancy: perfect. &lt;br&gt;
Every generation-side metric was green, because the answer was faithful and relevant to the chunks it got. The bug was upstream: dense top-k retrieval never fetched 1011's chunk, so the generator never had a chance.&lt;/p&gt;

&lt;p&gt;This is the single most important thing about RAG evaluation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Faithfulness measures the generator. It cannot see a retrieval recall miss. If the chunk never arrives, a perfectly faithful answer will confidently omit it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two fixes, both worth knowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Metadata-filtered retrieval for anything that's really an aggregation ("which / how many / list all"). For structured data, don't make cosine similarity guess a category — filter on it:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 'status=Returned' -&amp;gt; {"status": {"$eq": "Returned"}}
&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Returned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once "Returned" is a hard filter instead of a fuzzy vector match, recall stops depending on luck.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measure retrieval separately with retrieval-only metrics — hit@k, MRR, precision@k, recall@k — so a recall miss shows up as a retrieval number, not as a mysteriously incomplete answer. 'ContextualRecall' in DeepEval is the gate for exactly this.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The lesson: a RAG eval that only scores the final answer is half an eval. Score the retriever and the generator as separate stages, or retrieval failures will keep hiding behind faithful-sounding prose.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug #3: the red metric I refused to turn green
&lt;/h2&gt;

&lt;p&gt;'ContextualRelevancyMetric' came back 0/3. My instinct was to lower its threshold until it passed. I didn't — because it wasn't wrong.&lt;/p&gt;

&lt;p&gt;At 'top_k = 6' on a question that needs one order, five of the six retrieved chunks are irrelevant. The metric measures signal-to-noise in what you retrieved, and my signal-to-noise was genuinely 1/6. A red 'ContextualRelevancy' next to a green 'Faithfulness' isn't a contradiction — it's a diagnosis: the answer is correct, but the retriever is dragging in junk that costs tokens, latency, and (at higher noise levels) accuracy.&lt;/p&gt;

&lt;p&gt;The honest fixes are architectural, not cosmetic: lower k, add a reranker, or route aggregation queries to a metadata filter (see bug #2). Gaming the threshold would have deleted the one signal telling me my retriever needed a reranker.&lt;/p&gt;

&lt;p&gt;The lesson: not every red metric is a bug in your eval. Some are your eval doing its job. The discipline is telling "this metric is misconfigured" (bug #1) apart from "this metric is reporting a real weakness" (bug #3) — and never moving a threshold just to get a green build.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus: when the built-in metrics can't express what you mean — GEval
&lt;/h2&gt;

&lt;p&gt;The metrics above are general. Real apps have bespoke rules. Mine was: the answer must cite the correct '[order NNNN]' id for every fact it states. No built-in metric knows what "the correct order id" means for my data.&lt;/p&gt;

&lt;p&gt;That's what 'GEval' is for — a custom LLM-as-judge metric built from a natural-language rubric. It's a direct implementation of the &lt;a href="https://arxiv.org/abs/2303.16634" rel="noopener noreferrer"&gt;G-Eval paper&lt;/a&gt;: you describe the criterion, DeepEval turns it into chain-of-thought evaluation steps, and the judge LLM scores 0–1 against a threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GEval&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;

&lt;span class="n"&gt;citation_correctness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CitationCorrectness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decide whether the answer cites the correct [order NNNN] id(s) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;for the facts it states. Penalize missing or wrong citations.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;evaluation_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;            &lt;span class="c1"&gt;# which fields the judge is allowed to read
&lt;/span&gt;        &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTUAL_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETRIEVAL_CONTEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to respect if you use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's an LLM judge, so it has bias, cost, and variance. Pin the judge model, set temperature 0, and prefer explicit 'evaluation_steps' over a one-line 'criteria' when you need run-to-run consistency.&lt;/li&gt;
&lt;li&gt;Validate it against human labels before you trust it as a gate. A custom metric is only as good as its correlation with what you actually care about. And don't reach for an LLM judge where a regex or schema check would be cheaper and exact.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it together as a CI gate
&lt;/h2&gt;

&lt;p&gt;Once the metrics are configured correctly, the gate is boring in the best way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;assert_test&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_orders_pipeline_quality&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_eval_samples_from_pipeline&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_deepeval_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;assert_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_to_test_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire that into a CLI or 'pytest' step and every change to your prompt, chunking, or k gets scored before it ships. My suite currently sits at 18/21 (86%) — and I left the 3 reds visible, because two of them (bug #2's recall, bug #3's relevancy) are telling me exactly what to fix next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five things I'd tell past me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A green suite you don't understand is worse than no suite — it launders bugs as quality.&lt;/li&gt;
&lt;li&gt;'retrieval_context' ≠ 'context'. Evidence is not truth. (Bug #1)&lt;/li&gt;
&lt;li&gt;Faithfulness can't see a retrieval recall miss. Score retrieval and generation as separate stages. (Bug #2)&lt;/li&gt;
&lt;li&gt;Some red metrics are your eval working, not failing. Don't move thresholds to feel good. (Bug #3)&lt;/li&gt;
&lt;li&gt;For bespoke rules, 'GEval' is your escape hatch — but pin the judge and validate it against humans.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building RAG in 2026, the pipeline is the easy 20%. The eval — configured so its numbers actually mean something — is the 80% that decides whether you can ship changes without praying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the eval metric that fooled you the longest? I'll trade you my Hallucination-metric story for yours in the comments&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>genai</category>
      <category>deepeval</category>
    </item>
  </channel>
</rss>
