Evals
Run dataset and CI evaluations against prompts, agents, tools or workflows. Hosted targets make network requests and incur provider charges; “dataset evaluation” does not mean model-free or offline execution.
Start Here
Before running the local commands below, configure a local workspace
so .actspan.env disables portal reporting. Export your OpenAI key before
live calls:
export OPENAI_API_KEY="<your-openai-api-key>"
Run one smoke case after the target succeeds normally:
cd "$HOME/actspan/actspan-examples/openai/data_enrichment"
actspan eval run eval_summarize_data_enrichment.yaml \
--mode smoke \
--fail-on-constraints \
--report-json /tmp/actspan-eval.json
Expected result: the summary reports total cases, pass rate, must-pass failures, constraint status, failed case IDs, average cost, and the detailed case-record location.
A nonzero exit caused by a failed constraint is an evaluation result, not automatically a CLI defect. Read the failed checks before changing the runner.
Use the public examples archive for the command above. It contains the complete summary suite and fixed dataset. The field synopses below are reference fragments, not additional files in that archive. Create their workspace and dataset before running them.
Syntax (core fields)
id: <eval_id> # required
description: "..." # optional
prompt: # inline prompt OR target
provider: openai
model: gpt-4.1-mini
template: |
...
# or:
target:
file: ./workspace.yaml
kind: agent | prompt | tool | workflow
name: <ref_name>
dataset: # required
from_file: ./datasets/basic_math.yaml
format: yaml|jsonl
id_key: case_id
input_key: question
expected_substring_key: expected
input_map: { question: question } # map dataset fields into template/target
metric: ... | checks: { rules: [...] } # at least one required
variants: [] # optional sweeps
params: {} # optional model params applied to each run
recommend: # optional recommend config
search: { method: grid, params: { temperature: { values: [0.0, 0.3] } } }
trials: 1
Field notes:
idrequired; used aseval_idin run outputs.- Use either
prompt(inline) ortarget(reference to an existing prompt, agent, tool or workflow). - If
targetis omitted and an adjacentworkspace.yamlcontainssummarize_agent, the CLI auto-selects that agent as the default eval target. dataset: inline rows or file reference; supports key remapping.metricorchecksmust be present.variants: optional overrides to sweep params/template.recommend: optional search settings foreval recommend.params: optional model params applied to each run (temperature, max_output_tokens, etc.).input_map: map dataset fields into template/target variables.constraints: optional hard constraints for recommend/run gating.suggest: useactspan eval suggeston the--runs-outartifact to generate isolated improvement suggestions.- eval commands default to
--state-mode minimal, which suppresses runtime state/history/cache/telemetry and writes default eval artifacts under.actspan/evals/<eval_id>/. --state-mode offwrites nothing to disk and is incompatible with--runs-out,--report-json,--lock-out, and--write-yaml-patch.--no-stateis a legacy alias for--state-mode off.--mode smoketrims the dataset and grid for quick, low-cost smoke runs.--max-concurrency Nruns eval cases in parallel. The default is4, and progress plus ETA are emitted to stderr during runtime eval commands.--resume-from PATHresumes a runtime eval from an existingruns.jsonlartifact. When resuming in place, new rows append incrementally as cases finish.
Common params (examples)
temperature: 0–2; lower settings can reduce sampling variability but do not guarantee deterministic results. Good defaults: 0–0.3 for classify, 0.2–0.5 for summarize.max_output_tokens: cap verbosity; keeps costs bounded.top_p,frequency_penalty,presence_penalty: standard OpenAI sampling knobs.- Any OpenAI request param supported by your provider adapter can be placed in
params.
Workflow evaluation from the public archive
The public refund directory includes eval_refunds.yaml with this target:
target:
file: ./workspace.yaml
kind: workflow
name: refund_request
This is an excerpt: run the complete downloaded file with its matching workspace, policies, synthetic fixtures and fresh ledger as described in the refund walkthrough. Evaluations can write sandbox payments. A stage or prompt evaluation does not establish full workflow coverage.
Reference example: create sample_eval.yaml
The following self-contained illustrative dataset makes live OpenAI requests. It is not an additional file shipped in the public examples archive.
id: math_sample
dataset:
- { id: answer42, question: "What is 6 times 7?", expected_substring: "42" }
prompt:
provider: openai
model: gpt-4.1-mini
template: |
You are a calculator. Respond with ONLY the numeric answer.
Question: {{ question }}
input_map: { question: question }
checks:
rules:
- id: must_contain_42
type: must_include_any
phrases: ["42"]
recommend:
search:
method: grid
params:
temperature: { values: [0.0, 0.3] }
trials: 1
Comprehensive example (inline dataset + metric + checks + variants):
id: classify_eval
description: "Classify short questions into qa vs fact_finding"
prompt:
provider: openai
model: gpt-4.1-mini
template: |
You are a classifier. Return JSON: {"routing": "qa|fact_finding", "confidence": 0-1}.
Question: {{ question }}
params:
temperature: 0
input_map: { question: input }
dataset:
- { id: case1, input: "What is 2+2?", expected_substring: "qa" }
- { id: case2, input: "Tell me about my account", expected_substring: "fact_finding" }
metric:
type: contains_substring
field: output_text
checks:
rules:
- id: must_output_json
type: regex_must_match
regex: "^\\{.*routing.*\\}$"
variants:
- id: temp_sweep
overrides:
params:
temperature: 0.2
constraints:
- metric: pass_rate
op: ">="
value: 0.9
Reference fragment for your own declared classify_agent and separately created JSONL dataset:
id: agent_eval
target:
file: ./workspace.yaml
kind: agent
name: classify_agent
dataset:
from_file: ./datasets/classify_cases.jsonl
format: jsonl
id_key: id
input_key: input
expected_substring_key: expected
params:
temperature: 0.1
metric:
type: contains_substring
field: output_text
checks:
rules:
- id: must_return_json
type: regex_must_match
regex: "^\\{.*routing.*\\}$"
Tool source evidence
tool_called proves that a tool invocation was observed. It does not prove
that a retrieval or hosted search returned inspectable source evidence. Use
tool_sources_present when the workflow requires actual source URLs:
checks:
rules:
- id: web_search_completed
type: tool_called
name: web_search
ok: true
- id: web_search_returned_sources
type: tool_sources_present
name: web_search
min_count: 1
hard_fail: true
The source check counts unique returned URLs. A reported source count without the underlying source records does not pass the check. Passing this rule proves that inspectable source evidence exists; it does not by itself prove that every generated claim is supported by a specific source.
For prompt and agent targets, feedback retries and JSON repair retain the actual tool traces from earlier attempts. Attempt metadata identifies the response that produced each trace; the final raw provider response still describes the call that produced the final output. Usage totals include the returned attempts. A repair call with no tools cannot erase an earlier MCP call or manufacture evidence for a tool that was never called. Schema-valid repair alone does not establish that repaired values are supported by the retrieved sources.
Inline dataset with variants (sweep temperature):
id: inline_sweep
dataset:
- { id: a, input: "Paris?", expected_substring: "qa" }
- { id: b, input: "Tell me about your systems", expected_substring: "fact_finding" }
prompt:
provider: openai
model: gpt-4.1-mini
template: "Classify: {{ input }}"
input_map: { input: input }
metric:
type: contains_substring
field: output_text
variants:
- id: temp0
overrides: { params: { temperature: 0.0 } }
- id: temp03
overrides: { params: { temperature: 0.3 } }
Recommend sweeping multiple params:
id: sweep_multi
prompt:
provider: openai
model: gpt-4.1-mini
template: "Answer briefly: {{ question }}"
input_map: { question: input }
dataset:
- { id: x, input: "What is 2+2?", expected_substring: "4" }
metric: { type: contains_substring, field: output_text }
recommend:
search:
method: grid
params:
temperature: { values: [0.0, 0.3] }
max_output_tokens: { values: [64, 128] }
trials: 1
This grid has 2 × 2 = 4 configurations. Planned model executions are
configurations × dataset cases × trials; provider retries, tools and output
repair can add requests. With one case and one trial, this example plans four
executions. Review the grid and pricing before launching it.
Notes:
- You can sweep any provider param your adapter supports (e.g.,
top_p, penalties,logprobs,tool_choice, etc.). Each key underparamsproduces a cartesian product of values. - Keep the grid reasonable; large grids can be expensive. Use
trials> 1 for repeated sampling per config if desired.
Inputs vs. Outputs in evals
- Inputs to the model come from
datasetrows, mapped viainput_mapinto your prompt/target variables. - Outputs produced by
actspan eval run|sweep|recommenddefault to.actspan/evals/<eval_id>/runs.jsonl,.actspan/evals/<eval_id>/report.json, and forrecommend,.actspan/evals/<eval_id>/lock.yaml. --runs-out,--report-json,--summary-json, and--lock-outoverride those defaults where supported.actspan eval run --report-json ...writes a compact machine report by default. Full raw case evidence stays inruns.jsonl; pass--report-detail fullonly when you need one forensic single-file artifact.actspan eval run --summary-json ...writes a small human-review report with metrics, failed cases, compact per-case summaries, tool evidence, and links to raw evidence.- You can also save outputs elsewhere by passing explicit paths.
Each JSONL line typically includes:
eval_id,case_id(dataset id),variant_id(if any)input(mapped fields),prompt_name/targetinforesponsefields:output_text,output_parsed(if JSON),usage(tokens/cost),modelmetricresult orchecksresults (pass/fail, reasons)constraints_okandconstraint_reasons(if constraints are used)statusanderror(if the call failed)
- You can post-process these outputs for reporting or feed them into prompts in follow-up runs (e.g., use
output_parsedto debug prompt quality). - You can also feed the runs artifact into
actspan eval suggestto generate isolated suggestions about what to change next. - Suggestions are validation artifacts, not automatic edits. The intended loop is:
suggest -> candidate revision -> compare/gate -> human decision. actspanshould validate a suggestion before trusting it, but it should not auto-apply that suggestion to source or release state.- Hosted-tool targets such as
web_searchemit a warning because they can be slow/expensive. Use--mode smokeor smaller datasets/grids for those evals. actspan eval prune --eval-id <id>removes.actspan/evals/<id>/for a completed eval run.actspan cache prune --older-than 1dandactspan state prune --older-than 7dclean up old local artifacts.
Example:
actspan eval run sample_eval.yaml
actspan eval recommend sample_eval.yaml \
--runs-out .actspan/evals/math_sample/recommend.runs.jsonl \
--report-json eval_reports/sample_recommend.json \
--lock-out eval_locks/candidate.lock.yaml
actspan eval suggest .actspan/evals/math_sample/recommend.runs.jsonl \
--report-json eval_reports/sample.suggestions.json
actspan eval compare sample_eval.yaml \
eval_locks/baseline.lock.yaml \
eval_locks/candidate.lock.yaml \
--runs-out eval_runs/sample_compare.jsonl \
--report-json eval_reports/sample_compare.json
actspan eval dataset add-case eval_datasets/regressions.yaml \
--input "Where is my order?" \
--expected-substring "tracking" \
--must-pass \
--slice regressions
actspan eval dataset add-from-runs eval_datasets/regressions.yaml \
.actspan/evals/math_sample/runs.jsonl \
--slice regressions
actspan eval grader calibrate eval_reports/sample.report.json labels/human_labels.yaml \
--report-json eval_reports/sample.calibration.json
actspan eval gate eval_reports/sample_compare.json --format json
Resume a long-running eval from the same artifact:
actspan eval recommend sample_eval.yaml \
--runs-out .actspan/evals/sample_eval/runs.jsonl \
--resume-from .actspan/evals/sample_eval/runs.jsonl \
--report-json .actspan/evals/sample_eval/report.json
Canonical objects emitted today:
actspan.eval.datasetactspan.eval.grader_versionactspan.eval.case_resultactspan.eval.experimentactspan.eval.suggestionactspan.eval.grader_calibrationactspan.eval.release_gateactspan.eval.review_report
What suggest is for:
- identify clustered failure patterns
- surface recurring client prompt shapes
- create isolated suggestions before starting an experiment
- preserve debug refs back to the sampled failed cases
What suggest is not for:
- deciding the winner between baseline and candidate revisions
- promoting changes directly without experiment/eval validation
What compare is for:
- running the same eval spec against baseline and candidate lockfiles
- generating a stable experiment artifact
- showing deltas, regressions, and a promotion-oriented recommendation
What compare is not for:
- pairwise side-by-side LLM judging
- asking one judge prompt to pick a winner between baseline and candidate outputs
- subjective preference tasks such as "which answer sounds better?" without explicit per-output scoring rules
What dataset is for:
- turning manual regressions into reusable dataset rows
- importing failed eval runs into regression slices
- preserving enough source metadata to trace why a case exists later
What gate is for:
- turning compare or run evidence into a stable release decision
- marking a candidate as
promotable,review, orblocked - preserving the baseline/candidate evidence bundle that drove the decision
What grader calibrate is for:
- checking grader agreement against human-labeled cases
- measuring false positives, false negatives, disagreement rate, and score error
- producing a reusable calibration artifact tied to the current
grader_version
Example run output line (abridged):
{
"eval_id": "classify_eval",
"case_id": "case1",
"variant_id": "temp0",
"input": { "input": "What is 2+2?" },
"response": {
"output_text": "{\"routing\":\"qa\",\"confidence\":0.98}",
"output_parsed": { "routing": "qa", "confidence": 0.98 },
"usage": { "input_tokens": 42, "output_tokens": 18, "total_tokens": 60 },
"model": "gpt-4.1-mini-2025-04-14"
},
"metric": { "type": "contains_substring", "score": 1.0, "passed": true },
"checks": [
{ "id": "must_return_json", "passed": true }
],
"constraints_ok": true,
"status": "success"
}
The remaining compare, dataset and calibration commands are reference examples. Create the named lockfiles, datasets, reports and human labels first; those paths are not supplied by the public archive. Do not run a release gate on a missing or mismatched report.
Validation
actspan validate sample_eval.yaml --schema eval
Common errors
- Missing
idor dataset fields. - Providing both
promptandtarget(use only one). - Checks/metric absent.
- Dataset path wrong or format not set to yaml/jsonl correctly.