Skip to main content
The trials engine runs each eval case K independent times so you get a pass rate and a frontier of shaky cases, not one lucky (or unlucky) sample.

Quick Start

1

Run K attempts per case

Build an agent, wrap your cases in an EvalPackage, and call run_trials.
2

Async variant

Use arun_trials inside an existing event loop.
3

Save and reload

Persist the full report — including per-attempt trajectories — and load it back.
4

Gate CI on the pass rate

Read summary() for a stable, machine-parseable dict and fail loudly below your bar.

How It Works

Each case runs K times on a fresh, isolated copy of the agent; every completed attempt is scored, then results are assembled into a TrialReport.

Scoring — one contract, four fallbacks

Scoring runs only on stop_reason="completed" attempts and resolves in this exact order.
  1. EvalCase.verify callable — if present, it wins. A raising verify is caught and recorded as a failed TrialScore with reason="verify error: ..." — one bad metric never aborts the report.
  2. Tool assertions — if case.metadata["expected_tools"] is set, the engine reuses ReliabilityEvaluator._extract_tool_calls. Score is 1 − (missing / expected); passes only when every expected tool was called.
  3. Criteria / expected via Judge — falls through to the existing Judge. The judge’s 0–10 score is normalised to [0, 1].
  4. No scorer configured — a non-empty completion counts as a pass (reason="no scorer configured"); an empty completion is a fail.
Unscored ≠ zero. Timeouts and errors produce a TrialAttempt with score=None. Unscored attempts are excluded from pass-rate arithmetic — they are not counted as failures. Do not conflate “unscored” with “scored 0”.

Configuration Options

run_trials() and arun_trials() share the same signature.

TrialScore

TrialScore.to_dict() returns {"value", "passed", "reason"}.

TrialAttempt

TrialAttempt also has to_dict(), from_dict(), and to_eval_result() (adapts to the existing EvalResult shape).

TrialReport methods

New EvalCase / EvalResult fields

verify return values are coerced: boolTrialScore(1.0/0.0), int/floatTrialScore(value=x, passed=x >= 0.5), TrialScore → returned as-is, anything else → truthiness fallback.

Isolation guarantees

Every attempt runs on an independent copy of the agent so K attempts are K real samples and your live agent is never mutated.
  • Fresh agent_id and _session_id per attempt.
  • chat_history rebound to a new list (not shared with the caller).
  • Independent system-prompt cache.
  • memory and knowledge writes severed (set to None for the attempt).

Timeouts & wedged agents

case.timeout_seconds bounds when the report returns, not when every side effect stops. The attempt runs in a worker thread via asyncio.to_thread; on timeout the coroutine returns immediately with stop_reason="timeout", but Python cannot force-kill the underlying thread — a wedged agent call may keep running in the background until it finishes on its own.

Common Patterns

Frontier-first iteration

Run k=8, look at report.frontier(), and focus improvement work there — mastered and zero-pass cases are lower-value to iterate on.

CI gating with summary()

Assert a threshold on the machine-parseable dict inside a pytest job.

Save trajectories → SFT dataset

Persist the report, then export it to a trainer-ready dataset.

Which scorer should I use?

Pick the strongest signal your case can provide.

Best Practices

Gauge cost with a small k first, then scale up once the pipeline is trustworthy.
concurrency sizes the thread pool — set it to your API rate limit divided by 2, not higher.
A verify callable has no LLM cost and no judge bias. Use it whenever you have a deterministic metric.
Iterate on frontier() cases, not every case where pass_rates() < 1.0 — mastered and zero-pass cases are lower-value.
Set capture_record=False on very large runs (>100 cases × k>16) to keep the report JSON compact.

Evaluation Loop

Iteratively run, judge, and keep the best output

Judge

LLM-as-judge — the fallback scorer for criteria and expected

Prompt Optimizer

The other consumer of eval packages

Extensibility

run_trials is the concrete EvalRunnerProtocol