From f54dd8dbe40ccef4f1bf651b56e2a38e032a10ec Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Mon, 6 Jul 2026 08:50:31 +0200 Subject: [PATCH] docs(grading): align reference answers and grading artifacts --- README.md | 12 +- .../docs/docs/next/evaluation/batch-cli.mdx | 57 ++++--- .../docs/docs/next/evaluation/eval-cases.mdx | 109 +++++++----- .../docs/docs/next/evaluation/eval-files.mdx | 30 +++- .../docs/docs/next/evaluation/examples.mdx | 161 +++++++++--------- .../docs/next/evaluation/running-evals.mdx | 7 +- .../docs/next/getting-started/quickstart.mdx | 14 +- .../docs/next/graders/custom-assertions.mdx | 42 +++-- .../docs/docs/next/graders/llm-graders.mdx | 51 ++++-- .../docs/docs/next/graders/script-graders.mdx | 12 +- .../docs/next/graders/structured-data.mdx | 13 +- .../docs/next/guides/agent-eval-layers.mdx | 14 +- .../docs/next/guides/benchmark-provenance.mdx | 8 +- .../docs/docs/next/guides/eval-authoring.mdx | 8 +- .../integrations/autoevals-integration.mdx | 53 +++--- .../docs/next/reference/promptfoo-parity.mdx | 14 +- .../docs/next/reference/result-artifacts.mdx | 91 ++++++---- .../content/docs/docs/next/tools/results.mdx | 4 +- packages/sdk/README.md | 3 + skills-data/agentv-eval-writer/SKILL.md | 79 ++++++--- 20 files changed, 468 insertions(+), 314 deletions(-) diff --git a/README.md b/README.md index 97779b7f8..024122be1 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,11 @@ tests: ``` Plain assertion strings are short-form rubric criteria: AgentV groups them into -`llm-rubric` and writes each criterion to `grading.json.assertion_results` for the -Dashboard. Use explicit `type: llm-rubric` when you need weights, required flags, or -`score_ranges`, or when you need a custom grader prompt, grader target, or -output transforms; use string `value` for free-form rubric checks. Executable -graders use `type: script`. +`llm-rubric` and writes grader detail to `grading.json.component_results` for +the Dashboard. Use explicit `type: llm-rubric` when you need weights, required +flags, `score_ranges`, a custom grader prompt, a grader target, or output +transforms; use string `value` for free-form rubric checks. Executable graders +use `type: script`. The target can be an eval-local object when this eval needs target settings of its own: @@ -200,7 +200,7 @@ Run bundle layout: │ │ │ └── graders/ # grader files used │ │ └── sample-1/ # one materialized sample │ │ ├── result.json # compact attempt manifest -│ │ ├── grading.json # assertion_results and grader evidence +│ │ ├── grading.json # pass, score, reason, component_results │ │ ├── metrics.json # tool calls, transcript stats, behavior metrics │ │ ├── transcript.json # normalized agent transcript │ │ ├── transcript-raw.jsonl # raw agent output (debugging) diff --git a/apps/web/src/content/docs/docs/next/evaluation/batch-cli.mdx b/apps/web/src/content/docs/docs/next/evaluation/batch-cli.mdx index 6c60e8d8b..733cc52c7 100644 --- a/apps/web/src/content/docs/docs/next/evaluation/batch-cli.mdx +++ b/apps/web/src/content/docs/docs/next/evaluation/batch-cli.mdx @@ -34,11 +34,11 @@ tests: - id: case-001 criteria: |- Batch runner returns JSON with decision=CLEAR. - - expected_output: - - role: assistant - content: - decision: CLEAR + vars: + expected_output: + - role: assistant + content: + decision: CLEAR input: - role: system @@ -62,11 +62,11 @@ tests: - id: case-002 criteria: |- Batch runner returns JSON with decision=REVIEW. - - expected_output: - - role: assistant - content: - decision: REVIEW + vars: + expected_output: + - role: assistant + content: + decision: REVIEW input: - role: system @@ -156,11 +156,12 @@ Each test has its own grader that validates the batch runner output. The grader **Output (stdout):** ```json { + "pass": true, "score": 1.0, - "assertions": [ - { "text": "decision matches: CLEAR", "passed": true } - ], - "reasoning": "Batch runner decision matches expected." + "reason": "Batch runner decision matches expected.", + "checks": [ + { "text": "decision matches: CLEAR", "pass": true, "reason": "Expected and actual decisions are CLEAR." } + ] } ``` @@ -188,22 +189,23 @@ function main() { candidateDecision = undefined; } - const assertions: Array<{ text: string; passed: boolean }> = []; + const checks: Array<{ text: string; pass: boolean; reason: string }> = []; if (expectedDecision === candidateDecision) { - assertions.push({ text: `decision matches: ${expectedDecision}`, passed: true }); + checks.push({ text: `decision matches: ${expectedDecision}`, pass: true, reason: 'Expected and actual decisions match.' }); } else { - assertions.push({ text: `mismatch: expected=${expectedDecision} actual=${candidateDecision}`, passed: false }); + checks.push({ text: `mismatch: expected=${expectedDecision} actual=${candidateDecision}`, pass: false, reason: 'Expected and actual decisions differ.' }); } - const passed = assertions.every(a => a.passed); + const passed = checks.every((check) => check.pass); process.stdout.write(JSON.stringify({ + pass: passed, score: passed ? 1 : 0, - assertions, - reasoning: passed + reason: passed ? 'Batch runner output matches expected.' : 'Batch runner output did not match expected.', + checks, })); } @@ -222,15 +224,16 @@ main(); ## Structured Content -Use structured objects in `expected_output` to define expected output fields for easy validation: +Use structured objects in `vars.expected_output` to define expected output fields for easy validation: ```yaml -expected_output: - - role: assistant - content: - decision: CLEAR - confidence: high - reasons: [] +vars: + expected_output: + - role: assistant + content: + decision: CLEAR + confidence: high + reasons: [] ``` The grader extracts these fields and compares them against the parsed candidate output. diff --git a/apps/web/src/content/docs/docs/next/evaluation/eval-cases.mdx b/apps/web/src/content/docs/docs/next/evaluation/eval-cases.mdx index 2f65beea3..b9644167d 100644 --- a/apps/web/src/content/docs/docs/next/evaluation/eval-cases.mdx +++ b/apps/web/src/content/docs/docs/next/evaluation/eval-cases.mdx @@ -6,18 +6,24 @@ sidebar: slug: docs/evaluation/eval-cases --- -Tests are individual test entries within an evaluation file. Each test defines input messages, expected outcomes, and optional grader overrides. +Tests are individual test entries within an evaluation file. In normal authored +eval YAML, prompts live in top-level `prompts`, case data lives in +`tests[].vars`, and graders live in `assert`. ## Basic Structure ```yaml +prompts: + - "{{ question }}" + tests: - id: addition - input: What is 15 + 27? - - expected_output: "42" + vars: + question: What is 15 + 27? + expected_output: "42" assert: - - The answer is exactly 42 + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` ## Fields @@ -25,9 +31,9 @@ tests: | Field | Required | Description | |-------|----------|-------------| | `id` | Yes | Unique identifier for the test | -| `input` | Yes | Input sent to the target (string, object, or message array) | +| `vars` | Yes when the prompt needs row data | Prompt-template variables for this row | | `criteria` | No | Optional shared grader guidance for the case | -| `expected_output` | No | Passive gold/reference data available to graders (string, object, or message array) | +| `vars.expected_output` | No | Conventional reference-answer var for graders to consume explicitly | | `assert` | Yes | Per-test graders; plain strings become `llm-rubric` rubric checks | | `execution` | No | Per-case grader/default overrides such as `skip_defaults`; target selection belongs in top-level `target` or CLI `--target` | | `environment` | No | Per-case environment recipe (overrides suite-level) | @@ -78,27 +84,39 @@ names one specific rubric item inside a `llm-rubric` criteria array. ## Expected Output -Optional reference response for comparison by graders. Write `expected_output` -as gold/reference data the target could have produced, not as a rubric or "the -agent should..." criteria list. `expected_output` is passive by default: it is -stored on the case and passed to graders, but it does not choose a grader by -itself. A grader may treat it as a strict target, semantic reference, structured -object, or supporting context depending on the grader type. Add explicit -assertion strings, `llm-rubric`, `script`, `field-accuracy`, or another -reference-aware grader when you want the reference data evaluated. +Optional reference responses belong in `tests[].vars.expected_output` or +`default_test.vars.expected_output`. Write the value as gold/reference data the +target could have produced, not as a rubric or "the agent should..." criteria +list. `expected_output` is passive by default: it is just a var. It does not +choose a grader by itself. Add explicit assertion strings, `llm-rubric`, +`script`, `field-accuracy`, or another reference-aware grader when you want the +reference data evaluated. -A string expands to a single assistant message: +A low-friction Promptfoo-compatible semantic check puts the reference in vars +and consumes it from an explicit `llm-rubric` assertion: ```yaml -expected_output: "42" +prompts: + - "{{ question }}" + +tests: + - id: addition + vars: + question: What is 15 + 27? + expected_output: "42" + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` -For structured or multi-message expected output, use a message array: +For structured expected output, keep the object under `vars.expected_output` +and use a grader that knows how to compare it: ```yaml -expected_output: - - role: assistant - content: "42" +vars: + expected_output: + status: approved + confidence: 0.95 ``` ## Per-Case Execution Overrides @@ -354,12 +372,9 @@ context; each rubric item uses `outcome` for the specific desired behavior being ```yaml tests: - id: denied-party - input: - - role: user - content: Screen "Acme Corp" against denied parties list - expected_output: - - role: assistant - content: "DENIED" + vars: + input: Screen "Acme Corp" against denied parties list + expected_output: "DENIED" assert: - type: contains value: "DENIED" @@ -409,11 +424,11 @@ Required gates are evaluated after all graders run. If any required grader falls ## How Reference Fields and `assert` Interact -`expected_output` is reference data, not a grader. It is stored on the case and -provided to graders that know how to use it, but it does not create an LLM -grading call by itself. A grader can use that data as an exact target, a -semantic reference, a structured comparison object, or supporting context. Put -the grading contract in `assert`. +`vars.expected_output` is reference data, not a grader. It is provided to +graders that know how to use it, but it does not create an LLM grading call by +itself. A grader can use that data as an exact target, a semantic reference, a +structured comparison object, or supporting context. Put the grading contract in +`assert`. Plain assertion strings are the default shape for semantic checks: @@ -442,18 +457,19 @@ tests: ``` When `assert` is defined, only the declared graders run. No implicit grader is -added because `expected_output` exists. Declared graders such as plain rubric -strings, explicit `llm-rubric` entries, or `script` graders receive the case context, including -`expected_output`, as input automatically. +added because `vars.expected_output` exists. Declared graders such as plain +rubric strings, explicit `llm-rubric` entries, or `script` graders receive the +case context, including `expected_output`, as input automatically. -This means a case with `expected_output` and only deterministic assertions evaluates only -those deterministic assertions: +This means a case with `vars.expected_output` and only deterministic assertions +evaluates only those deterministic assertions: ```yaml tests: - id: deterministic-reference - input: "What is 2 + 2?" - expected_output: "4" # reference data only + vars: + input: "What is 2 + 2?" + expected_output: "4" # reference data only assert: - type: contains # only this grader runs value: "4" @@ -465,12 +481,13 @@ keep those checks in `assert`: ```yaml tests: - id: verification-learning-capture - input: | - Decide what durable repo change should be made after a PR closeout - revealed reusable verification workflow lessons. - expected_output: | - The durable repo change is to update .agents/verification.md with the - reusable verification workflow lessons. + vars: + input: | + Decide what durable repo change should be made after a PR closeout + revealed reusable verification workflow lessons. + expected_output: | + The durable repo change is to update .agents/verification.md with the + reusable verification workflow lessons. assert: - The answer recommends updating .agents/verification.md rather than leaving the learning only in PR comments or private evidence. - The answer avoids preserving one-off observations as durable guidance. @@ -527,4 +544,4 @@ tests: `metadata` is passed to lifecycle hooks as `case_metadata`, preserved in result records, and available to in-process custom assertions. AgentV does not interpret arbitrary metadata keys itself; use `environment`, `extensions`, -`prompts`, `expected_output`, and `assert` for operational behavior. +`prompts`, `vars.expected_output`, and `assert` for operational behavior. diff --git a/apps/web/src/content/docs/docs/next/evaluation/eval-files.mdx b/apps/web/src/content/docs/docs/next/evaluation/eval-files.mdx index 202872f19..4c2c945e1 100644 --- a/apps/web/src/content/docs/docs/next/evaluation/eval-files.mdx +++ b/apps/web/src/content/docs/docs/next/evaluation/eval-files.mdx @@ -105,8 +105,9 @@ Raw cases are just case data: ```yaml # evals/cases/refund-smoke.cases.yaml - id: damaged-item - input: The item arrived damaged. What should support do? - expected_output: Offer a replacement or refund path. + vars: + question: The item arrived damaged. What should support do? + expected_output: Offer a replacement or refund path. ``` A run-focused eval stays ordinary eval YAML while choosing a target and run controls: @@ -126,7 +127,10 @@ tests: - id: local-edge-case vars: question: Can a final-sale item be refunded after damage in transit? - expected_output: Explain the final-sale exception for damaged transit. + expected_output: Explain the final-sale exception for damaged transit. + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` The `experiments/` directory is optional and user-owned. AgentV does not infer @@ -156,7 +160,10 @@ tests: - id: addition vars: question: What is 15 + 27? - expected_output: "42" + expected_output: "42" + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` ### Top-level Fields @@ -214,6 +221,9 @@ prompts: default_test: vars: audience: engineers + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" targets: - id: local-mini @@ -232,7 +242,7 @@ tests: - id: release-notes vars: topic: the July release notes - expected_output: concise release-note summary + expected_output: concise release-note summary assert: - Identifies the most important change - Avoids unsupported details @@ -240,7 +250,7 @@ tests: vars: audience: executives topic: the next roadmap phase - expected_output: concise roadmap summary + expected_output: concise roadmap summary assert: - Identifies the main product direction - Uses the requested audience framing @@ -662,16 +672,18 @@ tests: - id: capital vars: question: What is the capital of France? - expected_answer: Paris + expected_output: Paris criteria: "Answers {{ vars.question }} correctly" - expected_output: "{{ vars.expected_answer }}" + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` ### Behavior - `vars` is defined per test as an object, with optional defaults from `default_test.vars` - `{{ vars.name }}` and dotted paths like `{{ vars.user.name }}` are supported -- Substitution applies to `prompts`, `criteria`, `expected_output`, assertion values/metrics, and conversation turn `input` / `expected_output` / assertions +- Substitution applies to `prompts`, `criteria`, `vars.expected_output`, assertion values/metrics, and conversation turn `input` / `expected_output` / assertions - When the whole string is a single placeholder, the original JSON value is preserved - Missing variables render as empty strings following Nunjucks semantics - `vars` interpolation is separate from environment interpolation: `{{ vars.question }}` uses test data, `{{ env.PROJECT_NAME }}` uses environment variables diff --git a/apps/web/src/content/docs/docs/next/evaluation/examples.mdx b/apps/web/src/content/docs/docs/next/evaluation/examples.mdx index 109d2d100..d314e9417 100644 --- a/apps/web/src/content/docs/docs/next/evaluation/examples.mdx +++ b/apps/web/src/content/docs/docs/next/evaluation/examples.mdx @@ -16,13 +16,17 @@ A minimal eval with a single question and expected answer: description: Basic arithmetic evaluation target: default +prompts: + - "{{ question }}" + tests: - id: simple-addition - criteria: Correctly calculates 2+2 - - input: What is 2 + 2? - - expected_output: "4" + vars: + question: What is 2 + 2? + expected_output: "4" + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` ## Code Review with File References @@ -33,39 +37,39 @@ Use multipart content to attach files alongside text prompts: description: Code review with guidelines target: azure-base +prompts: + - - role: system + content: You are an expert code reviewer. + - role: user + content: + - type: text + value: |- + Review this function for security issues: + + ```python + def get_user(user_id): + query = f"SELECT * FROM users WHERE id = {user_id}" + return db.execute(query) + ``` + - type: file + value: /prompts/security-guidelines.md + tests: - id: code-review-basic - criteria: Assistant provides helpful code analysis with security considerations - - input: - - role: system - content: You are an expert code reviewer. - - role: user - content: - - type: text - value: |- - Review this function for security issues: - - ```python - def get_user(user_id): - query = f"SELECT * FROM users WHERE id = {user_id}" - return db.execute(query) - ``` - - type: file - value: /prompts/security-guidelines.md - - expected_output: - - role: assistant - content: |- - This code has a critical SQL injection vulnerability. The user_id is directly - interpolated into the query string without sanitization. - - Recommended fix: - ```python - def get_user(user_id): - query = "SELECT * FROM users WHERE id = ?" - return db.execute(query, (user_id,)) - ``` + vars: + expected_output: |- + This code has a critical SQL injection vulnerability. The user_id is directly + interpolated into the query string without sanitization. + + Recommended fix: + ```python + def get_user(user_id): + query = "SELECT * FROM users WHERE id = ?" + return db.execute(query, (user_id,)) + ``` + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ```` ## Multi-Grader @@ -76,9 +80,21 @@ Combine a script grader and an LLM grader on the same test: description: JSON generation with validation target: default +prompts: + - "{{ input }}" + tests: - id: json-generation-with-validation - criteria: Generates valid JSON with required fields + vars: + input: |- + Generate a JSON object for a user with name "Alice", + email "alice@example.com", and role "admin". + expected_output: |- + { + "name": "Alice", + "email": "alice@example.com", + "role": "admin" + } assert: - name: json_format_validator @@ -87,18 +103,8 @@ tests: cwd: ./graders - name: content_evaluator type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" prompt: ./graders/semantic_correctness.md - - input: |- - Generate a JSON object for a user with name "Alice", - email "alice@example.com", and role "admin". - - expected_output: |- - { - "name": "Alice", - "email": "alice@example.com", - "role": "admin" - } ``` ## File Output Transform @@ -255,25 +261,26 @@ tests: - role: user content: |- For `[1, 2, 3, 4]` I expect `[1, 2, 3, 4]`, but I get `[1, 2, 3]`. - - expected_output: - - role: assistant - content: |- - You have an off-by-one error in your loop bounds. - You're iterating with `range(len(items) - 1)`, which stops before the last index. - To include all items, you can either: - - Use `range(len(items))`, or - - Iterate directly over the list: `for item in items:` - - Here's a corrected version: - - ```python - def get_items(items): - result = [] - for item in items: - result.append(item) - return result - ``` + vars: + expected_output: |- + You have an off-by-one error in your loop bounds. + You're iterating with `range(len(items) - 1)`, which stops before the last index. + To include all items, you can either: + - Use `range(len(items))`, or + - Iterate directly over the list: `for item in items:` + + Here's a corrected version: + + ```python + def get_items(items): + result = [] + for item in items: + result.append(item) + return result + ``` + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ```` ## Batch CLI @@ -288,11 +295,11 @@ tests: - id: aml-001 criteria: |- Batch runner returns JSON with decision=CLEAR. - - expected_output: - - role: assistant - content: - decision: CLEAR + vars: + expected_output: + - role: assistant + content: + decision: CLEAR input: - role: system @@ -321,11 +328,11 @@ tests: - id: aml-002 criteria: |- Batch runner returns JSON with decision=REVIEW. - - expected_output: - - role: assistant - content: - decision: REVIEW + vars: + expected_output: + - role: assistant + content: + decision: REVIEW input: - role: system diff --git a/apps/web/src/content/docs/docs/next/evaluation/running-evals.mdx b/apps/web/src/content/docs/docs/next/evaluation/running-evals.mdx index 684e0ff80..e1791d352 100644 --- a/apps/web/src/content/docs/docs/next/evaluation/running-evals.mdx +++ b/apps/web/src/content/docs/docs/next/evaluation/running-evals.mdx @@ -29,10 +29,11 @@ Each `scores[]` entry includes per-grader timing: { "name": "format_structure", "type": "llm-rubric", + "pass": true, "score": 0.9, - "verdict": "pass", - "assertions": [ - { "text": "clear structure", "passed": true } + "reason": "The answer is clearly structured.", + "component_results": [ + { "pass": true, "score": 0.9, "reason": "Clear structure is present." } ], "duration_ms": 9103, "started_at": "2026-03-09T00:05:10.123Z", diff --git a/apps/web/src/content/docs/docs/next/getting-started/quickstart.mdx b/apps/web/src/content/docs/docs/next/getting-started/quickstart.mdx index f0abe7591..a1f8e1f61 100644 --- a/apps/web/src/content/docs/docs/next/getting-started/quickstart.mdx +++ b/apps/web/src/content/docs/docs/next/getting-started/quickstart.mdx @@ -46,15 +46,17 @@ Create `./evals/example.yaml`: description: Math problem solving evaluation target: default +prompts: + - "{{ question }}" + tests: - id: addition - criteria: Correctly calculates 15 + 27 = 42 - - input: What is 15 + 27? - - expected_output: "42" - + vars: + question: What is 15 + 27? + expected_output: "42" assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - name: math_check type: script command: [./validators/check_math.py] diff --git a/apps/web/src/content/docs/docs/next/graders/custom-assertions.mdx b/apps/web/src/content/docs/docs/next/graders/custom-assertions.mdx index d33c0317d..84211d68f 100644 --- a/apps/web/src/content/docs/docs/next/graders/custom-assertions.mdx +++ b/apps/web/src/content/docs/docs/next/graders/custom-assertions.mdx @@ -64,7 +64,7 @@ assert: ## Pass/Fail Pattern -The simplest pattern returns `pass` (boolean) and an optional `assertions` array: +The simplest pattern returns `pass` (boolean), `reason`, and optional `checks`: ```typescript // .agentv/assertions/min-words.ts @@ -75,7 +75,8 @@ export default defineAssertion(({ output }) => { const pass = wordCount >= 3; return { pass, - assertions: [{ text: `Output has ${wordCount} words`, passed: pass }], + reason: pass ? 'Output has enough words' : 'Output is too short', + checks: [{ text: `Output has ${wordCount} words`, pass, reason: `${wordCount} words observed` }], }; }); ``` @@ -95,9 +96,10 @@ export default defineAssertion(({ output, traceSummary }) => { const isEfficient = (traceSummary?.eventCount ?? 0) <= 5 ? 0.5 : 0; return { score: hasContent + isEfficient, - assertions: [ - { text: 'Has content', passed: hasContent > 0 }, - { text: 'Efficient', passed: isEfficient > 0 }, + reason: 'Checks content and trace length', + checks: [ + { text: 'Has content', pass: hasContent > 0, reason: hasContent > 0 ? 'Output is non-empty' : 'Output is empty' }, + { text: 'Efficient', pass: isEfficient > 0, reason: isEfficient > 0 ? 'Trace is within limit' : 'Trace is too long' }, ], }; }); @@ -113,8 +115,9 @@ The handler must return an `AssertionScore` object: |-------|------|-------------| | `pass` | `boolean` | Explicit pass/fail. If omitted, derived from `score` (>= 0.5 = pass). | | `score` | `number` | Numeric score between 0 and 1. Defaults to 1 if `pass=true`, 0 if `pass=false`. | -| `assertions` | `Array<{ text: string, passed: boolean, evidence?: string }>` | Per-aspect results. Each entry describes one check with its verdict and optional evidence. | -| `details` | `Record` | Optional structured data for domain-specific metrics. | +| `reason` | `string` | Explanation for the aggregate decision. | +| `checks` | `Array<{ text: string, pass: boolean, score?: number, reason: string }>` | Optional per-aspect SDK convenience results. Public artifacts normalize them to `component_results`. | +| `metadata` | `Record` | Optional structured data for domain-specific metrics. | ## Context Available to Assertions @@ -145,9 +148,11 @@ Expected output: ```json { + "pass": true, "score": 1, - "assertions": [ - { "text": "Output has 6 words", "passed": true } + "reason": "Output has enough words", + "checks": [ + { "text": "Output has 6 words", "pass": true, "reason": "6 words observed" } ] } ``` @@ -208,12 +213,14 @@ export default defineAssertion(({ output }) => { return { pass, score: pass ? 1.0 : Math.min(wordCount / minWords, 0.9), - assertions: [ + reason: pass ? 'Output meets the word-count requirement' : 'Output is below the word-count requirement', + checks: [ { text: pass ? `Output has ${wordCount} words (>= ${minWords} required)` : `Output has only ${wordCount} words (need >= ${minWords})`, - passed: pass, + pass, + reason: `${wordCount} words observed`, }, ], }; @@ -229,10 +236,14 @@ description: Demonstrates custom assertions with convention discovery target: default +prompts: + - "{{ input }}" + tests: - id: greeting-response - input: "Say hello and introduce yourself" - expected_output: "Hello! I'm an AI assistant here to help you." + vars: + input: "Say hello and introduce yourself" + expected_output: "Hello! I'm an AI assistant here to help you." assert: - Agent gives a multi-word greeting - type: contains @@ -240,8 +251,9 @@ tests: - type: min-words - id: short-answer - input: "What is 2+2?" - expected_output: "The answer is 4." + vars: + input: "What is 2+2?" + expected_output: "The answer is 4." assert: - Agent gives a short but valid response - type: contains diff --git a/apps/web/src/content/docs/docs/next/graders/llm-graders.mdx b/apps/web/src/content/docs/docs/next/graders/llm-graders.mdx index b5d08d13a..286d476fc 100644 --- a/apps/web/src/content/docs/docs/next/graders/llm-graders.mdx +++ b/apps/web/src/content/docs/docs/next/graders/llm-graders.mdx @@ -15,17 +15,22 @@ handled by the built-in `llm-rubric` rubric grader. Use `type: llm-rubric` when need a custom prompt, target, or grader-specific transform: ```yaml +prompts: + - "{{ task }}" + tests: - id: simple-eval - input: "Debug this function..." + vars: + task: "Debug this function..." assert: - Correctly explains the bug and proposes a fix ``` -`expected_output` is passive gold/reference data. It is available to graders but -does not create an LLM grading call by itself. Depending on the grader, it can -be used as an exact target, a semantic reference answer, a structured object, or -supporting context. See [How reference fields and assertions interact](/docs/evaluation/eval-cases/#how-reference-fields-and-assertions-interact). +Reference answers belong in `vars.expected_output`. That var is available to +graders, but it does not create an LLM grading call by itself. Consume it from +an explicit assertion, usually `type: llm-rubric` with a `value` such as +`"Matches the reference answer: {{ expected_output }}"`. See [How reference +fields and assertions interact](/docs/evaluation/eval-cases/#how-reference-fields-and-assertions-interact). ## Configuration @@ -87,6 +92,12 @@ Use `prompt: ./path/to/prompt.md` for the common relative-path case. Use `prompt Structured task input belongs in `input`. If `input` is a message whose `content` is a JSON object, `{{input}}` renders that object as formatted JSON for the grader prompt; no separate grader-only input field is required. Use `metadata` for provenance or suite-level source fields, and `rubrics_json` for rubric arrays. +For Promptfoo-compatible custom rubric prompts, the standard judge prompt uses +`{{ output }}` for the candidate answer and `{{ rubric }}` for the rendered +assertion value. The judge response should be JSON-compatible with +`{ "reason": string, "pass": boolean, "score": number }`; AgentV normalizes +that into public `grading.json` fields `reason`, `pass`, and `score`. + Suite-level `metadata` is inherited by every test. When rubric items vary per test, keep the grader on each test and reuse the prompt file: ```yaml @@ -95,11 +106,15 @@ metadata: source_commit: 8d9419829f443f84b804d033bb2c3b1fbd788629 source_file: src/evals/dataset/finance_agent.csv +prompts: + - "{{ input }}" + tests: - id: apple-research - input: - company: Apple - ticker: AAPL + vars: + input: + company: Apple + ticker: AAPL metadata: row: 1 assert: @@ -254,17 +269,18 @@ Template variables are derived internally through three layers: What users write in YAML or JSONL: -- `input` may be a shorthand string or a full message array. `input: "What is 2+2?"` expands to `[{ role: "user", content: "What is 2+2?" }]`. -- `expected_output` may be a shorthand string or a full message array. `expected_output: "4"` expands to `[{ role: "assistant", content: "4" }]`. +- Prompt templates render from `tests[].vars` and `default_test.vars`. +- `vars.expected_output` is a conventional reference-answer var. It is available to assertion values and grader templates, but it is not an authored sibling field on the test. ### 2. Resolved Layer -After parsing, canonical message arrays replace the shorthand fields: +After prompt rendering, canonical message arrays represent the resolved prompt +input and reference data: - `input: TestMessage[]` -- canonical resolved input - `expected_output: TestMessage[]` -- canonical resolved expected output -At this layer, `input` and `expected_output` no longer exist as separate fields. +At this layer, authored prompt vars and resolved grader context are separate. ### 3. Template Variable Layer @@ -284,8 +300,15 @@ Derived strings injected into grader prompts: ```yaml # User writes: -input: "What is 2+2?" -expected_output: "The answer is 4" +prompts: + - "{{ question }}" +tests: + - vars: + question: "What is 2+2?" + expected_output: "The answer is 4" + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" ``` ``` diff --git a/apps/web/src/content/docs/docs/next/graders/script-graders.mdx b/apps/web/src/content/docs/docs/next/graders/script-graders.mdx index c1c71dbed..270ef8e4a 100644 --- a/apps/web/src/content/docs/docs/next/graders/script-graders.mdx +++ b/apps/web/src/content/docs/docs/next/graders/script-graders.mdx @@ -67,7 +67,11 @@ Emit a JSON object for numeric scores or multi-aspect results: | `pass` | `boolean` | Aggregate pass/fail decision | | `score` | `number` | 0.0 to 1.0 | | `reason` | `string` | Explanation for the aggregate decision | -| `checks` | `Array<{ text, pass, score?, reason, evidence? }>` | Optional per-aspect results with verdict, optional score, reason, and evidence | +| `checks` | `Array<{ text, pass, score?, reason }>` | Optional per-aspect authoring/API convenience results | + +`checks` is a script-grader convenience shape. Public `grading.json` artifacts +normalize those entries into recursive `component_results` with `pass`, `score`, +and `reason`. ### Plain-text output (exit-code convention) @@ -381,7 +385,11 @@ Beyond the basic fields (`input`, `output`, `expected_output`), script graders r | `error_count` | `number` | Failed tool calls | | `llm_call_count` | `number` | Number of LLM calls (assistant messages) | -Use `expected_output` for reference answers and `output` for the actual final answer from live runs. Use `messages` or `trace` when you need tool calls, intermediate messages, or replay/provenance data. +Use `expected_output` for reference answers and `output` for the actual final +answer from live runs. In authored YAML, put reference answers in +`vars.expected_output` and consume them from explicit assertions. Use +`messages` or `trace` when you need tool calls, intermediate messages, or +replay/provenance data. ## Workspace Access diff --git a/apps/web/src/content/docs/docs/next/graders/structured-data.mdx b/apps/web/src/content/docs/docs/next/graders/structured-data.mdx index cfe5880b8..7ce0f4523 100644 --- a/apps/web/src/content/docs/docs/next/graders/structured-data.mdx +++ b/apps/web/src/content/docs/docs/next/graders/structured-data.mdx @@ -15,19 +15,22 @@ Built-in graders for grading structured outputs and gating on execution metrics: ## Ground Truth -Put the expected structured output in the test case `expected_output` (as an object or message array). Graders read expected values from there. +Put the expected structured output in `tests[].vars.expected_output`. Graders +read expected values from that explicit reference var. ```yaml tests: - id: invoice-001 - expected_output: - invoice_number: "INV-2025-001234" - net_total: 1889 + vars: + expected_output: + invoice_number: "INV-2025-001234" + net_total: 1889 ``` ## Field Accuracy -Use `field-accuracy` to compare fields in the candidate JSON against the ground-truth object in `expected_output`. +Use `field-accuracy` to compare fields in the candidate JSON against the +ground-truth object in `vars.expected_output`. ```yaml assert: diff --git a/apps/web/src/content/docs/docs/next/guides/agent-eval-layers.mdx b/apps/web/src/content/docs/docs/next/guides/agent-eval-layers.mdx index 7efffeb07..f6d48267a 100644 --- a/apps/web/src/content/docs/docs/next/guides/agent-eval-layers.mdx +++ b/apps/web/src/content/docs/docs/next/guides/agent-eval-layers.mdx @@ -138,19 +138,21 @@ sidebar: target: default +prompts: + - - role: user + content: "What is the capital of France? Verify using a search tool." + tests: - id: full-stack-eval criteria: >- Agent researches the topic, uses appropriate tools in order, produces a correct answer, and operates safely. - - input: - - role: user - content: "What is the capital of France? Verify using a search tool." - - expected_output: "The capital of France is Paris." + vars: + expected_output: "The capital of France is Paris." assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" # Layer 1: Reasoning - Agent reasoned about which tool to use before acting diff --git a/apps/web/src/content/docs/docs/next/guides/benchmark-provenance.mdx b/apps/web/src/content/docs/docs/next/guides/benchmark-provenance.mdx index e89679257..246605e8b 100644 --- a/apps/web/src/content/docs/docs/next/guides/benchmark-provenance.mdx +++ b/apps/web/src/content/docs/docs/next/guides/benchmark-provenance.mdx @@ -237,11 +237,9 @@ tests: question: | Research the company filing and answer: What drove the year-over-year change in gross margin? - expected_output: - - role: assistant - content: | - Gross margin improved because product mix shifted toward higher-margin - software revenue while fulfillment costs declined. + expected_output: | + Gross margin improved because product mix shifted toward higher-margin + software revenue while fulfillment costs declined. metadata: source_repo: https://github.com/example/finance-research-dataset.git source_commit: 05b8b2e9f071e8d0a6f1c2b3d4e5f60718293abc diff --git a/apps/web/src/content/docs/docs/next/guides/eval-authoring.mdx b/apps/web/src/content/docs/docs/next/guides/eval-authoring.mdx index d6193945e..05543e8a9 100644 --- a/apps/web/src/content/docs/docs/next/guides/eval-authoring.mdx +++ b/apps/web/src/content/docs/docs/next/guides/eval-authoring.mdx @@ -179,10 +179,12 @@ tests: input: | The eval harness has prepared the historical AgentV checkout. Use that checkout to decide which durable guidance should change. - expected_output: | - The durable repo change is to update .agents/verification.md with the - reusable verification workflow lessons. + expected_output: | + The durable repo change is to update .agents/verification.md with the + reusable verification workflow lessons. assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - The answer uses the pinned ./agentv checkout to verify the existing guidance. - The answer preserves the historical commit SHA as context. ``` diff --git a/apps/web/src/content/docs/docs/next/integrations/autoevals-integration.mdx b/apps/web/src/content/docs/docs/next/integrations/autoevals-integration.mdx index 65fb10197..bdb72eb0e 100644 --- a/apps/web/src/content/docs/docs/next/integrations/autoevals-integration.mdx +++ b/apps/web/src/content/docs/docs/next/integrations/autoevals-integration.mdx @@ -56,12 +56,14 @@ Use the `Factuality` scorer as an AgentV `script` grader to verify answer correc **EVAL.yaml:** ```yaml +prompts: + - "{{ question }}" + tests: - id: capital-city - input: - - role: user - content: "What is the capital of France?" - expected_output: "Paris is the capital of France." + vars: + question: "What is the capital of France?" + expected_output: "Paris is the capital of France." assert: - name: factuality type: script @@ -95,9 +97,10 @@ const rationale = result.metadata?.rationale ?? "No rationale provided"; console.log( JSON.stringify({ + pass: score >= 0.5, score, - assertions: [{ text: rationale, passed: score >= 0.5 }], - reasoning: rationale, + reason: rationale, + checks: [{ text: "Factuality", pass: score >= 0.5, score, reason: rationale }], }) ); ``` @@ -111,12 +114,14 @@ Use the `Faithfulness` scorer to detect hallucination in a RAG pipeline. **EVAL.yaml:** ```yaml +prompts: + - "{{ task }}" + tests: - id: rag-faithfulness - input: - - role: user - content: "Summarize the key findings from the research paper." - expected_output: "The paper found that transformer models outperform RNNs on long-range tasks." + vars: + task: "Summarize the key findings from the research paper." + expected_output: "The paper found that transformer models outperform RNNs on long-range tasks." assert: - name: faithfulness type: script @@ -154,9 +159,10 @@ score = result.score or 0 rationale = (result.metadata or {}).get("rationale", "No rationale provided") print(json.dumps({ + "pass": score >= 0.5, "score": score, - "assertions": [{"text": rationale, "passed": score >= 0.5}], - "reasoning": rationale, + "reason": rationale, + "checks": [{"text": "Faithfulness", "pass": score >= 0.5, "score": score, "reason": rationale}], })) ``` @@ -208,12 +214,14 @@ Combine multiple autoevals scorers in a single script grader for comprehensive R **EVAL.yaml:** ```yaml +prompts: + - "{{ question }}" + tests: - id: rag-pipeline - input: - - role: user - content: "What are the benefits of exercise?" - expected_output: "Exercise improves cardiovascular health, mental well-being, and longevity." + vars: + question: "What are the benefits of exercise?" + expected_output: "Exercise improves cardiovascular health, mental well-being, and longevity." assert: - name: rag-quality type: script @@ -264,14 +272,16 @@ const results = [ { name: "Context Relevancy", ...contextRelevancy }, ]; -const assertions: Array<{ text: string; passed: boolean }> = []; +const checks: Array<{ text: string; pass: boolean; score: number; reason: string }> = []; for (const r of results) { const score = r.score ?? 0; const rationale = r.metadata?.rationale ?? "No rationale"; - assertions.push({ + checks.push({ text: `${r.name} (${score.toFixed(2)}): ${rationale}`, - passed: score >= 0.5, + pass: score >= 0.5, + score, + reason: rationale, }); } @@ -280,9 +290,10 @@ const avgScore = console.log( JSON.stringify({ + pass: avgScore >= 0.5, score: avgScore, - assertions, - reasoning: `Average score across ${results.length} RAG metrics: ${avgScore.toFixed(2)}`, + reason: `Average score across ${results.length} RAG metrics: ${avgScore.toFixed(2)}`, + checks, }) ); ``` diff --git a/apps/web/src/content/docs/docs/next/reference/promptfoo-parity.mdx b/apps/web/src/content/docs/docs/next/reference/promptfoo-parity.mdx index c47dbf0dc..fa2f07d90 100644 --- a/apps/web/src/content/docs/docs/next/reference/promptfoo-parity.mdx +++ b/apps/web/src/content/docs/docs/next/reference/promptfoo-parity.mdx @@ -34,15 +34,17 @@ implements equivalent semantics directly. | --- | --- | --- | --- | --- | | TypeScript config file | `promptfooconfig.ts` default export typed as Promptfoo `UnifiedConfig`. | Explicit `*.eval.ts` or `*.eval.mts` default export typed as `EvalConfig` from `@agentv/sdk`. | Keep AgentV divergence | AgentV does not accept `promptfooconfig.ts` by default because the config semantics intentionally diverge: top-level `providers` is not an AgentV runtime alias, and AgentV keeps `target`/`targets`, `environment`, `evaluate_options`, and `assert` as its supported contract. Directory discovery includes explicit `*.eval.ts` and `*.eval.mts` config files, but not arbitrary helper `.ts` files. | | Prompt matrix | Top-level `prompts` rendered with each test's `vars`. | Top-level `prompts` rendered with `tests[].vars` and `default_test.vars`. | Align with Promptfoo | This is the canonical Promptfoo-compatible input shape in AgentV. Prompt entries can be inline strings, chat arrays, files, or generated prompt functions. | -| Test rows | `tests` can be inline rows or a case-file reference; rows carry `vars`, `assert`, metadata, prompt/provider filters, and expected data. | `tests` can be inline rows or a raw-case path; rows carry `vars`, `assert`, `expected_output`, metadata, optional environment overrides, and run overrides. | Align with Promptfoo | AgentV uses field-local file refs such as `tests: file://...`, `prompts: file://...`, and `default_test: file://...`; coding-agent testbeds use `environment: file://...`. There is no separate imports table. | +| Test rows | `tests` can be inline rows or a case-file reference; rows carry `vars`, `assert`, metadata, prompt/provider filters, and expected data through vars or assertion values. | `tests` can be inline rows or a raw-case path; rows carry `vars`, `assert`, metadata, optional environment overrides, and run overrides. | Align with Promptfoo | AgentV uses field-local file refs such as `tests: file://...`, `prompts: file://...`, and `default_test: file://...`; coding-agent testbeds use `environment: file://...`. There is no separate imports table. | | Variables | `tests[].vars` plus `defaultTest.vars`; prompt templates can reference top-level var names. | `tests[].vars` plus `default_test.vars`; templates can use `{{ name }}` or `{{ vars.name }}`. | Align with Promptfoo | Per-test vars override default vars by key. | | Default test | `defaultTest`, inline object or `file://` reference. | `default_test`, inline object or `file://` / `ref://` reference. | Align with Promptfoo | AgentV uses `snake_case` for YAML. Shared prompt matrix defaults belong in `default_test.vars`. | +| Reference answers | Promptfoo stores reference data in assertion values or arbitrary vars such as `vars.expected_output`; `llm-rubric` consumes it when `value` includes `{{ expected_output }}`. | Store reference answers in `tests[].vars.expected_output` or `default_test.vars.expected_output`, then consume them explicitly with `assert` or `default_test.assert`, usually `type: llm-rubric` and `value: "Matches the reference answer: {{ expected_output }}"`. | Align with Promptfoo | A var named `expected_output` is just data. AgentV does not create hidden grading behavior for that name. Authored direct `expected_output` at eval, `default_test`, or test level is rejected in current YAML. | | Output transform | `defaultTest.options.transform`, `tests[].options.transform`, and assertion-level `transform`. | `default_test.options.transform`, `tests[].options.transform`, and assertion-level `transform`. | Align with Promptfoo | Use `transform` to shape provider output before grading, including file-output conversions such as `.xlsx` to text. `tests[].options.transform` overrides the inherited default transform; assertion-level `transform` is scoped to one grader. | | Deprecated `postprocess` | Older Promptfoo configs may still mention `postprocess`. | Rejected. | Removed/rejected surface | Use `transform`. AgentV intentionally does not adopt Promptfoo's deprecated `postprocess` spelling. | | Evaluate options | `evaluateOptions` for runtime controls. | `evaluate_options` for runtime controls. | Align with Promptfoo | AgentV uses `evaluate_options.repeat`, `evaluate_options.budget_usd`, and `evaluate_options.max_concurrency`. | | Authored concurrency | Common Promptfoo usage includes runtime options such as `maxConcurrency`. | `evaluate_options.max_concurrency`. | Keep AgentV divergence | Do not author `execution.max_concurrency` or top-level `workers` in eval YAML. CLI `--workers` remains an operator override. | | Target selection | Promptfoo normal evals use `providers`; `targets` can alias providers in unified config. | Use top-level `target` for one system under test or top-level `targets` for a target matrix. | Keep AgentV divergence | AgentV reserves `provider` for the backend/adapter kind inside a target object. Top-level `providers` is rejected to avoid overloading that term. | | Target object identity | Provider options often use `id` for backend/provider spec and optional `label` for display or matching. | Target objects use stable `id` for target identity, `provider` for backend kind, optional `runtime`, and `config` for provider settings. | Keep AgentV divergence | AgentV does not copy Promptfoo's `label`/`id` baggage because `provider` already names the backend boundary. | +| Provider-prompt mapping | `providerPromptMap` maps provider identities to prompt subsets. | Rejected. Use explicit AgentV composition: separate eval suites/files for target-specific prompt subsets, top-level `prompts` plus `tests`/`default_test.vars`, `target`/`targets` or CLI `--target`, and tags/run metadata for grouping. | Removed/rejected surface | AgentV avoids inheriting Promptfoo provider identity baggage. Do not author `providerPromptMap` or `provider_prompt_map`. | | Direct authored input | Promptfoo prompt authoring normally goes through `prompts` plus vars. | Top-level `input` and inline `tests[].input` are removed from normal authored eval YAML. External raw-case imports may still carry internal input rows for compatibility. | Removed AgentV extension | Author prompt text, chat/system/user messages, and file-backed prompt content as `prompts`; put row data in `tests[].vars` and shared defaults in `default_test.vars`. | | Authored preprocessors | Not Promptfoo's canonical output-shaping surface. | Rejected in current authored YAML. | Removed/rejected surface | Use `transform` at `default_test.options`, `tests[].options`, or the assertion that needs the shaped output. Historical versioned docs may still show old preprocessor examples. | | Suite assertions | `assert` entries can be strings or typed assertion objects. | `assert` entries can be strings, typed assertion objects, script graders, or AgentV extension graders. | Align with Promptfoo | Plain strings become semantic rubric checks. Use `assert`, not `assertions`, in current authored eval YAML. | @@ -55,6 +57,7 @@ implements equivalent semantics directly. | Other Promptfoo trace assertions | Promptfoo also includes `tool-call-f1`, `trace-span-count`, `trace-span-duration`, and `trace-error-spans`. | Not accepted yet. | Defer/future-scope | Use `script` assertions or `execution-metrics` when current AgentV primitives cover the requirement. | | Coding-agent testbeds | Promptfoo normal evals do not define a typed coding-agent testbed primitive. | `environment`, usually inline or `environment: file://...`, plus distinct top-level `env` and lifecycle `extensions`. | Keep AgentV extension | `environment` is an AgentV extension informed by Margin local-agent ergonomics and Harbor/Terminal-Bench Docker substrate evidence. Use it for host/Docker workdir, setup argv, fixtures, services, and repo materialization. Use top-level `env` for provider/eval variables and `extensions` for lifecycle hooks. | | Run artifacts and inspection | Promptfoo owns its own result viewer and output formats. | AgentV writes `.agentv/results//` bundles with `summary.json`, `.internal/index.jsonl`, sidecars, and local Dashboard support. | Keep AgentV extension | AgentV-owned bundles are the source of truth for compare, Dashboard, CI, and adapters. Phoenix is link-out correlation only through safe external trace metadata. | +| Grading result artifacts | Promptfoo `GradingResult` uses aggregate `pass`, `score`, `reason`, recursive `componentResults`, optional `assertion`, optional `namedScores`, and optional `metadata`. | AgentV persists the same concepts as `pass`, `score`, `reason`, recursive `component_results`, optional `assertion`, optional `named_scores`, and optional `metadata`. | Align with Promptfoo | AgentV keeps split filesystem run bundles and `snake_case` persisted fields. Promptfoo import/export adapters may translate `componentResults`/`namedScores` to and from AgentV `component_results`/`named_scores`. | | Compare command | Promptfoo has its own result comparison surfaces. | `agentv results compare `. | Keep AgentV extension | Compare consumes completed AgentV run indexes such as `.agentv/results//.internal/index.jsonl`. | | CLI runtime filters | Promptfoo exposes filters such as prompt/provider/test subset flags. | AgentV supports its current CLI filters and selection fields; full Promptfoo runtime-filter parity is future work. | Defer/future-scope | Prefer current AgentV CLI flags and authored selection fields until runtime-filter parity lands. | | Wire-format casing | Promptfoo config uses camelCase fields such as `defaultTest` and `evaluateOptions`. | AgentV YAML, JSONL, artifacts, and CLI JSON use `snake_case`; internal TypeScript uses `camelCase`. | Keep AgentV divergence | Translate only at process boundaries. New public wire fields should be `snake_case`. | @@ -74,6 +77,9 @@ prompts: default_test: vars: audience: engineers + assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" evaluate_options: max_concurrency: 2 @@ -82,7 +88,7 @@ tests: - id: release-notes vars: topic: the July release notes - expected_output: concise release-note summary + expected_output: concise release-note summary assert: - Identifies the most important change - type: assert-set @@ -121,8 +127,10 @@ tests: - id: refund-policy vars: task: Update the refund policy handler. - expected_output: The handler supports the damaged-item exception. + expected_output: The handler supports the damaged-item exception. assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - type: trajectory:tool-used value: name: shell diff --git a/apps/web/src/content/docs/docs/next/reference/result-artifacts.mdx b/apps/web/src/content/docs/docs/next/reference/result-artifacts.mdx index 41e8f4b06..321102f2e 100644 --- a/apps/web/src/content/docs/docs/next/reference/result-artifacts.mdx +++ b/apps/web/src/content/docs/docs/next/reference/result-artifacts.mdx @@ -99,7 +99,7 @@ reserved for rebuildable local state and are skipped by run discovery. | `.internal/index.jsonl` | Canonical per-run row index: one row per case/result aggregate, with identity fields, filter metadata, scores, status, and explicit run-relative paths to sidecars. | Filtering, compare/trend inputs, Dashboard detail routing, rerun/resume lookup, export adapters, and artifact discovery. | | `result.json` | Compact per-attempt manifest for one attempt directory, including AgentV `execution_status` and `verdict`. | Loading one attempt without scanning the whole run index. | | `environment.json` / `environment_path` | Redacted environment recipe provenance: authored inline/file reference, resolved recipe hash, host or Docker type, resolved workdir, setup argv command, setup log output/error, Docker context/image/digest fields when available, and repo provenance only when authored or emitted by setup. Index rows carry `environment_path` plus a compact `environment` summary; large setup logs stay in the sidecar. | Reproducing and reviewing the testbed without treating setup side effects as row metadata. Repository identity is opaque unless the environment recipe or setup output states it explicitly. | -| `grading.json` | Grader outputs, `assertion_results`, rubric evidence, execution-metric grader facts, and scoring provenance. | Explaining why a row passed or failed. | +| `grading.json` | Grader outputs and scoring provenance: aggregate `pass`, `score`, `reason`, recursive `component_results`, and optional `assertion`, `named_scores`, and `metadata`. | Explaining why a row passed or failed. | | `metrics.json` | Duration, token usage, cost, execution status, trajectory, and derived executor behavior such as tool calls, files touched, shell commands, errors, turns, and output sizes. | Dashboard behavior views, cost/latency reporting, metric-style graders, adapter projections, and lightweight analysis. | | `target-execution.json` | Provider-neutral target runtime envelope, including command, cwd, timeout, exit code or signal, error kind, timestamps, log truncation metadata, and artifact paths. | Distinguishing target task failures, target crashes, timeouts, cancellation, malformed provider output, and sandbox/runner failures from AgentV orchestrator failures. | | `stdout.txt` / `stderr.txt` | Captured target process logs when the runtime exposes them, with truncation metadata recorded in `target-execution.json`. | Debugging crashed, timed-out, cancelled, or malformed target runs without treating raw logs as the canonical row index. | @@ -117,57 +117,74 @@ artifact discovery on `.internal/index.jsonl`. ## Grading Contract -Each per-attempt `grading.json` uses `assertion_results` for the public -per-criterion rows. The internal grader API and eval YAML still use -`assertions`; the sidecar converts those rows at the artifact boundary. +Each per-attempt `grading.json` uses Promptfoo-compatible grading result +concepts persisted in AgentV `snake_case`. The aggregate result is always +`pass`, `score`, and `reason`. Nested grader, assertion, or script-check detail +lives in recursive `component_results`. Components may carry `assertion` +metadata, `named_scores`, and `metadata` when the grader produced those details. + +Script grader SDKs may expose `checks` as an authoring convenience. Those checks +are normalized into public artifact `component_results`; `checks`, +`assertion_results`, `passed`, `graders`, and `evidence` are not public +`grading.json` fields. ```json { + "pass": false, "score": 0.5, - "verdict": "fail", - "assertion_results": [ + "reason": "1/2 grading components passed.", + "component_results": [ { - "text": "Answer cites the changed file", - "passed": true, - "evidence": "The answer cites src/refunds.ts.", + "pass": true, "score": 1, - "verdict": "pass" + "reason": "The answer cites src/refunds.ts.", + "assertion": { + "type": "llm-rubric", + "metric": "cites_changed_file", + "value": "Answer cites the changed file" + } }, { - "text": "Tests were updated", - "passed": false, - "evidence": "No test file path or diff was provided.", + "pass": false, "score": 0, - "verdict": "fail" - } - ], - "summary": { - "passed": 1, - "failed": 1, - "total": 2, - "pass_rate": 0.5 - }, - "graders": [ - { - "name": "implementation_review", - "type": "llm-rubric", - "score": 0.5, - "verdict": "fail", - "assertion_results": [] + "reason": "No test file path or diff was provided.", + "assertion": { + "type": "script", + "metric": "tests_updated" + }, + "metadata": { + "script": "graders/check-tests.ts" + } } ] } ``` -`score` values are normalized to the `0..1` range. `verdict` is `pass`, -`fail`, or `skip` at the artifact level, and `pass` or `fail` on individual -assertion rows. Evidence stays in `grading.json` so the sidecar remains useful -without loading traces. +`score` values are normalized to the `0..1` range. `pass` is the boolean +quality decision for the aggregate or component. `reason` explains that +decision. `named_scores` is an optional object for grader-specific score +breakdowns: + +```json +{ + "pass": true, + "score": 0.91, + "reason": "The answer is grounded and complete.", + "named_scores": { + "groundedness": 0.94, + "completeness": 0.88 + }, + "metadata": { + "grader_model": "gpt-5.4-mini" + } +} +``` -Aggregate grading artifacts use the same top-level `score` and `verdict` -fields. Their `score` is the mean normalized score across non-execution-error -attempts or cases, while `verdict` reflects the already derived execution -status for those quality results instead of recomputing a default threshold. +Aggregate grading artifacts use the same top-level `pass`, `score`, and +`reason` fields. Their `score` is the mean normalized score across +non-execution-error attempts or cases, while `pass` reflects the already +derived quality decision for those results instead of recomputing a default +threshold. ## Row Contract diff --git a/apps/web/src/content/docs/docs/next/tools/results.mdx b/apps/web/src/content/docs/docs/next/tools/results.mdx index 28fef8927..c1d6d5bbd 100644 --- a/apps/web/src/content/docs/docs/next/tools/results.mdx +++ b/apps/web/src/content/docs/docs/next/tools/results.mdx @@ -85,7 +85,7 @@ git push Use `--out docs/.html` when a repository should publish multiple runs. Link those files from the result repository README so readers can browse a dashboard-like report from GitHub Pages instead of running `agentv dashboard` or opening raw JSONL. -AgentV results report showing an expanded failing test case with unified assertions, deterministic type badges, pass/fail indicators, evidence text, and collapsible input/output +AgentV results report showing an expanded failing test case with unified assertions, deterministic type badges, pass/fail indicators, grader reasons, and collapsible input/output | Option | Description | |--------|-------------| @@ -190,7 +190,7 @@ Agent Skills eval artifacts map into AgentV like this: | Per-case answer | Generated target output artifact | `sample-N/outputs/answer.md` | | Per-attempt sidecars | Normalized transcript, metrics, and raw provider evidence | `sample-N/transcript.json`, `sample-N/transcript-raw.jsonl`, `sample-N/metrics.json` | | Per-sample `metrics.json` | Duration, token totals, cost, execution, trajectory, and usage source labels | `sample-N/metrics.json` | -| Per-attempt `grading.json` | `assertion_results`, graders, rubric evidence, and workspace-change grading facts | `sample-N/grading.json`; summary fields can reference the same trace/result facts | +| Per-attempt `grading.json` | Aggregate `pass`, `score`, `reason`, recursive `component_results`, and optional assertion metadata, named scores, and grader metadata | `sample-N/grading.json`; summary fields can reference the same trace/result facts | | Iteration-level `summary.json` | Pass rate, time, tokens, tool calls, cost aggregates | Run-level `summary.json` | | Transcript/log outlier analysis | Normalized transcript, raw evidence, metrics, and optional external trace link | `transcript.json` for portable review; `transcript-raw.jsonl` for native evidence; `metrics.json` for behavior summaries; `external_trace` for link-out correlation | | Aggregate pass rate/time/tokens/delta | Run summaries and comparison tooling | `summary.json`, result comparisons, and projection bundles | diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 0ea4e1823..1eecbf8ff 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -72,6 +72,9 @@ export default defineAssertion(({ output }) => ({ ``` Checks support `pass: boolean` for simple checks and `score: number` (0-1) for granular scoring. +`checks` is an SDK/script convenience shape; public `grading.json` artifacts +normalize checks into recursive `component_results` with `pass`, `score`, and +`reason`. ### defineScriptGrader (full control) diff --git a/skills-data/agentv-eval-writer/SKILL.md b/skills-data/agentv-eval-writer/SKILL.md index ba62c6eb0..66aebb32e 100644 --- a/skills-data/agentv-eval-writer/SKILL.md +++ b/skills-data/agentv-eval-writer/SKILL.md @@ -40,7 +40,7 @@ Use `@agentv/sdk` for TypeScript helper imports. Do not use `@agentv/eval` for n - Put grading criteria in `assert`, not in test-level `criteria`. Plain assertion strings become an `llm-rubric` grader. - Prefer plain assertion strings for semantic checks when the default rubric grader can judge them. Use `type: llm-rubric` for structured criteria, custom prompts, custom grader targets, or assertion-level transforms, and `type: script` when grading must execute code. -- Write `expected_output` as a golden/reference answer the target could have produced. Do not write criteria, scoring instructions, or "the agent should..." rubric prose there. +- Put reference answers in `tests[].vars.expected_output` or `default_test.vars.expected_output`, and consume them with an explicit assertion such as `type: llm-rubric` with `value: "Matches the reference answer: {{ expected_output }}"`. Do not write criteria, scoring instructions, or "the agent should..." rubric prose as the reference answer. - For historical or repo-state evals, materialize the repo through a pinned `environment` setup recipe. Mentioning a SHA only in prompt prose is not enough because the agent needs an actual checkout to inspect. ## Evaluation Types @@ -61,7 +61,12 @@ agentv convert evals.json agentv eval evals.json ``` -The converter maps `prompt` → `input`, `expected_output` → `expected_output`, and Agent Skills `assertions` → AgentV `assert` (`llm-rubric` checks), and resolves `files[]` paths. The generated YAML includes TODO comments for AgentV features to add (environment setup, script graders, rubrics, required gates). +The converter maps Agent Skills prompt text into AgentV prompt/vars data, +promotes Agent Skills `expected_output` into explicit `llm-rubric` criteria or +`vars.expected_output` when it is true reference data, and maps Agent Skills +`assertions` to AgentV `assert` (`llm-rubric` checks). The generated YAML +includes TODO comments for AgentV features to add (environment setup, script +graders, rubrics, required gates). After converting, enhance the YAML with AgentV-specific capabilities shown below. @@ -103,12 +108,15 @@ prompts: tests: - id: multi-turn-context - expected_output: "Your name is Alice." + vars: + expected_output: "Your name is Alice." assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - Correctly recalls the user's name from earlier in the conversation ``` -**Guidelines:** preserve exact wording in `expected_output`; aim for 5–15 tests per transcript; pick exchanges that test different capabilities. +**Guidelines:** preserve exact wording in `vars.expected_output`; aim for 5–15 tests per transcript; pick exchanges that test different capabilities. ## Quick Start @@ -123,8 +131,10 @@ tests: - id: greeting vars: prompt: "Say hello" - expected_output: "Hello! How can I help you?" + expected_output: "Hello! How can I help you?" assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - Greeting is friendly and warm - Offers to help ``` @@ -140,7 +150,7 @@ tests: |-------|----------|-------------| | `id` | yes | Unique identifier | | `vars` | yes when the prompt needs row data | Prompt-template variables for this row | -| `expected_output` | no | Gold-standard reference answer (string shorthand or full message array) | +| `vars.expected_output` | no | Conventional reference-answer var consumed by explicit graders | | `assert` | yes | Graders: deterministic checks, `llm-rubric` checks, script graders, or plain string rubric criteria | | `execution` | no | Per-case grader/default overrides such as `skip_defaults`; target selection belongs in top-level `target` or CLI `--target` | | `environment` | no | Per-case coding-agent testbed config (overrides suite-level) | @@ -176,15 +186,19 @@ tests: - id: password-reset vars: question: How do I reset my password? - expected_output: Password reset guidance + expected_output: Password reset guidance assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - Gives correct password reset guidance - id: admin-access vars: audience: admins question: How do I revoke a user's access? - expected_output: Access revocation guidance + expected_output: Access revocation guidance assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - Gives safe access revocation guidance ``` @@ -204,7 +218,7 @@ then render those vars from the prompt template next to the input. **Shorthand forms:** - Prompt entries can be strings, message arrays, file references, or prompt objects. - Put chat/system/user messages in `prompts`, not in `tests[].input`. -- `expected_output` (string/object) expands to `[{role: "assistant", content: ...}]` +- `vars.expected_output` is a conventional reference-answer variable; explicit assertions decide how to grade it - Use these canonical field names on disk; keep the wire format `snake_case` **Message format:** `{role, content}` where role is `system`, `user`, `assistant`, or `tool` @@ -356,12 +370,14 @@ tests: verification guidance was added. Decide what durable repo change should be made and explain why. - expected_output: | - The durable repo change is to update .agents/verification.md with the - reusable verification workflow lessons. AGENTS.md already routes this - class of work to .agents/verification.md, so no extra AGENTS.md edit is - needed unless that routing is missing. + expected_output: | + The durable repo change is to update .agents/verification.md with the + reusable verification workflow lessons. AGENTS.md already routes this + class of work to .agents/verification.md, so no extra AGENTS.md edit is + needed unless that routing is missing. assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - The answer recommends updating .agents/verification.md rather than leaving the learning only in PR comments or private evidence. - The answer uses the pinned ./agentv checkout to verify the AGENTS.md routing. - The answer preserves the historical commit SHA as context. @@ -387,9 +403,9 @@ tests: value: "fix" ``` -`expected_output` is passive reference data. It is available to graders through -`{{expected_output}}` and the script stdin payload, but it does not create an -implicit LLM grading call by itself. +`vars.expected_output` is passive reference data. It is available to graders +through `{{ expected_output }}` and the script stdin payload, but it does not +create an implicit LLM grading call by itself. **Common mistake:** putting rubric prose in `expected_output` instead of an assertion: @@ -402,7 +418,7 @@ tests: - id: bad-example vars: prompt: "What is 2+2?" - expected_output: The assistant should explain why the answer is 4. # reference answer field, not a grader + expected_output: The assistant should explain why the answer is 4. # reference answer var, not a grader ``` Write this as: @@ -415,8 +431,10 @@ tests: - id: good-example vars: prompt: "What is 2+2?" - expected_output: "4" + expected_output: "4" assert: + - type: llm-rubric + value: "Matches the reference answer: {{ expected_output }}" - The answer is 4 and explains the arithmetic briefly ``` @@ -523,9 +541,10 @@ Configure via the `assert` array. Multiple graders produce a weighted average sc cwd: ./scripts # optional working directory target: {} # optional: enable LLM target proxy (max_calls: 50) ``` -Contract: stdin JSON -> stdout JSON `{score, assertions: [{text, passed, evidence?}], reasoning}` +Contract: stdin JSON -> stdout JSON `{pass, score, reason, checks?: [{text, pass, score?, reason}]}` Raw stdin uses snake_case and includes: `input`, `expected_output`, `output` (final answer string), `messages`, `trace`, `trace_summary`, `token_usage`, `cost_usd`, `duration_ms`, `start_time`, `end_time`, `file_changes`, `workspace_path`, `config` SDK handlers receive the same payload in camelCase: `expectedOutput`, `traceSummary`, `tokenUsage`, `costUsd`, `durationMs`, `startTime`, `endTime`, `fileChanges`, `workspacePath`. +`checks` is an SDK/script convenience shape; public `grading.json` artifacts normalize checks into recursive `component_results`. When an environment prepares a workspace directory, `workspace_path` is the absolute path to that directory (also available as `AGENTV_WORKSPACE_PATH` env var). Use this for functional grading (e.g., running `npm test` in the prepared workdir). For deterministic workspace checks that fit normal Vitest `expect(...)` tests, prefer a plain verifier file and the built-in adapter: ```yaml @@ -809,9 +828,11 @@ import { defineAssertion } from '@agentv/sdk'; export default defineAssertion(({ output, trace }) => { const finalOutput = output ?? ''; + const pass = finalOutput.length > 0 && (trace?.eventCount ?? 0) <= 10; return { - pass: finalOutput.length > 0 && (trace?.eventCount ?? 0) <= 10, - reasoning: 'Checks content exists and is efficient', + pass, + score: pass ? 1 : 0, + reason: 'Checks content exists and is efficient', }; }); ``` @@ -827,17 +848,21 @@ import { defineScriptGrader } from '@agentv/sdk'; export default defineScriptGrader(({ output, trace }) => { const finalOutput = output ?? ''; + const hasOutput = finalOutput.length > 0; + const efficient = (trace?.eventCount ?? 0) <= 5; return { - score: finalOutput.length > 0 && (trace?.eventCount ?? 0) <= 5 ? 1.0 : 0.5, - assert: [ - { text: 'Output is not empty', passed: finalOutput.length > 0 }, - { text: 'Efficient tool usage', passed: (trace?.eventCount ?? 0) <= 5 }, + pass: hasOutput && efficient, + score: hasOutput && efficient ? 1.0 : 0.5, + reason: 'Checks content exists and tool usage is bounded', + checks: [ + { text: 'Output is not empty', pass: hasOutput, reason: hasOutput ? 'Output text is present' : 'Output is empty' }, + { text: 'Efficient tool usage', pass: efficient, reason: efficient ? 'Trace event count is within limit' : 'Trace event count is too high' }, ], }; }); ``` -Use `defineScriptGrader()` when the custom component is a command-backed grader with explicit score control, custom assertion-result arrays, workspace commands, or LLM calls through a grader target. `defineScriptGrader()` scripts are referenced in YAML with `type: script` and `command: [bun, run, grader.ts]`. Plain Vitest workspace verifier files can use `command: [agentv, eval, graders/check.test.ts]`. +Use `defineScriptGrader()` when the custom component is a command-backed grader with explicit score control, check arrays, workspace commands, or LLM calls through a grader target. `defineScriptGrader()` scripts are referenced in YAML with `type: script` and `command: [bun, run, grader.ts]`. Plain Vitest workspace verifier files can use `command: [agentv, eval, graders/check.test.ts]`. ### Convention-Based Discovery