██████╗ ██╗ ██╗ █████╗ ██╗ ███████╗██╗ ██╗ █████╗ ██╗
██╔════╝ ██║ ██║██╔══██╗██║ ██╔════╝██║ ██║██╔══██╗██║
██║ ███╗██║ ██║███████║██║ █████╗█████╗ ██║ ██║███████║██║
██║ ██║╚██╗ ██╔╝██╔══██║██║ ╚════╝██╔══╝ ╚██╗ ██╔╝██╔══██║██║
╚██████╔╝ ╚████╔╝ ██║ ██║███████╗ ███████╗ ╚████╔╝ ██║ ██║███████╗
╚═════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚══════╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝
██╗ ██╗██╗████████╗
██║ ██╔╝██║╚══██╔══╝
█████╔╝ ██║ ██║
██╔═██╗ ██║ ██║
██║ ██╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝
Eval harness for LangGraph + Claude agents. Catch regressions before they ship.
Agent prompts drift silently. A well-intentioned change to a system prompt, tool schema, or model version can break 20% of your inputs without a single error in your logs. You find out from a user, not from CI.
This kit makes that failure visible before it ships — a lightweight eval harness you run on every commit, not a monitoring platform you pay for.
uv add agent-eval-kit # or: pip install agent-eval-kitimport asyncio
from agent_eval_kit import GoldenSet, run_evals
from agent_eval_kit.judges import ExactMatchJudge
from agent_eval_kit.reporters import write_markdown
golden = GoldenSet.from_jsonl("evals/golden_set.jsonl")
async def my_agent(input: dict) -> dict:
... # your LangGraph graph or Claude agent
async def main():
run = await run_evals(golden, my_agent, ExactMatchJudge())
print(write_markdown(run))
assert run.pass_rate >= 0.90, f"pass rate {run.pass_rate:.1%} below threshold"
asyncio.run(main())Done. That's the whole integration.
┌─────────────────────────────────────┐
│ GoldenSet │
│ cases loaded from JSONL · tagged │
└──────────────────┬──────────────────┘
│
▼
┌───────────────┐ ┌─────────────────────────────────────┐
│ Your agent │◄───────│ run_evals() │
│ (async fn) │ │ concurrent · semaphore-controlled │
└───────┬───────┘ └──────────────────┬──────────────────┘
│ │
│ actual output │ per-case result
▼ ▼
┌───────────────┐ ┌─────────────────────────────────────┐
│ Judge │ │ EvalRun │
│ (3 types) │ │ pass_rate · mean_score · cost_usd │
└───────┬───────┘ └──────────────────┬──────────────────┘
│ │
│ score · pass · reason │
▼ ▼
┌───────────────┐ ┌─────────────────────────────────────┐
│ CostTracker │ │ diff_runs() │
│ per-case USD │ │ regressions · improvements · delta │
└───────────────┘ └──────────────────┬──────────────────┘
│
┌────────────┴──────────────┐
▼ ▼
Markdown report JUnit XML
(PR comments) (CI test results)
|
|
|
| Output type | Judge | Cost | Latency |
|---|---|---|---|
| Classification label | ExactMatchJudge |
Free | ~0ms |
| Structured JSON fields | ExactMatchJudge(keys=[...]) |
Free | ~0ms |
| Numeric value (cost, metric) | NumericToleranceJudge |
Free | ~0ms |
| Free-text recommendation | LLMJudge |
~$0.001 | ~300ms |
| SQL query correctness | LLMJudge |
~$0.001 | ~300ms |
| Multi-criteria output | Compose judges — cheapest first | Varies | Varies |
The pattern: start with exact match or numeric tolerance, escalate to LLM judge only when the output genuinely requires semantic evaluation.
A LangGraph agent that detects spend anomalies in AWS Cost & Usage Report data. No real AWS account needed — uses deterministic synthetic CUR-style data with seeded anomalies.
examples/cost_anomaly_agent/
├── synthetic_data.py # generate fake CUR rows — seeded, reproducible
├── agent.py # LangGraph graph: stats → detect → recommend
├── golden_set.jsonl # 20 eval cases (clean baseline + known spikes)
└── run_evals.py # end-to-end eval runner with custom judge
# Generate golden set (no API key — deterministic synthetic data)
uv run python examples/cost_anomaly_agent/generate_golden_set.py
# Run evals (ANTHROPIC_API_KEY required for the recommendation node)
make cost-anomalyThe agent graph:
compute_stats ──► detect_anomalies ──► recommend (Claude Sonnet) ──► END
│ │ │
mean + σ z-score > 2.5σ natural language
calculation → anomaly list recommendation
A Claude-powered natural language to SQL agent over the Chinook music database — a public sample schema with artists, albums, tracks, invoices, and customers.
Uses LLMJudge to evaluate semantic SQL correctness rather than string matching.
# Download Chinook SQLite (one-time)
curl -L https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite \
-o examples/nl_to_sql_agent/chinook.dbexamples/nl_to_sql_agent/
├── agent.py # Claude Sonnet → SQL → execute → return rows
├── golden_set.jsonl # 10 NL questions with structural SQL validators
└── README.md
The pattern for production use:
# 1. Run evals on main, save as baseline
uv run python -m agent_eval_kit.cli run --save baseline.json
# 2. Make your change (prompt edit, model version, tool schema)
# 3. Run evals on the change, diff against baseline
uv run python -m agent_eval_kit.cli run --diff baseline.json
# 4. CI exits non-zero if has_regressions == TrueIn GitHub Actions:
- name: Run evals
run: uv run pytest evals/ -v
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Publish results
uses: mikepenz/action-junit-report@v4
with:
report_paths: eval-results.xmlThe longer argument for why you need this and how to do it right: docs/why-eval.md.
Key principles:
| Principle | Why |
|---|---|
| Golden sets over spot checks | 20 well-chosen cases beat ad-hoc testing. Spot checks only cover what you thought to check. |
| Regression budget, not 100% target | Chasing 100% pass rate burns time on edge cases. Set a budget (≤2 new failures per PR) and enforce it. |
| Cheapest judge first | Exact match → tolerance → LLM judge. Most outputs can be validated for free. |
| Evals gate merges, humans gate deploys | Automate the obvious failures. Reserve human review for the cases that passed automation. |
| Write cases when you write the agent | The exercise forces you to specify what the agent is actually supposed to do — which is the hardest part. |
Language Python 3.12
Agent LangGraph 0.2 · langchain-anthropic
AI Claude Sonnet 4.6 (agents) · Claude Haiku 4.5 (judge)
Validation pydantic v2
Tooling uv · ruff · pyright · pytest · pytest-asyncio