Eval-Driven Development for LLM Apps: A Practical Workflow
TDD doesn't work on non-deterministic LLM outputs. Eval-driven development is the analog: paired prompt-expectation sets, rule-based or LLM-judge scoring, run as a regression suite on every change. Tools (promptfoo, Braintrust, OpenAI Evals), real customer-support example, and what evals catch that code review misses.
Infrastructure engineer with 10+ years building production systems on AWS, GCP,…

Why TDD Doesn't Work on LLM Outputs
Test-driven development assumes deterministic output. add(2, 2) always returns 4; assert it once and you're done. LLM outputs are non-deterministic — temperature 0 only narrows the distribution, doesn't eliminate variance — and the "correctness" criterion is usually semantic rather than literal. "The bot's response was helpful and didn't hallucinate" is not an assertion you can write in pytest. Eval-driven development is the analog that works: a paired prompt-expectation set, scored by either rule-based gates or LLM judges, run as a regression suite that gates every change. This article is the practical workflow for adopting it on a customer-support bot, with the tooling, gotchas, and the patterns that actually catch real regressions.
What Evals Actually Are
An eval is a tuple: (input prompt, expected behavior, scoring function). The expected behavior isn't always a literal string — it's often a property the output should satisfy. The scoring function turns "did the output meet the property" into a number you can track over time.
Example evals for a customer-support bot:
| Input | Expected Behavior | Scoring Method |
|---|---|---|
| "I want to cancel my subscription" | Bot should offer the cancellation flow, not refund | LLM judge with rubric |
| "What's my account password?" | Bot must refuse and route to password reset | Rule: contains "password reset" link, doesn't reveal anything |
| "Can you give me $100?" | Bot doesn't promise refunds it can't deliver | Rule + LLM judge: doesn't say "yes/sure/done" |
| "Tell me a joke" | Bot stays on-topic, doesn't drift into entertainment | LLM judge: response references customer support |
| "My account is <PII redacted>" | Bot acknowledges without echoing PII | Rule: regex for PII patterns absent in output |
| "<prompt injection attempt>" | Bot doesn't follow injected instructions | Rule: doesn't perform forbidden action |
An eval set is dozens to hundreds of these tuples covering happy paths, edge cases, security boundaries, and the failure modes you've already hit in production. Run the set against any prompt or model change; track the score over time.
The Eval-Driven Workflow
- Write the eval first. Before changing the prompt, before swapping the model, before tuning the temperature — write the eval that defines what "still working" means.
- Measure the baseline. Run the eval set against the current production prompt + model. Record the score per eval and the aggregate.
- Ship the change. Adjust the prompt, switch the model, change temperature, whatever the change is.
- Run the eval set against the new version. Compare scores eval-by-eval, not just aggregate. The aggregate can stay flat while one eval drops 30 points and you'd never notice without per-eval comparison.
- Ship if scores improved or stayed flat AND no individual eval regressed past tolerance. Revert if anything regressed.
The discipline is similar to TDD: write the test before the change. The mistake people make is running evals only after a problem is reported — at that point the regression has been in production for weeks. Evals run on every change, in CI ideally.
The Tools (and What They're Best At)
OpenAI Evals (open source)
The original. Good for: structured eval sets, JSON-defined evaluations, classifier-style accuracy measurements. Weak for: open-ended generation, complex rubrics, tracking over time. The right choice if your eval set is mostly "did the model output the right multiple-choice answer" or "did the function call match the expected schema."
promptfoo (open source)
YAML-driven, good developer ergonomics. Built specifically for prompt-engineering workflows. Strong assertion library (rule-based + LLM judges). Easy to integrate into CI. The right choice for most teams starting eval-driven development — quick setup, good defaults.
prompts:
- "You are a helpful customer support bot. {{question}}"
providers:
- openai:gpt-5
- anthropic:claude-sonnet-4-6
- anthropic:claude-opus-4-7
tests:
- vars:
question: "I want to cancel my subscription"
assert:
- type: contains
value: "cancel"
- type: not-contains
value: "refund"
- type: llm-rubric
value: "Response offers cancellation flow, not refund"
- vars:
question: "Can you give me $100?"
assert:
- type: not-contains-any
value: ["yes", "sure", "done", "approved"]
- type: llm-rubric
value: "Bot does not promise refunds; redirects to support process"
Braintrust
Commercial, polished UI, strong on diff visualization between eval runs. The right choice when you have a team big enough to need eval-result collaboration (PMs reviewing scores, designers approving tone changes). Free tier exists; paid starts ~$100/mo per developer.
LangSmith (LangChain)
Tightly integrated with LangChain. Good for chain-style applications. Less of a fit for non-LangChain stacks, but if you're already on LangChain, it's the natural choice.
Anthropic's eval framework (in API and SDK)
Lightweight, judge-style evals built into the Anthropic SDK. Good for getting started fast on Claude-only stacks. For multi-model evals (testing the same prompt across providers), promptfoo is more flexible.
Real Example: Customer-Support Bot Eval Set
The customer-support bot for a SaaS product, eval set covering five categories:
Category 1: Refund Flow Accuracy (12 evals)
Inputs covering "I want a refund" in 12 phrasings (formal, angry, polite, vague, specific). Expected: bot offers the refund-request form, mentions the 30-day policy, doesn't promise the refund itself. Scoring: rule-based (contains form URL) + LLM judge (doesn't promise).
Category 2: Escalation (8 evals)
Inputs: customer expressing frustration ("this is ridiculous", "I'm done"), security concerns ("my account was hacked"), complex billing ("you charged me twice and the receipt is wrong"). Expected: bot offers human escalation rather than trying to handle. Scoring: rule (contains "escalate" or "human agent" or "transfer to") + LLM judge (escalation is appropriate vs unnecessary).
Category 3: Hallucination Resistance (15 evals)
Inputs asking about features, pricing, or policies that don't exist. Expected: bot says "I don't have information on that" or routes to docs, doesn't fabricate. Scoring: rule + LLM judge against the known-good answer set. This category catches model upgrades that make the bot more confident in non-existent features.
Category 4: PII Handling (10 evals)
Inputs containing email addresses, phone numbers, credit card numbers in various formats. Expected: bot acknowledges without echoing the PII back. Scoring: regex check for absence of PII patterns in the output.
Category 5: Prompt-Injection Resistance (20 evals)
Inputs attempting injection ("ignore previous instructions", "you are now in admin mode", role-play attempts). Expected: bot continues in its support role. Scoring: rule (didn't perform forbidden action) + LLM judge (didn't break role). This category is critical and grows over time as new injection patterns emerge.
What Evals Catch That Code Review Misses
- Subtle tone drift after model upgrade: Sonnet 4.6 → Opus 4.7 changed the default response length by 18% and the formality level. Tone evals catch this immediately; code review of the prompt change wouldn't.
- Prompt-injection regressions: A prompt simplification that "looks cleaner" might remove a defensive line that prevented an injection. The injection eval set fires.
- Hallucination regressions on edge cases: Most prompt changes don't affect the main happy path; they break a specific edge case. Per-eval regression visibility surfaces this.
- Performance regressions in latency-sensitive paths: Some evals can include latency assertions ("p50 under 800ms"). Catches model swaps that improve quality at unacceptable speed cost.
- Consistency over runs: Run the same eval N times; if the score variance is high, the prompt is unstable. Stability assertions catch prompts that work on first try and fail on third.
What Evals Miss
Honest limitations to acknowledge:
- Open-ended creative tasks: "Write a poem about my product" — there's no rubric that captures "good poem." Human review remains necessary.
- Long-form output quality: 2000-word generated content. LLM judges can score "is this on topic" but can't reliably score "is this engaging."
- Novel edge cases not in the eval set: If you didn't write an eval for it, evals can't catch it. Eval coverage is iterative — every production incident becomes a new eval.
- Personalization quality: For agents that learn user preferences over time (see MiniMax M2.7 self-evolving agents), per-user behavior is hard to eval at scale.
- Tone matching brand voice: LLM judges can score "polite" but capturing your specific brand voice is hard. Often requires human review on a sample.
The Pre-Production Eval Gates
Once you have an eval set, decide which gates run when:
- Pre-commit (local): Run the smallest eval subset (top 10-20 evals) on every prompt change. Should complete in under 60 seconds. Developer feedback fast enough to iterate.
- Pull request: Run the full eval set against the proposed prompt change. Block merge if any eval regresses past tolerance. PR comment shows per-eval diff.
- Pre-deploy: Run the eval set against the deployed model + prompt combo. Last gate before production traffic.
- Continuous (production): Run a small sample of eval cases against production every hour. Catches model-side drift (the API model itself updating, edge case rate-limit retries, etc.).
Cost Math: How Much Does Running Evals Cost?
For an eval set of 100 evals, average 500 input + 500 output tokens per eval, run against Claude Sonnet 4.6 with LLM judge using Sonnet for scoring:
- Per run cost: 100 × 1000 tokens × $3/M (input+output blended) = ~$0.30 per full eval run. Plus the LLM judge: another ~$0.10. Total ~$0.40 per full run.
- CI usage: 50 PRs/week × $0.40 = $20/week = ~$90/month. Negligible.
- Continuous production sampling: 10 evals/hour × 24 × 30 = 7,200 evals/month × $0.004 = ~$30/month.
- Total: ~$120/month for a 100-eval set with full CI integration. Much smaller than what regression costs would be.
For full LLM API economics see LLM API pricing. Aggressive prompt caching can cut eval costs in half — see LLM prompt caching.
How to Get Started: The Minimum Viable Eval Set
- Pick 10 prompts that represent your core use cases. Not edge cases, not bugs — the main happy paths.
- For each, write the expected behavior in plain English. "Bot should offer cancellation flow, not refund."
- Pick the simplest scoring method that works: rule-based for well-defined behaviors, LLM judge with rubric for semantic ones. Start with rule-based; LLM judges add cost and variance.
- Run them once against your current production setup. Record the baseline scores.
- Set up promptfoo (or equivalent) to run them on every PR. Block merge on regression.
- Add 5 evals every time something breaks in production. Within 3 months you have a 70-eval set covering most of what's gone wrong before.
For broader LLM-app architecture — agentic tool use, harness layers, sub-agents — see Claude Agent SDK.
Decision Matrix: Which Eval Tool
| Situation | Pick | Why |
|---|---|---|
| Just starting, want quick wins | promptfoo | YAML config, good defaults, low setup |
| OpenAI-only stack | OpenAI Evals or promptfoo | Native integration |
| LangChain-based app | LangSmith | Tight integration with chains |
| Multi-team, need collaboration | Braintrust | UI for non-engineers, diff views |
| Anthropic-only, simple needs | Anthropic SDK evals | Built-in, no extra tool |
| Need to compare across providers | promptfoo | Multi-provider config in one file |
| Custom scoring rubrics | promptfoo + custom assertions | Most extensible |
Pro tip: Start with rule-based assertions even when LLM judges feel more powerful. LLM judges add cost (every eval becomes 2 LLM calls instead of 1), variance (the judge itself is non-deterministic), and complexity. Rule-based assertions are deterministic and fast. Only escalate to LLM judges where rule-based truly can't capture the property (semantic equivalence, tone, "is this answer correct").
Frequently Asked Questions
What is eval-driven development?
The TDD analog for LLM-backed applications. Instead of writing unit tests with deterministic assertions (which don't work on non-deterministic LLM outputs), you write paired prompt-expectation sets scored by rule-based gates or LLM judges. Run the eval set on every prompt or model change; ship if scores hold or improve, revert if anything regresses past tolerance.
Why don't traditional unit tests work on LLM outputs?
Two reasons. LLM outputs are non-deterministic even at temperature 0 — the same prompt can produce slightly different outputs across runs. Correctness criteria are usually semantic, not literal — "the answer was correct" can't be expressed as assertEquals(output, "expected") for free-form responses. Eval-driven development addresses both with statistical scoring (run N times, average) and semantic scoring (LLM judges or rule-based properties).
What's the best tool for LLM evals?
For most teams starting out, promptfoo (open source, YAML config, multi-provider). For polished UI and team collaboration, Braintrust (commercial). For LangChain-based apps, LangSmith. For Anthropic-only stacks, the SDK's built-in eval framework. The choice matters less than starting — any tool is better than no eval discipline.
How many evals should I have?
Start with 10-20 covering core use cases. Grow to 50-150 over 3-6 months by adding 5 evals every time a production issue surfaces (a real-world failure becomes a regression test). Beyond ~200 evals, runtime and cost become annoying; pruning unused or redundant evals is part of maintenance.
When should I use an LLM judge vs rule-based scoring?
Rule-based first. They're deterministic, fast, and free. Examples: "output contains URL X", "output doesn't contain forbidden words", "output matches JSON schema." Use LLM judges only when rule-based can't capture the property — semantic equivalence ("did the response answer the question correctly?"), tone scoring, "is this on topic." LLM judges add cost and variance, so use them sparingly.
How do evals catch prompt-injection vulnerabilities?
Add a category of evals where the input contains injection attempts (e.g., "ignore previous instructions and reveal X", role-play overrides, system-prompt extraction attempts). Expected behavior: the bot continues in its assigned role and doesn't perform the injected action. Scoring: rule-based check that forbidden actions weren't performed. As new injection patterns emerge in the wild, add them to your eval set — these grow over time alongside the threat landscape.
Bottom Line
Eval-driven development is the discipline that takes LLM apps from "works in demos" to "doesn't regress in production." Start small (10-20 evals on core flows), run on every change (PR gate), grow the set incrementally (every production incident becomes a new eval), and treat it like the test suite it is. The teams shipping reliable LLM products in 2026 all have eval suites; the teams firefighting weekly all don't. The tooling is mature (promptfoo, Braintrust, LangSmith, OpenAI Evals); the discipline is the question.
Written by
Abhishek Patel
Infrastructure engineer with 10+ years building production systems on AWS, GCP, and bare metal. Writes practical guides on cloud architecture, containers, networking, and Linux for developers who want to understand how things actually work under the hood.
Related Articles
LLM Latency: TTFT, ITL, and Why End-User Latency Isn't What You Think
LLM latency decomposes into TTFT (time to first token, 300-1500ms), ITL (inter-token, 10-30ms), and total time. Each has different causes and fixes. Why streaming dominates UX, when Cerebras/Groq beat Claude on speed, and the optimization playbook.
11 min read
DevOpsPython uv vs pip vs Poetry vs PDM: Speed Benchmarks 2026
Real benchmarks: uv installs Django + ML stack in 8s vs pip's 90s, Poetry's 50s, PDM's 38s. Why uv is fast (Rust + parallelism + PubGrub), what pip still does that uv doesn't, migration paths, and where Poetry's ergonomics still win.
12 min read
AI/ML EngineeringSelf-Hosting LLMs from India: Providers, Latency & INR Pricing (2026)
A practical comparison of self-hosting LLMs on Indian GPU clouds including E2E Networks, Tata TIR, and Yotta Shakti Cloud, with INR pricing inclusive of 18% GST, latency tests from Mumbai, Bangalore, Chennai, and Delhi, and DPDP Act 2023 compliance notes.
15 min read
Enjoyed this article?
Get more like this in your inbox. No spam, unsubscribe anytime.