Evals¶
Run eval cases on any agent, grade them and report (Evaluate your agent).
agent_adapter.evals¶
Evals: run cases on any agent, grade the runs, and report for people and CI.
Names load on first use, so the pytest plugin can import this package cheaply.
CASE_EVENT = 'custom.eval.case'
module-attribute
¶
DEFAULT_EVALS_FOLDER = '.agent_adapter/evals'
module-attribute
¶
Grader = Callable[[EvalCase, Any], Awaitable[Score | None]]
module-attribute
¶
CallableTarget
¶
CaseChange
¶
Bases: BaseModel
One case that got worse or better between two reports.
CaseResult
¶
Bases: BaseModel
One case: its run id (open it in the viewer), verdict, grades, cost and time.
ChatTarget
¶
A chat-page agent fn(message, session); each case is a new one-turn conversation.
__init__(agent, *, name='')
¶
Raises: ConfigError: agent is not callable.
run_case(case, *, executor, ctx)
async
¶
The agent's reply text.
Raises:
| Type | Description |
|---|---|
ToolUsageError
|
the reply is neither text nor an async iterator of text. |
EvalCase
¶
Bases: BaseModel
One evaluation input; expected is for the graders only.
EvalComparison
¶
Bases: BaseModel
How a report differs from a baseline: regressions, improvements, added and removed cases.
regressed
property
¶
True when a shared case got worse, or a baseline case is missing now.
EvalReport
¶
Bases: BaseModel
Every case result of one eval, with totals; render it as JUnit, Markdown or JSON.
by_grader
property
¶
Totals per grader over the cases it saw; an ungraded case counts as 0.
by_tag
property
¶
Totals per tag; a case counts once under each of its tags.
cost_usd
property
¶
Total cost of every case run.
failed
property
¶
The cases that did not pass.
mean_score
property
¶
Mean case score; errors and ungraded cases count as 0.
n_cases
property
¶
How many cases ran.
n_errors
property
¶
How many cases failed to run or did not finish.
n_passed
property
¶
How many cases passed.
pass_rate
property
¶
Share of cases that passed.
compare(baseline, *, tolerance=0.0)
¶
This report against baseline: a case regresses when it stops passing or loses
more than tolerance of score.
from_json(text)
classmethod
¶
A report from to_json output; the totals are recomputed.
Raises:
| Type | Description |
|---|---|
ValidationError
|
not a report. |
to_json(*, indent=2)
¶
The report, with its totals, as JSON (EvalReport.from_json reads it back).
to_junit()
¶
JUnit XML: one test case per eval case, failures and errors with their reasons.
to_markdown()
¶
A Markdown summary: totals, per-tag table and the failing cases.
EvalRun
dataclass
¶
What graders see of one case run; tool_calls and cost_usd are None when unobserved.
FlowTarget
¶
A Flow run by FlowRunner; initial_state(case) builds the state, output reads it.
GradeResult
¶
Bases: BaseModel
One grader's verdict on one case; score None means it did not grade.
GroupSummary
¶
Bases: BaseModel
Totals of a group of cases (one tag, or one grader).
LLMJudge
¶
A grader that asks model to score each answer against rubric; it never raises.
Case data and the answer are escaped, so they cannot close their tags. The judge's own
passed is ignored unless trust_passed=True: the case passes on value >= pass_at.
Expected values are scrubbed from the notes. Any LLMPort works.
__call__(case, run)
async
¶
The score, or None (logged) when the model or its reply fails.
__init__(model, rubric, *, stage='judge', show_expected=True, trust_passed=False)
¶
Raises: ConfigError: an empty rubric.
grade(case, run)
async
¶
The model's score for run.
Raises:
| Type | Description |
|---|---|
ModelError
|
the model call failed. |
ValidationError
|
the reply is not a valid score. |
Outcome
dataclass
¶
A target's output, and why it did not finish ("" when it did); unfinished is an error.
RecordedRun
dataclass
¶
One recorded run: its key, id, status, whether content was kept, events and tags.
RunCase
dataclass
¶
An eval case made from one recorded run; source_run is that run's id.
record()
¶
The JSONL object (load_cases reads id, input, expected, tags).
RunCases
dataclass
¶
The cases made from a runs folder, and the runs skipped.
Score
¶
Bases: BaseModel
A grader's verdict on one run; value is in [0, 1] and notes is the reason.
reason
property
¶
The grader's reason (the same text as notes).
SkippedRun
dataclass
¶
A run that gave no case, and why.
Target
¶
Bases: Protocol
Runs one case in the given run (ctx) on executor; returns the output or an Outcome.
ToolLoopTarget
¶
run_tool_loop over registry with an optional system prompt; output is the answer.
__init__(model, registry, *, system='', max_steps=8, output_schema=None, name='tool_loop')
¶
Raises: ConfigError: registry is not a Registry.
run_case(case, *, executor, ctx)
async
¶
The final text (or parsed output with output_schema).
Raises:
| Type | Description |
|---|---|
ApprovalPending
|
a tool call is waiting for an approval. |
as_target(target)
¶
target as a Target: a Target as is, a 2-argument callable as a chat agent, else a callable.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a Flow (wrap it in FlowTarget), or not a callable of one or two arguments. |
assert_eval(target, case, graders, *, executor=None, timeout_s=300.0, pass_at=1.0)
¶
Run one case (in a test, say); the result, or AssertionError with the reasons.
Raises:
| Type | Description |
|---|---|
AssertionError
|
the case errored or did not pass. |
ConfigError
|
a bad target, grader or option. |
cases_from_runs(folder, *, run_keys=None, status=None, tags=(), expected_from='output')
¶
Eval cases from the recorded runs in folder (a runs folder or one run file).
run_keys picks runs; status keeps runs with that status (ok, failed, paused,
unfinished); tags keeps runs carrying every one of these run tags. Runs without
recorded content are skipped with a reason.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a missing folder, a bad |
OSError
|
the folder cannot be listed. |
eval_executor()
¶
The default eval executor: an empty registry (no stray tools) and a fresh ledger.
load_cases(path, *, id_field='id', input_field='input', expected_field='expected', tags_field='tags')
¶
Cases from JSONL (one object per line), JSON (a list of objects) or CSV (a header row).
id and input are required; expected and tags are optional. Tags are a list, or a
string split on , or ;. CSV cells are text; an empty expected cell means none.
Other fields are ignored.
Raises:
| Type | Description |
|---|---|
ConfigError
|
unreadable file, unknown suffix, bad JSON, a missing field, duplicate ids, or no cases. |
run_eval(target, cases, graders, *, concurrency=4, executor=None, name='', timeout_s=300.0, pass_at=1.0, save_to=None, capture_content=True)
async
¶
Run each case on target, grade it with every grader, and return the report.
Each case is its own run on executor (default: eval_executor()),
so its hooks, approvals, budgets, ledger and sinks apply, and a recorder attached to it
records the run. A grader without its own verdict passes at score >= pass_at; a grader
that errors or does not grade fails the case and scores 0. A failed grade adds at most
pass_at to the case score. save_to also writes the report into that folder
(DEFAULT_EVALS_FOLDER is the one the viewer's Evals view reads). With
capture_content=False the report keeps no input, expected value, output or judge notes.
Raises:
| Type | Description |
|---|---|
ConfigError
|
no cases, duplicate case ids, no graders, or a bad option. |
OSError
|
|
run_eval_sync(target, cases, graders, **options)
¶
save_report(report, folder=DEFAULT_EVALS_FOLDER)
¶
Write report as <folder>/<key>.json (atomically) and return the path.
Raises:
| Type | Description |
|---|---|
OSError
|
the folder cannot be created or the file cannot be written. |
write_cases(path, cases)
¶
Append cases to a .jsonl file, skipping ids it already holds; (written, skipped ids).
Raises:
| Type | Description |
|---|---|
ConfigError
|
not a |
OSError
|
the file cannot be written. |
agent_adapter.evals.advisor¶
Cost advisor: find a cheaper model per flow node or stage that keeps the eval score.
COMBINED = 'combined'
module-attribute
¶
AdvisorReport
¶
Bases: BaseModel
Per-step model advice from evals and the ledger; apply mapping yourself, never auto.
advised
property
¶
{step: candidate} per step on its own, verified together or not.
mapping
property
¶
The advice to apply: advised once verified together, else empty (see note).
from_json(text)
classmethod
¶
A report from to_json output.
Raises:
| Type | Description |
|---|---|
ValidationError
|
not an advisor report. |
to_json(*, indent=2)
¶
The report as JSON (AdvisorReport.from_json reads it back).
to_markdown()
¶
A Markdown summary: the baseline, one row per step, and the mapping to apply.
StepAdvice
¶
Bases: BaseModel
The advice for one step: the cheapest candidate that keeps the score, if any.
StepModels
¶
An LLMPort that answers each step with steps[step], else with baseline.
Steps are node:<name> (any call made inside a flow node) or stage:<stage> (a tool
loop, agent or other stage); the key "*" covers every step not listed. Pass it as
FlowRunner(model=...), ToolLoopTarget(model, ...) or run_tool_loop(model, ...).
A routed port gets the request with model=None, so it uses its own model.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
cache_key_fields()
¶
Every routed port's key material, plus the flow node running now (if any).
generate(request)
async
¶
The reply of the port mapped to the current step.
Raises:
| Type | Description |
|---|---|
ModelError
|
as the chosen port. |
port_for(step)
¶
The port that answers step.
stream(request)
async
¶
The stream of the port mapped to the current step.
Raises:
| Type | Description |
|---|---|
ModelError
|
as the chosen port. |
StepUse
¶
Bases: BaseModel
What one step's model calls cost in one eval run, summed over its cases.
Trial
¶
Bases: BaseModel
One eval run: model answers step and every other step stays on the baseline.
advise(target_factory, cases, graders, *, candidates, baseline, per_step=True, min_score_ratio=0.98, prices=None, max_spend=None, max_output_tokens=DEFAULT_MAX_OUTPUT_TOKENS, executor=None, concurrency=4, pass_at=1.0, verify=True, name='')
async
¶
Run the evals on baseline, then each cheaper candidate per step; report the advice.
target_factory(model) builds the eval target around the given port (pass it on as
FlowRunner(model=...), ToolLoopTarget(model, ...), ...). Steps are the flow nodes and
stages that called it. Each trial swaps one step's model and keeps the rest on the
baseline; with per_step=False a candidate replaces every step at once. prices is a
Prices or a table mapping (default: the executor ledger's, else the built-in table).
max_spend (USD) caps every model call the advisor makes, LLMJudge graders included:
each holds its worst-case cost (output limited to max_output_tokens) before it runs and
settles the real cost after; the advisor stops early when a hold does not fit. verify
reruns the evals with all advice at once; report.mapping holds the advice only when that
run kept the score (else it is empty and report.note says why). Nothing is applied: pass
report.mapping to apply_mapping yourself.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad option, an unknown baseline, a non-port candidate, or, with
|
advise_sync(target_factory, cases, graders, **options)
¶
apply_mapping(baseline, mapping, candidates)
¶
A StepModels from an advisor mapping {step: candidate name}.
Raises:
| Type | Description |
|---|---|
ConfigError
|
a mapped name is not in |
current_step(request)
¶
The step of a model call running now.
entry_step(entry)
¶
The step of a ledger entry, from its metadata["node"] and stage.
step_of(stage, node='')
¶
The step of a model call: node:<node> inside a flow node, else stage:<stage>.
agent_adapter.evals.from_runs¶
Turn recorded runs into eval cases: production runs become regression cases for CI.
ExpectedFrom = Literal['output', 'none']
module-attribute
¶
case_from_run(run, *, expected_from='output', case_id='', extra_tags=())
¶
A case from one run's recorded content, or why there is none (never invented data).
Raises:
| Type | Description |
|---|---|
ConfigError
|
a bad |
Also exported here, documented under the canonical path: RecordedRun, RunCase, RunCases, SkippedRun, cases_from_runs, write_cases.
agent_adapter.evals.graders¶
Ready-made graders async (case, run) -> Score | None; they never raise (an error is None).
Check = Callable[[EvalCase, Any], CheckResult | Awaitable[CheckResult]]
module-attribute
¶
JUDGE_SCHEMA = OutputSchema(name='judge_score', json_schema={'type': 'object', 'properties': {'value': {'type': 'number', 'description': '0 (fails) to 1 (fully meets the rubric)'}, 'passed': {'type': 'boolean'}, 'notes': {'type': 'string', 'description': 'one short reason'}}, 'required': ['value', 'passed', 'notes'], 'additionalProperties': False})
module-attribute
¶
all_of(*graders, weights=None)
¶
Weighted mean of the graders that graded; passed only if every one graded and none failed.
A sub-grader that errors or does not grade makes passed False (fail closed); passed is
None only when every sub-grader gave a value without a verdict.
Raises:
| Type | Description |
|---|---|
ConfigError
|
no graders, or weights of the wrong length, negative or summing to 0. |
code_check(fn)
¶
Your check: fn(case, run), sync or async, returns a bool, a float in [0, 1] or a Score.
contains(*, normalise=True)
¶
1.0 when the expected text (or one of a list of them) appears in the output.
exact(*, normalise=True)
¶
1.0 when the output equals the expected text (or one of a list of them).
json_schema(schema)
¶
1.0 when the output (JSON text, a dict or a model) matches schema.
Checked keywords: type, enum, const, bounds, lengths, properties, required, additionalProperties, items, anyOf and oneOf; others are ignored.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
llm_judge(model, rubric, *, stage='judge', show_expected=True, trust_passed=True)
¶
The optimiser's form of LLMJudge: it keeps the judge's own passed by default.
Raises:
| Type | Description |
|---|---|
ConfigError
|
an empty rubric. |
max_cost(usd)
¶
Passes when the run cost at most usd (from the ledger, else the events).
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
max_latency(seconds)
¶
Passes when the run took at most seconds of wall time.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
regex(pattern=None, *, flags=re.IGNORECASE)
¶
1.0 when pattern (default: the expected pattern or list of them) matches the output.
Raises:
| Type | Description |
|---|---|
ConfigError
|
|
tools_called(*names, forbid=(), only=False)
¶
Every tool in names was called, none in forbid was, and with only no other one.
value is the share of names called (0 when a forbidden or extra tool ran).
Raises:
| Type | Description |
|---|---|
ConfigError
|
no names and nothing forbidden, or a name that is not text. |
Also exported here, documented under the canonical path: Grader, LLMJudge.