Skip to content

Evaluation

Evaluation converts application behavior into optimization evidence. The common optimize(...) API builds an output evaluation internally. Use the explicit API when you need repeated calls, cache control, validators, or multiple metric roles.

Output scoring

Evaluation.output runs the subject exactly once, then gives the result to the scorer:

from pydantic_gepa import Candidate, Evaluation, Example, MetricResult, Runtime

runtime = Runtime[str, str](
    lambda text: text.strip().lower(),
    identity="normalize-text-v1",
)

evaluation = Evaluation.output(
    runtime,
    lambda ctx: {
        "accuracy": float(ctx.output == ctx.example.expected_output),
        "length": MetricResult(
            score=float(len(ctx.output or "")),
            role="diagnostic",
        ),
    },
    objective="accuracy",
)

result = evaluation.run(
    Candidate(values={"instructions": "Normalize text."}),
    Example(inputs="  HELLO ", expected_output="hello"),
)

Evaluator-controlled execution

Evaluation.controlled gives the evaluator a Context. The evaluator may call ctx.run() or await ctx.arun() zero, one, or several times:

from pydantic_gepa import Context

def stability(ctx: Context[str, str, None]) -> MetricResult:
    first = ctx.run()
    second = ctx.run()
    return MetricResult(
        score=float(first == second),
        feedback="Compared two independent executions.",
        side_info={"first": first, "second": second},
    )

evaluation = Evaluation.controlled(runtime, stability)

This form supports robustness checks, pairwise judges, repeated sampling, and evaluators that reject an input without invoking the subject.

Metric roles

MetricResult supports three roles:

Role Purpose
objective Drives candidate selection
constraint Represents a condition that must remain acceptable
diagnostic Preserved for analysis and reflection without becoming the objective

Use one named objective for GEPA. Preserve other metrics in feedback and side information instead of collapsing every concern into an unexplained scalar.

MetricResult(
    score=0.75,
    role="objective",
    feedback="Two of eight expected fields were wrong.",
    side_info={"wrong_fields": ["postal_code", "country"]},
)

Failure and validation policy

EvaluationConfig controls invalid candidates, task failures, evaluator failures, infrastructure errors, failure scores, score bounds, caching, and validation hooks:

from pydantic_gepa import EvaluationConfig

config = EvaluationConfig(
    on_task_error="record",
    on_evaluator_error="raise",
    invalid_score="raise",
    failure_score=0.0,
    min_score=0.0,
    max_score=1.0,
)

NaN, infinity, missing objectives, and out-of-range objective values are never silently forwarded to GEPA.

Caching

InMemoryCache and the CacheStore contract support repeated evaluation. The cache identity includes the candidate, example, runtime, evaluator, stage, and evaluation configuration. Mark an evaluation deterministic=False when output may vary; nondeterministic evaluation is not cached unless explicitly enabled.

Traces and artifacts

During controlled evaluation, Context.capture() and Context.acapture() retain individual runtime attempts. Context.artifact() adds structured evidence for reflection or external recording. Keep feedback concise and place large or binary evidence in artifacts or attachments.

Final rescoring

After search, evaluate a selected candidate against a stable split:

from pydantic_gepa import rescore

final = rescore(evaluation, result.best_candidate, split, policy="validation")

Use arun_rescore in an async application. Final test data should not influence candidate mutation or selection.