Skip to content

Module 07: running evaluations

This module runs the full offline evaluation pipeline: the predictor calls the live workflow for every dataset item, the scoring service computes metrics, and the gate decides whether the run passes.

Run every command in this module from your scaffold pod directory, the one where you ran uv sync in Module 01 §1.2.

7.1 Run the evaluation

Make sure you're still on VPN. Both shared services already run for you in the cloud (see Module 01 §1.1).

uv run python evaluation/offline_eval/main.py run \
  --run-name workshop_local \
  --no-fail-on-gate

--no-fail-on-gate keeps the process exit code 0 even when the gate fails so you can inspect results without the run aborting. Remove the flag once you are happy with the metric thresholds.

How scoring actually runs

Scoring a batch is a scoring job, not one blocking call: main.py run submits the batch to the scoring service and polls it automatically until it finishes, so the command can never time out regardless of how long a batch takes. Each judge call within the batch runs concurrently (bounded by the scoring service's own configured limit), rather than one metric at a time, so a batch across several metrics finishes faster than the sum of every individual judge call.

While it waits, main.py run's own terminal prints a progress line on every poll, naming the job's status and how many judge calls have finished:

Scoring job 3f2a1c9e...: running (7/12 evaluations)

A reasoning-tier LLM-judge call typically takes 9 to 30 seconds, so seeing the same job sit at running for a while between progress lines is normal for a larger batch or a slower judge model. It isn't evidence of a stuck process. If you want to confirm independently, the scoring service's own terminal also logs when it starts and finishes a submitted job.

7.2 Inspect results

Results are written to evaluation/offline_eval/outputs/. Each run creates a timestamped folder containing:

File Contents
predictions.jsonl Raw workflow outputs per item, as PredictionRecords
scores.jsonl Per-item, per-metric MetricResults from the scoring service
aggregate_metrics.json Per-metric averages, min/max, and pass rates
gating_result.json Gate result (pass/fail, and which metrics failed if any)
run_summary.json Combined overview: item counts, aggregate metrics, gate result, and the Langfuse experiment (dataset-run) URLs this run published to

The run also publishes an experiment (a dataset run) to Langfuse where you can compare scores across runs and see item-level metric details.

7.3 Read the gate result

{
  "passed": false,
  "mode": "all_metrics",
  "minimum_item_count": 1,
  "total_items": 10,
  "failing_metrics": [
    {
      "scope": "reply_generation",
      "metric_name": "reply_correctness",
      "actual": 3.32,
      "threshold": 3.8,
      "reason": "Aggregate score 3.32 for 'reply_correctness' did not meet threshold 3.8."
    }
  ]
}

failing_metrics names exactly which metric(s) on which scope(s) missed their threshold and by how much, enough to decide whether the failure is a real regression, an unlucky LLM-judge sample (Module 06), or a rubric that needs tuning, without re-deriving it from scores.jsonl by hand.

Continue to Module 08.