<?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: michael hurst</title>
    <description>The latest articles on DEV Community by michael hurst (@michael_hurst_c009b1bdeb8).</description>
    <link>https://dev.to/michael_hurst_c009b1bdeb8</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%2F4103266%2F0ef90da7-f276-4450-94eb-3466c443a0ed.jpg</url>
      <title>DEV Community: michael hurst</title>
      <link>https://dev.to/michael_hurst_c009b1bdeb8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michael_hurst_c009b1bdeb8"/>
    <language>en</language>
    <item>
      <title>I audited twelve eval samples from AWS, Google and Azure for one defect. My first check couldn't see it.</title>
      <dc:creator>michael hurst</dc:creator>
      <pubDate>Tue, 01 Sep 2026 17:05:20 +0000</pubDate>
      <link>https://dev.to/michael_hurst_c009b1bdeb8/i-audited-twelve-eval-samples-from-aws-google-and-azure-for-one-defect-my-first-check-couldnt-1laf</link>
      <guid>https://dev.to/michael_hurst_c009b1bdeb8/i-audited-twelve-eval-samples-from-aws-google-and-azure-for-one-defect-my-first-check-couldnt-1laf</guid>
      <description>&lt;p&gt;The defect: an LLM-as-judge that silently defaults to the same model as the thing it grades. Not a design opinion — a default nobody chose, in code that exists to be copied.&lt;/p&gt;

&lt;p&gt;I checked twelve reference implementations. Six findings, six clears, every verdict adversarially verified before filing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;awslabs/Agent-EvalKit&lt;/td&gt;
&lt;td&gt;finding — hand-written judge defaults to the subject's model&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/michael_hurst_c009b1bdeb8/an-aws-labs-agent-eval-sample-uses-the-same-model-as-judge-and-subject-e29"&gt;article&lt;/a&gt;, &lt;a href="https://github.com/awslabs/Agent-EvalKit/issues/4" rel="noopener noreferrer"&gt;#4&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aws-samples/prompt-migration-for-large-language-model-agility&lt;/td&gt;
&lt;td&gt;finding — comparison judged by the winning arm's own model&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/aws-samples/prompt-migration-for-large-language-model-agility/issues/2" rel="noopener noreferrer"&gt;#2&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aws-samples/sample-gen-ai-evaluations-workshop&lt;/td&gt;
&lt;td&gt;finding — 8 of 37 judge-bearing files coupled&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/aws-samples/sample-gen-ai-evaluations-workshop/issues/90" rel="noopener noreferrer"&gt;#90&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;promptfoo/promptfoo&lt;/td&gt;
&lt;td&gt;finding — &lt;code&gt;defaultTest.provider&lt;/code&gt; becomes the grader when none is set&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/promptfoo/promptfoo/issues/10581" rel="noopener noreferrer"&gt;#10581&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK&lt;/td&gt;
&lt;td&gt;finding — one &lt;code&gt;MODEL_NAME&lt;/code&gt; to agent and judge in three examples&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/471" rel="noopener noreferrer"&gt;#471&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure-Samples/aihlsignited-medevals&lt;/td&gt;
&lt;td&gt;finding — pipeline injects the subject's deployment as the judge&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Azure-Samples/aihlsignited-medevals/issues/15" rel="noopener noreferrer"&gt;#15&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure-Samples/azure-search-openai-demo&lt;/td&gt;
&lt;td&gt;clear — dedicated &lt;code&gt;AZURE_OPENAI_EVAL_DEPLOYMENT&lt;/code&gt;, no fallback&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aws-samples/sample-from-prototype-to-production…&lt;/td&gt;
&lt;td&gt;clear — same model, and the README says so&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aws-samples/build-an-automated-…-evaluation-pipeline&lt;/td&gt;
&lt;td&gt;clear&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;microsoft/call-center-ai&lt;/td&gt;
&lt;td&gt;clear — judge is explicitly the &lt;em&gt;fast&lt;/em&gt; model, chat runs the &lt;em&gt;slow&lt;/em&gt; one&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;confident-ai/deepeval, vibrantlabsai/ragas&lt;/td&gt;
&lt;td&gt;clear — libraries, no subject to coincide with&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The check that couldn't see the defect
&lt;/h2&gt;

&lt;p&gt;My first pass on the workshop repo grepped for &lt;code&gt;JUDGE_MODEL_ID&lt;/code&gt;, &lt;code&gt;JUDGE_MODEL&lt;/code&gt;, &lt;code&gt;AGENT_MODEL&lt;/code&gt;. It matched 21 files, every one separated its judge cleanly, and I wrote CLEAR.&lt;/p&gt;

&lt;p&gt;That grep matches the naming convention of code that does it &lt;em&gt;right&lt;/em&gt;. A file that passes one variable to both roles has no &lt;code&gt;JUDGE_&lt;/code&gt; anything to match. &lt;strong&gt;The absence of the convention is the defect's signature, and the probe read it as absence of the defect.&lt;/strong&gt; The anchor passed. Every cited line was accurate. The verdict was wrong.&lt;/p&gt;

&lt;p&gt;So I keyed on model literals instead — and missed the notebooks whose model arrives by &lt;code&gt;from config import MODEL_ID&lt;/code&gt;. Blind the other way.&lt;/p&gt;

&lt;p&gt;What produced a denominator: classify each file by &lt;em&gt;role&lt;/em&gt; — which model generates the thing being evaluated, which model grades it — tracing imports, then send every coupled call to a second agent told to refute it. At &lt;code&gt;4a72984&lt;/code&gt;: 76 files, 37 with a judge, 8 coupled, none of the 8 refuted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification overturned half of what I first wrote
&lt;/h2&gt;

&lt;p&gt;Round two: four audits, all CLEAR on first pass. Two adversarial readers per verdict flipped two of them. Then I went back to round one — three library clears nobody had checked — and six of six votes refuted the readings. Two stayed CLEAR with corrected evidence; promptfoo's became a finding.&lt;/p&gt;

&lt;p&gt;Round three, the reverse: an auditor called &lt;code&gt;microsoft/call-center-ai&lt;/code&gt; a finding. The refuter showed the judge is set explicitly to the fast model, the feature-flag parser falls through so chat runs the slow one, and the audit's "flag value is unknowable" claim was wrong — a bicep file seeds it. Filed as a finding, that would have been a false accusation against a repo with 6,563 stars (measured 2026-09-01).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An unverified CLEAR is unknown. An unverified FINDING is a false accusation that hasn't been published yet.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the clears do
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azure-search-openai-demo&lt;/code&gt; reads its judge from a variable that exists only for grading. &lt;code&gt;sample-from-prototype-to-production&lt;/code&gt; uses the same model for both and says so in its README — the caveat is the whole difference. The libraries default silently to a hardcoded model on your own API key — deepeval's &lt;code&gt;DEFAULT_GPT_MODEL = "gpt-5.4"&lt;/code&gt; (&lt;code&gt;deepeval/models/llms/constants.py:5&lt;/code&gt; at &lt;code&gt;169230e&lt;/code&gt;), ragas's &lt;code&gt;llm_factory("gpt-4o-mini", client=OpenAI())&lt;/code&gt; (&lt;code&gt;src/ragas/evaluation.py:174-179&lt;/code&gt; at &lt;code&gt;298b682&lt;/code&gt;) — both documented; the samples are where that inheritance gets hand-written into one variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own in three lines
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;List every model literal and every env var that resolves to one.&lt;/li&gt;
&lt;li&gt;For each, ask what &lt;em&gt;generates&lt;/em&gt; with it and what &lt;em&gt;grades&lt;/em&gt; with it. Trace imports.&lt;/li&gt;
&lt;li&gt;Any variable that answers both: that's the finding, whether or not the name says &lt;code&gt;JUDGE&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of my own issues carried an overstated clause; a re-read caught it and I posted the correction on #90. Every draft here, this one included, went through an outside-in gate before it went out — never the writer grading itself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>An AWS Labs agent-eval sample uses the same model as judge and subject</title>
      <dc:creator>michael hurst</dc:creator>
      <pubDate>Mon, 31 Aug 2026 19:59:48 +0000</pubDate>
      <link>https://dev.to/michael_hurst_c009b1bdeb8/an-aws-labs-agent-eval-sample-uses-the-same-model-as-judge-and-subject-e29</link>
      <guid>https://dev.to/michael_hurst_c009b1bdeb8/an-aws-labs-agent-eval-sample-uses-the-same-model-as-judge-and-subject-e29</guid>
      <description>&lt;p&gt;&lt;code&gt;awslabs/Agent-EvalKit&lt;/code&gt; is a sample kit for evaluating AI agents. Its QA example defines the DeepEval judge in &lt;code&gt;examples/qa_agent_evaluation/eval/metrics.py&lt;/code&gt;:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BedrockLLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeepEvalBaseLLM&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Custom DeepEval LLM using LiteLLM to call Bedrock.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both metric factories instantiate it with no argument:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_faithfulness_metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bedrock_llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockLLM&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;FaithfulnessMetric&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="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock_llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include_reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent under evaluation runs &lt;code&gt;us.anthropic.claude-sonnet-4-20250514-v1:0&lt;/code&gt; — that is &lt;code&gt;gen_ai.request.model&lt;/code&gt; in the example's own &lt;code&gt;eval/otel-traces.jsonl&lt;/code&gt;, and it is the only model id that appears anywhere in that trace file.&lt;/p&gt;

&lt;p&gt;So the judge and the subject are the same model. (Not the same string: the judge carries LiteLLM's &lt;code&gt;bedrock/&lt;/code&gt; route prefix. Same model, one hop of indirection.) The bundled report gives the agent a faithfulness score of 78.2%, and the thing measuring faithfulness is the thing whose faithfulness is being measured.&lt;/p&gt;

&lt;p&gt;Check it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://api.github.com/repos/awslabs/Agent-EvalKit/tarball/main | &lt;span class="nb"&gt;tar &lt;/span&gt;xz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;awslabs-Agent-EvalKit-&lt;span class="k"&gt;*&lt;/span&gt;/examples/qa_agent_evaluation/eval &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"judge:"&lt;/span&gt;   &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'bedrock/us\.anthropic\.[a-z0-9.:-]*'&lt;/span&gt; metrics.py | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"subject:"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'us\.anthropic\.[a-z0-9.:-]*'&lt;/span&gt; otel-traces.jsonl | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"caveats:"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rniE&lt;/span&gt; &lt;span class="s1"&gt;'same model|self-grad|bias|independent|different model|cross-model|judge model'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       ../../../ &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.md &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.py &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"^ nothing under 'caveats:' = undisclosed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed at &lt;code&gt;472fbc6&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth arguing about
&lt;/h2&gt;

&lt;p&gt;Not "AWS shipped something bad." It is a Labs sample — 38 stars at the time of writing, last pushed in March 2026 — and using one Bedrock model for both roles is defensible on cost, latency and dependency count. If someone had written that tradeoff down I would have no post.&lt;/p&gt;

&lt;p&gt;The point is that nobody chose it. It is a kwarg default on a helper class. There is no config surface for the judge model, no line in the README noting that the judge defaults to whatever the agent runs on, and — per that last grep — no mention anywhere in the repo of judge independence, model bias, or cross-model evaluation. The most consequential decision in an evaluation harness was never surfaced as a decision at all.&lt;/p&gt;

&lt;p&gt;That is the failure mode I think is worth naming. Self-grading almost never arrives as a claim you can disagree with. It arrives as a default, three call frames down, in a constructor nobody reads.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;examples/&lt;/code&gt; is precisely where defaults go to breed. Sample directories exist to be copied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I am standing
&lt;/h2&gt;

&lt;p&gt;I build an independent validation gate, so treat this as an interested party pointing at a thing that flatters him, and go run the command.&lt;/p&gt;

&lt;p&gt;And the number that matters here is the unflattering one, so: on a 113-example internal holdout, our hosted API flagged 19.5% of the clean items — "clean" meaning an item our adjudicated labels recorded as containing no defect, so a flag on one is the gate being wrong, not the item. Banked 2026-08-10. That is our data on our holdout; yours will differ. A separate judge is not a correct judge. It is only a judge that is not marking its own work.&lt;/p&gt;

&lt;p&gt;I also filed an unrelated bug in the same file — a debug &lt;code&gt;exit()&lt;/code&gt; that stops the example running — as &lt;a href="https://github.com/awslabs/Agent-EvalKit/issues/4" rel="noopener noreferrer"&gt;issue #4&lt;/a&gt;, with a fix in &lt;a href="https://github.com/awslabs/Agent-EvalKit/pull/5" rel="noopener noreferrer"&gt;PR #5&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
