Skip to content

Module 04: run manifest

This module covers the two YAML files that configure an evaluation: the project manifest, which declares every metric and dataset once, and the run file, which names one run's scopes and inherits the rest. Together they resolve to a RunManifest: which datasets, evaluators, thresholds, and gate rules define one evaluation run. Everything in Modules 05 to 07 executes against a run you understand by the end of this one.

Every file path in this module is relative to your scaffold pod directory, the one where you ran uv sync in Module 01 §1.2.

4.1 Inspect the two files

evaluation/
  evaluation_manifest.yaml          the project manifest: metrics and datasets
  evaluation_manifest.schema.json   its JSON Schema, for the editor
  runs/
    workshop_local.yaml             the run file this workshop runs
    run_file.schema.json            its JSON Schema

Open evaluation/runs/workshop_local.yaml first:

# yaml-language-server: $schema=run_file.schema.json
name: workshop_local
description: Workshop offline-evaluation run over both support-assistant scopes.
predictor_adapter: evaluation.offline_eval.predictors:SupportWorkflowPredictor
scopes:
- intent_classification
- reply_generation
gate:
  mode: all_metrics
  minimum_item_count: 1
Field Meaning
name Identifies this run: becomes part of the Langfuse dataset-run name and the output folder path
predictor_adapter The predictor class the run uses, as module.path:ClassName (Module 05)
scopes Which evaluation scopes this run covers
gate Run-level pass/fail policy (4.3)

Everything else comes from evaluation/evaluation_manifest.yaml:

Block Meaning
evaluators Every metric this pod uses, each listing the scopes it scores (4.2)
dataset_sync.scope_to_dataset Which Langfuse dataset backs each scope
dataset_sync.scoped_dir, archive_dir Where eval-dataset keeps working copies and release snapshots (Module 08)
baselines Scores promoted from a reference run; empty until you promote one

A run file can also replace a scope's dataset, narrow a scope to some of its metrics, or change one metric's threshold for that run only, without touching the project manifest. To see exactly what a run will execute, resolve it:

uv run eval-dataset resolve-run \
  --run-file evaluation/runs/workshop_local.yaml \
  --manifest evaluation/evaluation_manifest.yaml

It prints the resolved RunManifest, with each scope's dataset and metrics spelled out.

The first line of each file names its JSON Schema, so VS Code (with the YAML extension) checks the file as you type: an unknown key, a missing field, or a predictor_adapter that is not module.path:ClassName is underlined before anything runs.

4.2 Anatomy of one metric

Each entry under evaluators in the project manifest is one metric, keyed by its name:

Field Required Meaning
framework Yes Which scoring-service framework handles this metric: deepeval, azure_ai_evaluation, builtin_deterministic, or custom_rubric
evaluator Yes The evaluator class name within that framework, e.g. GEval, StructuredFieldMatch, inline
scopes Yes Every scope this metric scores
enabled No (default true) Set false to keep a metric defined but skip running it
threshold No Pass/fail cutoff on the normalized 1-to-5 scale, regardless of the framework's native scale
direction No (default higher_is_better) higher_is_better or lower_is_better
evaluator_params No Framework-specific constructor arguments, forwarded verbatim to the evaluator class. The scoring service's catalogue declares which parameter names each evaluator accepts; an unknown key is rejected, not silently ignored

Two worked examples from the current manifest: one LLM-judge metric, one deterministic metric:

evaluators:
  intent_correctness:
    framework: deepeval
    evaluator: GEval
    threshold: 3.8
    evaluator_params:
      name: IntentCorrectness
      criteria: >-
        Score 1.0 if the predicted intent exactly matches the expected intent. ...
      evaluation_params:
      - ACTUAL_OUTPUT
      - EXPECTED_OUTPUT
      input_key: email_body
      actual_output_key: intent
      expected_output_key: intent
    scopes:
    - intent_classification
  account_status_match:
    framework: builtin_deterministic
    evaluator: StructuredFieldMatch
    threshold: 5.0
    evaluator_params:
      field: account_status
      expected_field: expected_account_status
    scopes:
    - reply_generation

StructuredFieldMatch is a generic deterministic evaluator: it compares prediction.prediction[field] against item.expected_output[expected_field] for exact equality (expected_field defaults to field when both sides use the same key). Because a match/mismatch only ever produces normalized score 5.0/1.0, threshold: 5.0 means only an exact match passes. A 3.0 threshold would accept nothing, since there is no partial-credit value this evaluator can produce.

The two pods' manifests aren't identical, and that's expected

langchain-pod's manifest adds a third reply_generation metric, answer_relevancy (DeepEval's AnswerRelevancyMetric), that agent-framework-pod's does not define. The project manifest is pod-local configuration, not a fixed shared shape; a pod adds, removes, or tunes metrics independently of any other pod's manifest, as long as each metric resolves against the scoring service's evaluator catalogue.

4.3 The gate

gate:
  mode: all_metrics
  minimum_item_count: 1

mode: all_metrics means every enabled metric across every scope must meet its own threshold for the run to pass. One failing metric fails the whole run. minimum_item_count guards against a false-positive pass on an accidentally-empty dataset (e.g. a typo'd dataset name that resolves to zero items).

4.4 Hands-on: extend the manifest

Pick any existing catalogue evaluator you have not yet used in this manifest (check scoring_app/docs/01-evaluator-catalogue.md for the full list) and add it as a new metric under evaluators in evaluation/evaluation_manifest.yaml, with the scope it should score in scopes. A builtin_deterministic evaluator is the fastest to verify, since its outcome is a plain equality check rather than an LLM judgment. Run eval-dataset resolve-run (4.1) and check the metric appears under its scope, then re-run the CLI (Module 07) and confirm your new metric appears in aggregate_metrics.json.

Continue to Module 05.