niceeval 0.5.2 → 0.5.4
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 +11 -6
- package/docs-site/zh/concepts/experiment.mdx +8 -6
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/ci-integration.mdx +2 -0
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +13 -13
- package/docs-site/zh/guides/experiments.mdx +4 -3
- package/docs-site/zh/guides/publish-report.mdx +91 -0
- package/docs-site/zh/guides/report-components.mdx +4 -4
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +2 -2
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +11 -6
- package/docs-site/zh/guides/sandbox-backends.mdx +27 -0
- package/docs-site/zh/guides/viewing-results.mdx +5 -21
- package/docs-site/zh/guides/write-experiment.mdx +24 -10
- 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/builtin-agents.mdx +4 -0
- package/docs-site/zh/reference/cli.mdx +9 -6
- package/docs-site/zh/reference/define-agent.mdx +19 -3
- package/docs-site/zh/reference/define-config.mdx +3 -1
- package/docs-site/zh/reference/define-eval.mdx +9 -3
- package/docs-site/zh/reference/events.mdx +4 -0
- package/docs-site/zh/reference/expect.mdx +4 -0
- 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 +5 -1
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/index.ts +2 -0
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +10 -2
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +38 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +6 -3
- package/src/context/session.ts +5 -2
- package/src/context/types.ts +2 -2
- package/src/define.test.ts +82 -0
- package/src/define.ts +68 -7
- package/src/i18n/en.ts +4 -2
- package/src/i18n/zh-CN.ts +4 -2
- package/src/report/aggregate.ts +16 -16
- package/src/report/components.tsx +1 -1
- package/src/report/compute.ts +7 -7
- package/src/report/dual-face.test.tsx +1 -1
- package/src/report/{param.ts → flag.ts} +6 -6
- package/src/report/index.ts +3 -3
- package/src/report/react/MetricLine.tsx +1 -1
- package/src/report/react/fixtures.ts +1 -1
- package/src/report/report.test.ts +17 -17
- package/src/report/report.ts +1 -1
- package/src/report/types.ts +11 -11
- package/src/results/results.test.ts +2 -2
- package/src/runner/attempt.ts +53 -6
- package/src/runner/fingerprint.ts +1 -1
- 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 +8 -8
- package/src/sandbox/types.ts +45 -4
- package/src/show/compose.ts +1 -1
- package/src/view/app/App.tsx +1 -1
- package/src/view/app/css.d.ts +3 -0
- package/src/view/app/i18n.ts +3 -3
- package/src/view/app/lib/rows.ts +5 -5
- package/src/view/app/types.ts +1 -1
- package/src/view/client-dist/app.js +2 -2
- package/src/view/data.test.ts +26 -2
- package/src/view/data.ts +28 -4
- package/src/view/shared/types.ts +1 -1
|
@@ -19,7 +19,7 @@ description: "Adapter 是你写的适配器。本文讲清楚 send 函数传入
|
|
|
19
19
|
|
|
20
20
|
回程:Adapter 把应用的原始返回翻译成 `Turn`——其中 `events` 是一个按发生顺序排列的事件对象数组(下文有实际值)
|
|
21
21
|
|
|
22
|
-
被测系统的 URL、鉴权、协议细节通过 Adapter 工厂的配置传入。`defineExperiment` 里放的是已经配置好的 agent 实例;Adapter 在 `send` 闭包里消费这些配置,再把每轮动态值(如 `ctx.model`、`ctx.
|
|
22
|
+
被测系统的 URL、鉴权、协议细节通过 Adapter 工厂的配置传入。`defineExperiment` 里放的是已经配置好的 agent 实例;Adapter 在 `send` 闭包里消费这些配置,再把每轮动态值(如 `ctx.model`、`ctx.flags`、`ctx.telemetry`)随请求转发给应用:
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
// agents/web-agent.ts
|
|
@@ -48,7 +48,7 @@ export function webAgent(options: WebAgentOptions): Agent {
|
|
|
48
48
|
message: input.text,
|
|
49
49
|
files: input.files,
|
|
50
50
|
model: ctx.model,
|
|
51
|
-
|
|
51
|
+
flags: ctx.flags,
|
|
52
52
|
}),
|
|
53
53
|
signal: ctx.signal,
|
|
54
54
|
});
|
|
@@ -81,13 +81,13 @@ export default defineExperiment({
|
|
|
81
81
|
apiKey: process.env.STAGING_AGENT_API_KEY,
|
|
82
82
|
}),
|
|
83
83
|
model: "gpt-5.4",
|
|
84
|
-
|
|
84
|
+
flags: { promptVariant: "concise" },
|
|
85
85
|
});
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
## 接入 Tier
|
|
89
89
|
|
|
90
|
-
按「Adapter 接到哪里、额外拿到什么观测数据」,接入分三档:**Tier 1 只接 send**(应用代码一行不改,全套断言在这一档就齐了)、**Tier 2 send + OTel**(应用把 span 发给 [NiceEval](https://niceeval.com/),换 `niceeval view` 的调用瀑布图)、**Tier 3 侵入改造 + experiment
|
|
90
|
+
按「Adapter 接到哪里、额外拿到什么观测数据」,接入分三档:**Tier 1 只接 send**(应用代码一行不改,全套断言在这一档就齐了)、**Tier 2 send + OTel**(应用把 span 发给 [NiceEval](https://niceeval.com/),换 `niceeval view` 的调用瀑布图)、**Tier 3 侵入改造 + experiment flags**(feature A/B)。每档投入什么、买到什么、什么时候升级,见 [Tier](/zh/concepts/tier)。
|
|
91
91
|
|
|
92
92
|
eval 侧的驱动 API——`t.send()`、`t.sendFile()`、`t.newSession()`、HITL 的 `t.respond()` / `t.respondAll()`
|
|
93
93
|
统一都是调用 Adapter 的 `send`。
|
|
@@ -109,6 +109,10 @@ interface Agent {
|
|
|
109
109
|
|
|
110
110
|
`send` 是唯一要实现的函数。它的签名里只有三个类型,分开看。
|
|
111
111
|
|
|
112
|
+
<Note>
|
|
113
|
+
这里的 `setup` / `teardown` 是 Agent 自己"怎么连自己"的私事(装 CLI、写鉴权配置)。按实验变化的环境准备(装某个实验专属的二进制、预热、跨 attempt 保存状态)不写在 Agent 上,挂在 `sandbox` 字段那个 Sandbox spec 自己的 `.setup()` / `.teardown()` 链式方法上,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
|
|
114
|
+
</Note>
|
|
115
|
+
|
|
112
116
|
### 传入:`TurnInput`
|
|
113
117
|
|
|
114
118
|
```ts
|
|
@@ -187,7 +191,8 @@ interface AgentContext {
|
|
|
187
191
|
readonly model?: string; // Tier 1 的模型对比:experiment.model 透传;应用接口收模型选择就转发
|
|
188
192
|
readonly reasoningEffort?: string; // 推理努力程度:experiment.reasoningEffort 透传,归属与 model 一致
|
|
189
193
|
readonly telemetry?: Telemetry; // Tier 2:本轮的 W3C trace context(headers,每轮一个新 traceparent)
|
|
190
|
-
readonly
|
|
194
|
+
readonly flags: Readonly<Record<string, unknown>>; // Tier 3 的 feature A/B:experiment.flags 透传;没配时是 {}
|
|
195
|
+
readonly experimentId?: string; // 路径推导的实验 id,与结果里的 experimentId 同源;不经 experiment 跑时是 undefined
|
|
191
196
|
|
|
192
197
|
readonly sandbox?: Sandbox; // 仅 defineSandboxAgent 构造的 Agent 会拿到
|
|
193
198
|
}
|
|
@@ -209,7 +214,7 @@ interface AgentSession {
|
|
|
209
214
|
}
|
|
210
215
|
```
|
|
211
216
|
|
|
212
|
-
`ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`
|
|
217
|
+
`ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`flags` 由 experiment 声明、运行器原样递过来,Adapter 只负责随请求转发给应用,不解释它们的含义;`telemetry` 仅在配置了 OTel 接入时出现,`send` 里只需要把 `headers` spread 进请求头——接收端点在 `defineConfig` 里固定、由应用启动时指向,不从这里传,见[OTel 接入](/zh/guides/connect-otel)。`experimentId` 是路径推导出的稳定标识,典型用途是在 Sandbox 的环境钩子里按实验隔离跨 attempt 的状态(缓存目录名、快照 tag 按它分区),见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
|
|
213
218
|
|
|
214
219
|
`session` 是本条会话线的自有状态,[NiceEval](https://niceeval.com/) 对它只承诺一件事:**同一条会话线的每次 `send` 拿到同一个 `ctx.session`,新会话线(eval 的第一轮,或 `t.newSession()` 之后)拿到一个全新的。** 会话续接(`id`/`capture`、`history`)和 HITL 停轮现场(`hold`/`take`)的存取器都在它上面,"第一轮"就是新会话线的自然形态——`id` 是 `undefined`、`history.get()` 是空数组,没有要判断的分支;`state` 是这些存取器之外的逃生舱,框架从不往里写数据。
|
|
215
220
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "实验(Experiment):评谁、怎么跑"
|
|
3
3
|
sidebarTitle: "实验"
|
|
4
|
-
description: "Experiment 是可签入的运行配置:同一批 eval 对着哪个 agent
|
|
4
|
+
description: "Experiment 是可签入的运行配置:同一批 eval 对着哪个 agent、哪个模型、开哪些 flags、跑几次。eval 与 experiment 分开的原因——晚绑定,让同一份 eval 换着对象跑。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
**experiment** 对着哪个 agent、用哪个模型、开哪些
|
|
7
|
+
**experiment** 对着哪个 agent、用哪个模型、开哪些 flags、跑几次。用于做 AB 测试、模型对比、feature 对比——`flags` 就是 A/B 测试里的 feature flag,一个 experiment 是一组 flag 取值。
|
|
8
8
|
|
|
9
9
|
## 一个 experiment 里有什么
|
|
10
10
|
|
|
@@ -16,15 +16,17 @@ export default defineExperiment({
|
|
|
16
16
|
description: "简洁 prompt 变体",
|
|
17
17
|
agent: webAgent({ baseUrl: "https://staging.example.com" }), // 已配置好的 agent 实例
|
|
18
18
|
model: "gpt-5.4", // 经 ctx.model 透传给 Adapter
|
|
19
|
-
|
|
19
|
+
flags: { promptVariant: "concise" }, // 经 ctx.flags 透传;eval 里是 t.flags
|
|
20
20
|
runs: 3, // 每条 eval 跑几次(对抗随机性)
|
|
21
21
|
});
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
- `agent`:评谁。放的是已经配置好的实例——被测系统的 URL、鉴权传给 Adapter 工厂,不进 experiment 的其它字段。
|
|
25
|
-
- `model` / `
|
|
25
|
+
- `model` / `flags`:透传语义。[NiceEval](https://niceeval.com/) 不解释它们的含义,原样经 `ctx` 递给 Adapter,由 Adapter 随请求转发、应用按需切换——这正是 [Tier](/zh/concepts/tier) 里模型对比(Tier 1)和 feature A/B(Tier 3)的通道。
|
|
26
26
|
- `runs`、`budget`、并发、`sandbox` 等运行参数:怎么跑、跑多少。完整字段见[写实验](/zh/guides/write-experiment)。
|
|
27
27
|
|
|
28
|
+
Experiment 是纯配置数据,没有 `setup` / `teardown` 这类生命周期字段。要按实验准备环境(装二进制、预热、跨 attempt 存取状态),挂在 `sandbox` 字段的 spec 上——`dockerSandbox()` 等工厂返回的对象可以链 `.setup()` / `.teardown()`,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
|
|
29
|
+
|
|
28
30
|
## 矩阵对比
|
|
29
31
|
|
|
30
32
|
要比较的每个变体写一个 experiment 文件:两个模型就是两个文件,只差 `model` 一行;prompt A/B 就是只差一个参数。同一批 eval 在多个 experiment 下各跑一遍,pass rate、成本、延迟就有了可比的横截面——`niceeval view` 里叠着看。适合比什么、结果怎么读,见[实验矩阵](/zh/guides/experiments)。
|
|
@@ -32,6 +34,6 @@ export default defineExperiment({
|
|
|
32
34
|
## 相关阅读
|
|
33
35
|
|
|
34
36
|
- [写实验](/zh/guides/write-experiment) — `defineExperiment` 的完整字段:runs、预算、并发与 sandbox。
|
|
35
|
-
- [实验矩阵](/zh/guides/experiments) — 跨 agent / model /
|
|
37
|
+
- [实验矩阵](/zh/guides/experiments) — 跨 agent / model / flags 的对比怎么组织、怎么读结果。
|
|
36
38
|
- [评估](/zh/concepts/evals) — 另一半:eval 是什么、生命周期与 outcome。
|
|
37
|
-
- [Tier](/zh/concepts/tier) — `model` / `
|
|
39
|
+
- [Tier](/zh/concepts/tier) — `model` / `flags` 各在哪一档生效。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "接入 Tier:三档投入,三组能力"
|
|
3
3
|
sidebarTitle: "Tier"
|
|
4
|
-
description: "按「Adapter 接到哪里、额外拿到什么观测数据」接入分三档:Tier 1 只接 send,Tier 2 加 OTel,Tier 3 侵入改造暴露 experiment
|
|
4
|
+
description: "按「Adapter 接到哪里、额外拿到什么观测数据」接入分三档:Tier 1 只接 send,Tier 2 加 OTel,Tier 3 侵入改造暴露 experiment flags。每档买到什么、什么时候升级。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
接入 [NiceEval](https://niceeval.com/) 不是全有或全无的一次性工程。按「Adapter 接到哪里、额外拿到什么观测数据」,接入分三档——每一档在上一档之上加一种投入、换一组新能力,已写的 eval 全程复用,不用改。
|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
## Tier 1:只接 send
|
|
12
12
|
|
|
@@ -22,9 +22,9 @@ description: "按「Adapter 接到哪里、额外拿到什么观测数据」接
|
|
|
22
22
|
|
|
23
23
|
这一档买到的是**观测**:`niceeval view` 的调用瀑布图——应用内部每次模型调用、每次工具执行、各自的耗时与 token,按轮铺成时间线。断言不受影响:span 只进瀑布图,不进事件流、不喂断言。走法见[OTel 接入](/zh/guides/connect-otel)。
|
|
24
24
|
|
|
25
|
-
## Tier 3:侵入改造 + experiment
|
|
25
|
+
## Tier 3:侵入改造 + experiment flags
|
|
26
26
|
|
|
27
|
-
要评「改 prompt / 换工具集 / 开关某个 feature 谁更好」这类 **feature A/B test** 时,对照点在应用内部,前两档拿不到。这一档改应用内部代码,把变体暴露成外部可选的配置:experiment 的 `
|
|
27
|
+
要评「改 prompt / 换工具集 / 开关某个 feature 谁更好」这类 **feature A/B test** 时,对照点在应用内部,前两档拿不到。这一档改应用内部代码,把变体暴露成外部可选的配置:experiment 的 `flags` 经 `ctx.flags` 透传给 Adapter,Adapter 随请求交给应用(HTTP header、请求体字段、环境变量都行),应用按参数切换变体。
|
|
28
28
|
|
|
29
29
|
注意侵入的是应用(把变体暴露成配置),不是接入面——Adapter 本身不变,依然只经对外接口调用应用。
|
|
30
30
|
|
|
@@ -39,6 +39,6 @@ description: "按「Adapter 接到哪里、额外拿到什么观测数据」接
|
|
|
39
39
|
## 相关阅读
|
|
40
40
|
|
|
41
41
|
- [接入你的 Agent](/zh/guides/connect-your-agent) — 接入全景:最小接入与参数通道。
|
|
42
|
-
- [Adapter](/zh/concepts/adapter) — 契约本身:`send` 传入什么返回什么,`ctx.model` / `ctx.telemetry` / `ctx.
|
|
42
|
+
- [Adapter](/zh/concepts/adapter) — 契约本身:`send` 传入什么返回什么,`ctx.model` / `ctx.telemetry` / `ctx.flags` 按档位出现。
|
|
43
43
|
- [OTel 接入](/zh/guides/connect-otel) — Tier 2 的完整走法。
|
|
44
|
-
- [实验](/zh/concepts/experiment) — model /
|
|
44
|
+
- [实验](/zh/concepts/experiment) — model / flags 对比在 experiment 侧怎么声明。
|
|
@@ -116,9 +116,9 @@ npx niceeval view # 本地查看器里看结果
|
|
|
116
116
|
配置归属只有两条通道,分清它们,接入就不会乱:
|
|
117
117
|
|
|
118
118
|
1. **静态配置走 Adapter 工厂。** URL、鉴权、协议细节这类"这个环境长什么样"的配置,作为工厂参数写在 experiment 文件里——`defineExperiment` 的 `agent` 字段放的是**已经配置好的实例**。
|
|
119
|
-
2. **每轮动态值走 `ctx`。** experiment 声明的 `model`、`
|
|
119
|
+
2. **每轮动态值走 `ctx`。** experiment 声明的 `model`、`flags`,运行器每轮经 `ctx` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发给应用。
|
|
120
120
|
|
|
121
|
-
两条通道拼在一起,就是把第一步的 my-bot 从「写死 URL 的实例」改成「收配置的工厂」:default export 换成一个返回 `defineAgent(...)` 的函数,`send` 里写死的地方换成读工厂参数;experiment 声明的 `model`、`
|
|
121
|
+
两条通道拼在一起,就是把第一步的 my-bot 从「写死 URL 的实例」改成「收配置的工厂」:default export 换成一个返回 `defineAgent(...)` 的函数,`send` 里写死的地方换成读工厂参数;experiment 声明的 `model`、`flags` 每轮从 `ctx` 到达,`send` 里随请求转发:
|
|
122
122
|
|
|
123
123
|
```ts
|
|
124
124
|
// agents/my-bot.ts —— 工厂:静态配置进闭包
|
|
@@ -135,7 +135,7 @@ export function myBot(options: { baseUrl: string }): Agent {
|
|
|
135
135
|
body: JSON.stringify({
|
|
136
136
|
message: input.text,
|
|
137
137
|
model: ctx.model, // ← experiment.model:应用接口收模型选择就转发
|
|
138
|
-
|
|
138
|
+
flags: ctx.flags, // ← experiment.flags:原样交给应用,应用按参数切换
|
|
139
139
|
}),
|
|
140
140
|
signal: ctx.signal, // ← 运行器的超时与取消:挂到每个请求上
|
|
141
141
|
});
|
|
@@ -160,7 +160,7 @@ export default defineExperiment({
|
|
|
160
160
|
description: "my-bot 基线",
|
|
161
161
|
agent: myBot({ baseUrl: "http://localhost:3000" }), // ← 静态配置:实例化时传
|
|
162
162
|
model: "gpt-5.4", // ← 动态值:经 ctx.model 到 send
|
|
163
|
-
|
|
163
|
+
flags: { promptVariant: "concise" }, // ← 动态值:经 ctx.flags 到 send
|
|
164
164
|
});
|
|
165
165
|
```
|
|
166
166
|
|
|
@@ -170,7 +170,7 @@ export default defineExperiment({
|
|
|
170
170
|
|---|---|---|
|
|
171
171
|
| `signal` | 运行器(超时与取消) | 挂到发出的每个请求上 |
|
|
172
172
|
| `model` | experiment 的 `model` | 应用接口收模型选择就随请求转发;不收就忽略 |
|
|
173
|
-
| `
|
|
173
|
+
| `flags` | experiment 的 `flags` | 原样转发(请求体、header 都行),应用按参数切换变体 |
|
|
174
174
|
| `telemetry` | 配置了 OTel 接入时出现 | 只碰 `headers`:每轮新的 W3C `traceparent`,spread 进请求头。接收端点每次运行都一样,在 `defineConfig` 里固定、应用启动时指向它,不从 send 传——见 [OTel 接入](/zh/guides/connect-otel) |
|
|
175
175
|
| `session` | 运行器(每条会话线一份) | 会话续接与 HITL 停轮现场的存取器都在它上面:`history()`、`id` / `capture()`、`hold()` / `take()`,见[写 send](/zh/guides/write-send) |
|
|
176
176
|
| `log(msg)` | 运行器 | 写进本轮日志,`niceeval view` 里可见 |
|
|
@@ -208,7 +208,7 @@ npx niceeval exp prod
|
|
|
208
208
|
| 多轮对话、`t.newSession()` 隔离 | 接上 `ctx.session`:`history()` 或 `id` + `capture()` | [写 send](/zh/guides/write-send) |
|
|
209
209
|
| 审批流(HITL,人工介入) | 停轮返回 `waiting` + `input.requested`,回答轮续跑 | [HITL](/zh/concepts/hitl) |
|
|
210
210
|
| `niceeval view` 的调用瀑布图 | 应用把 OTel span 发给 NiceEval(不影响断言) | [OTel 接入](/zh/guides/connect-otel) |
|
|
211
|
-
| feature A/B 对比 | 应用把变体暴露成 `
|
|
211
|
+
| feature A/B 对比 | 应用把变体暴露成 `flags` 可切换的配置 | [Tier](/zh/concepts/tier)、[写实验](/zh/guides/write-experiment) |
|
|
212
212
|
|
|
213
213
|
这些增量落在哪一档、各档买到什么,见 [Tier](/zh/concepts/tier)。
|
|
214
214
|
|
|
@@ -219,7 +219,7 @@ npx niceeval exp prod
|
|
|
219
219
|
## 相关阅读
|
|
220
220
|
|
|
221
221
|
- [官方适配器一览](/zh/guides/official-adapters) —— 被测对象能直接用官方 Adapter 时,Sandbox / 非 Sandbox 各有哪些、配置项怎么写。
|
|
222
|
-
- [写 send](/zh/guides/write-send) —— 手写 Adapter 的完整教程:七步递进,从发一条消息到 HITL、OTel、
|
|
222
|
+
- [写 send](/zh/guides/write-send) —— 手写 Adapter 的完整教程:七步递进,从发一条消息到 HITL、OTel、flags。
|
|
223
223
|
- [Adapter](/zh/concepts/adapter) —— `send` 的契约:`TurnInput` / `AgentContext` / `Turn` 逐字段。
|
|
224
224
|
- [OTel 接入](/zh/guides/connect-otel) —— 把应用的 span 也发给 [NiceEval](https://niceeval.com/),换 `niceeval view` 的调用瀑布图。
|
|
225
225
|
- [Tier](/zh/concepts/tier) —— 三档接入各投入什么、买到什么。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "用报告积木搭自己的口径"
|
|
3
3
|
sidebarTitle: "自定义报告"
|
|
4
|
-
description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,Row / Col 负责排版,双面组件同时渲染网页与终端——niceeval show 与 niceeval view
|
|
4
|
+
description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,Row / Col 负责排版,双面组件同时渲染网页与终端——niceeval show 与 niceeval view 默认渲染的默认报告,就是内置的那份报告文件。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
[查看结果](/zh/guides/viewing-results)讲「用」:官方两扇门 `niceeval show`(终端)和 `niceeval view`(网页)怎么看。本页讲「写」:官方摆法不够时,怎么写一份自己的报告——考试成绩单、代码行数榜、质量 × 成本 frontier。
|
|
@@ -10,7 +10,7 @@ description: "一份报告就是一个报告文件:官方宿主打开结果、
|
|
|
10
10
|
|
|
11
11
|
## 官方默认报告也是一份报告文件
|
|
12
12
|
|
|
13
|
-
`niceeval view` 和 `niceeval show`
|
|
13
|
+
`niceeval view` 和 `niceeval show` 不传 `--report` 时渲染的默认报告不是私有实现,而是内置的一份报告文件:`niceeval view` 就等于 `niceeval view --report <内置默认报告>`,`show` 同理。我们自己用的零件和给你的完全一样,没有私有数据通道,默认报告上的每个数字你都能用同样几行代码原样重算出来。
|
|
14
14
|
|
|
15
15
|
| | 网页(人看) | 终端(agent 和你看) |
|
|
16
16
|
| --- | --- | --- |
|
|
@@ -20,7 +20,7 @@ description: "一份报告就是一个报告文件:官方宿主打开结果、
|
|
|
20
20
|
四个格子是同一个宿主。自定义只有三个层次,逐层深入:
|
|
21
21
|
|
|
22
22
|
1. **换摆法**:用官方组件和 `Row` / `Col` 重摆版面——大多数「要自己的口径」到这层就够了。
|
|
23
|
-
2. **换口径**:`defineMetric` 定义自己的指标,`
|
|
23
|
+
2. **换口径**:`defineMetric` 定义自己的指标,`flag()` 把 experiment 声明的变量当维度和轴。
|
|
24
24
|
3. **换形态**:官方组件表达不了的展示,用 `defineComponent` 写自己的组件——网页怎么渲染、终端字符怎么排,两个面你都说了算。
|
|
25
25
|
|
|
26
26
|
## 一份报告 = 一个报告文件
|
|
@@ -52,15 +52,15 @@ niceeval show --report reports/exam.tsx # 终端:同一棵树走文本面
|
|
|
52
52
|
niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面,attempt 深链直达证据室
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 就是 `results.latest()`
|
|
55
|
+
注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 就是 `results.latest()` 的返回选集——宿主按「每个实验取最新」挑好的现刻水位,与默认报告同口径,快照在 `selection.snapshots`,挑选警告在 `selection.warnings`。组件的 `data` 函数直接吃选集,警告随行——`RunOverview` 会把残缺快照的警告如实显示,不用你另外接线;默认挑法不合口径时,拿 `results` 自己挑,手工挑的 `Snapshot[]` 数组同样能喂给每个 `data` 函数。
|
|
56
56
|
|
|
57
|
-
命令行的范围先作用在挑选上,报告拿到的就是范围内的水位:位置参数的 eval id 前缀收窄选集覆盖的 eval(覆盖警告的分母同样收窄到范围内),`--run` 把结果根换成指定目录,`--experiment` 让选集只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--transcript` / `--trace` / `--diff
|
|
57
|
+
命令行的范围先作用在挑选上,报告拿到的就是范围内的水位:位置参数的 eval id 前缀收窄选集覆盖的 eval(覆盖警告的分母同样收窄到范围内),`--run` 把结果根换成指定目录,`--experiment` 让选集只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--transcript` / `--trace` / `--diff`)只看证据,不渲染报告。
|
|
58
58
|
|
|
59
59
|
`<DefaultReport />` 零 props、纯声明:官方水位只读瘦身条目(不碰 diff 这类重工件),宿主总是替你备好,摆不摆在你。它渲染的是**宿主注入的那份选集**的官方水位——跟随命令行范围,但不跟随你在报告里自己另挑的口径;这正是它的意义:当官方锚点,和你的口径并排对照。要「官方摆法 + 自选口径」,用它脚下的同一批组件自己摆——它没有任何私有零件。
|
|
60
60
|
|
|
61
61
|
页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。你在报告文件里只见到组件本身,从不碰某一面——走哪扇门由宿主决定。官方组件覆盖常见摆法——榜单 `MetricTable`、逐题格子 `MetricMatrix`、分组条形 `MetricBars`、成绩单 `Scoreboard`、质量 × 成本 frontier `MetricScatter`、趋势线 `MetricLine`、成对对比 `DeltaTable`、失败清单 `CaseList`——每个组件回答什么问题、怎么调用、终端字符输出长什么样,逐个见[报告组件](/zh/guides/report-components)。它们在宿主里自动接上证据室:网页面的格子深链到 attempt 弹窗,终端面在条目下印出 `niceeval show <eval id>` 下钻命令。
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
默认报告没有特权:`<DefaultReport />` 只是不传 `--report` 时渲染的那份内置报告,本身也是一个普通组件。「官方水位 + 自己的口径」像上面那样把它摆进自己的树;不要官方水位就不摆——组合发生在你的 tsx 里,没有 tab、没有注册表、没有配置。
|
|
64
64
|
|
|
65
65
|
## 排版:Row 与 Col 在两个面都成立
|
|
66
66
|
|
|
@@ -90,7 +90,7 @@ codex 78.0/100 40/50 38/50 │ codex 80% $0.51
|
|
|
90
90
|
|
|
91
91
|
## 换口径:自定义指标
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
默认报告里那张逐实验指标表的口径拆开就是 `MetricTable.data(selection, { rows: "experiment", columns: [passRate, costUSD, durationMs] })`——每个组件的数据算法挂在它自己身上,组件名后打个点就找到;换口径只是把 columns 换成自己的指标:
|
|
94
94
|
|
|
95
95
|
```tsx
|
|
96
96
|
// reports/golf.tsx —— code-golf:谁写出能用的代码,谁写得短
|
|
@@ -138,7 +138,7 @@ codex 80% 355 lines $0.51
|
|
|
138
138
|
|
|
139
139
|
## 变量来自配置,不来自命名
|
|
140
140
|
|
|
141
|
-
画「并行 agent 数 × 模拟延迟 × 得分」这类 scaling 趋势时,图上的变量不该编码进 experiment id(`ultra-16agents-300ms`),再在报告里解析字符串抠出来——那是约定不是配置,改个命名整张图就散。变量的家是 experiment 文件的 `
|
|
141
|
+
画「并行 agent 数 × 模拟延迟 × 得分」这类 scaling 趋势时,图上的变量不该编码进 experiment id(`ultra-16agents-300ms`),再在报告里解析字符串抠出来——那是约定不是配置,改个命名整张图就散。变量的家是 experiment 文件的 `flags`:一文件一个档位,把变量声明成数据:
|
|
142
142
|
|
|
143
143
|
```ts
|
|
144
144
|
// experiments/ultra/agents-16.ts
|
|
@@ -147,21 +147,21 @@ import { bub } from "../../agents/bub.ts";
|
|
|
147
147
|
|
|
148
148
|
export default defineExperiment({
|
|
149
149
|
agent: bub({ mode: "ultra" }),
|
|
150
|
-
|
|
150
|
+
flags: { agents: 16, latencyMs: 300 },
|
|
151
151
|
});
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
报告侧用 `
|
|
154
|
+
报告侧用 `flag()` 把声明的值直接当维度或轴用,不解析任何名字:
|
|
155
155
|
|
|
156
156
|
```tsx
|
|
157
157
|
<MetricLine data={await MetricLine.data(selection, {
|
|
158
|
-
x:
|
|
159
|
-
series:
|
|
158
|
+
x: flag("latencyMs", { label: "Simulated latency", unit: "ms" }),
|
|
159
|
+
series: flag("agents", { label: (v) => `${v} agents` }),
|
|
160
160
|
y: passRate,
|
|
161
161
|
})} />
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
`
|
|
164
|
+
`flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度;内置维度(`agent`、`model`、`experiment`、`evalGroup`……)覆盖不了的变量都走它。没有声明该 flag 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。flags 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
|
|
165
165
|
|
|
166
166
|
## 换形态:自定义组件,一个定义两个面
|
|
167
167
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "实验矩阵:用运行矩阵比较 agents 和 models"
|
|
3
3
|
sidebarTitle: "实验矩阵"
|
|
4
|
-
description: "使用 NiceEval experiments 让同一批 eval 横跨多个 agents、models 和
|
|
4
|
+
description: "使用 NiceEval experiments 让同一批 eval 横跨多个 agents、models 和 flags,比较 pass rate、成本和延迟。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Experiment 用于比较多组运行配置。典型问题包括:Claude Code 和 Codex 谁在同一批 coding-agent 任务上 pass rate 更高?某个 prompt 改动是否降低成本?不同模型的延迟和质量如何取舍?
|
|
@@ -37,14 +37,15 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
|
|
|
37
37
|
|
|
38
38
|
这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。
|
|
39
39
|
|
|
40
|
-
`defineExperiment` 的字段怎么写、`
|
|
40
|
+
`defineExperiment` 的字段怎么写、`flags` 怎么透传到 adapter,见[写实验](/zh/guides/write-experiment)。
|
|
41
41
|
|
|
42
42
|
## 适合比较什么
|
|
43
43
|
|
|
44
44
|
- 不同 Adapters。
|
|
45
45
|
- 不同模型(Tier 1 接入即可:应用接口暴露模型选择,`model` 经 `ctx.model` 透传)。
|
|
46
|
-
- 不同 prompts 或 feature
|
|
46
|
+
- 不同 prompts 或 feature flags(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的 flag,经 `flags` → `ctx.flags` 透传)。
|
|
47
47
|
- 不同 sandbox 后端。
|
|
48
|
+
- 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
|
|
48
49
|
- 同一任务的 pass@N。
|
|
49
50
|
|
|
50
51
|
Tier 1 / Tier 2 / Tier 3 的定义见 [Tier](/zh/concepts/tier)。
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "通过 CI 发布报告"
|
|
3
|
+
sidebarTitle: "CI 发布报告"
|
|
4
|
+
description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一行 view --out,push 即发布。发布的站和本地 niceeval view 完全一样;零可读结果时构建失败,保住上一次部署。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`niceeval view --out <目录>` 把查看器导出成一个纯静态目录:报告、快照列表、transcript、trace 瀑布,和本地 `niceeval view` 看到的完全一样(导出行为见[查看结果 · 导出与静态托管](/zh/guides/viewing-results#导出与静态托管))。CI 发布只需要让结果数据到构建机手里——把 `.niceeval/` 提交进仓库,仓库本身就是数据源。本地跑完 eval,提交并 push 就是发布,不需要任何脚本或本地构建命令。
|
|
8
|
+
|
|
9
|
+
## `.gitignore` 只排除 `diff.json`
|
|
10
|
+
|
|
11
|
+
```gitignore
|
|
12
|
+
# .niceeval/ 提交进仓库:它是线上报告的数据源,CI 构建时直接从这里生成整站。
|
|
13
|
+
# 只排除 diff.json —— 单轮可达上百 MB,查看器也不读它。
|
|
14
|
+
.niceeval/**/diff.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 构建命令就是导出命令
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx niceeval view --out site
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`.niceeval/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages 保留上一次部署。错误逐条列出被跳过的 run 目录与原因,schemaVersion 场景还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
|
|
24
|
+
|
|
25
|
+
## 发布自定义报告
|
|
26
|
+
|
|
27
|
+
不传 `--report` 时,发布出来的首页是默认报告。想让首页换成自己的报告,把 [`defineReport` 报告文件](/zh/guides/custom-reports)传给 `--report` 就行——attempt 证据页(transcript、trace、代码视图)仍在同一个站里,报告里的每个数字点进去就是对应证据,和本地 `view --report` 看到的一模一样:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx niceeval view --report reports/exam.tsx --out site
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
报告文件和 `.niceeval/` 一样提交在仓库里,改完版面 push,线上就跟着更新。用下面的 `vercel.json` / workflow 时,把构建命令换成这一行即可,其余配置不用动。
|
|
34
|
+
|
|
35
|
+
## 接托管平台
|
|
36
|
+
|
|
37
|
+
**Vercel**:仓库根放一个 `vercel.json`,导入项目后 push 即部署。
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"installCommand": "pnpm install --frozen-lockfile",
|
|
42
|
+
"buildCommand": "npx niceeval view --out site",
|
|
43
|
+
"outputDirectory": "site"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**GitHub Pages**:仓库 Settings → Pages 把 Source 设为 GitHub Actions,再加 workflow:
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
# .github/workflows/report.yml
|
|
51
|
+
name: report
|
|
52
|
+
on:
|
|
53
|
+
push:
|
|
54
|
+
branches: [main]
|
|
55
|
+
|
|
56
|
+
permissions:
|
|
57
|
+
contents: read
|
|
58
|
+
pages: write
|
|
59
|
+
id-token: write
|
|
60
|
+
|
|
61
|
+
jobs:
|
|
62
|
+
build:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: pnpm/action-setup@v4
|
|
67
|
+
- uses: actions/setup-node@v4
|
|
68
|
+
with:
|
|
69
|
+
node-version: 22
|
|
70
|
+
cache: pnpm
|
|
71
|
+
- run: pnpm install --frozen-lockfile
|
|
72
|
+
- run: npx niceeval view --out site
|
|
73
|
+
- uses: actions/upload-pages-artifact@v3
|
|
74
|
+
with:
|
|
75
|
+
path: site
|
|
76
|
+
deploy:
|
|
77
|
+
needs: build
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
environment:
|
|
80
|
+
name: github-pages
|
|
81
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
82
|
+
steps:
|
|
83
|
+
- id: deployment
|
|
84
|
+
uses: actions/deploy-pages@v4
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
日常循环从此只有两步:本地跑 eval,提交 `.niceeval/` 并 push。发布出去的站带完整历史——同事打开链接,看到的和你本地 `niceeval view` 一模一样;仓库、本地查看器、线上报告三处同一个真相源。仓库体积随落盘增长(`diff.json` 已排除,增长主要来自 transcript 和 trace 工件),不想保留的旧 run 直接删对应目录即可,查看器和报告跟着仓库走。
|
|
88
|
+
|
|
89
|
+
## 发布的是完整证据
|
|
90
|
+
|
|
91
|
+
整站导出带完整证据:transcript、源码快照、trace 原样进站点,不消毒。发布到公网前确认结果里没有密钥或敏感数据;自定义报告页的自由文本消毒见[结果数据层](/zh/guides/results-data)的 `redact` 钩子。
|
|
@@ -174,16 +174,16 @@ pass ↑ (好 → 右上)
|
|
|
174
174
|
A bub-high B bub-medium C codex-high D codex-low
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
网页面点带 hover(值与 `samples/total`)、同系列连线、点击深链下钻。终端面用字母标点、图例列在图下;x 或 y 缺数据的点两个面都不画,注脚如实报「n 个点缺数据」;点太密排不下时降级为坐标表,不硬挤。维度槽也收 `
|
|
177
|
+
网页面点带 hover(值与 `samples/total`)、同系列连线、点击深链下钻。终端面用字母标点、图例列在图下;x 或 y 缺数据的点两个面都不画,注脚如实报「n 个点缺数据」;点太密排不下时降级为坐标表,不硬挤。维度槽也收 `flag()`——用 experiment 声明的变量分组,见[自定义报告](/zh/guides/custom-reports)的「变量来自配置」一节。
|
|
178
178
|
|
|
179
179
|
## `MetricLine` —— 趋势线
|
|
180
180
|
|
|
181
|
-
x 是有序变量、每个系列一条线,回答「变量拧大,分数怎么走」——并行 agent 数 × 模拟延迟 × 得分这类 scaling 图就是它。与 `MetricScatter` 的分工:scatter 的两轴都是测出来的指标(找 frontier),line 的 x 是你配置的变量(看趋势)。变量在 experiment 的 `
|
|
181
|
+
x 是有序变量、每个系列一条线,回答「变量拧大,分数怎么走」——并行 agent 数 × 模拟延迟 × 得分这类 scaling 图就是它。与 `MetricScatter` 的分工:scatter 的两轴都是测出来的指标(找 frontier),line 的 x 是你配置的变量(看趋势)。变量在 experiment 的 `flags` 里声明,报告用 `flag()` 直接引用,不从 experiment 命名里解析。
|
|
182
182
|
|
|
183
183
|
```tsx
|
|
184
184
|
<MetricLine data={await MetricLine.data(selection, {
|
|
185
|
-
x:
|
|
186
|
-
series:
|
|
185
|
+
x: flag("latencyMs", { label: "Simulated latency", unit: "ms" }),
|
|
186
|
+
series: flag("agents", { label: (v) => `${v} agents` }),
|
|
187
187
|
y: passRate,
|
|
188
188
|
})} />
|
|
189
189
|
```
|
|
@@ -46,7 +46,7 @@ export default defineEval({
|
|
|
46
46
|
- 每个 attempt 是 Braintrust 里的一行;`runs: 3` 会产生三行,靠 metadata 里的 `attempt` 区分。
|
|
47
47
|
- soft 断言按名字记为 score;gate 断言记在 `gate:` 前缀下。这样在 Braintrust 的实验对比里,gate 回归和 soft 分数回归用同一套 diff 看。
|
|
48
48
|
- metrics 带开始/结束时间、token 用量和估算成本;缺的数据不写,不补零。
|
|
49
|
-
- metadata 带 `agent`、`model`、`experiment`、`
|
|
49
|
+
- metadata 带 `agent`、`model`、`experiment`、`flags`、`outcome` 和失败断言明细,方便在 Braintrust 里按维度过滤。
|
|
50
50
|
|
|
51
51
|
### 配置项
|
|
52
52
|
|
|
@@ -105,7 +105,7 @@ for (const s of results.skipped) {
|
|
|
105
105
|
|
|
106
106
|
## 选快照:`results.latest()`
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
多数场景先回答「现在什么水位」,选择器替你挑「每个实验最新一次」——这也是默认报告的口径。返回的是一个**选集(Selection)**,快照和警告绑在一起走:
|
|
109
109
|
|
|
110
110
|
```typescript
|
|
111
111
|
const latest = results.latest({
|
|
@@ -211,7 +211,7 @@ await copySnapshots(results.latest(), "site-data/run", {
|
|
|
211
211
|
|
|
212
212
|
第一个参数收 `Selection` 或手工挑的 `Snapshot[]`——和报告积木同一个输入约定。`artifacts` 的合法值是 `"events" | "trace" | "o11y" | "diff" | "sources"`,缺省全带。目标目录已存在且非空时报错,不静默覆盖——发布脚本要幂等就自己先清目标目录。
|
|
213
213
|
|
|
214
|
-
复制不改工件内容、不消毒;发布前给自由文本消毒用报告积木 `CaseList.data` 的 `redact` 钩子。唯一随行补记的是挑选时的**覆盖事实**:`partial-coverage` 警告的分母是实验的历史并集,而发布目录没有历史——所以每个复制出的快照带上 `knownEvalIds`(复制时刻该实验已知的 eval 并集),reader 端把它并进 `exp.evalIds` 的计算(取本地历史与快照携带值的并集)。发布目录上重新 `openResults().latest()
|
|
214
|
+
复制不改工件内容、不消毒;发布前给自由文本消毒用报告积木 `CaseList.data` 的 `redact` 钩子。唯一随行补记的是挑选时的**覆盖事实**:`partial-coverage` 警告的分母是实验的历史并集,而发布目录没有历史——所以每个复制出的快照带上 `knownEvalIds`(复制时刻该实验已知的 eval 并集),reader 端把它并进 `exp.evalIds` 的计算(取本地历史与快照携带值的并集)。发布目录上重新 `openResults().latest()`,残缺警告被同一套机制重新算出来,不靠发布者转述。复制出的目录就是标准结果目录,`niceeval view --run <目录>` 直接能看;要让报告站随 push 自动更新,workflow 见[通过 CI 发布报告](/zh/guides/publish-report)。
|
|
215
215
|
|
|
216
216
|
## 分层速览
|
|
217
217
|
|
|
@@ -86,17 +86,22 @@ export default defineExperiment({
|
|
|
86
86
|
|
|
87
87
|
```text
|
|
88
88
|
createSandbox
|
|
89
|
+
→ sandbox spec 的 .setup() 钩子? # 环境准备(按实验装东西);没挂就跳过
|
|
89
90
|
→ git init && git commit
|
|
90
|
-
→ adapter.setup?
|
|
91
|
-
→ test(t): uploadDirectory(...)
|
|
91
|
+
→ adapter.setup? # 装 CLI / 写 agent 配置
|
|
92
|
+
→ test(t): uploadDirectory(...) # 写入这条 eval 的起始文件
|
|
92
93
|
→ adapter.send(input, ctx)
|
|
93
|
-
→ test(t): runCommand(...)
|
|
94
|
-
→ collectGeneratedFiles()
|
|
94
|
+
→ test(t): runCommand(...) # 手工运行验证命令
|
|
95
|
+
→ collectGeneratedFiles() # git diff HEAD
|
|
96
|
+
→ adapter.teardown? # agent 收尾
|
|
97
|
+
→ sandbox spec 的 .teardown() 钩子? # 环境收尾(如回存状态),销毁前最后跑
|
|
95
98
|
→ sandbox.stop()
|
|
96
99
|
```
|
|
97
100
|
|
|
98
101
|
起始文件和验证命令都写在 `test(t)` 中。agent 执行阶段只能看到你已经写进 sandbox 的文件。
|
|
99
102
|
|
|
103
|
+
环境钩子(`.setup()` / `.teardown()`)挂在 experiment `sandbox` 字段的 spec 上,用来做"按实验变化的环境准备"——装某个实验专属的二进制、预热、跨 attempt 载入和回存状态。它排在 git 基线之前,所以写下的文件不会被算进 agent 产出的 diff。写法和规则见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
|
|
104
|
+
|
|
100
105
|
## 自定义 sandbox agent
|
|
101
106
|
|
|
102
107
|
```ts
|
|
@@ -115,9 +120,9 @@ export default defineSandboxAgent({
|
|
|
115
120
|
});
|
|
116
121
|
```
|
|
117
122
|
|
|
118
|
-
## `ctx.model` 与 `ctx.
|
|
123
|
+
## `ctx.model` 与 `ctx.flags`
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
experiment 声明的 model 和 flags 会出现在 adapter context 中。adapter 可以决定如何把它们转成 CLI 参数或 HTTP payload。
|
|
121
126
|
|
|
122
127
|
## 在 experiment 中使用自定义 agent
|
|
123
128
|
|
|
@@ -49,6 +49,33 @@ export default defineExperiment({
|
|
|
49
49
|
|
|
50
50
|
漏装时不会静默失败:NiceEval 在创建 sandbox 的那一刻报错并直接给出上面的安装命令,例如 `Docker sandbox requires 'dockerode'. Install it with: pnpm add dockerode @types/dockerode`。
|
|
51
51
|
|
|
52
|
+
## 环境钩子
|
|
53
|
+
|
|
54
|
+
`dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` 返回的 spec 上有两个链式方法:`.setup(fn)` 和 `.teardown(fn)`。它们解决一类具体问题:**有些东西你本想直接做进 Docker 镜像或 E2B 模板,但它要按实验变化**——比如某个实验要装一个额外的二进制、预热一次模型、写一份 hook 文件,或者在多次 attempt 之间载入和回存状态。静态镜像做不到"每个实验装不同的东西",这两个钩子就是运行时补上的那一层。
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
export default defineExperiment({
|
|
58
|
+
agent: codexAgent({ mcpServers: [mempalMcp] }),
|
|
59
|
+
sandbox: e2bSandbox({ template: "fasteval-agents" })
|
|
60
|
+
.setup(mempalSetup("codex")) // 装二进制、预热、写 hook、载入状态
|
|
61
|
+
.teardown(mempalTeardown("codex")), // 回存状态
|
|
62
|
+
maxConcurrency: 1, // 载入和回存之间不能并发,声明串行
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
规则一览:
|
|
67
|
+
|
|
68
|
+
- **签名**:钩子函数和 Adapter 的 `setup` / `teardown` 一样是 `(sandbox, ctx)`;`setup` 可以返回一个清理函数。
|
|
69
|
+
- **不可变**:每次 `.setup()` / `.teardown()` 都返回一个新 spec,原对象不变,可以继续链。
|
|
70
|
+
- **多个钩子**:多个 `.setup()` 按追加顺序执行;多个 `.teardown()` 按追加的逆序执行。
|
|
71
|
+
- **执行时机**:`setup` 钩子在 sandbox 创建后、git 基线之前最先跑——它写下的文件会进基线,不会被算进 agent 产出的 diff;`teardown` 钩子在 Adapter 的 `teardown` 之后、sandbox 销毁之前最后跑——把状态回存到外部正好用这个时机。
|
|
72
|
+
- **失败语义**:`setup` 钩子抛错,这次 attempt 记为 `errored`(环境问题,不是 agent 做错题);`teardown` 钩子报错只记日志,不改变已产出的结果。
|
|
73
|
+
- **不带 sandbox 的 Agent**:`defineAgent` 构造的 Agent 没有 sandbox,`sandbox` 字段对它不生效,钩子自然不会跑。
|
|
74
|
+
|
|
75
|
+
钩子里可以用 `ctx.experimentId`(路径推导的实验 id)当状态隔离的键,比如不同实验各自维护一份跨 attempt 的缓存。跨 attempt 状态的载入和回存是你自己在钩子里写的普通代码——[NiceEval](https://niceeval.com/) 不提供状态存储;要保证同一实验的 attempt 不并发读写同一份状态,在 experiment 上声明 `maxConcurrency: 1`。
|
|
76
|
+
|
|
77
|
+
和另外两处 setup 的分工:Adapter 的 `setup` 管"怎么连被测 agent"(装 CLI、写鉴权配置);eval 里 `test(t)` 开头的代码管"这道题需要哪些起始文件";sandbox 的 `.setup()` 管"这次实验的环境里要多装什么"。三层各写各的,互相不知道对方的内容。MCP server、Skill、model 这些被测 agent 的配置仍然只从 Adapter 工厂参数进——环境钩子不做 Adapter 的事。
|
|
78
|
+
|
|
52
79
|
## Docker
|
|
53
80
|
|
|
54
81
|
Docker 适合本地开发和标准 CI。优点是简单、可控、无云端依赖;缺点是机器资源有限,冷启动和安装依赖可能较慢。
|