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.
Files changed (175) hide show
  1. package/docs-site/zh/concepts/adapter.mdx +6 -6
  2. package/docs-site/zh/concepts/experiment.mdx +7 -7
  3. package/docs-site/zh/concepts/overview.mdx +1 -1
  4. package/docs-site/zh/concepts/tier.mdx +6 -6
  5. package/docs-site/zh/guides/agent-feedback-loop.mdx +63 -20
  6. package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
  7. package/docs-site/zh/guides/custom-reports.mdx +243 -0
  8. package/docs-site/zh/guides/experiments.mdx +3 -3
  9. package/docs-site/zh/guides/report-components.mdx +252 -0
  10. package/docs-site/zh/guides/reporters.mdx +1 -1
  11. package/docs-site/zh/guides/results-data.mdx +224 -0
  12. package/docs-site/zh/guides/runner.mdx +1 -1
  13. package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
  14. package/docs-site/zh/guides/viewing-results.mdx +147 -77
  15. package/docs-site/zh/guides/write-experiment.mdx +9 -9
  16. package/docs-site/zh/guides/write-send.mdx +6 -6
  17. package/docs-site/zh/index.mdx +1 -1
  18. package/docs-site/zh/introduction.mdx +1 -1
  19. package/docs-site/zh/reference/cli.mdx +10 -2
  20. package/docs-site/zh/reference/define-agent.mdx +5 -5
  21. package/docs-site/zh/reference/define-config.mdx +1 -1
  22. package/docs-site/zh/reference/define-eval.mdx +3 -3
  23. package/package.json +3 -2
  24. package/src/agents/ai-sdk.test.ts +1 -1
  25. package/src/agents/ai-sdk.ts +2 -2
  26. package/src/agents/bub.ts +14 -4
  27. package/src/agents/claude-code.ts +1 -1
  28. package/src/agents/codex.ts +2 -2
  29. package/src/agents/streaming.test.ts +1 -1
  30. package/src/agents/types.ts +4 -4
  31. package/src/agents/ui-message-stream.test.ts +1 -1
  32. package/src/cli.ts +104 -14
  33. package/src/context/context.test.ts +1 -1
  34. package/src/context/context.ts +3 -3
  35. package/src/context/session.ts +2 -2
  36. package/src/context/types.ts +2 -2
  37. package/src/i18n/en.ts +29 -7
  38. package/src/i18n/zh-CN.ts +24 -2
  39. package/src/report/aggregate.ts +79 -50
  40. package/src/report/components.tsx +190 -0
  41. package/src/report/compute.ts +149 -84
  42. package/src/report/default-report.tsx +222 -0
  43. package/src/report/dual-face.test.tsx +527 -0
  44. package/src/report/format.ts +29 -0
  45. package/src/report/index.ts +79 -24
  46. package/src/report/load.ts +67 -0
  47. package/src/report/metrics.ts +2 -1
  48. package/src/report/param.ts +18 -0
  49. package/src/report/primitives.tsx +117 -0
  50. package/src/report/react/CaseList.tsx +2 -2
  51. package/src/report/react/DeltaTable.tsx +2 -2
  52. package/src/report/react/MetricBars.tsx +109 -0
  53. package/src/report/react/MetricLine.tsx +200 -0
  54. package/src/report/react/MetricMatrix.tsx +1 -1
  55. package/src/report/react/MetricScatter.tsx +1 -1
  56. package/src/report/react/MetricTable.tsx +1 -1
  57. package/src/report/react/RunOverview.tsx +4 -4
  58. package/src/report/react/Scoreboard.tsx +3 -3
  59. package/src/report/react/cell.tsx +1 -1
  60. package/src/report/react/fixtures.ts +98 -36
  61. package/src/report/react/format.ts +3 -28
  62. package/src/report/react/index.tsx +41 -24
  63. package/src/report/react/render.test.tsx +8 -8
  64. package/src/report/react/styles.css +77 -0
  65. package/src/report/report.test.ts +248 -128
  66. package/src/report/report.ts +69 -0
  67. package/src/report/text/faces.ts +356 -0
  68. package/src/report/text/layout.ts +125 -0
  69. package/src/report/text/plot.ts +127 -0
  70. package/src/report/tree.ts +220 -0
  71. package/src/report/types.ts +75 -27
  72. package/src/report/web.ts +40 -0
  73. package/src/results/copy.ts +95 -41
  74. package/src/results/format.ts +12 -11
  75. package/src/results/index.ts +34 -17
  76. package/src/results/open.ts +201 -81
  77. package/src/results/results.test.ts +595 -191
  78. package/src/results/select.ts +107 -55
  79. package/src/results/types.ts +137 -45
  80. package/src/results/writer.ts +258 -0
  81. package/src/runner/attempt.ts +3 -3
  82. package/src/runner/fingerprint.ts +1 -1
  83. package/src/runner/reporters/artifacts.ts +38 -78
  84. package/src/runner/reporters/braintrust.test.ts +2 -2
  85. package/src/runner/reporters/braintrust.ts +3 -3
  86. package/src/runner/run.ts +1 -1
  87. package/src/runner/types.ts +18 -12
  88. package/src/show/compose.ts +215 -0
  89. package/src/show/index.ts +263 -0
  90. package/src/show/render.ts +456 -0
  91. package/src/show/show.test.ts +505 -0
  92. package/src/view/app/App.tsx +85 -24
  93. package/src/view/app/components/CostScoreChart.tsx +29 -10
  94. package/src/view/app/components/ExperimentTable.tsx +27 -9
  95. package/src/view/app/components/GroupSelector.tsx +1 -1
  96. package/src/view/app/i18n.ts +18 -3
  97. package/src/view/app/lib/attempt-route.test.ts +9 -9
  98. package/src/view/app/lib/attempt-route.ts +5 -5
  99. package/src/view/app/lib/outcome.ts +1 -3
  100. package/src/view/app/lib/rows.ts +95 -15
  101. package/src/view/app/main.tsx +18 -6
  102. package/src/view/app/pages/RunsPage.tsx +4 -7
  103. package/src/view/app/pages/TracesPage.tsx +3 -8
  104. package/src/view/app/types.ts +50 -6
  105. package/src/view/client-dist/app.css +1 -1
  106. package/src/view/client-dist/app.js +20 -20
  107. package/src/view/data.test.ts +254 -0
  108. package/src/view/data.ts +336 -0
  109. package/src/view/index.ts +64 -10
  110. package/src/view/server.ts +31 -9
  111. package/src/view/shared/types.ts +47 -41
  112. package/src/view/styles.css +5 -0
  113. package/src/view/template.html +1 -0
  114. package/src/view/view-report.test.ts +308 -0
  115. package/docs-site/.mintignore +0 -13
  116. package/docs-site/AGENTS.md +0 -46
  117. package/docs-site/concepts/adapter.mdx +0 -287
  118. package/docs-site/concepts/assert.mdx +0 -243
  119. package/docs-site/concepts/drive.mdx +0 -118
  120. package/docs-site/concepts/evals.mdx +0 -108
  121. package/docs-site/concepts/experiment.mdx +0 -37
  122. package/docs-site/concepts/hitl.mdx +0 -83
  123. package/docs-site/concepts/judge.mdx +0 -129
  124. package/docs-site/concepts/overview.mdx +0 -98
  125. package/docs-site/concepts/tier.mdx +0 -42
  126. package/docs-site/docs-ref/00-index.md +0 -23
  127. package/docs-site/docs-ref/01-page-types.md +0 -43
  128. package/docs-site/docs-ref/03-style-rules.md +0 -45
  129. package/docs-site/docs-ref/04-code-examples.md +0 -37
  130. package/docs-site/docs-ref/05-dx-failure-paths.md +0 -45
  131. package/docs-site/docs-ref/06-checklists.md +0 -53
  132. package/docs-site/docs.json +0 -256
  133. package/docs-site/example/ai-agent-application.mdx +0 -148
  134. package/docs-site/example/claude-code-codex-plugin.mdx +0 -162
  135. package/docs-site/example/claude-code-codex-skill.mdx +0 -147
  136. package/docs-site/example/showcase.mdx +0 -39
  137. package/docs-site/example/tier1-ai-sdk-v7.mdx +0 -136
  138. package/docs-site/example/tier1-claude-sdk.mdx +0 -119
  139. package/docs-site/example/tier1-codex-sdk.mdx +0 -111
  140. package/docs-site/example/tier1-langgraph.mdx +0 -119
  141. package/docs-site/example/tier1-pi-sdk.mdx +0 -119
  142. package/docs-site/favicon.svg +0 -5
  143. package/docs-site/github-diff.css +0 -135
  144. package/docs-site/github-diff.js +0 -11
  145. package/docs-site/guides/authoring.mdx +0 -129
  146. package/docs-site/guides/ci-integration.mdx +0 -97
  147. package/docs-site/guides/connect-otel.mdx +0 -209
  148. package/docs-site/guides/connect-your-agent.mdx +0 -221
  149. package/docs-site/guides/dataset-fanout.mdx +0 -98
  150. package/docs-site/guides/experiments.mdx +0 -71
  151. package/docs-site/guides/fixtures.mdx +0 -147
  152. package/docs-site/guides/reporters.mdx +0 -113
  153. package/docs-site/guides/runner.mdx +0 -79
  154. package/docs-site/guides/sandbox-agent.mdx +0 -138
  155. package/docs-site/guides/sandbox-backends.mdx +0 -64
  156. package/docs-site/guides/scoring-guide.mdx +0 -92
  157. package/docs-site/guides/viewing-results.mdx +0 -195
  158. package/docs-site/guides/write-experiment.mdx +0 -81
  159. package/docs-site/guides/write-send.mdx +0 -347
  160. package/docs-site/index.mdx +0 -181
  161. package/docs-site/introduction.mdx +0 -141
  162. package/docs-site/quickstart.mdx +0 -136
  163. package/docs-site/reference/builtin-agents.mdx +0 -161
  164. package/docs-site/reference/capabilities.mdx +0 -76
  165. package/docs-site/reference/cli.mdx +0 -120
  166. package/docs-site/reference/define-agent.mdx +0 -168
  167. package/docs-site/reference/define-config.mdx +0 -41
  168. package/docs-site/reference/define-eval.mdx +0 -160
  169. package/docs-site/reference/events.mdx +0 -131
  170. package/docs-site/reference/expect.mdx +0 -112
  171. package/docs-site/tracker.js +0 -8
  172. package/src/report/react/data.ts +0 -17
  173. package/src/view/aggregate.ts +0 -103
  174. package/src/view/loader.test.ts +0 -61
  175. 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).