niceeval 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs-site/zh/concepts/adapter.mdx +6 -6
- package/docs-site/zh/concepts/experiment.mdx +7 -7
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +63 -20
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +243 -0
- package/docs-site/zh/guides/experiments.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +252 -0
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +224 -0
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/viewing-results.mdx +147 -77
- package/docs-site/zh/guides/write-experiment.mdx +9 -9
- package/docs-site/zh/guides/write-send.mdx +6 -6
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/introduction.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +10 -2
- package/docs-site/zh/reference/define-agent.mdx +5 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/package.json +3 -2
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/ai-sdk.ts +2 -2
- package/src/agents/bub.ts +14 -4
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +4 -4
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +104 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +3 -3
- package/src/context/session.ts +2 -2
- package/src/context/types.ts +2 -2
- package/src/i18n/en.ts +29 -7
- package/src/i18n/zh-CN.ts +24 -2
- package/src/report/aggregate.ts +79 -50
- package/src/report/components.tsx +190 -0
- package/src/report/compute.ts +149 -84
- package/src/report/default-report.tsx +222 -0
- package/src/report/dual-face.test.tsx +527 -0
- package/src/report/format.ts +29 -0
- package/src/report/index.ts +79 -24
- package/src/report/load.ts +67 -0
- package/src/report/metrics.ts +2 -1
- package/src/report/param.ts +18 -0
- package/src/report/primitives.tsx +117 -0
- package/src/report/react/CaseList.tsx +2 -2
- package/src/report/react/DeltaTable.tsx +2 -2
- package/src/report/react/MetricBars.tsx +109 -0
- package/src/report/react/MetricLine.tsx +200 -0
- package/src/report/react/MetricMatrix.tsx +1 -1
- package/src/report/react/MetricScatter.tsx +1 -1
- package/src/report/react/MetricTable.tsx +1 -1
- package/src/report/react/RunOverview.tsx +4 -4
- package/src/report/react/Scoreboard.tsx +3 -3
- package/src/report/react/cell.tsx +1 -1
- package/src/report/react/fixtures.ts +98 -36
- package/src/report/react/format.ts +3 -28
- package/src/report/react/index.tsx +41 -24
- package/src/report/react/render.test.tsx +8 -8
- package/src/report/react/styles.css +77 -0
- package/src/report/report.test.ts +248 -128
- package/src/report/report.ts +69 -0
- package/src/report/text/faces.ts +356 -0
- package/src/report/text/layout.ts +125 -0
- package/src/report/text/plot.ts +127 -0
- package/src/report/tree.ts +220 -0
- package/src/report/types.ts +75 -27
- package/src/report/web.ts +40 -0
- package/src/results/copy.ts +95 -41
- package/src/results/format.ts +12 -11
- package/src/results/index.ts +34 -17
- package/src/results/open.ts +201 -81
- package/src/results/results.test.ts +595 -191
- package/src/results/select.ts +107 -55
- package/src/results/types.ts +137 -45
- package/src/results/writer.ts +258 -0
- package/src/runner/attempt.ts +3 -3
- package/src/runner/fingerprint.ts +1 -1
- package/src/runner/reporters/artifacts.ts +38 -78
- package/src/runner/reporters/braintrust.test.ts +2 -2
- package/src/runner/reporters/braintrust.ts +3 -3
- package/src/runner/run.ts +1 -1
- package/src/runner/types.ts +18 -12
- package/src/show/compose.ts +215 -0
- package/src/show/index.ts +263 -0
- package/src/show/render.ts +456 -0
- package/src/show/show.test.ts +505 -0
- package/src/view/app/App.tsx +85 -24
- package/src/view/app/components/CostScoreChart.tsx +29 -10
- package/src/view/app/components/ExperimentTable.tsx +27 -9
- package/src/view/app/components/GroupSelector.tsx +1 -1
- package/src/view/app/i18n.ts +18 -3
- package/src/view/app/lib/attempt-route.test.ts +9 -9
- package/src/view/app/lib/attempt-route.ts +5 -5
- package/src/view/app/lib/outcome.ts +1 -3
- package/src/view/app/lib/rows.ts +95 -15
- package/src/view/app/main.tsx +18 -6
- package/src/view/app/pages/RunsPage.tsx +4 -7
- package/src/view/app/pages/TracesPage.tsx +3 -8
- package/src/view/app/types.ts +50 -6
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.test.ts +254 -0
- package/src/view/data.ts +336 -0
- package/src/view/index.ts +64 -10
- package/src/view/server.ts +31 -9
- package/src/view/shared/types.ts +47 -41
- package/src/view/styles.css +5 -0
- package/src/view/template.html +1 -0
- package/src/view/view-report.test.ts +308 -0
- package/docs-site/.mintignore +0 -13
- package/docs-site/AGENTS.md +0 -46
- package/docs-site/concepts/adapter.mdx +0 -287
- package/docs-site/concepts/assert.mdx +0 -243
- package/docs-site/concepts/drive.mdx +0 -118
- package/docs-site/concepts/evals.mdx +0 -108
- package/docs-site/concepts/experiment.mdx +0 -37
- package/docs-site/concepts/hitl.mdx +0 -83
- package/docs-site/concepts/judge.mdx +0 -129
- package/docs-site/concepts/overview.mdx +0 -98
- package/docs-site/concepts/tier.mdx +0 -42
- package/docs-site/docs-ref/00-index.md +0 -23
- package/docs-site/docs-ref/01-page-types.md +0 -43
- package/docs-site/docs-ref/03-style-rules.md +0 -45
- package/docs-site/docs-ref/04-code-examples.md +0 -37
- package/docs-site/docs-ref/05-dx-failure-paths.md +0 -45
- package/docs-site/docs-ref/06-checklists.md +0 -53
- package/docs-site/docs.json +0 -256
- package/docs-site/example/ai-agent-application.mdx +0 -148
- package/docs-site/example/claude-code-codex-plugin.mdx +0 -162
- package/docs-site/example/claude-code-codex-skill.mdx +0 -147
- package/docs-site/example/showcase.mdx +0 -39
- package/docs-site/example/tier1-ai-sdk-v7.mdx +0 -136
- package/docs-site/example/tier1-claude-sdk.mdx +0 -119
- package/docs-site/example/tier1-codex-sdk.mdx +0 -111
- package/docs-site/example/tier1-langgraph.mdx +0 -119
- package/docs-site/example/tier1-pi-sdk.mdx +0 -119
- package/docs-site/favicon.svg +0 -5
- package/docs-site/github-diff.css +0 -135
- package/docs-site/github-diff.js +0 -11
- package/docs-site/guides/authoring.mdx +0 -129
- package/docs-site/guides/ci-integration.mdx +0 -97
- package/docs-site/guides/connect-otel.mdx +0 -209
- package/docs-site/guides/connect-your-agent.mdx +0 -221
- package/docs-site/guides/dataset-fanout.mdx +0 -98
- package/docs-site/guides/experiments.mdx +0 -71
- package/docs-site/guides/fixtures.mdx +0 -147
- package/docs-site/guides/reporters.mdx +0 -113
- package/docs-site/guides/runner.mdx +0 -79
- package/docs-site/guides/sandbox-agent.mdx +0 -138
- package/docs-site/guides/sandbox-backends.mdx +0 -64
- package/docs-site/guides/scoring-guide.mdx +0 -92
- package/docs-site/guides/viewing-results.mdx +0 -195
- package/docs-site/guides/write-experiment.mdx +0 -81
- package/docs-site/guides/write-send.mdx +0 -347
- package/docs-site/index.mdx +0 -181
- package/docs-site/introduction.mdx +0 -141
- package/docs-site/quickstart.mdx +0 -136
- package/docs-site/reference/builtin-agents.mdx +0 -161
- package/docs-site/reference/capabilities.mdx +0 -76
- package/docs-site/reference/cli.mdx +0 -120
- package/docs-site/reference/define-agent.mdx +0 -168
- package/docs-site/reference/define-config.mdx +0 -41
- package/docs-site/reference/define-eval.mdx +0 -160
- package/docs-site/reference/events.mdx +0 -131
- package/docs-site/reference/expect.mdx +0 -112
- package/docs-site/tracker.js +0 -8
- package/src/report/react/data.ts +0 -17
- package/src/view/aggregate.ts +0 -103
- package/src/view/loader.test.ts +0 -61
- package/src/view/loader.ts +0 -257
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Connect your agent"
|
|
3
|
-
sidebarTitle: "Connect Agent"
|
|
4
|
-
description: "The full picture of integration: write an adapter, configure an experiment, and get the first eval running; how configuration and parameters are declared in the experiment and consumed by the Adapter. Event streams, multi-turn, HITL, and tracing are increments, each with its own page."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Connecting a subject under test to NiceEval means writing one **adapter** for it: `defineAgent` wrapping a `send` function — it receives one turn of input, drives your agent, and returns that turn's result. The entire integration is this one file.
|
|
8
|
-
|
|
9
|
-
This page answers only two questions: **how this file and its two companions (experiment, eval) get running end to end**, and **how parameters flow from the experiment to the Adapter and on to your app**. Event streams, multi-turn, HITL, and tracing are all optional increments that come later, each with its own page; a map is at the end.
|
|
10
|
-
|
|
11
|
-
## Pick a path first: what is your subject under test?
|
|
12
|
-
|
|
13
|
-
<CardGroup cols={3}>
|
|
14
|
-
<Card title="AI SDK app" icon="bolt" href="/reference/builtin-agents">
|
|
15
|
-
Apps built with the Vercel AI SDK (`useChat` backend): the built-in `uiMessageStreamAgent` connects to their HTTP interface non-intrusively — zero mapping, HITL included, with a complete before/after example. You do not need this page.
|
|
16
|
-
</Card>
|
|
17
|
-
<Card title="Coding agent CLI" icon="terminal" href="/guides/sandbox-agent">
|
|
18
|
-
Evaluating claude-code / codex / bub and other file-editing coding agents: use the built-in sandbox agent.
|
|
19
|
-
</Card>
|
|
20
|
-
<Card title="Other AI Agent" icon="plug">
|
|
21
|
-
Custom agent loops, LangGraph / OpenAI Agents SDK apps, deployed agents: hand-write `send` (this page + [Write Send](/guides/write-send)). If the app already emits OTel, hook it up while you are at it — `niceeval view` gains a call waterfall ([OTel integration](/guides/connect-otel)).
|
|
22
|
-
</Card>
|
|
23
|
-
</CardGroup>
|
|
24
|
-
|
|
25
|
-
## Get the minimal integration running
|
|
26
|
-
|
|
27
|
-
Assume the project has already run `npx niceeval init` (so `niceeval.config.ts` and the `evals/` directory exist). Three files, one job each: **the adapter connects to the system under test, the experiment decides who to evaluate and how to run, the eval writes the interaction and assertions.**
|
|
28
|
-
|
|
29
|
-
**1. Write the adapter.** The minimal integration fills only `status` and `events`: whatever your agent said goes into one `message` event.
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// agents/my-bot.ts
|
|
33
|
-
import { defineAgent } from "niceeval/adapter";
|
|
34
|
-
|
|
35
|
-
export default defineAgent({
|
|
36
|
-
name: "my-bot",
|
|
37
|
-
async send(input, ctx) {
|
|
38
|
-
const r = await fetch("http://localhost:3000/chat", {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: { "content-type": "application/json" },
|
|
41
|
-
body: JSON.stringify({ message: input.text }),
|
|
42
|
-
signal: ctx.signal,
|
|
43
|
-
});
|
|
44
|
-
const body = await r.json();
|
|
45
|
-
return {
|
|
46
|
-
status: r.ok ? "completed" : "failed",
|
|
47
|
-
events: [{ type: "message", role: "assistant", text: body.reply }],
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Hardcoding the URL is fine for now — how to pass it in from the experiment is covered below in ["How parameters flow in"](#how-parameters-flow-in-the-experiment-declares-the-adapter-consumes). The full `send` contract (every field of `TurnInput` / `AgentContext` / `Turn`) is in [Adapter](/concepts/adapter).
|
|
54
|
-
|
|
55
|
-
Even when the agent runtime and the evals live in the same codebase, still go through HTTP — do not replace the `fetch` with an in-process function call:
|
|
56
|
-
|
|
57
|
-
- **A direct call does not evaluate the path your users take.** The HTTP layer, serialization, middleware, and streaming are all bypassed; a passing eval does not mean production behavior is correct.
|
|
58
|
-
- **No process isolation, so results are not reproducible.** Crashes, global state, and memory leaks in the code under test drag the runner down with them; concurrent evals pollute each other through shared state.
|
|
59
|
-
- **Timeouts and cancellation stop working.** `ctx.signal` can only actually kill a runaway turn when it is attached to a request; an in-process function call cannot be stopped.
|
|
60
|
-
- **The adapter gets welded to this codebase.** An HTTP adapter runs against local / staging / production by swapping `baseUrl` (see the two experiment files below); a direct call cannot.
|
|
61
|
-
|
|
62
|
-
Starting a local dev server costs one command; none of the above is worth trading for it.
|
|
63
|
-
|
|
64
|
-
**2. Register it in an experiment:**
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
// experiments/my-bot.ts
|
|
68
|
-
import { defineExperiment } from "niceeval";
|
|
69
|
-
import myBot from "../agents/my-bot.ts";
|
|
70
|
-
|
|
71
|
-
export default defineExperiment({
|
|
72
|
-
description: "my-bot baseline",
|
|
73
|
-
agent: myBot,
|
|
74
|
-
runs: 1,
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**3. Write the first eval, then run:**
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
// evals/refund-policy.eval.ts
|
|
82
|
-
import { defineEval } from "niceeval";
|
|
83
|
-
import { includes } from "niceeval/expect";
|
|
84
|
-
|
|
85
|
-
export default defineEval({
|
|
86
|
-
description: "Refund policy Q&A",
|
|
87
|
-
async test(t) {
|
|
88
|
-
await t.send("What is your refund policy?");
|
|
89
|
-
t.succeeded();
|
|
90
|
-
t.check(t.reply, includes("30 days"));
|
|
91
|
-
t.judge.autoevals.closedQA("Does the answer explain the refund window?").atLeast(0.7);
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npx niceeval exp my-bot # run all evals under this experiment
|
|
98
|
-
npx niceeval exp my-bot refund # only those whose ID starts with refund
|
|
99
|
-
npx niceeval view # inspect results in the local viewer
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**What success looks like**: one verdict line per eval in the terminal, ending with a `N passed, N failed` summary; `npx niceeval view` shows each eval's per-turn inputs, events, and scoring detail.
|
|
103
|
-
|
|
104
|
-
If it does not run, triage by where the error shows up, in three buckets:
|
|
105
|
-
|
|
106
|
-
- **`fetch` throws outright** (connection refused, etc.): the app is not up, or the URL in `send` is wrong — first send the same request to that endpoint with `curl` to confirm.
|
|
107
|
-
- **`t.succeeded()` fails and the turn verdict is failed**: the request went out but the app returned non-2xx. Print the response body with `ctx.log()` inside `send`; it shows up in this turn's log in `niceeval view`.
|
|
108
|
-
- **Only content assertions fail**: the integration itself already works — compare the actual value of `t.reply` in `view`, then adjust the assertion or the app.
|
|
109
|
-
|
|
110
|
-
At this point the integration is complete: text assertions and judge scoring both work. For more assertions (tools, multi-turn, approval flows), see the increment map at the end.
|
|
111
|
-
|
|
112
|
-
## How parameters flow in: the experiment declares, the Adapter consumes
|
|
113
|
-
|
|
114
|
-
Configuration has exactly two channels; keep them apart and the integration stays untangled:
|
|
115
|
-
|
|
116
|
-
1. **Static configuration goes through the Adapter factory.** URL, auth, protocol details — the "what does this environment look like" kind of configuration — are written as factory options in the experiment file: the `agent` field of `defineExperiment` holds an **already-configured instance**.
|
|
117
|
-
2. **Per-turn dynamic values go through `ctx`.** The `model` and `flags` declared by the experiment are handed to `send` verbatim via `ctx` every turn; the Adapter does not interpret their meaning, it only forwards them to the app with the request.
|
|
118
|
-
|
|
119
|
-
Putting the two channels together means turning step 1's my-bot from "an instance with a hardcoded URL" into "a factory that receives configuration": the default export becomes a function returning `defineAgent(...)`, the hardcoded spots in `send` become reads of factory options; the `model` and `flags` declared by the experiment arrive via `ctx` every turn and are forwarded inside `send` with the request:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// agents/my-bot.ts — factory: static configuration goes into the closure
|
|
123
|
-
import { defineAgent } from "niceeval/adapter";
|
|
124
|
-
import type { Agent } from "niceeval/adapter";
|
|
125
|
-
|
|
126
|
-
export function myBot(options: { baseUrl: string }): Agent {
|
|
127
|
-
return defineAgent({
|
|
128
|
-
name: "my-bot",
|
|
129
|
-
async send(input, ctx) {
|
|
130
|
-
const r = await fetch(`${options.baseUrl}/chat`, { // ← factory option: where to connect
|
|
131
|
-
method: "POST",
|
|
132
|
-
headers: { "content-type": "application/json" },
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
message: input.text,
|
|
135
|
-
model: ctx.model, // ← experiment.model: forward it if the app's interface accepts a model choice
|
|
136
|
-
flags: ctx.flags, // ← experiment.flags: hand to the app as-is; the app switches on flags
|
|
137
|
-
}),
|
|
138
|
-
signal: ctx.signal, // ← runner timeout and cancellation: attach to every request
|
|
139
|
-
});
|
|
140
|
-
const body = await r.json();
|
|
141
|
-
return {
|
|
142
|
-
status: r.ok ? "completed" : "failed",
|
|
143
|
-
events: [{ type: "message", role: "assistant", text: body.reply }],
|
|
144
|
-
};
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Auth headers, protocol switches, and the like are static configuration too — add them to `options` the same way. The experiment side changes in two small places: a named import of the factory, and the `agent` field goes from "referencing an instance" to "calling the factory":
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
// experiments/my-bot.ts — where parameters are declared
|
|
154
|
-
import { defineExperiment } from "niceeval";
|
|
155
|
-
import { myBot } from "../agents/my-bot.ts";
|
|
156
|
-
|
|
157
|
-
export default defineExperiment({
|
|
158
|
-
description: "my-bot baseline",
|
|
159
|
-
agent: myBot({ baseUrl: "http://localhost:3000" }), // ← static configuration: passed at instantiation
|
|
160
|
-
model: "gpt-5.4", // ← dynamic value: reaches send via ctx.model
|
|
161
|
-
flags: { promptVariant: "concise" }, // ← dynamic value: reaches send via ctx.flags
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
The `ctx` fields a turn may use, and how to consume them:
|
|
166
|
-
|
|
167
|
-
| `ctx` field | Who provides it | How the Adapter consumes it |
|
|
168
|
-
|---|---|---|
|
|
169
|
-
| `signal` | The runner (timeout and cancellation) | Attach it to every outgoing request |
|
|
170
|
-
| `model` | The experiment's `model` | Forward it with the request if the app's interface accepts a model choice; ignore it otherwise |
|
|
171
|
-
| `flags` | The experiment's `flags` | Forward as-is (request body or header both work); the app switches variants on flags |
|
|
172
|
-
| `telemetry` | Present when OTel integration is configured | Touch only `headers`: a fresh W3C `traceparent` per turn, spread into the request headers. The receiver endpoint is the same on every run — pin it in `defineConfig` and point the app at it on startup; it is not passed from send — see [OTel integration](/guides/connect-otel) |
|
|
173
|
-
| `session` | The runner (one per session line) | The accessors for session continuation and HITL held state all live on it: `history()`, `id` / `capture()`, `hold()` / `take()` — see [Write Send](/guides/write-send) |
|
|
174
|
-
| `log(msg)` | The runner | Written into this turn's log, visible in `niceeval view` |
|
|
175
|
-
|
|
176
|
-
Running the same agent against local and production separately is just two experiment files with two sets of factory options:
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
// experiments/local.ts
|
|
180
|
-
export default defineExperiment({
|
|
181
|
-
agent: myBot({ baseUrl: "http://localhost:3000" }),
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// experiments/prod.ts
|
|
185
|
-
export default defineExperiment({
|
|
186
|
-
agent: myBot({ baseUrl: "https://api.example.com" }),
|
|
187
|
-
});
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
npx niceeval exp local
|
|
192
|
-
npx niceeval exp prod
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Do not put URLs into CLI positional arguments — the positional arguments after the experiment name only filter eval IDs. For the full experiment fields (`runs`, `budget`, concurrency, `sandbox`), see [Write experiments](/guides/write-experiment).
|
|
196
|
-
|
|
197
|
-
## The increments after: a map
|
|
198
|
-
|
|
199
|
-
After the minimal integration, every increment you give the adapter unlocks a family of assertions, and the evals you have already written stay untouched:
|
|
200
|
-
|
|
201
|
-
| What you want to unlock | What to add to the adapter | Where to look |
|
|
202
|
-
|---|---|---|
|
|
203
|
-
| Tool assertions (`calledTool` / `toolOrder` / negative assertions) | Map the app's response into the standard event stream | [Write Send](/guides/write-send), [Events reference](/reference/events) |
|
|
204
|
-
| Multi-turn conversations, `t.newSession()` isolation | Connect `ctx.session`: `history()` or `id` + `capture()` | [Write Send](/guides/write-send) |
|
|
205
|
-
| Approval flows (HITL, human-in-the-loop) | Pause the turn with `waiting` + `input.requested`, resume on the answer turn | [HITL](/concepts/hitl) |
|
|
206
|
-
| The call waterfall in `niceeval view` | The app sends OTel spans to NiceEval (does not affect assertions) | [OTel integration](/guides/connect-otel) |
|
|
207
|
-
| Feature A/B comparison | The app exposes variants as configuration switchable by `flags` | [Tier](/concepts/tier), [Write experiments](/guides/write-experiment) |
|
|
208
|
-
|
|
209
|
-
Which tier each increment lands in, and what each tier buys, is in [Tier](/concepts/tier).
|
|
210
|
-
|
|
211
|
-
## Reference implementations
|
|
212
|
-
|
|
213
|
-
Complete runnable references: the five non-intrusive integration examples under [`examples/zh/tier1`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1) (ai-sdk-v7, claude-sdk, codex-sdk, pi-sdk, langgraph), covering event-stream assertions, multi-turn isolation, HITL approve / reject, and the trace waterfall. When hand-writing `send`, all you really write is the transport and the mapping table — the state slots for session continuation and HITL pause/resume are on `ctx.session`, and frame-by-frame driving is official machinery; see [Built-in agent capabilities](/reference/builtin-agents).
|
|
214
|
-
|
|
215
|
-
## Related reading
|
|
216
|
-
|
|
217
|
-
- [Write Send](/guides/write-send) — the complete tutorial for hand-writing an Adapter: seven progressive steps, from sending one message to HITL, OTel, and flags.
|
|
218
|
-
- [Adapter](/concepts/adapter) — the `send` contract: `TurnInput` / `AgentContext` / `Turn`, field by field.
|
|
219
|
-
- [OTel integration](/guides/connect-otel) — send the app's spans to NiceEval too, in exchange for the call waterfall in `niceeval view`.
|
|
220
|
-
- [Tier](/concepts/tier) — what each of the three integration tiers costs and buys.
|
|
221
|
-
- [Write experiments](/guides/write-experiment) — the full `defineExperiment` fields.
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Dataset fan-out: run evals across many test cases"
|
|
3
|
-
sidebarTitle: "Datasets"
|
|
4
|
-
description: "Export an array from a .eval.ts file to fan out into many evals. Use loadYaml or loadJson to drive evals from external datasets with stable IDs."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Dataset fan-out is a good fit when many test cases share the same structure and only the inputs change. Typical examples are SQL generation, intent classification, retrieval QA, and tool selection.
|
|
8
|
-
|
|
9
|
-
## How fan-out works
|
|
10
|
-
|
|
11
|
-
A `.eval.ts` file can export an array by default:
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { defineEval } from "niceeval";
|
|
15
|
-
import { equals } from "niceeval/expect";
|
|
16
|
-
|
|
17
|
-
const rows = [
|
|
18
|
-
{ task: "Count users", prompt: "Count all users", sql: "SELECT COUNT(*) FROM users;" },
|
|
19
|
-
{ task: "Recent orders", prompt: "Find recent orders", sql: "SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;" },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
export default rows.map((row) =>
|
|
23
|
-
defineEval({
|
|
24
|
-
description: row.task,
|
|
25
|
-
async test(t) {
|
|
26
|
-
await t.send(row.prompt);
|
|
27
|
-
t.check(t.reply, equals(row.sql));
|
|
28
|
-
},
|
|
29
|
-
}),
|
|
30
|
-
);
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Generated ID format
|
|
34
|
-
|
|
35
|
-
If the file is `evals/sql.eval.ts`, the generated IDs are:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
sql/0000
|
|
39
|
-
sql/0001
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
The numeric suffix is zero-padded so IDs stay stable and easy to filter.
|
|
43
|
-
|
|
44
|
-
## Loading from YAML and JSON
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { defineEval } from "niceeval";
|
|
48
|
-
import { loadYaml } from "niceeval/loaders";
|
|
49
|
-
import { equals } from "niceeval/expect";
|
|
50
|
-
|
|
51
|
-
const doc = await loadYaml("evals/data/sql-cases.yaml");
|
|
52
|
-
const rows = doc.cases as { task: string; prompt: string; sql: string }[];
|
|
53
|
-
|
|
54
|
-
export default rows.map((row) =>
|
|
55
|
-
defineEval({
|
|
56
|
-
description: row.task,
|
|
57
|
-
async test(t) {
|
|
58
|
-
await t.send(row.prompt);
|
|
59
|
-
t.check(t.reply, equals(row.sql));
|
|
60
|
-
},
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
cases:
|
|
67
|
-
- task: Count users
|
|
68
|
-
prompt: Count all rows in the users table
|
|
69
|
-
sql: SELECT COUNT(*) FROM users;
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Filtering dataset evals
|
|
73
|
-
|
|
74
|
-
Because each eval in a fan-out set shares the same file-level ID prefix, you can run them all with a single CLI argument:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# Run the whole dataset
|
|
78
|
-
npx niceeval exp local sql
|
|
79
|
-
|
|
80
|
-
# Run only the first case
|
|
81
|
-
npx niceeval exp local sql/0000
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Datasets vs separate files
|
|
86
|
-
|
|
87
|
-
<Tabs>
|
|
88
|
-
<Tab title="Use a dataset">
|
|
89
|
-
The cases have exactly the same structure. Only the input and expected output change.
|
|
90
|
-
</Tab>
|
|
91
|
-
<Tab title="Use separate files">
|
|
92
|
-
Each case has a different flow, different assertions, or needs a different agent configuration.
|
|
93
|
-
</Tab>
|
|
94
|
-
</Tabs>
|
|
95
|
-
|
|
96
|
-
<Tip>
|
|
97
|
-
Datasets are good for broad horizontal coverage. Separate eval files are better when the behavior itself is complex.
|
|
98
|
-
</Tip>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Experiment matrix: compare agents and models with run matrices"
|
|
3
|
-
sidebarTitle: "Experiments"
|
|
4
|
-
description: "Use NiceEval experiments to run the same evals across multiple agents, models, and feature flags, then compare pass rate, cost, and latency."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Experiments compare multiple run configurations. Typical questions include: which coding agent has the higher pass rate on the same task set, whether a prompt change lowers cost, and how latency trades off against quality across models.
|
|
8
|
-
|
|
9
|
-
## Basic shape
|
|
10
|
-
|
|
11
|
-
**One experiment file = one configuration** (`one agent x one model`). `model` is a single string, not an array. To compare across models or agents, put multiple files in the same **experiment group directory** and keep everything else fixed:
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
experiments/
|
|
15
|
-
compare-models/
|
|
16
|
-
gpt-5.4.ts
|
|
17
|
-
deepseek-v4-pro.ts
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
import { defineExperiment } from "niceeval";
|
|
22
|
-
import { webAgent } from "../../adapter/adapter.ts";
|
|
23
|
-
|
|
24
|
-
export default defineExperiment({
|
|
25
|
-
description: "gpt-5.4 comparison model",
|
|
26
|
-
agent: webAgent({ baseUrl: "http://127.0.0.1:5188" }),
|
|
27
|
-
model: "gpt-5.4",
|
|
28
|
-
runs: 2,
|
|
29
|
-
earlyExit: true,
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npx niceeval exp compare-models
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Each configuration stays in its own file, which makes naming, diffing, and review straightforward. The directory structure itself explains which files belong to the same comparison.
|
|
38
|
-
|
|
39
|
-
See [Write Experiments](/guides/write-experiment) for the full `defineExperiment` field list and how `flags` flow into adapters.
|
|
40
|
-
|
|
41
|
-
## What this is good for
|
|
42
|
-
|
|
43
|
-
- Comparing different Adapters
|
|
44
|
-
- Comparing different models. Tier 1 is enough as long as the application exposes model choice and the value is forwarded through `ctx.model`
|
|
45
|
-
- Comparing prompts or feature flags. This usually needs Tier 3, because the application has to expose the variant as an experiment-selectable config and forward it through `flags` -> `ctx.flags`
|
|
46
|
-
- Comparing different sandbox backends
|
|
47
|
-
- Measuring pass@N for the same task
|
|
48
|
-
|
|
49
|
-
See [Tier](/concepts/tier) for what Tier 1, Tier 2, and Tier 3 mean.
|
|
50
|
-
|
|
51
|
-
## Reading the results
|
|
52
|
-
|
|
53
|
-
Experiment output is typically shown per `(agent, model, eval)` cell:
|
|
54
|
-
|
|
55
|
-
```text
|
|
56
|
-
api-validation claude-code+zod-skill pass@3 = 3/3 (100%) mean 34s
|
|
57
|
-
api-validation claude-code pass@3 = 1/3 (33%) mean 41s
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Beyond pass rate, you should also compare mean time, tokens, cost, and failure types.
|
|
61
|
-
|
|
62
|
-
## Design advice
|
|
63
|
-
|
|
64
|
-
- Keep the eval set stable so the comparison does not mix in extra variables.
|
|
65
|
-
- Run multiple attempts per cell, especially for non-deterministic coding agents.
|
|
66
|
-
- Make the budget and concurrency explicit.
|
|
67
|
-
- Group failure modes instead of looking only at the total score.
|
|
68
|
-
|
|
69
|
-
## Relation to ordinary runs
|
|
70
|
-
|
|
71
|
-
`npx niceeval exp <experiment>` answers "did this eval suite pass under this configuration?" An experiment group answers "which configuration is better?" Both use the same evals, adapters, scoring, and artifacts.
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Sandbox fixtures: evaluate coding agents with tasks"
|
|
3
|
-
sidebarTitle: "Fixtures"
|
|
4
|
-
description: "Use .eval.ts files to prepare isolated workspaces, send real coding-agent tasks, and verify results with files, commands, diffs, and judges."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
When you evaluate a coding agent, a chat assertion is not enough. You usually need to give it a real project, let it read and edit files, run commands, and then inspect the result. The current recommended niceeval shape is a normal `.eval.ts` file that explicitly prepares the sandbox workspace, sends the task, and records assertions.
|
|
8
|
-
|
|
9
|
-
See [`examples/zh/coding-agent-skill`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/coding-agent-skill) for a runnable reference.
|
|
10
|
-
|
|
11
|
-
## Recommended structure
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
evals/
|
|
15
|
-
├─ api-validation.eval.ts
|
|
16
|
-
├─ config-schema.eval.ts
|
|
17
|
-
└─ ponytail-safe-path.eval.ts
|
|
18
|
-
|
|
19
|
-
workspaces/
|
|
20
|
-
└─ ts-starter/
|
|
21
|
-
├─ package.json
|
|
22
|
-
├─ tsconfig.json
|
|
23
|
-
└─ src/
|
|
24
|
-
|
|
25
|
-
skills/
|
|
26
|
-
└─ zod.md
|
|
27
|
-
|
|
28
|
-
experiments/
|
|
29
|
-
├─ baseline.ts
|
|
30
|
-
└─ with-skill.ts
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`workspaces/` contains the starting project the agent can see. The `.eval.ts` file decides when to upload it, what prompt to send, and what to verify. `experiments/` decides which agent/model runs and whether a Skill or plugin is injected.
|
|
34
|
-
|
|
35
|
-
## Prepare the workspace
|
|
36
|
-
|
|
37
|
-
Upload the starting project inside the eval:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
import { defineEval } from "niceeval";
|
|
41
|
-
|
|
42
|
-
export default defineEval({
|
|
43
|
-
description: "Add request-body validation to an Express route",
|
|
44
|
-
async test(t) {
|
|
45
|
-
await t.sandbox.uploadDirectory("../workspaces/ts-starter");
|
|
46
|
-
|
|
47
|
-
await t.send("Implement POST /users in src/routes/users.ts.").then((turn) => turn.succeeded());
|
|
48
|
-
|
|
49
|
-
t.sandbox.fileChanged("src/routes/users.ts");
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Use `t.sandbox.writeFiles()` to add small seed files inside a specific eval, such as a Python probe or a focused TODO file.
|
|
55
|
-
|
|
56
|
-
## Write the task prompt
|
|
57
|
-
|
|
58
|
-
The prompt should read like a real work order. Describe the goal and constraints, but do not leak the verification answer.
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
await t
|
|
62
|
-
.send(
|
|
63
|
-
`Implement environment validation in src/config/env.ts.
|
|
64
|
-
Requirements:
|
|
65
|
-
- Define EnvSchema with Zod
|
|
66
|
-
- Convert PORT to a number
|
|
67
|
-
- DATABASE_URL must be a valid URL
|
|
68
|
-
- JWT_SECRET must be at least 32 characters
|
|
69
|
-
- Export the env object and Env type`,
|
|
70
|
-
)
|
|
71
|
-
.then((turn) => turn.succeeded());
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Leave implicit requirements, such as path traversal defense or reuse of existing helpers, for the verification phase. That is how you measure whether a Skill or plugin helped the agent infer missing context.
|
|
75
|
-
|
|
76
|
-
## Verify files and code
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
import { excludes, includes } from "niceeval/expect";
|
|
80
|
-
|
|
81
|
-
const src = await t.sandbox.readSourceFiles({ extensions: ["ts"] });
|
|
82
|
-
const config = src.fileMatching(/env/);
|
|
83
|
-
const code = config?.content ?? "";
|
|
84
|
-
|
|
85
|
-
t.check(code, includes(/z\.object\s*\(/));
|
|
86
|
-
t.check(code, includes(/EnvSchema\.parse\s*\(\s*process\.env/));
|
|
87
|
-
t.check(code, excludes(/process\.env\.\w+\s*\?\?/));
|
|
88
|
-
t.sandbox.fileChanged("src/config/env.ts");
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`t.sandbox.fileChanged()` is a scoped assertion. It is evaluated from the sandbox diff after the run.
|
|
92
|
-
|
|
93
|
-
## Run project tests or probes
|
|
94
|
-
|
|
95
|
-
Real commands are often more reliable than text matching for coding-agent tasks:
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
await t.sandbox.writeFiles({
|
|
99
|
-
"_test_traversal.py": [
|
|
100
|
-
"import sys, os",
|
|
101
|
-
"sys.path.insert(0, '.')",
|
|
102
|
-
"from uploads import safe_upload_path",
|
|
103
|
-
"base = '/var/uploads'",
|
|
104
|
-
"try:",
|
|
105
|
-
" p = safe_upload_path(base, '../../etc/passwd')",
|
|
106
|
-
" assert os.path.commonpath([base, os.path.abspath(p)]) == base",
|
|
107
|
-
"except (ValueError, PermissionError, AssertionError):",
|
|
108
|
-
" pass",
|
|
109
|
-
"print('ok')",
|
|
110
|
-
].join("\n"),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
const result = await t.sandbox.runCommand("python3", ["_test_traversal.py"]);
|
|
114
|
-
t.check(result.stdout.trim(), includes(/ok/));
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
You can also run the project's own test, lint, or build scripts:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
const result = await t.sandbox.runCommand("npm", ["test"]);
|
|
121
|
-
t.check(result.exitCode, includes(0));
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Use experiments for baselines
|
|
125
|
-
|
|
126
|
-
Do not put agent names, plugin names, or URLs into CLI positionals. Experiments decide which agent to run and how; eval positionals only filter eval IDs.
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
pnpm exec niceeval exp compare
|
|
130
|
-
pnpm exec niceeval exp compare api-validation
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
A typical A/B setup:
|
|
134
|
-
|
|
135
|
-
- `experiments/baseline.ts`: plain agent.
|
|
136
|
-
- `experiments/with-skill.ts`: same agent plus a setup hook that writes `CLAUDE.md`.
|
|
137
|
-
- Same eval set, model, runs, and budget in both cells.
|
|
138
|
-
|
|
139
|
-
## When to use sandbox fixtures
|
|
140
|
-
|
|
141
|
-
- The agent must modify real files.
|
|
142
|
-
- You need to run tests, builds, or lint.
|
|
143
|
-
- You need to inspect diffs.
|
|
144
|
-
- You are evaluating Claude Code, Codex, or another coding agent.
|
|
145
|
-
- You are comparing whether a Skill, plugin, Hook, or MCP server improves real tasks.
|
|
146
|
-
|
|
147
|
-
If the system under test is a direct agent application, a direct adapter is lighter.
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Report Results to Braintrust and Other Destinations"
|
|
3
|
-
sidebarTitle: "Reporters"
|
|
4
|
-
description: "Use built-in reporters to send eval results to Braintrust experiments, JUnit XML, JSON files, or custom destinations."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
[NiceEval](https://niceeval.com/) runs and scores evals itself. Reporters send the results elsewhere. Console output and `.niceeval/` artifacts are built-in reporters and are always enabled. Other reporters are imported from `niceeval/reporters` and mounted as needed.
|
|
8
|
-
|
|
9
|
-
There are two mount points:
|
|
10
|
-
|
|
11
|
-
- `reporters` in `niceeval.config.ts`: observes every eval in the run. Shared destinations, such as one Braintrust experiment, usually belong here.
|
|
12
|
-
- `reporters` on a single eval: the instance only observes that eval. If multiple evals reference the same instance, their results are merged into the same destination. Listing an instance on an eval again after it was already configured globally does not report it twice.
|
|
13
|
-
|
|
14
|
-
## Braintrust
|
|
15
|
-
|
|
16
|
-
`Braintrust(...)` uploads one NiceEval run as a Braintrust experiment, with one row per attempt. Put it in config to cover the whole run:
|
|
17
|
-
|
|
18
|
-
```ts niceeval.config.ts
|
|
19
|
-
import { defineConfig } from "niceeval";
|
|
20
|
-
import { Braintrust } from "niceeval/reporters";
|
|
21
|
-
|
|
22
|
-
export default defineConfig({
|
|
23
|
-
reporters: [Braintrust({ project: "weather-agent" })],
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Mount it on an eval when only some evals should report:
|
|
28
|
-
|
|
29
|
-
```ts evals/forecast.eval.ts
|
|
30
|
-
import { defineEval } from "niceeval";
|
|
31
|
-
import { Braintrust } from "niceeval/reporters";
|
|
32
|
-
|
|
33
|
-
export default defineEval({
|
|
34
|
-
reporters: [Braintrust({ project: "weather-agent" })],
|
|
35
|
-
async test(t) {
|
|
36
|
-
await t.send("Is Beijing good for cycling today?");
|
|
37
|
-
t.succeeded();
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Prerequisites: install the optional `braintrust` package (`npm install braintrust`; other features work without it) and set `BRAINTRUST_API_KEY`. Pass `apiKey` when the key must be provided explicitly in code. After the run, the terminal prints the experiment URL.
|
|
43
|
-
|
|
44
|
-
### Reporting Shape
|
|
45
|
-
|
|
46
|
-
- Each attempt becomes one Braintrust row. `runs: 3` creates three rows, distinguished by `attempt` in metadata.
|
|
47
|
-
- Soft assertions are recorded as scores by name. Gate assertions are recorded under a `gate:` prefix, so gate regressions and soft score regressions appear in the same Braintrust experiment diff surface.
|
|
48
|
-
- Metrics include start/end time, token usage, and estimated cost. Missing data is omitted rather than filled with zero.
|
|
49
|
-
- Metadata includes `agent`, `model`, `experiment`, `flags`, `outcome`, and failed assertion details, so Braintrust filters can slice by those dimensions.
|
|
50
|
-
|
|
51
|
-
### Options
|
|
52
|
-
|
|
53
|
-
<ParamField body="project" type="string">
|
|
54
|
-
Braintrust project name. Defaults to `niceeval`.
|
|
55
|
-
</ParamField>
|
|
56
|
-
|
|
57
|
-
<ParamField body="projectId" type="string">
|
|
58
|
-
Braintrust project ID. Provide either `project` or `projectId`.
|
|
59
|
-
</ParamField>
|
|
60
|
-
|
|
61
|
-
<ParamField body="experiment" type="string">
|
|
62
|
-
Experiment name. If omitted, Braintrust names it automatically.
|
|
63
|
-
</ParamField>
|
|
64
|
-
|
|
65
|
-
<ParamField body="baseExperiment" type="string">
|
|
66
|
-
Existing experiment name to use as the diff base.
|
|
67
|
-
</ParamField>
|
|
68
|
-
|
|
69
|
-
<ParamField body="baseExperimentId" type="string">
|
|
70
|
-
Existing experiment ID to use as the diff base.
|
|
71
|
-
</ParamField>
|
|
72
|
-
|
|
73
|
-
<ParamField body="update" type="boolean">
|
|
74
|
-
When `true`, updates an existing experiment with the same name instead of creating a new one.
|
|
75
|
-
</ParamField>
|
|
76
|
-
|
|
77
|
-
<ParamField body="metadata" type="Record<string, unknown>">
|
|
78
|
-
Experiment-level metadata. Merged with NiceEval's automatic fields; this value wins on name conflicts.
|
|
79
|
-
</ParamField>
|
|
80
|
-
|
|
81
|
-
<ParamField body="apiKey" type="string">
|
|
82
|
-
Braintrust API key. Defaults to the SDK reading `BRAINTRUST_API_KEY`.
|
|
83
|
-
</ParamField>
|
|
84
|
-
|
|
85
|
-
## JUnit and JSON
|
|
86
|
-
|
|
87
|
-
`JUnit(path)` writes JUnit XML so failures appear in CI test report UIs. For temporary output, use the CLI flag `--junit <path>` without changing config. `Json(path)` writes the full `RunSummary` as JSON for downstream scripts or dashboards. See [CI Integration](./ci-integration) for a complete CI setup.
|
|
88
|
-
|
|
89
|
-
## Custom Reporters
|
|
90
|
-
|
|
91
|
-
A reporter is an object that implements optional callbacks and receives the same structured results as built-in reporters:
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
import type { Reporter } from "niceeval";
|
|
95
|
-
|
|
96
|
-
const notify: Reporter = {
|
|
97
|
-
async onEvalComplete(result) {
|
|
98
|
-
if (result.outcome === "failed") {
|
|
99
|
-
await fetch("http://localhost:3000/notify", {
|
|
100
|
-
method: "POST",
|
|
101
|
-
body: JSON.stringify({ eval: result.id, agent: result.agent }),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
- `onRunStart(evals, agent, shape)`: run start, with the eval list that will actually run and the run shape.
|
|
109
|
-
- `onEvalComplete(result)`: fires as soon as each attempt completes. Callbacks are serialized and do not interleave.
|
|
110
|
-
- `onRunComplete(summary)`: run complete, with the aggregate summary.
|
|
111
|
-
- `onEvent(event)`: lower-level event stream, such as `eval:start` and `run:budgetExceeded`.
|
|
112
|
-
|
|
113
|
-
Reporter errors only print diagnostics; they do not stop the run. Custom reporters are only needed for destinations not covered by built-ins. `.niceeval/` artifacts already record all results, so post-run analysis can read them directly. See [Viewing Results](./viewing-results).
|