niceeval 0.4.4 → 0.4.6

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 (177) hide show
  1. package/README.md +21 -12
  2. package/README.zh.md +8 -9
  3. package/docs-site/.mintignore +13 -0
  4. package/docs-site/AGENTS.md +46 -0
  5. package/docs-site/concepts/adapter.mdx +287 -0
  6. package/docs-site/concepts/assert.mdx +243 -0
  7. package/docs-site/concepts/drive.mdx +118 -0
  8. package/docs-site/concepts/evals.mdx +108 -0
  9. package/docs-site/concepts/experiment.mdx +37 -0
  10. package/docs-site/concepts/hitl.mdx +83 -0
  11. package/docs-site/concepts/judge.mdx +129 -0
  12. package/docs-site/concepts/overview.mdx +98 -0
  13. package/docs-site/concepts/tier.mdx +42 -0
  14. package/docs-site/docs-ref/00-index.md +23 -0
  15. package/docs-site/docs-ref/01-page-types.md +43 -0
  16. package/docs-site/docs-ref/03-style-rules.md +45 -0
  17. package/docs-site/docs-ref/04-code-examples.md +37 -0
  18. package/docs-site/docs-ref/05-dx-failure-paths.md +45 -0
  19. package/docs-site/docs-ref/06-checklists.md +53 -0
  20. package/docs-site/docs.json +256 -0
  21. package/docs-site/example/ai-agent-application.mdx +148 -0
  22. package/docs-site/example/claude-code-codex-plugin.mdx +162 -0
  23. package/docs-site/example/claude-code-codex-skill.mdx +147 -0
  24. package/docs-site/example/showcase.mdx +39 -0
  25. package/docs-site/example/tier1-ai-sdk-v7.mdx +136 -0
  26. package/docs-site/example/tier1-claude-sdk.mdx +119 -0
  27. package/docs-site/example/tier1-codex-sdk.mdx +111 -0
  28. package/docs-site/example/tier1-langgraph.mdx +119 -0
  29. package/docs-site/example/tier1-pi-sdk.mdx +119 -0
  30. package/docs-site/favicon.svg +5 -0
  31. package/docs-site/github-diff.css +135 -0
  32. package/docs-site/github-diff.js +11 -0
  33. package/docs-site/guides/authoring.mdx +129 -0
  34. package/docs-site/guides/ci-integration.mdx +97 -0
  35. package/docs-site/guides/connect-otel.mdx +209 -0
  36. package/docs-site/guides/connect-your-agent.mdx +221 -0
  37. package/docs-site/guides/dataset-fanout.mdx +98 -0
  38. package/docs-site/guides/experiments.mdx +71 -0
  39. package/docs-site/guides/fixtures.mdx +147 -0
  40. package/docs-site/guides/reporters.mdx +113 -0
  41. package/docs-site/guides/runner.mdx +79 -0
  42. package/docs-site/guides/sandbox-agent.mdx +138 -0
  43. package/docs-site/guides/sandbox-backends.mdx +64 -0
  44. package/docs-site/guides/scoring-guide.mdx +92 -0
  45. package/docs-site/guides/viewing-results.mdx +195 -0
  46. package/docs-site/guides/write-experiment.mdx +81 -0
  47. package/docs-site/guides/write-send.mdx +347 -0
  48. package/docs-site/images/adapter-tiers-zh.svg +60 -0
  49. package/docs-site/images/agent-turn-roundtrip-en.svg +62 -0
  50. package/docs-site/images/agent-turn-roundtrip-zh.svg +77 -0
  51. package/docs-site/images/hitl-handshake-zh.svg +89 -0
  52. package/docs-site/images/logo.svg +6 -0
  53. package/docs-site/index.mdx +181 -0
  54. package/docs-site/introduction.mdx +141 -0
  55. package/docs-site/quickstart.mdx +136 -0
  56. package/docs-site/reference/builtin-agents.mdx +161 -0
  57. package/docs-site/reference/capabilities.mdx +76 -0
  58. package/docs-site/reference/cli.mdx +120 -0
  59. package/docs-site/reference/define-agent.mdx +168 -0
  60. package/docs-site/reference/define-config.mdx +41 -0
  61. package/docs-site/reference/define-eval.mdx +160 -0
  62. package/docs-site/reference/events.mdx +131 -0
  63. package/docs-site/reference/expect.mdx +112 -0
  64. package/docs-site/tracker.js +8 -0
  65. package/docs-site/zh/concepts/adapter.mdx +286 -0
  66. package/docs-site/zh/concepts/assert.mdx +243 -0
  67. package/docs-site/zh/concepts/drive.mdx +118 -0
  68. package/docs-site/zh/concepts/evals.mdx +124 -0
  69. package/docs-site/zh/concepts/experiment.mdx +37 -0
  70. package/docs-site/zh/concepts/hitl.mdx +83 -0
  71. package/docs-site/zh/concepts/judge.mdx +130 -0
  72. package/docs-site/zh/concepts/overview.mdx +109 -0
  73. package/docs-site/zh/concepts/tier.mdx +44 -0
  74. package/docs-site/zh/example/ai-agent-application.mdx +152 -0
  75. package/docs-site/zh/example/claude-code-codex-plugin.mdx +163 -0
  76. package/docs-site/zh/example/claude-code-codex-skill.mdx +147 -0
  77. package/docs-site/zh/example/showcase.mdx +39 -0
  78. package/docs-site/zh/example/tier1-ai-sdk-v7.mdx +140 -0
  79. package/docs-site/zh/example/tier1-claude-sdk.mdx +122 -0
  80. package/docs-site/zh/example/tier1-codex-sdk.mdx +117 -0
  81. package/docs-site/zh/example/tier1-langgraph.mdx +123 -0
  82. package/docs-site/zh/example/tier1-pi-sdk.mdx +122 -0
  83. package/docs-site/zh/guides/agent-feedback-loop.mdx +79 -0
  84. package/docs-site/zh/guides/authoring.mdx +129 -0
  85. package/docs-site/zh/guides/ci-integration.mdx +97 -0
  86. package/docs-site/zh/guides/connect-otel.mdx +208 -0
  87. package/docs-site/zh/guides/connect-your-agent.mdx +226 -0
  88. package/docs-site/zh/guides/dataset-fanout.mdx +83 -0
  89. package/docs-site/zh/guides/experiments.mdx +72 -0
  90. package/docs-site/zh/guides/fixtures.mdx +147 -0
  91. package/docs-site/zh/guides/official-adapters.mdx +132 -0
  92. package/docs-site/zh/guides/reporters.mdx +113 -0
  93. package/docs-site/zh/guides/runner.mdx +82 -0
  94. package/docs-site/zh/guides/sandbox-agent.mdx +139 -0
  95. package/docs-site/zh/guides/sandbox-backends.mdx +75 -0
  96. package/docs-site/zh/guides/scoring-guide.mdx +92 -0
  97. package/docs-site/zh/guides/viewing-results.mdx +195 -0
  98. package/docs-site/zh/guides/write-experiment.mdx +101 -0
  99. package/docs-site/zh/guides/write-send.mdx +353 -0
  100. package/docs-site/zh/index.mdx +180 -0
  101. package/docs-site/zh/introduction.mdx +140 -0
  102. package/docs-site/zh/quickstart.mdx +137 -0
  103. package/docs-site/zh/reference/builtin-agents.mdx +364 -0
  104. package/docs-site/zh/reference/capabilities.mdx +76 -0
  105. package/docs-site/zh/reference/cli.mdx +140 -0
  106. package/docs-site/zh/reference/define-agent.mdx +461 -0
  107. package/docs-site/zh/reference/define-config.mdx +107 -0
  108. package/docs-site/zh/reference/define-eval.mdx +674 -0
  109. package/docs-site/zh/reference/events.mdx +252 -0
  110. package/docs-site/zh/reference/expect.mdx +208 -0
  111. package/package.json +3 -2
  112. package/src/agents/bub.ts +15 -5
  113. package/src/agents/claude-code.ts +8 -5
  114. package/src/agents/codex.ts +9 -5
  115. package/src/agents/sdk-streams.test.ts +4 -4
  116. package/src/agents/sdk-streams.ts +14 -9
  117. package/src/agents/types.ts +40 -1
  118. package/src/agents/ui-message-stream.ts +1 -0
  119. package/src/cli.ts +36 -7
  120. package/src/context/types.ts +140 -0
  121. package/src/expect/index.ts +15 -8
  122. package/src/i18n/en.ts +19 -6
  123. package/src/i18n/zh-CN.ts +19 -6
  124. package/src/o11y/parsers/bub.test.ts +71 -0
  125. package/src/o11y/parsers/bub.ts +5 -0
  126. package/src/o11y/types.ts +27 -2
  127. package/src/runner/reporters/artifacts.ts +11 -3
  128. package/src/runner/reporters/live.ts +45 -8
  129. package/src/runner/run.ts +71 -21
  130. package/src/runner/types.ts +45 -2
  131. package/src/sandbox/types.ts +18 -0
  132. package/src/scoring/types.ts +5 -0
  133. package/src/shared/types.ts +3 -0
  134. package/src/util.test.ts +26 -1
  135. package/src/util.ts +16 -0
  136. package/src/view/app/App.tsx +2 -2
  137. package/src/view/app/components/AttemptModal.tsx +2 -0
  138. package/src/view/app/components/CopyControls.tsx +87 -28
  139. package/src/view/app/i18n.ts +3 -3
  140. package/src/view/client-dist/app.css +1 -1
  141. package/src/view/client-dist/app.js +18 -18
  142. package/docs/README.md +0 -120
  143. package/docs/adapters/README.md +0 -60
  144. package/docs/adapters/authoring.md +0 -179
  145. package/docs/adapters/coding-agent-skills-plugins.md +0 -324
  146. package/docs/adapters/collection.md +0 -128
  147. package/docs/adapters/contract.md +0 -264
  148. package/docs/adapters/reference/agent-eval.md +0 -215
  149. package/docs/adapters/reference/agent-loop-apis.md +0 -69
  150. package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
  151. package/docs/adapters/reference/eve-protocol.md +0 -127
  152. package/docs/adapters/reference/otel-genai.md +0 -107
  153. package/docs/adapters/reference/otel-instrumentation.md +0 -65
  154. package/docs/adapters/targets.md +0 -99
  155. package/docs/architecture.md +0 -140
  156. package/docs/assertions.md +0 -387
  157. package/docs/capabilities-by-construction.md +0 -48
  158. package/docs/cli.md +0 -173
  159. package/docs/concepts.md +0 -106
  160. package/docs/e2e-ci.md +0 -332
  161. package/docs/eval-authoring.md +0 -319
  162. package/docs/experiments.md +0 -157
  163. package/docs/getting-started.md +0 -224
  164. package/docs/multi-agent.md +0 -145
  165. package/docs/observability.md +0 -337
  166. package/docs/origin-integration.md +0 -195
  167. package/docs/references.md +0 -35
  168. package/docs/reports.md +0 -551
  169. package/docs/results-format.md +0 -228
  170. package/docs/results-lib.md +0 -191
  171. package/docs/runner.md +0 -104
  172. package/docs/sandbox.md +0 -276
  173. package/docs/scoring.md +0 -119
  174. package/docs/source-map.md +0 -126
  175. package/docs/tier-sync.md +0 -193
  176. package/docs/view.md +0 -194
  177. package/docs/vision.md +0 -88
package/docs/cli.md DELETED
@@ -1,173 +0,0 @@
1
- # CLI 参考
2
-
3
- `niceeval` 的命令行。设计目标:运行配置可签入、可复现;真正执行 eval 时必须通过 `experiments/` 里的 experiment。
4
-
5
- > 本篇描述 CLI 的目标模型和当前约定。若代码实现与这里的设计不一致,应进一步讨论并决定是修代码、修设计,还是记录为明确的阶段性差异。
6
-
7
- ## 两类参数:`exp` 选择「怎么跑」,后续位置参数筛「哪些 eval」
8
-
9
- 执行 eval 时先选 experiment,再可选地用 eval id 前缀缩小范围:
10
-
11
- - `niceeval exp [组|配置]` = 跑**哪个运行配置**。agent、model、flags、runs、预算等都来自 experiment 文件。
12
- - `niceeval exp [组|配置] [eval id 前缀...]` = 在该 experiment 下只跑部分 eval。这个位置参数只筛 eval,不是 agent 名、URL 或模型。
13
- - flags 只做调度级临时覆盖,例如 `--runs`、`--max-concurrency`、`--timeout`。`--agent` / `--model` 不覆盖 experiment;要换 agent 或 model,新增或复制一个 experiment 文件。沙箱后端同理不接受 CLI 覆盖——写在 experiment(或 config)的 `sandbox` 字段里。
14
-
15
- ```sh
16
- niceeval exp <实验组|配置> <选哪些 eval> <flag:调度覆盖>
17
- # └怎么跑┘ └eval filter┘ └可选覆盖┘
18
- ```
19
-
20
- 裸 `niceeval weather` 不会运行;要写成 `niceeval exp local weather` 或 `niceeval exp compare weather`。这样结果始终带 experiment 身份,能复现也能在 `niceeval view` 里按配置对比。
21
-
22
- ## 命令
23
-
24
- ```sh
25
- niceeval exp [组|配置] [pattern...] # 跑实验:全部 / 一组 / 单个配置;可再用 eval id 前缀过滤
26
- niceeval init # 生成 evals/ 与 niceeval.config.ts
27
- niceeval list # 只列出发现到的 eval,不运行
28
- niceeval clean # 删除 .niceeval/ 历史运行工件
29
- niceeval watch # 监听文件变化,改即重跑(规划中)
30
- niceeval view [结果目录|summary.json] # 起本地 web 查看器并打开浏览器,读 .niceeval/ 历史运行出图
31
- ```
32
-
33
- 实验 = 可签入的运行配置(哪个 agent/几次/预算)。**一文件一配置,一文件夹一组可对比实验**(`niceeval exp <组>` 跑整组)。详见 [Experiments](experiments.md)。
34
-
35
- `pattern` 是 `exp` 命令后的 eval id 前缀过滤,可多个。匹配规则:`id === pattern` 或 `id` 以 `pattern + "/"` 开头(精确或目录前缀):
36
-
37
- ```sh
38
- niceeval exp # 跑 experiments/ 下全部实验
39
- niceeval exp local weather # 在 local 实验里跑 weather 或 weather/*
40
- niceeval exp local weather/brooklyn # 在 local 实验里跑单个 eval
41
- niceeval exp compare fixtures/ billing/ # 在 compare 组里跑多个前缀,并集
42
- ```
43
-
44
- ## 选择被测对象(agent,写进 experiment)
45
-
46
- ```sh
47
- niceeval exp <组|配置> # agent / model 来自 experiments/<组|配置>.ts
48
- ```
49
-
50
- agent、model、feature flags、sandbox 后端都属于 experiment,不是临时 CLI flag。要连你自己的服务,写一个 agent adapter,再在 experiment 里引用它;URL 和鉴权是 adapter / experiment 的私有配置,不是位置参数。沙箱型 agent 在哪跑(docker / vercel / e2b / 三方)写在 experiment(或 config)的 `sandbox` 字段里,用 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()`(从 `niceeval/sandbox` 导入)——没有默认值,没写就在起沙箱时直接报错。
51
-
52
- ## 调度
53
-
54
- ```sh
55
- niceeval exp compare --runs <n> # 临时覆盖每个 eval 的 runs
56
- niceeval exp compare --early-exit # 先过一次即停其余(默认开)
57
- niceeval exp compare --no-early-exit # 关掉,要完整通过率分布
58
- niceeval exp compare --max-concurrency <n> # 并发上限
59
- niceeval exp compare --timeout <ms> # 单 eval 超时
60
- niceeval exp compare --force # 忽略指纹缓存,全量重跑
61
- niceeval exp compare --tag <tag> # 按单个标签过滤
62
- ```
63
-
64
- 不带 `--force` 时,续跑携带的基线是跨全部历史 run、每 `(experimentId, evalId)` 取最新一份(`src/view/loader.ts` 的 `loadLatestResultsPerEval`):上次 passed 且 fingerprint 匹配的直接携入新 summary,只真跑失败/errored/缺失的。所以「补齐一组实验」就是重跑 `niceeval exp <组>` 本身。注意带 eval-id 位置参数时,summary 只含本次计划内的 eval——补跑别用位置参数,否则产出部分快照(见 memory 的 rerun-with-eval-filter-partial-snapshot)。
65
-
66
- ## 评分与退出
67
-
68
- ```sh
69
- niceeval exp compare --strict # soft 低于阈值也判红(CI 用)
70
- niceeval exp compare --budget <usd> # 整轮估算成本上限,累计超了就停止派发新 attempt
71
- ```
72
-
73
- 沙箱型里跑什么校验命令、跑不跑,是 `test(t)` 里的普通代码:`t.sandbox.runCommand` 跑命令,`t.check(result, commandSucceeded())` 断言退出码。
74
-
75
- 退出码:有 `outcome=failed`(含 `--strict` 下 soft 未达标而改判的)或 `outcome=errored` → 非零;全 `passed` / `skipped` 时返回 `0`。`failed` 与 `errored` 在报告里分开统计。
76
-
77
- 每个 eval 的 token 用量与估算成本会出现在控制台和 `summary.json`,跨 agent 对比即得「质量 × 成本」。详见 [Observability](observability.md#用量与成本token--计费)。
78
-
79
- ## 查看结果
80
-
81
- ```sh
82
- niceeval view # 起本地 web,自动打开浏览器,读 .niceeval/
83
- niceeval view .niceeval/<run>/summary.json
84
- niceeval view --port 4317 # 固定端口;被占用时向后找可用端口
85
- niceeval view --no-open # 只启动服务并打印 URL(适合远端 shell / CI)
86
- niceeval view --out site # 目录式静态导出,不启动服务(index.html + 查看器工件)
87
- niceeval clean # 清掉这些历史运行结果
88
- ```
89
-
90
- 每次 `exp` 运行会写 `.niceeval/<timestamp>/summary.json` 与 attempt 级 JSON 工件;`view` 启动本地服务后默认用系统浏览器打开 URL,并直接读这些结构化工件。当前查看器先提供 Next.js evals 风格的密集榜单:按 experiment 聚合、可排序、可搜索、可展开看单个 eval attempt 的断言/错误/用量。结果保存格式见 [Results Format](results-format.md)。
91
-
92
- ## 输出
93
-
94
- ```sh
95
- niceeval exp compare --junit <path> # 写 JUnit XML
96
- niceeval exp compare --quiet # 只出最终汇总
97
- ```
98
-
99
- ## 干跑
100
-
101
- ```sh
102
- niceeval exp compare --dry # 只发现、不真正调用 agent / LLM
103
- ```
104
-
105
- `--dry` 用来检查发现和过滤是否如预期,不烧 token。
106
-
107
- ## 环境变量
108
-
109
- ```sh
110
- # agent 的 API key(按 adapter 决定读哪个)
111
- ANTHROPIC_API_KEY=sk-ant-... # claude-code / 直连
112
- OPENAI_API_KEY=sk-... # codex / 直连
113
- BUB_API_KEY=... # bub
114
- AI_GATEWAY_API_KEY=... # 经网关的变体
115
-
116
- # 沙箱后端认证(auto 探测用)
117
- VERCEL_API_TOKEN=... # 用 vercel sandbox
118
- VERCEL_TEAM_ID=... # Vercel team
119
- # 都没有 → 自动用本地 docker
120
-
121
- # 评判模型(LLM-as-judge)
122
- # 复用上面对应 vendor 的 key,或在 config 里单独指定
123
- NICEEVAL_JUDGE_MODEL=... # config / eval 未指定 judge.model 时的兜底(没有内置默认模型)
124
-
125
- # 调度项(标志 > 环境变量 > config > 内置默认,见下节)
126
- NICEEVAL_RUNS=3 # 对应 --runs
127
- NICEEVAL_MAX_CONCURRENCY=4 # 对应 --max-concurrency
128
- NICEEVAL_TIMEOUT=600000 # 对应 --timeout(毫秒)
129
- NICEEVAL_BUDGET=5 # 对应 --budget(美元)
130
-
131
- # CLI 输出语言
132
- NICEEVAL_LANG=en # en / zh-CN;覆盖系统 locale
133
- NICEEVAL_LOCALE=zh-CN # NICEEVAL_LANG 未设置时生效
134
- ```
135
-
136
- CLI 文案默认跟随 `NICEEVAL_LANG` / `NICEEVAL_LOCALE`、`LC_ALL` / `LC_MESSAGES` / `LANG`。检测到 `zh` 使用 `zh-CN`,检测到其它语言使用 `en`;都没有时默认 `zh-CN`。这只影响 CLI / runtime 输出,不改变 eval 结果里的机器字段,也不翻译 LLM judge prompt。
137
-
138
- ## 配置优先级
139
-
140
- 同一项调度配置的覆盖顺序(高 → 低):
141
-
142
- ```text
143
- CLI 标志 → 环境变量 → niceeval.config.ts → 内置默认
144
- ```
145
-
146
- 例:`--max-concurrency 4` 压过 config 里的 `maxConcurrency: 8`。agent / model / flags 不走 CLI 覆盖,复制 experiment 文件修改。
147
-
148
- ## 典型用法
149
-
150
- 下面每行都先选 experiment,再可选 eval id 前缀:
151
-
152
- ```sh
153
- # 本地开发:零云依赖评一个 coding agent
154
- ANTHROPIC_API_KEY=sk-ant-... niceeval exp local fixtures/
155
-
156
- # 衡量稳定性:跑 10 次看通过率,不早停
157
- niceeval exp local fixtures/button --runs 10 --no-early-exit
158
-
159
- # 评已部署服务的会话质量(URL 是 agent / experiment 私事)
160
- niceeval exp prod-smoke weather billing
161
-
162
- # CI:严格模式 + JUnit
163
- niceeval exp ci --strict --junit .niceeval/junit.xml
164
-
165
- # 只看会发现什么,不烧钱
166
- niceeval exp compare --dry
167
- ```
168
-
169
- ## 相关阅读
170
-
171
- - [Getting Started](getting-started.md) —— 从安装到第一次运行。
172
- - [Runner](runner.md) —— 这些标志背后的调度语义。
173
- - [Config](concepts.md#配置词汇) —— `niceeval.config.ts` 字段。
package/docs/concepts.md DELETED
@@ -1,106 +0,0 @@
1
- # Concepts
2
-
3
- 什么时候读这一篇:
4
-
5
- - 你碰到一个不认识的 niceeval 术语;
6
- - 你在写文档 / 代码,想跟现有用法保持一致;
7
- - 你需要一页纸把整套词汇过一遍。
8
-
9
- 这是一份术语表。两个同义词并存时,**首选写法**用粗体。
10
-
11
- ## 评测核心词汇
12
-
13
- **Eval** —— 评测的最小单元。一个 Eval = 一个 [Task](#task) 跑在一个 [Agent](#agent) 上,由若干 [Scorer](#scorer) 判分。统一由 `defineEval` 定义——会话型和沙箱型不是两个定义函数,`test(t)` 里 `t` 要不要带 `t.sandbox`,取决于引用的 [Agent](#agent) 是不是 `defineSandboxAgent` 构造的(`kind: "sandbox"`),不取决于用哪个 define 函数。每个 Eval 有一个从路径推导的 **id**。
14
-
15
- **Task** —— 要让被测对象完成的"那件事"。不管会话型还是沙箱型,都是一串 `t.send(...)` 的输入——沙箱型只是 `t` 多了 `t.sandbox`,任务本身照样写在 `t.send(...)` 里。Task 描述意图,不描述如何判分。
16
-
17
- **Agent** —— "一条连到 AI 的连接"的抽象,由 experiment 引用。`Agent.kind` 只有两类:`"remote"`(按你自己服务的协议发请求,`defineAgent` 产出)、`"sandbox"`(在 [Sandbox](#sandbox) 里 spawn coding agent 的 CLI,`defineSandboxAgent` 产出)。进程内直调你的函数不是独立的第三类——它只是 `kind: "remote"` 的 `send` 里选择怎么实现的一种写法,而且不是推荐写法(测函数不等于测生产路径,详见[接入你的 Agent · 为什么不直调](../docs-site/zh/guides/connect-your-agent.mdx))。运行器只认统一动词 `send`,`t` 上暴露哪些动作由 Agent 的[能力](#capability)决定——能力不是声明出来的,是 `send` 实际做到了什么的构造证据。niceeval 不定义任何 agent 协议,所以没有 `--url`、没有通用 http target —— 连你自己的服务也是写一个 agent,URL 是它的内部配置。详见 [Agents 与 Adapters](adapters/README.md)。
18
-
19
- **Scorer** / **评分器** —— 把"结果"映射成分数的东西。三类:**值级断言**(`expect` 里的 `includes`/`equals`/`matches`…;`check` 记录并继续,`require` 作为前置条件立即等待并失败中止)、**作用域断言**(`t.succeeded()`/`t.calledTool()`…,在 `test` 结束后对本次 eval run 聚合评估;同一套断言挂在 [Session](#运行与结果) 上则只看这条 session,挂在 [Turn](#运行与结果) 上则只看这一轮)、**LLM-as-judge**(用一个评判模型给开放式回答打分)。沙箱型里,手工在沙箱内跑验证命令,再用 `t.check(result, commandSucceeded())` 判定,本身也是一种 Scorer。
20
-
21
- **Assertion** / **断言** —— Scorer 的一次具体应用,带名字、严重级([gate / soft](#severity))、可选阈值,产出一个 0–1 的分数和过/挂。
22
-
23
- **Outcome** / **判决** —— 一个 Eval 的评分结论,只有四态:`passed` / `failed` / `errored` / `skipped`。规则:显式 `t.skip(reason)` → `skipped`;执行出错(超时、异常、作者错误)→ `errored`;任一 gate 断言不过,或 `--strict` 下有 soft 断言低于阈值 → `failed`;否则 → `passed`。**没有 `scored` 这个中间态**——soft 断言没达标,在非 `--strict` 下就是 `passed`,分数照样如实记录、供横向对比,只是不影响这四态判定。`failed` 只表示断言/评分不通过,`errored` 是环境、超时、adapter、agent runtime 等执行问题,两者互斥,报告、JUnit、CI 都按这个口径分开统计,别把 `errored` 当成 agent 任务做错了。
24
-
25
- **Severity** / **严重级** —— 断言的两档。**gate**:硬性要求,不过即判 `failed`,任何时候都生效。**soft**:质量分,不会单独让 eval 立即 `failed`——`.atLeast(x)` 本身就是 soft 带阈值的写法:非 `--strict` 下低于阈值仍判 `passed`(分数如实记录),`--strict` 下才降级为 `failed`;不调 `.atLeast()` 时走匹配器自己的默认档(如 judge 默认 soft、无阈值,纯记分永不 fail)。
26
-
27
- ## 被测对象与适配器
28
-
29
- **Adapter** / **适配器** —— 某个 [Agent](#agent) 的具体实现,**由用户编写**(niceeval 也内置几个常用 coding agent 的 adapter)。一个 Adapter 实现一个 Agent:远程型(`kind: "remote"`)按你服务的协议发请求,沙箱型(`kind: "sandbox"`)则拥有该 agent 的 CLI 参数、认证方式、默认模型、transcript 位置等全部特殊性。接新 agent = 加一个 Adapter,不动核心。同一个 agent 可有多个变体(如直连 API vs 经网关)。
30
-
31
- **Sandbox** / **沙箱** —— 封装"在哪里、如何隔离地跑命令"的对象。统一接口:`workdir` / `runCommand` / `readFile` / `writeFiles` / `uploadDirectory` / `stop`。实现包括 Docker、Vercel Sandbox、其它三方。命令工作目录通过 `runCommand` / `runShell` 的 `cwd` option 表达,不提供可变的 working directory。
32
-
33
- **workdir** —— 沙箱内 agent 的默认工作目录,也是 git 基线和 diff 采集的锚点;绝对值随后端不同(docker `/home/sandbox/workspace`、e2b `/home/user/workspace`、vercel `/vercel/sandbox`)。API 里所有沙箱侧相对路径、省略的 `targetDir` / `cwd` 都解析到它;eval 作者用相对路径写完整条 eval,必须要绝对路径时读 `t.sandbox.workdir`。详见 [Sandbox · 路径与 workdir](sandbox.md#路径与-workdir一个坐标系)。
34
-
35
- **Sandbox author API** / **沙箱作者 API** —— 沙箱型 eval 里暴露给 `test(t)` 的 `t.sandbox`。它分三类:文件 IO(`writeFiles` / `readFile`)、命令执行(`runCommand` / `runShell`)和结果断言 / diff(`fileChanged` / `diff` / `file`)。沙箱生命周期由 runner 管,`stop()` 不暴露给 eval 作者。
36
-
37
- **Backend** / **后端** —— 某个 Sandbox 的具体实现选择(`docker` / `vercel` / …)。`auto` 表示按环境探测(有云 token 用云,否则用 Docker)。
38
-
39
- **Capability** / **能力** —— `t` 上暴露哪些动作(会话续接、工具调用观测、文件 diff、trace…),完全由**构造证据**决定,不是声明式的能力位:`send` 里接了 `ctx.session` 的续接存取器就有多轮,返回过 `waiting` + `input.requested` 就有 HITL,用官方转换器就带完整性证明(负断言可信),`defineSandboxAgent` 构造就有 `t.sandbox`。`Agent` 接口上不存在 `capabilities` 字段。核心**按构造证据分发**,不按名字分支。这是 [Vision](vision.md) 的承重墙。逐能力的精确义务见[能力参考](../docs-site/zh/reference/capabilities.mdx)与[适配器契约](adapters/contract.md#能力从哪来构造证明不是问卷)。
40
-
41
- **Integration tier** / **接入 Tier** —— 按「Adapter 接到哪里、额外拿到什么观测数据」给接入方式分的三档(和下面的**模型档**是两回事,后者说的是给 agent 指定哪个模型)。**Tier 1(只接 send)**:应用代码一行不改——adapter 适配应用现有对外接口实现 `send`,靠手写事件映射或官方转换器拿到工具断言;应用接口本身暴露模型选择的话,**模型对比**类 [Experiment](#experiment) 也在这一档(`model` 经 `ctx.model` 透传)。**Tier 2(send + OTel)**:还是同一个 `send`,事件来源换成应用发给 niceeval 的 OTel span(应用已埋点则零代码,未埋点补一段通用 OTel 初始化)——买到的是观测质量的跃升:事件流免手写映射、负断言带完整性证明、trace 瀑布图。**Tier 3(侵入改造 + experiment flags)**:改应用内部代码,把内部可变点(prompt、工具集、feature flag)暴露成 experiment 可选的配置(经 `flags` → `ctx.flags` 透传),解锁**完整的 feature A/B test**——对照的不再只是模型,而是应用内部的功能变体。前两档都是**无侵入**的:应用按自己的方式启动,eval 侧不 spawn 应用进程、不另开端口,Adapter 只对着用户前端本来就在用的那个接口收发。三档递进不互斥,详见 [docs-site · Tier](../docs-site/zh/concepts/tier.mdx)。
42
-
43
- **Model tier** / **模型档** —— 给 agent 指定模型的标识(如 `opus`)。由 [Experiment](#experiment) 的 `model` 字段指定;省略则用 agent 原生默认,不经额外的策略层决定。推理努力程度(如 `low`/`medium`/`high`,取值由具体模型定义)是独立的 `reasoningEffort` 字段,经 `ctx.reasoningEffort`/`t.reasoningEffort` 透传,归属与 `model` 一致——都是实验决定、agent 留空。
44
-
45
- ## 数据集与发现
46
-
47
- **Dataset** / **数据集** —— 一组共享同一 `test` 逻辑、只有输入不同的 case。用 `loadYaml`/`loadJson` 读进来,`.map(row => defineEval(...))` 扇出。生成的 id 形如 `sql/0000`、`sql/0001`(零填充 4 位)。
48
-
49
- **Discovery** / **发现** —— 运行器扫 `evals/` 找 `*.eval.ts` 文件,据路径推导 id 并排序。没有目录层面的隐式发现——沙箱型 eval 和会话型 eval 一样,必须有一个 `.eval.ts` 文件;起始文件靠 `test()` 里手工 `t.sandbox.writeFiles` / `uploadFiles` 放进沙箱(见 [Eval Authoring](eval-authoring.md#沙箱型手工把文件放进沙箱)),不靠运行器扫目录。
50
-
51
- ## 运行与结果
52
-
53
- **Runner** / **运行器** —— 调度引擎。负责发现、有界并发执行、重试、早停、缓存,以及把结果交给报告器。详见 [Runner](runner.md)。
54
-
55
- **Experiment** / **实验** —— 一份可签入的**运行配置**,描述「怎么跑这批 eval」:用哪个 [Agent](#agent)、跑几次、过滤哪些、预算多少。由 `defineExperiment` 定义在 `experiments/` 下,id 从路径推导。**一文件 = 一个单一配置**;**一个文件夹 = 一组要并排对比的实验**(`niceeval exp <组>` 跑整组),可比性由目录表达。它**不碰评分**——「怎么算对」是 eval 的事。详见 [Experiments](experiments.md)。
56
-
57
- **Comparison group** / **可对比组** —— `experiments/` 下的一个文件夹,装一组"要并排比较"的单一配置(如同模型下 bub vs codex)。同组互为对照、`niceeval view` 并列展示;不同组是不同的对比维度。比文件内数组多表达了"可比性"语义。详见 [实验怎么组织](experiments.md#实验怎么组织文件夹--一组可对比的实验)。
58
-
59
- **Run** —— 一次 `niceeval` 调用对一批 eval 的完整执行,产出一份 **summary**。
60
-
61
- **Attempt** / **尝试** —— 同一个 eval 的第 i 次重复运行(`runs > 1` 时取通过率用)。这是 runner/result 的执行单位,不是 author-facing API 层。`t` 上的作用域断言会在一个 Attempt 的范围内聚合(全部 session + 全部 turn),不跨 Attempt、也不是上面 Run 那么大的范围。
62
-
63
- **Session** —— 一条会话线。`t` 驱动主 session;`t.newSession()` 返回独立 session,用于并行或隔离的多会话测试。`session.*` 作用域断言只看这条 session 已经发生的事件;这些事件仍会汇入 `t.*` run 级断言。
64
-
65
- **Turn** —— `t.send()` 的一次返回值,对应这一轮的标准事件流片段。带 `message` / `data` / `toolCalls` / `status` / `usage` / `events` 等只读字段,以及一套与 `t` 同名的作用域断言(`turn.calledTool`/`turn.succeeded`/…),作用域收窄成只看这一轮,详见 [Assertions](assertions.md#作用域规则)。
66
-
67
- **EarlyExit** / **早停** —— 一个 eval 取通过率时,先过一次即中止其余 attempt 的策略(可关)。
68
-
69
- **Fingerprint** / **指纹** —— `(eval 代码 + 配置)` 的哈希,用于缓存去重:指纹未变且已通过的,默认跳过。
70
-
71
- **Transcript** —— agent 一次运行的逐事件记录。原始形态是各 agent 自己的 JSONL,被**归一化**成统一事件模型(message / tool_call / tool_result / thinking / error)后供断言和报告消费。详见 [Observability](observability.md)。
72
-
73
- **o11y summary** —— 从**标准事件流**(见 [Observability](observability.md))派生的统计:工具调用计数、读/改的文件、shell 命令、web 请求、思考块数、**耗时、token 用量、估算成本**等。会注入沙箱(`__niceeval__/results.json`),让你在沙箱内手工跑的验证测试能断言 agent 的**行为**而不只是结果。
74
-
75
- **Usage** / **用量** —— 一次运行的 token 计数(`inputTokens` / `outputTokens` / 可选 cache 读写)。随结果带回:remote agent 由 `send` 返回,沙箱型由 transcript 解析器从 agent 的 JSONL 抠出累加。可经 `t.usage` 读、`t.maxTokens()` 断言。
76
-
77
- **Cost** / **成本** —— 用量经配置的价格表(模型 → 每百万 token 单价)换算的估算金额(`estimatedCostUSD`)。让跨 agent 对比从 pass-rate 升级为**质量 × 成本**。`--budget <usd>` 给整轮设上限。详见 [Observability](observability.md#用量与成本token--计费)。
78
-
79
- **Reporter** / **报告器** —— 消费运行结果的插件,可实现分阶段 `onEvent`(`run:start` / `eval:start` / `eval:complete` / `run:summary` 等),也兼容 `onRunStart` / `onEvalComplete` / `onRunComplete`。内置控制台、JUnit、JSON;可接第三方实验跟踪平台。报告器在独立的串行队列上回调,不阻塞执行池。详见 [Reporters](observability.md#reporters)。
80
-
81
- **Artifact** / **工件** —— 落盘的结构化产物,位于 `.niceeval/<时间戳>/`:run 级 `summary.json`,以及 attempt 级 `events.json`、`sources.json`、`trace.json`、`o11y.json`、`diff.json`。每个文件都是 JSON,不是 JSONL / NDJSON。详见 [Results Format](results-format.md)。
82
-
83
- ## 配置词汇
84
-
85
- **`niceeval.config.ts`** —— 项目级配置,`defineConfig` 默认导出。完整字段:
86
-
87
- | 字段 | 类型 | 作用 |
88
- |---|---|---|
89
- | `judge` | `JudgeConfig` | 默认评判模型(见 [Scoring](scoring.md#3-llm-as-judge)) |
90
- | `reporters` | `Reporter[]` | 全局报告器(见 [Observability](observability.md#reporters)) |
91
- | `maxConcurrency` | `number` | 并发上限(见 [Runner](runner.md#调度有界并发)) |
92
- | `timeoutMs` | `number` | 单 eval 超时 |
93
- | `sandbox` | `SandboxOption` | 项目默认 sandbox spec(`dockerSandbox()` 等工厂产出);experiment 可覆盖。两处都没设、又用了沙箱型 agent 时直接报错——没有隐式默认,也没有 `--sandbox` 这种 CLI 覆盖 |
94
- | `pricing` | `Record<string, Price>` | 价格表覆盖,合并在内置快照之上(见 [Observability](observability.md#换算成本价格表从哪来)) |
95
-
96
- agent 不在 config 里注册:每个 experiment 直接引用一个 agent adapter(见 [Experiments](experiments.md#defineexperiment-的形状))。config 只管项目级默认与全局资源。
97
-
98
- **Strict mode** / **严格模式** —— 默认情况下 soft 断言低于阈值仍判 `passed`;`--strict` 下同样的情况改判 `failed`。用于 CI 把质量回归当成红灯。
99
-
100
- **环境预置** —— niceeval 把准备逻辑放在普通代码里。要在跑 agent 前准备环境,放三处之一:这条 eval 的沙箱预置写在 `test(t)` 里(手工 `t.sandbox.writeFiles` / `runCommand`),连 agent / 装 CLI 写在 [`SandboxAgent.setup`](adapters/contract.md#agent-契约),整轮共享的外部服务(mock API、DB)用外部编排(`docker compose` / CI 脚本)起停、经 env 传入。详见 [Sandbox · 环境预置放哪](sandbox.md#环境预置放哪)。
101
-
102
- ## 相关阅读
103
-
104
- - [Architecture](architecture.md) —— 这些名词在模块图里各自的位置。
105
- - [Authoring](eval-authoring.md) —— Eval / Task / Dataset 怎么写。
106
- - [Scoring](scoring.md) —— Scorer / Assertion / Severity / Outcome 的完整手册。
package/docs/e2e-ci.md DELETED
@@ -1,332 +0,0 @@
1
- # E2E CI(部分落地)
2
-
3
- > 状态:核心骨架已落地并在本地跑绿(2026-07-07)。`e2e/` 目录已建:shared factory/profile 套件、从 tier1 拷来的五个被测应用(`e2e/apps/`)、五个 SDK 薄项目(`e2e/projects/`)、`e2e/scripts/verify.mjs`——全矩阵 6 次 CLI 调用(5 个 ci 全绿 exit 0 + ai-sdk-v7 verdicts 按期望 exit 1)全部符合期望表。**尚未落地**:`.github/workflows/e2e.yml`(GitHub secrets 还没建)、`aisdk-transformer` / `http-mapping` 两个纯框架项目、示例冒烟 job(第 6 节,P0 文档债也还没修)、L1 nightly 沙箱矩阵。与原提案的实现偏差:verdicts fixture 挂在最便宜的 ai-sdk-v7 上(纯框架项目未建);`ciExperiment(agent)` 不吃 profile——能力过滤靠"stub 文件是否存在",ci/verdicts 切分靠 `evals` 谓词按 `deliberate-` 前缀;profile 实际字段是 `weatherToolName / calcToolName / usage / sandboxTools / workspaceDir`(hitl 由 `calcToolName` 非空推导);`e2e/apps` 不是逐字节拷贝而是**只保 backend 的协议夹具**——前端、vite/开发工作流、langgraph 的 node 侧文件和 Jaeger compose 都已裁掉(前端不参与被测协议,裁掉能显著缩短 CI 安装),将来 tier1 → e2e/apps 的 drift check 对比范围只框 `src/backend/`;另注意 `budget` 护栏对不报 usage 的 agent 无法执行(见 `memory/e2e-suite-landing-gotchas.md`)。
4
- >
5
- > 本文其余部分保持原提案文本——用真实的 `niceeval exp` 全链路(发现 → 调度 → 断言 → 评分 → 工件 → 退出码)验证框架和官方适配路径,而不是只跑 typecheck。当前 `.github/workflows/ci.yml` 只有 typecheck / site:build / docs 校验,没有任何 workflow 真正执行过 eval。**e2e 全程不允许假 AI**:所有被测应用和框架级项目都调真实模型,CI 里的 key 从 GitHub Actions secrets 注入,本地开发者自己在 `.env` 放真实 key——这和 `examples/zh/origin` 那 6 个示例的既有政策一致(见 `memory/origin-examples-real-ai-credentials.md`),e2e 不搞例外。
6
-
7
- ## 1. 目标
8
-
9
- 一次 e2e CI 要同时证明五件事:
10
-
11
- 1. **完整路径**:从 `evals/` 发现、experiment 展开运行矩阵、`t.*` 断言收集、gate/soft 判决、`.niceeval/<run>/` 工件落盘,到进程退出码,每一环都被真实执行并被机器校验——包括"该红的时候红"(deliberate-fail 必须 exit 1),不是只测 happy path。
12
- 2. **一套 suite,全矩阵可靠通过**:所有 SDK 适配项目共用**同一份** eval/experiment 定义(单一事实来源,见第 3 节),CI 把每个项目都跑一遍。真实模型跑真实调用,单次尝试允许抖动,但有限重试(见 4.1)吸收不了的失败一定是真回归——任何一个 SDK 的适配层回归都会把矩阵打红,不会被"模型这次没答好"这种噪音长期掩盖,也不会被 retry 永久掩盖。
13
- 3. **正反验证所有功能**:套件对每个功能都配正反两条用例——该调工具时 `t.calledTool`、不该调时 `t.notCalledTool`/`t.usedNoTools`、HITL 有 approve 也有 deny、会话有记忆也有 `newSession` 隔离;再加 deliberate-fail/error 验证"断言真的会挂"。防止"断言永真"这类静默失效。
14
- 4. **重复运行统计**:一个 experiment 配 `runs: N, earlyExit: false` 跑重复调度、并发、pass 率计数(niceeval 的字段就叫 `runs`,不叫 pass/trials;`earlyExit` 不关掉的话第一次通过就会 abort 其余 attempt,拿不到完整分布)。真实模型调用要花钱,PR 门禁上 `N` 取小值(如 10),完整的大样本统计(如 100)挪到 nightly 跑,见第 4 节。
15
- 5. **官方适配矩阵**:内建 `uiMessageStreamAgent`、`fromAiSdk`/`fromClaudeSdkMessages`/`fromCodexThreadEvents`/`fromPiAgentEvents` 转换器、自写 `defineAgent` 映射,以及沙箱型 claude-code / codex / bub × docker / e2b / vercel,每条官方路径至少一条 CI 覆盖。
16
-
17
- ## 2. 现状盘点
18
-
19
- ### tier1 的 eval 只当证据看,不复用
20
-
21
- e2e 从 tier1 拷的是**被测应用和 adapter**(各 SDK 真正不同的部分);eval/experiment 套件在 e2e 里围绕功能覆盖矩阵**全新编写**,不搬 tier1 五个项目的 eval 文件——那些是文档叙事的示例,职责不同。但 tier1 的 eval 仍然值得盘点一次,因为它们是"一套 eval 能不能字面共享"的现成证据:`examples/zh/tier1/{ai-sdk-v7,claude-sdk,pi-sdk,langgraph}` 四个项目的 `evals/` 文件名完全一致(`basic-qa` / `weather-tool` / `hitl-approve` / `hitl-deny` / `session-isolation`;`codex-sdk` 是 coding agent,另有 `create-file` / `run-command`),实际 diff 过,**字面并不同构**,差异全部来自 SDK 协议本身:
22
-
23
- - claude-sdk 的工具名是 MCP 命名空间下的 `mcp__demo-tools__get_weather`,不是裸的 `get_weather`;
24
- - basic-qa 是否断言 `t.maxTokens` 取决于该 SDK 协议给不给 usage(UI Message Stream 协议帧里没有 usage,pi-sdk 的 `message_end` 里有);
25
- - codex-sdk 没有 HITL 和 weather 工具,功能集不同。
26
-
27
- 所以 e2e 的"一套 eval"不能靠字面复制或软链实现,要靠参数化新写(见第 3 节);tier1 的这些差异正好告诉我们 profile 里要有哪些开关。另外源码层面确认:`src/runner/discover.ts` 固定扫 `<root>/evals` 与 `<root>/experiments`,`walkFiles` 用 Dirent 的 `isDirectory()/isFile()` 判断——**symlink 条目两者都返回 false,逐文件或逐子目录软链不会被发现**;整个 `evals/` 目录本身做软链虽可行(readdir 跟随路径),但前提是文件逐字同构,上面已经否定了。
28
-
29
- 另外,tier1 各项目本来就已经在调真实模型——`claude-sdk`/`pi-sdk`/`langgraph` 的 backend 用 `process.env.AGENT_MODEL ?? "deepseek-v4-flash"` 走 DeepSeek 兼容端点,`codex-sdk` 默认 `"gpt-5.4"` 走 Codex 代理,凭据映射和 `examples/zh/origin` 一致(见 `memory/origin-examples-real-ai-credentials.md`)。e2e/apps 直接复用这套映射即可,不需要为"跑得起来"发明新机制,只需要在 CI 里把对应 key 从 GitHub Actions secrets 注入(见第 3.2、7 节)。
30
-
31
- ### `examples/zh/ai-sdk` 的 README 是需要清理的过期文档
32
-
33
- `examples/zh/ai-sdk/README.md` 现在写着"默认是 `AGENT_MODE=mock`,不需要 API key",但实测 `src/server.ts`、`src/ai-sdk-runtime.ts`、`src/models.ts` 里**完全没有** `AGENT_MODE` 分支或任何 mock provider——`resolveModel()` 无条件要求真实 `OPENAI_API_KEY`/`DEEPSEEK_API_KEY`,`.env.example` 里也不提 `AGENT_MODE`。这条 mock 声明本身就是过期文档,不是 e2e 该保留或恢复的能力——落地 L1(第 6 节)之前要先把 README 这几行改成真实 key 说明,而不是去"实现"一个文档里写着但代码里没有、而且按项目政策也不该有的 mock 分支。
34
-
35
- ### 框架侧对 CI 重要的事实(源码已确认)
36
-
37
- - **退出码**:全过/跳过 → 0;任一 failed 或 errored → 1;框架崩溃 → 2(`src/cli.ts:461,468`)。CI 判成败首选退出码,细分读 `summary.json`。
38
- - **指纹缓存会静默跳过上次 passed 的 eval**(`src/runner/run.ts` 的 `run()`,匹配逻辑在文件开头 ~60 行附近,以 `cacheKey`/`computeFingerprint` 为核心)。CI 必须加 `--force`,或保证 `.niceeval/` 不跨 run 复用,否则回归会被缓存掩盖。
39
- - **judge 无 key 时 no-op**(`src/scoring/judge.ts` 的 `resolveJudge()`/`buildJudge()`,`if (!resolved.apiKey) return noOpJudge()`):不配 judge key,`t.judge.autoevals.*` 断言静默跳过、不判红。e2e 里"judge 真的在判"必须显式配 judge key 验证,不能靠这条 no-op 蒙混过关——这一点和是否用真实模型无关,是两个独立的开关。
40
- - **可用 flags**:`--runs`、`--no-early-exit`、`--force`、`--junit <path>`、`--strict`、`--max-concurrency`。**不要用** `--json`(死 flag)、`--reporter`(不存在)、`--agent`/`--model`(exp 下报错)、`--sandbox`(已移除)。
41
- - **`runs` 的语义**:每个 `(agent × model × eval)` 组合跑 `runs` 次;被 earlyExit abort 的 attempt 不计入分母。
42
-
43
- ## 3. 核心设计:`e2e/` 里一份共享套件 + 每个 SDK 一个薄项目
44
-
45
- 新建 `e2e/` 目录(与 `examples/`、`src/` 平级),里面三种东西:一份**全新编写**的共享 eval/experiment 定义(不复用 tier1 的 eval 文件)、从 tier1 拷来的被测应用与 adapter、每个 SDK 一个只剩"绑定"职责的薄 niceeval 项目:
46
-
47
- ```text
48
- e2e/
49
- shared/ # 唯一一份 eval / experiment 定义(单一事实来源)
50
- profile.ts # AgentProfile 类型:工具名、能力开关
51
- evals.ts # weatherTool(p) / basicQa(p) / hitlApprove(p) ... 的 factory
52
- experiments.ts # ciExperiment(agent, p) / passN(agent, p) factory
53
- verdicts.ts # deliberateFail() / deliberateError() factory(只进 verdicts 实验)
54
- apps/ # 被测应用,从 examples/zh/tier1/<name> 拷来,原样保留真实调用
55
- ai-sdk-v7/ claude-sdk/ codex-sdk/ pi-sdk/ langgraph/
56
- projects/ # 每个 SDK 一个 niceeval 项目:adapter + profile + 3 行 stub
57
- ai-sdk-v7/
58
- niceeval.config.ts
59
- agents/ai-sdk-v7.ts # 拷自 tier1——adapter 是各 SDK 真正不同的部分,不共享
60
- profile.ts # 该项目的能力声明(见下)
61
- evals/weather-tool.eval.ts # stub:export default weatherTool(profile)
62
- evals/... # 其余 stub,一 eval 一文件,保住可读的 eval id
63
- experiments/ci.ts # stub:export default ciExperiment(agent, profile)
64
- claude-sdk/ codex-sdk/ pi-sdk/ langgraph/
65
- aisdk-transformer/ # 纯框架项目:defineAgent + 官方 fromAiSdk,进程内直调真实 DeepSeek
66
- http-mapping/ # 纯框架项目:defineAgent + 手写映射,进程内直调真实 DeepSeek REST
67
- scripts/
68
- verify.mjs # 元校验:跑 CLI 子进程,对照期望表检查退出码 + summary.json
69
- ```
70
-
71
- ### 3.1 为什么是 factory + profile,不是软链
72
-
73
- eval 定义本来就不 import agent(agent 由 experiment 绑定),所以共享 eval 在框架模型上是顺的;挡路的只有两件事:discover 只认 `<root>/evals` 下的实体文件(symlink 条目不被发现,见第 2 节),以及各 SDK 的协议差异(工具名、usage、HITL 支持)。两个问题一个解法——共享层导出**参数化的 factory**,每个项目用一个 `profile.ts` 声明自己的协议现实,`evals/` 下只放 stub 文件喂给 discover:
74
-
75
- ```typescript
76
- // e2e/shared/profile.ts(与源码同步)
77
- export interface AgentProfile {
78
- weatherToolName: string | null; // "get_weather" 或 MCP 命名空间名;coding agent 为 null
79
- calcToolName: string | null; // 经审批门控的计算器;不支持 HITL(codex-sdk)为 null
80
- searchToolName: string | null; // 网络搜索工具;只有 ai-sdk-v7 的被测应用注册了
81
- usage: boolean; // 协议是否携带 usage(决定 basic-qa 是否断言 maxTokens)
82
- sandboxTools: boolean; // 是否是 coding agent(决定 create-file/run-command 等是否生效)
83
- workspaceDir?: string; // coding agent 的工作目录(eval 直接读磁盘核实)
84
- }
85
- ```
86
-
87
- ```typescript
88
- // e2e/projects/claude-sdk/profile.ts
89
- import type { AgentProfile } from "../../shared/profile.ts";
90
-
91
- export default {
92
- weatherToolName: "mcp__demo-tools__get_weather", // MCP 命名空间是协议现实,不抹平
93
- usage: true,
94
- hitl: true,
95
- sandboxTools: false,
96
- } satisfies AgentProfile;
97
- ```
98
-
99
- ```typescript
100
- // e2e/projects/claude-sdk/evals/weather-tool.eval.ts —— 整个文件就这三行
101
- import { weatherTool } from "../../../shared/evals.ts";
102
- import profile from "../profile.ts";
103
-
104
- export default weatherTool(profile);
105
- ```
106
-
107
- ```typescript
108
- // e2e/shared/evals.ts(节选)
109
- import { defineEval } from "niceeval";
110
- import { includes } from "niceeval/expect";
111
- import type { AgentProfile } from "./profile.ts";
112
-
113
- export function weatherTool(p: AgentProfile) {
114
- return defineEval({
115
- description: "问天气必须调用天气工具(正调)",
116
- async test(t) {
117
- await t.send("北京今天天气怎么样?");
118
- t.succeeded();
119
- t.calledTool(p.weatherToolName, { input: { city: "北京" } });
120
- t.notCalledTool("send_email");
121
- t.check(t.reply, includes("25"));
122
- },
123
- });
124
- }
125
-
126
- export function noToolChitchat(p: AgentProfile) {
127
- return defineEval({
128
- description: "寒暄不该调用任何工具(反调)",
129
- async test(t) {
130
- await t.send("你好,介绍一下你自己。");
131
- t.succeeded();
132
- t.usedNoTools();
133
- t.notCalledTool(p.weatherToolName);
134
- },
135
- });
136
- }
137
- ```
138
-
139
- 断言逻辑改一处、全矩阵生效;某个 SDK 的协议差异只体现在它自己的 profile 里,diff 一眼可见。experiment 同理:`ciExperiment(agent, profile)` 按 profile 过滤 eval 集(`hitl: false` 的项目不排 hitl 用例),每个项目的 `experiments/ci.ts` 也是 3 行 stub。
140
-
141
- **防静默失配**:profile 关掉一个能力,对应 eval stub 就不该存在于该项目——verify.mjs 按 profile 算出每个项目的期望 eval 数,和 `summary.json` 的 results 数对账,防止"少排了用例还全绿"。
142
-
143
- ### 3.2 apps:拷贝自 tier1,真实 key 从 secrets 注入
144
-
145
- `e2e/apps/<name>` 从 `examples/zh/tier1/<name>` 拷贝,保留原有的高位端口分配,**不加任何 mock 分支**——tier1 里这些应用本来就已经是真实调用(`AGENT_MODEL ?? "deepseek-v4-flash"` / Codex 代理),e2e 直接复用同一套凭据映射即可。CI 里跑这些应用时,workflow 把 `DEEPSEEK_API_KEY`(claude-sdk / pi-sdk / langgraph / ai-sdk-v7,分别走 Anthropic 兼容和 OpenAI 兼容两个 base URL)、`CODEX_API_KEY`(codex-sdk)从 GitHub Actions secrets 注入进子进程环境,本地开发者跑同样的套件就在自己的 `.env` 里放同一把 key(和 tier1 项目现在的开发方式完全一致,没有新概念)。
146
-
147
- 为了让 PR 门禁的花费可控,e2e/apps 统一钉住**便宜模型**(DeepSeek `deepseek-v4-flash` / Codex 代理默认模型),更贵的模型对比矩阵挪到 4.2 的 nightly 层。
148
-
149
- 拷贝的代价是和 tier1 漂移。缓解:`scripts/sync-tiers.mjs` 已经在做 origin → tier1 的同步,给它加一条 tier1 → `e2e/apps` 的 check,`pnpm tiers:check` 顺带守住。e2e/apps 的定位是"协议保真的测试夹具",不承担 tier1 的文档叙事职责,允许它为可测性加代码(比如把端口做成可配置的),但不允许加假响应。
150
-
151
- ### 3.3 aisdk-transformer / http-mapping:不依赖任何 app 进程的框架基线
152
-
153
- PR 门禁需要一个启动成本最低、最便宜的真实基线,不依赖起 5 个 SDK app 进程。`e2e/projects/aisdk-transformer` 和 `http-mapping` 不需要 `e2e/apps` 里的任何应用——adapter 在进程内直接调真实 DeepSeek,不经过任何被测服务器:
154
-
155
- ```typescript
156
- // e2e/projects/aisdk-transformer/agents/aisdk-transformer.ts(示意)
157
- import { defineAgent } from "niceeval";
158
- import { fromAiSdk } from "niceeval/agents/ai-sdk";
159
- import { createOpenAI } from "@ai-sdk/openai";
160
- import { generateText, tool } from "ai";
161
- import { z } from "zod";
162
-
163
- const deepseek = createOpenAI({
164
- apiKey: process.env.DEEPSEEK_API_KEY,
165
- baseURL: "https://api.deepseek.com",
166
- });
167
-
168
- export default defineAgent({
169
- name: "aisdk-transformer",
170
- async send(ctx) {
171
- const result = await generateText({
172
- model: deepseek.chat("deepseek-v4-flash"),
173
- messages: ctx.messages,
174
- tools: {
175
- get_weather: tool({
176
- inputSchema: z.object({ city: z.string() }),
177
- execute: async ({ city }) => ({ city, tempC: 25, condition: "晴" }),
178
- }),
179
- },
180
- });
181
- return fromAiSdk(result); // 官方转换器,真实的 generateText 结果直喂
182
- },
183
- });
184
- ```
185
-
186
- - **aisdk-transformer**:`defineAgent` + 官方 `fromAiSdk` 转换器,喂真实 `generateText` 结果(不是伪造成 AI SDK 形状的假数据)。
187
- - **http-mapping**:`defineAgent` + 手写映射,直接 `fetch` DeepSeek 的 chat completions REST 接口,自己把 JSON 响应映射成标准事件流,实现参照 `examples/zh/ai-sdk/adapter/adapter.ts` 裁剪。
188
-
189
- 两个项目复用同一份 shared 套件(profile:`usage: false, hitl: false`),分别覆盖"官方转换器"和"自写映射"两条官方支持的事件流生成方式,都是对真实 DeepSeek 端点的真实调用——省的是"起被测服务器进程"和"更贵的模型"这两项成本,不是省"真实推理"这一项。`runs: N` 统计、deliberate-fail/error、缓存行为这些框架级专项都挂在这两个项目上,因为它们最便宜、启动最快。
190
-
191
- deliberate-fail / deliberate-error 这类"故意红"的 fixture 设计成**对模型输出不敏感**——比如断言一个真实压根不会被调用的工具名(`t.calledTool("this_tool_does_not_exist")`),或者给 adapter 喂一个格式错误的输入触发运行时异常——不管真实模型这次答得好不好,这两条断言都必然会挂,不依赖任何伪造的坏响应。只进 `verdicts` 实验(不进 `ci`),由 verify.mjs 以"期望 exit 1"的方式正向消费——这是第 1 节"正反验证"在退出码层的那一半。
192
-
193
- ## 4. 两层执行模型
194
-
195
- 不再按"要不要秘密"分层(e2e 全程都要真实 key),而是按"多久跑一次、花多少钱、跑多大的模型/样本量"分两层:
196
-
197
- | 层 | 触发 | 模型档位 & runs | 跑什么 |
198
- |---|---|---|---|
199
- | **L0 PR 门禁**(真实 key,便宜模型) | 每个 PR + push main | `deepseek-v4-flash` 等便宜模型;`runs: 3, earlyExit: true`(第一次过就收尾,吸收单次抖动) | `e2e/projects/*` 全矩阵:aisdk-transformer / http-mapping 必跑;apps 里已接入的 SDK 项目逐个纳入。共享套件通过 + verdicts 期望 exit 1 + `runs` 统计(小样本) + 缓存行为 |
200
- | **L1 真实矩阵**(nightly) | schedule cron + 手动 dispatch | 完整官方模型矩阵(gpt-4o、gpt-5.4 等) + `runs: 20~100`,统计更稳的通过率分布 | 同一份共享套件切到完整模型矩阵 + judge 真产分(配 judge key)+ 沙箱矩阵 |
201
-
202
- L0 是合并门禁;L1 是每日健康信号(模型矩阵更贵、样本更大,允许波动,不阻塞 PR)。注意 L0 里"CI 起 app"不违反 tier1 的"eval 不代管进程"原则——起应用的是 CI workflow(扮演用户),不是 eval 框架。`examples/zh/ai-sdk` 的示例冒烟(原第 6 节的"L1")并入 L0 的 PR 门禁,作为独立 job 跑,详见第 6 节。
203
-
204
- ### 4.1 L0 的通过语义:确定性部分精确断言,模型相关部分容忍抖动
205
-
206
- verify.mjs 对每个项目区分两类断言:
207
-
208
- - **确定性部分**(和真实模型答得好不好无关):discover 出的 eval 数量按 profile 精确对账(防止少排用例);`verdicts` 实验里 deliberate-fail/error 的失败/报错次数是精确断言,因为这些 fixture 本来就设计成对模型输出不敏感。
209
- - **模型相关部分**(工具是否调对、judge 打分):允许有限重试吸收单次抖动——`runs: 3, earlyExit: true`,一次通过就算过。断言口径从"exact count"改成"通过率下限",例如 `ci` 实验断言 `passed >= 期望 eval 数 × 0.95`(留一点余量给真实模型的单次波动;如果某个 eval 连续 3 次都没过,说明是适配层真回归,不是抖动,矩阵照样打红)。
210
-
211
- ```javascript
212
- // e2e/scripts/verify.mjs(示意,节选)
213
- const PROJECTS = [
214
- { dir: "projects/aisdk-transformer", exp: "ci", expectExit: 0, expect: toleratedCounts(profileOf("aisdk-transformer")) },
215
- { dir: "projects/aisdk-transformer", exp: "verdicts", expectExit: 1, expect: { failedAtLeast: 1, erroredAtLeast: 1 } },
216
- { dir: "projects/aisdk-transformer", exp: "pass-n", expectExit: 0, expect: { passedAtLeast: 18 /* 2 条 eval × 10 runs,留 10% 余量 */ } },
217
- { dir: "projects/http-mapping", exp: "ci", expectExit: 0, expect: toleratedCounts(profileOf("http-mapping")) },
218
- { dir: "projects/ai-sdk-v7", exp: "ci", expectExit: 0, expect: toleratedCounts(profileOf("ai-sdk-v7")) },
219
- // ... 其余已接入的 SDK 项目
220
- ];
221
- ```
222
-
223
- ### 4.2 L1:nightly 真实矩阵 + 沙箱
224
-
225
- 两块内容。第一块:e2e/projects 里的共享套件切到**完整官方模型矩阵**,`--runs 20` 起步,加 judge key 顺带验证 judge 断言真的产出分数而非 no-op;更大的模型矩阵和样本量本来就更贵更慢,放 nightly 而不是 PR 门禁,失败发通知(GitHub issue / 通知渠道)而不是标红 main。
226
-
227
- 第二块:沙箱矩阵,按"官方适配器 × 沙箱后端"取最小生成集,不做全叉乘:
228
-
229
- | Job | agent | sandbox | 秘密 | eval 集 |
230
- |---|---|---|---|---|
231
- | claude-code × docker | `claudeCodeAgent()` | `dockerSandbox()` | `ANTHROPIC_API_KEY` | `sandbox-smoke` + `examples/zh/coding-agent-skill` 子集(`--runs 1`) |
232
- | codex × docker | `codexAgent()` | `dockerSandbox()` | `CODEX_API_KEY` | `sandbox-smoke`;额外断言 `trace.json` 产生(codex 是唯一原生发 OTLP 的内置 agent,顺带覆盖 tracing 接收链路) |
233
- | bub × docker | `bubAgent()` | `dockerSandbox()` | `BUB_API_KEY` | `sandbox-smoke` |
234
- | claude-code × e2b | `claudeCodeAgent()` | `e2bSandbox({ template: "fasteval-agents" })` | `ANTHROPIC_API_KEY` + `E2B_API_KEY` | `sandbox-smoke` |
235
- | claude-code × vercel | `claudeCodeAgent()` | `vercelSandbox()` | `ANTHROPIC_API_KEY` + Vercel token | `sandbox-smoke`(注意 vercel session 寿命 ~360s 上限,eval 要够小) |
236
-
237
- `sandbox-smoke` 也是 shared 里的 factory(`sandboxTools: true` 的 profile 才排):让 agent 创建一个指定内容的文件,断言 `t.sandbox.fileChanged` + `diff` 内容——目标是验证"沙箱起得来、agent 装得上、transcript 读得回",不是考模型能力,所以提示词要简单到几乎不可能失败。
238
-
239
- 已知约束(来自项目 memory,落地时直接采纳):e2b 的 `base` 模板 node20/481MB 会 OOM,必须用预制的 `fasteval-agents` 模板;vercel 后端默认并发 1(避免 429),session 上限约 360s;沙箱内不要 hardcode `/home/node`。
240
-
241
- L1 每个 experiment 都设 `budget`(建议单 job ≤ $2)和 `timeoutMs`,workflow 层再加 job timeout 兜底——PR 门禁(L0)因为跑得频繁,单个 job 的 `budget` 应该卡得更紧(建议 ≤ $0.5),便宜模型 + 小 runs 是控制单次 PR 成本的主要手段。
242
-
243
- ## 5. 元校验脚本(e2e 的"真正的测试")
244
-
245
- L0 的本体不是那些 eval——eval 是 fixture;本体是 `e2e/scripts/verify.mjs`:它把 CLI 当黑盒子进程跑,对照期望表校验退出码和 `summary.json`(完整示意见 4.1)。verify.mjs 还负责两个专项:
246
-
247
- - **缓存行为**:同一 experiment 连跑两次,第二次**不带** `--force` → 断言 summary 里携入的 cached 结果仍计为 passed 且没有真跑(比较 `durationMs` 或 attempt 工件时间戳,顺带省一次真实调用的钱);第二次**带** `--force` → 断言全部真跑。这把"CI 忘了 --force 会静默跳过"的坑变成被测行为。
248
- - **工件形状**:抽查一个 attempt 目录,断言 `events.json` 是 JSON array、`summary.json` 顶层有 `format/schemaVersion/producer/passed/failed/errored/results[]`、`results[].artifactsDir` 指向存在的目录。防止 results-format 无声漂移。
249
-
250
- ## 6. L0 里的示例冒烟 job
251
-
252
- 目的只有一个:公开示例必须能按 README 跑通。首选 `examples/zh/ai-sdk`——落地前必须先完成第 2 节提到的清理:把 README 里"默认是 `AGENT_MODE=mock`"那几行改成真实 key 说明,因为代码里从来就没有这个开关。清理之后,这个 job 和 L0 里其它项目一样,从 GitHub Actions secrets 注入真实 key:
253
-
254
- ```yaml
255
- # job 示意
256
- - run: pnpm install --dir examples/zh/ai-sdk
257
- - run: node server & # 起 127.0.0.1:5188,等端口就绪
258
- env:
259
- OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
260
- OPENAI_BASE_URL: https://api.deepseek.com
261
- AGENT_MODEL: deepseek-v4-flash
262
- - run: pnpm exec niceeval list # eval 发现冒烟
263
- - run: pnpm exec niceeval exp compare-models --runs 3 --force --junit junit.xml
264
- ```
265
-
266
- 验收前提(落地时先确认,不满足就先修示例本身,而不是加 mock 绕过去):`weather-tool` 等 gate 断言必须能在真实 DeepSeek 调用下稳定通过——即真实模型对天气提问确实会触发 `get_weather` 工具调用。judge 断言没配 judge key 时自动跳过,是否额外配 judge key 走完整评分是 nightly(4.2)的事,不是这个冒烟 job 的职责。
267
-
268
- 这个 job 和 e2e/projects 矩阵的分工:e2e/projects 测的是框架和适配层(fixture 允许为可测性改造);这个 job 测的是"用户照 README 操作会不会翻车"(fixture 一个字都不许为 CI 改)。`examples/zh/tier1/*` 不用这个 job 覆盖——它们的可测化版本已经在 `e2e/apps` 里了。
269
-
270
- ## 7. Workflow 编排
271
-
272
- 新增 `.github/workflows/e2e.yml`:
273
-
274
- ```yaml
275
- on:
276
- push: { branches: [main] }
277
- pull_request:
278
- schedule: [{ cron: "0 3 * * *" }] # L1 nightly
279
- workflow_dispatch:
280
-
281
- jobs:
282
- e2e-matrix: # L0,PR 门禁
283
- runs-on: ubuntu-latest
284
- env:
285
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
286
- CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
287
- steps: [checkout, pnpm install, 起已接入的 apps, node e2e/scripts/verify.mjs]
288
-
289
- e2e-examples: # 第 6 节的示例冒烟,PR 门禁
290
- runs-on: ubuntu-latest
291
- env:
292
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
293
- # 见第 6 节
294
-
295
- e2e-nightly: # L1,仅 schedule / dispatch
296
- if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
297
- strategy: { fail-fast: false, matrix: { target: [claude-docker, codex-docker, bub-docker, claude-e2b, claude-vercel, sdk-projects-full-matrix] } }
298
- runs-on: ubuntu-latest # ubuntu runner 自带 Docker
299
- env:
300
- ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
301
- CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }}
302
- BUB_API_KEY: ${{ secrets.BUB_API_KEY }}
303
- E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
304
- NICEEVAL_JUDGE_KEY: ${{ secrets.NICEEVAL_JUDGE_KEY }}
305
- ```
306
-
307
- 需要建的 GitHub repo secrets:`DEEPSEEK_API_KEY`(claude-sdk / pi-sdk / langgraph / ai-sdk-v7 / examples/zh/ai-sdk 共用,分饰 Anthropic 兼容与 OpenAI 兼容两个 base URL,同一把 key)、`CODEX_API_KEY`(codex-sdk & codex 沙箱)、`ANTHROPIC_API_KEY`(claude-code 沙箱)、`BUB_API_KEY`、`E2B_API_KEY`、Vercel token、`NICEEVAL_JUDGE_KEY`(nightly judge)。值可以复用 `examples/zh/origin` 已经在用的那套凭据(见 `memory/origin-examples-real-ai-credentials.md`),不需要重新申请。
308
-
309
- 所有 job 统一约定:
310
-
311
- - 每次调用 `niceeval exp` 都带 `--force`(禁指纹缓存)+ `--junit <path>`;junit 和整个 `.niceeval/` 目录用 `actions/upload-artifact` 上传,失败时可下载 `events.json` / `trace.json` 排查。
312
- - 成败判据 = 进程退出码;verify.mjs 层再做计数级校验(L0 用容忍区间,见 4.1)。
313
- - 只有 pull_request 触发的 workflow 才能读到 repo secrets(同仓库 PR;fork 来的 PR 默认拿不到 secrets,这是 GitHub 本身的安全模型,不是本设计要解决的问题)——如果未来要支持 fork PR 跑 e2e,需要额外做 `pull_request_target` + 手动审核这类隔离,不在本提案范围内。
314
-
315
- ## 8. 分阶段落地
316
-
317
- 1. **P0(先修文档债)**:把 `examples/zh/ai-sdk/README.md` 里过期的 `AGENT_MODE=mock` 声明改成真实 key 说明,确认 `weather-tool` 等 gate 断言在真实 DeepSeek 调用下能稳定通过——这是第 6 节能落地的前提,和 e2e 骨架搭建可以并行。
318
- 2. **P1(先立骨架)**:建 `e2e/shared` + `e2e/projects/{aisdk-transformer,http-mapping}` + verify.mjs(共享套件、正反 eval、deliberate-fail/error、`runs: N` 统计),申请 `DEEPSEEK_API_KEY` secret,加 `e2e-matrix` job 进 PR 门禁。这一步依赖最小、成本最低——完整路径、正反断言、重复运行统计、退出码、缓存行为全部落网,factory/profile 机制也在最便宜的项目上定型。
319
- 3. **P2**:拷 `examples/zh/tier1/ai-sdk-v7` 进 `e2e/apps`,`e2e/projects/ai-sdk-v7` 接入共享套件、复用 P0 已修好的 secrets——第一个真实 SDK 协议栈进 L0 矩阵,加 `e2e-examples` job。
320
- 4. **P3**:langgraph、pi-sdk、claude-sdk、codex-sdk 逐个接入 `e2e/apps` 并纳入矩阵(凭据映射直接照抄 tier1,不需要额外调研)。给 `sync-tiers.mjs` 加 tier1 → e2e/apps 的 drift check。
321
- 5. **P4**:补 `sandbox-smoke` factory;开 L1 nightly 的 claude-code × docker(第一条真实沙箱链路),随后铺满 L1 矩阵(codex / bub / e2b / vercel + sdk-projects 完整模型矩阵 + judge)。
322
- 6. **之后**:`e2e/projects/{codex-sdk,langgraph}` 的 OTel 瀑布图链路(`spanMapper` / 固定端口)补一条"trace.json 非空"的冒烟断言,纳入 L1;评估 L0 的 retry/容忍区间参数(3.1 提到的 `runs: 3`、95% 阈值)是否需要按项目调整——不同 SDK 的真实抖动率可能不一样。
323
-
324
- ## 9. 明确不做的
325
-
326
- - 不做字面共享:不把同一份 `.eval.ts` 软链/复制进多个项目——discover 不跟随 symlink 条目,且各 SDK 的工具名和能力差异是协议现实,抹平它们等于测一个不存在的协议。共享的单位是 factory,差异收敛进 profile。
327
- - 不和 tier1 共享 eval:`examples/zh/tier1` 的 eval 继续服务文档叙事,e2e 的套件独立编写、独立演进,两边不建同步关系;从 tier1 拷的只有应用和 adapter(受 drift check 约束)。
328
- - 不用任何形式的假 AI/本地 mock server:e2e 全程调真实模型,费用通过便宜模型档位、小 `runs`、per-experiment `budget` 护栏控制,不通过伪造响应控制;PR 门禁和 nightly 的区别是"模型档位、样本量、跑多勤",不是"真的假的"。
329
- - 不测"OTel 派生事件"这类断言——OTel 只喂瀑布图,e2e 要测的是它产出了 `trace.json`,不是它替代了事件映射。
330
- - 不用 `--tag` 做 CI 内的用例切分(当前实现只收单值,与文档不一致;分层用 experiment 的 `evals` 过滤器表达,更明确)。
331
- - 不支持 fork 来的 PR 跑需要 secrets 的 e2e job(GitHub 默认不把 secrets 传给 fork PR;需要更严格的隔离方案的话另开提案)。
332
- - 版本字段只做格式契约校验,不把具体 `producer.version` 写死到测试里;版本不兼容的读取行为由 view loader 自己测。