Batch CLI Evaluation
Batch CLI evaluation handles tools that process multiple inputs at once — bulk classifiers, screening engines, or any runner that reads all tests and outputs results in one pass.
Overview
Section titled “Overview”Use batch CLI evaluation when:
- An external tool processes multiple inputs in a single invocation (e.g., AML screening, bulk classification)
- The runner reads the eval YAML directly to extract all tests
- Output is JSONL with records keyed by test
id - Each test has its own grader to validate its corresponding output record
Execution Flow
Section titled “Execution Flow”- AgentV invokes the batch runner once, passing
--eval <yaml-path>and--output <jsonl-path> - Batch runner reads the eval YAML, extracts all tests, processes them, and writes JSONL output keyed by
id - AgentV parses the JSONL and routes each record to its matching test by
id - Per-test graders validate the output for each test independently
Eval File Structure
Section titled “Eval File Structure”description: Batch CLI demo using structured inputproviders: - batch_cli
prompts: - "{{ input }}"
tests: - id: case-001 criteria: |- Batch runner returns JSON with decision=CLEAR. vars: input: - role: system content: You are a batch processor. - role: user content: request: type: screening_check jurisdiction: AU row: id: case-001 name: Example A amount: 5000 expected_output: - role: assistant content: decision: CLEAR
assert: - name: decision-check type: script command: [bun, run, ./scripts/check-output.ts] cwd: .
- id: case-002 criteria: |- Batch runner returns JSON with decision=REVIEW. vars: input: - role: system content: You are a batch processor. - role: user content: request: type: screening_check jurisdiction: AU row: id: case-002 name: Example B amount: 25000 expected_output: - role: assistant content: decision: REVIEW
assert: - name: decision-check type: script command: [bun, run, ./scripts/check-output.ts] cwd: .Batch Runner Contract
Section titled “Batch Runner Contract”The batch runner reads the eval YAML directly and processes all tests in one invocation.
The runner receives the eval file path via --eval and an output path via --output:
bun run batch-runner.ts --eval ./my-eval.yaml --output ./output.jsonlOutput
Section titled “Output”JSONL where each line is a JSON object with an id matching a test:
{"id": "case-001", "text": "{\"decision\": \"CLEAR\", ...}"}{"id": "case-002", "text": "{\"decision\": \"REVIEW\", ...}"}The id field must match the test id for AgentV to route output to the correct grader.
Output with Trajectory Assertions
Section titled “Output with Trajectory Assertions”To enable trajectory:* assertions, include output with tool_calls:
{ "id": "case-001", "text": "{\"decision\": \"CLEAR\", ...}", "output": [ { "role": "assistant", "tool_calls": [ { "tool": "screening_check", "input": { "origin_country": "NZ", "amount": 5000 }, "output": { "decision": "CLEAR", "reasons": [] } } ] }, { "role": "assistant", "content": { "decision": "CLEAR" } } ]}AgentV extracts tool calls directly from output[].tool_calls[] for trajectory assertions.
Grader Implementation
Section titled “Grader Implementation”Each test has its own grader that validates the batch runner output. The grader receives the standard script input via stdin.
Input (stdin):
{ "output": "{\"id\":\"case-001\",\"decision\":\"CLEAR\",...}", "expected_output": [{"role": "assistant", "content": {"decision": "CLEAR"}}], "input": [...]}Output (stdout):
{ "pass": true, "score": 1.0, "reason": "Batch runner decision matches expected.", "checks": [ { "text": "decision matches: CLEAR", "pass": true, "reason": "Expected and actual decisions are CLEAR." } ]}Example Grader
Section titled “Example Grader”import fs from 'node:fs';
type EvalInput = { output?: string; expected_output?: Array<{ role: string; content: unknown }>;};
function main() { const stdin = fs.readFileSync(0, 'utf8'); const input = JSON.parse(stdin) as EvalInput;
const expectedDecision = findExpectedDecision(input.expected_output);
let candidateDecision: string | undefined; try { const parsed = JSON.parse(input.output ?? ''); candidateDecision = parsed.decision; } catch { candidateDecision = undefined; }
const checks: Array<{ text: string; pass: boolean; reason: string }> = [];
if (expectedDecision === candidateDecision) { checks.push({ text: `decision matches: ${expectedDecision}`, pass: true, reason: 'Expected and actual decisions match.' }); } else { checks.push({ text: `mismatch: expected=${expectedDecision} actual=${candidateDecision}`, pass: false, reason: 'Expected and actual decisions differ.' }); }
const passed = checks.every((check) => check.pass);
process.stdout.write(JSON.stringify({ pass: passed, score: passed ? 1 : 0, reason: passed ? 'Batch runner output matches expected.' : 'Batch runner output did not match expected.', checks, }));}
function findExpectedDecision(messages?: Array<{ role: string; content: unknown }>) { if (!messages) return undefined; for (const msg of messages) { if (typeof msg.content === 'object' && msg.content !== null) { return (msg.content as Record<string, unknown>).decision as string; } } return undefined;}
main();Structured Content
Section titled “Structured Content”Use structured objects in vars.expected_output to define expected output fields for easy validation:
vars: expected_output: - role: assistant content: decision: CLEAR confidence: high reasons: []The grader extracts these fields and compares them against the parsed candidate output.
Target Configuration
Section titled “Target Configuration”Configure the batch CLI provider in your targets file or eval file:
# In agentv-providers.yaml or eval fileproviders: batch_cli: provider: cli command: bun run ./scripts/batch-runner.ts --eval {EVAL_FILE} --output {OUTPUT_FILE} batch_requests: trueKey settings:
| Setting | Description |
|---|---|
provider: cli | Use the CLI provider |
batch_requests: true | Run once for all tests instead of per-test |
{EVAL_FILE} | Placeholder replaced with the eval file path |
{OUTPUT_FILE} | Placeholder replaced with the JSONL output path |
Best Practices
Section titled “Best Practices”- Use unique test IDs — the batch runner and AgentV use
idto route outputs to the correct grader - Structured input — put structured data in
user.contentfor the runner to extract - Structured expected_output — define expected output as objects for easy comparison
- Deterministic runners — batch runners should produce consistent output for reliable testing
- Healthcheck support — add a
--healthcheckflag for runner validation:if (args.includes('--healthcheck')) {console.log('batch-runner: healthy');return;}