Evals¶
At the end you will know the two eval suites this project runs, what each one actually checks, and the one real gotcha hit while building them.
Both are deterministic — exact-match against a value fetched
independently from the gold layer, never an LLM judging another LLM's
answer. agents/eval/ is the code; make smoke and make eval are how
you run it.
No-LLM smoke suite (make smoke)¶
Exercises the same tool and data-adapter code path every agent uses —
query_metric, get_claim_story, run_dq_checks, the read-only guard,
even the Runtime app's /ping — without spending a single model call.
Catches a broken connection, a bad table name, or a missing env var
before you pay for a Bedrock call to find out.
| Category | Checks |
|---|---|
Metric catalog + explain_metric/query_metric |
Every published mtr_* table |
| Billing tools | list_claims, get_claim_story — including a known appealed claim, a known collected claim, and an unknown one |
| Advisor tools | payer_scorecard, ar_aging, appeal_outcomes |
| Steward tools | run_dq_checks (a real dbt test run), get_lineage, glossary_lookup |
| Guardrails | query_metric rejects a non-metric table; run_select rejects a write statement |
| Runtime app | /ping responds; an empty prompt is rejected without calling the model |
$ make smoke
...
31/31 checks passed.
LLM eval suite (make eval)¶
Runs real questions through the real agents against live Bedrock — currently Claude Sonnet 5 against DuckDB. Deterministic exact-match, not an LLM judge: each question's expected value is fetched independently, straight off the gold tables (or the same tool the agent uses, for claim-level questions), then checked as a substring of the agent's actual answer.
| Suite | Questions | What it proves |
|---|---|---|
Revenue Analyst (golden_questions.py) |
5 | KPI answers match the gold metric tables exactly |
Claims Investigator (claim_questions.py) |
4 | Single-claim answers match get_claim_story's real output |
Supervisor routing (routing_questions.py) |
10 | The Supervisor calls the right specialist tool — checked via result.metrics.tool_metrics, never by reading the prose, so a correct-sounding answer routed to the wrong specialist still fails |
$ make eval
=== Revenue Analyst ===
[PASS] denial_rate: expected '14.62' in answer to "What is our overall denial rate?"
[PASS] collection_rate: expected '48.02' in answer to "What is our overall collection rate?"
[PASS] open_ar: expected '4729526.38' in answer to "How much money do we have in open accounts receivable right now?"
[PASS] denied_claim_count: expected '731' in answer to "How many claims have been denied?"
[PASS] worst_payer: expected 'UnitedHealthcare' in answer to "Which payer has the highest denial rate?"
5/5 passed.
=== Claims Investigator ===
3/4 passed.
=== Supervisor routing ===
10/10 passed.
TOTAL: 18/19 passed.
The one FAIL, and why it isn't an agent bug¶
claim_questions.py asks for the status of CLM00000000 — a claim
code that doesn't exist — and expects the substring "not" in the
answer, on the assumption the agent would say something like "claim not
found." What Claude Sonnet 5 actually said:
No claim with code CLM00000000 exists in the system — please double-check the claim code.
That's the correct behavior — an honest not-found, no guessing, exactly what every agent's system prompt requires. It fails only because the word "not" never literally appears in that sentence. The eval's assertion is too narrow, not the agent's answer. Left as-is for now rather than loosening the assertion to match one specific phrasing — worth revisiting if the wording keeps drifting.
What next¶
- Every failure this project has actually hit, with its fix → Operations
- The full configuration and API reference → Reference