Skip to content

feat(appkit): eval suite features — concurrency, retries, reporters (stack 4/5)#480

Open
MarioCadenas wants to merge 8 commits into
pr/agent-evals-3-datasetsfrom
pr/agent-evals-4-suite
Open

feat(appkit): eval suite features — concurrency, retries, reporters (stack 4/5)#480
MarioCadenas wants to merge 8 commits into
pr/agent-evals-3-datasetsfrom
pr/agent-evals-4-suite

Conversation

@MarioCadenas

Copy link
Copy Markdown
Collaborator

Stack 4/5 · targets pr/agent-evals-3-datasets (review after #3).

CI-grade suite features.

  • Tool-call argument assertions: t.calledToolWith(name, expected) (deep-partial arg match).
  • Multi-turn dataset rows: userTurns(input) replays a row's full messages[] against one thread.
  • Concurrency: --concurrency N / maxConcurrency — bounded, order-preserving pool.
  • Pass-rate gating: --min-pass-rate 0..1 — gate on aggregate rate instead of all-must-pass.
  • Timeout / config / tags: enforce timeoutMs, load per-dir evals.config.ts, --tag filtering.
  • Retries: --retries N — re-run only on infra failures (turn/timeout), never on assertion failures.
  • Reporters: --reporter json|junit + --output for CI.

Includes two small dedup refactors (credential resolution; agent-dir listing / int parsing).

…al CLI

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The eval driver captured tool-call names but discarded their arguments. Parse
the function-call `arguments` JSON into `DriveResult.toolCallDetails` (the later
`done` event's fuller args win over the initial `added`), and expose
`t.calledToolWith(name, expected)` — passes when a call to `name` had args that
deep-contain `expected` (nested-aware partial match; extra args ignored). Gate
by default, like `calledTool`. `toolCalls: string[]` is unchanged.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
A managed-dataset row's `inputs.messages` can be a full conversation, not just
one question. Add `userTurns(input)` to extract every user-message content in
order; the example dataset eval replays them against one thread so the agent
sees the accumulating conversation. Interleaved assistant/system turns are
ignored — the agent generates its own responses. Single-user-turn rows are
unchanged (one send).

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The runner drove every eval and dataset row serially. Add `maxConcurrency`
(RunEvalsOptions + `--concurrency <n>`, default 1 = serial) so up to N rows run
in parallel — a real speedup for dataset sweeps, where each row is an HTTP turn.

A `runBounded` worker pool places each result in its input slot, so output order
matches discovery/row order regardless of completion order (reportToMlflow and
the summary depend on it). Work is expanded serially first to preserve load and
dataset-read error handling; each row still gets a fresh driver. This also makes
the previously-declared-but-unwired `EvalConfig.maxConcurrency` real.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Add `--min-pass-rate <0..1>` to the agent eval CLI: instead of requiring every
eval to pass, exit non-zero only when the aggregate pass rate falls below the
threshold. `summarize()` now returns `passRate` (passed / scored, excluding
skips; 1 when nothing scored). Without the flag, behavior is unchanged (any gate
failure fails the run).

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Wire three eval fields that were declared but inert:

- `timeoutMs` — runEval races the test against a per-eval timeout and records a
  clean non-passing result ("eval timed out after Nms") instead of hanging.
  Precedence: def.timeoutMs > runner/CLI --timeout > unbounded. Timer is always
  cleared.
- `evals.config.ts` (defineEvalConfig) — discovered per-agent and loaded via the
  tsx loader; its maxConcurrency/timeoutMs apply as defaults (CLI flag > config >
  built-in). Judge model still comes from the CLI (needs creds the config lacks).
- `tags` + `--tag <tag...>` — run only evals whose tags intersect the filter.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Add `--retries <n>` / `RunEvalsOptions.retries`: re-run an eval up to N extra
times when it fails on an infrastructure error (a thrown error or timeout —
`result.error` set), to absorb transient turn/stream flakiness. Assertion
failures are never retried — a wrong reply is real signal, and retrying a flaky
judge until it passes would corrupt the result. Each attempt gets a fresh
driver. Extracted as `runWithRetries` (unit-tested for attempt counting,
stop-on-success, and no-retry-on-assertion-failure).

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Add `--reporter <text|json|junit>` (default text) and `--output <file>` to the
agent eval CLI. `formatResultsJson` emits `{summary, results}`; `formatResultsJUnit`
emits a `<testsuite>` with a `<testcase>` per eval (`<failure>`/`<skipped>` as
appropriate, all values XML-escaped). In json/junit mode the per-eval streaming
is suppressed and human banners go to stderr so stdout stays clean for piping or
`--output`; exit-code and pass-rate gating are unchanged.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas
MarioCadenas requested a review from a team as a code owner July 16, 2026 14:26
@MarioCadenas
MarioCadenas requested review from ditadi and removed request for a team July 16, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant