niceeval 0.5.2 → 0.5.3

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 (64) hide show
  1. package/docs-site/zh/concepts/adapter.mdx +6 -6
  2. package/docs-site/zh/concepts/experiment.mdx +6 -6
  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/ci-integration.mdx +2 -0
  6. package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
  7. package/docs-site/zh/guides/custom-reports.mdx +7 -7
  8. package/docs-site/zh/guides/experiments.mdx +3 -3
  9. package/docs-site/zh/guides/publish-report.mdx +91 -0
  10. package/docs-site/zh/guides/report-components.mdx +4 -4
  11. package/docs-site/zh/guides/reporters.mdx +1 -1
  12. package/docs-site/zh/guides/results-data.mdx +1 -1
  13. package/docs-site/zh/guides/runner.mdx +1 -1
  14. package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
  15. package/docs-site/zh/guides/viewing-results.mdx +3 -19
  16. package/docs-site/zh/guides/write-experiment.mdx +9 -9
  17. package/docs-site/zh/guides/write-send.mdx +6 -6
  18. package/docs-site/zh/index.mdx +1 -1
  19. package/docs-site/zh/introduction.mdx +1 -1
  20. package/docs-site/zh/reference/builtin-agents.mdx +4 -0
  21. package/docs-site/zh/reference/cli.mdx +9 -6
  22. package/docs-site/zh/reference/define-agent.mdx +7 -3
  23. package/docs-site/zh/reference/define-config.mdx +3 -1
  24. package/docs-site/zh/reference/define-eval.mdx +9 -3
  25. package/docs-site/zh/reference/events.mdx +4 -0
  26. package/docs-site/zh/reference/expect.mdx +4 -0
  27. package/package.json +3 -2
  28. package/src/agents/ai-sdk.test.ts +1 -1
  29. package/src/agents/ai-sdk.ts +2 -2
  30. package/src/agents/claude-code.ts +1 -1
  31. package/src/agents/codex.ts +2 -2
  32. package/src/agents/streaming.test.ts +1 -1
  33. package/src/agents/types.ts +2 -2
  34. package/src/agents/ui-message-stream.test.ts +1 -1
  35. package/src/cli.ts +38 -14
  36. package/src/context/context.test.ts +1 -1
  37. package/src/context/context.ts +3 -3
  38. package/src/context/session.ts +2 -2
  39. package/src/context/types.ts +2 -2
  40. package/src/i18n/en.ts +2 -2
  41. package/src/i18n/zh-CN.ts +2 -2
  42. package/src/report/aggregate.ts +16 -16
  43. package/src/report/components.tsx +1 -1
  44. package/src/report/compute.ts +7 -7
  45. package/src/report/dual-face.test.tsx +1 -1
  46. package/src/report/{param.ts → flag.ts} +6 -6
  47. package/src/report/index.ts +3 -3
  48. package/src/report/react/MetricLine.tsx +1 -1
  49. package/src/report/react/fixtures.ts +1 -1
  50. package/src/report/report.test.ts +17 -17
  51. package/src/report/types.ts +11 -11
  52. package/src/results/results.test.ts +2 -2
  53. package/src/runner/attempt.ts +3 -3
  54. package/src/runner/fingerprint.ts +1 -1
  55. package/src/runner/reporters/braintrust.test.ts +2 -2
  56. package/src/runner/reporters/braintrust.ts +3 -3
  57. package/src/runner/run.ts +1 -1
  58. package/src/runner/types.ts +8 -8
  59. package/src/view/app/css.d.ts +3 -0
  60. package/src/view/app/i18n.ts +3 -3
  61. package/src/view/app/lib/rows.ts +4 -4
  62. package/src/view/client-dist/app.js +2 -2
  63. package/src/view/data.test.ts +25 -1
  64. package/src/view/data.ts +27 -3
@@ -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.params`、`ctx.telemetry`)随请求转发给应用:
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
- params: ctx.params,
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
- params: { promptVariant: "concise" },
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 params**(feature A/B)。每档投入什么、买到什么、什么时候升级,见 [Tier](/zh/concepts/tier)。
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`。
@@ -187,7 +187,7 @@ interface AgentContext {
187
187
  readonly model?: string; // Tier 1 的模型对比:experiment.model 透传;应用接口收模型选择就转发
188
188
  readonly reasoningEffort?: string; // 推理努力程度:experiment.reasoningEffort 透传,归属与 model 一致
189
189
  readonly telemetry?: Telemetry; // Tier 2:本轮的 W3C trace context(headers,每轮一个新 traceparent)
190
- readonly params: Readonly<Record<string, unknown>>; // Tier 3 的 feature A/B:experiment.params 透传;没配时是 {}
190
+ readonly flags: Readonly<Record<string, unknown>>; // Tier 3 的 feature A/B:experiment.flags 透传;没配时是 {}
191
191
 
192
192
  readonly sandbox?: Sandbox; // 仅 defineSandboxAgent 构造的 Agent 会拿到
193
193
  }
@@ -209,7 +209,7 @@ interface AgentSession {
209
209
  }
210
210
  ```
211
211
 
212
- `ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`params` 由 experiment 声明、运行器原样递过来,Adapter 只负责随请求转发给应用,不解释它们的含义;`telemetry` 仅在配置了 OTel 接入时出现,`send` 里只需要把 `headers` spread 进请求头——接收端点在 `defineConfig` 里固定、由应用启动时指向,不从这里传,见[OTel 接入](/zh/guides/connect-otel)。
212
+ `ctx` 里没有要你检查的标志位。三个档位字段都是"透传"语义:`model`、`flags` 由 experiment 声明、运行器原样递过来,Adapter 只负责随请求转发给应用,不解释它们的含义;`telemetry` 仅在配置了 OTel 接入时出现,`send` 里只需要把 `headers` spread 进请求头——接收端点在 `defineConfig` 里固定、由应用启动时指向,不从这里传,见[OTel 接入](/zh/guides/connect-otel)。
213
213
 
214
214
  `session` 是本条会话线的自有状态,[NiceEval](https://niceeval.com/) 对它只承诺一件事:**同一条会话线的每次 `send` 拿到同一个 `ctx.session`,新会话线(eval 的第一轮,或 `t.newSession()` 之后)拿到一个全新的。** 会话续接(`id`/`capture`、`history`)和 HITL 停轮现场(`hold`/`take`)的存取器都在它上面,"第一轮"就是新会话线的自然形态——`id` 是 `undefined`、`history.get()` 是空数组,没有要判断的分支;`state` 是这些存取器之外的逃生舱,框架从不往里写数据。
215
215
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  title: "实验(Experiment):评谁、怎么跑"
3
3
  sidebarTitle: "实验"
4
- description: "Experiment 是可签入的运行配置:同一批 eval 对着哪个 agent、哪个模型、哪些 params、跑几次。eval 与 experiment 分开的原因——晚绑定,让同一份 eval 换着对象跑。"
4
+ description: "Experiment 是可签入的运行配置:同一批 eval 对着哪个 agent、哪个模型、开哪些 flags、跑几次。eval 与 experiment 分开的原因——晚绑定,让同一份 eval 换着对象跑。"
5
5
  ---
6
6
 
7
- **experiment** 对着哪个 agent、用哪个模型、开哪些 params、跑几次。用于做 AB 测试、模型对比、feature 对比。
7
+ **experiment** 对着哪个 agent、用哪个模型、开哪些 flags、跑几次。用于做 AB 测试、模型对比、feature 对比——`flags` 就是 A/B 测试里的 feature flag,一个 experiment 是一组 flag 取值。
8
8
 
9
9
  ## 一个 experiment 里有什么
10
10
 
@@ -16,13 +16,13 @@ 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
- params: { promptVariant: "concise" }, // 经 ctx.params 透传;eval 里是 t.params
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` / `params`:透传语义。[NiceEval](https://niceeval.com/) 不解释它们的含义,原样经 `ctx` 递给 Adapter,由 Adapter 随请求转发、应用按需切换——这正是 [Tier](/zh/concepts/tier) 里模型对比(Tier 1)和 feature A/B(Tier 3)的通道。
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
28
  ## 矩阵对比
@@ -32,6 +32,6 @@ export default defineExperiment({
32
32
  ## 相关阅读
33
33
 
34
34
  - [写实验](/zh/guides/write-experiment) — `defineExperiment` 的完整字段:runs、预算、并发与 sandbox。
35
- - [实验矩阵](/zh/guides/experiments) — 跨 agent / model / params 的对比怎么组织、怎么读结果。
35
+ - [实验矩阵](/zh/guides/experiments) — 跨 agent / model / flags 的对比怎么组织、怎么读结果。
36
36
  - [评估](/zh/concepts/evals) — 另一半:eval 是什么、生命周期与 outcome。
37
- - [Tier](/zh/concepts/tier) — `model` / `params` 各在哪一档生效。
37
+ - [Tier](/zh/concepts/tier) — `model` / `flags` 各在哪一档生效。
@@ -85,7 +85,7 @@ Subject under test / Sandbox backend
85
85
  运行后落盘的结构化结果文件。
86
86
  </Accordion>
87
87
  <Accordion title="Experiment">
88
- 用矩阵方式比较多个 agent、model 或 params 的运行配置。
88
+ 用矩阵方式比较多个 agent、model 或 flags 的运行配置。
89
89
  </Accordion>
90
90
  </AccordionGroup>
91
91
 
@@ -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 params。每档买到什么、什么时候升级。"
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
- ![接入分三个 Tier:Tier 1 只接 send 应用零改动;Tier 2 加 OTel 换 niceeval view 的调用瀑布图;Tier 3 侵入改造把变体暴露成 params 做 A/B。](/images/adapter-tiers-zh.svg)
9
+ ![接入分三个 Tier:Tier 1 只接 send 应用零改动;Tier 2 加 OTel 换 niceeval view 的调用瀑布图;Tier 3 侵入改造把变体暴露成 flags 做 A/B。](/images/adapter-tiers-zh.svg)
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 params
25
+ ## Tier 3:侵入改造 + experiment flags
26
26
 
27
- 要评「改 prompt / 换工具集 / 开关某个 feature 谁更好」这类 **feature A/B test** 时,对照点在应用内部,前两档拿不到。这一档改应用内部代码,把变体暴露成外部可选的配置:experiment 的 `params` 经 `ctx.params` 透传给 Adapter,Adapter 随请求交给应用(HTTP header、请求体字段、环境变量都行),应用按参数切换变体。
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.params` 按档位出现。
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 / params 对比在 experiment 侧怎么声明。
44
+ - [实验](/zh/concepts/experiment) — model / flags 对比在 experiment 侧怎么声明。
@@ -95,3 +95,5 @@ npx niceeval exp ci --max-concurrency 2
95
95
  运行完整 experiment,记录 pass rate、成本和延迟趋势。
96
96
  </Card>
97
97
  </CardGroup>
98
+
99
+ 跑出来的结果要发布成静态报告站(Vercel / GitHub Pages 自动更新),见[通过 CI 发布报告](./publish-report)。
@@ -116,9 +116,9 @@ npx niceeval view # 本地查看器里看结果
116
116
  配置归属只有两条通道,分清它们,接入就不会乱:
117
117
 
118
118
  1. **静态配置走 Adapter 工厂。** URL、鉴权、协议细节这类"这个环境长什么样"的配置,作为工厂参数写在 experiment 文件里——`defineExperiment` 的 `agent` 字段放的是**已经配置好的实例**。
119
- 2. **每轮动态值走 `ctx`。** experiment 声明的 `model`、`params`,运行器每轮经 `ctx` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发给应用。
119
+ 2. **每轮动态值走 `ctx`。** experiment 声明的 `model`、`flags`,运行器每轮经 `ctx` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发给应用。
120
120
 
121
- 两条通道拼在一起,就是把第一步的 my-bot 从「写死 URL 的实例」改成「收配置的工厂」:default export 换成一个返回 `defineAgent(...)` 的函数,`send` 里写死的地方换成读工厂参数;experiment 声明的 `model`、`params` 每轮从 `ctx` 到达,`send` 里随请求转发:
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
- params: ctx.params, // ← experiment.params:原样交给应用,应用按参数切换
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
- params: { promptVariant: "concise" }, // ← 动态值:经 ctx.params 到 send
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
- | `params` | experiment 的 `params` | 原样转发(请求体、header 都行),应用按参数切换变体 |
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 对比 | 应用把变体暴露成 `params` 可切换的配置 | [Tier](/zh/concepts/tier)、[写实验](/zh/guides/write-experiment) |
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、params
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) —— 三档接入各投入什么、买到什么。
@@ -20,7 +20,7 @@ description: "一份报告就是一个报告文件:官方宿主打开结果、
20
20
  四个格子是同一个宿主。自定义只有三个层次,逐层深入:
21
21
 
22
22
  1. **换摆法**:用官方组件和 `Row` / `Col` 重摆版面——大多数「要自己的口径」到这层就够了。
23
- 2. **换口径**:`defineMetric` 定义自己的指标,`param()` 把 experiment 声明的变量当维度和轴。
23
+ 2. **换口径**:`defineMetric` 定义自己的指标,`flag()` 把 experiment 声明的变量当维度和轴。
24
24
  3. **换形态**:官方组件表达不了的展示,用 `defineComponent` 写自己的组件——网页怎么渲染、终端字符怎么排,两个面你都说了算。
25
25
 
26
26
  ## 一份报告 = 一个报告文件
@@ -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 文件的 `params`:一文件一个档位,把变量声明成数据:
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
- params: { agents: 16, latencyMs: 300 },
150
+ flags: { agents: 16, latencyMs: 300 },
151
151
  });
152
152
  ```
153
153
 
154
- 报告侧用 `param()` 把声明的值直接当维度或轴用,不解析任何名字:
154
+ 报告侧用 `flag()` 把声明的值直接当维度或轴用,不解析任何名字:
155
155
 
156
156
  ```tsx
157
157
  <MetricLine data={await MetricLine.data(selection, {
158
- x: param("latencyMs", { label: "Simulated latency", unit: "ms" }),
159
- series: param("agents", { label: (v) => `${v} agents` }),
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
- `param()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度;内置维度(`agent`、`model`、`experiment`、`evalGroup`……)覆盖不了的变量都走它。没有声明该 param 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。params 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
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 和 params,比较 pass rate、成本和延迟。"
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,13 +37,13 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
37
37
 
38
38
  这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。
39
39
 
40
- `defineExperiment` 的字段怎么写、`params` 怎么透传到 adapter,见[写实验](/zh/guides/write-experiment)。
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 开关(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的配置,经 `params` → `ctx.params` 透传)。
46
+ - 不同 prompts 或 feature flags(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的 flag,经 `flags` → `ctx.flags` 透传)。
47
47
  - 不同 sandbox 后端。
48
48
  - 同一任务的 pass@N。
49
49
 
@@ -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 个点缺数据」;点太密排不下时降级为坐标表,不硬挤。维度槽也收 `param()`——用 experiment 声明的变量分组,见[自定义报告](/zh/guides/custom-reports)的「变量来自配置」一节。
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 的 `params` 里声明,报告用 `param()` 直接引用,不从 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: param("latencyMs", { label: "Simulated latency", unit: "ms" }),
186
- series: param("agents", { label: (v) => `${v} agents` }),
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`、`params`、`outcome` 和失败断言明细,方便在 Braintrust 里按维度过滤。
49
+ - metadata 带 `agent`、`model`、`experiment`、`flags`、`outcome` 和失败断言明细,方便在 Braintrust 里按维度过滤。
50
50
 
51
51
  ### 配置项
52
52
 
@@ -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()`,残缺警告被同一套机制重新算出来,不靠发布者转述。CI 静态托管的完整流程见[查看结果 · 导出与静态托管](/zh/guides/viewing-results#导出与静态托管)。
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
 
@@ -29,7 +29,7 @@ npx niceeval exp local weather
29
29
  npx niceeval exp local fixtures/button
30
30
  ```
31
31
 
32
- 不要把 agent 名放进位置参数。agent/model/params 写进 experiment。
32
+ 不要把 agent 名放进位置参数。agent/model/flags 写进 experiment。
33
33
 
34
34
  ## 并发
35
35
 
@@ -115,9 +115,9 @@ export default defineSandboxAgent({
115
115
  });
116
116
  ```
117
117
 
118
- ## `ctx.model` 与 `ctx.params`
118
+ ## `ctx.model` 与 `ctx.flags`
119
119
 
120
- CLI 传入的模型和自定义 params 会出现在 adapter context 中。adapter 可以决定如何把它们转成 CLI 参数或 HTTP payload。
120
+ experiment 声明的 model 和 flags 会出现在 adapter context 中。adapter 可以决定如何把它们转成 CLI 参数或 HTTP payload。
121
121
 
122
122
  ## 在 experiment 中使用自定义 agent
123
123
 
@@ -195,30 +195,14 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的工件(`source
195
195
 
196
196
  `--out` 只接受目录。没有单文件导出:代码视图、transcript 和 trace 依赖工件文件,单个 HTML 装不下完整证据。要把结果发给别人,托管整站发链接即可。
197
197
 
198
+ 零可读结果时 `view` 直接报错、非零退出——本地服务起不来,`--out` 不导出空页面。错误逐条列出被跳过的 run 目录与原因;落盘 schemaVersion 与当前版本不兼容时,还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
199
+
198
200
  两点限制:
199
201
 
200
202
  - `diff.json` 和 `o11y.json` 不会被复制。查看器不读取它们,且 diff 可能达到上百 MB。
201
203
  - 用 `file://` 直接打开 `index.html` 时浏览器不允许 fetch 工件,代码视图会提示源码不可用。本地预览用 http 服务打开。
202
204
 
203
- 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。`.niceeval/` 通常不进版本库,如果构建发生在别的机器(CI、托管平台),先用 `copySnapshots` 把要发布的快照瘦身复制进仓库,再对着快照目录导出:
204
-
205
- ```typescript
206
- // scripts/snapshot.ts —— 跑完 eval 后执行,产物提交进仓库
207
- import { openResults, copySnapshots } from "niceeval/results";
208
-
209
- const results = await openResults(".niceeval");
210
- await copySnapshots(results.latest(), "site-data/run", {
211
- artifacts: ["sources", "events", "trace"],
212
- });
213
- ```
214
-
215
- ```bash
216
- npx niceeval view --run site-data/run --out site # CI 上对着快照构建
217
- ```
218
-
219
- 结果目录经 `--run` 递入,与 `show --run` 同一语义——位置参数永远留给 eval id 前缀。
220
-
221
- `results.latest()` 按实验挑最新快照,而不是挑最新的 run——某次运行只跑了部分实验时,其它实验仍保留上一轮的结果。
205
+ 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,把 `.niceeval/` 提交进仓库、CI 上一行 `view --out` 导出——workflow 与平台接线见[通过 CI 发布报告](/zh/guides/publish-report)。
222
206
 
223
207
  ## Artifact 说明
224
208
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  title: "写实验:用 defineExperiment 固定运行配置"
3
3
  sidebarTitle: "写实验"
4
- description: "学习如何在 experiments/ 里用 defineExperiment 选择 agent、传 model 和 params、设置 runs、预算、并发与 sandbox。"
4
+ description: "学习如何在 experiments/ 里用 defineExperiment 选择 agent、传 model 和 flags、设置 runs、预算、并发与 sandbox。"
5
5
  ---
6
6
 
7
- Experiment 是可签入的运行配置:同一批 eval 对着哪个 adapter、哪个模型、哪些参数、跑几次、预算多少,都写在 `experiments/` 里。CLI 的位置参数只负责筛 eval,不负责临时改 agent 或运行配置。
7
+ Experiment 是可签入的运行配置:同一批 eval 对着哪个 adapter、哪个模型、开哪些 flags、跑几次、预算多少,都写在 `experiments/` 里。CLI 的位置参数只负责筛 eval,不负责临时改 agent 或运行配置。
8
8
 
9
9
  ## 最小实验
10
10
 
@@ -20,7 +20,7 @@ export default defineExperiment({
20
20
 
21
21
  `agent` 放的是已经配置好的 agent 实例。被测系统的 URL、鉴权、协议细节通常传给 adapter 工厂;运行器不会单独保存一个 `agentConfig` 字段。
22
22
 
23
- ## 传 model 和 params
23
+ ## 传 model 和 flags
24
24
 
25
25
  ```ts
26
26
  // experiments/concise.ts
@@ -34,7 +34,7 @@ export default defineExperiment({
34
34
  apiKey: process.env.STAGING_AGENT_API_KEY,
35
35
  }),
36
36
  model: "gpt-5.4",
37
- params: {
37
+ flags: {
38
38
  promptVariant: "concise",
39
39
  webResearch: false,
40
40
  },
@@ -46,7 +46,7 @@ export default defineExperiment({
46
46
 
47
47
  `model` 会作为 `ctx.model` 传给 adapter;应用接口支持模型选择时,adapter 把它放进请求体、header 或 CLI 参数即可。
48
48
 
49
- `params` 会作为 `ctx.params` 传给 adapter,也会作为 `t.params` 出现在 eval 里。它适合表达 experiment 维度的 feature 开关,例如 prompt 版本、工具集、检索开关。真正按参数切换行为的是你的应用或 adapter,[NiceEval](https://niceeval.com/) 只负责原样透传。
49
+ `flags` 会作为 `ctx.flags` 传给 adapter,也会作为 `t.flags` 出现在 eval 里。语义就是产品 A/B 测试里的 feature flag:一个 experiment 就是一组 flag 取值,例如 prompt 版本、工具集、检索开关;值可以是任意 JSON(变体名、数字、配置对象),不限于布尔。真正按 flag 切换行为的是你的应用或 adapter,[NiceEval](https://niceeval.com/) 只负责原样透传。它与命令行开关是两回事——后者在文档里一律写作 CLI flag 或字面的 `--xxx`。
50
50
 
51
51
  ```ts
52
52
  // agents/web-agent.ts
@@ -57,7 +57,7 @@ async send(input, ctx) {
57
57
  body: JSON.stringify({
58
58
  message: input.text,
59
59
  model: ctx.model,
60
- params: ctx.params,
60
+ flags: ctx.flags,
61
61
  }),
62
62
  signal: ctx.signal,
63
63
  });
@@ -66,7 +66,7 @@ async send(input, ctx) {
66
66
 
67
67
  ## 一组实验怎么写
68
68
 
69
- 一个实验文件是一格配置。要比较多个模型、agent 或 params,就把多个文件放进同一个文件夹:
69
+ 一个实验文件是一格配置。要比较多个模型、agent 或 flag 取值,就把多个文件放进同一个文件夹:
70
70
 
71
71
  ```text
72
72
  experiments/
@@ -89,7 +89,7 @@ npx niceeval exp prompt-variants
89
89
  | `agent` | 选择并配置 adapter;必填 |
90
90
  | `model` | 单个模型名,经 `ctx.model` 透传 |
91
91
  | `reasoningEffort` | 单个推理努力程度(如 `"high"`),经 `ctx.reasoningEffort` / `t.reasoningEffort` 透传,归属与 `model` 一致 |
92
- | `params` | 任意 JSON 对象,经 `ctx.params` / `t.params` 透传 |
92
+ | `flags` | 实验条件(A/B 里的 feature flag),任意 JSON 对象,经 `ctx.flags` / `t.flags` 透传 |
93
93
  | `runs` | 每个 eval × 配置最多跑几次 |
94
94
  | `earlyExit` | 多次运行里通过一次就提前停止 |
95
95
  | `evals` | 限定这一格配置跑哪些 eval |
@@ -98,4 +98,4 @@ npx niceeval exp prompt-variants
98
98
  | `maxConcurrency` | 这一格配置的并发上限 |
99
99
  | `sandbox` | sandbox agent 使用的后端,如 `dockerSandbox()` / `e2bSandbox()` |
100
100
 
101
- 跨配置比较的设计建议见[实验矩阵](/zh/guides/experiments)。adapter 如何消费 `ctx.model` 和 `ctx.params` 见[Adapter](/zh/concepts/adapter)。
101
+ 跨配置比较的设计建议见[实验矩阵](/zh/guides/experiments)。adapter 如何消费 `ctx.model` 和 `ctx.flags` 见[Adapter](/zh/concepts/adapter)。
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "如何写好 Send"
3
3
  sidebarTitle: "如何写好 Send"
4
- description: "手写 Adapter 的 send 函数,一条主线七步走:发消息拿回复、接上之前的消息、记录消费、把工具解析成事件、人工介入(HITL)、接上 OTel trace、透传 experiment 的 params。每一步只在上一步的代码上多几行,每一步解锁一组断言。"
4
+ description: "手写 Adapter 的 send 函数,一条主线七步走:发消息拿回复、接上之前的消息、记录消费、把工具解析成事件、人工介入(HITL)、接上 OTel trace、透传 experiment 的 flags。每一步只在上一步的代码上多几行,每一步解锁一组断言。"
5
5
  ---
6
6
 
7
7
  [Adapter](/zh/concepts/adapter) 讲了契约:`send` 传入 `TurnInput` 和 `AgentContext`,返回 `Turn`。这一页讲怎么写它——从"发一条消息拿到回复"的最小形态起步,一步一步加到完整接入。每一步只在上一步的代码上多几行,前面已有的部分在代码里用 `// ……省略` 标出;每一步末尾说明这几行解锁了哪些断言。改完任何一步都能立刻验证:把新解锁的断言写进一条 eval 重跑 `npx niceeval exp`,`niceeval view` 里能看到这一步新增的数据——多轮轨迹、用量、工具事件、待回答请求。
@@ -303,16 +303,16 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces node server.js
303
303
 
304
304
  **这一步解锁**:`niceeval view` 里的调用瀑布图——应用内部每次模型调用、每次工具执行的耗时与 token,按轮铺成时间线。断言不变:判决依据在前几步的事件映射里已经齐了,span 只进瀑布图、不喂断言。接收器怎么起、span 怎么归属到轮,见 [OTel 接入](/zh/guides/connect-otel)。
305
305
 
306
- ## 第七步:透传 experiment 的 params(A/B 对比)
306
+ ## 第七步:透传 experiment 的 flags(A/B 对比)
307
307
 
308
- 应用把变体暴露成可切换的配置后,experiment 声明 `params`,运行器每轮经 `ctx.params` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发——应用按参数切换变体:
308
+ 应用把变体暴露成可切换的配置后,experiment 声明 `flags`,运行器每轮经 `ctx.flags` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发——应用按参数切换变体:
309
309
 
310
310
  ```ts
311
311
  // ……send 其余部分同前,省略
312
312
  body: JSON.stringify({
313
313
  messages,
314
314
  model: ctx.model, // ← 第一步就转发的 experiment.model;这里一起列出
315
- params: ctx.params, // ← experiment.params 原样透传;没配时是 {}
315
+ flags: ctx.flags, // ← experiment.flags 原样透传;没配时是 {}
316
316
  }),
317
317
  ```
318
318
 
@@ -324,11 +324,11 @@ import chatApp from "../agents/chat-app.ts";
324
324
  export default defineExperiment({
325
325
  agent: chatApp,
326
326
  model: "gpt-5.4", // ← 经 ctx.model 到 send
327
- params: { promptVariant: "concise" }, // ← 每轮经 ctx.params 到 send
327
+ flags: { promptVariant: "concise" }, // ← 每轮经 ctx.flags 到 send
328
328
  });
329
329
  ```
330
330
 
331
- 两个 experiment 文件各声明一份 `params`,`npx niceeval exp` 分别跑同一批 eval,就是一组 A/B 对比。这是三档接入里的 Tier 3(要求应用配合暴露开关),投入与回报见 [Tier](/zh/concepts/tier);`params` 与 `model`、`runs` 等其余 experiment 字段见[写实验](/zh/guides/write-experiment)。
331
+ 两个 experiment 文件各声明一份 `flags`,`npx niceeval exp` 分别跑同一批 eval,就是一组 A/B 对比。这是三档接入里的 Tier 3(要求应用配合暴露开关),投入与回报见 [Tier](/zh/concepts/tier);`flags` 与 `model`、`runs` 等其余 experiment 字段见[写实验](/zh/guides/write-experiment)。
332
332
 
333
333
  **这一步解锁**:同一批 eval 跨变体的成绩对比。
334
334
 
@@ -87,7 +87,7 @@ eval 运行完成后,[NiceEval](https://niceeval.com/) 会生成易读报告
87
87
  | 概念 | 一句话 |
88
88
  |---|---|
89
89
  | [Eval](/zh/concepts/evals) | 一个测试用例:写在 `evals/*.eval.ts` 里,描述测什么。 |
90
- | [Experiment](/zh/concepts/experiment) | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 params。 |
90
+ | [Experiment](/zh/concepts/experiment) | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 flags。 |
91
91
  | [Adapter](/zh/concepts/adapter) | 连接被测系统的适配层:实现一个 `send`,把返回翻译成标准事件流。 |
92
92
  | [Sandbox](/zh/guides/sandbox-backends) | 需要隔离工作区的 coding agent 才用得到;直连 Web Agent 不需要。 |
93
93
  | [Tier](/zh/concepts/tier) | 接入 Adapter 的三档投入:Tier 1 只接 send,Tier 2 加 OTel 换调用瀑布图,Tier 3 侵入改造做 feature A/B。 |
@@ -114,7 +114,7 @@ NiceEval 是一个 Agent-Native 的评估工具。Dataset / golden 那一套「
114
114
  | 概念 | 一句话 |
115
115
  |---|---|
116
116
  | Eval | 一个测试用例:写在 `evals/*.eval.ts` 里,描述测什么。 |
117
- | Experiment | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 params。 |
117
+ | Experiment | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 flags。 |
118
118
  | Adapter | 连接被测系统的适配层:实现一个 `send`,把返回翻译成标准事件流。 |
119
119
  | Sandbox | 需要隔离工作区的 coding agent 才用得到;直连 Web Agent 不需要。 |
120
120
  | Tier | 接入 Adapter 的三档投入:Tier 1 只接 send,Tier 2 加 OTel 换调用瀑布图,Tier 3 侵入改造做 feature A/B。 |