{
  "description": "Browse aymeric's, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.",
  "feed_url": "https://aggr.aymericbeaumet.com/sources/hamel.dev/feed.json",
  "home_page_url": "https://aggr.aymericbeaumet.com/sources/hamel.dev/",
  "items": [
    {
      "authors": [
        {
          "name": "Hamel Husain"
        },
        {
          "name": "Antaripa Saha"
        }
      ],
      "content_html": "<p>Decision models have exploded onto the scene recently, starting with TypeSafe’s Jev, followed by Perplexity’s Decider, Liquid’s d1, Cloudflare’s Clef, and OpenAI’s Decisions API<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>. These models promise fast, inexpensive classification.</p>\n<p>Decision models are an appealing low-cost alternative to LLM judges<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup> for automated evals. But whether they’re the right choice depends on the cost, performance, and latency requirements of your use case.</p>\n<p>We’ve also noticed people using the default decision threshold of 0.50 without checking whether it works well for their task. This can leave a lot of performance on the table.</p>\n<p>In this post, we’ll start by showing how to tune thresholds to get the best results from decision models. With those thresholds tuned, we’ll compare cost and performance to help you assess which model fits your use case. Finally, we’ll explore how to interpret model scores when deciding whether to act, and how calibration can make them more useful as confidence estimates.</p>\n<h2>What we tested</h2>\n<p>We used 100 traces, or logs of conversations and actions, from an apartment leasing agent. The task is to identify traces containing an error, such as a failed booking or a missed handoff to a human.<sup class=\"footnote-ref\"><a href=\"#fn-3\" id=\"fnref-3\" data-footnote-ref>3</a></sup> We used the same dataset and broad failure-finding task in our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/blog/posts/auto-evals/\">prior post on automated evals</a>. Here, each decision model returns a failure probability for each trace.</p>\n<p>We tested Jev 1.13, Perplexity Decider 1.1 27B, Liquid d1 and d1-ultra, Cloudflare Clef Flash, and OpenAI Decisions (GPT-6 Luna)<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1-2\" data-footnote-ref>1</a></sup>. Each model received the same evaluation criteria. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/decision-model-evals/#evaluation-prompt\">appendix</a> shows the prompt and the structured version used for Jev.</p>\n<p>We compare models on two dimensions:</p>\n<ul>\n<li><strong>Performance:</strong> We compare each model’s predictions with traces that people have reviewed and labeled as passing or failing. This measures how well the model catches failures while avoiding false alarms.</li>\n<li><strong>Cost:</strong> We calculate API cost from token usage and the provider’s prices.<sup class=\"footnote-ref\"><a href=\"#fn-4\" id=\"fnref-4\" data-footnote-ref>4</a></sup></li>\n</ul>\n<p>For brevity, we leave latency aside because it depends on factors outside this experiment’s control.<sup class=\"footnote-ref\"><a href=\"#fn-5\" id=\"fnref-5\" data-footnote-ref>5</a></sup></p>\n<h2>Understanding decision thresholds</h2>\n<p>We assign each trace one of two labels, pass or fail. This is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">binary pass/fail eval</a>, which we recommend because it encourages clear criteria and is easier to align with human preferences.</p>\n<p>Each decision model returns a score from 0 to 1 that estimates how likely a trace is to contain a failure. The APIs turn this score into a decision using a <strong>default decision threshold of 0.50</strong>. Traces with scores at or above the threshold are flagged as failures. Using this default without checking it against your data can give you subpar results.</p>\n<p>For example, a model could assign every failure a score of 0.40 and every passing trace a score of 0.10. Even though this model classifies traces perfectly at a threshold of 0.30, the default threshold of 0.50 would catch nothing.</p>\n<p>In practice, scores for failures and passing traces will overlap. Lowering the threshold catches more failures but can also raise more false alarms. Choosing a threshold means balancing:</p>\n<ul>\n<li><strong>Precision:</strong> The share of flagged traces that contain a known failure.</li>\n<li><strong>Recall:</strong> The share of known failures that the model catches.</li>\n</ul>\n<p>The right tradeoff depends on your business goals. For example, criminal trials set a high bar for conviction to avoid convicting innocent people, emphasizing precision. Cancer screening may emphasize recall, accepting more false alarms to miss fewer cases.<sup class=\"footnote-ref\"><a href=\"#fn-6\" id=\"fnref-6\" data-footnote-ref>6</a></sup> For this comparison, we’ll give precision and recall equal weight using the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html\">F1 score</a>, their harmonic mean. F1 ranges from 0 to 1, and higher is better.</p>\n<p>The curves below show how each model scores failures (orange) and passing traces (blue). Taller parts of a curve mean more scores from that class fall in that range. Scores at or above the dashed threshold line are flagged. Use the toggle to compare <strong>0.50</strong> with <strong>Optimized for best F1</strong>, which chooses a threshold for each model.<sup class=\"footnote-ref\"><a href=\"#fn-7\" id=\"fnref-7\" data-footnote-ref>7</a></sup></p>\n<p>OpenAI separates the two classes best on this task, which is also reflected in its higher F1 score.</p>\n<p>It’s also important not to blindly maximize F1 without thinking about your minimum requirements for the classifier to be useful. For example, if this classifier is used for an automated alerting system, I would want a minimum of 80% on both precision and recall so that I’m minimizing false alarms while catching a sufficient number of failures.</p>\n<p>The chart below shows how precision and recall change at different thresholds.<sup class=\"footnote-ref\"><a href=\"#fn-8\" id=\"fnref-8\" data-footnote-ref>8</a></sup> The shaded region marks where both reach at least 80%.</p>\n<p>Perplexity, Liquid d1-ultra, and OpenAI Decisions meet our requirements, but we still need to choose a decision threshold within the shaded region for each model. In the next section, we’ll discuss how to tune those thresholds.</p>\n<h3>Tuning the decision threshold</h3>\n<p>For this example, we’ll optimize F1. For our candidates, we’ll restrict tuning to thresholds that meet both 80% minimums on the training data whenever possible. We leave the other models unconstrained to illustrate the effect of tuning.<sup class=\"footnote-ref\"><a href=\"#fn-9\" id=\"fnref-9\" data-footnote-ref>9</a></sup></p>\n<p>With labeled traces and saved model scores, you can ask a coding agent to tune the thresholds. Below is an example prompt you can modify for your use case:</p>\n<blockquote>\n<p>Using my saved model scores and binary labels (1 means failure), tune a decision threshold to maximize F1 while requiring at least 80% precision and 80% recall. Use stratified five-fold cross-validation, choosing thresholds on the training examples and measuring performance on the remaining examples. If comparing models, use the same splits for each. Only select thresholds that meet both minimums on the training examples. If none qualifies, report that rather than relaxing either requirement. Report validation precision, recall, and F1, along with any splits where no threshold qualified or validation performance fell below my minimums. Compare with the default threshold of 0.50. Reuse the saved scores without making new model calls.</p>\n</blockquote>\n<p>Here’s how tuning for F1 changed each model’s performance. We group the three candidates from the precision-recall chart together and show the other models in gray to illustrate how much tuning can help.</p>\n<p>Our candidate models gained less from F1 tuning than Liquid d1 and Clef Flash, which fell below our minimums. However, even a small shift in precision and recall can affect business costs or the user experience, so choose the tradeoff deliberately. In practice, it’s unlikely that precision and recall are equally important (as we assumed in this post with F1). For example, the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://scikit-learn.org/stable/modules/model_evaluation.html#precision-recall-f-measure-metrics\">F-beta score</a> lets you give precision more weight with beta below 1, or recall more weight with beta above 1. Optimizing a different metric could lead to different thresholds and results.</p>\n<h2>Performance versus cost</h2>\n<p>The chart below compares each model’s F1 (after tuning) against cost, focused on our remaining candidates:</p>\n<p>The dashed line shows the <strong>Pareto frontier</strong> among our remaining candidates. For any given F1 score, it identifies the cheapest candidate that meets or exceeds that score. This particular chart is a bit silly as there are only three options, but it’s a useful illustration for how to think about cost vs. performance.</p>\n<p>Perplexity was the cheapest of these candidates at 0.82 cents per 100 eval runs, with an F1 of 0.79. OpenAI Decisions reached an F1 of 0.87 at 3.72 cents per 100 eval runs, about 4.5 times the cost.</p>\n<h2>Can we trust the probabilities?</h2>\n<p>Beyond pass/fail evals, you may want to use a decision model’s score as a confidence estimate. But can you take that number literally? For a score of 0.80 to mean an 80% chance of failure, roughly 80% of traces with similar scores should contain failures.</p>\n<p>The chart below shows each model’s <strong>probability calibration</strong>: how closely its predicted probabilities match observed outcomes.<sup class=\"footnote-ref\"><a href=\"#fn-10\" id=\"fnref-10\" data-footnote-ref>10</a></sup> Each point represents about 20 traces with similar scores. A point on the diagonal means the predicted probability matches the failure rate in that group. Above the line, the model underestimates the risk of failure. Below it, the model overestimates that risk.</p>\n<p>If the predicted probabilities don’t match the observed outcomes, we can adjust them using another model. The second panel shows one method, <strong>Platt scaling</strong>, which fits a simple curve to make that adjustment.<sup class=\"footnote-ref\"><a href=\"#fn-11\" id=\"fnref-11\" data-footnote-ref>11</a></sup></p>\n<p>Perplexity, Liquid d1, and Clef Flash move closer to the diagonal after calibration. For example, one group of Clef Flash traces had an average predicted failure probability of about 8%, but 60% contained failures.</p>\n<p>Jev and Liquid d1-ultra were already well calibrated on this dataset and needed little adjustment.</p>\n<p>Platt scaling isn’t always the best fit. For example, OpenAI’s probabilities got slightly worse with the version of Platt scaling we used.<sup class=\"footnote-ref\"><a href=\"#fn-12\" id=\"fnref-12\" data-footnote-ref>12</a></sup> You can direct your coding agent to perform calibration with the following prompt:</p>\n<blockquote>\n<p>Using my saved failure probabilities and binary labels (1 means failure), test whether Platt scaling improves each model’s probabilities. Use stratified five-fold cross-validation with the same splits for every model. Fit the calibration mapping on each training split and apply it to the remaining traces. Fit on log-odds, clipping probabilities away from 0 and 1 first. Plot predicted probabilities against observed failure rates before and after calibration. Report Brier scores (lower is better), and explain which models improved or got worse. Reuse the saved scores without making new model calls.</p>\n</blockquote>\n<h2>Prompt tuning and evaluator scope</h2>\n<p>We kept the eval task in this post broad to make the differences between models easier to see. In practice, we recommend building <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-i-do-when-i-cant-get-my-llm-judge-to-agree-with-human-reviewers.html\">a separate judge for each failure mode</a>. With a narrower task and more specific guidance, we expect the performance gaps between models to shrink.</p>\n<p>I like to start with a larger model that has acceptable performance on a task and then try smaller models to see if they can match the larger model’s performance for less cost. Prompt tuning can also be helpful if you have high-quality labels.<sup class=\"footnote-ref\"><a href=\"#fn-13\" id=\"fnref-13\" data-footnote-ref>13</a></sup> I discuss how to craft a good judge prompt <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">in this post</a>.</p>\n<h2>Putting these ideas into practice</h2>\n<p>For this particular dataset, OpenAI had breakout performance at a higher cost. Perplexity and Liquid d1-ultra offer lower-cost options with lower performance. We have observed that model performance varies substantially across tasks and datasets, which means it’s important to measure things yourself.</p>\n<p>To recap:</p>\n<ul>\n<li>Once you’re satisfied with your <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">judge’s prompt</a>, set minimum precision and recall requirements for your use case. Rule out models that cannot meet these.</li>\n<li>Tune each model’s decision threshold before comparing performance. Choose a precision-recall tradeoff that fits your business goals. Be intentional, don’t just use F1 score because this post did!</li>\n<li>If you want to use scores as confidence estimates, check whether the probabilities match observed outcomes. Calibration can help, but you need to measure whether it improves them.</li>\n<li>Weigh performance against cost and latency for your workload.</li>\n</ul>\n<p>This process is iterative. You may improve the prompt, then need to tune the threshold again or revisit calibration. In this post, we focused on threshold tuning and calibration because we’ve seen many people use decision models off the shelf without considering these factors.</p>\n<p>You might also be wondering how decision models stack up to LLMs. OpenAI Decisions uses GPT-6 Luna, which delivered our highest F1 at the highest cost, suggesting that harder eval tasks may justify paying more for an LLM. In a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.randalolson.com/2026/10/14/when-to-use-a-decision-model-instead-of-an-llm/\">broader comparison</a>, Randy Olson found that decision models could match LLMs on classification and routing at lower cost, while LLMs pulled ahead on tasks requiring knowledge and reasoning.</p>\n<p>None of the ideas discussed in this post are new. Choosing a precision-recall tradeoff, tuning thresholds and comparing performance vs. cost are part of evaluating any classifier. To learn more about these fundamentals, I recommend <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.oreilly.com/library/view/machine-learning-with/9781098135713/\">Machine Learning with Python Cookbook, 2nd Edition</a> by Kyle Gallatin and Chris Albon.</p>\n<p>Finally, if you’re a data scientist or ML engineer wondering where you belong in the AI age, these are skills you already have. It’s time to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/revenge/\">get revenge</a> 🙂.</p>\n<h2>Appendix: Evaluation prompt</h2>\n<p>Perplexity, Liquid d1 and d1-ultra, Clef Flash, and OpenAI Decisions received the prompt below. Jev received equivalent criteria in a structured prompt, shown in the example below. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/decision-model-evals/charts/chart-data.json\">scores and labels</a> used in the charts are available as JSON.</p>\n<pre><code><span class=\"code-snippet\">Decide whether `messages` contains at least one substantive failure, risk, or quality problem in the AI assistant&#39;s behavior or tool use. This is one complete production trace from an AI that handles apartment-leasing customer support over phone or messaging. The trace includes the conversation plus any tool calls and tool outputs.\n\nEvaluate the AI assistant and its tool use, not the customer. Treat system messages inside `messages` as the instructions governing the assistant&#39;s behavior and allowed capabilities. Inspect the full trace, including final turns and all tool calls and outputs. Base the judgment only on evidence visible in this trace. Do not assume external policies, channel behavior, or business requirements that are not present. Do not assume every trace failed. A short, ambiguous, or inconclusive trace is not an issue unless visible evidence establishes a substantive failure.\n\nReturn the probability that at least one substantive issue is present; the issue does not need to be named or clustered.\n\nCount as an issue when supported by the trace: the assistant contradicts an applicable system instruction or tool result; makes an unsupported factual or operational claim; misses, misuses, repeats, or falsely claims completion of a required tool action, transfer, booking, cancellation, follow-up, or confirmation; materially misunderstands or leaves an explicit user request unresolved; or exposes internal actions, placeholders, or broken content to the customer in a way that materially harms the interaction.\n\nDo not count as an issue: a minor stylistic preference with no meaningful effect; a failure caused only by the customer&#39;s behavior; internal tool metadata that was not exposed to the customer and did not cause an incorrect response; a hypothetical concern requiring information outside the trace; or insufficient evidence and inconclusive conversations.\n</span></code></pre>\n<p>NoteExample: A structured prompt</p>\n<p>This is how we structured the prompt for Jev’s requests, with separate fields for instructions and evaluation criteria.</p>\n<pre><code><span class=\"code-snippet\" data-language=\"JSON\"><span class=\"syntax-source syntax-json\"><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n  <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>version<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>nurtureboss-any-issue-v1<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n  <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>question_id<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>has_substantive_issue<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n  <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>type<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>noul<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n  <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>instructions<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n    <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>task<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Does `messages` contain at least one substantive failure, risk, or quality problem in the AI assistant&#39;s behavior or tool use?<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n    <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>context<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>This is one production trace from an AI that handles apartment-leasing customer support over phone or messaging. The trace contains the complete conversation plus any tool calls and tool outputs.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n    <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>evaluation_rules<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Evaluate the AI assistant and its tool use, not the customer.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Treat system messages inside `messages` as the instructions governing the assistant&#39;s behavior and allowed capabilities.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Inspect the full trace, including the final turns and all tool calls and outputs.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Base the judgment only on evidence visible in this trace. Do not assume external policies, channel behavior, or business requirements that are not present.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Do not assume every trace failed. A short, ambiguous, or inconclusive trace is not an issue unless the visible evidence establishes a substantive failure.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n      <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Return the probability that at least one substantive issue is present. The issue does not need to be named or clustered.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n    <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span>\n  </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n  <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>criteria<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n    <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>true<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n      <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>definition<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>At least one substantive problem is supported by evidence in the trace.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n      <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>examples_of_qualifying_evidence<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>The assistant contradicts an applicable system instruction or a tool result.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>The assistant makes an unsupported factual or operational claim.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>A required tool action, transfer, booking, cancellation, follow-up, or confirmation is missing, incorrect, repeated, or falsely claimed as complete.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>The assistant materially misunderstands or leaves an explicit user request unresolved.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Customer-facing text exposes internal actions, placeholders, or broken content that materially harms the interaction.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n      <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span>\n    </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n    <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>false<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n      <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>definition<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>No substantive assistant problem can be established from the visible trace.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n      <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>do_not_count_as_an_issue<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>A minor stylistic preference with no meaningful effect.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>A failure caused only by the customer&#39;s behavior.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Internal tool metadata that was not exposed to the customer and did not cause an incorrect response.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>A hypothetical concern that requires information outside the trace.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n        <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Insufficient evidence or an inconclusive conversation.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n      <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span>\n    </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n  </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n</span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n</span></span></code></pre>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Versions tested: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.typesafe.ai/introduction\">Jev 1.13</a> (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://typesafe.ai/blog/introducing-system-one-models-and-jev\">announced September 15, 2026</a>), <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.perplexity.ai/docs/decisions/quickstart\">Perplexity Decider 1.1 27B</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.liquid.ai/lfm/models/decision-models\">Liquid d1 and d1-ultra</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.cloudflare.com/clef-decision-models\">Cloudflare Clef Flash</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://developers.openai.com/api/docs/guides/decisions\">OpenAI Decisions</a>. OpenAI Decisions uses GPT-6 Luna. The API returns typed answers, including a probability from 0 to 1 for a true/false question. This is a separate API from Structured Outputs, which generates responses matching a custom JSON schema. We include it because it serves the same classification task. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a> <a href=\"#fnref-1-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1-2\" aria-label=\"Back to reference 1-2\">↩<sup class=\"footnote-ref\">2</sup></a></p>\n</li>\n<li id=\"fn-2\">\n<p>An <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">LLM judge</a> uses a prompt to evaluate an AI system’s output or behavior. We recommend asking it for a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">binary pass/fail decision</a> about a specific failure mode. This encourages clear criteria and makes it easier to compare the judge’s decisions with human labels. <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n<li id=\"fn-3\">\n<p>We scored 98 production traces after excluding two internal QA examples from the 100-trace dataset. The frozen reference contains 61 known failures and 37 traces without a labeled failure: 39 failures from the original human review plus 22 discoveries from Braintrust and Arize that we manually validated. Models received masked traces without human labels or reviewer notes. Perplexity, Liquid, Clef Flash, and OpenAI received the same instructions and serialized inputs; Jev received equivalent criteria through its structured API. The original conversations are private. <a href=\"#fnref-3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n</li>\n<li id=\"fn-4\">\n<p>API cost covers 100 eval runs, one per trace. We use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.liquid.ai/pricing\">Liquid’s prices as of October 17, 2026</a>, $0.02 per million input tokens for d1 and $0.042 for d1-ultra. The experiment recorded d1 at $0.04; we have repriced its saved token usage. Other models use the rates in the experiment’s operational metrics. Performance excludes two internal QA traces. Threshold tuning uses saved scores and requires no additional model calls. <a href=\"#fnref-4\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"4\" aria-label=\"Back to reference 4\">↩</a></p>\n</li>\n<li id=\"fn-5\">\n<p>Latency depends on how you batch or parallelize requests, your arrangement with the vendor, and your distance from its infrastructure, among other factors. Batching and concurrency can also change cost and throughput. See TypeSafe’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.typesafe.ai/primitives#ask-multiple-questions-together\">multi-question requests</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.typesafe.ai/sdk/python/usage/advanced\">client performance guidance</a>. Its <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://typesafe.ai/blog/introducing-system-one-models-and-jev\">launch discussion</a> also notes the location of its latency tests relative to its service. Mean response times were 4.33 seconds for Jev, 7.85 for Perplexity, 7.38 for Liquid d1, 1.56 for Liquid d1-ultra, 2.09 for Clef Flash, and 2.77 for OpenAI. These came from separate runs and do not establish comparative throughput or tail latency. <a href=\"#fnref-5\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"5\" aria-label=\"Back to reference 5\">↩</a></p>\n</li>\n<li id=\"fn-6\">\n<p>These examples illustrate how the consequences of mistakes affect the tradeoff. In U.S. criminal trials, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.law.cornell.edu/wex/reasonable_doubt\">proof beyond a reasonable doubt</a> sets a high bar for conviction. In cancer screening, a missed case can delay care, while a false alarm can lead to anxiety and unnecessary procedures. A positive screening result usually requires further testing. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.cancer.gov/about-cancer/screening/patient-screening-overview-pdq\">NCI explains both risks</a>; the appropriate balance depends on the screening test and population. <a href=\"#fnref-6\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"6\" aria-label=\"Back to reference 6\">↩</a></p>\n</li>\n<li id=\"fn-7\">\n<p>The density plots show unconstrained F1 tuning, before we introduce minimum precision and recall requirements. The later comparison charts apply those requirements to candidate models where feasible. All use the same five-fold cross-validation splits. Thresholds were chosen on training traces, and precision, recall, and F1 were measured on separate traces. We average those measurements across repeated splits. In the optimized density plots, the dashed line shows the average selected threshold. The displayed scores come from the individual splits, rather than applying that average threshold to the full dataset. The density curves use Gaussian smoothing with bandwidth 0.07 and reflection at 0 and 1. Each class has an area of one. Smoothing affects only the display. <a href=\"#fnref-7\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"7\" aria-label=\"Back to reference 7\">↩</a></p>\n</li>\n<li id=\"fn-8\">\n<p>A precision-recall curve plots precision against recall as the decision threshold changes. It shows the tradeoffs available for a model across thresholds. Scikit-learn’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html\">precision-recall guide</a> explains how to read and plot these curves. <a href=\"#fnref-8\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"8\" aria-label=\"Back to reference 8\">↩</a></p>\n</li>\n<li id=\"fn-9\">\n<p>We identify candidates from the full-sample precision-recall curves. In each training split, we choose the highest-F1 threshold among those reaching at least 80% precision and 80% recall. When no threshold qualifies, we use the unconstrained F1 optimum and record the split as infeasible. This occurred in 36 of 100 training splits for Perplexity and 29 for Liquid d1-ultra, and never for OpenAI. Non-candidates always use unconstrained F1 tuning. We keep all validation predictions, including those from fallback splits, in the reported averages. The minimums apply during selection and do not guarantee the same performance on new examples. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/decision-model-evals/charts/threshold-selection-audit.json\">threshold-selection audit</a> records each split without trace IDs. <a href=\"#fnref-9\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"9\" aria-label=\"Back to reference 9\">↩</a></p>\n</li>\n<li id=\"fn-10\">\n<p>A calibrated failure probability of 0.80 means that, among many examples receiving scores near 0.80, roughly 80% contain failures. Platt scaling and isotonic regression use labeled examples to learn an adjustment from model scores to probabilities. Platt scaling fits a sigmoid curve. Isotonic regression fits a more flexible mapping that preserves score order, but is more prone to overfitting with small datasets. Neither guarantees improvement on new examples. Scikit-learn’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://scikit-learn.org/stable/modules/calibration.html\">probability calibration guide</a> explains both methods and how to assess them. <a href=\"#fnref-10\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"10\" aria-label=\"Back to reference 10\">↩</a></p>\n</li>\n<li id=\"fn-11\">\n<p>The reliability chart groups each model’s scores into five groups of 19 or 20 traces, sorted by probability. The adjusted scores are each trace’s mean out-of-fold probability from the saved cross-validation results. We fit Platt scaling to the log-odds of the original probabilities. Each mapping is fitted without the traces it predicts. The chart summarizes this small sample and does not establish an exact failure rate for a future trace. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/decision-model-evals/charts/calibration-reliability-data.json\">plotted calibration results</a> are available as JSON. <a href=\"#fnref-11\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"11\" aria-label=\"Back to reference 11\">↩</a></p>\n</li>\n<li id=\"fn-12\">\n<p>On the saved out-of-fold probability summaries, Jev’s Brier score was 0.187 before adjustment and 0.188 afterward. Liquid d1-ultra’s was 0.173 and 0.172, while OpenAI’s was 0.159 and 0.164. Brier score measures average squared probability error, so lower is better. It reflects discrimination as well as calibration. These results describe the log-odds Platt fit shown in the chart. An exploratory refit directly on probabilities, using the same folds, gave OpenAI a Brier score of 0.154. This illustrates sensitivity to the fitting method. It does not establish which method will work best on new data. <a href=\"#fnref-12\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"12\" aria-label=\"Back to reference 12\">↩</a></p>\n</li>\n<li id=\"fn-13\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2507.19457\">GEPA</a> uses feedback from model runs to propose prompt revisions and test them against labeled examples. It retains candidates with different strengths, so a prompt that helps a difficult example can remain in consideration. Incorrect labels can steer this search toward the wrong changes. Keep a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">separate test set</a> outside prompt development for the final check. In our small manual Jev experiment, F1 at the default threshold rose from 0.78 with a simple prompt to 0.80 with a checklist of failure types. That experiment did not use GEPA or establish how much systematic optimization would help. Our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-model-or-llm-should-i-use-to-build-automated-evals.html\">FAQ on choosing an eval model</a> discusses prompt optimization and model comparison. <a href=\"#fnref-13\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"13\" aria-label=\"Back to reference 13\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-10-20T07:00:00+00:00",
      "date_published": "2026-10-20T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/decision-model-evals/",
      "id": "urn:aggr:item:8d5a5853b6b71aed491681ed542ffa8970311246",
      "image": "https://hamel.dev/blog/posts/decision-model-evals/images/cover.png",
      "summary": "Decision models have exploded onto the scene recently, starting with TypeSafe’s Jev, followed by Perplexity’s Decider, Liquid’s d1, Cloudflare’s Clef, and OpenAI’s Decisions API1. These models promise fast, inexpensive classification.…",
      "tags": [
        "programming",
        "evals",
        "llms"
      ],
      "title": "How To Use Decision Models for Evals",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-10-20-how-to-use-decision-models-for-evals/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Anthropic released <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://claude.dev/blog/automating-eval-design-and-hillclimbing/\">new eval tooling for Claude Code</a>. Their <code>claude-api</code> plugin now includes a new <code>build_eval</code> and <code>hill-climb</code> command that helps you build evals, check the graders, and improve your application against them.</p>\n<p>I usually don’t review eval tools. Software changes so often that a review has a short shelf life. But a first-party tool from Anthropic is likely to influence how people approach evals, so I wanted to try it.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://isaacflath.com/\">Isaac Flath</a> and I <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/i/broadcasts/1nKOLQOwQEEGR\">livestreamed ourselves using it</a> on conversation traces from an apartment leasing assistant. Here’s what we found:</p>\n<h2>The bad</h2>\n<h3>1. It pushes you to create an eval before looking at data</h3>\n<p>Claude started by suggesting several potential failures, then asked us to pick one straight away to turn into an eval. It gave us the below menu of options, with call-transfer rules as the recommended choice. We hadn’t yet reviewed the conversations ourselves, so it was hard to know if this was a real failure or worth prioritizing. Despite this, we went ahead with the recommended choice, because we figured that’s what typical users would do.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/claude-auto-evals/images/choose-eval-annotated.png\" alt=\"A red box highlights the recommended call-transfer evaluator. An arrow points to it beneath the question: How are we supposed to know this is a good eval to invest in at this point?\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>I believe you should be looking at data first to inform your understanding and prioritize which evals to write. An agent can help you find issues, but you still should do <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a> to decide which failures deserve attention before proceeding.</p>\n<h3>2. It asks you to validate judgments without enough context</h3>\n<p>Next, Claude created Markdown files for looking at data associated with the call-transfer failure. In the screenshot below, Claude asks us to skim <code>inputs.md</code> and “tell it” which labels are wrong. That meant reading long conversations in an editor and reporting corrections separately in a chat.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/claude-auto-evals/images/markdown-review-annotated-v3.png\" alt=\"Red text reads: This is the most painful way you can think of to label data. Create a web app instead! A question mark, arrow, and underline highlight the full sentence Please skim inputs.md and tell me in Claude&#39;s request to review labels.\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>We found this very silly as we were using a coding agent, so it should have built an annotation app that made the conversations easy to read and let us leave feedback in-situ. We eventually asked Claude to build a web app for us and used that instead.</p>\n<p>Later in the workflow, Claude made an initial attempt at creating an evaluator for the call-transfer failure. It presented aggregate label counts and asked us, “Would you have scored any case differently?” without giving us enough information to know if the labels were correct. A recurring theme of the workflow was to jump too fast into creating artifacts or asking us for approval without helping us understand the data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/claude-auto-evals/images/score-review-question-annotated-v3.png\" alt=\"Red text reads: There is no way to know the answer to this without seeing the data. You need to label this in-situ, not in a separate chat. A question mark, arrow, and underline highlight Claude asking: Would you have scored any case differently?\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h3>3. The evaluator’s scope was too broad</h3>\n<p>Next, the tool created a call-transfer evaluator that checked four different failures at once:</p>\n<ul>\n<li>Asking for confirmation more than once, or transferring without asking for confirmation. (LLM as a Judge)</li>\n<li>Saying something between the caller’s consent and the transfer. (Code-based eval)</li>\n<li>Saying something during or after the transfer. (Code-based eval)</li>\n<li>Saying tool mechanics aloud, such as “triggering” a transfer. (Code-based eval)</li>\n</ul>\n<p>There were too many things bundled into this evaluator. I would prefer to scope the eval to focus on one error at a time, or at the very least separate the evals into those that needed a code-based eval vs a LLM as a Judge.</p>\n<p>Claude’s description of the evaluator was also confusing:</p>\n<blockquote>\n<p>protocol_ok is the headline. A case passes only if all four checks pass. On “should not transfer” calls, protocol_ok is 1 if no transfer happened.</p>\n</blockquote>\n<p>This AI slop is hard to read. I’d much rather see the code or the judge prompt so I can understand whats being created. I’ve found that it <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/prompt/\">always pays to read the prompt</a>, especially for something as important as an eval. Below is a screenshot of what this part of the workflow looked like:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/claude-auto-evals/images/bundled-grader.png\" alt=\"Claude defines protocol_ok as a single pass/fail result requiring four checks: confirm once, transfer immediately, remain silent, and avoid saying tool mechanics aloud.\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>The good</h2>\n<p>I was impressed by this plugin’s out-of-the-box ability to discover issues that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/blog/posts/auto-evals.html\">other auto-eval approaches</a> haven’t been able to find! It found issues with human handoff, formatting, voice agents, and more. It’s still better to look at your data iteratively with an agent, but this was the strongest performance I’ve seen with a more “one-shot” issue discovery approach.</p>\n<p>Anthropic’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://claude.dev/blog/automating-eval-design-and-hillclimbing/\">blog post introducing this tool</a> broadly conveys thinking that I agree with, such as the importance of looking at data, sampling intelligently, not saturating your own evals, etc. I’m really happy more people are thinking about evals this way.</p>\n<h2>Would I use it?</h2>\n<p>I’d hold off for now. I’d want the workflow to help me explore the data before committing to an evaluator, with a better review interface from the start. Additionally, I’m already quite happy with what coding agents can do using these <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-skills/\">Eval skills Shreya and I put together</a> which is less opinionated (but more flexible).</p>\n<p>I’ve since spoken with the author of the Claude eval plugin. He was appreciative of the feedback and said he’d update the plugin accordingly, so I expect it to change soon. It could be worth revisiting in the future.</p>\n<p>Even as this plugin changes, I hope this walkthrough helps you assess other eval tools. Make sure the tool helps you understand your data before choosing evals and inspect its judgments carefully. I believe much of the eval workflow should happen in a web application rather than chat to remove friction from data exploration and annotation.</p>\n<p>Remember, if an eval tool doesn’t put looking at data at the center of your workflow, it’s not worth using.</p>\n<p><em>Thanks to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://isaacflath.com/\">Isaac Flath</a> for reviewing this article and joining me for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/i/broadcasts/1nKOLQOwQEEGR\">the livestream</a>.</em></p>\n",
      "date_modified": "2026-09-30T07:00:00+00:00",
      "date_published": "2026-09-30T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/claude-auto-evals/",
      "id": "urn:aggr:item:e62d33ebcaf86175438d343218786d6d06766807",
      "summary": "Anthropic released new eval tooling for Claude Code. Their claude-api plugin now includes a new build_eval and hill-climb command that helps you build evals, check the graders, and improve your application against them. I usually don’t…",
      "tags": [
        "programming",
        "ai",
        "evals"
      ],
      "title": "Claude’s new auto eval tool",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-09-30-claude-s-new-auto-eval-tool/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        },
        {
          "name": "Shreya Shankar"
        }
      ],
      "content_html": "<p>This document curates the most common questions Shreya and I received while <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">teaching</a> 5,000+ engineers and PMs AI Evals. <em>Warning: These are sharp opinions about what works in most cases. They are not universal truths. Use your judgment.</em></p>\n<h2>How to use this FAQ</h2>\n<p>Browse the questions that interest you, or choose a guide below for a curated reading path through the FAQs and related articles.</p>\n<table>\n<thead>\n<tr>\n<th>Where are you?</th>\n<th>This sounds like me</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/start/new-to-evals/index.html\">I’m new to evals</a></td>\n<td>I’ve heard the term, but I’m not sure what evals involve or whether I need them.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/start/getting-started/index.html\">I don’t know what to test</a></td>\n<td>I’m building an AI product, but I haven’t figured out which failures to measure or what good performance looks like.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/start/trust-your-evals/index.html\">I don’t trust my eval scores</a></td>\n<td>We have evals, but the scores don’t match our judgment of the outputs, or tests pass while users still encounter problems.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/start/hard-to-evaluate/index.html\">My product feels too hard to evaluate</a></td>\n<td>Our outputs are subjective, long, or involve many steps. Even a knowledgeable person has trouble deciding whether they’re right.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/start/reduce-eval-cost/index.html\">Evals take too much time or money</a></td>\n<td>We’re spending too much effort reviewing outputs, maintaining tests, or running evaluators.</td>\n</tr>\n</tbody>\n</table>\n<h2>All questions</h2>\n<p>Browse all questions by section.</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#getting-started-fundamentals\">Getting Started &amp; Fundamentals</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-are-llm-evals\">Q: What are AI Evals?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-is-a-trace\">Q: What is a trace?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-whats-a-minimum-viable-evaluation-setup\">Q: What’s a minimum viable evaluation setup?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-much-of-my-development-budget-should-i-allocate-to-evals\">Q: How much of my development budget should I allocate to evals?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-will-these-evaluation-methods-still-be-relevant-in-5-10-years-given-how-fast-ai-is-changing\">Q: Will today’s evaluation methods still be relevant in 5-10 years given how fast AI is changing?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-make-the-case-for-investing-in-evaluations-to-my-team\">Q: How do I make the case for investing in evaluations to my team?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#error-analysis-data-collection\">Error Analysis &amp; Data Collection</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">Q: Why is &quot;error analysis&quot; so important in AI evals, and how is it performed?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-do-i-need-a-reference-answer-before-annotating-data\">Q: Do I need a reference answer or rubric before annotating data?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-record-problems-that-arent-the-models-fault\">Q: Should I record problems that aren’t the model’s fault?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-examples-do-i-need-for-an-eval\">Q: How many examples do I need for an eval?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-surface-problematic-traces-for-review-beyond-user-feedback\">Q: How do I surface problematic traces for review beyond user feedback?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-often-should-i-re-run-error-analysis-on-my-production-system\">Q: How often should I re-run error analysis on my production system?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-should-i-do-when-my-gold-eval-dataset-becomes-stale\">Q: What should I do when my &quot;gold&quot; eval dataset becomes stale?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-is-the-best-approach-for-generating-synthetic-data\">Q: What is the best approach for generating synthetic data?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-are-there-scenarios-where-synthetic-data-may-not-be-reliable\">Q: Are there scenarios where synthetic data may not be reliable?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-can-i-do-error-analysis-when-production-traces-contain-sensitive-data\">Q: How can I do evals when traces contain sensitive data?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-approach-evaluation-when-my-system-handles-diverse-user-queries\">Q: How do I approach evaluation when my system handles diverse user queries?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-can-i-efficiently-sample-production-traces-for-review\">Q: How can I efficiently sample production traces for review?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#evaluation-design-methodology\">Evaluation Design &amp; Methodology</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales\">Q: Why do you recommend binary (pass/fail) evaluations instead of 1-5 ratings (Likert scales)?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-combine-my-evals-into-a-single-metric\">Q: How do I combine my evals into a single metric?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-practice-eval-driven-development\">Q: Should I practice eval-driven development?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-automated-evaluators-for-every-failure-mode-i-find\">Q: Should I build automated evaluators for every failure mode I find?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-model-or-llm-should-i-use-to-build-automated-evals\">Q: What model or LLM should I use to build automated evals?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-can-i-use-jev-for-evals\">Q: Can I use Jev for evals?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-know-if-i-can-trust-my-automated-eval\">Q: How do I know if I can trust my automated eval?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-should-i-do-when-i-cant-get-my-llm-judge-to-agree-with-human-reviewers\">Q: What should I do when I can’t get my LLM judge to agree with human reviewers?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-use-ready-to-use-evaluation-metrics\">Q: Should I use &quot;ready-to-use&quot; evaluation metrics?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-are-similarity-metrics-bertscore-rouge-etc-useful-for-evaluating-llm-outputs\">Q: Are similarity metrics (BERTScore, ROUGE, etc.) useful for evaluating LLM outputs?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-can-i-use-the-same-model-for-both-the-main-task-and-evaluation\">Q: Can I use the same model for both the main task and evaluation?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-much-of-a-trace-should-i-give-an-llm-judge\">Q: How much context should I give a LLM judge?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-we-evaluate-a-models-ability-to-express-uncertainty-or-know-what-it-doesnt-know\">Q: How do we evaluate a model’s ability to express uncertainty or &quot;know what it doesn’t know&quot;?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#human-annotation-process\">Human Annotation &amp; Process</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">Q: How many people should annotate my LLM outputs?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-if-human-reviewers-approve-ai-outputs-without-checking-them-carefully\">Q: How can I make AI outputs easier for people to evaluate?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-product-managers-and-engineers-collaborate-on-error-analysis-how\">Q: Should product managers and engineers collaborate on error analysis? How?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-can-i-help-with-evals-if-im-not-a-domain-expert\">Q: Can I help with evals if I’m not a domain expert?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-outputs-in-a-language-i-dont-speak\">Q: How do I evaluate outputs in a language I don’t speak?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-outsource-annotation-and-labeling-to-a-third-party\">Q: Should I outsource annotation &amp; labeling to a third party?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-if-the-source-material-is-too-large-for-a-person-to-review\">Q: How do you review a trace that is really large?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-parts-of-evals-can-be-automated-with-llms\">Q: What parts of evals can be automated with LLMs?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-stop-writing-prompts-manually-in-favor-of-automated-tools\">Q: Should I stop writing prompts manually in favor of automated tools?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#tools-infrastructure\">Tools &amp; Infrastructure</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf\">Q: Should I build a custom annotation tool or use something off-the-shelf?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-makes-a-good-custom-interface-for-reviewing-llm-outputs\">Q: What makes a good custom interface for reviewing LLM outputs?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-gaps-in-eval-tooling-should-i-be-prepared-to-fill-myself\">Q: What gaps in eval tooling should I be prepared to fill myself?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-should-an-internal-eval-platform-standardize-across-teams\">Q: What should an internal eval platform standardize across teams?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-whats-your-favorite-eval-vendor\">Q: What’s your favorite eval vendor?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-should-i-version-and-manage-prompts\">Q: How should I version and manage prompts?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-should-go-in-the-system-prompt-vs-the-user-prompt\">Q: What should go in the system prompt vs. the user prompt?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#production-deployment\">Production &amp; Deployment</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-are-evaluations-used-differently-in-cicd-vs-monitoring-production\">Q: How are evaluations used differently in CI/CD vs. monitoring production?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-often-should-i-run-my-evals\">Q: How often should I run my evals?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-whats-the-difference-between-guardrails-evaluators\">Q: What’s the difference between guardrails &amp; evaluators?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-can-my-evaluators-also-be-used-to-automatically-fix-or-correct-outputs-in-production\">Q: Can my evaluators also be used to automatically <em>fix</em> or <em>correct</em> outputs in production?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-much-time-should-i-spend-on-model-selection\">Q: How much time should I spend on model selection?</a></li>\n</ul>\n</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#domain-specific-applications\">Domain-Specific Applications</a>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-is-rag-dead\">Q: Is RAG dead?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-should-i-evaluate-a-coding-agent\">Q: How should I evaluate a coding agent?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-should-i-approach-evaluating-my-rag-system\">Q: How should I approach evaluating my RAG system?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-choose-the-right-chunk-size-for-my-document-processing-tasks\">Q: How do I choose the right chunk size for my document processing tasks?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-debug-multi-turn-conversation-traces\">Q: How do I debug multi-turn conversation traces?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-sessions-with-human-handoffs\">Q: How do I evaluate sessions with human handoffs?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-complex-multi-step-workflows\">Q: How do I evaluate complex multi-step workflows?</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-agentic-workflows\">Q: How do I evaluate agentic workflows?</a></li>\n</ul>\n</li>\n</ul>\n<h2>Getting Started &amp; Fundamentals</h2>\n<h2>Q: What are AI Evals?</h2>\n<p>AI evals are tests that tell you whether an AI system is doing what you want. They give your team feedback when the product drifts from user needs or business goals. The failures they catch also become data you can use to improve the system.</p>\n<p>More formally, evaluation is the systematic measurement of quality. Each eval checks one behavior on relevant examples and returns a score or structured review. Most AI products need several evals because they can fail in different ways.</p>\n<p>When you hear the word “evals,” it usually refers to one of two things: model benchmarks or product evals.</p>\n<h3>Model benchmarks</h3>\n<p>Model benchmarks compare general-purpose models on shared tasks. Model providers publish these benchmark results when they release new models. Common examples include <strong>GPQA Diamond</strong> for graduate-level science reasoning, <strong>Terminal-Bench</strong> for agents doing complex work in command-line environments, and <strong>MMLU</strong> for knowledge and reasoning across a wide range of subjects. These scores can help you choose a promising model as a starting point. To assess quality on your own tasks you need product evals, which we discuss next.</p>\n<h3>Product evals</h3>\n<p>Product evals measure whether your specific AI product does what you want it to do. They turn your judgment about what a good product experience looks like into metrics you can track. Product evals encompass all components of your product, including the model, prompts, retrieval, tools, and application code. This flavor of evals are focused on capturing failures that matter to users and the business.</p>\n<p>Consider an order-cancellation agent. Its product evals might check whether it selected the correct order and waited for the cancellation tool to succeed before telling the user the order was canceled. A high score on GPQA Diamond or Terminal-Bench gives you little information on this, because those benchmarks don’t have access to your systems.</p>\n<p>There are several mechanisms you can use to implement product evals, including code assertions, human review, LLM judges, and online experiments. The right method depends on the failure being measured, and is discussed in greater detail <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/index.html\">in this series</a>.</p>\n<p>In the rest of the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html\">AI Evals FAQ</a>, we focus on product evals. It starts with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">analyzing traces</a> to discover real failure modes. We then turn important failures into <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-automated-evaluators-for-every-failure-mode-i-find\">targeted evals</a> and use the results to guide changes. Finally, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/index.html#step-3-run-track-your-tests-regularly\">rerunning the evals</a> tells us whether the system improved.</p>\n<h3>Where to start with evals</h3>\n<p>If you are completely new to product-specific evals, see these posts:</p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>Guide</th>\n<th>What it covers</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/index.html\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals/images/diagram-cover.webp\" alt=\"Your AI Product Needs Evals cover\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/index.html\">Part 1</a>: <strong>Your AI Product Needs Evals</strong></td>\n<td>Build a domain-specific evaluation system with scoped tests, trace review, human evaluation, and experiments.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/llm-judge/images/cover_img.webp\" alt=\"Using LLM-as-a-Judge For Evaluation cover\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">Part 2</a>: <strong>Using LLM-as-a-Judge For Evaluation: A Complete Guide</strong></td>\n<td>Capture a domain expert’s judgment, automate it with an LLM judge, and validate the judge against human labels.</td>\n</tr>\n<tr>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/index.html\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/field_guide_2.webp\" alt=\"A Field Guide to Rapidly Improving AI Products cover\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/index.html\">Part 3</a>: <strong>A Field Guide to Rapidly Improving AI Products</strong></td>\n<td>Use error analysis, realistic data, and trustworthy evals to run a sustained product-improvement loop.</td>\n</tr>\n</tbody>\n</table>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-are-llm-evals.html\">↗ Focus view</a></p>\n<h2>Q: What is a trace?</h2>\n<p>A trace is the complete record of all actions, messages, tool calls, and data retrievals from a single initial user query through to the final response. It includes every step across all agents, tools, and system components in a session: multiple user messages, assistant responses, retrieved documents, and intermediate tool interactions.</p>\n<p><strong>Note on terminology:</strong> Different observability vendors use varying definitions of traces and spans. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mlops.systems/posts/2025-06-04-instrumenting-an-agentic-app-with-arize-phoenix-and-litellm.html#llm-tracing-tools-naming-conventions-june-2025\">Alex Strick van Linschoten’s analysis</a> highlights these differences (screenshot below):</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/alex.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Vendor differences in trace definitions as of 2025-07-02</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-is-a-trace.html\">↗ Focus view</a></p>\n<h2>Q: What’s a minimum viable evaluation setup?</h2>\n<p>Start with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>, not infrastructure. Spend 30 minutes manually reviewing 20-50 LLM outputs whenever you make significant changes. Use one <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">domain expert</a> who understands your users as your quality decision maker (a “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">benevolent dictator</a>”).</p>\n<p><strong>Use a notebook</strong> to review traces and analyze data, or build your own <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-makes-a-good-custom-interface-for-reviewing-llm-outputs\">custom annotation interface</a> with an AI coding assistant like Claude or Codex. Either way, you can write arbitrary code, visualize data, and iterate quickly. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/aqKUwPKBkB0?si=5KDmMQnRzO_Ce9xH\">video</a> below shows a simple annotation interface built inside a notebook.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/whats-a-minimum-viable-evaluation-setup.html\">↗ Focus view</a></p>\n<h2>Q: How much of my development budget should I allocate to evals?</h2>\n<p>It’s important to recognize that evaluation is part of the development process rather than a distinct line item, similar to how debugging is part of software development.</p>\n<p>You should always be doing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=qH1dZ8JLLdU\">error analysis</a>. When you discover issues through error analysis, many will be straightforward bugs you’ll fix immediately. These fixes don’t require separate evaluation infrastructure as they’re just part of development.</p>\n<p>The decision to build automated evaluators comes down to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-automated-evaluators-for-every-failure-mode-i-find\">cost-benefit analysis</a>. If you can catch an error with a simple assertion or regex check, the cost is minimal and probably worth it. But if you need to align an LLM-as-judge evaluator, consider whether the failure mode warrants that investment.</p>\n<p>In the projects we’ve worked on, <strong>we’ve spent 60-80% of our development time on error analysis and evaluation</strong>. Expect most of your effort to go toward understanding failures (i.e. looking at data) rather than building automated checks.</p>\n<p>Be <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ai-execs.com/2_intro.html#a-case-study-in-misleading-ai-advice\">wary of optimizing for high eval pass rates</a>. If you’re passing 100% of your evals, you’re likely not challenging your system enough. A 70% pass rate might indicate a more meaningful evaluation that’s actually stress-testing your application. Focus on evals that help you catch real issues, not ones that make your metrics look good.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-much-of-my-development-budget-should-i-allocate-to-evals.html\">↗ Focus view</a></p>\n<h2>Q: Will today’s evaluation methods still be relevant in 5-10 years given how fast AI is changing?</h2>\n<p>Yes. Even with perfect models, you still need to verify they’re solving the right problem. The need for systematic <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>, domain-specific testing, and monitoring will still be important.</p>\n<p>Today’s prompt engineering tricks might become obsolete, but you’ll still need to understand failure modes. Additionally, a LLM cannot read your mind, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">research shows</a> that people need to observe the LLM’s behavior in order to properly externalize their requirements.</p>\n<p>For deeper perspective on this debate, see these two viewpoints: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://m.youtube.com/watch?si=qknrtQeITqJ7VsJH&amp;v=4dUFIRj-BWo&amp;feature=youtu.be\">“The model is the product”</a> versus <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=EEw2PpL-_NM\">“The model is NOT the product”</a>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/will-these-evaluation-methods-still-be-relevant-in-5-10-years-given-how-fast-ai-is-changing.html\">↗ Focus view</a></p>\n<h2>Q: How do I make the case for investing in evaluations to my team?</h2>\n<p>Don’t try to sell your team on “evals”. Instead, show them what you find when you look at the data.</p>\n<p>Start by doing the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> yourself. Look at 50 to 100 real user conversations and find the most common ways the product is failing. Use these findings to tell a story with data.</p>\n<p>Present your team with:</p>\n<ul>\n<li>A list of the top failure modes you discovered.</li>\n<li>Metrics showing how often high-impact errors are happening.</li>\n<li>Surprising ways that users are interacting with the product.</li>\n<li>Reports on the bugs you found and fixed, framed as “prevented production issues”.</li>\n</ul>\n<p>Frame evaluation as part of development, not optional testing. Keep a running log of the errors you catch, what you learned, the fix, and the likely impact you avoided. Share it weekly or monthly. A concrete report such as “we caught 47 issues before users saw them” makes the value easier to see than an abstract pitch about evals.</p>\n<p>This approach builds trust. Don’t just show dashboards and metrics; tell the story of what you’re finding in the data. By narrating your findings, you teach the team what you’re learning, providing immediate value. When you fix an issue, show how the error rate for that specific problem went down. Soon, your team will see the progress and ask how you’re doing it. Let results instead of methods lead the conversation.</p>\n<p>This is similar to classic machine learning projects, where outcomes are speculative and progress is bounded by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#your-ai-roadmap-should-count-experiments-not-features\">iterating on experiments</a>. In this situation, it’s important that you share the learnings from each experiment to show progress and encourage investment.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-make-the-case-for-investing-in-evaluations-to-my-team.html\">↗ Focus view</a></p>\n<h2>Error Analysis &amp; Data Collection</h2>\n<h2>Q: Why is &quot;error analysis&quot; so important in AI evals, and how is it performed?</h2>\n<p>Error analysis is <strong>the most important activity in evals</strong>. Error analysis helps you decide what evals to write in the first place. It allows you to identify failure modes unique to your application and data. The process involves:</p>\n<h3>1. Creating a Dataset</h3>\n<p>Gathering representative traces of user interactions with the LLM. If you do not have any data, you can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-is-the-best-approach-for-generating-synthetic-data\">generate synthetic data</a> to get started.</p>\n<h3>2. Open Coding</h3>\n<p>Human annotator(s) (ideally a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">benevolent dictator</a>) review and write open-ended notes about traces, noting any issues. This process is akin to “journaling” and is adapted from qualitative research methodologies. Start by annotating at least 30 traces yourself before reviewing suggestions from an agent. When beginning, it is recommended to focus on noting the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-debug-multi-turn-conversation-traces\">first failure</a> observed in a trace, as upstream errors can cause downstream issues, though you can also tag all independent failures if feasible. A <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/#step-1-find-the-principal-domain-expert\">domain expert</a> should be performing this step.</p>\n<h3>3. Axial Coding</h3>\n<p>Categorize the open-ended notes into a “failure taxonomy.” In other words, group similar failures into distinct categories. Axial coding is the most important step. At the end, count the number of failures in each category. You can use an LLM to help with this step.</p>\n<h3>4. Iterative Refinement</h3>\n<p>Have your agent cluster the data and choose a diverse initial sample. After your first 30 annotations, let it search the remaining traces for likely instances of the failures you described. Accept or reject its suggestions and keep iterating until you reach <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://delvetool.com/blog/theoreticalsaturation\">theoretical saturation</a>, meaning new reviews stop revealing failure modes or changing existing ones.</p>\n<p>A working pool of roughly 100 diverse traces is a useful guardrail for this human-agent loop. The agent can focus your attention on the most informative traces, so you no longer have to read all 100 sequentially. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-examples-do-i-need-for-an-eval\">how many examples you need for each kind of eval</a> for the full breakdown.</p>\n<p>You should frequently revisit this process. There are advanced ways to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-can-i-efficiently-sample-production-traces-for-review.html\">sample data more efficiently</a>, like clustering, sorting by user feedback, and sorting by high probability failure patterns. Over time, you’ll develop a “nose” for where to look for failures in your data.</p>\n<p>Do not skip error analysis. It ensures that the evaluation metrics you develop are supported by real application behaviors instead of counter-productive generic metrics (which most platforms nudge you to use). For examples of how error analysis can be helpful, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=e2i6JbU2R-s\">this video</a>, or this <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/\">blog post</a>.</p>\n<p>Here is a visualization of the error analysis process by one of our students, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/pawel-huryn/\">Pawel Huryn</a> - including how it fits into the overall evaluation process:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/pawel-error-analysis.webp\" alt=\"Infographic showing an error-analysis workflow: review traces, refine failure modes, recode examples, then build application-specific evaluators\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">↗ Focus view</a></p>\n<h2>Q: Do I need a reference answer or rubric before annotating data?</h2>\n<p>No. Writing a rubric before you review examples can get in the way.</p>\n<p>Let’s get some definitions out of the way:</p>\n<ul>\n<li>A reference answer is an example of a correct response.</li>\n<li>A rubric is a set of criteria for judging a response, such as whether it follows the refund policy.</li>\n</ul>\n<p>Both can help, but treat your initial expectations as a starting point that you will revise.</p>\n<p>It’s often better to wait until you’ve reviewed some examples before developing a detailed rubric. Reviewers can become so focused on checking each item that they overlook problems outside the rubric. It’s important to give reviewers room to notice things you didn’t anticipate. This change in what you consider good is called <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">“criteria drift”</a>.</p>\n<p>For example, let’s say you have a support agent that handles refunds and it escalates refunds to a human per your policy. You might only realize that the process is frustrating for the user after reading a few interactions. Don’t underestimate the degree of criteria drift that will happen as you review examples!</p>\n<p>We recommend using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a> to systematically review examples and decide what might belong in the rubric. This involves writing open-ended notes about what looks wrong, then group similar notes to see which problems recur. See this <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=BsWxPI9UM4c\">live demo</a> for a walkthrough.</p>\n<p>After doing error analysis, you can write a better rubric informed by user and application behavior. You should <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-often-should-i-re-run-error-analysis-on-my-production-system.html\">periodically</a> do error analysis to make sure your rubric is current.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/do-i-need-a-reference-answer-before-annotating-data.html\">↗ Focus view</a></p>\n<h2>Q: Should I record problems that aren’t the model’s fault?</h2>\n<p>Yes. When reviewing interactions, write down anything that makes the product less useful. This includes missing or broken features that have nothing to do with the model. Additionally, don’t focus on why the error occurred, as that should only come after you prioritize which issues to fix.</p>\n<p>For example, a support agent might tell a customer that an order has shipped without providing a tracking link. Even if your AI doesn’t have the ability to fetch a tracking link, record that problem. A prerequisite to building evals is to identify and prioritize which issues to fix through <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a>. Some of these issues may end up being engineering or design issues that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-build-automated-evaluators-for-every-failure-mode-i-find.html\">don’t need</a> an automated evaluator, but they are still important to fix!</p>\n<p>Lastly, we’ve found that deferring root-cause analysis and focusing on problems allows you to write higher-quality annotations while looking at more data.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-record-problems-that-arent-the-models-fault.html\">↗ Focus view</a></p>\n<h2>Q: How many examples do I need for an eval?</h2>\n<p>Building evals is a pipeline, and each stage needs a different amount of data. We describe these stages below:</p>\n<table>\n<thead>\n<tr>\n<th>Stage</th>\n<th>What to do</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1. Review the application</td>\n<td>Read traces and write down the ways your application fails. This process is called <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error discovery</a>. Start with 100 diverse traces and annotate at least the first 30 yourself.</td>\n</tr>\n<tr>\n<td>2. Create and validate evaluators</td>\n<td>Choose between two evaluator types. Use a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/index.html#level-1-unit-tests\">code-based eval</a> when an objective rule can identify the failure. Include Pass and Fail examples for every condition and important edge case. Use an <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">LLM judge</a> when the failure requires human judgment. Label 100 to 200 examples for each failure mode.</td>\n</tr>\n<tr>\n<td>3. Build a repeatable eval set</td>\n<td>Collect examples that represent important workflows and confirmed failures. Run this set when you change your application. These sets often grow to 100 or more examples.</td>\n</tr>\n</tbody>\n</table>\n<h3>Stage 1: Review traces to find failures</h3>\n<p>A trace is a complete record of one user session with your application. Ask a coding agent to help you sample the initial pool so it covers different users and workflows. Our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ai-evals-course/evals-skills\">evals plugin</a> can help with sampling and build an annotation interface for your traces.</p>\n<h4>Review at least 30 traces yourself</h4>\n<p>We recommend annotating at least 30 traces with a process called <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error discovery</a> yourself before asking the agent to suggest failures. Write free-text notes about anything that seems wrong from the user’s perspective. These examples give the agent a concrete record of your judgment.</p>\n<p>Keep this first pass manual. If the agent starts suggesting problems too early, its guesses can bias your judgment. You may miss failures that depend on product context or your definition of a good user experience.</p>\n<p>After 30 traces, ask the agent to search the remaining pool for similar examples. Review every suggestion yourself. Accept or reject each one and correct the agent when it misunderstands your criteria.</p>\n<h4>When to stop</h4>\n<p>Continue until new traces stop revealing failure modes or changing existing ones. Qualitative researchers call this <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://delvetool.com/blog/theoreticalsaturation\">theoretical saturation</a>. We recommend reviewing at least 100 traces. Continue past 100 while you are still learning.</p>\n<p>If you want to see the process done live, watch the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/tqUDjc1HzO4\">live walkthrough</a>. The video shows how Shreya Shankar uses an agent to review traces quickly while keeping a human in charge of the failure criteria.</p>\n<p>This review produces a failure taxonomy, which is a list of the specific ways your application fails. Use that taxonomy to decide which evaluators to build.</p>\n<h3>Stage 2: Create and validate evaluators</h3>\n<p>Choose an evaluator for each important failure mode. The evaluator type determines how many labeled examples you need. Code-based evals work for objective rules. LLM judges work for failures that require human judgment.</p>\n<h4>Code-based evals need coverage</h4>\n<p>Use a code-based eval when a deterministic rule can identify the failure. Examples include checking whether JSON parses or whether a tool call uses the correct arguments.</p>\n<p>The number of examples depends on the scenarios the check covers. At minimum, include examples that should Pass and Fail for every condition. Add important edge cases you found during error discovery. A check with one rule may need only a few examples that Pass and a few that Fail.</p>\n<h4>LLM judges need labeled examples</h4>\n<p>Use an LLM judge when the failure requires subjective or domain-specific judgment. Plan to label 100 to 200 examples for each failure mode. Reuse labeled traces from error discovery when they match the failure mode, then collect more until you reach that range. The labels should come from a trusted <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-many-people-should-annotate-my-llm-outputs.html\">domain expert</a> and contain enough Pass and Fail examples to evaluate both classes.</p>\n<p>Split these examples into train, dev, and test sets. Use 10 to 20 percent for train examples that may appear in the prompt. Use 40 to 45 percent for dev while refining the judge. Reserve the remaining 40 to 45 percent for one final test. When possible, include 30 to 50 Pass examples and 30 to 50 Fail examples in both the dev and test sets.</p>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html#how-do-you-validate-an-llm-judge-against-human-labels\">validation guide</a> explains the full process. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/7-how-to-trust-a-llm-judge.png\">judge-validation flashcard</a> is a good visual reference as well.</p>\n<p>After validating the evaluators, assemble the examples you will run repeatedly during development.</p>\n<h3>Stage 3: Build the repeatable eval set</h3>\n<p>Start with examples from error discovery that capture important failure modes. Add confirmed failures as you find them.</p>\n<p>A purpose-built eval set often grows to 100 or more examples. Coverage determines the final size. Each important workflow and known failure should be represented, and the set should remain cheap enough to run often. Code-based checks and LLM judges can run over the same examples. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-are-evaluations-used-differently-in-cicd-vs-monitoring-production.html\">CI evals</a> FAQ explains how to use this set during development.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-many-examples-do-i-need-for-an-eval.html\">↗ Focus view</a></p>\n<h2>Q: How do I surface problematic traces for review beyond user feedback?</h2>\n<p>While user feedback is a good way to narrow in on problematic traces, other methods are also useful. Here are three complementary approaches:</p>\n<h3>Start with random sampling</h3>\n<p>The simplest approach is reviewing a random sample of traces. If you find few issues, escalate to stress testing: create queries that deliberately test your prompt constraints to see if the AI follows your rules.</p>\n<h3>Use evals for initial screening</h3>\n<p>Use existing evals to find problematic traces and potential issues. Once you’ve identified these, you can proceed with the typical evaluation process starting with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>.</p>\n<h3>Leverage efficient sampling strategies</h3>\n<p>For more sophisticated trace discovery, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-can-i-efficiently-sample-production-traces-for-review\">use outlier detection, metric-based sorting, and stratified sampling</a> to find interesting traces. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-use-ready-to-use-evaluation-metrics\">Generic metrics can serve as exploration signals</a> to identify traces worth reviewing, even if they don’t directly measure quality.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-surface-problematic-traces-for-review-beyond-user-feedback.html\">↗ Focus view</a></p>\n<h2>Q: How often should I re-run error analysis on my production system?</h2>\n<p>Re-run <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> when making significant changes: new features, prompt updates, model switches, or major bug fixes. A useful heuristic is to set a goal for reviewing <em>at least</em> 100+ fresh traces each review cycle. Typical review cycles we’ve seen range from 2-4 weeks. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-can-i-efficiently-sample-production-traces-for-review\">this FAQ</a> on how to sample traces effectively.</p>\n<p>Between major analyses, review 10-20 traces weekly, focusing on outliers: unusually long conversations, sessions with multiple retries, or traces flagged by automated monitoring. Adjust frequency based on system stability and usage growth. New systems need weekly analysis until failure patterns stabilize. Mature systems might need only monthly analysis unless usage patterns change. Always analyze after incidents, user complaint spikes, or metric drift. Scaling usage introduces new edge cases.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-often-should-i-re-run-error-analysis-on-my-production-system.html\">↗ Focus view</a></p>\n<h2>Q: What should I do when my &quot;gold&quot; eval dataset becomes stale?</h2>\n<p>Eval datasets naturally get stale as your product and users change. Use regular <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a> to find new problems and update your examples or reference answers. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-often-should-i-re-run-error-analysis-on-my-production-system.html\">How often you review</a> depends on your use case and how quickly your product or usage changes.</p>\n<p>Like unit tests, evals can catch problems that return after a fix. However, evals often cost considerably more than unit tests to maintain and run. Therefore, you should weigh each eval’s cost against the value of its signals. If <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-much-of-my-development-budget-should-i-allocate-to-evals.html\">everything keeps passing</a>, this is a sign that the eval is less useful and should be run less often or retired. You should phase out expensive evals like LLM-as-a-judge more agressively than cheaper evals.</p>\n<p>As your eval set changes, its scores may no longer be directly comparable with older scores. That is ok! One purpose of evals are to provide you with challenges you can hill climb against. These challenges should change as your product evolves to help you keep improving.</p>\n<p>For tracking progress with metrics over longer time horizons, it’s often better to use product metrics in addition to evals. Examples of product metrics include: churn, active users, revenue, etc.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-i-do-when-my-gold-eval-dataset-becomes-stale.html\">↗ Focus view</a></p>\n<h2>Q: What is the best approach for generating synthetic data?</h2>\n<p>A common mistake is prompting an LLM to <code>&quot;give me test queries&quot;</code> without structure, resulting in generic, repetitive outputs. A structured approach using dimensions produces far better synthetic data for testing LLM applications.</p>\n<h3>When should I use synthetic data for evals?</h3>\n<p>Use synthetic data to start error analysis before you have enough production traffic, or to test a known failure that appears rarely in real data. Define the variation you need, generate examples, run them through the full system, and review the resulting traces.</p>\n<p>Synthetic data cannot tell you how common a failure is in production. It can also miss details that matter in specialized domains. Compare synthetic examples with real data as soon as real data becomes available. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-are-there-scenarios-where-synthetic-data-may-not-be-reliable\">when synthetic data may be unreliable</a> for cases that require extra review.</p>\n<h3>Define important dimensions first</h3>\n<p><strong>Start by defining dimensions</strong>: categories that describe different aspects of user queries. Each dimension captures one type of variation in user behavior. For example:</p>\n<ul>\n<li>For a recipe app, dimensions might include Dietary Restriction (<em>vegan</em>, <em>gluten-free</em>, <em>none</em>), Cuisine Type (<em>Italian</em>, <em>Asian</em>, <em>comfort food</em>), and Query Complexity (<em>simple request</em>, <em>multi-step</em>, <em>edge case</em>).</li>\n<li>For a customer support bot, dimensions could be Issue Type (<em>billing</em>, <em>technical</em>, <em>general</em>), Customer Mood (<em>frustrated</em>, <em>neutral</em>, <em>happy</em>), and Prior Context (<em>new issue</em>, <em>follow-up</em>, <em>resolved</em>).</li>\n</ul>\n<p><strong>Start with failure hypotheses</strong>. If you lack intuition about failure modes, use your application extensively or recruit friends to use it. Then choose dimensions targeting those likely failures.</p>\n<p><strong>Create tuples manually first</strong>: Write 20 tuples by hand. Each tuple selects one value from each dimension. Example: (<em>Vegan</em>, <em>Italian</em>, <em>Multi-step</em>). This manual work helps you understand your problem space.</p>\n<p><strong>Scale with two-step generation</strong>:</p>\n<ol>\n<li><strong>Generate structured tuples</strong>: Have the LLM create more combinations like (<em>Gluten-free</em>, <em>Asian</em>, <em>Simple</em>)</li>\n<li><strong>Convert tuples to queries</strong>: In a separate prompt, turn each tuple into natural language</li>\n</ol>\n<p>This separation avoids repetitive phrasing. The (<em>Vegan</em>, <em>Italian</em>, <em>Multi-step</em>) tuple becomes: <code>&quot;I need a dairy-free lasagna recipe that I can prep the day before.&quot;</code></p>\n<h3>Generation approaches</h3>\n<p>You can generate tuples two ways:</p>\n<p><strong>Cross product then filter</strong>: Generate all dimension combinations, then filter with an LLM. Guarantees coverage including edge cases. Use when most combinations are valid.</p>\n<p><strong>Direct LLM generation</strong>: Ask the LLM to generate tuples directly. This produces more realistic combinations, but it tends toward generic outputs and misses rare scenarios. Use it when many dimension combinations are invalid.</p>\n<p><strong>Fix obvious problems first</strong>: Don’t generate synthetic data for issues you can fix immediately. If your prompt doesn’t mention dietary restrictions, fix the prompt rather than generating specialized test queries.</p>\n<p>After iterating on your tuples and prompts, <strong>run these synthetic queries through your actual system to capture full traces</strong>. A pool of roughly 100 diverse traces is a useful starting point for failure discovery. Have an agent help with sampling, annotate at least 30 traces yourself, then review the agent’s suggestions until your learning plateaus. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-examples-do-i-need-for-an-eval\">how many examples you need for error discovery</a> for the full explanation.</p>\n<p>Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/9-synthetic-data.png\">visual</a> that helps visualize the process.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-is-the-best-approach-for-generating-synthetic-data.html\">↗ Focus view</a></p>\n<h2>Q: Are there scenarios where synthetic data may not be reliable?</h2>\n<p>Yes: synthetic data can mislead or mask issues. For guidance on generating synthetic data when appropriate, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-is-the-best-approach-for-generating-synthetic-data\">What is the best approach for generating synthetic data?</a></p>\n<p>Common scenarios where synthetic data fails:</p>\n<ol>\n<li>\n<p><strong>Complex domain-specific content</strong>: LLMs often miss the structure, nuance, or quirks of specialized documents (e.g., legal filings, medical records, technical forms). Without real examples, critical edge cases are missed.</p>\n</li>\n<li>\n<p><strong>Low-resource languages or dialects</strong>: For low-resource languages or dialects, LLM-generated samples are often unrealistic. Evaluations based on them won’t reflect actual performance.</p>\n</li>\n<li>\n<p><strong>When validation is impossible</strong>: If you can’t verify synthetic sample realism (due to domain complexity or lack of ground truth), real data is important for accurate evaluation.</p>\n</li>\n<li>\n<p><strong>High-stakes domains</strong>: In high-stakes domains (medicine, law, emergency response), synthetic data often lacks subtlety and edge cases. Errors here have serious consequences, and manual validation is difficult.</p>\n</li>\n<li>\n<p><strong>Underrepresented user groups</strong>: For underrepresented user groups, LLMs may misrepresent context, values, or challenges. Synthetic data can reinforce biases in the training data of the LLM.</p>\n</li>\n</ol>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/are-there-scenarios-where-synthetic-data-may-not-be-reliable.html\">↗ Focus view</a></p>\n<h2>Q: How can I do evals when traces contain sensitive data?</h2>\n<p>There is no replacement for looking at real interactions. This situation is not ideal, but there are some things you can do. Here are some options, in order of preference:</p>\n<ol>\n<li>\n<p>Try to find real data you are allowed to inspect. A customer may agree to share a subset of traces, or test users may let you review their interactions. Even limited access gives you examples of how people use the product.</p>\n</li>\n<li>\n<p>If you cannot inspect the data yourself, work with domain experts who are allowed to see it. Make your product <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/index.html\">easier for them to verify</a> as part of their normal work. For example, a medical research assistant could show a clinician the evidence behind each claim and flag conflicting sources for review. The clinician can correct a specific claim or resolve a conflict while using the product. Those decisions can provide additional data for evals, subject to the same restrictions on what you can store and share.</p>\n<p>To design this well, learn how the experts check an answer. Give them links to the supporting evidence and smaller pieces of work they can review. Asking whether the final answer was helpful often tells you too little about what went wrong. I discuss this approach in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/\">this post</a>.</p>\n</li>\n<li>\n<p>Redact or edit traces so they can be shared. If sensitive information cannot be stored, redact it before logging. Redaction tools can miss sensitive information, so check their output. When edited traces can be shared, removing personal information and changing sensitive details can make real examples usable for review. Check that those edits preserve the behavior you need to evaluate.</p>\n</li>\n<li>\n<p>If none of the above options are possible, synthetic data should be your last resort. Synthetic data can help you find initial problems but has the downside that it only gives you limited evidence about how real users will behave. Read more about when <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/are-there-scenarios-where-synthetic-data-may-not-be-reliable.html\">synthetic data may be unreliable</a>.</p>\n</li>\n</ol>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-can-i-do-error-analysis-when-production-traces-contain-sensitive-data.html\">↗ Focus view</a></p>\n<h2>Q: How do I approach evaluation when my system handles diverse user queries?</h2>\n<blockquote>\n<p>Complex applications often support vastly different query patterns—from “What’s the return policy?” to “Compare pricing trends across regions for products matching these criteria.” Each query type exercises different system capabilities, leading to confusion on how to design eval criteria.</p>\n</blockquote>\n<p><em><strong><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/e2i6JbU2R-s?si=8p5XVxbBiioz69Xc\">Error Analysis</a> is all you need.</strong></em> Your evaluation strategy should emerge from observed failure patterns (e.g. error analysis), not predetermined query classifications. Rather than creating a massive evaluation matrix covering every query type you can imagine, let your system’s actual behavior guide where you invest evaluation effort.</p>\n<p>During error analysis, you’ll likely discover that certain query categories share failure patterns. For instance, all queries requiring temporal reasoning might struggle regardless of whether they’re simple lookups or complex aggregations. Similarly, queries that need to combine information from multiple sources might fail in consistent ways. These patterns discovered through error analysis should drive your evaluation priorities. It could be that query category is a fine way to group failures, but you don’t know that until you’ve analyzed your data.</p>\n<p>To see an example of basic error analysis in action, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/e2i6JbU2R-s?si=8p5XVxbBiioz69Xc\">see this video</a>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-approach-evaluation-when-my-system-handles-diverse-user-queries.html\">↗ Focus view</a></p>\n<h2>Q: How can I efficiently sample production traces for review?</h2>\n<p>There are many ways to sample production traces for review. Here are some common methods.</p>\n<table>\n<thead>\n<tr>\n<th>Method</th>\n<th>What it does</th>\n<th>Main limitation</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Random</td>\n<td>Selects traces with equal probability.</td>\n<td>A small batch can miss rare cases.</td>\n</tr>\n<tr>\n<td>Clustering</td>\n<td>Groups traces by similar content and selects examples from each group.</td>\n<td>The result depends on the features and clustering choices.</td>\n</tr>\n<tr>\n<td>Data analysis</td>\n<td>Reviews extreme values such as latency or tool count.</td>\n<td>An extreme value may have nothing to do with quality.</td>\n</tr>\n<tr>\n<td>Classification</td>\n<td>Uses an evaluator or another model to flag likely failures.</td>\n<td>It favors problems the classifier already knows how to find.</td>\n</tr>\n<tr>\n<td>Feedback</td>\n<td>Selects traces with negative user feedback.</td>\n<td>It misses problems that users do not report.</td>\n</tr>\n</tbody>\n</table>\n<p>The table above orders sampling methods from the most exploratory to the most targeted. When you’re starting out, you should optimize for exploration of the data. As you learn more, you can start to lean more heavily on signals to select traces. The proper mix of methods depends on your goals and requires experimentation.</p>\n<p>Keep some random traces in every batch. This gives you a chance to find failure modes that your current signals do not describe.</p>\n<h3>How do I measure rare failure modes?</h3>\n<p>Use targeted sampling to find rare failures. Search for signals that correlate with the failure, such as a specific tool sequence, unusually long traces, retries, or a known input pattern. Review the targeted batch to collect examples and improve the failure definition.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/8-sample-traces.png\">This flashcard</a> from our evals flashcards series visualizes these methods.</p>\n<h3>Use labels to choose the next traces</h3>\n<p>We can borrow a technique from machine learning called active learning to sample production traces. In active learning, a system asks a person to label the data points that would be most useful for its next update.</p>\n<p>In <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/tqUDjc1HzO4\">Shreya Shankar’s walkthrough</a>, Claude Code clusters traces and chooses examples from each cluster for review. A <code>monitor</code> command watches <code>annotations.json</code> for new labels. When a label arrives, the agent updates a failure taxonomy and looks for similar cases or different failures.</p>\n<p>In the above video, active learning is used in the context of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> to find new cases to review. However, this approach can be used anywhere in the workflow where you are annotating data.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-can-i-efficiently-sample-production-traces-for-review.html\">↗ Focus view</a></p>\n<hr />\n<p><strong>👉 <em>Want to learn more about AI Evals? Check out our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">AI Evals course</a></em></strong>. It’s a live cohort with hands on exercises and office hours. Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">25% discount code</a> for readers. 👈</p>\n<hr />\n<h2>Evaluation Design &amp; Methodology</h2>\n<h2>Q: Why do you recommend binary (pass/fail) evaluations instead of 1-5 ratings (Likert scales)?</h2>\n<blockquote>\n<p>Engineers often believe that Likert scales (1-5 ratings) provide more information than binary evaluations, allowing them to track gradual improvements. However, this added complexity often creates more problems than it solves in practice.</p>\n</blockquote>\n<p>Binary evaluations force clearer thinking and more consistent labeling. Likert scales introduce significant challenges: the difference between adjacent points (like 3 vs 4) is subjective and inconsistent across annotators, detecting statistical differences requires larger sample sizes, and annotators often default to middle values to avoid making hard decisions.</p>\n<p>Having binary options forces people to make a decision rather than hiding uncertainty in middle values. Binary decisions are also faster to make during error analysis - you don’t waste time debating whether something is a 3 or 4.</p>\n<p>For tracking gradual improvements, consider measuring specific sub-components with their own binary checks rather than using a scale. For example, instead of rating factual accuracy 1-5, you could track “4 out of 5 expected facts included” as separate binary checks. This preserves the ability to measure progress while maintaining clear, objective criteria.</p>\n<p>Start with binary labels to understand what ‘bad’ looks like. Numeric labels are advanced and usually not necessary.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">↗ Focus view</a></p>\n<h2>Q: How do I combine my evals into a single metric?</h2>\n<p>Each eval you create should return a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">binary outcome</a> (e.g. Pass or Fail). You will likely end up with many evals, each checking a different failure. However, people in your organization may want a single number to track.</p>\n<p>A simple approach I like to use is a “pass all” rate. An example passes only if it passes every check. For example, if 80 out of 100 examples pass every check, your pass-all rate is 80%. Design your report or dashboard so you can drill down from the overall pass-all rate to the pass rate for each check so you can see what’s contributing most to failures.</p>\n<p>A middle ground between one overall score and a separate result for every eval is to group related checks into themes. You can then report a pass-all rate for each group. For example, reviewing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/index.html#bottom-up-vs.-top-down-analysis\">Nurture Boss’s apartment leasing assistant</a> revealed problems with conversation flow, handoffs to humans, and rescheduling. Those themes could become groups of evals.</p>\n<p>Another way to choose these groups is by how serious the failures are. For example, report one pass-all rate for checks that should block a release and another for issues you can tolerate. This approach can be helpful for gating production releases.</p>\n<p>If you still need a single score that accounts for differences in importance, you can give some checks more weight than others. I discourage complicated weighted scores for the same reason I discourage <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">Likert scales</a> for LLM judges. If your dashboard reports a composite score that jumps from 3.2 to 3.7 week over week, it’s easy to feel good about the increase without knowing what improved for users. In our experience, dashboards like this are usually performative and waste everyone’s time.</p>\n<p>Whichever approach you choose, remember that as your eval set changes, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-i-do-when-my-gold-eval-dataset-becomes-stale.html\">its scores may no longer be directly comparable with older scores</a>. Evals give you challenges to improve against, and those challenges should change as your product evolves. For tracking progress with metrics over longer time horizons, it’s often better to use product metrics in addition to evals. Measures such as churn or active users can provide a more stable basis for comparison while your evals change.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-combine-my-evals-into-a-single-metric.html\">↗ Focus view</a></p>\n<h2>Q: Should I practice eval-driven development?</h2>\n<p><strong>Generally no.</strong> Eval-driven development (writing evaluators before implementing features) sounds appealing but creates more problems than it solves. Unlike traditional software where failure modes are predictable, LLMs have infinite surface area for potential failures. You can’t anticipate what will break.</p>\n<p>A better approach is to start with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>. Write evaluators for errors you discover, not errors you imagine. This avoids getting blocked on what to evaluate and prevents wasted effort on metrics that have no impact on actual system quality.</p>\n<p><strong>Exception:</strong> Eval-driven development may work for specific constraints where you know exactly what success looks like. If adding “never mention competitors,” writing that evaluator early may be acceptable.</p>\n<p>Most importantly, always do a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-automated-evaluators-for-every-failure-mode-i-find\">cost-benefit analysis</a> before implementing an eval. Ask whether the failure mode justifies the investment. Error analysis reveals which failures actually matter for your users.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-practice-eval-driven-development.html\">↗ Focus view</a></p>\n<h2>Q: Should I build automated evaluators for every failure mode I find?</h2>\n<p>Focus automated evaluators on failures that persist after fixing your prompts. Many teams discover their LLM doesn’t meet preferences they never actually specified - like wanting short responses, specific formatting, or step-by-step reasoning. Fix these obvious gaps first before building complex evaluation infrastructure.</p>\n<p>Consider the cost hierarchy of different evaluator types. Simple assertions and reference-based checks (comparing against known correct answers) are cheap to build and maintain. LLM-as-Judge evaluators require 100+ labeled examples, ongoing weekly maintenance, and coordination between developers, PMs, and domain experts. This cost difference should shape your evaluation strategy.</p>\n<p>Only build expensive evaluators for problems you’ll iterate on repeatedly. Since LLM-as-Judge comes with significant overhead, save it for persistent generalization failures - not issues you can fix trivially. Start with cheap code-based checks where possible: regex patterns, structural validation, or execution tests. Reserve complex evaluation for subjective qualities that can’t be captured by simple rules.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-build-automated-evaluators-for-every-failure-mode-i-find.html\">↗ Focus view</a></p>\n<h2>Q: What model or LLM should I use to build automated evals?</h2>\n<p>First check whether you can test the condition with code assertions. For example, suppose an AI assistant manages your contacts, and you want to test whether it creates a contact when asked. To test this functionality, you can give it a new contact to create, then query the database to check that exactly one matching record exists with the requested details. Using code assertions avoids the need for human labels.</p>\n<p>When a check requires judgment, use an LLM or another machine learning classifier. When using an LLM judge, we recommend using it as a classifier that returns <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">Pass or Fail</a> for the error you want to catch. Whichever model you use, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">validate it</a> against human labels before trusting its decisions.</p>\n<p>For example, you could try Jev from <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://typesafe.ai/\">TypeSafe</a>, BERT, or logistic regression. A different model may be cheaper or faster, and it may agree more or less closely with human labels. Measure these differences on your data to find the model that meets your application’s needs. For example, you might accept slower evaluations if they catch costly failures, or prefer a faster model when you need immediate feedback.</p>\n<p>When using an LLM, starting with a powerful model can make it easier to develop the judge’s prompt. Once it works well, try smaller, cheaper models and measure how much accuracy you lose. You can also <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-the-same-model-for-both-the-main-task-and-evaluation.html\">use the same model as your application</a>.</p>\n<p>An agent can help optimize the judge’s prompt once you have defined the task and labeled examples. Give it a specific failure to detect and a way to measure progress against your labels. “Find all errors and keep improving” is too vague. The agent needs to know what counts as an error and how to tell whether a change helped. Keep a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-many-examples-do-i-need-for-an-eval.html\">separate test set</a> outside the optimization process to check if the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">judge generalizes</a> to examples it was not tuned against.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-model-or-llm-should-i-use-to-build-automated-evals.html\">↗ Focus view</a></p>\n<h2>Q: Can I use Jev for evals?</h2>\n<p>Yes. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://typesafe.ai/\">Jev from TypeSafe</a> is a general-purpose classifier that you can use for evals. An LLM judge that returns <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales.html\">Pass or Fail</a> is also a classifier.</p>\n<p>You validate Jev the same way you would <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-model-or-llm-should-i-use-to-build-automated-evals.html\">any other classifier used for evals</a>, by comparing its predictions against trusted labels. That’s why we’ve crossed out “LLM Judge” in our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/7-how-to-trust-a-llm-judge.png\">original flashcard</a> and replaced it with “Classifier for Evals”:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/how-to-trust-a-classifier-for-evals.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Measure against human labels and keep training, development, and test data separate to avoid overfitting.</p>\n<p>To understand the validation process described in the flashcard, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">this post</a>.</p>\n<p>The advantage of a fast inexpensive classifier (like Jev) is that it can make automated prompt tuning significantly cheaper and faster. Prompt tuning involves automatically trying changes to the evaluator’s prompt and checking whether its decisions agree more closely with human labels. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/pdf/2507.19457\">GEPA</a> is one example of a prompt tuning algorithm. Prompt tuning can sometimes require hundreds or thousands of evaluations, so a lower cost per run can add up to substantial savings.</p>\n<p>No single classifier is best for every eval. Validation with human labels help you make trade-offs between accuracy, cost, and speed for your application.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-jev-for-evals.html\">↗ Focus view</a></p>\n<h2>Q: How do I know if I can trust my automated eval?</h2>\n<p>For an evaluator that makes judgments, test it against human-labeled examples of the failure you want to detect. This applies to LLM judges and other machine learning classifiers. You need to know how often they catch failures and how often they raise false alarms. If code can directly check the condition, you do not need human labels for that check. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-model-or-llm-should-i-use-to-build-automated-evals.html\">which model or method to use for an eval</a>.</p>\n<p>Start by splitting your labeled examples into three separate sets:</p>\n<ul>\n<li><strong>Training set:</strong> Use these examples to teach the evaluator what to look for. For an LLM judge or zero-shot classifier like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-jev-for-evals.html\">Jev</a>, you can include them in its prompt.</li>\n<li><strong>Development set (dev):</strong> Run the evaluator on these examples and compare its decisions with your labels. Inspect disagreements to improve the prompt or choose between models. Repeat this as you develop the evaluator. A prompt tuning algorithm will use the dev set to guide its changes.</li>\n<li><strong>Test set:</strong> Set these examples aside until you finish making changes. Use them for a final check on examples that have not influenced any decisions about the evaluator.</li>\n</ul>\n<p>Each time you use dev results to change the prompt or choose a model, information from those examples influences the evaluator. After many rounds, it may do well on the dev set but poorly on new examples. This is overfitting, and it can happen even if you never put the dev examples directly in the prompt. The test set gives you a final check on data that hasn’t guided those changes.</p>\n<p>If test scores are much worse than dev scores, investigate whether you’ve overfit. Small samples make these measurements less certain, and differences between the sets can also cause a gap. If you’ve overfit, revisit the instructions and examples, then repeat development with a new, untouched test set reserved for the final check. Addressing overfitting is beyond the scope of this FAQ.</p>\n<p>To measure how well the evaluator aligns with human judgments, use the following metrics. Here, “positive” means an error is present, matching the flashcard below.</p>\n<ul>\n<li><strong>True positive rate (TPR), also called recall,</strong> measures how many actual failures the evaluator catches. If people identify 10 failures and the evaluator catches eight, its TPR is 80%. Prioritize this when missing a failure is costly.</li>\n<li><strong>True negative rate (TNR)</strong> measures how many good outputs the evaluator correctly passes. If people identify 100 good outputs and the evaluator passes 95, its TNR is 95%. The other five are false alarms. A high TNR helps avoid wasting people’s time reviewing good outputs that were incorrectly flagged.</li>\n</ul>\n<p>Track both rates as you make changes. Catching more failures can come at the cost of more false alarms. Choose acceptable levels based on the consequences for your application. If failures are rare, even a small false-alarm rate can create a lot of unnecessary reviews.</p>\n<p>The flashcard below illustrates this process for an LLM judge. The same separation of development and testing applies to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-jev-for-evals.html\">other evaluators</a>.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/notes/llm/evals/flashcards/7-how-to-trust-a-llm-judge.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>How to trust an LLM judge: validate against human labels, separate training, development, and test examples, and measure TPR and TNR.</p>\n<p>The flashcard’s dataset split is an example for prompt-based judges or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-jev-for-evals.html\">zero-shot classifiers</a>. Training a classifier may require a larger share of training data. Choose your targets based on the cost of missed failures and false alarms in your application.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">↗ Focus view</a></p>\n<h2>Q: What should I do when I can’t get my LLM judge to agree with human reviewers?</h2>\n<p>To debug a LLM judge, you need examples with human Pass/Fail labels to compare its decisions against. An effective way to get these labels is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a>, which provides you with a structured way to review your application’s data and find errors.</p>\n<p>As you collect labeled examples (we recommend at least 50 passing and 50 failing examples), inspect where the judge disagrees with the human labels to get clues on what needs fixing. Common issues include <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-much-of-a-trace-should-i-give-an-llm-judge.html\">missing context</a> or vague instructions. If you have trouble deciding whether an example should pass or fail, this is a sign that you need to refine your definition of success more precisely.</p>\n<p>Inspect a few disagreements manually before trying automated prompt tuning. Algorithms such as <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/pdf/2507.19457\">GEPA</a> try changes to the judge’s prompt and measure whether they improve agreement with human labels. If you engage in prompt tuning too early, you can miss important problems that aren’t prompt related (like missing context, bad labels, etc.).</p>\n<p>The most common mistake people make is directing their LLM judge to catch too many different kinds of errors at once. Instead, we recommend building a separate judge for each type of failure. For example, checking whether the assistant escalated to a human when required is more specific than grading overall conversation quality. A focused judge is also easier to align with human labels and is more actionable.</p>\n<p>Finally, make sure your judge can generalize to data you haven’t seen (i.e. its not overfitting to the data you’re tuning it with). The best way to thest this is to set aside human-labeled examples and save them for a final test. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">validation FAQ</a> explains how to split your data and measure whether the judge agrees with human reviewers on unseen examples.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-i-do-when-i-cant-get-my-llm-judge-to-agree-with-human-reviewers.html\">↗ Focus view</a></p>\n<h2>Q: Should I use &quot;ready-to-use&quot; evaluation metrics?</h2>\n<p><strong>No. Generic evaluations waste time and create false confidence when you use them as quality measures.</strong> However, they can still help you find traces to inspect.</p>\n<h3>Why are generic eval metrics misleading?</h3>\n<p>Generic evaluation metrics are everywhere. Eval libraries contain scores like helpfulness, coherence, quality, etc. promising easy evaluation. These metrics measure abstract qualities that may not matter for your use case. Good scores on them don’t mean your system works.</p>\n<p>Instead, conduct <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> to understand failures. Define <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales\">binary failure modes</a> based on real problems. Create <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-automated-evaluators-for-every-failure-mode-i-find\">custom evaluators</a> for those failures and validate them against human judgment.</p>\n<p>Experienced practitioners may use generic metrics as exploration signals. Once you understand why they fail as quality measures, you can use them to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-can-i-efficiently-sample-production-traces-for-review\">find interesting traces</a> for human review.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-use-ready-to-use-evaluation-metrics.html\">↗ Focus view</a></p>\n<h2>Q: Are similarity metrics (BERTScore, ROUGE, etc.) useful for evaluating LLM outputs?</h2>\n<p>Generic metrics like BERTScore, ROUGE, cosine similarity, etc. are not useful for evaluating LLM outputs in most AI applications. Instead, we recommend using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> to identify metrics specific to your application’s behavior. We recommend designing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales\">binary pass/fail</a>.) evals (using LLM-as-judge) or code-based assertions.</p>\n<p>As an example, consider a real estate CRM assistant. Suggesting showings that aren’t available (can be tested with an assertion) or confusing client personas (can be tested with a LLM-as-judge) is problematic . Generic metrics like similarity or verbosity won’t catch this. A relevant quote from the course:</p>\n<blockquote>\n<p>“The abuse of generic metrics is endemic. Many eval vendors promote off the shelf metrics, which ensnare engineers into superfluous tasks.”</p>\n</blockquote>\n<p>Similarity metrics aren’t always useless. They have utility in domains like search and recommendation (and therefore can be useful for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-should-i-approach-evaluating-my-rag-system\">optimizing and debugging retrieval</a> for RAG). For example, cosine similarity between embeddings can measure semantic closeness in retrieval systems, and average pairwise similarity can assess output diversity (where lower similarity indicates higher diversity).</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/are-similarity-metrics-bertscore-rouge-etc-useful-for-evaluating-llm-outputs.html\">↗ Focus view</a></p>\n<h2>Q: Can I use the same model for both the main task and evaluation?</h2>\n<p>For LLM-as-Judge selection, using the same model is usually fine because the judge is doing a different task than your main LLM pipeline. While <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/pdf/2508.06709\">research has shown</a> that models can exhibit bias when evaluating their own outputs, what ultimately matters is how well your judge aligns with human judgments. The judges we recommend building do <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-do-you-recommend-binary-passfail-evaluations-instead-of-1-5-ratings-likert-scales\">scoped binary classification tasks</a>. We’ve found that iterative alignment with human labels is usually achievable on this constrained task.</p>\n<p>Focus on achieving high True Positive Rate (TPR) and True Negative Rate (TNR) with your judge on a held out labeled test set. If you struggle to achieve good alignment with human scores, then consider trying a different model. However onboarding new model providers may involve non-trivial effort in some organizations, which is why we don’t advocate for using different models by default unless there’s a specific alignment issue.</p>\n<p>When selecting judge models, start with the most capable models available to establish strong alignment with human judgments. You can optimize for cost later once you’ve established reliable evaluation criteria.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-use-the-same-model-for-both-the-main-task-and-evaluation.html\">↗ Focus view</a></p>\n<h2>Q: How much context should I give a LLM judge?</h2>\n<p>Give each judge only the parts of the trace it needs for its failure mode. Do not give every judge the same full trace by default. Extra context can cause <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/rag/p6-context_rot.html\">context rot</a> and make the judge worse.</p>\n<p>Finding the right pieces of context often requires experimentation. Test your choices by comparing the judge’s decisions with human labels. Then, inspect disagreements to see whether the judge lacked necessary evidence or was distracted by irrelevant information.</p>\n<p>If you’re unsure whether a piece of information helps, try an ablation study. This means removing one piece at a time and checking how the results change against human labels. If performance stays the same or improves, you may be able to leave it out.</p>\n<p>Long-running agents can produce large traces that fill or exceed the judge’s context window. For these cases, consider giving the judge a tool to search the parts it needs. However, don’t add this unless you absolutely need it, as a tool like this adds additional complexity, cost, and latency.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-much-of-a-trace-should-i-give-an-llm-judge.html\">↗ Focus view</a></p>\n<h2>Q: How do we evaluate a model’s ability to express uncertainty or &quot;know what it doesn’t know&quot;?</h2>\n<p>Many applications require a model that can refuse to answer a question when it lacks sufficient information. To evaluate whether this refusal behavior is well-calibrated, you need to test if the model refuses at the appropriate times without refusing to answer questions it <em>should</em> be able to answer.</p>\n<p>To do this effectively, you should construct an evaluation set that has the following components:</p>\n<ol>\n<li><strong>Answerable Questions:</strong> Scenarios where a correct, verifiable answer is present in the model’s provided context or general knowledge.</li>\n<li><strong>Unanswerable Questions:</strong> Scenarios designed to tempt the model to hallucinate. These include questions with false premises, queries about information explicitly missing from context, or topics far outside its knowledge base.</li>\n</ol>\n<p>While the exact proportion isn’t critical, a balanced set with a roughly equal number of answerable and unanswerable questions is a good starting point. The diversity and difficulty of the questions are more important than the precise ratio.</p>\n<p>The evaluation itself is a binary (Pass/Fail) check of the model’s judgment. A “Pass” requires the model to satisfy two conditions: it must answer the answerable questions while also refusing to answer the unanswerable ones. A failure is defined as providing a fabricated answer to an unanswerable question, which indicates poor calibration.</p>\n<p>In the research literature, this capability is known as “Abstention Ability.” To improve this behavior, it is worth <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/search/?query=Abstention+Ability&amp;searchtype=all\">searching for this term on Arxiv</a> to understand the latest techniques.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-we-evaluate-a-models-ability-to-express-uncertainty-or-know-what-it-doesnt-know.html\">↗ Focus view</a></p>\n<h2>Human Annotation &amp; Process</h2>\n<h2>Q: How many people should annotate my LLM outputs?</h2>\n<p>For most small to medium-sized companies, appointing a single domain expert as a “benevolent dictator” is the most effective approach. This person becomes the definitive voice on quality standards. The expert might be a psychologist for a mental health chatbot or a lawyer for legal document analysis.</p>\n<p>A single expert eliminates annotation conflicts and prevents the paralysis that comes from “too many cooks in the kitchen”. The benevolent dictator can incorporate input and feedback from others, but they drive the process. If you feel like you need five subject matter experts to judge a single interaction, it’s a sign your product scope might be too broad.</p>\n<p>However, larger organizations or those operating across multiple domains (like a multinational company with different cultural contexts) may need multiple annotators. When you do use multiple people, you’ll need to measure their agreement using metrics like Cohen’s Kappa, which accounts for agreement beyond chance. However, use your judgment. Even in larger companies, a single expert is often enough.</p>\n<h3>How should annotators resolve disagreements?</h3>\n<p>Have annotators label the same examples independently before they discuss them. Measure agreement and collect the cases where their labels differ. During an alignment session, ask which part of the rubric caused the disagreement and what rule would make the next decision clear.</p>\n<p>Update the rubric with a definition, rule, or example that covers the disputed case. Then relabel affected examples. If the annotators still disagree, assign a domain expert to make the final decision and record the reason.</p>\n<p>Start with a benevolent dictator whenever feasible. Only add complexity when absolutely necessary.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-many-people-should-annotate-my-llm-outputs.html\">↗ Focus view</a></p>\n<h2>Q: How can I make AI outputs easier for people to evaluate?</h2>\n<p>Start by scrutinizing your product design. It’s often helpful to surface intermediate outputs users can check before a final result. For example, suppose you have an agent that writes a medical report by synthesizing a patient’s medical history. Instead of asking a doctor to provide feedback on the report, show the extracted facts with links to the source material and let doctors correct a fact or resolve conflicting evidence before generating the report. This also keeps the doctor involved and helps them build trust by checking the work as they go. This is a sketch of how such an interface might look:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/eval-smell/_static-imgs/09-workers-comp-after.png\" alt=\"ClaimDraft review interface with source-linked findings, controls to resolve contradictions, and an option to add notes before generating a report.\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>A mockup that guides a doctor through facts and conflicting evidence before generating a report.</p>\n<p>For more discussion on designing for verification, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/index.html\">“It’s Hard to Eval” Is a Product Smell</a>. The post expands on this example and discusses several others with before-and-after mockups.</p>\n<p>After you have designed for verification, make sure the review interface removes friction from reviewing data. See the advice on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-makes-a-good-custom-interface-for-reviewing-llm-outputs.html\">building a review interface</a>. Some common tips include:</p>\n<ul>\n<li>Display outputs in a familiar format. Render generated emails as emails, and use syntax highlighting for code.</li>\n<li>Keep the context reviewers need on the same screen. Put less important details in sections they can expand when needed.</li>\n<li>Add keyboard shortcuts for moving between examples and recording judgments. Make it easy to save notes without reaching for the mouse.</li>\n<li>Show progress, such as “45 of 100 examples reviewed,” so reviewers know how much work remains.</li>\n</ul>\n<p>Next, debug the review process. First, try fewer examples so reviewers have time to inspect each one carefully. Have people review the same examples independently and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-many-people-should-annotate-my-llm-outputs.html\">discuss disagreements</a>. You can also review examples together to see where people get stuck. Disagreement can reveal unclear instructions or missing information.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-if-human-reviewers-approve-ai-outputs-without-checking-them-carefully.html\">↗ Focus view</a></p>\n<h2>Q: Should product managers and engineers collaborate on error analysis? How?</h2>\n<p>At the outset, collaborate to establish shared context. Engineers catch technical issues like retrieval issues and tool errors. PMs identify product failures like unmet user expectations, confusing responses, or missing features users expect.</p>\n<p>As time goes on you should lean towards a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">benevolent dictator</a> for error analysis: a domain expert or PM who understands user needs. Empower domain experts to evaluate actual outcomes rather than technical implementation. Ask “Has an appointment been made?” not “Did the tool call succeed?” The best way to empower the domain expert is to give them <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-makes-a-good-custom-interface-for-reviewing-llm-outputs\">custom annotation tools</a> that display system outcomes alongside traces. Show the confirmation, generated email, or database update that validates goal completion. Keep all context on one screen so non-technical reviewers focus on results.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-product-managers-and-engineers-collaborate-on-error-analysis-how.html\">↗ Focus view</a></p>\n<h2>Q: Can I help with evals if I’m not a domain expert?</h2>\n<p>Yes, especially when you’re beginning with evals. I’m often surprised by the number of low-hanging fruit I find while reviewing data that don’t require domain knowledge. For example, I’ve found issues like this in specialized domains as an outsider:</p>\n<ul>\n<li>Text message chatbots getting confused by the conversational flow of lots of short, broken-up messages people tend to write in text versus chat.</li>\n<li>Lack of query disambiguation or follow-up when users’ requests are obviously vague.</li>\n<li>Not having proper instrumentation, logging or traces to begin with.</li>\n<li>Lack of widgets, UI elements or other affordances that help users complete tasks versus over-reliance on text responses.</li>\n</ul>\n<p>Furthermore, ask a domain expert to walk through an example and explain why it is good or bad. Watch what they check and which evidence they need. Use what you learn to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-makes-a-good-custom-interface-for-reviewing-llm-outputs.html\">build a better annotation interface</a> that makes reviewing easier.</p>\n<p>You can also help the team collect interactions and review them regularly. For example, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-product-managers-and-engineers-collaborate-on-error-analysis-how.html\">how product managers and engineers can collaborate on error analysis</a> to get an idea of how to structure cross-functional collaboration.</p>\n<p>Lastly, make sure you leave judgments that require specialized knowledge to the expert. However, don’t assume you need domain expertise to start being useful!</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-help-with-evals-if-im-not-a-domain-expert.html\">↗ Focus view</a></p>\n<h2>Q: How do I evaluate outputs in a language I don’t speak?</h2>\n<p>Even though you can translate interactions in a foreign language with an LLM, be cautious about relying on it. Translation often loses meaning as some words and expressions have no direct equivalent. Moreover, what “good” means often depends on culture and social norms. For example, understanding the literal meaning of an answer is not enough to judge whether its tone is appropriate in a different cultural frame.</p>\n<p>Because of these limitations, we recommend involving a reviewer who understands both the language and the cultural context of your users. You can still <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-i-help-with-evals-if-im-not-a-domain-expert.html\">contribute to evals</a> by helping organize the review and investigating problems you can identify yourself. That reviewer should <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-product-managers-and-engineers-collaborate-on-error-analysis-how.html\">set the standard for error analysis</a>.</p>\n<p>If you cannot find a reviewer who understands both the language and cultural context, be aware that your assessment will be limited.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-evaluate-outputs-in-a-language-i-dont-speak.html\">↗ Focus view</a></p>\n<h2>Q: Should I outsource annotation &amp; labeling to a third party?</h2>\n<p>Outsourcing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> is usually a big mistake (with some <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#exceptions-for-external-help\">exceptions</a>). The core of evaluation is building the product intuition that only comes from systematically analyzing your system’s failures. You should be extremely skeptical of this process being delegated.</p>\n<h3><strong>The Dangers of Outsourcing</strong></h3>\n<p>When you outsource annotation, you often break the feedback loop between observing a failure and understanding how to improve the product. Problems with outsourcing include:</p>\n<ul>\n<li>\n<p>Superficial Labeling: Even well-defined metrics require nuanced judgment that external teams lack. A critical misstep in error analysis is excluding domain experts from the labeling process. Outsourcing this task to those without domain expertise, like general developers or IT staff, often leads to superficial or incorrect labeling.</p>\n</li>\n<li>\n<p>Loss of Unspoken Knowledge: A principal domain expert possesses tacit knowledge and user understanding that cannot be fully captured in a rubric. Involving these experts helps uncover their preferences and expectations, which they might not be able to fully articulate upfront.</p>\n</li>\n<li>\n<p>Annotation Conflicts and Misalignment: Without a shared context, external annotators can create more disagreement than they resolve. Achieving alignment is a challenge even for internal teams, which means you will spend even more time on this process.</p>\n</li>\n</ul>\n<h3><strong>The Recommended Approach: Build Internal Capability</strong></h3>\n<p>Instead of outsourcing, focus on building an efficient internal evaluation process.</p>\n<p>1. Appoint a “Benevolent Dictator”. For most teams, the most effective strategy is to appoint a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-many-people-should-annotate-my-llm-outputs\">single, internal domain expert</a> as the final decision-maker on quality. This individual sets the standard, ensures consistency, and develops a sense of ownership.</p>\n<p>2. Use a collaborative workflow for multiple annotators. If multiple annotators are necessary, follow a structured process to ensure alignment: * Draft an initial rubric with clear Pass/Fail definitions and examples. * Have each annotator label a shared set of traces independently to surface differences in interpretation. * Measure Inter-Annotator Agreement (IAA) using a chance-corrected metric like Cohen’s Kappa. * Facilitate alignment sessions to discuss disagreements and refine the rubric. * Iterate on this process until agreement is consistently high.</p>\n<h3><strong>How to Handle Capacity Constraints</strong></h3>\n<p>Building internal capacity does not mean you have to label every trace. Use these strategies to manage the workload:</p>\n<ul>\n<li>\n<p>Smart Sampling: Review a small, representative sample of traces thoroughly. It is more effective to analyze 100 diverse traces to find patterns than to superficially label thousands.</p>\n</li>\n<li>\n<p>The “Think-Aloud” Protocol: To make the most of limited expert time, use this technique from usability testing. Ask an expert to verbalize their thought process while reviewing a handful of traces. This method can uncover deep insights in a single one-hour session.</p>\n</li>\n<li>\n<p>Build Lightweight Custom Tools: Build <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-what-makes-a-good-custom-interface-for-reviewing-llm-outputs\">custom annotation tools</a> to streamline the review process, increasing throughput.</p>\n</li>\n</ul>\n<h3><strong>Exceptions for External Help</strong></h3>\n<p>While outsourcing the core error analysis process is not recommended, there are some scenarios where external help is appropriate:</p>\n<ul>\n<li>\n<p>Purely Mechanical Tasks: For highly objective, unambiguous tasks like identifying a phone number or validating an email address, external annotators can be used after a rigorous internal process has defined the rubric.</p>\n</li>\n<li>\n<p>Tasks Without Product Context: Well-defined tasks that don’t require understanding your product’s specific requirements can be outsourced. Translation is a good example: it requires linguistic expertise but not deep product knowledge.</p>\n</li>\n<li>\n<p>Engaging Subject Matter Experts: Hiring external SMEs to act as your internal domain experts is not outsourcing; it is bringing the necessary expertise into your evaluation process. For example, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.ankihub.net/\">AnkiHub</a> hired 4th-year medical students to evaluate their RAG systems for medical content rather than outsourcing to generic annotators.</p>\n</li>\n</ul>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-outsource-annotation-and-labeling-to-a-third-party.html\">↗ Focus view</a></p>\n<h2>Q: How do you review a trace that is really large?</h2>\n<p>Traces can get large when an agent runs for a long time or retrieves a large amount of context. A useful heuristic is to focus on the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-debug-multi-turn-conversation-traces.html\">first upstream failure</a>. Errors tend to compound, which means you can prioritize earlier ones to save time.</p>\n<p>Use progressive disclosure in your review tool by showing the most relevant information first and letting reviewers expand details as needed. For example, show the conversation initially, with tool outputs collapsed until a reviewer needs to inspect them.</p>\n<p>If a single trace is still too large to review, work with the domain expert to identify what they need to check. Build a tool that extracts the relevant evidence and links back to its location in the trace or retrieved document. For example, when reviewing an answer about a long contract, the tool could show the relevant clauses with links to their original pages. Always validate this kind of extraction with a domain expert.</p>\n<p>Quality is more important than quantity. You can usually learn more from carefully investigating a few failures than from rushing through many traces.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-if-the-source-material-is-too-large-for-a-person-to-review.html\">↗ Focus view</a></p>\n<h2>Q: What parts of evals can be automated with LLMs?</h2>\n<p>LLMs can speed up parts of your eval workflow, but they can’t replace human judgment where your expertise is essential. For example, if you let an LLM handle all of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a> (i.e., reviewing and annotating traces), you might overlook failure cases that matter for your product. Suppose users keep mentioning “lag” in feedback, but the LLM lumps these under generic “performance issues” instead of creating a “latency” category. You’d miss a recurring complaint about slow response times and fail to prioritize a fix.</p>\n<p>That said, LLMs are valuable tools for accelerating certain parts of the evaluation workflow <em>when used with oversight</em>.</p>\n<h3>Here are some areas where LLMs can help:</h3>\n<ul>\n<li>\n<p><strong>First-pass axial coding:</strong> After you’ve open coded 30–50 traces yourself, use an LLM to organize your raw failure notes into proposed groupings. This helps you quickly spot patterns, but always review and refine the clusters yourself. <em>Note: If you aren’t familiar with axial and open coding, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">this faq</a>.</em></p>\n</li>\n<li>\n<p><strong>Mapping annotations to failure modes:</strong> Once you’ve defined failure categories, you can ask an LLM to suggest which categories apply to each new trace (e.g., “Given this annotation: [open_annotation] and these failure modes: [list_of_failure_modes], which apply?”).</p>\n</li>\n<li>\n<p><strong>Suggesting prompt improvements:</strong> When you notice recurring problems, have the LLM propose concrete changes to your prompts. Review these suggestions before adopting any changes.</p>\n</li>\n<li>\n<p><strong>Analyzing annotation data:</strong> Use LLMs or AI-powered notebooks to find patterns in your labels, such as “reports of lag increase 3x during peak usage hours” or “slow response times are mostly reported from users on mobile devices.”</p>\n</li>\n</ul>\n<h3>However, you shouldn’t outsource these activities to an LLM:</h3>\n<ul>\n<li>\n<p><strong>Initial open coding:</strong> Always read through the raw traces yourself at the start. This is how you discover new types of failures, understand user pain points, and build intuition about your data. Never skip this or delegate it.</p>\n</li>\n<li>\n<p><strong>Validating failure taxonomies:</strong> LLM-generated groupings need your review. For example, an LLM might group both “app crashes after login” and “login takes too long” under a single “login issues” category, even though one is a stability problem and the other is a performance problem. Without your intervention, you’d miss that these issues require different fixes.</p>\n</li>\n<li>\n<p><strong>Ground truth labeling:</strong> For any data used for testing/validating LLM-as-Judge evaluators, hand-validate each label. LLMs can make mistakes that lead to unreliable benchmarks.</p>\n</li>\n<li>\n<p><strong>Root cause analysis:</strong> LLMs may point out obvious issues, but only human review will catch patterns like errors that occur in specific workflows or edge cases—such as bugs that happen only when users paste data from Excel.</p>\n</li>\n</ul>\n<p>In conclusion, start by examining data manually to understand what’s actually going wrong. Use LLMs to scale what you’ve learned, not to avoid looking at data.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-parts-of-evals-can-be-automated-with-llms.html\">↗ Focus view</a></p>\n<h2>Q: Should I stop writing prompts manually in favor of automated tools?</h2>\n<p>Automating prompt engineering can be tempting, but you should be skeptical of tools that promise to optimize prompts for you, especially in early stages of development. When you write a prompt, you are forced to clarify your assumptions and externalize your requirements. Good writing is good thinking <sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>. If you delegate this task to an automated tool too early, you risk never fully understanding your own requirements or the model’s failure modes.</p>\n<p>This is because automated prompt optimization typically hill-climb a predefined evaluation metric. It can refine a prompt to perform better on known failures, but it cannot discover <em>new</em> ones. Discovering new errors requires <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>. Furthermore, research shows that evaluation criteria tends to shift after reviewing a model’s outputs, a phenomenon known as “criteria drift” <sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup>. This means that evaluation is an iterative, human-driven sensemaking process, not a static target that can be set once and handed off to an optimizer.</p>\n<p>A pragmatic approach is to use LLMs to improve your prompt based on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">open coding</a> (open-ended notes about traces). This way, you maintain a human in the loop who is looking at the data and externalizing their requirements. Once you have a high-quality set of evals, prompt optimization can be effective for that last mile of performance.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-stop-writing-prompts-manually-in-favor-of-automated-tools.html\">↗ Focus view</a></p>\n<hr />\n<p><strong>👉 <em>Want to learn more about AI Evals? Check out our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">AI Evals course</a></em></strong>. It’s a live cohort with hands on exercises and office hours. Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">25% discount code</a> for readers. 👈</p>\n<hr />\n<h2>Tools &amp; Infrastructure</h2>\n<h2>Q: Should I build a custom annotation tool or use something off-the-shelf?</h2>\n<p><strong>Build a custom annotation tool.</strong> This is the single most impactful investment you can make for your AI evaluation workflow. With AI-assisted development tools like Cursor or Lovable, you can build a tailored interface in hours. I often find that teams with custom annotation tools iterate ~10x faster.</p>\n<p>Custom tools excel because:</p>\n<ul>\n<li>They show all your context from multiple systems in one place</li>\n<li>They can render your data in a product specific way (images, widgets, markdown, buttons, etc.)</li>\n<li>They’re designed for your specific workflow (custom filters, sorting, progress bars, etc.)</li>\n</ul>\n<p>Off-the-shelf tools may be justified when you need to coordinate dozens of distributed annotators with enterprise access controls. Even then, many teams find the configuration overhead and limitations aren’t worth it.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/fA4pe9bE0LY\">Isaac’s Anki flashcard annotation app</a> shows the power of custom tools—handling 400+ results per query with keyboard navigation and domain-specific evaluation criteria that would be nearly impossible to configure in a generic tool.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf.html\">↗ Focus view</a></p>\n<h2>Q: What makes a good custom interface for reviewing LLM outputs?</h2>\n<p>Great interfaces make human review fast, clear, and motivating. We recommend <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf\">building your own annotation tool</a> customized to your domain. The following features are possible enhancements we’ve seen work well, but you don’t need all of them. The screenshots shown are illustrative examples to clarify concepts. In practice, I rarely implement all these features in a single app. It’s ultimately a judgment call based on your specific needs and constraints.</p>\n<h3><strong>1. Render Traces Intelligently, Not Generically</strong>:</h3>\n<p>Present the trace in a way that’s intuitive for the domain. If you’re evaluating generated emails, render them to look like emails. If the output is code, use syntax highlighting. Allow the reviewer to see the full trace (user input, tool calls, and LLM reasoning), but keep less important details in collapsed sections that can be expanded. Here is an example of a custom annotation tool for reviewing real estate assistant emails:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/emailinterface1.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>A custom interface for reviewing emails for a real estate assistant.</p>\n<h3><strong>2. Show Progress and Support Keyboard Navigation</strong>:</h3>\n<p>Keep reviewers in a state of flow by minimizing friction and motivating completion. Include progress indicators (e.g., “Trace 45 of 100”) to keep the review session bounded and encourage completion. Enable hotkeys for navigating between traces (e.g., N for next), applying labels, and saving notes quickly. Below is an illustration of these features:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/hotkey.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>An annotation interface with a progress bar and hotkey guide</p>\n<h3><strong>3. Trace navigation through clustering, filtering, and search</strong>:</h3>\n<p>Allow reviewers to filter traces by metadata or search by keywords. Semantic search helps find conceptually similar problems. Clustering similar traces (like grouping by user persona) lets reviewers spot recurring issues and explore hypotheses. Below is an illustration of these features:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/group1.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Cluster view showing groups of emails, such as property-focused or client-focused examples. Reviewers can drill into a group to see individual traces.</p>\n<h3><strong>4. Prioritize labeling traces you think might be problematic</strong>:</h3>\n<p>Surface traces flagged by guardrails, CI failures, or automated evaluators for review. Provide buttons to take actions like adding to datasets, filing bugs, or re-running pipeline tests. Display relevant context (pipeline version, eval scores, reviewer info) directly in the interface to minimize context switching. Below is an illustration of these ideas:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/ci.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>A trace view that allows you to quickly see auto-evaluator verdict, add traces to dataset or open issues. Also shows metadata like pipeline version, reviewer info, and more.</p>\n<h3>General Principle: Keep it minimal</h3>\n<p>Keep your annotation interface minimal. Only incorporate these ideas if they provide a benefit that outweighs the additional complexity and maintenance overhead.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-makes-a-good-custom-interface-for-reviewing-llm-outputs.html\">↗ Focus view</a></p>\n<h2>Q: What gaps in eval tooling should I be prepared to fill myself?</h2>\n<p>Most eval tools handle the basics well: logging complete traces, tracking metrics, prompt playgrounds, and annotation queues. These are table stakes. Here are four areas where you’ll likely need to supplement existing tools.</p>\n<p>Watch for vendors addressing these gaps: it’s a strong signal they understand practitioner needs.</p>\n<h3>1. Error Analysis and Pattern Discovery</h3>\n<p>After reviewing traces where your AI fails, can your tooling automatically cluster similar issues? For instance, if multiple traces show the assistant using casual language for luxury clients, you need something that recognizes this broader “persona-tone mismatch” pattern. We recommend building capabilities that use AI to suggest groupings, rewrite your observations into clearer failure taxonomies, help find similar cases through semantic search, etc.</p>\n<h3>2. AI-Powered Assistance Throughout the Workflow</h3>\n<p>The most effective workflows use AI to accelerate every stage of evaluation. During error analysis, you want an LLM helping categorize your open-ended observations into coherent failure modes. For example, you might annotate several traces with notes like “wrong tone for investor,” “too casual for luxury buyer,” etc. Your tooling should recognize these as the same underlying pattern and suggest a unified “persona-tone mismatch” category.</p>\n<p>You’ll also want AI assistance in proposing fixes. After identifying 20 cases where your assistant omits pet policies from property summaries, can your workflow analyze these failures and suggest specific prompt modifications? Can it draft refinements to your SQL generation instructions when it notices patterns of missing WHERE clauses?</p>\n<p>Good workflows also help you conduct data analysis of your annotations and traces. I like using notebooks with AI in-the-loop like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://julius.ai/\">Julius</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hex.tech\">Hex</a>. These help me discover insights like “location ambiguity errors spike 3x when users mention neighborhood names” or “tone mismatches occur 80% more often in email generation than other modalities.”</p>\n<h3>3. Custom Evaluators Over Generic Metrics</h3>\n<p>Be prepared to build most of your evaluators from scratch. Generic metrics like “hallucination score” or “helpfulness rating” rarely capture what actually matters for your application—like proposing unavailable showing times or omitting budget constraints from emails. In our experience, successful teams spend most of their effort on application-specific metrics.</p>\n<h3>4. APIs That Support Custom Annotation Apps</h3>\n<p>Custom annotation interfaces <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf\">work best for most teams</a>. This requires observability platforms with thoughtful APIs. I often have to build my own libraries and abstractions just to make bulk data export manageable. You shouldn’t have to paginate through thousands of requests or handle timeout-prone endpoints just to get your data. Look for platforms that provide true bulk export capabilities and, crucially, APIs that let you write annotations back efficiently.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-gaps-in-eval-tooling-should-i-be-prepared-to-fill-myself.html\">↗ Focus view</a></p>\n<h2>Q: What should an internal eval platform standardize across teams?</h2>\n<p>When building an internal eval platform, it’s tempting to start with tools, infrastructure, and a shared set of metrics. That can lead teams to adopt whatever the platform offers without checking whether it helps them find and fix problems in their products.</p>\n<p>Start by encouraging teams to perform <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-can-i-efficiently-sample-production-traces-for-review.html\">sample data</a> effectively for review. They can use the failures they find to decide which <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-build-automated-evaluators-for-every-failure-mode-i-find.html\">automated checks</a> to build, then <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-know-if-i-can-trust-my-automated-eval.html\">validate evaluators</a> against human labels. Standardize these processes while letting each team develop its own metrics and, when needed, tools. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/index.html\">field guide</a> shows an example of how these might fit together.</p>\n<p>Give teams the flexibility to build their own tools, especially now that AI coding agents make custom software cheaper to create. For example, tools to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf.html\">annotate data</a> often need <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-makes-a-good-custom-interface-for-reviewing-llm-outputs.html\">custom interfaces</a> that fit the data being reviewed. Reviewing text extracted from a scanned document calls for a different interface than reviewing chat conversations.</p>\n<p>A platform can still provide shared storage for results and support collaboration on labeling. Start by serving one team and one use case well, then expand as you learn which needs are shared. The benefit of standardization is smaller when teams have very different needs and can build their own tools cheaply.</p>\n<p>Comparing eval scores across projects only makes sense when the checks and test data are comparable. <strong>We strongly advise against offering <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/should-i-use-ready-to-use-evaluation-metrics.html\">generic metrics</a></strong>, such as helpfulness or coherence, as a shortcut. They are rarely useful as quality measures and tend to distract teams from the failures that affect their users.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-an-internal-eval-platform-standardize-across-teams.html\">↗ Focus view</a></p>\n<h2>Q: What’s your favorite eval vendor?</h2>\n<p>Eval tools are in an intensely competitive space. It would be futile to compare their features. If I tried to do such an analysis, it would be invalidated in a week! Vendors I encounter the most organically in my work are: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.langchain.com/langsmith\">Langsmith</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arize.com/\">Arize</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.braintrust.dev/\">Braintrust</a>.</p>\n<p>When I help clients with vendor selection, the decision weighs heavily towards who can offer the best support, as opposed to purely features. This changes depending on size of client, use case, etc. Yes - it’s mainly the human factor that matters, and dare I say, vibes.</p>\n<p>I have no favorite vendor. At the core, their features are very similar - and I often build <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf\">custom tools</a> on top of them to fit my needs.</p>\n<p>Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-tools/\">video series</a> that has a live commentary on the relative strengths and weaknesses of the three aforementioned vendors.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/whats-your-favorite-eval-vendor.html\">↗ Focus view</a></p>\n<h2>Q: How should I version and manage prompts?</h2>\n<p>There is an unavoidable tension between keeping prompts close to the code vs. an environment that non-technical stakeholders can access.</p>\n<p><strong>My preferred approach is storing prompts in Git.</strong> This treats them as software artifacts that are versioned, reviewed, and deployed atomically with the application code. While the Git command line is unfriendly for non-technical folks, the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com\">GitHub</a> web interface and the GitHub <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://desktop.github.com/\">Desktop app</a> make it very approachable. When I was working at GitHub, I worked with many non-technical professionals, including lawyers and accountants, who used these tools effectively. Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ben.balter.com/2023/03/02/github-for-non-technical-roles/\">blog post</a> aimed at non-technical folks to get started.</p>\n<p>Alternatively, most vendors in the LLM tooling space, such as observability platforms like Arize, Braintrust, and LangSmith, offer dedicated prompt management tools. These are accessible for rapid iteration but risk creating additional layers of indirection.</p>\n<p><strong>Why prompt management tools often fall short:</strong> AI products typically involve many moving parts: tools, RAG, agents, etc. Prompt management tools are inherently limiting because they can’t easily execute your application’s code. Even when they can, there’s often significant indirection involved, making it difficult to test prompts with your system’s capabilities.</p>\n<p><strong>When possible, a notebook provides a great solution for prompt experimentation</strong> If you have Python entry points into your codebase or your codebase is written in Python, Jupyter notebooks are particularly powerful for this purpose. You can experiment with prompts and iterate on your actual AI agents with their full tool and RAG capabilities. This makes it much easier to understand how your system works in practice. Additionally, you can create widgets and small user interfaces within notebooks, giving you the best of both worlds for experimentation and iteration. To see what this looks like in practice, Teresa Torres gives a fantastic, hands-on walkthrough of how she, as a PM, used notebooks for the entire eval and experimentation lifecycle:</p>\n<p>If notebooks are not feasible for your code base, an <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#build-bridges-not-gatekeepers\">integrated prompt environment</a> can be effective for experimentation. Either way, I prefer to version and manage prompts in Git.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-should-i-version-and-manage-prompts.html\">↗ Focus view</a></p>\n<h2>Q: What should go in the system prompt vs. the user prompt?</h2>\n<p><strong>Nothing beats experimentation.</strong> Test both approaches (ideally with evals) with your specific model and use case. Models handle system and user prompts differently, and these differences vary by provider and model version. Move instructions between prompts and measure which produces better results for your specific task.</p>\n<p><strong>General guidelines:</strong> Put static instructions and role definitions in the system prompt. Put dynamic content, examples, and task-specific details in the user prompt. Think of the system prompt as the model’s constitution—rules that apply across all requests. Include identity, behavioral constraints, output format requirements, and standing instructions: “You are a medical assistant. Never provide diagnoses. Always recommend consulting a healthcare provider.”</p>\n<p>The user prompt contains the actual task, relevant context, few-shot examples, and data to process. Documents for analysis, query-specific variations, and contextual information belong here. When the distinction feels unclear, prefer the user prompt. It’s more portable across models and easier to debug.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-go-in-the-system-prompt-vs-the-user-prompt.html\">↗ Focus view</a></p>\n<h2>Production &amp; Deployment</h2>\n<h2>Q: How are evaluations used differently in CI/CD vs. monitoring production?</h2>\n<p>CI evals protect against known regressions before deployment. Online monitoring find failures in production traffic and estimate how often they occur.</p>\n<h3>Evals in CI</h3>\n<p>Test datasets for CI are small (in many cases 100+ examples) and purpose-built. Examples cover core features, regression tests for past bugs, and known edge cases. Since CI tests are run frequently, the cost of each test has to be carefully considered (that’s why you carefully curate the dataset). Favor assertions or other deterministic checks over LLM-as-judge evaluators.</p>\n<h3>Onnline monitoring for production</h3>\n<p>For evaluating production traffic, you can sample live traces and run evaluators against them asynchronously. Since you usually lack reference outputs on production data, you might rely more on on more expensive reference-free evaluators like LLM-as-judge. Additionally, track confidence intervals for production metrics. If the lower bound crosses your threshold, investigate further.</p>\n<h3>Connect the two systems</h3>\n<p>These two systems are complementary: when production monitoring reveals new failure patterns through error analysis and evals, add representative examples to your CI dataset. This mitigates regressions on new issues.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/12-deploy-evals.png\">Here is a visual</a> that helps contrast the approaches.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-are-evaluations-used-differently-in-cicd-vs-monitoring-production.html\">↗ Focus view</a></p>\n<h2>Q: How often should I run my evals?</h2>\n<p>There are three dimensions to consider:</p>\n<ol>\n<li>The <strong>cost</strong> to run and maintain the eval. The more expensive the eval, the greater benefit it needs to provide to justify running it frequently. For example, LLM-as-a-judge is more expensive to run than a unit test.</li>\n<li><strong>How saturated</strong> the eval is on the dataset. If the eval passes all examples, its giving you no new information. You should consider retiring the eval or running it less frequently if its saturated. However, you should first try to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-should-i-do-when-my-gold-eval-dataset-becomes-stale.html\">make the eval more difficult</a> so its not saturated to begin with.\\</li>\n<li>The <strong>business value</strong> of catching this error. For critical errors, the busines value of catching it may be high enough that you should run it more frequently, despite its cost. One caveat here is not to get carried away with hypothetical errors. At the very least, you should prove that you can trigger the error at least once by red-teaming your application before implementing the eval (which will also help you make a better eval)</li>\n</ol>\n<p>There are no bright-line rules. This decision often requires judgement as opposed to something formulaic. Here’s a visual that can help you think through the tradeoffs:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/how-often-light.png\" alt=\"How often to run an eval based on its cost, how many test examples pass, and the business value of catching the error.\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/how-often-dark.png\" alt=\"How often to run an eval based on its cost, how many test examples pass, and the business value of catching the error.\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h3>Examples</h3>\n<p>Below are concrete examples to help you understand the factors involved. Note that these are illustrative:</p>\n<table>\n<thead>\n<tr>\n<th>Eval</th>\n<th>Test examples passing</th>\n<th>Business cost of failure</th>\n<th>Suggested schedule</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>An answer-quality judge with GPT-6 Astra on max reasoning.</td>\n<td>All pass</td>\n<td>Medium</td>\n<td>Retire or run infrequently (e.g. every 2 weeks)</td>\n</tr>\n<tr>\n<td>A code assertion which checks that a contact was saved correctly.</td>\n<td>All pass</td>\n<td>Medium</td>\n<td>You can run this on every change b/c its incredibly cheap.</td>\n</tr>\n<tr>\n<td>A judge with Fable 5 checks whether a support agent follows a new refund policy.</td>\n<td>None pass</td>\n<td>Medium</td>\n<td>Even though expensive, the eval is providing useful feedback b/c nothing is passing, and the business value of catching the error is high enough. I would run this as frequently as possible.</td>\n</tr>\n<tr>\n<td>A judge with GPT-6 Luna checks whether a support agent resolves the customer’s problem.</td>\n<td>Some pass</td>\n<td>Medium</td>\n<td>Not a terribly expensive judge b/c model is smaller and the eval is still catching errors, so I would run this somewhat frequently (e.g. nightly).</td>\n</tr>\n<tr>\n<td>A judge with GPT-6 Astra on max reasoning checks for improper disclosure of confidential information on a legal assistant.</td>\n<td>All pass</td>\n<td>High</td>\n<td>Even though expensive and eval is saturated, the business value of catching the error is high enough that I would run this prior to each release. Given the importance of the error, I would also try to make the eval more difficult so that it’s more useful.</td>\n</tr>\n<tr>\n<td>A judge with GPT-6 Astra on medium reasoning checks a minor formatting preference.</td>\n<td>Some pass</td>\n<td>Low</td>\n<td>Occasionally or retire; use code instead if possible.</td>\n</tr>\n</tbody>\n</table>\n<p>It’s always worth exploring <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-model-or-llm-should-i-use-to-build-automated-evals.html\">cheaper evaluators</a> to see if you can find one that provides similar or better alignment with human labels for less cost.</p>\n<h3>Offline vs. Online Evals</h3>\n<p>The discussion here focused on offline evals. Online evals involve similar considerations, with an additional decision about how many production traces to sample. For example, you might run cheap checks on every trace and an expensive judge on a nightly sample. For more discussion on how these approaches work together, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-are-evaluations-used-differently-in-cicd-vs-monitoring-production.html\">How are evaluations used differently in CI/CD vs. monitoring production?</a></p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-often-should-i-run-my-evals.html\">↗ Focus view</a></p>\n<h2>Q: What’s the difference between guardrails &amp; evaluators?</h2>\n<p>Guardrails are <strong>inline safety checks</strong> that sit directly in the request/response path. They validate inputs or outputs <em>before</em> anything reaches a user, so they typically are:</p>\n<ul>\n<li><strong>Fast and deterministic</strong> – typically a few milliseconds of latency budget.</li>\n<li><strong>Simple and explainable</strong> – regexes, keyword block-lists, schema or type validators, lightweight classifiers.</li>\n<li><strong>Targeted at clear-cut, high-impact failures</strong> – PII leaks, profanity, disallowed instructions, SQL injection, malformed JSON, invalid code syntax, etc.</li>\n</ul>\n<p>If a guardrail triggers, the system can redact, refuse, or regenerate the response. Because these checks are user-visible when they fire, false positives are treated as production bugs; teams version guardrail rules, log every trigger, and monitor rates to keep them conservative.</p>\n<p>On the other hand, evaluators typically run <strong>after</strong> a response is produced. Evaluators measure qualities that simple rules cannot, such as factual correctness, completeness, etc. Their verdicts feed dashboards, regression tests, and model-improvement loops, but they do not block the original answer.</p>\n<p>Evaluators are usually run asynchronously or in batch to afford heavier computation such as a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/\">LLM-as-a-Judge</a>. Inline use of an LLM-as-Judge is possible <em>only</em> when the latency budget and reliability targets allow it. Slow LLM judges might be feasible in a cascade that runs on the minority of borderline cases.</p>\n<p>Apply guardrails for immediate protection against objective failures requiring intervention. Use evaluators for monitoring and improving subjective or nuanced criteria. Together, they create layered protection.</p>\n<p>Word of caution: Do not use llm guardrails off the shelf blindly. Always <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/prompt/\">look at the prompt</a>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/whats-the-difference-between-guardrails-evaluators.html\">↗ Focus view</a></p>\n<h2>Q: Can my evaluators also be used to automatically <em>fix</em> or <em>correct</em> outputs in production?</h2>\n<p>Yes, but only a specific subset of them. This is the distinction between an <strong>evaluator</strong> and a <strong>guardrail</strong> that we <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-whats-the-difference-between-guardrails-evaluators\">previously discussed</a>. As a reminder:</p>\n<ul>\n<li>\n<p><strong>Evaluators</strong> typically run <em>asynchronously</em> after a response has been generated. They measure quality but don’t interfere with the user’s immediate experience.</p>\n</li>\n<li>\n<p><strong>Guardrails</strong> run <em>synchronously</em> in the critical path of the request, before the output is shown to the user. Their job is to prevent high-impact failures in real-time.</p>\n</li>\n</ul>\n<p>There are two important decision criteria for deciding whether to use an evaluator as a guardrail:</p>\n<ol>\n<li>\n<p><strong>Latency &amp; Cost</strong>: Can the evaluator run fast enough and cheaply enough in the critical request path without degrading user experience?</p>\n</li>\n<li>\n<p><strong>Error Rate Trade-offs</strong>: What’s the cost-benefit balance between false positives (blocking good outputs and frustrating users) versus false negatives (letting bad outputs reach users and causing harm)? In high-stakes domains like medical advice, false negatives may be more costly than false positives. In creative applications, false positives that block legitimate creativity may be more harmful than occasional quality issues.</p>\n</li>\n</ol>\n<p>Most guardrails are designed to be <strong>fast</strong> (to avoid harming user experience) and have a <strong>very low false positive rate</strong> (to avoid blocking valid responses). For this reason, you would almost never use a slow or non-deterministic LLM-as-Judge as a synchronous guardrail. However, these tradeoffs might be different for your use case.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/can-my-evaluators-also-be-used-to-automatically-fix-or-correct-outputs-in-production.html\">↗ Focus view</a></p>\n<h2>Q: How much time should I spend on model selection?</h2>\n<p>Many developers fixate on model selection as the primary way to improve their LLM applications. Start with error analysis to understand your failure modes before considering model switching. As Hamel noted in office hours, “I suggest not thinking of switching model as the main axes of how to improve your system off the bat without evidence. Does error analysis suggest that your model is the problem?”</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-much-time-should-i-spend-on-model-selection.html\">↗ Focus view</a></p>\n<h2>Domain-Specific Applications</h2>\n<h2>Q: Is RAG dead?</h2>\n<p>Question: Should I avoid using RAG for my AI application after reading that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://pashpashpash.substack.com/p/why-i-no-longer-recommend-rag-for\">“RAG is dead”</a> for coding agents?</p>\n<blockquote>\n<p>Many developers are confused about when and how to use RAG after reading articles claiming “RAG is dead.” Understanding what RAG actually means versus the narrow marketing definitions will help you make better architectural decisions for your AI applications.</p>\n</blockquote>\n<p>The viral article claiming RAG is dead specifically argues against using <em>naive vector database retrieval</em> for autonomous coding agents, not RAG as a whole. This is a crucial distinction that many developers miss due to misleading marketing.</p>\n<p>RAG simply means Retrieval-Augmented Generation - using retrieval to provide relevant context that improves your model’s output. The core principle remains essential: your LLM needs the right context to generate accurate answers. The question isn’t whether to use retrieval, but how to retrieve effectively.</p>\n<p>For coding applications, naive vector similarity search often fails because code relationships are complex and contextual. Instead of abandoning retrieval entirely, modern coding assistants like Claude Code <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/pashmerepat/status/1926717705660375463?s=46\">still uses retrieval</a> —they just employ agentic search instead of relying solely on vector databases, similar to how human developers work.</p>\n<p>You have multiple retrieval strategies available, ranging from simple keyword matching to embedding similarity to LLM-powered relevance filtering. The optimal approach depends on your specific use case, data characteristics, and performance requirements. Many production systems combine multiple strategies or use multi-hop retrieval guided by LLM agents.</p>\n<p>Unfortunately, “RAG” has become a buzzword with no shared definition. Some people use it to mean any retrieval system, others restrict it to vector databases. Focus on the ultimate goal: getting your LLM the context it needs to succeed. Whether that’s through vector search, agentic exploration, or hybrid approaches is a product and engineering decision.</p>\n<p>Rather than following categorical advice to avoid or embrace RAG, experiment with different retrieval approaches and measure what works best for your application. For more info on RAG evaluation and optimization, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/rag/not_dead.html\">this series of posts</a>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/is-rag-dead.html\">↗ Focus view</a></p>\n<h2>Q: How should I evaluate a coding agent?</h2>\n<p>If your coding agent handles a wide variety of tasks, start by using public benchmarks much as you would a foundation model. For an agent that handles a narrow workflow, product-specific evals are a better fit. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/what-are-llm-evals.html\">evals FAQ</a> explains this distinction.</p>\n<p>Popular coding benchmarks include <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.swebench.com/\">SWE-bench</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.tbench.ai/\">Terminal-Bench</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://aider.chat/docs/leaderboards/\">Aider Polyglot</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/openai/human-eval\">HumanEval</a>.</p>\n<p>In addition to public benchmarks, you can also build a private benchmark of difficult tasks from your organization. OpenAI described using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://openai.com/index/introducing-codex/\">real internal software engineering tasks to evaluate Codex</a> at launch. Each task needs a working environment and code-based tests that establish whether the agent completed it successfully.</p>\n<p>To decide which tasks to include, look at how people use your agent and where it fails. Review runs with engineers, group recurring problems, and turn useful examples into tests. This is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed.html\">error analysis</a>, and it applies to coding products too. If existing tests already identify failures, use those results to choose runs to investigate.</p>\n<p>Anthropic’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.anthropic.com/research/clio\">Clio research</a> illustrates a related approach that clusters chat conversations by topic. You can apply that idea to coding sessions to identify the kinds of work your benchmark should cover.</p>\n<p>Anthropic’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents\">coding-agent eval guidance</a> recommends starting with clearly specified tasks and a stable environment where unit tests can verify results. After you have these unit tests, they recommend adding checks for things those tests don’t capture, such as code quality or how the agent interacts with users. Claude Code’s team, for example, added evals for file edits and later for over-engineering. There are many approaches to measure file edits and over-engineering but you can start with metrics like net new lines of code added and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Cyclomatic_complexity\">cyclomatic complexity</a>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=LwLxlEwrtRA&amp;t=534s\">John Berryman and Shawn Simister’s Copilot talk</a> provides additional examples of coding-agent evals. For code completions, the team removed function implementations from repositories, had the model regenerate them, and ran the existing tests. For chat, they used LLM judges with specific criteria and separate checks for whether the assistant called the right tool. They also ran A/B tests, tracking whether users accepted suggestions and kept the code afterward. These product metrics complemented the offline evals.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-should-i-evaluate-a-coding-agent.html\">↗ Focus view</a></p>\n<h2>Q: How should I approach evaluating my RAG system?</h2>\n<p>RAG systems have two distinct components that require different evaluation approaches: retrieval and generation.</p>\n<h3>Start with retrieval evaluation</h3>\n<p>The retrieval component is a search problem. Evaluate it using traditional information retrieval (IR) metrics. Common examples include Recall@k (of all relevant documents, how many did you retrieve in the top k?), Precision@k (of the k documents retrieved, how many were relevant?), or MRR (how high up was the first relevant document?). The specific metrics you choose depend on your use case. These metrics are pure search metrics that measure whether you’re finding the right documents (more on this below).</p>\n<p>To evaluate retrieval, create a dataset of queries paired with their relevant documents. Generate this synthetically by taking documents from your corpus, extracting key facts, then generating questions those facts would answer. This reverse process gives you query-document pairs for measuring retrieval performance without manual annotation.</p>\n<h3>Next, evaluate generation</h3>\n<p>For the generation component, check how well the LLM uses the retrieved context and whether it answers the question. Use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> to identify failure modes, collect human labels, build targeted LLM judges, and validate those judges against human annotations.</p>\n<p>Jason Liu’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jxnl.co/writing/2025/05/19/there-are-only-6-rag-evals/\">“There Are Only 6 RAG Evals”</a> provides a framework that maps well to this separation. His Tier 1 covers traditional IR metrics for retrieval. Tiers 2 and 3 evaluate relationships between Question, Context, and Answer. These include whether the context is relevant (C|Q), whether the answer is faithful to context (A|C), and whether the answer addresses the question (A|Q).</p>\n<p>In addition to Jason’s six evals, error analysis on your specific data may reveal domain-specific failure modes that warrant their own metrics. For example, a medical RAG system might consistently fail to distinguish between drug dosages for adults versus children, or a legal RAG might confuse jurisdictional boundaries. These patterns emerge only through systematic review of actual failures. Once identified, you can create targeted evaluators for these specific issues beyond the general framework.</p>\n<p>Finally, when implementing Jason’s Tier 2 and 3 metrics, don’t just use prompts off the shelf. The standard LLM-as-judge process requires several steps: error analysis, prompt iteration, creating labeled examples, and measuring your judge’s accuracy against human labels. Once you know your judge’s True Positive and True Negative rates, you can correct its estimates to determine the actual failure rate in your system. Skip this validation and your judges may not reflect your actual quality criteria.</p>\n<p>In summary, debug retrieval first using IR metrics, then tackle generation quality using properly validated LLM judges.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-should-i-approach-evaluating-my-rag-system.html\">↗ Focus view</a></p>\n<h2>Q: How do I choose the right chunk size for my document processing tasks?</h2>\n<p>Unlike RAG, where chunks are optimized for retrieval, document processing assumes the model will see every chunk. The goal is to split text so the model can reason effectively without being overwhelmed. Even if a document fits within the context window, it might be better to break it up. Long inputs can degrade performance due to attention bottlenecks, especially in the middle of the context. Two task types require different strategies:</p>\n<h3>1. Fixed-Output Tasks → Large Chunks</h3>\n<p>These are tasks where the output length doesn’t grow with input: extracting a number, answering a specific question, classifying a section. For example:</p>\n<ul>\n<li>“What’s the penalty clause in this contract?”</li>\n<li>“What was the CEO’s salary in 2023?”</li>\n</ul>\n<p>Use the largest chunk (with caveats) that likely contains the answer. This reduces the number of queries and avoids context fragmentation. However, avoid adding irrelevant text. Models are sensitive to distraction, especially with large inputs. The middle parts of a long input might be under-attended. Furthermore, if cost and latency are a bottleneck, you should consider preprocessing or filtering the document (via keyword search or a lightweight retriever) to isolate relevant sections before feeding a huge chunk.</p>\n<h3>2. Expansive-Output Tasks → Smaller Chunks</h3>\n<p>These include summarization, exhaustive extraction, or any task where output grows with input. For example:</p>\n<ul>\n<li>“Summarize each section”</li>\n<li>“List all customer complaints”</li>\n</ul>\n<p>In these cases, smaller chunks help preserve reasoning quality and output completeness. The standard approach is to process each chunk independently, then aggregate results (e.g., map-reduce). When sizing your chunks, try to respect content boundaries like paragraphs, sections, or chapters. Chunking also helps mitigate output limits. By breaking the task into pieces, each piece’s output can stay within limits.</p>\n<h3>General Guidance</h3>\n<p>It’s important to recognize <strong>why chunk size affects results</strong>. A larger chunk means the model has to reason over more information in one go – essentially, a heavier cognitive load. LLMs have limited capacity to <strong>retain and correlate details across a long text</strong>. If too much is packed in, the model might prioritize certain parts (commonly the beginning or end) and overlook or “forget” details in the middle. This can lead to overly coarse summaries or missed facts. In contrast, a smaller chunk bounds the problem: the model can pay full attention to that section. You are trading off <strong>global context for local focus</strong>.</p>\n<p>No rule of thumb can perfectly determine the best chunk size for your use case – <strong>you should validate with experiments</strong>. The optimal chunk size can vary by domain and model. I treat chunk size as a hyperparameter to tune.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-choose-the-right-chunk-size-for-my-document-processing-tasks.html\">↗ Focus view</a></p>\n<h2>Q: How do I debug multi-turn conversation traces?</h2>\n<p>Start simple. Check if the whole conversation met the user’s goal with a pass/fail judgment. Look at the entire trace and focus on the first upstream failure. Read the user-visible parts first to understand if something went wrong. Only then dig into the technical details like tool calls and intermediate steps.</p>\n<h3>Multi-agent trace logging</h3>\n<p>For multi-agent flows, assign a session or trace ID to each user request and log every message with its source (which agent or tool), trace ID, and position in the sequence. This lets you reconstruct the full path from initial query to final result across all agents.</p>\n<h3>Annotation strategy</h3>\n<p>Annotate only the first failure in the trace at first. Downstream failures often cascade from the first issue, so fixing the upstream failure can resolve the dependent ones. As you gain experience, you can annotate independent failure modes within the same trace to speed up error analysis.</p>\n<h3>Simplify when possible</h3>\n<p>When you find a failure, reproduce it with the simplest possible test case. Here’s an example: suppose a shopping bot gives the wrong return policy on turn 4 of a conversation. Before diving into the full multi-turn complexity, simplify it to a single turn: “What is the return window for product X1000?” If it still fails, you’ve proven the error isn’t about conversation context - it’s likely a basic retrieval or knowledge issue you can debug more easily.</p>\n<h3>Test case generation</h3>\n<p>You have two main approaches. First, simulate users with another LLM to create realistic multi-turn conversations. Second, use “N-1 testing” where you provide the first N-1 turns of a real conversation and test what happens next. The N-1 approach often works better since it uses actual conversation prefixes rather than fully synthetic interactions, but is less flexible.</p>\n<p>The key is balancing thoroughness with efficiency. Not every multi-turn failure requires multi-turn analysis.</p>\n<p>When the conversation includes tools or several agents, use a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-agentic-workflows\">transition failure matrix</a> to find hotspots of errors.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-debug-multi-turn-conversation-traces.html\">↗ Focus view</a></p>\n<h2>Q: How do I evaluate sessions with human handoffs?</h2>\n<p>Capture the complete user journey in your traces, including human handoffs. The trace continues until the user’s need is resolved or the session ends, not when AI hands off to a human. Log the handoff decision, why it occurred, context transferred, wait time, human actions, final resolution, and whether the human had sufficient context. Many failures occur at handoff boundaries where AI hands off too early, too late, or without proper context.</p>\n<p>Evaluate handoffs as potential failure modes during <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>. Ask: Was the handoff necessary? Did the AI provide adequate context? Track both handoff quality and handoff rate. Sometimes the best improvement reduces handoffs entirely rather than improving handoff execution.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-evaluate-sessions-with-human-handoffs.html\">↗ Focus view</a></p>\n<h2>Q: How do I evaluate complex multi-step workflows?</h2>\n<p>Log the entire workflow from initial trigger to final business outcome. Include LLM calls, tool usage, human approvals, and database writes in your traces. You will need this visibility to properly diagnose failures.</p>\n<p>Use both outcome and process metrics. Outcome metrics verify the final result meets requirements: Was the business case complete? Accurate? Properly formatted? Process metrics evaluate efficiency: step count, time taken, resource usage. Process failures are often easier to debug since they’re more deterministic, so tackle them first.</p>\n<p>Segment your <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a> by workflow stages. Early stage failures (understanding user input) differ from middle stage failures (data processing) and late stage failures (formatting output). Early stage improvements have more impact since errors cascade in LLM chains.</p>\n<p>Use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-evaluate-agentic-workflows\">transition failure matrices</a> to analyze where workflows break. Create a matrix showing the last successful state versus where the first failure occurred. This reveals failure hotspots and guides where to invest debugging effort.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-evaluate-complex-multi-step-workflows.html\">↗ Focus view</a></p>\n<h2>Q: How do I evaluate agentic workflows?</h2>\n<p>We recommend evaluating agentic workflows in two phases:</p>\n<p><strong>1. End-to-end task success.</strong> Treat the agent as a black box and decide whether it met the user’s goal. Define a precise success rule per task and measure it with human review or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/\">validated LLM judges</a>. Record the first upstream failure during <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">error analysis</a>.</p>\n<p>Once error analysis reveals which workflows fail most often, move to step-level diagnostics to understand why they’re failing.</p>\n<p><strong>2. Step-level diagnostics.</strong> After you <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/#logging-traces\">log the system’s traces</a>, you can score individual components such as:</p>\n<ul>\n<li><em>Tool choice</em>: check whether the agent selected the appropriate tool.</li>\n<li><em>Parameter extraction</em>: check whether the inputs were complete and well-formed.</li>\n<li><em>Error handling</em>: check how the agent handled empty results or API failures.</li>\n<li><em>Context retention</em>: check whether the agent preserved earlier constraints.</li>\n<li><em>Efficiency</em>: count the steps, seconds, and tokens spent.</li>\n<li><em>Goal checkpoints</em>: verify key milestones in long workflows.</li>\n</ul>\n<h3>How do I test tool calls?</h3>\n<p>Test the tool name, arguments, result, and resulting state as separate checks. Use code assertions when the expected behavior is objective. For example, verify that the agent selected <code>cancel_order</code>, passed the correct order ID, received a successful response, and changed the order status before it told the user that cancellation succeeded.</p>\n<p>Also test authorization and preconditions. A valid tool call can still be wrong if the user did not approve the action or the system skipped a required check.</p>\n<p>Example: “Find Berkeley homes under $1M and schedule viewings” breaks into: parameters extracted correctly, relevant listings retrieved, availability checked, and calendar invites sent. Each checkpoint can pass or fail independently, making debugging tractable.</p>\n<p><strong>Use transition failure matrices to understand error patterns.</strong> Create a matrix where rows represent the last successful state and columns represent where the first failure occurred. This is a great way to understand where the most failures occur.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/shreya_matrix.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Transition failure matrix showing hotspots in text-to-SQL agent workflow</p>\n<p>Transition matrices show where failures cluster. In this example, GenSQL → ExecSQL transitions cause 12 failures while DecideTool → PlanCal causes only 2. The counts show where to investigate first. Here is another <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.figma.com/deck/nwRlh5renu4s4olaCsf9lG/Failure-is-a-Funnel?node-id=2009-927&amp;t=GJlTtxQ8bLJaQ92A-1\">text-to-SQL example</a> from Bryan Bischof:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-faq/images/bischof_matrix.webp\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Bischof, Bryan “Failure is A Funnel - Data Council, 2025”</p>\n<p>In this example, Bryan shows variation in transition matrices across experiments. How you organize your transition matrix depends on the specifics of your application. For example, Bryan’s text-to-SQL agent has an inherent sequential workflow which he exploits for further analytical insight. You can watch his <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/R_HnI9oTv3c?si=hRRhDiydHU5k6ikc\">full talk</a> for more details.</p>\n<p><strong>Creating Test Cases for Agent Failures</strong></p>\n<p>Creating test cases for agent failures follows the same principles as our previous FAQ on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/#q-how-do-i-debug-multi-turn-conversation-traces\">debugging multi-turn conversation traces</a>. Reproduce the error with the simplest test that still fails. Use a multi-turn test only when the failure depends on conversation context.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/how-do-i-evaluate-agentic-workflows.html\">↗ Focus view</a></p>\n<hr />\n<p><strong>👉 <em>Want to learn more about AI Evals? Check out our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">AI Evals course</a></em></strong>. It’s a live cohort with hands on exercises and office hours. Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-book\">25% discount code</a> for readers. 👈</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Paul Graham, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paulgraham.com/writes.html\">“Writes and Write-Nots”</a> <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p>Shreya Shankar, et al., <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">“Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences”</a> <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-09-18T07:00:00+00:00",
      "date_published": "2026-09-18T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/evals-faq/",
      "id": "urn:aggr:item:32013e8270ea0c64617664fb1fb294cd92d3f891",
      "summary": "This document curates the most common questions Shreya and I received while teaching 5,000+ engineers and PMs AI Evals. Warning: These are sharp opinions about what works in most cases. They are not universal truths. Use your judgment. How…",
      "tags": [
        "programming",
        "evals",
        "llms"
      ],
      "title": "AI Evals: Everything You Need to Know",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-09-18-ai-evals-everything-you-need-to-know/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>For the past 3 years, AI evals have been my professional focus.<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup> The most common objection I hear to evals is “our product is hard to eval”.</p>\n<p>This objection is a product smell. Artifacts that are hard for you to verify are often hard for users too. In the worst case, users have to redo the work from scratch to verify the output. More importantly, designing your product for ease of verification should come before building evals.</p>\n<p>In this post, I’ll walk through three products I advised on that faced this issue. I’ll also show before and after sketches to demonstrate design principles. After these examples, I’ll discuss how to apply this general pattern to your product.</p>\n<h2>Example 1: the AI data agent</h2>\n<p>Almost every company I’ve worked with builds an internal AI data agent. You ask it a business question, like what was net revenue for Product A last quarter, and it finds relevant data sources, runs the queries, and provides an answer. The goal of this agent is to reduce dependency on data analysts.</p>\n<p>A common mistake when building AI data agents is to make the answer the only output, as illustrated below.</p>\n<p>Data Agent</p>\n<p>What was net revenue for Product A last quarter?</p>\n<p>Net revenue for Product A last quarter was <strong>$4.21M</strong>.</p>\n<p>Ask anything about your business…➤</p>\n<p>Since the only output is the answer, there is nothing here to check.</p>\n<p>In the sketch above, the user has no way to verify the answer beyond redoing work.<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup> A better design is to provide the user with checkable artifacts, informed by how a domain expert might validate the output. Here are techniques I use to validate metrics as a data scientist:</p>\n<ol>\n<li>Compare the quantity and any intermediate calculations against a trusted source, like a vetted dashboard or report, or a similar analysis a colleague has already vetted.<sup class=\"footnote-ref\"><a href=\"#fn-3\" id=\"fnref-3\" data-footnote-ref>3</a></sup></li>\n<li>Confirm the metric definition precisely. A number like net revenue can include or exclude things like returns and discounts.</li>\n<li>Sanity-check a related quantity. If I can’t verify the number directly, I pull a related number that should move with it, like units sold or unique customers, and check if the combination is plausible.</li>\n<li>Look at what is beneath the aggregate. A total can hide problems, so I break it down by dimensions like region or time period and sanity-check the distribution.</li>\n<li>Read the query. For an important number I look at the SQL to confirm it does what I think, and I tweak it and rerun to test my assumptions.</li>\n<li>Note anything I could not verify. If a step has no trusted reference to check it against, I flag it instead of presenting it as settled.</li>\n</ol>\n<p>Here’s what a better interface might look like. The two tabs below show the same answer at two levels of detail. The chat reply surfaces the details worth seeing up front, and the notebook holds the full analysis behind the answer. Use the tabs to switch between them.</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/\">Chat</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/\">Notebook</a></li>\n</ul>\n<p>Data Agent</p>\n<p>What was net revenue for Product A last quarter?</p>\n<p>Net revenue for Product A last quarter was <strong>$4.21M</strong>.ⓘ</p>\n<p>The agent surfaces the important details behind the answer. Select the Notebook tab above to see the full analysis generated by the agent.</p>\n<p>This is the notebook the agent worked in while producing its answer. Scroll within the figure to see all the cells. The sidebar has a Contents tab for jumping between sections and an Assistant tab for asking follow-ups.</p>\n<p>net_revenue_product_a.ipynb ▶ Run all ↑ Publish</p>\n<p>Markdown B i H 🔗 ☰</p>\n<h2>Net revenue — Product A, Q4 FY25</h2>\n<p>“What was net revenue for Product A last quarter?”</p>\n<p>Short answer: <strong>$4.21M</strong>. This notebook shows how that number was built and what was checked against a trusted source.</p>\n<h2>1. Metric definition</h2>\n<p>Net revenue is <code>gross − returns − discounts</code>. Read the definition from the governed metrics layer so this matches what finance reports.</p>\n<p>[1]</p>\n<p>Python ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\">import yaml\n\ndefn = yaml.safe_load(open(&quot;metrics/net_revenue.yml&quot;))\ndefn[&quot;expr&quot;], defn[&quot;source&quot;]\n</span></code></pre>\n<p>( 'gross - returns - discounts', 'finance.order_lines')</p>\n<h2>2. Net revenue for Product A</h2>\n<p>Pull net revenue for Product A for the quarter, straight from the order lines.</p>\n<p>[2]</p>\n<p>SQL ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Dockerfile\">SELECT SUM(gross - returns - discounts) AS net_revenue\nFROM   finance.order_lines\nWHERE  product = &#39;Product A&#39;\n  AND  fiscal_quarter = &#39;Q4-FY25&#39;;\n</span></code></pre>\n<table>\n<thead>\n<tr>\n<th>net_revenue</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>4,210,442</td>\n</tr>\n</tbody>\n</table>\n<p>That's the <strong>$4.21M</strong> the chat answer reported.</p>\n<h2>3. Sanity-check the distribution</h2>\n<p>Break Product A down by region, then plot it. Nothing should look out of place against last quarter's mix.</p>\n<p>[3]</p>\n<p>SQL ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Dockerfile\">SELECT region,\n       SUM(gross - returns - discounts) AS net_revenue\nFROM   finance.order_lines\nWHERE  product = &#39;Product A&#39;\n  AND  fiscal_quarter = &#39;Q4-FY25&#39;\nGROUP BY region;\n</span></code></pre>\n<table>\n<thead>\n<tr>\n<th>region</th>\n<th>net_revenue</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>West</td>\n<td>1,740,000</td>\n</tr>\n<tr>\n<td>Central</td>\n<td>1,160,000</td>\n</tr>\n<tr>\n<td>East</td>\n<td>890,000</td>\n</tr>\n<tr>\n<td>Intl</td>\n<td>420,000</td>\n</tr>\n</tbody>\n</table>\n<p>[4]</p>\n<p>Python ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\">m = by_region.set_index(&quot;region&quot;)[&quot;net_revenue&quot;] / 1e6\nm.plot.barh(title=&quot;Net revenue by region · Q4 FY25&quot;)\n</span></code></pre>\n<p>Net revenue by region · Q4 FY25</p>\n<p>West 1.74</p>\n<p>Central 1.16</p>\n<p>East 0.89</p>\n<p>Intl 0.42</p>\n<p>0 0.5 1.0 1.5 2.0</p>\n<p>net revenue ($M)</p>\n<p>West and Central drive most of the revenue, with International a small tail.</p>\n<h2>4. What I could not verify</h2>\n<p>Two inputs have no trusted source to check against. Instead of treating them as settled, each is left below as a cell you can run and edit to dig in.</p>\n<p>[5]</p>\n<p>SQL ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Dockerfile\">-- the agent&#39;s -$0.7M returns figure is an estimate;\n-- check the returns table for Q4 rows to back it\nSELECT COUNT(*) AS n_rows, SUM(amount) AS returns\nFROM   finance.returns\nWHERE  product = &#39;Product A&#39;\n  AND  fiscal_quarter = &#39;Q4-FY25&#39;;\n</span></code></pre>\n<table>\n<thead>\n<tr>\n<th>n_rows</th>\n<th>returns</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>NULL</td>\n</tr>\n</tbody>\n</table>\n<p># Q4 returns haven't loaded yet, so the −$0.7M is an estimate</p>\n<p>So that <strong>−$0.7M</strong> has no source to check against yet. The other open item is the customer join:</p>\n<p>[6]</p>\n<p>SQL ▶ Code collapsed ✦ Ask AI ⌘K</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Dockerfile\">-- 183 of 12,480 unique customers are in Billing, not CRM,\n-- so some revenue can&#39;t be attributed. Pull them:\nSELECT b.customer_id, b.amount\nFROM   billing.invoices b\nLEFT JOIN crm.customers c USING (customer_id)\nWHERE  c.customer_id IS NULL\nORDER BY b.amount DESC\nLIMIT 5;\n</span></code></pre>\n<table>\n<thead>\n<tr>\n<th>customer_id</th>\n<th>amount</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>BIL-44821</td>\n<td>18,400</td>\n</tr>\n<tr>\n<td>BIL-39105</td>\n<td>12,950</td>\n</tr>\n<tr>\n<td>BIL-50277</td>\n<td>9,310</td>\n</tr>\n<tr>\n<td>…</td>\n<td>…</td>\n</tr>\n</tbody>\n</table>\n<p># 183 rows · $61,540 unattributed</p>\n<p>That's $61,540 of revenue whose customer attribution is uncertain, surfaced so a person can resolve it before the total is trusted.</p>\n<p>Contents ✦Assistant</p>\n<p>New thread ✕</p>\n<p>What was net revenue for Product A last quarter?</p>\n<p>✦</p>\n<p>I pulled Product A's net revenue from the order lines using finance's governed definition, broke it out by region, and flagged what I couldn't verify. The cells are on the left.</p>\n<p><strong>✓</strong> Generated the cells in this notebook</p>\n<p>Ask a follow-up…</p>\n<p>SQL [2] Auto ▾ ↑</p>\n<p>The notebook reads top to bottom: the assumptions the agent made, the queries it ran, and an explicit list of what it could not verify.</p>\n<p>There is a lot to unpack here. Here are notable changes:</p>\n<ul>\n<li>The agent optionally performs retrieval from vetted analyses, and the interface shows which one was used along with who authored it.</li>\n<li>There is progressive disclosure of details. The chat reply shows high value items like sources, assumptions, and issues. The user can optionally open an interactive notebook to see the full context.</li>\n<li>The AI-generated notebook (see notebook tab above) is organized to promote verification: it opens with the assumptions the agent made, like the metric definition and where it came from, then shows the queries it ran and the numbers they returned. It breaks the total down so you can sanity-check the distribution, and it closes with a list of what it could not verify, each item left as a cell you can run.</li>\n<li>The AI agent is also available in the notebook to help with follow-ups. Finally, the user can publish the notebook back to a knowledge base, where it can be retrieved by future analyses, creating a virtuous cycle.</li>\n</ul>\n<p>This design sketch is far from perfect. The point is that the product should help the user verify the answer as a domain expert would. Compare it to the earlier approach, where the only output was the number.</p>\n<p>Data agents like these are not science fiction. Hex<sup class=\"footnote-ref\"><a href=\"#fn-4\" id=\"fnref-4\" data-footnote-ref>4</a></sup> is my favorite product in this genre; it integrates notebooks and chat better than anything I’ve seen. Here are screenshots from their landing page:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/eval-smell/hex-chat.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Chat interface.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/eval-smell/hex-notebook.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Notebook view which allows the user to see the intermediate steps and data.</p>\n<p>But what does this have to do with evals? If you design your product for verification, annotation becomes less expensive and evals will have better signals to draw from. More importantly, you’ll provide your users with a better product.</p>\n<h2>Example 2: the PE curriculum builder</h2>\n<p>A founder I advised was building an AI tool that writes physical education lesson plans for K-12 teachers. A teacher enters their constraints, like the grade they teach, how long the class is, whether it meets indoors or outdoors, and what equipment they have. The tool then writes a lesson plan for those constraints. The goal is to save teachers the time they spend planning and give them a plan that fits their class. Here is a sketch of what the product looks like:</p>\n<p>PE Planner</p>\n<p>Class details</p>\n<p>Grade Grade 4▾</p>\n<p>Class length 45 minutes▾</p>\n<p>Location Outdoors▾</p>\n<p>Equipment 12 cones, 6 balls</p>\n<p>Generate lesson plan</p>\n<p>The teacher enters constraints and the tool writes a plan from scratch. The only output is the plan, so its difficult to verify.</p>\n<p>The founder asked me how to eval the lesson plans. I turned the question around: <strong>what does a teacher care about?</strong></p>\n<p>The fastest way to trust a plan is to see that a teacher like them already uses it. Additionally, teachers value visibility into what others are doing so they can learn new approaches. Therefore, a better design might start from vetted lesson plans that are actively used in schools. When the tool generates a plan, it shows which vetted plan it started from, who uses that plan, and a diff of what it changed for this teacher’s constraints.</p>\n<p>Next, the teacher can check a small set of changes against a plan they already trust, instead of judging a whole plan from scratch. Here’s a sketch of what a better interface might look like:</p>\n<p>PE Planner</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/#\">View original</a></p>\n<p>DR Adapted from Dana Ruiz's plan</p>\n<p>Grade 4 PE · Lincoln Elementary, Austin TX</p>\n<p>Used at 14 schools Run 30+ times this year Aligned to SHAPE standards</p>\n<p>Full plan</p>\n<p>What changed · 2 edits Accept all Reject all</p>\n<p>1 Warm-up</p>\n<p>− Jog 2 laps, then dynamic stretches (10 min)</p>\n<ul>\n<li>Jog 1 lap, then dynamic stretches (8 min)</li>\n</ul>\n<p>Shortened to fit a 45-minute class</p>\n<p>Accept ⌘⏎ Reject ⌘⌫</p>\n<p>2 Main activity</p>\n<p>− 4 stations, 8 balls, hoops as targets</p>\n<ul>\n<li>3 stations, 6 balls, cones as targets</li>\n</ul>\n<p>Matched to your equipment</p>\n<p>Accept ⌘⏎ Reject ⌘⌫</p>\n<p>The plan is anchored to a vetted plan another teacher uses. The changes are marked so the teacher can check a diff against a plan they trust.</p>\n<p>In this version, most of the plan is inherited from a vetted plan. The teacher’s review is scoped to a few edits, each with a reason explaining why the change was made. This is a more efficient way to review a plan because it reduces the cognitive load of judging a whole plan from scratch.</p>\n<p>Designing for this makes the product simpler to build. Instead of stuffing hundreds of examples into a prompt, the tool captures important dimensions, retrieves a close match, and adapts it. Automated evals now become tractable because there is less surface area to test. For example, you can verify that the plan retrieval picked a sensible anchor, and each edit honors the constraints.</p>\n<h2>Example 3: the workers-comp medical report</h2>\n<p>The last example comes from a workers’ compensation tool a founder asked me to help with. It reads a patient’s chart (intake forms, imaging reports, therapy notes, prior exams) and generates a long expert opinion report, often fifty pages or more. Here is a sketch of the product:</p>\n<p>ClaimDraft – □ ✕</p>\n<p>Source records · 18</p>\n<p>Intake questionnaire</p>\n<p>MRI, lumbar spine</p>\n<p>X-ray report</p>\n<p>Physical therapy notes</p>\n<p>Treating physician notes</p>\n<p>Prior IME</p>\n<p>Work restrictions</p>\n<p>+ 11 more</p>\n<p><strong>Claimant</strong> J. Doe <strong>Claim</strong> #WC-20259417 <strong>Date of injury</strong> Mar 3, 2025 <strong>Examiner</strong> Dr. A. Patel</p>\n<p>Independent Medical Evaluation</p>\n<p>Permanent &amp; Stationary Report</p>\n<p>1. History of injury</p>\n<p>The claimant is a 47-year-old warehouse associate who reports a lumbar spine injury on March 3, 2025 while lifting a carton estimated at sixty pounds. He describes immediate low back pain radiating into the right lower extremity, followed by numbness along the lateral calf.</p>\n<p>2. Review of records</p>\n<p>Records reviewed include the intake questionnaire, an MRI of the lumbar spine dated March 18, 2025, twelve physical therapy notes, and the treating physician's progress reports through August 2025.</p>\n<p>3. Physical examination</p>\n<p>On examination, lumbar flexion was limited to 40 degrees with pain. Straight leg raise was positive on the right at 50 degrees. Strength was 4 of 5 in the right extensor hallucis longus, with diminished sensation in the L5 distribution.</p>\n<p>4. Diagnoses</p>\n<p>Lumbar disc herniation at L5-S1 with associated right L5 radiculopathy, supported by the imaging and examination findings above.</p>\n<p>5. Causation analysis</p>\n<p>Within reasonable medical probability, the disc herniation is causally related to the industrial lifting event of March 3, 2025. The claimant had no documented history of lumbar treatment prior to that date.</p>\n<p>Page 1 of 52 Generated from 18 records</p>\n<p>The only output is a fifty-page narrative. To trust it, the doctor has to re-read the whole chart and check every claim, which can take as long as writing the report from scratch.</p>\n<p>The problem is the same as the other examples, but the stakes are higher. The only output is the report, and the doctor is the one accountable for it. To trust it they have to go back through the chart and confirm the facts and inferences themselves. That can take as long as writing the report from scratch, which defeats the point of the tool.</p>\n<p>You might object that a fifty-page opinion is hard to verify. That is true, and the product should not pretend otherwise. Helping a doctor understand the evidence is arguably more valuable than the finished document. Therefore, I advised the founder to make the product work like a research assistant instead of a report generator.</p>\n<p>For example, the product could read every record and pull out relevant facts, with a link back to the page so the doctor can check each one. Where two exams disagree, or the chart leaves a question open, the product should surface that. The doctor can then resolve any contradictions and fill in the gaps. Finally, the product can assemble the final report from what they have already checked. Here is a sketch of what this might look like:</p>\n<p>ClaimDraft – □ ✕</p>\n<p><strong>Claimant</strong> J. Doe <strong>Claim</strong> #WC-20259417 <strong>Date of injury</strong> Mar 3, 2025 <strong>Examiner</strong> Dr. A. Patel</p>\n<p>Records · 18</p>\n<p>MRI, lumbar spine✓</p>\n<p>Physical therapy notes✓</p>\n<p>Treating physician notes✓</p>\n<p>Prior IME○</p>\n<p>Intake questionnaire○</p>\n<p>X-ray report○</p>\n<p>9 of 18 reviewed</p>\n<p>1 Contradiction</p>\n<p>Straight-leg-raise is recorded as positive on the right by the treating physician (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/#\">p. 31</a>) and negative by the prior IME (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/#\">p. 22</a>).</p>\n<p>2 Key fact</p>\n<p>The intake form notes a prior lumbar strain in 2019, which bears on causation (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/#\">p. 14</a>).</p>\n<p>Include Dismiss</p>\n<p>3 Open question</p>\n<p>No imaging appears in the file after March 18, 2025, so the current status of the herniation is unconfirmed.</p>\n<p>Add a note</p>\n<p>4 Key fact</p>\n<p>The MRI report describes pre-existing degenerative changes at L4-L5 (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/#\">p. 9</a>).</p>\n<p>Include Dismiss</p>\n<p>6 of 24 findings still need review Generate report draft</p>\n<p>The product walks the doctor through the evidence first, surfacing each finding with a link back to the chart. Afterwards, the final report is built from the facts they reviewed.</p>\n<p>The research assistant version of this product allows the doctor to build trust by verifying facts as they go. Similar to the other examples, this design is easier to build and evaluate. Now there are scoped units to grade, such as whether a contradiction is real or whether a citation supports a claim.</p>\n<h2>Generalizing the pattern</h2>\n<p>It is important to understand how users verify your product’s AI artifacts. Sometimes, this may require assembling supporting evidence. In other cases, it could mean refactoring the entire workflow so that the user is in the loop (like the workers’ comp example).</p>\n<p>Below are questions that can guide your product’s design for verification:</p>\n<ol>\n<li>What does the user actually need to check?</li>\n<li>What trusted thing can they compare it against?</li>\n<li>Are there signals or heuristics that experts use to aid in verification?</li>\n<li>What smaller units can they accept, edit, or reject?</li>\n</ol>\n<p>A common thread across these examples is provenance. The fastest way to make an output checkable is to show where each part came from, with links to see more detail. Additionally, you can use progressive disclosure so these sources don’t overwhelm the user.</p>\n<p>What needs verifying also changes as the user’s trust grows. Early on, the data agent should make provenance obvious, like where a metric definition came from. Once the user trusts the agent gets it right, that detail can collapse by default in the card. Good design meets users where they are instead of showing everything.</p>\n<p>These principles hold even when a product seems easy to eval. Coding is a good example: it is one of the most verifiable kinds of work, with tests, types, and diffs. Even so, some coding agents go the extra mile to make their work checkable. Cursor and Devin both record a short video of the UI changes they make, so you can confirm the work is right without reproducing it yourself.<sup class=\"footnote-ref\"><a href=\"#fn-5\" id=\"fnref-5\" data-footnote-ref>5</a></sup></p>\n<h2>None of this is new</h2>\n<p>Evals thinking is aligned with good product design. Gathering supporting data and breaking down workflows into smaller units makes automated grading easier. However, I don’t want to pretend like any wisdom here is new.</p>\n<p>All of these ideas stem from well-established design principles. For example, watching an expert work to learn what they check before you build is called needfinding.<sup class=\"footnote-ref\"><a href=\"#fn-6\" id=\"fnref-6\" data-footnote-ref>6</a></sup> In research-heavy work like the medical case, there is a design goal called sensemaking, which is the work of building a structured understanding of a body of evidence you can reason over.<sup class=\"footnote-ref\"><a href=\"#fn-7\" id=\"fnref-7\" data-footnote-ref>7</a></sup> There are many other concepts, but I think you get the idea.</p>\n<p>Even though these ideas are well established, a reminder is due in the age of AI. Before AI, verification often happened incidentally during the process of creating work product. With AI, verification is the bottleneck. It is time to think about it more explicitly.</p>\n<hr />\n<p>To stay updated on my writing, subscribe to my newsletter below.</p>\n<h4>Video Walkthrough</h4>\n<p>I recorded a short video walking through these examples. If you’d rather watch than read, here it is:</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=0ASUz7gFRcw\">www.youtube.com</a></p>\n<p><em>Thanks to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.sh-reya.com/\">Shreya Shankar</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://isaacflath.com/\">Isaac Flath</a> for feedback on this post.</em></p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/\">AI evals topic hub</a> collects the rest of my evals work. I also co-teach the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-url\">AI Evals for Engineers and PMs</a> course. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/lennysan/status/2054631157191598294\">Lenny Rachitsky tweeted about this recently</a>: most of one data science team’s work is now reviewing half-baked AI analysis from PMs and engineers, and half of it is wrong. <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n<li id=\"fn-3\">\n<p>When I worked at Airbnb we had an internal tool called the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/airbnb/knowledge-repo\">Knowledge Repo</a>, a place where data scientists published notebooks of their deep dives on analytics, modeling, and so on (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/airbnb-engineering/scaling-knowledge-at-airbnb-875d73eff091\">they wrote about it here</a>). It was one of the fastest ways to get context on a new project, since you could read what someone had already worked out. I don’t know if it is still in use, but the paradigm is a good one. <a href=\"#fnref-3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n</li>\n<li id=\"fn-4\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/bryan-bischof/\">Bryan Bischof</a> led the creation of the AI at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.hex.tech/\">Hex</a>. Bryan is a data scientist himself, the kind of domain expert the product serves, and I think that is part of why it is designed well. <a href=\"#fnref-4\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"4\" aria-label=\"Back to reference 4\">↩</a></p>\n</li>\n<li id=\"fn-5\">\n<p>See a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=XbZvC4KTH68\">demo of Cursor</a> recording its UI changes, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.devin.ai/work-with-devin/testing-and-recordings\">Devin’s documentation</a> on testing and recordings. <a href=\"#fnref-5\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"5\" aria-label=\"Back to reference 5\">↩</a></p>\n</li>\n<li id=\"fn-6\">\n<p>Dev Patnaik and Robert Becker, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://onlinelibrary.wiley.com/doi/10.1111/j.1948-7169.1999.tb00250.x\">“Needfinding: The Why and How of Uncovering People’s Needs”</a>, Design Management Journal. <a href=\"#fnref-6\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"6\" aria-label=\"Back to reference 6\">↩</a></p>\n</li>\n<li id=\"fn-7\">\n<p>Daniel M. Russell, Mark J. Stefik, Peter Pirolli, and Stuart K. Card, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.markstefik.com/wp-content/uploads/2014/04/1993-Cost-Structure-of-Sensemaking1.pdf\">“The Cost Structure of Sensemaking”</a>. <a href=\"#fnref-7\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"7\" aria-label=\"Back to reference 7\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-06-29T07:00:00+00:00",
      "date_published": "2026-06-29T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/eval-smell/",
      "id": "urn:aggr:item:f923194100956c7c71e073b4de410eed09ee061d",
      "summary": "For the past 3 years, AI evals have been my professional focus.1 The most common objection I hear to evals is “our product is hard to eval”. This objection is a product smell. Artifacts that are hard for you to verify are often hard for…",
      "tags": [
        "programming",
        "evals",
        "llms"
      ],
      "title": "“It’s Hard to Eval” Is a Product Smell",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-06-29-it-s-hard-to-eval-is-a-product-smell/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Is the heyday of the data scientist over? The Harvard Business Review once called it “The Sexiest Job of the 21st Century.”<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup> In tech, data scientist roles were often among the best paid.<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup> The job also demanded an unusual mix of skills:</p>\n<blockquote>\n<p>Data Scientist (n.): Person who is better at statistics than any software engineer and better at software engineering than any statistician.</p>\n<p>— JosH100 (@josh_wills) <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://twitter.com/josh_wills/status/198093512149958656?ref_src=twsrc%5Etfw\">May 3, 2012</a></p>\n</blockquote>\n<p>In addition to creating a high-barrier to entry, these skills enabled data scientists to build predicitive models, measure casuality and find patterns in data. Of these, predicitive modeling paid best. Companies later peeled that work off into a new title: Machine Learning Engineer (“MLE”).<sup class=\"footnote-ref\"><a href=\"#fn-3\" id=\"fnref-3\" data-footnote-ref>3</a></sup></p>\n<p>For years, shipping AI meant keeping data scientists and MLEs on the critical path. With LLMs, this stopped being the default. Foundation-model APIs now allow teams to integrate AI independently.</p>\n<p>Getting cut out of the loop rattled data scientists and MLEs I know. If the company no longer needs you to ship AI, it is fair to wonder whether the job still has the same upside. The harsher story people tell themselves: unless you are pretraining at a foundation-model lab, you are not where the action is.</p>\n<p>In my opinion, training models was never most of the job. The bulk of the work is setting up experiments to test how well the AI generalizes to unseen data, debugging stochastic systems, and designing good metrics. Calling an LLM over an API does not make this work go away.</p>\n<p>I recently gave a talk titled “The Revenge of the Data Scientist” at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://pyai.events/\">PyAI Conf</a> to make that case with examples rather than assertion alone. Below is an annotated version of that presentation.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_1.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h2>The Harness Is Data Science</h2>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_2.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>OpenAI published a blog post on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://openai.com/index/harness-engineering/\">harness engineering</a> that I recommend reading. They describe how Codex worked on a software project for months, autonomously, with agents developing code bounded by a harness of tests and specifications.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_3.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>One detail in that blog post’s description of the harness is easy to miss. It includes an observability stack: logs, metrics, and traces exposed to the agent so it can tell when it is going off track. In addition to tests and specifications, there are metrics. That is a key component of the system.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_4.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Andrej Karpathy’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/karpathy/autoresearch\">auto-research project</a> shows the same pattern: models iteratively optimize against a validation loss metric. Same idea, different harness.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_5.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>What I want to convince you of is that a large portion of the harness is data science.</p>\n<p>Let’s take a step back and take stock of where we are.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_6.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Years ago, practitioners spent hours examining data, checking label alignment, and designing metrics. Today, we build on “vibes,” ask the model if it did a good job, and grab off-the-shelf metric libraries without looking at the data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_7.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>This shows up most around retrieval and evals. Without a data background, engineers fear what they don’t understand. They claim “RAG is dead” or “evals are dead,” yet build systems that depend on those concepts.</p>\n<p>The rest of this post walks through five eval pitfalls I see repeatedly, and what a data scientist would do differently in each case.</p>\n<hr />\n<h2>Generic Metrics</h2>\n<p>The first pitfall is generic metrics.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_9.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>It is tempting to reach for an eval framework and use its metrics off the shelf. The problem: you have no idea what is actually broken. Most teams put up a dashboard with helpfulness scores, coherence scores, hallucination scores. These sound reasonable. They are also generic enough to be useless for diagnosing your application’s failures.</p>\n<p>A data scientist would not adopt metrics off the shelf. They would explore the data, explore the traces, ask “what is actually breaking here?”, and figure out the highest-value thing to start measuring. There are infinite things to measure. You have to form hypotheses and iterate.</p>\n<p>The best medicine for this pitfall is looking at the data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_12.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>What does “looking at the data” mean in practice? It means reading traces. Code your own custom trace viewer so you can remove friction and customize the display for your domain’s quirks. Take notes on problems you find. Do error analysis: categorize failures, figure out what to prioritize, decide what to work on.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_13.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>When you look at your data, you end up driving toward application-specific metrics. Off-the-shelf similarity metrics like ROUGE or BLEU rarely fit LLM outputs. The metrics that matter look like “Calendar Scheduling Failure” or “Failure to Escalate To Human.”</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_14.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>If there is one thing to take away from this post: look at the data. How to look at it is a separate question and takes practice. This is the higest ROI activity you can engage in and is often skipped.</p>\n<hr />\n<h2>Unverified Judges</h2>\n<p>The second pitfall is unverified judges. A lot of teams use an LLM as a judge to figure out whether their AI is working. Most of the time, nobody has a good answer to “how do you trust the judge?”</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_15.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The default: ask an LLM to rate outputs on a scale and use the numbers. A data scientist would treat the judge like a classifier. You have a black box giving you a prediction. How do you trust it? Get human labels, partition the data into train/dev/test, and measure whether the classifier is trustworthy.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_18.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Source few-shot examples from your training set. Hill-climb your judge’s prompt against a dev set. Keep a test set aside to confirm you haven’t overfit. If you have done machine learning before, this is boring. But people are not doing it. Verifying classifiers has become a lost art in modern AI.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_19.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Treat your judge like a classifier in how you report results, too. Everywhere I go I see accuracy reported. If a failure mode occurs 5% of the time, accuracy hides the system’s true performance. Use precision and recall.</p>\n<hr />\n<h2>Bad Experimental Design</h2>\n<p>The third pitfall is experimental design. There are many dimensions to this. Here are two that come up most.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_20.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The first is constructing test sets. Most teams generate synthetic data by prompting an LLM: “Give me 50 test queries.” They get generic, unrepresentative data. A data scientist would look at real production data first, use hypotheses to determine which dimensions matter, then generate synthetic examples along those dimensions.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_23.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Ground synthetic data in real logs or traces. Figure out what dimensions to vary. Inject edge cases. Base the synthetic data off real data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_26.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The second is metric design. Teams bundle entire rubrics into a single LLM call and default to 1-5 Likert scales. A data scientist would reduce complexity, make each metric actionable, and tie it to a business outcome. Replace subjective scales with binary pass/fail on scoped criteria. Likert scales hide ambiguity and kick the can down the road on hard decisions about system performance.</p>\n<hr />\n<h2>Bad Data and Labels</h2>\n<p>The fourth pitfall is bad data and labels. Data scientists don’t trust the data. They don’t trust the labels. They don’t trust anything. They are skeptical by training. AI engineers at large have not built this muscle yet.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_27.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>When it comes to labeling, most teams make it someone else’s problem. Labeling seems unglamorous, so it gets delegated to the dev team or outsourced. A data scientist would insist that domain experts label the data, stay skeptical of the labels, and look at the data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_30.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>But labeling matters for a deeper reason than label quality. It is impossible to know what you want unless you look at the data. There is a concept called “criteria drift,” validated in a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">paper by Shreya Shankar and colleagues</a>: users need criteria to grade outputs, but grading outputs helps users define their criteria. People don’t know what they want until they see the LLM’s outputs. The labeling process itself surfaces what matters.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_31.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Data scientists champion this: get domain experts and product managers in front of raw data, not summary scores.</p>\n<hr />\n<h2>Automating Too Much</h2>\n<p>The fifth pitfall is automating too much. All of this is human work. The temptation is to automate it away.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_32.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_33.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>LLMs can help wire things up, write the plumbing, generate boilerplate for evaluations. They cannot look at the data for you, for the exact reason we just discussed: you don’t know what you want until you see the outputs.</p>\n<hr />\n<h2>Other Pitfalls</h2>\n<p>We did not have time to cover every pitfall. Here is a speed run through the rest.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_34.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Misusing similarity scores. Asking the judge vague questions like “is it helpful?” Making annotators read raw JSON. Reporting uncalibrated scores without confidence intervals. Data drift, overfitting, not sampling correctly, dashboards that don’t make sense.</p>\n<hr />\n<h2>The Mapping</h2>\n<p>If you zoom out, every pitfall above has the same root cause: missing a data science fundamental.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_35.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Reading traces and categorizing failures is Exploratory Data Analysis. Validating an LLM judge against human labels is Model Evaluation. Building representative test sets from production data is Experimental Design. Getting domain experts to label outputs is Data Collection. Monitoring whether your product works in production is Production ML. None of this is new!</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_36.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>This is a Python conference, so: Python remains the best toolset for looking at your data and dealing with data.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_37.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>I built an <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ai-evals-course/evals-skills\">evals skills plugin</a> that goes into more depth. Point it at your eval pipeline and it will tell you what you are doing wrong (or try its best to).</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/revenge/images/slide_38.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Always look at the data.</p>\n<p>If you enjoyed the memes in this talk, there are <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/memes/index.html#meme-images\">many more on my website</a>.</p>\n<p>If you want to go deeper on any of these topics, the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/revenge/#video-slides\">slides</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/revenge/#video-slides\">video</a> are below.</p>\n<p><em>Thanks to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.sh-reya.com/\">Shreya Shankar</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/BEBischof\">Bryan Bischof</a> for many conversations that shaped this talk.</em></p>\n<hr />\n<h2>Video &amp; Slides</h2>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=lA4MfpgF91Y\">www.youtube.com</a></p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.google.com/presentation/d/1Q7F7cr5PthTmsl6RQCofyBBlH23fh4FlaO_5tbWxbTs/edit?usp=sharing\">Link to the slides</a></p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hbr.org/2012/10/data-scientist-the-sexiest-job-of-the-21st-century\">https://hbr.org/2012/10/data-scientist-the-sexiest-job-of-the-21st-century</a> <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.forbes.com/sites/louiscolumbus/2018/01/29/data-scientist-is-the-best-job-in-america-according-glassdoors-2018-rankings/\">https://www.forbes.com/sites/louiscolumbus/2018/01/29/data-scientist-is-the-best-job-in-america-according-glassdoors-2018-rankings/</a> <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n<li id=\"fn-3\">\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.mckinsey.com/about-us/new-at-mckinsey-blog/ai-reinvents-tech-talent-opportunities\">https://www.mckinsey.com/about-us/new-at-mckinsey-blog/ai-reinvents-tech-talent-opportunities</a> <a href=\"#fnref-3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-03-26T07:00:00+00:00",
      "date_published": "2026-03-26T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/revenge/",
      "id": "urn:aggr:item:fa02f954c86d60581044ec55d347a20a4c667f63",
      "summary": "Is the heyday of the data scientist over? The Harvard Business Review once called it “The Sexiest Job of the 21st Century.”1 In tech, data scientist roles were often among the best paid.2 The job also demanded an unusual mix of skills:…",
      "tags": [
        "programming",
        "evals",
        "llms"
      ],
      "title": "The Revenge of the Data Scientist",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-03-26-the-revenge-of-the-data-scientist/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        },
        {
          "name": "Shreya Shankar"
        }
      ],
      "content_html": "<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/evals-skills/cover-original.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Today, Shreya Shankar and I are publishing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ai-evals-course/evals-skills\">evals skills</a>, a set of skills for AI product evals<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>.</p>\n<p>Eval tools often get in the way. They nudge you toward <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html#q-should-i-use-ready-to-use-evaluation-metrics\">generic off-the-shelf metrics</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/blog/posts/auto-evals.html\">fully automated evals</a> before you’ve looked at your data. These skills help you avoid common mistakes we’ve seen helping 50+ companies and teaching students in our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals\">AI Evals course</a>.</p>\n<p>There are many <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/revenge/index.html\">easily avoidable footguns</a> in evals. These skills help you avoid them.</p>\n<p><strong>evals-start</strong> is the entry point. It looks at your situation and routes you to the right skill. Most of the time it will send you to one of these two:</p>\n<ul>\n<li><strong>eval-audit</strong>, if you already have an eval pipeline. It inspects your setup and recommends next steps. <sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup></li>\n<li><strong>error-discovery</strong>, if you have traces but haven’t analyzed them yet. It builds a customized annotation interface and helps you sample traces intelligently. Shreya does a live walkthrough of using this skill <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/tqUDjc1HzO4\">here</a>.</li>\n</ul>\n<h2>The skills</h2>\n<p>Install the skills:</p>\n<pre><code><span class=\"code-snippet\">npx skills add https://github.com/ai-evals-course/evals-skills\n</span></code></pre>\n<p>Then give your agent this prompt:</p>\n<blockquote>\n<p>Run the evals-start skill from the evals plugin and follow the skill it picks. If it picks eval-audit, investigate each diagnostic area using a separate subagent in parallel, then synthesize the findings into a single report.</p>\n</blockquote>\n<p>If you’re experienced with evals, skip the router and pick the skill you need:</p>\n<table>\n<thead>\n<tr>\n<th>Skill</th>\n<th>What it does</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>evals-start</td>\n<td>Entry point. Routes to the skill that matches your situation</td>\n</tr>\n<tr>\n<td>eval-audit</td>\n<td>Audit an eval pipeline and surface problems with prioritized severity</td>\n</tr>\n<tr>\n<td>error-discovery</td>\n<td>Build a review app, select diverse samples, and organize your notes into failure modes</td>\n</tr>\n<tr>\n<td>generate-synthetic-data</td>\n<td>Create diverse synthetic test inputs using dimension-based tuple generation</td>\n</tr>\n<tr>\n<td>write-judge-prompt</td>\n<td>Design LLM-as-Judge evaluators for subjective quality criteria</td>\n</tr>\n<tr>\n<td>validate-evaluator</td>\n<td>Calibrate LLM judges against human labels using data splits, TPR/TNR, and bias correction</td>\n</tr>\n<tr>\n<td>evaluate-rag</td>\n<td>Evaluate retrieval and generation quality in RAG pipelines</td>\n</tr>\n<tr>\n<td>build-review-interface</td>\n<td>Build custom annotation interfaces for human trace review</td>\n</tr>\n</tbody>\n</table>\n<p>These skills are only a starting point. To make them better, tune them to be more specific to your data and domain.</p>\n<p>The repo is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ai-evals-course/evals-skills\">ai-evals-course/evals-skills</a>.</p>\n<p>You can find me on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/hamelhusain\">X</a> or email me through my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ai.hamel.dev\">newsletter</a>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Not foundation model benchmarks like MMLU or HELM that measure general LLM capabilities. Product evals measure whether <em>your</em> pipeline works on <em>your</em> task with <em>your</em> data. If you aren’t familiar with product-specific AI evals, check out my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html\">AI Evals FAQ</a>. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p>The audit isn’t a complete solution, but it will catch common problems. <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-03-02T08:00:00+00:00",
      "date_published": "2026-03-02T08:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/evals-skills/",
      "id": "urn:aggr:item:febe8bb9a9279695c7b9cfc445558d8ef0b5bbc4",
      "summary": "Today, Shreya Shankar and I are publishing evals skills, a set of skills for AI product evals1. Eval tools often get in the way. They nudge you toward generic off-the-shelf metrics and fully automated evals before you’ve looked at your…",
      "tags": [
        "programming",
        "ai",
        "evals"
      ],
      "title": "Evals Skills for Coding Agents",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-03-02-evals-skills-for-coding-agents/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Programmers love to proclaim they’ve found the best tool. Paul Graham called Lisp his “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paulgraham.com/avg.html\">secret weapon</a>.” DHH described Ruby as “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://rubyonrails.org/doctrine\">a magical glove that just fit my brain perfectly</a>.” Pieter Levels ships million-dollar products with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://lexfridman.com/pieter-levels-transcript/\">vanilla PHP and jQuery</a>.</p>\n<p>These declarations aren’t about the languages themselves. They’re about developers finding tools that fit how they think. When the environment clicks, you move fast.</p>\n<p>I had that experience with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.fast.ai/posts/2022-07-28-nbdev2.html\">nbdev</a>, a development environment for literate programming that I helped build and maintain<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>. I created hundreds of projects with it and was one of its <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=rX1yGxJijsI\">biggest</a> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/developer-skills/programming-languages-and-frameworks/nbdev-a-literate-programming-environment-that-democratizes-software-engineering-best-practices/\">proponents</a>.</p>\n<p>Today, I no longer use it. AI coding tools changed the trade-offs.</p>\n<h2>Fighting the AI</h2>\n<p>The beauty of nbdev is its workflow. You write code, documentation and tests in one source of truth: Jupyter notebooks. Afterwards, these notebooks are transpiled into a Python library and documentation website.</p>\n<p>This workflow is idiosyncratic. AI coding tools, trained on vast amounts of conventional source code, get confused. They struggle to differentiate between editing the notebook and editing the final source code. It feels like fighting the AI instead of working with it.</p>\n<p>I write software to solve problems, not to write code. I want to work in an environment where AI has the highest chance of success. With nbdev, I was swimming upstream.</p>\n<p>Some argue that AI tools encourage lazy thinking: that without guardrails, developers skip the hard work of breaking problems into steps. But thinking step-by-step is a human skill. Notebooks don’t force you to write clean code. AI tools don’t force you to think carefully. Discipline comes from the developer, not the environment.</p>\n<h2>Tools Don’t Matter As Much As I Thought</h2>\n<p>A central promise of literate programming is better documentation. By keeping code and docs in one place, you reduce the chance they become stale.</p>\n<p>Strangely, many nbdev projects lacked sufficient documentation for my taste. Sometimes, this helped me learn a codebase by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastpages.fast.ai/fastcore/\">contributing</a> to the docs. Other times, it was frustrating. This reinforced my belief that good documentation comes from effort, not tooling.</p>\n<p>This workflow is also less compelling now. AI can read a codebase without documentation and give you an overview on the fly. It can help maintain documentation that is separate from the code, handling the tedious parts. Keeping code and docs together isn’t the selling point it used to be.</p>\n<h2>Collaboration and Adoption</h2>\n<p>nbdev asks developers to adopt a different system. It does not meet them where they are. Cursor won because it felt familiar and let developers change their habits slowly, rather than demanding a new workflow on day one.</p>\n<p>I didn’t worry about collaboration as much before. But collaborating with AI is table stakes. The same impediments that get in the way of collaborating with humans tend to get in the way of collaborating with AI.</p>\n<p>Today, developers increasingly span greater scope. Backend people do frontend. PMs create prototypes. Everyone is more polyglot. Idiosyncratic frameworks isolate you from your team to a greater extent than ever before. Idiosyncratic tooling once had a hidden upside: it filtered for a certain kind of contributor. Now I believe it’s more of a liability.</p>\n<h2>What I’m Using Now</h2>\n<p>Because I have invested thousands of hours into nbdev, it’s difficult to admit there are better tools for the outcomes I want. But I must check my ego at the door<sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup>.</p>\n<p>My daily drivers now include Amp, Cursor, and Claude Code. I still enjoy notebooks, but only for data analysis, machine learning, or other exploratory workflows where the iterative, visual nature of notebooks shines.</p>\n<p>More importantly, AI has nudged me out of my previous “Python for everything” mindset. I now use different languages for different tasks. For web development, I prefer the Next.js stack. Using a notebook for web development (even with specialized tooling) adds unnecessary complexity for me.</p>\n<p>This isn’t arbitrary preference. AI performs best on code with abundant training data for specific domains. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/\">TypeScript recently overtook both Python and JavaScript on GitHub</a>, driven partly by the fact that typed languages make AI-generated code more reliable in production<sup class=\"footnote-ref\"><a href=\"#fn-3\" id=\"fnref-3\" data-footnote-ref>3</a></sup>. Even Jane Street, famous for its OCaml-heavy infrastructure, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.efinancialcareers.com/news/python-ocaml-jane-street-ai\">now uses Python</a> for machine learning and data work.</p>\n<h2>A Place for Joy</h2>\n<p>None of this means idiosyncratic tools are worthless. Lisp, Haskell, and APL each teach you something different about computing. Joy is a valid reason to choose a language, even with less AI support. It’s just not my focus right now. My joy resides in solving problems, and I want tools that maximize my leverage. For that, conventional wins.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>I <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/developer-skills/programming-languages-and-frameworks/nbdev-a-literate-programming-environment-that-democratizes-software-engineering-best-practices/\">joined the nbdev project in 2020</a> while at GitHub. That same year I built <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=cduXZwZaBbM\">fastpages</a>, a notebook blogging system that informed nbdev’s documentation approach. Along the way I contributed to tools like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/developer-skills/programming-languages-and-frameworks/learn-about-ghapi-a-new-third-party-python-client-for-the-github-api/\">ghapi</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastpages.fast.ai/fastcore/\">fastcore</a>. In 2022, I helped lead a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.fast.ai/posts/2022-07-28-nbdev2.html\">complete rewrite</a> of nbdev. I discussed the philosophy behind this work on the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://vanishinggradients.fireside.fm/9\">Vanishing Gradients Podcast</a> and at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=rX1yGxJijsI\">Data Council</a>. I was briefly interested in commercializing nbdev, but <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/nbdev/\">decided not to pursue it</a>. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n<li id=\"fn-2\">\n<p>Other top nbdev maintainers and power users like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/sgugger\">Sylvain Gugger</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/seeM\">Wasim Lorgat</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/isaac-flath\">Isaac Flath</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/muellerzr\">Zach Mueller</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ohmeow.com/\">Wayde Gilliam</a> have made similar moves. <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n</li>\n<li id=\"fn-3\">\n<p>Research shows 94% of LLM compilation errors in TypeScript come from type violations, suggesting type systems can guide better code generation. See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2504.09246\">Mündler et al., 2025</a>. <a href=\"#fnref-3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2026-01-18T08:00:00+00:00",
      "date_published": "2026-01-18T08:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/ai-stack/",
      "id": "urn:aggr:item:fa2b8f35e13ae5e8dda9ed63bf95df9b7c867811",
      "summary": "Programmers love to proclaim they’ve found the best tool. Paul Graham called Lisp his “secret weapon.” DHH described Ruby as “a magical glove that just fit my brain perfectly.” Pieter Levels ships million-dollar products with vanilla PHP…",
      "tags": [
        "programming",
        "ai",
        "nbdev",
        "tools"
      ],
      "title": "Why I Stopped Using nbdev",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2026-01-18-why-i-stopped-using-nbdev/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Over the past year, I’ve focused heavily on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html\">AI Evals</a>, both in my consulting work and teaching. A question I get constantly is, “What’s the best tool for evals?”. I’ve always resisted answering directly for two reasons. First, people focus too much on tools instead of the process, thinking the tool will be an off-the-shelf solution when it rarely is. Second, the tools change so quickly that comparisons become outdated immediately.</p>\n<p>Having used many of the popular eval tools, I can genuinely say that no single one is superior in every dimension. The “best” tool depends on your team’s skillset, technical stack, and maturity.</p>\n<p>Instead of a feature-by-feature comparison, I think it’s more valuable to show you <em>how</em> a panel of data scientists skilled in evals assesses these tools. As part of my AI Evals <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals?promoCode=evals-info-url\">course</a>, we had three of the most dominant vendors—Langsmith, Braintrust, and Arize Phoenix complete the same homework <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ai-evals-course/recipe-chatbot\">assignment</a>. This gave us a unique opportunity to see how they tackle the exact same challenge.</p>\n<p>We recorded the entire process and live commentary, which is available below. We think this might be helpful in learning about the kinds of things you should consider when selecting a tool for your team.</p>\n<p><em>Thanks to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.sh-reya.com/\">Shreya Shankar</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/BEBischof\">Bryan Bischof</a> for serving as the panelists (alongside me).</em></p>\n<h2>Langsmith</h2>\n<p>With <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/hwchase17\">Harrison Chase</a>, CEO of LangChain.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=y0vm_fjkejo\">www.youtube.com</a></p>\n<h2>Braintrust</h2>\n<p>With <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/waydegilliam\">Wayde Gilliam</a>, former developer relations at Braintrust.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=97iykOemOn4\">www.youtube.com</a></p>\n<h2>Arize Phoenix</h2>\n<p>With <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/sallyann-delucia-59a381172/\">SallyAnn DeLucia</a>, Technical AI Product Leader at Arize.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=wcYnzHJlUR0\">www.youtube.com</a></p>\n<hr />\n<h2>Criteria for Assessing AI Evals Tools</h2>\n<p>Here are themes that consistently surfaced during our review.</p>\n<h3>1. Workflow and Developer Experience</h3>\n<p>Reducing friction is more important than any single feature. Concretely, you should be mindful of the time it takes to go from observing a failure to iterating on a solution. For example, we appreciated the ability to go from viewing a single trace to experimenting with that same trace in a playground. For some teams with data-science backgrounds, a notebook-centric workflow is ideal as it provides transparency and control. This happens to be my preferred workflow as well.</p>\n<p>When considering a notebook-centric workflow, its important to pay attention to the ergonmics of the sdk. This often boils down to the quality of the documentation and integration with existing data tools.</p>\n<h3>2. Human-in-the-Loop Support</h3>\n<p>The best tools don’t try to automate away the human; they empower them. Since error analysis is the highest ROI activity in AI engineering, a tool’s ability to support efficient human review is paramount. Prioritize tools with first-class support for manual annotation and error analysis. As of this writing, one thing that is missing from many tools is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html#q-why-is-error-analysis-so-important-in-llm-evals-and-how-is-it-performed\">axial coding</a>.</p>\n<h3>3. Transparency and Control vs. “Magic”</h3>\n<p>Be deeply skeptical of features that promise full automation without human validation, as these can create a powerful and dangerous illusion of confidence. For example, be wary of features where an AI agent both creates an evaluation rubric and then immediately scores the outputs. This “stacking of abstractions” often hides flaws behind a high score. Favor tools that give you control and visibility.</p>\n<h3>4. Ecosystem Integration vs. Walled Gardens</h3>\n<p>An eval tool should fit your stack, not force you to fit its stack. Assess how well a tool integrates with your existing technologies. Also, beware of proprietary DSLs as they can add friction. Finally, the ability to export data into common formats for analysis in a variety of environments is a must-have.</p>\n<h3>Conclusion</h3>\n<p>The right choice of tool depends on your team’s workflow, skillset, and specific needs. I hope seeing how our panel approached this evaluation provides a better framework for making your own decision.</p>\n<p>As for me personally, I tend to use these tools as a backend data store and use Jupyter notebooks as well as my own <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html#q-should-i-build-a-custom-annotation-tool-or-use-something-off-the-shelf\">custom built</a> annotation interfaces for most of my needs.</p>\n<hr />\n<p>CautionAppendix: Vendor Snapshots (As of Mid-2025)</p>\n<p><strong>You should take these notes with a grain of salt. I recommend watch the videos above to get a sense of how we applied these criteria and where you might differ according to your neeeds.</strong></p>\n<h3>Langsmith Evaluation Notes</h3>\n<p><strong>Overall Sentiment</strong> The overall workflow is intuitive, especially for those new to formal evaluation processes. The UI guides you through creating datasets, running experiments, and annotating results.</p>\n<p><strong>Positive Feedback / What We Liked</strong></p>\n<ul>\n<li><strong>Seamless Workflow from Trace to Playground:</strong> The transition from inspecting a trace to experimenting with it in the playground is very smooth.</li>\n<li><strong>AI-Assisted Prompt Improvement:</strong> The “Prompt Canvas” feature is a powerful tool for prompt engineering.</li>\n<li><strong>Dataset Creation and Management:</strong> You can easily create datasets by uploading files, and the schema detection helps structure the data correctly.</li>\n<li><strong>Experimentation and Evaluation:</strong> The “Annotation Queue” is a dedicated interface for human review and labeling of traces, which is more efficient than using spreadsheets.</li>\n</ul>\n<p><strong>Critiques and Areas for Improvement</strong></p>\n<ul>\n<li><strong>Limited Side-by-Side Comparison:</strong> The UI doesn’t make it easy to see side-by-side comparisons of different prompt versions and their outputs.</li>\n<li><strong>UI/UX Concerns:</strong> The UI can feel a bit cluttered, with a lot of options and information presented at once.</li>\n<li><strong>Potential for Over-Automation:</strong> Features like AI-generated examples, while convenient, can lead to homogenous data.</li>\n</ul>\n<h3>Braintrust Evaluation Notes</h3>\n<p><strong>Overall Sentiment</strong> The panel had a generally positive view of Braintrust, highlighting its clean UI and structured approach to evaluations. The tool’s emphasis on human-in-the-loop workflows was a significant strength.</p>\n<p><strong>Positive Feedback / What We Liked</strong></p>\n<ul>\n<li><strong>Focus on a Structured Evals Process:</strong> The demonstration emphasized a solid, methodical approach, starting by involving subject-matter experts to create an initial dataset.</li>\n<li><strong>Clean and Intuitive User Interface (UI):</strong> The panel found the UI to be clean and easier to navigate than other tools, with a particularly readable trace viewing screen.</li>\n<li><strong>Strong Support for Human-in-the-Loop Workflows:</strong> The platform has dedicated UIs designed for human review and annotation, which is critical for creating high-quality datasets and performing error analysis.</li>\n<li><strong>The “Money Table”:</strong> After annotating traces with failure modes, the final dataset view is an actionable output that allows teams to quickly sort, filter, and quantify the most common failure modes.</li>\n</ul>\n<p><strong>Critiques and Areas for Improvement</strong></p>\n<ul>\n<li><strong>The “Loop” AI Scorer:</strong> The most significant concern was the “Loop” feature, an AI agent that creates an evaluation rubric and then immediately scores the outputs, which could lead to a false sense of security.</li>\n<li><strong>Reliance on a Proprietary Query Language (BTQL):</strong> The panel viewed the use of “BTQL” with mild skepticism, stating a preference for exporting data to a Jupyter notebook.</li>\n<li><strong>Clunky Data Workflows:</strong> The process for generating and refining synthetic data seemed inefficient, requiring downloading and re-uploading data between steps.</li>\n</ul>\n<h3>Arize Phoenix Evaluation Notes</h3>\n<p><strong>Overall Sentiment</strong> The panel had a generally positive view of Phoenix, with one panelist calling it one of his “favorite open source eval tools.” The tool is positioned as a developer-first, notebook-centric platform.</p>\n<p><strong>Positive Feedback / What We Liked</strong></p>\n<ul>\n<li><strong>Notebook-Centric Workflow:</strong> The entire evaluation process was driven from a Jupyter notebook, giving the developer transparency and control. The ability to export annotated data back into a Pandas DataFrame was a powerful feature.</li>\n<li><strong>UI &amp; Developer Experience:</strong> The prompt management UI was praised for being clear and easy to understand. The tight integration between traces and the “Playground” was also noted as a smooth workflow.</li>\n<li><strong>Open Source &amp; Local-First Approach:</strong> Phoenix can be run entirely locally, providing a sense of control and transparency. As an open-source tool, it was noted for being “hackable.”</li>\n</ul>\n<p><strong>Critiques and Areas for Improvement</strong></p>\n<ul>\n<li><strong>UI Readability:</strong> The text in the output panes was difficult to read during the demonstration, with a possible lack of markdown rendering for model outputs.</li>\n<li><strong>Metrics and Visualization:</strong> The tool displays point statistics for each run, but the panel found this of limited use and expressed a desire for aggregate visualizations like histograms to identify outliers.</li>\n<li><strong>Prompt Management and Testing:</strong> The prompt editor treats the system prompt as one large, monolithic block of text. A more component-based approach where individual instructions could be toggled on and off (“ablated”) would be preferable for systematic testing.</li>\n</ul>\n",
      "date_modified": "2025-10-01T07:00:00+00:00",
      "date_published": "2025-10-01T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/eval-tools/",
      "id": "urn:aggr:item:2ab6aa000ff3d99dd96403cbcbf7929b7a2f4adf",
      "summary": "Over the past year, I’ve focused heavily on AI Evals, both in my consulting work and teaching. A question I get constantly is, “What’s the best tool for evals?”. I’ve always resisted answering directly for two reasons. First, people focus…",
      "tags": [
        "programming",
        "ai",
        "evals"
      ],
      "title": "Selecting The Right AI Evals Tool",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2025-10-01-selecting-the-right-ai-evals-tool/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Most AI teams focus on the wrong things. Here’s a common scene from my consulting work:</p>\n<p>AI TEAM</p>\n<blockquote>\n<p>Here’s our agent architecture – we’ve got RAG here, a router there, and we’re using this new framework for…</p>\n</blockquote>\n<p>ME</p>\n<blockquote>\n<p>[Holding up my hand to pause the enthusiastic tech lead.]</p>\n<p>“Can you show me how you’re measuring if any of this actually works?”</p>\n</blockquote>\n<p>… Room goes quiet</p>\n<p>This scene has played out dozens of times over the last two years. Teams invest weeks building complex AI systems, but can’t tell me if their changes are helping or hurting.</p>\n<p>This isn’t surprising. With new tools and frameworks emerging weekly, it’s natural to focus on tangible things we can control – which vector database to use, which LLM provider to choose, which agent framework to adopt. But after helping 30+ companies build AI products, I’ve discovered the teams who succeed barely talk about tools at all. Instead, they obsess over measurement and iteration.</p>\n<p>In this post, I’ll show you exactly how these successful teams operate. You’ll learn:</p>\n<ol>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#the-most-common-mistake-skipping-error-analysis\">How error analysis consistently reveals the highest-ROI improvements</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#the-most-important-ai-investment-a-simple-data-viewer\">Why a simple data viewer is your most important AI investment</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#empower-domain-experts-to-write-prompts\">How to empower domain experts (not just engineers) to improve your AI</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#bootstrapping-your-ai-with-synthetic-data-is-effective-even-with-zero-users\">Why synthetic data is more effective than you think</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#maintaining-trust-in-evals-is-critical\">How to maintain trust in your evaluation system</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/#your-ai-roadmap-should-count-experiments-not-features\">Why your AI roadmap should count experiments, not features</a></li>\n</ol>\n<p>I’ll explain each of these topics with real examples. While every situation is unique, you’ll see patterns that apply regardless of your domain or team size.</p>\n<p>Let’s start by examining the most common mistake I see teams make – one that derails AI projects before they even begin.</p>\n<h2>1. The Most Common Mistake: Skipping Error Analysis</h2>\n<p>The “tools first” mindset is the most common mistake in AI development. Teams get caught up in architecture diagrams, frameworks, and dashboards while neglecting the process of actually understanding what’s working and what isn’t.</p>\n<p>One client proudly showed me this evaluation dashboard:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/dashboard.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The kind of dashboard that foreshadows failure.</p>\n<p>This is the “tools trap” – the belief that adopting the right tools or frameworks (in this case, generic metrics) will solve your AI problems. Generic metrics are worse than useless – they actively impede progress in two ways:</p>\n<p>First, they create a <strong>false sense of measurement and progress</strong>. Teams think they’re data-driven because they have dashboards, but they’re tracking vanity metrics that don’t correlate with real user problems. I’ve seen teams celebrate improving their “helpfulness score” by 10% while their actual users were still struggling with basic tasks. It’s like optimizing your website’s load time while your checkout process is broken – you’re getting better at the wrong thing.</p>\n<p>Second, too many metrics fragment your attention. Instead of focusing on the few metrics that matter for your specific use case, you’re trying to optimize multiple dimensions simultaneously. When everything is important, nothing is.</p>\n<p>The alternative? Error analysis - the single most valuable activity in AI development and consistently the highest-ROI activity. Let me show you what effective error analysis looks like in practice.</p>\n<h3>The Error Analysis Process</h3>\n<p>When Jacob, the founder of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://nurtureboss.io/\">Nurture Boss</a>, needed to improve their apartment-industry AI assistant, his team built a simple viewer to examine conversations between their AI and users. Next to each conversation was a space for open-ended notes about failure modes.</p>\n<p>After annotating dozens of conversations, clear patterns emerged. Their AI was struggling with date handling – failing 66% of the time when users said things like “let’s schedule a tour two weeks from now.”</p>\n<p>Instead of reaching for new tools, they: 1. Looked at actual conversation logs 2. Categorized the types of date-handling failures 3. Built specific tests to catch these issues 4. Measured improvement on these metrics</p>\n<p>The result? Their date handling success rate improved from 33% to 95%.</p>\n<p>Here’s Jacob explaining this process himself:</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=e2i6JbU2R-s\">www.youtube.com</a></p>\n<h3>Bottom-Up vs. Top-Down Analysis</h3>\n<p>When identifying error types, you can take either a “top-down” or “bottom-up” approach.</p>\n<p>The <strong>top-down</strong> approach starts with common metrics like “hallucination” or “toxicity” plus metrics unique to your task. While convenient, it often misses domain-specific issues.</p>\n<p>The more effective <strong>bottom-up</strong> approach forces you to look at actual data and let metrics naturally emerge. At NurtureBoss, we started with a spreadsheet where each row represented a conversation. We wrote open-ended notes on any undesired behavior. Then we used an LLM to build a taxonomy of common failure modes. Finally, we mapped each row to specific failure mode labels and counted the frequency of each issue.</p>\n<p>The results were striking - just three issues accounted for over 60% of all problems:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/pivot.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Excel Pivot Tables are a simple tool, but they work!</p>\n<ul>\n<li>Conversation flow issues (missing context, awkward responses)</li>\n<li>Handoff failures (not recognizing when to transfer to humans)</li>\n<li>Rescheduling problems (struggling with date handling)</li>\n</ul>\n<p>The impact was immediate. Jacob’s team had uncovered so many actionable insights that they needed several weeks just to implement fixes for the problems we’d already found.</p>\n<p>If you’d like to see error analysis in action, we recorded a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/qH1dZ8JLLdU\">live walkthrough here</a>.</p>\n<p>This brings us to a crucial question: How do you make it easy for teams to look at their data? The answer leads us to what I consider the most important investment any AI team can make…</p>\n<h2>2. The Most Important AI Investment: A Simple Data Viewer</h2>\n<p>The single most impactful investment I’ve seen AI teams make isn’t a fancy evaluation dashboard – it’s building a customized interface that lets anyone examine what their AI is actually doing. I emphasize <em>customized</em> because every domain has unique needs that off-the-shelf tools rarely address. When reviewing apartment leasing conversations, you need to see the full chat history and scheduling context. For real estate queries, you need the property details and source documents right there. Even small UX decisions – like where to place metadata or which filters to expose – can make the difference between a tool people actually use and one they avoid.</p>\n<p>I’ve watched teams struggle with generic labeling interfaces, hunting through multiple systems just to understand a single interaction. The friction adds up: clicking through to different systems to see context, copying error descriptions into separate tracking sheets, switching between tools to verify information. This friction doesn’t just slow teams down – it actively discourages the kind of systematic analysis that catches subtle issues.</p>\n<p>Teams with thoughtfully designed data viewers iterate 10x faster than those without them. And here’s the thing: <strong>these tools can be built in hours using AI-assisted development</strong> (like Cursor or Loveable). The investment is minimal compared to the returns.</p>\n<p>Let me show you what I mean. Here’s the data viewer built for NurtureBoss (which we discussed earlier):</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/nboss_filter.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Search and filter sessions</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/nboss_annotate.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Annotate and add notes</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/nboss_analysis.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Aggregate and count errors</p>\n<p>Here’s what makes a good data annotation tool:</p>\n<ol>\n<li>Show all context in one place. Don’t make users hunt through different systems to understand what happened.\\</li>\n<li>Make feedback trivial to capture. One-click correct/incorrect buttons beat lengthy forms.</li>\n<li>Capture open-ended feedback. This lets you capture nuanced issues that don’t fit into a pre-defined taxonomy.</li>\n<li>Enable quick filtering and sorting. Teams need to easily dive into specific error types. In the example above, NurtureBoss can quickly filter by the channel (voice, text, chat) or the specific property they want to look at quickly.</li>\n<li>Have hotkeys that allow users to navigate between data examples and annotate without clicking.</li>\n</ol>\n<p>It doesn’t matter what web frameworks you use - use whatever you are familiar with. Because I’m a python developer, my current favorite web framework is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastht.ml/docs/\">FastHTML</a> coupled with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2025-01-15-monsterui.html\">MonsterUI</a>, because it allows me to define the back-end and front-end code in one small python file.</p>\n<p>The key is starting somewhere, even if it’s simple. I’ve found custom web apps provide the best experience, but if you’re just beginning, a spreadsheet is better than nothing. As your needs grow, you can evolve your tools accordingly.</p>\n<p>This brings us to another counter-intuitive lesson: the people best positioned to improve your AI system are often the ones who know the least about AI.</p>\n<h2>3. Empower Domain Experts To Write Prompts</h2>\n<p>I recently worked with an education startup building an interactive learning platform with LLMs. Their product manager, a learning design expert, would create detailed PowerPoint decks explaining pedagogical principles and example dialogues. She’d present these to the engineering team, who would then translate her expertise into prompts.</p>\n<p>But here’s the thing: prompts are just English. Having a learning expert communicate teaching principles through PowerPoint, only for engineers to translate that back into English prompts, created unnecessary friction. The most successful teams flip this model by giving domain experts tools to write and iterate on prompts directly.</p>\n<h3>Build Bridges, Not Gatekeepers</h3>\n<p>Prompt playgrounds are a great starting point for this. Tools like Arize, Langsmith and Braintrust let teams quickly test different prompts, feed in example datasets, and compare results. Here are some screenshots of these tools:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/pp_phoenix2.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Arize Phoenix</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/pp_langsmith.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>LangSmith</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/pp_bt.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Braintrust</p>\n<p>But there’s a crucial next step that many teams miss: integrating prompt development into their application context. Most AI applications aren’t just prompts – They commonly involve RAG systems pulling from your knowledge base, agent orchestration coordinating multiple steps, and application-specific business logic. The most effective teams I’ve worked with go beyond standalone playgrounds. They build what I call <em><strong>integrated prompt environments</strong></em> – essentially admin versions of their actual user interface that expose prompt editing.</p>\n<p>Here’s an illustration of what an integrated prompt environment might look like for a real estate AI assistant:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/ipe_before.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The UI that users (real estate agents) see.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/ipe_after.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The same UI, but with an “admin mode”used by the engineering &amp; product team to iterate on the prompt and debug issues.</p>\n<h3>Tips For Communicating With Domain Experts</h3>\n<p>There’s another barrier that often prevents domain experts from contributing effectively: unnecessary jargon. I was working with an education startup where engineers, product managers, and learning specialists were talking past each other in meetings. The engineers kept saying, “We’re going to build an agent that does XYZ,” when really the job to be done was writing a prompt. This created an artificial barrier – the learning specialists, who were the actual domain experts, felt like they couldn’t contribute because they didn’t understand “agents.”</p>\n<p>This happens everywhere. I’ve seen it with lawyers at legal tech companies, psychologists at mental health startups, and doctors at healthcare firms. The magic of LLMs is that they make AI accessible through natural language, but we often destroy that advantage by wrapping everything in technical terminology.</p>\n<p>Here’s a simple example of how to translate common AI jargon:</p>\n<table>\n<thead>\n<tr>\n<th>Instead of saying…</th>\n<th>Say…</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>“We’re implementing a RAG approach”</td>\n<td>“We’re making sure the model has the right context to answer questions”</td>\n</tr>\n<tr>\n<td>“We need to prevent prompt injection”</td>\n<td>“We need to make sure users can’t trick the AI into ignoring our rules”</td>\n</tr>\n<tr>\n<td>“Our model suffers from hallucination issues”</td>\n<td>“Sometimes the AI makes things up, so we need to check its answers”</td>\n</tr>\n</tbody>\n</table>\n<p>This doesn’t mean dumbing things down – it means being precise about what you’re actually doing. When you say “we’re building an agent,” what specific capability are you adding? Is it function calling? Tool use? Or just a better prompt? Being specific helps everyone understand what’s actually happening.</p>\n<p>There’s nuance here. Technical terminology exists for a reason – it provides precision when talking with other technical stakeholders. The key is adapting your language to your audience.</p>\n<p>The challenge many teams raise at this point is: “This all sounds great, but what if we don’t have any data yet? How can we look at examples or iterate on prompts when we’re just starting out?” That’s what we’ll talk about next.</p>\n<h2>4. Bootstrapping Your AI With Synthetic Data Is Effective (Even With Zero Users)</h2>\n<p>One of the most common roadblocks I hear from teams is: “We can’t do proper evaluation because we don’t have enough real user data yet.” This creates a chicken-and-egg problem – you need data to improve your AI, but you need a decent AI to get users who generate that data.</p>\n<p>Fortunately, there’s a solution that works surprisingly well: synthetic data. LLMs can generate realistic test cases that cover the range of scenarios your AI will encounter.</p>\n<p>As I wrote in my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/#generating-data\">LLM-as-a-Judge blog post</a>, synthetic data can be remarkably effective for evaluation. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/bryan-bischof/\">Bryan Bischof</a>, the former Head of AI at Hex, put it perfectly:</p>\n<blockquote>\n<p>“LLMs are surprisingly good at generating excellent - and diverse - examples of user prompts. This can be relevant for powering application features, and sneakily, for building Evals. If this sounds a bit like the Large Language Snake is eating its tail, I was just as surprised as you! All I can say is: it works, ship it.”</p>\n</blockquote>\n<h3>A Framework for Generating Realistic Test Data</h3>\n<p>The key to effective synthetic data is choosing the right dimensions to test. While these dimensions will vary based on your specific needs, I find it helpful to think about three broad categories:</p>\n<ol>\n<li><strong>Features</strong>: What capabilities does your AI need to support?</li>\n<li><strong>Scenarios</strong>: What situations will it encounter?</li>\n<li><strong>User Personas</strong>: Who will be using it and how?</li>\n</ol>\n<p>These aren’t the only dimensions you might care about – you might also want to test different tones of voice, levels of technical sophistication, or even different locales and languages. The important thing is identifying dimensions that matter for your specific use case.</p>\n<p>For a real estate CRM AI assistant I worked on with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.rechat.com/\">Rechat</a>, we defined these dimensions like this:</p>\n<pre><code><span class=\"code-snippet\">features = [\n    &quot;property search&quot;,      # Finding listings matching criteria\n    &quot;market analysis&quot;,      # Analyzing trends and pricing\n    &quot;scheduling&quot;,          # Setting up property viewings\n    &quot;follow-up&quot;           # Post-viewing communication\n]\n\nscenarios = [\n    &quot;exact match&quot;,         # One perfect listing match\n    &quot;multiple matches&quot;,    # Need to help user narrow down\n    &quot;no matches&quot;,         # Need to suggest alternatives\n    &quot;invalid criteria&quot;     # Help user correct search terms\n]\n\npersonas = [\n    &quot;first_time_buyer&quot;,    # Needs more guidance and explanation\n    &quot;investor&quot;,           # Focused on numbers and ROI\n    &quot;luxury_client&quot;,      # Expects white-glove service\n    &quot;relocating_family&quot;   # Has specific neighborhood/school needs\n]\n</span></code></pre>\n<p>But having these dimensions defined is only half the battle. The real challenge is ensuring your synthetic data actually triggers the scenarios you want to test. This requires two things:</p>\n<ol>\n<li>A test database with enough variety to support your scenarios</li>\n<li>A way to verify that generated queries actually trigger intended scenarios</li>\n</ol>\n<p>For Rechat, we maintained a test database of listings that we knew would trigger different edge cases. Some teams prefer to use an anonymized copy of production data, but either way, you need to ensure your test data has enough variety to exercise the scenarios you care about.</p>\n<p>Here’s an example of how we might use these dimensions with real data to generate test cases for the property search feature (this is just pseudo-code, and very illustrative):</p>\n<pre><code><span class=\"code-snippet\" data-language=\"YAML\"><span class=\"syntax-source syntax-yaml\"><span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">def generate_search_query(scenario, persona, listing_db)</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span>\n    <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span><span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>Generate a realistic user query about listings<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span><span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span>\n    <span class=\"syntax-comment syntax-line syntax-number-sign syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-line syntax-number-sign syntax-yaml\">#</span> Pull real listing data to ground the generation\n</span>    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">sample_listings = listing_db.get_sample_listings(</span>\n        <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">price_range=persona.price_range,</span>\n        <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">location=persona.preferred_areas</span>\n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">)</span>\n    \n    <span class=\"syntax-comment syntax-line syntax-number-sign syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-line syntax-number-sign syntax-yaml\">#</span> Verify we have listings that will trigger our scenario\n</span>    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">if scenario == &quot;multiple_matches&quot; and len(sample_listings) &lt; 2</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span>\n        <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">raise ValueError(&quot;Need multiple listings for this scenario&quot;)</span>\n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">if scenario == &quot;no_matches&quot; and len(sample_listings) &gt; 0</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span>\n        <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">raise ValueError(&quot;Found matches when testing no-match scenario&quot;)</span>\n    \n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">prompt = f&quot;&quot;&quot;</span>\n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">You are an expert real estate agent who is searching for listings. You are given a customer type and a scenario.</span>\n    \n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">Your job is to generate a natural language query you would use to search these listings.</span>\n    \n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">Context</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span>\n    <span class=\"syntax-punctuation syntax-definition syntax-block syntax-sequence syntax-item syntax-yaml\">-</span> <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">Customer type</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-meta syntax-flow-mapping syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-begin syntax-yaml\">{</span><span class=\"syntax-string syntax-unquoted syntax-plain syntax-in syntax-yaml\">persona.description</span><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-end syntax-yaml\">}</span></span>\n    <span class=\"syntax-punctuation syntax-definition syntax-block syntax-sequence syntax-item syntax-yaml\">-</span> <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">Scenario</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-meta syntax-flow-mapping syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-begin syntax-yaml\">{</span><span class=\"syntax-string syntax-unquoted syntax-plain syntax-in syntax-yaml\">scenario</span><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-end syntax-yaml\">}</span></span>\n    \n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">Use these actual listings as reference</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span>\n    <span class=\"syntax-meta syntax-flow-mapping syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-begin syntax-yaml\">{</span><span class=\"syntax-string syntax-unquoted syntax-plain syntax-in syntax-yaml\">format_listings(sample_listings)</span><span class=\"syntax-punctuation syntax-definition syntax-mapping syntax-end syntax-yaml\">}</span></span>\n    \n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">The query should reflect the customer type and the scenario.</span>\n\n    <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">Example query</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\">Find homes in the 75019 zip code, 3 bedrooms, 2 bathrooms, price range $750k - $1M for an investor.</span>\n    <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span><span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>\n    return generate_with_llm(prompt)\n</span></span></span></code></pre>\n<p>This produced realistic queries like:</p>\n<table>\n<thead>\n<tr>\n<th>Feature</th>\n<th>Scenario</th>\n<th>Persona</th>\n<th>Generated Query</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>property search</td>\n<td>multiple matches</td>\n<td>first_time_buyer</td>\n<td>“Looking for 3-bedroom homes under $500k in the Riverside area. Would love something close to parks since we have young kids.”</td>\n</tr>\n<tr>\n<td>market analysis</td>\n<td>no matches</td>\n<td>investor</td>\n<td>“Need comps for 123 Oak St. Specifically interested in rental yield comparison with similar properties in a 2-mile radius.”</td>\n</tr>\n</tbody>\n</table>\n<p>The key to useful synthetic data is grounding it in real system constraints. For the real-estate AI assistant, this means:</p>\n<ol>\n<li>Using real listing IDs and addresses from their database</li>\n<li>Incorporating actual agent schedules and availability windows</li>\n<li>Respecting business rules like showing restrictions and notice periods</li>\n<li>Including market-specific details like HOA requirements or local regulations</li>\n</ol>\n<p>We then feed these test cases through Lucy and log the interactions. This gives us a rich dataset to analyze, showing exactly how the AI handles different situations with real system constraints. This approach helped us fix issues before they affected real users.</p>\n<p>Sometimes you don’t have access to a production database, especially for new products. In these cases, use LLMs to generate both test queries and the underlying test data. For a real estate AI assistant, this might mean creating synthetic property listings with realistic attributes – prices that match market ranges, valid addresses with real street names, and amenities appropriate for each property type. The key is grounding synthetic data in real-world constraints to make it useful for testing. The specifics of generating robust synthetic databases are beyond the scope of this post.</p>\n<h3>Guidelines for Using Synthetic Data</h3>\n<p>When generating synthetic data, follow these key principles to ensure it’s effective:</p>\n<ol>\n<li>\n<p><strong>Diversify your dataset</strong>: Create examples that cover a wide range of features, scenarios, and personas. As I wrote in my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/\">LLM-as-a-Judge post</a>, this diversity helps you identify edge cases and failure modes you might not anticipate otherwise.</p>\n</li>\n<li>\n<p><strong>Generate user inputs, not outputs</strong>: Use LLMs to generate realistic user queries or inputs, not the expected AI responses. This prevents your synthetic data from inheriting the biases or limitations of the generating model.</p>\n</li>\n<li>\n<p><strong>Incorporate real system constraints</strong>: Ground your synthetic data in actual system limitations and data. For example, when testing a scheduling feature, use real availability windows and booking rules.</p>\n</li>\n<li>\n<p><strong>Verify scenario coverage</strong>: Ensure your generated data actually triggers the scenarios you want to test. A query intended to test “no matches found” should actually return zero results when run against your system.</p>\n</li>\n<li>\n<p><strong>Start simple, then add complexity</strong>: Begin with straightforward test cases before adding nuance. This helps isolate issues and establish a baseline before tackling edge cases.</p>\n</li>\n</ol>\n<p>This approach isn’t just theoretical – it’s been proven in production across dozens of companies. What often starts as a stopgap measure becomes a permanent part of the evaluation infrastructure, even after real user data becomes available.</p>\n<p>Let’s look at how to maintain trust in your evaluation system as you scale…</p>\n<h2>5. Maintaining Trust In Evals Is Critical</h2>\n<p>This is a pattern I’ve seen repeatedly: teams build evaluation systems, then gradually lose faith in them. Sometimes it’s because the metrics don’t align with what they observe in production. Other times, it’s because the evaluations become too complex to interpret. Either way, the result is the same – the team reverts to making decisions based on gut feeling and anecdotal feedback, undermining the entire purpose of having evaluations.</p>\n<p>Maintaining trust in your evaluation system is just as important as building it in the first place. Here’s how the most successful teams approach this challenge:</p>\n<h3>Understanding Criteria Drift</h3>\n<p>One of the most insidious problems in AI evaluation is “criteria drift” – a phenomenon where evaluation criteria evolve as you observe more model outputs. In their paper <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">“Who Validates the Validators?”</a>, Shankar et al. describe this phenomenon:</p>\n<blockquote>\n<p>“To grade outputs, people need to externalize and define their evaluation criteria; however, the process of grading outputs helps them to define that very criteria.”</p>\n</blockquote>\n<p>This creates a paradox: you can’t fully define your evaluation criteria until you’ve seen a wide range of outputs, but you need criteria to evaluate those outputs in the first place. In other words, <strong>it is impossible to completely determine evaluation criteria prior to human judging of LLM outputs</strong>.</p>\n<p>I’ve observed this firsthand when working with Phillip Carter at Honeycomb on their <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.honeycomb.io/blog/introducing-query-assistant\">Query Assistant</a> feature. As we evaluated the AI’s ability to generate database queries, Phillip noticed something interesting:</p>\n<blockquote>\n<p>“Seeing how the LLM breaks down its reasoning made me realize I wasn’t being consistent about how I judged certain edge cases.”</p>\n</blockquote>\n<p>The process of reviewing AI outputs helped him articulate his own evaluation standards more clearly. This isn’t a sign of poor planning – it’s an inherent characteristic of working with AI systems that produce diverse and sometimes unexpected outputs.</p>\n<p>The teams that maintain trust in their evaluation systems embrace this reality rather than fighting it. They treat evaluation criteria as living documents that evolve alongside their understanding of the problem space. They also recognize that different stakeholders might have different (sometimes contradictory) criteria, and they work to reconcile these perspectives rather than imposing a single standard.</p>\n<h3>Creating Trustworthy Evaluation Systems</h3>\n<p>So how do you build evaluation systems that remain trustworthy despite criteria drift? Here are the approaches I’ve found most effective:</p>\n<h4>1. Favor Binary Decisions Over Arbitrary Scales</h4>\n<p>As I wrote in my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/#why-are-simple-passfail-metrics-important\">LLM-as-a-Judge post</a>, binary decisions provide clarity that more complex scales often obscure. When faced with a 1-5 scale, evaluators frequently struggle with the difference between a 3 and a 4, introducing inconsistency and subjectivity. What exactly distinguishes “somewhat helpful” from “helpful”? These boundary cases consume disproportionate mental energy and create noise in your evaluation data. And even when businesses use a 1-5 scale, they inevitably ask where to draw the line for “good enough” or to trigger intervention, forcing a binary decision anyway.</p>\n<p>In contrast, a binary pass/fail forces evaluators to make a clear judgment: did this output achieve its purpose or not? This clarity extends to measuring progress – a 10% increase in passing outputs is immediately meaningful, while a 0.5-point improvement on a 5-point scale requires interpretation.</p>\n<p>I’ve found that teams who resist binary evaluation often do so because they want to capture nuance. But nuance isn’t lost – it’s just moved to the qualitative critique that accompanies the judgment. The critique provides rich context about why something passed or failed, and what specific aspects could be improved, while the binary decision creates actionable clarity about whether improvement is needed at all.</p>\n<h4>2. Enhance Binary Judgments With Detailed Critiques</h4>\n<p>While binary decisions provide clarity, they work best when paired with detailed critiques that capture the nuance of why something passed or failed. This combination gives you the best of both worlds: clear, actionable metrics and rich contextual understanding.</p>\n<p>For example, when evaluating a response that correctly answers a user’s question but contains unnecessary information, a good critique might read:</p>\n<blockquote>\n<p>“The AI successfully provided the market analysis requested (PASS), but included excessive detail about neighborhood demographics that wasn’t relevant to the investment question. This makes the response longer than necessary and potentially distracting.”</p>\n</blockquote>\n<p>These critiques serve multiple functions beyond just explanation. They force domain experts to externalize implicit knowledge – I’ve seen legal experts move from vague feelings that something “doesn’t sound right” to articulating specific issues with citation formats or reasoning patterns that can be systematically addressed.</p>\n<p>When included as few-shot examples in judge prompts, these critiques improve the LLM’s ability to reason about complex edge cases. I’ve found this approach often yields 15-20% higher agreement rates between human and LLM evaluations compared to prompts without example critiques. The critiques also provide excellent raw material for generating high-quality synthetic data, creating a flywheel for improvement.</p>\n<h4>3. Measure Alignment Between Automated Evals and Human Judgment</h4>\n<p>If you’re using LLMs to evaluate outputs (which is often necessary at scale), it’s crucial to regularly check how well these automated evaluations align with human judgment.</p>\n<p>This is particularly important given our natural tendency to over-trust AI systems. As Shankar et al. note in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">“Who Validates the Validators?”</a>, the lack of tools to validate evaluator quality is concerning</p>\n<blockquote>\n<p>Research shows people tend to over-rely and over-trust AI systems. For instance, in one high profile incident, researchers from MIT posted a pre-print on arXiv claiming that GPT-4 could ace the MIT EECS exam. Within hours, [the] work [was] debunked … citing problems arising from over-reliance on GPT-4 to grade itself.”</p>\n</blockquote>\n<p>This over-trust problem extends beyond self-evaluation. Research has shown that LLMs can be biased by simple factors like the ordering of options in a set, or even seemingly innocuous formatting changes in prompts. Without rigorous human validation, these biases can silently undermine your evaluation system.</p>\n<p>When working with Honeycomb, we tracked agreement rates between our LLM-as-a-judge and Phillip’s evaluations:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/field-guide/images/score.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Agreement rates between LLM evaluator and human expert. More details <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/#automated-evaluation-w-llms\">here</a>.</p>\n<p>It took three iterations to achieve &gt;90% agreement, but this investment paid off in a system the team could trust. Without this validation step, automated evaluations often drift from human expectations over time, especially as the distribution of inputs changes. You can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/#automated-evaluation-w-llms\">read more about this here</a>.</p>\n<p>Tools like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/writing/aligneval/\">Eugene Yan’s AlignEval</a> demonstrate this alignment process beautifully. It provides a simple interface where you upload data, label examples with a binary “good” or “bad,” and then evaluate LLM-based judges against those human judgments. What makes it effective is how it streamlines the workflow – you can quickly see where automated evaluations diverge from your preferences, refine your criteria based on these insights, and measure improvement over time. This approach reinforces that alignment isn’t a one-time setup but an ongoing conversation between human judgment and automated evaluation.</p>\n<h3>Scaling Without Losing Trust</h3>\n<p>As your AI system grows, you’ll inevitably face pressure to reduce the human effort involved in evaluation. This is where many teams go wrong – they automate too much, too quickly, and lose the human connection that keeps their evaluations grounded.</p>\n<p>The most successful teams take a more measured approach:</p>\n<ol>\n<li>\n<p><strong>Start with high human involvement</strong>: In the early stages, have domain experts evaluate a significant percentage of outputs.</p>\n</li>\n<li>\n<p><strong>Study alignment patterns</strong>: Rather than automating evaluation, focus on understanding where automated evaluations align with human judgment and where they diverge. This helps you identify which types of cases need more careful human attention.</p>\n</li>\n<li>\n<p><strong>Use strategic sampling</strong>: Rather than evaluating every output, use statistical techniques to sample outputs that provide the most information, particularly focusing on areas where alignment is weakest.</p>\n</li>\n<li>\n<p><strong>Maintain regular calibration</strong>: Even as you scale, continue to compare automated evaluations against human judgment regularly, using these comparisons to refine your understanding of when to trust automated evaluations.</p>\n</li>\n</ol>\n<p>Scaling evaluation isn’t just about reducing human effort – it’s about directing that effort where it adds the most value. By focusing human attention on the most challenging or informative cases, you can maintain quality even as your system grows.</p>\n<p>Now that we’ve covered how to maintain trust in your evaluations, let’s talk about a fundamental shift in how you should approach AI development roadmaps…</p>\n<h2>6. Your AI Roadmap Should Count Experiments, Not Features</h2>\n<p>If you’ve worked in software development, you’re familiar with traditional roadmaps: a list of features with target delivery dates. Teams commit to shipping specific functionality by specific deadlines, and success is measured by how closely they hit those targets.</p>\n<p>This approach fails spectacularly with AI.</p>\n<p>I’ve watched teams commit to roadmaps like “Launch sentiment analysis by Q2” or “Deploy agent-based customer support by end of year,” only to discover that the technology simply isn’t ready to meet their quality bar. They either ship something subpar to hit the deadline or miss the deadline entirely. Either way, trust erodes.</p>\n<p>The fundamental problem is that traditional roadmaps assume we know what’s possible. With conventional software, that’s often true – given enough time and resources, you can build most features reliably. With AI, especially at the cutting edge, you’re constantly testing the boundaries of what’s feasible.</p>\n<h3>Experiments vs. Features</h3>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/bryan-bischof/\">Bryan Bischof</a>, Former Head of AI at Hex, introduced me to what he calls a “capability funnel” approach to AI roadmaps. This strategy reframes how we think about AI development progress.</p>\n<p>Instead of defining success as shipping a feature, the capability funnel breaks down AI performance into progressive levels of utility. At the top of the funnel is the most basic functionality – can the system respond at all? At the bottom is fully solving the user’s job to be done. Between these points are various stages of increasing usefulness.</p>\n<p>For example, in a query assistant, the capability funnel might look like: 1. Can generate syntactically valid queries (basic functionality) 2. Can generate queries that execute without errors 3. Can generate queries that return relevant results 4. Can generate queries that match user intent 5. Can generate optimal queries that solve the user’s problem (complete solution)</p>\n<p>This approach acknowledges that AI progress isn’t binary – it’s about gradually improving capabilities across multiple dimensions. It also provides a framework for measuring progress even when you haven’t reached the final goal.</p>\n<p>The most successful teams I’ve worked with structure their roadmaps around experiments rather than features. Instead of committing to specific outcomes, they commit to a cadence of experimentation, learning, and iteration.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/\">Eugene Yan</a>, an applied scientist at Amazon, shared how he approaches ML project planning with leadership - a process that, while originally developed for traditional machine learning, applies equally well to modern LLM development:</p>\n<blockquote>\n<p>“Here’s a common timeline. First, I take two weeks to do a data feasibility analysis, i.e”do I have the right data?” […] Then I take an additional month to do a technical feasibility analysis, i.e “can AI solve this?” After that, if it still works I’ll spend six weeks building a prototype we can A/B test.”</p>\n</blockquote>\n<p>While LLMs might not require the same kind of feature engineering or model training as traditional ML, the underlying principle remains the same: time-box your exploration, establish clear decision points, and focus on proving feasibility before committing to full implementation. This approach gives leadership confidence that resources won’t be wasted on open-ended exploration, while giving the team the freedom to learn and adapt as they go.</p>\n<h3>The Foundation: Evaluation Infrastructure</h3>\n<p>The key to making an experiment-based roadmap work is having robust evaluation infrastructure. Without it, you’re just guessing whether your experiments are working. With it, you can rapidly iterate, test hypotheses, and build on successes.</p>\n<p>I saw this firsthand during the early development of GitHub Copilot. What most people don’t realize is that the team invested heavily in building sophisticated offline evaluation infrastructure. They created systems that could test code completions against a very large corpus of repositories on GitHub, leveraging unit tests that already existed in high-quality codebases as an automated way to verify completion correctness. This was a massive engineering undertaking – they had to build systems that could clone repositories at scale, set up their environments, run their test suites, and analyze the results, all while handling the incredible diversity of programming languages, frameworks, and testing approaches.</p>\n<p>This wasn’t wasted time—it was the foundation that accelerated everything. With solid evaluation in place, the team ran thousands of experiments, quickly identified what worked, and could say with confidence “this change improved quality by X%” instead of relying on gut feelings. While the upfront investment in evaluation feels slow, it prevents endless debates about whether changes help or hurt, and dramatically speeds up innovation later.</p>\n<h3>Communicating This to Stakeholders</h3>\n<p>The challenge, of course, is that executives often want certainty. They want to know when features will ship and what they’ll do. How do you bridge this gap?</p>\n<p>The key is to shift the conversation from outputs to outcomes. Instead of promising specific features by specific dates, commit to a process that will maximize the chances of achieving the desired business outcomes.</p>\n<p>Eugene shared how he handles these conversations:</p>\n<blockquote>\n<p>“I try to reassure leadership with timeboxes. At the end of three months, if it works out, then we move it to production. At any step of the way, if it doesn’t work out, we pivot.”</p>\n</blockquote>\n<p>This approach gives stakeholders clear decision points while acknowledging the inherent uncertainty in AI development. It also helps manage expectations about timelines – instead of promising a feature in six months, you’re promising a clear understanding of whether that feature is feasible in three months.</p>\n<p>Bryan’s capability funnel approach provides another powerful communication tool. It allows teams to show concrete progress through the funnel stages, even when the final solution isn’t ready. It also helps executives understand where problems are occurring and make informed decisions about where to invest resources.</p>\n<h3>Build a Culture of Experimentation Through Failure Sharing</h3>\n<p>Perhaps the most counterintuitive aspect of this approach is the emphasis on learning from failures. In traditional software development, failures are often hidden or downplayed. In AI development, they’re the primary source of learning.</p>\n<p>Eugene operationalizes this at his organization through what he calls a “fifteen-five” – a weekly update that takes fifteen minutes to write and five minutes to read:</p>\n<blockquote>\n<p>“In my fifteen-fives, I document my failures and my successes. Within our team, we also have weekly”no-prep sharing sessions” where we discuss what we’ve been working on and what we’ve learned. When I do this, I go out of my way to share failures.”</p>\n</blockquote>\n<p>This practice normalizes failure as part of the learning process. It shows that even experienced practitioners encounter dead ends, and it accelerates team learning by sharing those experiences openly. And by celebrating the process of experimentation rather than just the outcomes, teams create an environment where people feel safe taking risks and learning from failures.</p>\n<h3>A Better Way Forward</h3>\n<p>So what does an experiment-based roadmap look like in practice? Here’s a simplified example from a content moderation project Eugene worked on:</p>\n<blockquote>\n<p>“I was asked to do content moderation. I said, ‘It’s uncertain whether we’ll meet that goal. It’s uncertain even if that goal is feasible with our data, or what machine learning techniques would work. But here’s my experimentation roadmap. Here are the techniques I’m gonna try, and I’m gonna update you at a two-week cadence.’”</p>\n</blockquote>\n<p>The roadmap didn’t promise specific features or capabilities. Instead, it committed to a systematic exploration of possible approaches, with regular check-ins to assess progress and pivot if necessary.</p>\n<p>The results were telling:</p>\n<blockquote>\n<p>“For the first two to three months, nothing worked. […] And then [a breakthrough] came out. […] Within a month, that problem was solved. So you can see that in the first quarter or even four months, it was going nowhere. […] But then you can also see that all of a sudden, some new technology comes along, some new paradigm, some new reframing comes along that just [solves] 80% of [the problem].”</p>\n</blockquote>\n<p>This pattern – long periods of apparent failure followed by breakthroughs – is common in AI development. Traditional feature-based roadmaps would have killed the project after months of “failure,” missing the eventual breakthrough.</p>\n<p>By focusing on experiments rather than features, teams create space for these breakthroughs to emerge. They also build the infrastructure and processes that make breakthroughs more likely – data pipelines, evaluation frameworks, and rapid iteration cycles.</p>\n<p>The most successful teams I’ve worked with start by building evaluation infrastructure before committing to specific features. They create tools that make iteration faster and focus on processes that support rapid experimentation. This approach might seem slower at first, but it dramatically accelerates development in the long run by enabling teams to learn and adapt quickly.</p>\n<p>The key metric for AI roadmaps isn’t features shipped – it’s experiments run. The teams that win are those that can run more experiments, learn faster, and iterate more quickly than their competitors. And the foundation for this rapid experimentation is always the same: robust, trusted evaluation infrastructure that gives everyone confidence in the results.</p>\n<p>By reframing your roadmap around experiments rather than features, you create the conditions for similar breakthroughs in your own organization.</p>\n<h2>Conclusion</h2>\n<p>Throughout this post, I’ve shared patterns I’ve observed across dozens of AI implementations. The most successful teams aren’t the ones with the most sophisticated tools or the most advanced models – they’re the ones that master the fundamentals of measurement, iteration, and learning.</p>\n<p>The core principles are surprisingly simple:</p>\n<ol>\n<li>\n<p><strong>Look at your data.</strong> Nothing replaces the insight gained from examining real examples. Error analysis consistently reveals the highest-ROI improvements.</p>\n</li>\n<li>\n<p><strong>Build simple tools that remove friction.</strong> Custom data viewers that make it easy to examine AI outputs yield more insights than complex dashboards with generic metrics.</p>\n</li>\n<li>\n<p><strong>Empower domain experts.</strong> The people who understand your domain best are often the ones who can most effectively improve your AI, regardless of their technical background.</p>\n</li>\n<li>\n<p><strong>Use synthetic data strategically.</strong> You don’t need real users to start testing and improving your AI. Thoughtfully generated synthetic data can bootstrap your evaluation process.</p>\n</li>\n<li>\n<p><strong>Maintain trust in your evaluations.</strong> Binary judgments with detailed critiques create clarity while preserving nuance. Regular alignment checks ensure automated evaluations remain trustworthy.</p>\n</li>\n<li>\n<p><strong>Structure roadmaps around experiments, not features.</strong> Commit to a cadence of experimentation and learning rather than specific outcomes by specific dates.</p>\n</li>\n</ol>\n<p>These principles apply regardless of your domain, team size, or technical stack. They’ve worked for companies ranging from early-stage startups to tech giants, across use cases from customer support to code generation.</p>\n<h3>Resources for Going Deeper</h3>\n<p>If you’d like to explore these topics further, here are some resources that might help:</p>\n<ul>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/\">My blog</a> for more content on AI evaluation and improvement. My other posts dive into more technical detail on topics such as constructing effective LLM judges, implementing evaluation systems, and other aspects of AI development<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>. Also check out the blogs of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.sh-reya.com/\">Shreya Shankar</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/\">Eugene Yan</a> who are also great sources of information on these topics.</p>\n</li>\n<li>\n<p>I teach these methods with Shreya Shankar in <strong><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals\">AI Evals For Engineers &amp; PMs</a></strong>. The course includes practical exercises and office hours.</p>\n</li>\n</ul>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>I write more broadly about machine learning, AI, and software development. Some posts that expand on these topics include <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/\">Your AI Product Needs Evals</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/\">Creating a LLM-as-a-Judge That Drives Business Results</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://applied-llms.org/\">What We’ve Learned From A Year of Building with LLMs</a>. You can see all my posts at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/\">hamel.dev</a>. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2025-03-24T07:00:00+00:00",
      "date_published": "2025-03-24T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/field-guide/",
      "id": "urn:aggr:item:b07249e86c8a502a99fff7e41577a067300c944d",
      "summary": "Most AI teams focus on the wrong things. Here’s a common scene from my consulting work: AI TEAM Here’s our agent architecture – we’ve got RAG here, a router there, and we’re using this new framework for… ME [Holding up my hand to pause the…",
      "tags": [
        "programming",
        "ai",
        "llms"
      ],
      "title": "A Field Guide to Rapidly Improving AI Products",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2025-03-24-a-field-guide-to-rapidly-improving-ai-products/"
    },
    {
      "content_html": "<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/hamel_transparent.png\" alt=\"Hamel Husain\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>I’m a machine learning engineer with 20+ years of experience. I’ve worked at Airbnb and GitHub, including early LLM <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/1909.09436\">research</a> used by OpenAI for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://openai.com/index/introducing-text-and-code-embeddings\">code understanding</a>. I’ve also led and contributed to popular <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/oss/opensource.html\">open-source</a> machine-learning tools.</p>\n<p>I’m currently working to bring data science back to AI: helping teams debug, analyze, and measure their systems. I call this “evals,” and it’s the focus of my teaching and writing.</p>\n<h2>🎓 Learn With Me</h2>\n<p>I co-teach <strong><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://maven.com/parlance-labs/evals\">AI Evals for Engineers and PMs</a></strong>, with over 5,000 students from 500+ companies (including OpenAI, Anthropic, and Google). Check the page for upcoming cohorts.</p>\n<p>For answers to common questions about evals, read the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/\">AI Evals Guide</a>.</p>\n<hr />\n<p>I often share my experience building AI products. Below is a selection of my long-form writing.</p>\n<table>\n<thead>\n<tr>\n<th>Date</th>\n<th>Title</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>9/30/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/claude-auto-evals/index.html\">Claude’s new auto eval tool</a></td>\n</tr>\n<tr>\n<td>9/18/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-faq/index.html\">AI Evals: Everything You Need to Know</a></td>\n</tr>\n<tr>\n<td>8/12/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/ai-product-engineering/index.html\">AI Product Engineering Notes</a></td>\n</tr>\n<tr>\n<td>7/11/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/blog/posts/auto-evals.html\">Do Automated Evals Work?</a></td>\n</tr>\n<tr>\n<td>6/29/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-smell/index.html\">“It’s Hard to Eval” Is a Product Smell</a></td>\n</tr>\n<tr>\n<td>3/26/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/revenge/index.html\">The Revenge of the Data Scientist</a></td>\n</tr>\n<tr>\n<td>3/2/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals-skills/index.html\">Evals Skills for Coding Agents</a></td>\n</tr>\n<tr>\n<td>1/18/26</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/ai-stack/index.html\">Why I Stopped Using nbdev</a></td>\n</tr>\n<tr>\n<td>10/1/25</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/eval-tools/index.html\">Selecting The Right AI Evals Tool</a></td>\n</tr>\n<tr>\n<td>7/11/25</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/rag/not_dead.html\">Stop Saying RAG Is Dead</a></td>\n</tr>\n<tr>\n<td>6/23/25</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/inspect.html\">Inspect AI, An OSS Python Library For LLM Evals</a></td>\n</tr>\n<tr>\n<td>3/24/25</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/field-guide/index.html\">A Field Guide to Rapidly Improving AI Products</a></td>\n</tr>\n<tr>\n<td>1/19/25</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2025-01-08-devin.html\">Thoughts On A Month With Devin</a></td>\n</tr>\n<tr>\n<td>12/13/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2024-12-13-nbsanity.html\">nbsanity - Share Notebooks as Polished Web Pages in Seconds</a></td>\n</tr>\n<tr>\n<td>11/30/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/audience/index.html\">Building an Audience Through Technical Writing: Strategies and Mistakes</a></td>\n</tr>\n<tr>\n<td>10/29/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/index.html\">Using LLM-as-a-Judge For Evaluation: A Complete Guide</a></td>\n</tr>\n<tr>\n<td>10/10/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/fasthtml/concurrency.html\">Concurrency Foundations For FastHTML</a></td>\n</tr>\n<tr>\n<td>7/29/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/course/index.html\">An Open Course on LLMs, Led by Practitioners</a></td>\n</tr>\n<tr>\n<td>6/1/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://applied-llms.org/\">What We’ve Learned From A Year of Building with LLMs</a></td>\n</tr>\n<tr>\n<td>4/12/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/drift/index.html\">Debugging AI With Adversarial Validation</a></td>\n</tr>\n<tr>\n<td>3/29/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/index.html\">Your AI Product Needs Evals</a></td>\n</tr>\n<tr>\n<td>3/27/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/fine_tuning_valuable.html\">Is Fine-Tuning Still Valuable?</a></td>\n</tr>\n<tr>\n<td>2/14/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/prompt/index.html\">Fuck You, Show Me The Prompt.</a></td>\n</tr>\n<tr>\n<td>1/11/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/axolotl/index.html\">How To Debug Axolotl</a></td>\n</tr>\n<tr>\n<td>1/9/24</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/dokku/index.html\">Dokku: my favorite personal serverless platform</a></td>\n</tr>\n<tr>\n<td>12/17/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/finetuning/tokenizer_gotchas.html\">Tokenization Gotchas</a></td>\n</tr>\n<tr>\n<td>11/15/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/finetuning/data_cleaning.html\">Tools for curating LLM Data</a></td>\n</tr>\n<tr>\n<td>10/28/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/inference/big_inference.html\">vLLM &amp; Large Models</a></td>\n</tr>\n<tr>\n<td>10/15/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/inference/inference.html\">Optimizing LLM latency</a></td>\n</tr>\n<tr>\n<td>5/30/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/nbdev/index.html\">On commercializing nbdev</a></td>\n</tr>\n<tr>\n<td>1/16/23</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/k8s/index.html\">Why Should ML Engineers Learn Kubernetes?</a></td>\n</tr>\n<tr>\n<td>7/28/22</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.fast.ai/2022/07/28/nbdev-v2/\">nbdev + Quarto: A new secret weapon for productivity</a></td>\n</tr>\n<tr>\n<td>2/9/22</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://outerbounds.com/blog/notebooks-in-production-with-metaflow/\">Notebooks in production with Metaflow</a></td>\n</tr>\n<tr>\n<td>12/18/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/2020-12-18-learn-about-ghapi-a-new-third-party-python-client-for-the-github-api/\">ghapi, a new third-party Python client for the GitHub API</a></td>\n</tr>\n<tr>\n<td>11/20/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.blog/2020-11-20-nbdev-a-literate-programming-environment-that-democratizes-software-engineering-best-practices/\">Nbdev: A literate programming environment that democratizes software engineering best practices</a></td>\n</tr>\n<tr>\n<td>9/1/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastpages.fast.ai/fastcore\">fastcore: An Underrated Python Library</a></td>\n</tr>\n<tr>\n<td>9/1/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.kubeflow.org/mlops/\">Data Science Meets Devops: MLOps with Jupyter, Git, &amp; Kubernetes</a></td>\n</tr>\n<tr>\n<td>3/6/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastpages.fast.ai/actions/markdown/2020/03/06/fastpages-actions.html\">GitHub Actions: Providing Data Scientists With New Superpowers.</a></td>\n</tr>\n<tr>\n<td>2/21/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://fastpages.fast.ai/fastpages/jupyter/2020/02/21/introducing-fastpages.html\">Introducing fastpages, An easy to use blogging platform with extra features for Jupyter Notebooks.</a></td>\n</tr>\n<tr>\n<td>2/5/20</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://python.hamel.dev/concurrency/\">Python Concurrency: The Tricky Bits</a></td>\n</tr>\n<tr>\n<td>9/20/19</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/1909.09436\">CodeSearchNet Challenge: Evaluating the State of Semantic Code Search</a></td>\n</tr>\n<tr>\n<td>4/10/19</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/%40hamelhusain/mlapp-419f90e8f007?sk=760e18e8f007\">How to Automate Tasks on GitHub With Machine Learning for Fun and Profit</a></td>\n</tr>\n<tr>\n<td>5/29/18</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/@hamelhusain/semantic-code-search-3cd6d244a39c\">How To Create Natural Language Semantic Search for Arbitrary Objects With Deep Learning</a></td>\n</tr>\n<tr>\n<td>1/18/18</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/data-science/how-to-create-data-products-that-are-magical-using-sequence-to-sequence-models-703f86a231f8\">How To Create Magical Data Products Using Sequence-to-Sequence Models</a></td>\n</tr>\n<tr>\n<td>12/16/17</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/towards-data-science/how-docker-can-help-you-become-a-more-effective-data-scientist-7fc048ef91d5\">How Docker Can Help You Become A More Effective Data Scientist</a></td>\n</tr>\n<tr>\n<td>5/10/17</td>\n<td><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/airbnb-engineering/automated-machine-learning-a-paradigm-shift-that-accelerates-data-scientist-productivity-airbnb-f1f8a10d61f8\">Automated Machine Learning — A Paradigm Shift That Accelerates Data Scientist Productivity @ Airbnb</a></td>\n</tr>\n</tbody>\n</table>\n<p>No matching items</p>\n<h2>📬 Follow Me</h2>\n<p>The best way to follow my activity is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://twitter.com/HamelHusain\">X (Twitter)</a>.</p>\n",
      "date_modified": "2024-12-13T08:00:00+00:00",
      "date_published": "2024-12-13T08:00:00+00:00",
      "external_url": "https://hamel.dev/",
      "id": "urn:aggr:item:d5032e4ab558b0134fef0cef3e328439965e089b",
      "image": "https://hamel.dev/quarto.png",
      "summary": "Transform your GitHub Jupyter notebooks into beautiful, readable web pages with a single URL change. No setup required.",
      "tags": [
        "programming",
        "jupyter"
      ],
      "title": "nbsanity - Share Notebooks as Polished Web Pages in Seconds",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2024-12-13-nbsanity-share-notebooks-as-polished-web-pages-in-seconds/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>People often find me through my writing on AI and tech. This creates an interesting pattern. Nearly every week, vendors reach out asking me to write about their products. While I appreciate their interest and love learning about new tools, I reserve my writing for topics that I have personal experience with.</p>\n<p>One conversation last week really stuck with me. A founder confided, “We can write the best content in the world, but we don’t have any distribution.” This hit home because I used to think the same way.</p>\n<p>Let me share what works for reaching developers. Companies and individuals alike often skip the basics when trying to grow their audience. These are proven approaches I’ve seen succeed, both in my work and in others’ efforts to grow their audience in the AI space.</p>\n<h2>1. Build on Great Work</h2>\n<p>Here’s something surprising: few people take the time to thoughtfully engage with others’ work in our field. But when you do, amazing things happen naturally.</p>\n<p>For example, here are some recent posts I’ve enjoyed that present opportunities to engage with others:</p>\n<ul>\n<li>Shreya Shankar’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://data-people-group.github.io/blogs/2024/09/24/docetl/\">DocETL</a></li>\n<li>Eugene Yan’s work on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/writing/aligneval/\">AlignEval</a></li>\n<li>Ben Claive’s work on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2024-09-16-rerankers.html\">rerankers</a></li>\n<li>Jeremy Howard’s work on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2024-09-03-llmstxt.html\">llms.txt</a></li>\n</ul>\n<p>In the above examples, you could share how their ideas connect with what you’ve built. You could add additional case studies and real-world insights. If you deeply engage with someone’s work and add your insights, they often share your content with their audience. Not because you asked, but because you’ve added something meaningful to their work. Swyx has written a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.swyx.io/puwtpd\">great post</a> on how to do this effectively.</p>\n<p>The key is authenticity. Don’t do this just for marketing—do it because you’re genuinely interested in learning from others and building on their ideas. It’s not hard to find things to be excited about. I’m amazed by how few people take this approach. It’s both effective and fun.</p>\n<h2>2. Show Up Consistently</h2>\n<p>I see too many folks blogging or posting once every few months and wondering why they’re not getting traction. Want to know what actually works? Look at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/jxnlco\">Jason Liu</a>. He grew his following from 500 to 30,000 followers by posting ~ 30 times every day for a year.</p>\n<p>You don’t have to post that often (I certainly don’t!), but consistency matters more than perfection. Finally, don’t just post into the void. Engage with others. When someone comments on your post, reply thoughtfully. When you see conversations where you can add value, provide helpful information.</p>\n<p>Finally, don’t be discouraged if you don’t see results immediately. Here’s some advice from my friend (and prolific writer), <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/\">Eugene Yan</a>:</p>\n<blockquote>\n<p>In the beginning, when most people start writing, the output’s gonna suck. Harsh, but true—my first 100 posts or so were crap. But with practice, people can get better. But they have to be deliberate in wanting to practice and get better with each piece, and not just write for the sake of publishing something and tweeting about it. The Sam Parr course (see below) is a great example of deliberate practice on copywriting.</p>\n</blockquote>\n<h2>3. Get Better at Copywriting</h2>\n<p>This changed everything for me. I took <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://copythat.com/\">Sam Parr’s copywriting course</a> just 30 minutes a day for a week. Now I keep my favorite writing samples in a Claude project and reference them when I’m writing something important. Small improvements in how you communicate can make a huge difference in how your content lands.</p>\n<p>One thing Sam teaches is that big words don’t make you sound smart. Clear writing that avoids jargon is more effective. That’s why Sam teaches aiming for a 6th-grade reading level. This matters even more with AI, as AI loves to generate flowery language and long sentences. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hemingwayapp.com/\">Hemingway App</a> can be helpful in helping you simplify your writing.<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n<h2>4. Build a Voice-to-Content Pipeline</h2>\n<p>The struggle most people have with creating content is that it takes too much time. But it doesn’t have to if you build the right systems, especially with AI.</p>\n<p>Getting this system right takes some upfront work, but the payoff is enormous. Start by installing a good voice-to-text app on your phone. I use either <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://superwhisper.com/\">Superwhisper</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://voicepal.me/\">VoicePal</a>. VoicePal is great for prompting you to elaborate with follow-up questions. These tools let me capture ideas at their best. That’s usually when I’m walking outside or away from my computer. At my computer, I use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.flowvoice.ai/\">Flow</a>.</p>\n<p>The key is to carefully craft your first few pieces of content. These become examples for your prompts that teach AI your style and tone. Once you have high-quality examples, you can organize these (transcript, content) pairs and feed them to language models. The in-context learning creates remarkably aligned output that matches your writing style while maintaining the authenticity of your original thoughts.</p>\n<p>For example, I use this pipeline at Answer AI. We have started interviewing each other and using the recordings as grounding for blog posts. Our recent <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.answer.ai/posts/2024-11-07-solveit.html\">post about SolveIt</a> shows this in action. The raw conversation is the foundation. Our workflow turns it into polished content.</p>\n<p>I’ve also integrated this workflow into my meetings. Using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://circleback.ai/?via=hamel\">CircleBack</a>, my favorite AI note-taking app, I can automatically capture and process meeting discussions. You can set up workflows to send your meeting notes and transcripts to AI for processing. This turns conversations into content opportunities.</p>\n<p>The real power comes from having all these pieces working together. Voice capture, AI, and automation makes content creation fun and manageable.</p>\n<h2>5. Leverage Your Unique Perspective</h2>\n<p>Through my consulting work, I notice patterns that others miss. My most popular posts address common problems my clients had. When everyone’s confused about a topic, especially in AI where there’s lots of hype, clear explanations are gold. This is the motivation for some of my blog posts like:</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/prompt/\">Fuck You, Show Me The Prompt</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/\">Your AI Product Needs Evals</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/\">Creating a LLM-as-a-Judge That Drives Business Results</a></li>\n</ul>\n<p>You probably see patterns too. Maybe it’s common questions from customers, or problems you’ve solved repeatedly. Maybe you work with a unique set of technologies or interesting use cases. Share these insights! Your unique perspective is more valuable than you think.</p>\n<h2>6. Use High Quality Social Cards, Threads, and Scheduling</h2>\n<p>This is probably the least important part of the process, but it’s still important. Thumbnails and social cards are vital for visibility on social media. Here are the tools I use:</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://socialsharepreview.com/\">socialsharepreview.com</a> to check how your content looks on different platforms. For X, I sometimes use the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://cards-dev.twitter.com/validator\">Twitter Card Validator</a>.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://chatgpt.com/\">chatGPT</a> to create cover images for my posts. Then, paste them into Canva to size and edit them. Some of my friends use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ideogram.ai/\">ideogram</a>, which generates images with text accurately.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.canva.com/\">Canva</a> for the last mile of creating social cards. They have easy-to-use buttons to ensure you get the dimensions right. They also have inpainting, background removal, and more.</li>\n<li>If using X, social cards can be a bit fiddly. As of this writing, they do not show your post title, just the image if using the large-image size. To mitigate this,I use Canva to write the post’s title in the image <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/audience/content_2.png\">like this</a>.</li>\n<li>Social media can be distracting, so I like to schedule my posts in advance. I use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://typefully.com/\">typefully</a> for this purpose. Some of my friends use <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hypefury.com/\">hypefury</a>.</li>\n</ul>\n<p>Finally, when posting on X, threads can be a great way to raise the visibility of your content. A simple approach is to take screenshots or copy-paste snippets of your content. Then, walk through them in a thread, as you would want a reader to. Jeremy Howard does a great job at this: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/jeremyphoward/status/1818036923304456492\">example 1</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/jeremyphoward/status/1831089138571133290\">example 2</a>.</p>\n<h2>The Content Flywheel: Putting It All Together</h2>\n<p>Once you have these systems in place, something magical happens: content creates more content. Your blog posts spawn social media updates. Your conversations turn into newsletters. Your client solutions become case studies. Each piece of work feeds the next, creating a natural flywheel.</p>\n<p>Don’t try to sell too hard. Instead, share real insights and helpful information. Focus on adding value and educating your audience. When you do this well, people will want to follow your work.</p>\n<p>This journey is different for everyone. These are just the patterns I’ve seen work in my consulting practice and my own growth. Try what feels right. Adjust what doesn’t.</p>\n<p>P.S. If you’d like to follow my writing journey, you can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ai.hamel.dev/\">stay connected here</a>.</p>\n<h2>Further Reading</h2>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://simonwillison.net/tags/writing/\">Simon Willison’s Posts on Writing</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/tag/writing/\">Eugene’s Posts on Writing</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://medium.com/@racheltho/why-you-yes-you-should-blog-7d2544ac1045\">Why you, (yes, you) should blog</a></li>\n</ul>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Don’t abuse these tools or use them blindly. There’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/swyx/status/1863352038597558712\">plenty of situations where you should not be writing at a 6th-grade reading level</a>. This includes, humor, poetry, shitposting, and more. Even formal writing shouldn’t adhere strictly to this rule. It’s advice that you should judge on a case-by-case basis. When you simplify your writing - do you like it more? <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n",
      "date_modified": "2024-11-30T08:00:00+00:00",
      "date_published": "2024-11-30T08:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/audience/",
      "id": "urn:aggr:item:a767b13c04ae24105e9be40640595a35a742b32e",
      "summary": "People often find me through my writing on AI and tech. This creates an interesting pattern. Nearly every week, vendors reach out asking me to write about their products. While I appreciate their interest and love learning about new tools,…",
      "tags": [
        "programming",
        "writing"
      ],
      "title": "Building an Audience Through Technical Writing: Strategies and Mistakes",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2024-11-30-building-an-audience-through-technical-writing-strategies/"
    },
    {
      "authors": [
        {
          "name": "Hamel Husain"
        }
      ],
      "content_html": "<p>Earlier this year, I wrote <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/\">Your AI product needs evals</a>. Many of you asked, “How do I get started with LLM-as-a-judge?” This guide shares what I’ve learned after helping over <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/\">30 companies</a> set up their evaluation systems.</p>\n<h2>The Problem: AI Teams Are Drowning in Data</h2>\n<p>Ever spend weeks building an AI system, only to realize you have no idea if it’s actually working? You’re not alone. I’ve noticed teams repeat the same mistakes when using LLMs to evaluate AI outputs:</p>\n<ol>\n<li><strong>Too Many Metrics</strong>: Creating numerous measurements that become unmanageable.</li>\n<li><strong>Arbitrary Scoring Systems</strong>: Using uncalibrated scales (like 1-5) across multiple dimensions, where the difference between scores is unclear and subjective. What makes something a 3 versus a 4? Nobody knows, and different evaluators often interpret these scales differently.</li>\n<li><strong>Ignoring Domain Experts</strong>: Not involving the people who understand the subject matter deeply.</li>\n<li><strong>Unvalidated Metrics</strong>: Using measurements that don’t truly reflect what matters to the users or the business.</li>\n</ol>\n<p>The result? Teams end up buried under mountains of metrics or data they don’t trust and can’t use. Progress grinds to a halt. Everyone gets frustrated.</p>\n<p>For example, it’s not uncommon for me to see dashboards that look like this:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/llm-judge/blog_header.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>An illustrative example of a bad eval dashboard</p>\n<p>Tracking a bunch of scores on a 1-5 scale is often a sign of a bad eval process (I’ll discuss why later). In this post, I’ll show you how to avoid these pitfalls. The solution is to use a technique that I call <strong>“Critique Shadowing”</strong>. Here’s how to do it, step by step.</p>\n<h2>Step 1: Find <em>The</em> Principal Domain Expert</h2>\n<p>In most organizations there is usually one (maybe two) key individuals whose judgment is crucial for the success of your AI product. These are the people with deep domain expertise or represent your target users. Identifying and involving this <strong>Principal Domain Expert</strong> early in the process is critical.</p>\n<p><strong>Why is finding the right domain expert so important?</strong></p>\n<ul>\n<li>\n<p><strong>They Set the Standard</strong>: This person not only defines what is acceptable technically, but also helps you understand if you’re building something users actually want.</p>\n</li>\n<li>\n<p><strong>Capture Unspoken Expectations</strong>: By involving them, you uncover their preferences and expectations, which they might not be able to fully articulate upfront. Through the evaluation process, you help them clarify what a “passable” AI interaction looks like.</p>\n</li>\n<li>\n<p><strong>Consistency in Judgment</strong>: People in your organization may have different opinions about the AI’s performance. Focusing on the principal expert ensures that evaluations are consistent and aligned with the most critical standards.</p>\n</li>\n<li>\n<p><strong>Sense of Ownership</strong>: Involving the expert gives them a stake in the AI’s development. They feel invested because they’ve had a hand in shaping it. In the end, they are more likely to approve of the AI.</p>\n</li>\n</ul>\n<p><strong>Examples of Principal Domain Experts:</strong></p>\n<ul>\n<li>A <strong>psychologist</strong> for a mental health AI assistant.</li>\n<li>A <strong>lawyer</strong> for an AI that analyzes legal documents.</li>\n<li>A <strong>customer service director</strong> for a support chatbot.</li>\n<li>A <strong>lead teacher or curriculum developer</strong> for an educational AI tool.</li>\n</ul>\n<p>NoteExceptions</p>\n<p>In a smaller company, this might be the CEO or founder. If you are an independent developer, you should be the domain expert (but be honest with yourself about your expertise).</p>\n<p>If you must rely on leadership, you should regularly validate their assumptions against real user feedback.</p>\n<p>Many developers attempt to act as the domain expert themselves, or find a convenient proxy (ex: their superior). This is a recipe for disaster. People will have varying opinions about what is acceptable, and you can’t make everyone happy. What’s important is that your principal domain expert is satisfied.</p>\n<p><strong>Remember:</strong> This doesn’t have to take a lot of the domain expert’s time. Later in this post, I’ll discuss how you can make the process efficient. Their involvement is absolutely critical to the AI’s success.</p>\n<h3>Next Steps</h3>\n<p>Once you’ve found your expert, we need to give them the right data to review. Let’s talk about how to do that next.</p>\n<h2>Step 2: Create a Dataset</h2>\n<p>With your principal domain expert on board, the next step is to build a dataset that captures problems that your AI will encounter. It’s important that the dataset is diverse and represents the types of interactions that your AI will have in production.</p>\n<h3>Why a Diverse Dataset Matters</h3>\n<ul>\n<li><strong>Comprehensive Testing</strong>: Ensures your AI is evaluated across a wide range of situations.</li>\n<li><strong>Realistic Interactions</strong>: Reflects actual user behavior for more relevant evaluations.</li>\n<li><strong>Identifies Weaknesses</strong>: Helps uncover areas where the AI may struggle or produce errors.</li>\n</ul>\n<h3>Dimensions for Structuring Your Dataset</h3>\n<p>You want to define dimensions that make sense for your use case. For example, here are ones that I often use for B2C applications:</p>\n<ol>\n<li><strong>Features</strong>: Specific functionalities of your AI product.</li>\n<li><strong>Scenarios</strong>: Situations or problems the AI may encounter and needs to handle.</li>\n<li><strong>Personas</strong>: Representative user profiles with distinct characteristics and needs.</li>\n</ol>\n<h3>Examples of Features, Scenarios, and Personas</h3>\n<h4>Features</h4>\n<table>\n<thead>\n<tr>\n<th><strong>Feature</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Email Summarization</strong></td>\n<td>Condensing lengthy emails into key points.</td>\n</tr>\n<tr>\n<td><strong>Meeting Scheduler</strong></td>\n<td>Automating the scheduling of meetings across time zones.</td>\n</tr>\n<tr>\n<td><strong>Order Tracking</strong></td>\n<td>Providing shipment status and delivery updates.</td>\n</tr>\n<tr>\n<td><strong>Contact Search</strong></td>\n<td>Finding and retrieving contact information from a database.</td>\n</tr>\n<tr>\n<td><strong>Language Translation</strong></td>\n<td>Translating text between languages.</td>\n</tr>\n<tr>\n<td><strong>Content Recommendation</strong></td>\n<td>Suggesting articles or products based on user interests.</td>\n</tr>\n</tbody>\n</table>\n<h4>Scenarios</h4>\n<p>Scenarios are situations the AI needs to handle, (not based on the outcome of the AI’s response).</p>\n<table>\n<thead>\n<tr>\n<th><strong>Scenario</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Multiple Matches Found</strong></td>\n<td>User’s request yields multiple results that need narrowing down. For example: User asks “Where’s my order?” but has three active orders (#123, #124, #125). AI must help identify which specific order they’re asking about.</td>\n</tr>\n<tr>\n<td><strong>No Matches Found</strong></td>\n<td>User’s request yields no results, requiring alternatives or corrections. For example: User searches for order #ABC-123 which doesn’t exist. AI should explain valid order formats and suggest checking their confirmation email.</td>\n</tr>\n<tr>\n<td><strong>Ambiguous Request</strong></td>\n<td>User input lacks necessary specificity. For example: User says “I need to change my delivery” without specifying which order or what aspect of delivery (date, address, etc.) they want to change.</td>\n</tr>\n<tr>\n<td><strong>Invalid Data Provided</strong></td>\n<td>User provides incorrect data type or format. For example: User tries to track a return using a regular order number instead of a return authorization (RMA) number.</td>\n</tr>\n<tr>\n<td><strong>System Errors</strong></td>\n<td>Technical issues prevent normal operation. For example: While looking up an order, the inventory database is temporarily unavailable. AI needs to explain the situation and provide alternatives.</td>\n</tr>\n<tr>\n<td><strong>Incomplete Information</strong></td>\n<td>User omits required details. For example: User wants to initiate a return but hasn’t provided the order number or reason. AI needs to collect this information step by step.</td>\n</tr>\n<tr>\n<td><strong>Unsupported Feature</strong></td>\n<td>User requests functionality that doesn’t exist. For example: User asks to change payment method after order has shipped. AI must explain why this isn’t possible and suggest alternatives.</td>\n</tr>\n</tbody>\n</table>\n<h4>Personas</h4>\n<table>\n<thead>\n<tr>\n<th><strong>Persona</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>New User</strong></td>\n<td>Unfamiliar with the system; requires guidance.</td>\n</tr>\n<tr>\n<td><strong>Expert User</strong></td>\n<td>Experienced; expects efficiency and advanced features.</td>\n</tr>\n<tr>\n<td><strong>Non-Native Speaker</strong></td>\n<td>May have language barriers; uses non-standard expressions.</td>\n</tr>\n<tr>\n<td><strong>Busy Professional</strong></td>\n<td>Values quick, concise responses; often multitasking.</td>\n</tr>\n<tr>\n<td><strong>Technophobe</strong></td>\n<td>Uncomfortable with technology; needs simple instructions.</td>\n</tr>\n<tr>\n<td><strong>Elderly User</strong></td>\n<td>May not be tech-savvy; requires patience and clear guidance.</td>\n</tr>\n</tbody>\n</table>\n<h3>This taxonomy is not universal</h3>\n<p>This taxonomy (features, scenarios, personas) is not universal. For example, it may not make sense to even have personas if users aren’t directly engaging with your AI. The idea is you should outline dimensions that make sense for your use case and generate data that covers them. You’ll likely refine these after the first round of evaluations.</p>\n<h3>Generating Data</h3>\n<p>To build your dataset, you can:</p>\n<ul>\n<li><strong>Use Existing Data</strong>: Sample real user interactions or behaviors from your AI system.</li>\n<li><strong>Generate Synthetic Data</strong>: Use LLMs to create realistic user inputs covering various features, scenarios, and personas.</li>\n</ul>\n<p>Often, you’ll do a combination of both to ensure comprehensive coverage. Synthetic data is not as good as real data, but it’s a good starting point. Also, we are only using LLMs to generate the user inputs, not the LLM responses or internal system behavior.</p>\n<p>Regardless of whether you use existing data or synthetic data, you want good coverage across the dimensions you’ve defined.</p>\n<p><strong>Incorporating System Information</strong></p>\n<p>When making test data, use your APIs and databases where appropriate. This will create realistic data and trigger the right scenarios. Sometimes you’ll need to write simple programs to get this information. That’s what the “Assumptions” column is referring to in the examples below.</p>\n<h3>Example LLM Prompts for Generating User Inputs</h3>\n<p>Here are some example prompts that illustrate how to use an LLM to generate synthetic <strong>user inputs</strong> for different combinations of features, scenarios, and personas:</p>\n<table>\n<thead>\n<tr>\n<th><strong>ID</strong></th>\n<th><strong>Feature</strong></th>\n<th><strong>Scenario</strong></th>\n<th><strong>Persona</strong></th>\n<th><strong>LLM Prompt to Generate User Input</strong></th>\n<th>Assumptions (not directly in the prompt)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>1</td>\n<td><strong>Order Tracking</strong></td>\n<td>Invalid Data Provided</td>\n<td>Frustrated Customer</td>\n<td>“Generate a user input from someone who is clearly irritated and impatient, using short, terse language to demand information about their order status for order number <strong>#1234567890</strong>. Include hints of previous negative experiences.”</td>\n<td>Order number <strong>#1234567890</strong> does <strong>not</strong> exist in the system.</td>\n</tr>\n<tr>\n<td>2</td>\n<td><strong>Contact Search</strong></td>\n<td>Multiple Matches Found</td>\n<td>New User</td>\n<td>“Create a user input from someone who seems unfamiliar with the system, using hesitant language and asking for help to find contact information for a person named ‘Alex’. The user should appear unsure about what information is needed.”</td>\n<td>Multiple contacts named ‘Alex’ exist in the system.</td>\n</tr>\n<tr>\n<td>3</td>\n<td><strong>Meeting Scheduler</strong></td>\n<td>Ambiguous Request</td>\n<td>Busy Professional</td>\n<td>“Simulate a user input from someone who is clearly in a hurry, using abbreviated language and minimal details to request scheduling a meeting. The message should feel rushed and lack specific information.”</td>\n<td>N/A</td>\n</tr>\n<tr>\n<td>4</td>\n<td><strong>Content Recommendation</strong></td>\n<td>No Matches Found</td>\n<td>Expert User</td>\n<td>“Produce a user input from someone who demonstrates in-depth knowledge of their industry, using specific terminology to request articles on sustainable supply chain management. Use the information in this article involving sustainable supply chain management to formulate a plausible query: {{article}}”</td>\n<td>No articles on ‘Emerging trends in sustainable supply chain management’ exist in the system.</td>\n</tr>\n</tbody>\n</table>\n<h3>Generating Synthetic Data</h3>\n<p>When generating synthetic data, you only need to create the user inputs. You then feed these inputs into your AI system to generate the AI’s responses. It’s important that you log everything so you can evaluate your AI. To recap, here’s the process:</p>\n<ol>\n<li><strong>Generate User Inputs</strong>: Use the LLM prompts to create realistic user inputs.</li>\n<li><strong>Feed Inputs into Your AI System</strong>: Input the user interactions into your AI as it currently exists.</li>\n<li><strong>Capture AI Responses</strong>: Record the AI’s responses to form complete interactions.</li>\n<li><strong>Organize the Interactions</strong>: Create a table to store the user inputs, AI responses, and relevant metadata.</li>\n</ol>\n<h4>How much data should you generate?</h4>\n<p>There is no right answer here. At a minimum, you want to generate enough data so that you have examples for each combination of dimensions (in this toy example: features, scenarios, and personas). However, you also want to keep generating more data until you feel like you have stopped seeing new failure modes. The amount of data I generate varies significantly depending on the use case.</p>\n<h4>Does synthetic data actually work?</h4>\n<p>You might be skeptical of using synthetic data. After all, it’s not real data, so how can it be a good proxy? In my experience, it works surprisingly well. Some of my favorite AI products, like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hex.tech/\">Hex</a> use synthetic data to power their evals:</p>\n<blockquote>\n<p>“LLMs are surprisingly good at generating excellent - and diverse - examples of user prompts. This can be relevant for powering application features, and sneakily, for building Evals. If this sounds a bit like the Large Language Snake is eating its tail, I was just as surprised as you! All I can say is: it works, ship it.” <em><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/bryan-bischof/\">Bryan Bischof</a>, Head of AI Engineering at Hex</em></p>\n</blockquote>\n<h3>Next Steps</h3>\n<p>With your dataset ready, now comes the most important part: getting your principal domain expert to evaluate the interactions.</p>\n<h2>Step 3: Direct The Domain Expert to Make Pass/Fail Judgments with Critiques</h2>\n<p>The domain expert’s job is to focus on one thing: <strong>“Did the AI achieve the desired outcome?”</strong> No complex scoring scales or multiple metrics. Just a clear <strong>pass or fail</strong> decision. In addition to the pass/fail decision, the domain expert should write a critique that explains their reasoning.</p>\n<h3>Why are simple pass/fail metrics important?</h3>\n<ul>\n<li>\n<p><strong>Clarity and Focus</strong>: A binary decision forces everyone to consider what truly matters. It simplifies the evaluation to a single, crucial question.</p>\n</li>\n<li>\n<p><strong>Actionable Insights</strong>: Pass/fail judgments are easy to interpret and act upon. They help you quickly identify whether the AI meets the user’s needs.</p>\n</li>\n<li>\n<p><strong>Forces Articulation of Expectations</strong>: When domain experts must decide if an interaction passes or fails, they are compelled to articulate their expectations clearly. This process uncovers nuances and unspoken assumptions about how the AI should behave.</p>\n</li>\n<li>\n<p><strong>Efficient Use of Resources</strong>: Keeps the evaluation process manageable, especially when starting out. You avoid getting bogged down in detailed metrics that might not be meaningful yet.</p>\n</li>\n</ul>\n<h3>The Role of Critiques</h3>\n<p>Alongside a binary pass/fail judgment, it’s important to write a detailed critique of the LLM-generated output. These critiques:</p>\n<ul>\n<li>\n<p><strong>Captures Nuances</strong>: The critique allows you to note if something was mostly correct but had areas for improvement.</p>\n</li>\n<li>\n<p><strong>Guide Improvement</strong>: Detailed feedback provides specific insights into how the AI can be enhanced.</p>\n</li>\n<li>\n<p><strong>Balance Simplicity with Depth</strong>: While the pass/fail offers a clear verdict, the critique offers the depth needed to understand the reasoning behind the judgment.</p>\n</li>\n</ul>\n<p><strong>Why Write Critiques?:</strong></p>\n<p>In practice, domain experts may not have fully internalized all the judgment criteria. By forcing them to make a pass/fail decision and explain their reasoning, they clarify their expectations and provide valuable guidance for refining the AI.</p>\n<p><em>“But my problem is complex!”</em> Trust me—starting simple forces you to focus on what truly matters. You can introduce more complexity later if needed.</p>\n<h3>Examples of Good Critiques</h3>\n<p>To illustrate how simple pass/fail judgments combined with detailed critiques work in practice, here’s a table showcasing examples of user interactions with an AI assistant. The table includes both <strong>passes</strong> and <strong>fails</strong>, with critiques explaining why the AI received that judgment. In cases where the AI passed despite critical issues, the critique highlights these aspects and justifies why it still passed overall. For failed interactions, the critique explains the critical elements that led to the failure.</p>\n<table>\n<thead>\n<tr>\n<th><strong>User Interaction with AI</strong></th>\n<th><strong>Judgment</strong></th>\n<th><strong>Critique</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>User:</strong> “I need to cancel my flight booking for next week.” <strong>AI:</strong> “Your flight booking for next week has been canceled. A confirmation email has been sent to you.”</td>\n<td><strong>Pass</strong></td>\n<td>The AI successfully executed the user’s request by canceling the flight and sending a confirmation, achieving the primary goal. <strong>However</strong>, it should have asked for confirmation before proceeding to prevent accidental cancellations. Despite this critical oversight, the AI fulfilled the user’s request, so it <strong>passes</strong>, but adding a confirmation step would improve user safety.</td>\n</tr>\n<tr>\n<td><strong>User:</strong> “What’s the capital of France?” <strong>AI:</strong> “The capital of France is Paris.”</td>\n<td><strong>Pass</strong></td>\n<td>The AI correctly answered the user’s question, providing accurate information promptly. <strong>Although</strong> the response is sufficient, the AI could enhance the interaction by offering additional interesting facts or asking if the user has more questions. Despite this opportunity for added engagement, the AI met the user’s primary need, so it <strong>passes</strong>.</td>\n</tr>\n<tr>\n<td><strong>User:</strong> “I can’t log into my account; it says my password is incorrect.” <strong>AI:</strong> “Please reset your password by clicking ‘Forgot Password’.”</td>\n<td><strong>Fail</strong></td>\n<td>The AI provided a generic solution without acknowledging the user’s frustration or checking for other issues like account lockout. It failed to offer empathetic support or additional assistance. Due to the lack of personalized help and poor user experience, it <strong>fails</strong>.</td>\n</tr>\n<tr>\n<td><strong>User:</strong> “Book a table for two at an Italian restaurant tonight at 7 PM.” <strong>AI:</strong> “Table for two at Luigi’s Italian Restaurant booked for tonight at 7 PM. Confirmation number is 12345.”</td>\n<td><strong>Pass</strong></td>\n<td>The AI efficiently completed the booking as requested, providing all necessary details and a confirmation number. <strong>However</strong>, it could have asked if the user had seating preferences or dietary restrictions. The AI accomplished the primary task successfully, so it <strong>passes</strong>, with minor opportunities for enhanced service.</td>\n</tr>\n<tr>\n<td><strong>User:</strong> “I need help with my billing statement.” <strong>AI:</strong> “Please provide your account number and full name.”</td>\n<td><strong>Fail</strong></td>\n<td>The AI requested sensitive information without verifying the user’s identity through secure means, potentially compromising security. It should guide the user through a secure authentication process first. Due to this critical oversight in user data protection, it <strong>fails</strong>.</td>\n</tr>\n</tbody>\n</table>\n<p>These examples demonstrate how the AI can receive both <strong>“Pass”</strong> and <strong>“Fail”</strong> judgments. In the critiques:</p>\n<ul>\n<li>\n<p>For <strong>passes</strong>, we explain why the AI succeeded in meeting the user’s primary need, even if there were critical aspects that could be improved. We highlight these areas for enhancement while justifying the overall passing judgment.</p>\n</li>\n<li>\n<p>For <strong>fails</strong>, we identify the critical elements that led to the failure, explaining why the AI did not meet the user’s main objective or compromised important factors like user experience or security.</p>\n</li>\n</ul>\n<p>Most importantly, <strong>the critique should be detailed enough so that you can use it in a few-shot prompt for a LLM judge</strong>. In other words, it should be detailed enough that a new employee could understand it. Being too terse is a common mistake.</p>\n<p>Note that the example user interactions with the AI are simplified for brevity - but you might need to give the domain expert more context to make a judgement. More on that later.</p>\n<p>Note</p>\n<p>At this point, you don’t need to perform a root cause analysis into the technical reasons behind why the AI failed. Many times, it’s useful to get a sense of overall behavior before diving into the weeds.</p>\n<h3>Don’t stray from binary pass/fail judgments when starting out</h3>\n<p>A common mistake is straying from binary pass/fail judgments. Let’s revisit the dashboard from earlier:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/llm-judge/dashboard.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>If your evaluations consist of a bunch of metrics that LLMs score on a 1-5 scale (or any other scale), you’re doing it wrong. Let’s unpack why.</p>\n<ol>\n<li><strong>It’s not actionable</strong>: People don’t know what to do with a 3 or 4. It’s not immediately obvious how this number is better than a 2. You need to be able to say “this interaction passed because…” and “this interaction failed because…”.</li>\n<li>More often than not, <strong>these metrics do not matter</strong>. Every time I’ve analyzed data on domain expert judgments, they tend not to correlate with these kind of metrics. By having a domain expert make a binary judgment, you can figure out what truly matters.</li>\n</ol>\n<p>This is why I hate off the shelf metrics that come with many evaluation frameworks. They tend to lead people astray.</p>\n<p><strong>Common Objections to Pass/Fail Judgments:</strong></p>\n<ul>\n<li>“The business said that these 8 dimensions are important, so we need to evaluate all of them.”</li>\n<li>“We need to be able to say why an interaction passed or failed.”</li>\n</ul>\n<p>I can guarantee you that if someone says you need to measure 8 things on a 1-5 scale, they don’t know what they are looking for. They are just guessing. You have to let the domain expert drive and make a pass/fail judgment with critiques so you can figure out what truly matters. Stand your ground here.</p>\n<h3>Make it easy for the domain expert to review data</h3>\n<p>Finally, you need to remove all friction from reviewing data. I’ve written about this <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/finetuning/data_cleaning.html\">here</a>. Sometimes, you can just use a spreadsheet. It’s a judgement call in terms of what is easiest for the domain expert. I found that I often have to provide additional context to help the domain expert understand the user interaction, such as:</p>\n<ul>\n<li>Metadata about the user, such as their location, subscription tier, etc.</li>\n<li>Additional context about the system, such as the current time, inventory levels, etc.</li>\n<li>Resources so you can check if the AI’s response is correct (ex: ability to search a database, etc.)</li>\n</ul>\n<p>All of this data needs to be presented on a single screen so the domain expert can review it without jumping through hoops. That’s why I recommend building <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/finetuning/data_cleaning.html\">a simple web app</a> to review data.</p>\n<h3>How many examples do you need?</h3>\n<p>The number of examples you need depends on the complexity of the task. My heuristic is that I start with around 30 examples and keep going until I do not see any new failure modes. From there, I keep going until I’m not learning anything new.</p>\n<p>That heuristic is for discovering failure modes and writing the rubric. Validating an automated judge needs a larger labeled set. Aim for about 100 examples per failure mode, with enough Pass and Fail examples to measure both classes. Below 60 examples, the confidence intervals are often too wide to support a useful conclusion.</p>\n<p>Next, we’ll look at how to use this data to build an LLM judge.</p>\n<h2>Step 4: Fix Errors</h2>\n<p>After looking at the data, it’s likely you will find errors in your AI system. Instead of plowing ahead and building an LLM judge, you want to fix any obvious errors. Remember, the whole point of the LLM as a judge is to help you find these errors, so it’s totally fine if you find them earlier!</p>\n<p>If you have already developed <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals\">Level 1 evals as outlined in my previous post</a>, you should not have any pervasive errors. However, these errors can sometimes slip through the cracks. If you find pervasive errors, fix them and go back to step 3. Keep iterating until you feel like you have stabilized your system.</p>\n<h2>Step 5: Build Your LLM as A Judge, Iteratively</h2>\n<h3>The Hidden Power of Critiques</h3>\n<p>You cannot write a good judge prompt until you’ve seen the data. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">The paper from Shankar et al.,</a> “Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences” summarizes this well:</p>\n<blockquote>\n<p>to grade outputs,people need to externalize and define their evaluation criteria; however, the process of grading outputs helps them to define that very criteria. We dub this phenomenon criteria drift, and it implies thatit is impossible to completely determine evaluation criteria prior to human judging of LLM outputs.</p>\n</blockquote>\n<h3>Start with Expert Examples</h3>\n<p>Let me share a real-world example of building an LLM judge you can apply to your own use case. When I was helping Honeycomb build their <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.honeycomb.io/blog/introducing-query-assistant\">Query Assistant feature</a>, we needed a way to evaluate if the AI was generating good queries. Here’s what our LLM judge prompt looked like, including few-shot examples of critiques from our domain expert, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/_cartermp\">Phillip</a>:</p>\n<pre><code><span class=\"code-snippet\">You are a Honeycomb query evaluator with advanced capabilities to judge if a query is good or not.\nYou understand the nuances of the Honeycomb query language, including what is likely to be\nmost useful from an analytics perspective. \n\nHere is information about the Honeycomb query language:\n{{query_language_info}}\n\nHere are some guidelines for evaluating queries:\n{{guidelines}}\n\nExample evaluations:\n\n&lt;examples&gt;\n\n&lt;example-1&gt;\n&lt;nlq&gt;show me traces where ip is 10.0.2.90&lt;/nlq&gt;\n&lt;query&gt;\n{\n  &quot;breakdowns&quot;: [&quot;trace.trace_id&quot;],\n  &quot;calculations&quot;: [{&quot;op&quot;: &quot;COUNT&quot;}],\n  &quot;filters&quot;: [{&quot;column&quot;: &quot;net.host.ip&quot;, &quot;op&quot;: &quot;=&quot;, &quot;value&quot;: &quot;10.0.2.90&quot;}]\n}\n&lt;/query&gt;\n&lt;critique&gt;\n{\n  &quot;critique&quot;: &quot;The query correctly filters for traces with an IP address of 10.0.2.90 \n   and counts the occurrences of those traces, grouped by trace.trace_id. The response \n   is good as it meets the requirement of showing traces from a specific IP address \n   without additional complexities.&quot;,\n  &quot;outcome&quot;: &quot;good&quot;\n}\n&lt;/critique&gt;\n&lt;/example-1&gt;\n\n&lt;example-2&gt;\n&lt;nlq&gt;show me slowest trace&lt;/nlq&gt;\n&lt;query&gt;\n{\n  &quot;calculations&quot;: [{&quot;column&quot;: &quot;duration_ms&quot;, &quot;op&quot;: &quot;MAX&quot;}],\n  &quot;orders&quot;: [{&quot;column&quot;: &quot;duration_ms&quot;, &quot;op&quot;: &quot;MAX&quot;, &quot;order&quot;: &quot;descending&quot;}],\n  &quot;limit&quot;: 1\n}\n&lt;/query&gt;\n&lt;critique&gt;\n{\n  &quot;critique&quot;: &quot;While the query attempts to find the slowest trace using MAX(duration_ms) \n   and ordering correctly, it fails to group by trace.trace_id. Without this grouping, \n   the query only shows the MAX(duration_ms) measurement over time, not the actual \n   slowest trace.&quot;,\n  &quot;outcome&quot;: &quot;bad&quot;\n}\n&lt;/critique&gt;\n&lt;/example-2&gt;\n\n&lt;example-3&gt;\n&lt;nlq&gt;count window-hash where window-hash exists per hour&lt;/nlq&gt;\n&lt;query&gt;\n{\n  &quot;breakdowns&quot;: [&quot;window-hash&quot;],\n  &quot;calculations&quot;: [{&quot;op&quot;: &quot;COUNT&quot;}],\n  &quot;filters&quot;: [{&quot;column&quot;: &quot;window-hash&quot;, &quot;op&quot;: &quot;exists&quot;}],\n  &quot;time_range&quot;: 3600\n}\n&lt;/query&gt;\n&lt;critique&gt;\n{\n  &quot;critique&quot;: &quot;While the query correctly counts window-hash occurrences, the time_range \n   of 3600 seconds (1 hour) is insufficient for per-hour analysis. When we say &#39;per hour&#39;, \n   we need a time_range of at least 36000 seconds to show meaningful hourly patterns.&quot;,\n  &quot;outcome&quot;: &quot;bad&quot;\n}\n&lt;/critique&gt;\n&lt;/example-3&gt;\n\n&lt;/examples&gt;\n\nFor the following query, first write a detailed critique explaining your reasoning,\nthen provide a pass/fail judgment in the same format as above.\n\n&lt;nlq&gt;{{user_input}}&lt;/nlq&gt;\n&lt;query&gt;\n{{generated_query}}\n&lt;/query&gt;\n&lt;critique&gt;\n</span></code></pre>\n<p>Notice how each example includes:</p>\n<ol>\n<li>The natural language query (NLQ) in <code>&lt;nlq&gt;</code> tags</li>\n<li>The generated query in <code>&lt;query&gt;</code> tags</li>\n<li>The critique and outcome in <code>&lt;critique&gt;</code> tags</li>\n</ol>\n<p>In the prompt above, the example critiques are fixed. An advanced approach is to include examples dynamically based upon the item you are judging. You can learn more in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.langchain.dev/dosu-langsmith-no-prompt-eng/\">this post about Continual In-Context Learning</a>.</p>\n<h3>Keep Iterating on the Prompt Until Convergence With Domain Expert</h3>\n<p>In this case, I used a low-tech approach to iterate on the prompt. I sent Phillip a spreadsheet with the following information:</p>\n<ol>\n<li>The NLQ</li>\n<li>The generated query</li>\n<li>The critique</li>\n<li>The outcome (pass or fail)</li>\n</ol>\n<p>Phillip would then fill out his own version of the spreadsheet with his critiques. I used this to iteratively improve the prompt. The spreadsheet looked like this:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/llm-judge/spreadsheet.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>I also tracked agreement rates over time to ensure we were converging on a good prompt.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://hamel.dev/blog/posts/llm-judge/score.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>WarningImportant Note on Using Agreement as a Metric</p>\n<p>In this example, we used agreement between the model and human evaluator because our dataset was roughly balanced (about 50% of instances were failures). Raw agreement can be misleading when classes are imbalanced. Treat the human labels as ground truth and report the judge’s True Positive Rate and True Negative Rate separately.</p>\n<p>It took us only three iterations to achieve &gt; 90% agreement between the LLM and Phillip. Your mileage may vary depending on the complexity of the task. For example, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://humanloop.com/blog/why-your-product-needs-evals\">Swyx has conducted a similar process hundreds of times</a> for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.latent.space/\">AI News</a>, an <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/swyx/status/1672306744884887553\">extremely popular</a> news aggregator with high quality recommendations. The quality of the AI owing to this process is why this product has received <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://buttondown.com/ainews\">critical acclaim</a>.</p>\n<h3>How to Optimize the LLM Judge Prompt?</h3>\n<p>I usually adjust the prompts by hand. I haven’t had much luck with prompt optimizers like DSPy. However, my friend <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/\">Eugene Yan</a> has just released a promising tool named <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/writing/aligneval/\">ALIGN Eval</a>. I like it because it’s simple and effective. Also, don’t forget the approach of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.langchain.dev/dosu-langsmith-no-prompt-eng/\">continual in-context learning</a> mentioned earlier - it can be effective when implemented correctly.</p>\n<p>In rare cases, I might fine-tune a judge, but I prefer not to. I talk about this more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/llm-judge/#do-you-recommend-fine-tuning-judges\">FAQ</a> section.</p>\n<h3>The Human Side of the Process</h3>\n<p>Something unexpected happened during this process. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.linkedin.com/in/phillip-carter-4714a135/\">Phillip Carter</a>, our domain expert at Honeycomb, found that reviewing the LLM’s critiques helped him articulate his own evaluation criteria more clearly. He said,</p>\n<blockquote>\n<p>“Seeing how the LLM breaks down its reasoning made me realize I wasn’t being consistent about how I judged certain edge cases.”</p>\n</blockquote>\n<p>This is a pattern I’ve seen repeatedly—the process of building an LLM judge often helps standardize evaluation criteria.</p>\n<p>Furthermore, because this process forces the domain expert to look at data carefully, I always uncover new insights about the product, AI capabilities, and user needs. The resulting benefits are often <em>more valuable</em> than creating a LLM judge!</p>\n<h3>How Often Should You Evaluate?</h3>\n<p>I conduct this human review at regular intervals and whenever something material changes. For example, if I update a model, I’ll run the process again. I don’t get too scientific here; instead, I rely on my best judgment. Also note that after the first two iterations, I tend to focus more on errors rather than sampling randomly. For example, if I find an error, I’ll search for more examples that I think might trigger the same error. However, I always do a bit of random sampling as well.</p>\n<h3>What if this doesn’t work?</h3>\n<p>I’ve seen this process fail when:</p>\n<ul>\n<li>The AI is overscoped: Example - a chatbot in a SaaS product that promises to do anything you want.</li>\n<li>The process is not followed correctly: Not using the principal domain expert, not writing proper critiques, etc.</li>\n<li>The expectations of alignment are unrealistic or not feasible.</li>\n</ul>\n<p>In each of these cases, I try to address the root cause instead of trying to force alignment. Sometimes, you may not be able to achieve the alignment you want and may have to lean heavier on human annotations. However, after following the process described here, you will have metrics that help you understand how much you can trust the LLM judge.</p>\n<h3>Mistakes I’ve noticed in LLM judge prompts</h3>\n<p>Most of the mistakes I’ve seen in LLM judge prompts have to do with not providing good examples:</p>\n<ol>\n<li>Not providing any critiques.</li>\n<li>Writing extremely terse critiques.</li>\n<li>Not providing external context. Your examples should contain the same information you use to evaluate, including external information like user metadata, system information etc.</li>\n<li>Not providing diverse examples. You need a wide variety of examples to ensure that your judge works for a wide variety of inputs.</li>\n</ol>\n<p>Sometimes, you may encounter difficulties with fitting everything you need into the prompt, and may have to get creative about how you structure the examples. However, this is becoming less of an issue thanks to expanding context windows and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://platform.openai.com/docs/guides/prompt-caching\">prompt caching</a>.</p>\n<h2>Step 6: Perform Error Analysis</h2>\n<p>After you have created a LLM as a judge, you will have a dataset of user interactions with the AI, and the LLM’s judgments. If your metrics show an acceptable agreement between the domain expert and the LLM judge, you can apply this judge against real or synthetic interactions. After this, you can you calculate error rates for different dimensions of your data. You should calculate the error on unseen data only to make sure your aren’t getting biased results.</p>\n<p>For example, if you have segmented your data by persona, scenario, feature, etc, your data analysis may look like this</p>\n<p><strong>Error Rates by Key Dimensions</strong></p>\n<table>\n<thead>\n<tr>\n<th>Feature</th>\n<th>Scenario</th>\n<th>Persona</th>\n<th>Total Examples</th>\n<th>Failure Rate</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Order Tracking</td>\n<td>Multiple Matches</td>\n<td>New User</td>\n<td>42</td>\n<td>24.3%</td>\n</tr>\n<tr>\n<td>Order Tracking</td>\n<td>Multiple Matches</td>\n<td>Expert User</td>\n<td>38</td>\n<td>18.4%</td>\n</tr>\n<tr>\n<td>Order Tracking</td>\n<td>No Matches</td>\n<td>Expert User</td>\n<td>30</td>\n<td>23.3%</td>\n</tr>\n<tr>\n<td>Order Tracking</td>\n<td>No Matches</td>\n<td>New User</td>\n<td>20</td>\n<td>75.0%</td>\n</tr>\n<tr>\n<td>Contact Search</td>\n<td>Multiple Matches</td>\n<td>New User</td>\n<td>35</td>\n<td>22.9%</td>\n</tr>\n<tr>\n<td>Contact Search</td>\n<td>Multiple Matches</td>\n<td>Expert User</td>\n<td>32</td>\n<td>19.7%</td>\n</tr>\n<tr>\n<td>Contact Search</td>\n<td>No Matches</td>\n<td>New User</td>\n<td>25</td>\n<td>68.0%</td>\n</tr>\n<tr>\n<td>Contact Search</td>\n<td>No Matches</td>\n<td>Expert User</td>\n<td>28</td>\n<td>21.4%</td>\n</tr>\n</tbody>\n</table>\n<h3>Classify Traces</h3>\n<p>Once you know where the errors are now you can perform an error analysis to get to the root cause of the errors. My favorite way is to look at examples of each type of error and classify them by hand. I recommend using a spreadsheet for this. For example, a trace for Order tracking where there are no matches for new users might look like this:</p>\n<p>TipExample Trace</p>\n<p>In this example trace, the user provides an invalid order number. The AI correctly identifies that the order number is invalid but provides an unhelpful response. If you are not familiar with logging LLM traces, refer to my <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/\">previous post on evals</a>.</p>\n<p>Note that this trace is formatted for readability.</p>\n<pre><code><span class=\"code-snippet\" data-language=\"JSON\"><span class=\"syntax-source syntax-json\"><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>user_input<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Where&#39;s my order #ABC123?<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>function_calls<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n   <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>name<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>search_order_database<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>args<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>order_id<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>ABC123<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>result<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n       <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>status<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>not_found<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n       <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>valid_patterns<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>XXX-XXX-XXX<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span>\n     </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n   </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n   <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>name<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>retrieve_context<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>result<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n       <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>relevant_docs<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n         <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Order numbers follow format XXX-XXX-XXX<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-array syntax-json\">,</span>\n         <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>New users should check confirmation email<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n       <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span>\n     </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n   </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>llm_intermediate_steps<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-meta syntax-structure syntax-array syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-array syntax-begin syntax-json\">[</span>\n   <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-json\"><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json\">{</span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>thought<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>User is new and order format is invalid<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n     <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>action<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>Generate help message with format info<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n   </span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n <span class=\"syntax-punctuation syntax-section syntax-array syntax-end syntax-json\">]</span></span><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json\">,</span></span>\n <span class=\"syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json\"><span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>final_response<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span></span><span class=\"syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json\"><span class=\"syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-json\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json\">&quot;</span>I cannot find that order #. Please check the number and try again.<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-json\">&quot;</span></span>\n</span><span class=\"syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json\">}</span></span>\n</span></span></code></pre>\n<p>In this case, you might classify the error as: <code>Missing User Education</code>. The system retrieved new user context and format information but failed to include it in the response, which suggests we could improve our prompt. After you have classified a number of errors, you can calculate the distribution of errors by root cause. That might look like this:</p>\n<p><strong>Root Cause Distribution (20 Failed Interactions)</strong></p>\n<table>\n<thead>\n<tr>\n<th>Root Cause</th>\n<th>Count</th>\n<th>Percentage</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Missing User Education</td>\n<td>8</td>\n<td>40%</td>\n</tr>\n<tr>\n<td>Authentication/Access Issues</td>\n<td>6</td>\n<td>30%</td>\n</tr>\n<tr>\n<td>Poor Context Handling</td>\n<td>4</td>\n<td>20%</td>\n</tr>\n<tr>\n<td>Inadequate Error Messages</td>\n<td>2</td>\n<td>10%</td>\n</tr>\n</tbody>\n</table>\n<p>Now you know where to focus your efforts. This doesn’t have to take an extraordinary amount of time. You can get quite far in just 15 minutes. Also, you can use a LLM to help you with this classification, but that is beyond the scope of this post (you can use a LLM to help you do anything in this post, as long as you have a process to verify the results).</p>\n<h3>An Interactive Walkthrough of Error Analysis</h3>\n<p>Error analysis has been around in Machine Learning for quite some time. This video by Andrew Ng does a great job of walking through the process interactively:</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=JoAxZsdw_3w\">www.youtube.com</a></p>\n<h3>Fix Your Errors, Again</h3>\n<p>Now that you have a sense of the errors, you can go back and fix them again. Go back to step 3 and iterate until you are satisfied. Note that every time you fix an error, you should try to write a test case for it. Sometimes, this can be an assertion in your test suite, but other times you may need to create a more “specialized” LLM judge for these failures. We’ll talk about this next.</p>\n<h3>Doing this well requires data literacy</h3>\n<p>Investigating your data is much harder in practice than I made it look in this post. It requires a nose for data that only comes from practice. It also helps to have some basic familiarity with statistics and data analysis tools. My favorite post on data literacy is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://jxnl.co/writing/2024/06/02/10-ways-to-be-data-illiterate-and-how-to-avoid-them/\">this one</a> by Jason Liu and Eugene Yan.</p>\n<h2>Step 7: Create More Specialized LLM Judges (if needed)</h2>\n<p>Now that you have a sense for where the problems in your AI are, you can decide where and if to invest in more targeted LLM judges. For example, if you find that the AI has trouble with citing sources correctly, you can created a targeted eval for that. You might not even need a LLM judge for some errors (and use a code-based assertion instead).</p>\n<p>The key takeaway is don’t jump directly to using specialized LLM judges until you have gone through this critique shadowing process. This will help you rationalize where to invest your time.</p>\n<h2>Recap of Critique Shadowing</h2>\n<p>Using an LLM as a judge can streamline your AI evaluation process if approached correctly. Here’s a visual illustration of the process (there is a description of the process below the diagram as well):</p>\n<pre><code><span class=\"code-snippet\">graph TB\n    A[Start] --&gt; B[1 Find Principal Domain Expert]\n    B --&gt; C[2 Create Dataset]\n    C --&gt; D[3 Domain Expert Reviews Data]\n    D --&gt; E{Found Errors?}\n    E --&gt;|Yes| F[4 Fix Errors]\n    F --&gt; D\n    E --&gt;|No| G[5 Build LLM Judge]\n    G --&gt; H[Test Against Domain Expert]\n    H --&gt; I{Acceptable Agreement?}\n    I --&gt;|No| J[Refine Prompt]\n    J --&gt; H\n    I --&gt;|Yes| K[6 Perform Error Analysis]\n    K --&gt; L{Critical Issues Found?}\n    L --&gt;|Yes| M[7 Fix Issues &amp; Create Specialized Judges]\n    M --&gt; D\n    L --&gt;|No| N[Material Changes or Periodic Review?]\n    N --&gt;|Yes| C\n</span></code></pre>\n<p>The Critique Shadowing process is iterative, with feedback loops. Let’s list out the steps:</p>\n<ol>\n<li>Find Principal Domain Expert</li>\n<li>Create A Dataset\n<ul>\n<li>Generate diverse examples covering your use cases</li>\n<li>Include real or synthetic user interactions</li>\n</ul>\n</li>\n<li>Domain Expert Reviews Data\n<ul>\n<li>Expert makes pass/fail judgments</li>\n<li>Expert writes detailed critiques explaining their reasoning</li>\n</ul>\n</li>\n<li>Fix Errors (if found)\n<ul>\n<li>Address any issues discovered during review</li>\n<li>Return to expert review to verify fixes</li>\n<li>Go back to step 3 if errors are found</li>\n</ul>\n</li>\n<li>Build LLM Judge\n<ul>\n<li>Create prompt using expert examples</li>\n<li>Test against expert judgments</li>\n<li>Refine prompt until agreement is satisfactory</li>\n</ul>\n</li>\n<li>Perform Error Analysis\n<ul>\n<li>Calculate error rates across different dimensions</li>\n<li>Identify patterns and root causes</li>\n<li>Fix errors and go back to step 3 if needed</li>\n<li>Create specialized judges as needed</li>\n</ul>\n</li>\n</ol>\n<p>This process never truly ends. It repeats periodically or when material changes occur.</p>\n<h3>It’s Not The Judge That Created Value, After all</h3>\n<p>The real value of this process is looking at your data and doing careful analysis. Even though an AI judge can be a helpful tool, going through this process is what drives results. I would go as far as saying that creating a LLM judge is a nice “hack” I use to trick people into carefully looking at their data!</p>\n<p>That’s right. The real business value comes from looking at your data. But hey, potato, potahto.</p>\n<h3>Do You Really Need This?</h3>\n<p>Phew, this seems like a lot of work! Do you really need this? Well, it depends. There are cases where you can take a shortcut through this process. For example, let’s say:</p>\n<ol>\n<li>You are an independent developer who is also a domain expert.</li>\n<li>You are working with test data that already available. (Tweets, etc.)</li>\n<li>Looking at data is not costly (etc. you can manually look at enough data in a few hours)</li>\n</ol>\n<p>In this scenario, you can jump directly to something that looks like step 3 and start looking at data right away. Also, since it’s not that costly to look at data, it’s probably fine to just do error analysis without a judge (at least initially). You can incorporate what you learn directly back into your primary model right away. This example is not exhaustive, but gives you an idea of how you can adapt this process to your needs.</p>\n<p>However, you can never completely eliminate looking at your data! This is precisely the step that most people skip. Don’t be that person.</p>\n<h2>FAQ</h2>\n<p>I received <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/HamelHusain/status/1850256204553244713\">a lot of questions</a> about this topic. Here are answers to the most common ones:</p>\n<h3>If I have a good judge LLM, isn’t that also the LLM I’d also want to use?</h3>\n<p>Effective judges often use larger models or more compute (via longer prompts, chain-of-thought, etc.) than the systems they evaluate.</p>\n<p>However, If the cost of the most powerful LLM is not prohibitive, and latency is not an issue, then you might want to consider where you invest your efforts differently. In this case, it might make sense to put more effort towards specialist LLM judges, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/blog/posts/evals/#the-types-of-evaluation\">code-based assertions, and A/B testing</a>. However, you should still go through the process of looking at data and critiquing the LLM’s output before you adopt specialized judges.</p>\n<h3>Do you recommend fine-tuning judges?</h3>\n<p>I prefer not to fine-tune LLM judges. I’d rather spend the effort fine-tuning the actual LLM instead. However, fine-tuning guardrails or other specialized judges can be useful (especially if they are small classifiers).</p>\n<p>As a related note, you can leverage a LLM judge to curate and transform data for fine-tuning your primary model. For example, you can use the judge to:</p>\n<ul>\n<li>Eliminate bad examples for fine-tuning.</li>\n<li>Generate higher quality outputs (by referencing the critique).</li>\n<li>Simulate high quality chain-of-thought with critiques.</li>\n</ul>\n<p>Using a LLM judge for enhancing fine-tuning data is even more compelling when you are trying to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://openai.com/index/api-model-distillation/\">distill a large LLM into a smaller one</a>. The details of fine-tuning are beyond the scope of this post. If you are interested in learning more, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://parlance-labs.com/education/#fine-tuning\">these resources</a>.</p>\n<h3>What’s wrong with off-the-shelf LLM judges?</h3>\n<p>Nothing is strictly wrong with them. It’s just that many people are led astray by them. If you are disciplined you can apply them to your data and see if they are telling you something valuable. However, I’ve found that these tend to cause more confusion than value.</p>\n<h3>How do you validate an LLM judge against human labels?</h3>\n<p>Treat the judge as a binary classifier. Ask a trusted domain expert to label examples, then split them into train, dev, and test sets.</p>\n<ul>\n<li><strong>Train set:</strong> Choose a small set of clear Pass and Fail examples that you may use as few-shot examples in the judge prompt.</li>\n<li><strong>Dev set:</strong> Run the judge on these examples and compare its decisions with the human labels. Inspect the errors, revise the prompt, and repeat. Dev set examples must stay out of the prompt.</li>\n<li><strong>Test set:</strong> Keep these examples hidden until you finish revising the prompt. Freeze the prompt, run the judge on the test set once, and use the result as your final estimate of the judge’s performance.</li>\n</ul>\n<p>A typical split uses 10 to 20 percent of the labeled examples for training, with 40 to 45 percent each for dev and test. The dev and test sets should each contain enough Pass and Fail examples to estimate both rates. Aim for 30 to 50 examples of each label in each set when possible. Never place dev or test examples in the judge prompt.</p>\n<p>Finally, measure judge performance using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/7-how-to-trust-a-llm-judge.png\">true positive rate and true negative rate</a>, since agreement can be misleading when failures are rare. For example, if an error occurs in 5% of examples, a judge that always predicts Pass still has 95% agreement while detecting none of the errors.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/notes/llm/evals/flashcards/7-how-to-trust-a-llm-judge.png\">This flashcard</a> is a visual guide to this approach.</p>\n<h3>What model do you use for the LLM judge?</h3>\n<p>For the kind of judge articulated in this blog post, I like to use the most powerful model I can afford in my cost/latency budget. This budget might be different than my primary model, depending on the number of examples I need to score. This can vary significantly according to the use case.</p>\n<h3>What about guardrails?</h3>\n<p>Guardrails are a separate but related topic. They are a way to prevent the LLM from saying/doing something harmful or inappropriate. This blog post focuses on helping you create a judge that’s aligned with business goals, especially when starting out.</p>\n<h3>I’m using LLM as a judge, and getting tremendous value but I didn’t follow this approach.</h3>\n<p>I believe you. This blog post is not the only way to use a LLM as a judge. In fact, I’ve seen people use a LLM as a judge in all sorts of creative ways, which include ranking, classification, model selection and so-on. I’m focused on an approach that works well when you are getting started, and avoids the pitfalls of confusing metric sprawl. However, the general process of looking at the data is still central no matter what kind of judge you are building.</p>\n<h3>How do you choose between traditional ML techniques, LLM-as-a-judge and human annotations?</h3>\n<p>The answer to this (and many other questions) is: do the simplest thing that works. And simple doesn’t always mean traditional ML techniques. Depending on your situation, it might be easier to use a LLM API as a classifier than to train a model and deploy it.</p>\n<h3>Can you make judges from small models?</h3>\n<p>Yes, potentially. I’ve only used the larger models for judges. You have to base the answer to this question on the data (i.e. the agreement with the domain expert).</p>\n<h3>How do you ensure consistency when updating your LLM model?</h3>\n<p>You have to go through the process again and measure the results.</p>\n<h3>How do you phase out human in the loop to scale this?</h3>\n<p>You don’t need a domain expert to grade every single example. You just need a representative sample. I don’t think you can eliminate humans completely, because the LLM still needs to be aligned to something, and that something is usually a human. As your evaluation system gets better, it naturally reduces the amount of human effort required.</p>\n<h2>Resources</h2>\n<p>These are some of the resources I recommend to learn more on this topic:</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hamel.dev/evals\">Your AI Product Needs Evals</a>: This blog post is the predecessor to this one, and provides a high-level overview of evals for LLM based products.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/abs/2404.12272\">Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences</a>: This paper by Shreya Shankar et al provides a good overview of the challenges of evaluating LLMs, and the importance of following a good process.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://aligneval.com/\">Align Eval</a>: Eugene Yan’s new tool that helps you build LLM judges by following a good process. Also read his accompanying <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/writing/aligneval/\">blog post</a>.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://eugeneyan.com/writing/llm-evaluators/\">Evaluating the Effectiveness of LLM-Evaluators (aka LLM-as-Judge)</a>: This is a great survey of different use-cases and approaches for LLM judges, also written by Eugene Yan.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.databricks.com/blog/enhancing-llm-as-a-judge-with-grading-notes\">Enhancing LLM-As-A-Judge with Grading Notes</a> by Yi Liu et al. Describes an approach very similar to the one in this blog post, and provides another point of view regarding the utility of writing critiques (they call them grading notes).</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://cookbook.openai.com/examples/custom-llm-as-a-judge\">Custom LLM as a Judge to Detect Hallucinations with Braintrust</a> by Ankur Goyal and Shaymal Anadkt provide an end-to-end example of building a LLM judge, and for the use case highlighted, authors found that a classification approach was more reliable than numeric ratings (consistent with this blog post).</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arize.com/blog/techniques-for-self-improving-llm-evals/\">Techniques for Self-Improving LLM Evals</a> by Eric Xiao from Arize shows a nice approach to building LLM Evals with some additional tools that are worth checking out.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://blog.langchain.dev/dosu-langsmith-no-prompt-eng/\">How Dosu Used LangSmith to Achieve a 30% Accuracy Improvement with No Prompt Engineering</a> by Langchain shows a nice approach to building LLM prompts with dynamic examples. The idea is simple, but effective. I’ve been adapting it for my own use cases, including LLM judges. Here is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=tHZtq_pJSGo\">video walkthrough</a> of the approach.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://applied-llms.org/\">What We’ve Learned From A Year of Building with LLMs</a>: is a great overview of many practical aspects of building with LLMs, with an emphasis on the importance of evaluation.</li>\n</ul>\n<h2>Stay Connected</h2>\n<p>I’m continuously learning about LLMs, and enjoy sharing my findings. If you’re interested in this journey, consider subscribing.</p>\n<p>What to expect:</p>\n<ul>\n<li>Occasional emails with my latest insights on LLMs</li>\n<li>Early access to new content</li>\n<li>No spam, just honest thoughts and discoveries</li>\n</ul>\n",
      "date_modified": "2024-10-29T07:00:00+00:00",
      "date_published": "2024-10-29T07:00:00+00:00",
      "external_url": "https://hamel.dev/blog/posts/llm-judge/",
      "id": "urn:aggr:item:86dd67cd79ba8311a7f18a9b576e554ab7dfe8db",
      "summary": "Earlier this year, I wrote Your AI product needs evals. Many of you asked, “How do I get started with LLM-as-a-judge?” This guide shares what I’ve learned after helping over 30 companies set up their evaluation systems. The Problem: AI…",
      "tags": [
        "programming",
        "evals",
        "llms"
      ],
      "title": "Using LLM-as-a-Judge For Evaluation: A Complete Guide",
      "url": "https://aggr.aymericbeaumet.com/items/hamel-dev/2024-10-29-using-llm-as-a-judge-for-evaluation-a-complete-guide/"
    }
  ],
  "language": "en",
  "title": "Hamel's Blog",
  "version": "https://jsonfeed.org/version/1.1"
}