niceeval 0.4.3 → 0.4.5

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 (179) 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 +1 -1
  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 +40 -10
  120. package/src/context/types.ts +140 -0
  121. package/src/expect/index.ts +15 -8
  122. package/src/i18n/en.ts +20 -6
  123. package/src/i18n/zh-CN.ts +20 -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/attempt.ts +2 -1
  128. package/src/runner/reporters/artifacts.ts +11 -3
  129. package/src/runner/reporters/live.ts +45 -8
  130. package/src/runner/run.test.ts +50 -0
  131. package/src/runner/run.ts +135 -41
  132. package/src/runner/types.ts +55 -2
  133. package/src/sandbox/types.ts +18 -0
  134. package/src/scoring/types.ts +5 -0
  135. package/src/shared/types.ts +3 -0
  136. package/src/util.test.ts +26 -1
  137. package/src/util.ts +16 -0
  138. package/src/view/app/App.tsx +2 -2
  139. package/src/view/app/components/AttemptModal.tsx +2 -0
  140. package/src/view/app/components/CopyControls.tsx +87 -28
  141. package/src/view/app/i18n.ts +3 -3
  142. package/src/view/client-dist/app.css +1 -1
  143. package/src/view/client-dist/app.js +18 -18
  144. package/docs/README.md +0 -120
  145. package/docs/adapters/README.md +0 -60
  146. package/docs/adapters/authoring.md +0 -179
  147. package/docs/adapters/coding-agent-skills-plugins.md +0 -324
  148. package/docs/adapters/collection.md +0 -128
  149. package/docs/adapters/contract.md +0 -264
  150. package/docs/adapters/reference/agent-eval.md +0 -215
  151. package/docs/adapters/reference/agent-loop-apis.md +0 -69
  152. package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
  153. package/docs/adapters/reference/eve-protocol.md +0 -127
  154. package/docs/adapters/reference/otel-genai.md +0 -107
  155. package/docs/adapters/reference/otel-instrumentation.md +0 -65
  156. package/docs/adapters/targets.md +0 -99
  157. package/docs/architecture.md +0 -140
  158. package/docs/assertions.md +0 -387
  159. package/docs/capabilities-by-construction.md +0 -48
  160. package/docs/cli.md +0 -173
  161. package/docs/concepts.md +0 -106
  162. package/docs/e2e-ci.md +0 -332
  163. package/docs/eval-authoring.md +0 -319
  164. package/docs/experiments.md +0 -157
  165. package/docs/getting-started.md +0 -224
  166. package/docs/multi-agent.md +0 -145
  167. package/docs/observability.md +0 -337
  168. package/docs/origin-integration.md +0 -195
  169. package/docs/references.md +0 -35
  170. package/docs/reports.md +0 -551
  171. package/docs/results-format.md +0 -228
  172. package/docs/results-lib.md +0 -191
  173. package/docs/runner.md +0 -104
  174. package/docs/sandbox.md +0 -276
  175. package/docs/scoring.md +0 -119
  176. package/docs/source-map.md +0 -126
  177. package/docs/tier-sync.md +0 -193
  178. package/docs/view.md +0 -194
  179. package/docs/vision.md +0 -88
package/docs/sandbox.md DELETED
@@ -1,276 +0,0 @@
1
- # Sandbox —— 在哪里跑
2
-
3
- 沙箱回答"在哪里、如何隔离地运行 agent 命令"。它把隔离环境的全部特殊性关进一个统一接口,让 [Adapter](adapters/README.md) 和核心都不必知道底下是 Docker 还是某个三方服务。
4
-
5
- ## 为什么需要沙箱
6
-
7
- 评一个 coding agent 意味着让一个 LLM 在真实文件系统上**执行任意命令**(装包、改文件、跑构建)。这必须隔离:
8
-
9
- - **安全** —— agent 可能跑出危险命令,不能碰你的机器。
10
- - **可复现** —— 每个 case 一套干净环境,互不污染。
11
- - **可并发** —— 几十个 case 同时跑,各自独立。
12
- - **可采集** —— 跑完用 `git diff` 取改动、读 transcript,环境随后销毁。
13
-
14
- ## 后端统一接口
15
-
16
- ```typescript
17
- interface Sandbox {
18
- /** agent 的默认工作目录;所有沙箱侧相对路径的解析基准。见下「路径与 workdir」。 */
19
- readonly workdir: string;
20
-
21
- runCommand(cmd: string, args?: string[], opts?: {
22
- env?: Record<string, string>;
23
- cwd?: string; // 省略 → workdir;相对路径 → 解析到 workdir 下
24
- root?: boolean; // 以 root 跑(默认 false → 非 root);见下「用户与 root」
25
- }): Promise<{ stdout: string; stderr: string; exitCode: number }>;
26
-
27
- runShell(script: string, opts?): Promise<CommandResult>; // 整段 shell
28
-
29
- readFile(path: string): Promise<string>; // 相对路径 → workdir 下
30
- writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>; // targetDir 省略 → workdir
31
- uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>; // 批量(可含二进制);同上
32
- uploadDirectory(localDir: string, targetDir?: string, opts?): Promise<void>; // 同上
33
-
34
- stop(): Promise<void>;
35
- }
36
- ```
37
-
38
- 这是后端实现和 runner 使用的底层接口,所以包含 `stop()`。eval 作者在 `test(t)` 里拿到的是 author-facing 的 `t.sandbox`:只暴露文件 IO、命令执行和结果断言 / diff,不暴露 `stop()`。沙箱生命周期由 runner 统一管理。
39
-
40
- ### 为什么 `runCommand` 和 `runShell` 不合并成一个
41
-
42
- `runCommand` 按 argv 数组传参,不经过 shell 解析——参数原样传给进程,天然不怕参数里带引号、`$`、`;`、反引号等特殊字符,也没有 shell 注入风险。`runShell` 接受一整段脚本交给 shell 解释,专门给需要管道、`&&`、通配符这类 shell 语义的场景用。
43
-
44
- 这不是两个方法碰巧长得像,是故意保留的两种不同意图:eval 里的命令参数经常来自数据集字段或 agent 生成的输出,内容不可控——比如 `runCommand("./verify.sh", [row.filename])`,`row.filename` 就算是 `"a; rm -rf /workspace"` 这种字符串,argv 形式下也只是一个普通参数值,不会被解释成两条命令。如果合并成一个走 shell 的 `run(cmd: string)`,调用者就必须自己把每个动态值转义成安全的 shell 字符串才能拼进去,一旦漏转义就是真实的命令注入。
45
-
46
- 参考过 eve.dev 的 `sandbox.run({ command })`(它下面所有后端都固定走 `bash -lc`,靠调用者自己用 `shellQuote()` 转义)——那套设计合理,是因为 eve 的调用方几乎都是 AI agent 自己的 bash 工具或内部工具核心,生成一整段 shell 命令本来就是它们的原生表达方式,shell 语义是刚需。niceeval 的调用方是写 eval 的人,大多数调用(`runCommand("npm", ["test"])`)根本不需要 shell 语义,不该为了少数需要管道/`&&`的场景让所有调用都背上手动转义的心智负担。
47
-
48
- ## 路径与 workdir:一个坐标系
49
-
50
- 每个后端的 agent 默认工作目录不同——这是**后端的知识,不是 eval 作者的负担**:
51
-
52
- | 后端 | workdir |
53
- | --- | --- |
54
- | docker | `/home/sandbox/workspace` |
55
- | E2B | `/home/user/workspace` |
56
- | Vercel Sandbox | `/vercel/sandbox` |
57
-
58
- 契约一句话:**API 里任何沙箱侧相对路径,一律解析到 `workdir`;省略的 `targetDir` / `cwd` 默认就是 `workdir`;绝对路径原样使用。** 本地侧(宿主机)的相对路径则解析到 eval 定义文件所在目录。两侧各只有一个锚点,学一次就够。
59
-
60
- 为什么 workdir 是唯一正确的默认值:整条流水线都锚定在它上面——git 基线打在那里、agent 的 cwd 在那里、跑完 `git diff HEAD` 采改动在那里、`t.sandbox.fileChanged(...)` 的路径也是对着那里解析的。把起始文件传到任何**别的**目录,agent 看不见它,diff 也采不到它,整条 eval 静默失效。所以对上传起始 workspace 这个最高频调用来说,workdir 不是"常见选择",是唯一能让系统其余部分正常工作的选择——一个参数如果 99% 的调用只有一个正确值,而调用者又不掌握这个值(它随后端变),强制填写就不是"显式更安全",是逼人抄错答案。
61
-
62
- ### 用户会怎么写:before / after
63
-
64
- 没有这个坐标系时,用户被迫自己拼两侧的绝对路径:
65
-
66
- ```typescript
67
- // ❌ before:用户要背下 docker 的路径,还要用 import.meta.url 拼本地绝对路径
68
- const WORKSPACE = new URL("../workspaces/ts-starter/", import.meta.url).pathname;
69
-
70
- export default defineEval({
71
- description: "实现 Button 组件",
72
- async test(t) {
73
- await t.sandbox.uploadDirectory(WORKSPACE, "/home/sandbox/workspace"); // ← docker 专属,切 e2b 即坏
74
- await t.send("在 src/components/Button.tsx 实现 Button,接受 label 和 onClick。");
75
- const test = await t.sandbox.runCommand("npm", ["test"], { cwd: "/home/sandbox/workspace" });
76
- t.check(test, commandSucceeded());
77
- t.sandbox.fileChanged("src/components/Button.tsx");
78
- },
79
- });
80
- ```
81
-
82
- 有坐标系后,同一条 eval:
83
-
84
- ```typescript
85
- // ✅ after:全程零绝对路径,换 dockerSandbox() / e2bSandbox() / vercelSandbox() 零改动切换
86
- export default defineEval({
87
- description: "实现 Button 组件",
88
- async test(t) {
89
- await t.sandbox.uploadDirectory("../workspaces/ts-starter"); // 本地相对 eval 文件;远端默认 workdir
90
- await t.send("在 src/components/Button.tsx 实现 Button,接受 label 和 onClick。");
91
- const test = await t.sandbox.runCommand("npm", ["test"]); // cwd 默认 workdir
92
- t.check(test, commandSucceeded());
93
- t.sandbox.fileChanged("src/components/Button.tsx"); // diff 路径本来就是 workdir 相对
94
- },
95
- });
96
- ```
97
-
98
- 消掉的东西:`import.meta.url` 拼路径的咒语、两处 hardcode 的后端专属绝对路径,以及"切换后端文件落到 agent 视野之外"这个静默 bug 的整个物种。用户对"文件在哪"的心智模型收敛成一句话:**一切相对路径都在 workspace 里**——和 git 的 repo 相对路径同构,不需要关心物理位置。
99
-
100
- ### 逃生舱:`sandbox.workdir`
101
-
102
- 绝对路径不会彻底消失,三种场景会穿透坐标系:往 prompt 里告诉 agent 一个路径、对照 agent 日志/工具输出里出现的绝对路径、`docker exec` 进容器手动调试。这时用 `workdir` 属性,不要背表:
103
-
104
- ```typescript
105
- await t.send(`参考 ${t.sandbox.workdir}/docs/CONVENTIONS.md 里的约定实现组件。`);
106
- ```
107
-
108
- 注意 `$HOME` 这类环境变量不是替代品:`targetDir` 是宿主侧 JS 里拼的字符串,shell 变量根本不展开——`uploadDirectory(dir, "$HOME/workspace")` 会真的创建一个叫 `$HOME` 的目录。运行时 `runShell("pwd")` 探测也不必要:workdir 是后端构造时就确定的静态字符串,声明就能解决的问题不用运行时手段。
109
-
110
- ### 为什么不伪造一个统一的 `/workspace`
111
-
112
- 另一条路是让所有后端都真的提供 `/workspace`(mkdir + symlink 到真实 workdir)。不走这条:`/workspace` 不是 agent 实际的 cwd,agent 的日志、工具输出、报错里出现的全是真实路径,伪造的统一路径会让用户在对照时更糊涂;云后端(vercel/e2b)对用户目录之外的文件系统权限也未必允许。这与「用户与 root」一节是同一处理哲学:**语义跨后端一致(相对路径→workdir),物理值诚实暴露差异(`workdir` 属性)**,不假装统一。
113
-
114
- ### 实现纪律
115
-
116
- 路径解析规则只允许一份实现:收敛在 `src/sandbox/paths.ts`(如 `resolveSandboxPath(workdir, path)`),三个内置后端共用;不允许每个后端各自复制一遍 `startsWith("/")` 判断——规则有多份实现,就会有后端悄悄不一致。`defineSandbox` 自定义后端只需声明自己的 `workdir` 字符串即可获得同一套行为。
117
-
118
- 旧文档曾推荐显式传 `"/workspace"`——它与任何后端的真实 workdir 都不一致,按它写的文件会落在 agent cwd 和 git 基线之外(agent 看不见、diff 采不到)。新代码和新示例都应写成"省略 `targetDir` / `cwd`",必要时通过 `sandbox.workdir` 取真实绝对路径。
119
-
120
- ## 用户与 root
121
-
122
- **默认非 root,按需提 root** —— 命令默认以沙箱的标准**非 root** 用户跑(agent 的自然环境:安全,且 Claude Code 等在 root 下会拒绝 `--dangerously-skip-permissions`)。需要 root 的命令(setup 装系统依赖:`apt-get install …`、`pip install --break-system-packages …`)给 `runCommand` 传 `{ root: true }`。
123
-
124
- ```typescript
125
- // eval setup:只有装系统依赖这步提 root;其余(含 agent、验证)默认非 root。
126
- await sandbox.runCommand("apt-get", ["install", "-y", "openjdk-17-jdk"], { root: true });
127
- await sandbox.runCommand("npm", ["install"]); // 默认非 root,cwd 默认 workdir
128
- ```
129
-
130
- **这套语义跨后端一致**,且与主流沙箱服务同构 —— 各后端把 `{ root: true }` 映射到自己的原生机制:
131
-
132
- | 后端 | 默认用户 | `{ root: true }` 映射 |
133
- | --- | --- | --- |
134
- | docker | `node`(UID 1000) | `exec --user root` |
135
- | E2B | 非 root(`user`) | `commands.run(cmd, { user: "root" })` |
136
- | Vercel Sandbox | 非 root(`vercel-sandbox`) | `runCommand(cmd, { sudo: true })` |
137
- | Daytona | create 时 `os_user` | per-command `user`(规划中) |
138
- | Modal | root | 已是 root → no-op |
139
-
140
- 约定:**默认值(非 root)与 `root` 的语义在所有后端保持一致**,不因后端而变。本就全程 root 的后端把提 root 视作 no-op;完全无法提 root 的后端可不支持(抛错)—— 但这是"不支持",不是"语义不同"。eval 因此不必感知底下是哪个后端。
141
-
142
- ## 后端选择:没有默认值,没有按名字选
143
-
144
- `sandbox` 字段的类型是 `SandboxOption`(= `SandboxSpec`,一个按 `backend` 区分的数据结构),**不接受裸字符串,也不会自动探测环境替你选一个**。沙箱型 agent 必须显式给 `sandbox` 一个工厂函数产出的 spec——写在 experiment 的 `sandbox` 字段,或写在 `niceeval.config.ts` 的 `sandbox` 字段做全项目兜底(`config.sandbox`,experiment 没设时用它)。两处都没设、又用了沙箱型 agent 时,`resolveSandbox()` 直接抛错,不会替你猜环境、不会静默兜底到某个后端。也没有 `--sandbox <name>` 这种 CLI 覆盖——后端选择是 experiment/config 的书面配置,不是运行时临时参数。
145
-
146
- ```typescript
147
- import { defineExperiment } from "niceeval";
148
- import { dockerSandbox } from "niceeval/sandbox";
149
- import { claudeCodeAgent } from "niceeval/adapter";
150
-
151
- export default defineExperiment({
152
- agent: claudeCodeAgent(),
153
- sandbox: dockerSandbox(), // 必填,沙箱型 agent 没有它就报错
154
- });
155
- ```
156
-
157
- ## Sandbox 作为数据结构(带参数)
158
-
159
- 后端名只是个字符串,带不了参数,也没法表达"哪个是镜像、哪个是快照 ID"。和 [agent](adapters/README.md) 一样,sandbox 用**数据结构**定义:工厂函数(从 `niceeval/sandbox` 导出)产出 spec,放进 `experiment.sandbox`。
160
-
161
- ```typescript
162
- import { dockerSandbox, vercelSandbox, e2bSandbox } from "niceeval/sandbox";
163
-
164
- dockerSandbox() // docker:用默认镜像
165
- dockerSandbox({ image: "niceeval-agents:node24" }) // docker:指定镜像
166
- vercelSandbox({ snapshotId: "snap_xxx" }) // vercel:从快照起
167
- e2bSandbox({ template: "niceeval-agents" }) // e2b:指定模板
168
- ```
169
-
170
- `sandbox/resolve.ts` 把 spec 归一化成 `{ backend, image?, snapshotId?, template?, runtime? }`,再按 `backend` 派发到各后端的 `create()` —— **核心仍不按后端名分支**,参数只在对应后端的 `create()` 里消费。
171
-
172
- 参数的典型用途是**预制模板**:把 agent CLI 烘焙进镜像/模板,让后续 eval 跳过安装直接开跑(见 [`sandbox/`](../sandbox/README.md))。
173
-
174
- ## Docker 后端(本地,零云依赖)
175
-
176
- 最常用、最便宜:无需任何云 token,本地有 Docker 即可。要点:
177
-
178
- - **保活容器** —— 用 `node:24-slim` 起一个 tail 日志文件的长生命周期容器,后续命令用 `docker exec` 进去跑(`AutoRemove` 在 stop 时清理)。
179
- - **非 root 用户(默认)** —— 默认以 `1000:1000`(node)跑命令;全局 npm 装到用户目录并入 `PATH`,避免权限问题。命令传 `{ root: true }` 时改以 root 跑(见「用户与 root」)。
180
- - **slim 镜像补全** —— `apt-get install ca-certificates git`(slim 不带)。
181
- - **文件上传** —— 用 tar 打包 `putArchive` 进容器,随后 `chown` 修正属主(putArchive 以 root 写入)。
182
- - **多路复用流** —— Docker 的 exec 流把 stdout/stderr 复用在一条流上(8 字节头 + payload),需要按帧解析。
183
- - **超时** —— 命令级超时,到点销毁流并报错。
184
-
185
- ```typescript
186
- const sandbox = await createSandbox({ backend: "docker", runtime: "node24", timeout });
187
- await sandbox.uploadFiles(workspaceFiles); // targetDir 省略 → workdir
188
- await sandbox.runCommand("npm", ["install"]); // cwd 省略 → workdir
189
- ```
190
-
191
- ## Vercel Sandbox 后端(云,可弹性扩并发)
192
-
193
- 需要 Vercel Sandbox 凭据;显式环境变量路径是 `VERCEL_API_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID`。适合 CI 里大并发、不想本地起 Docker 的场景。要点:
194
-
195
- - `VercelSandbox.create({ runtime, timeout })` 起一台微 VM。
196
- - 处理云沙箱的 session 生命周期,必要时 snapshot + rotate,避免长命令被 session 上限截断。
197
- - 批量 `writeFiles` 上传。
198
-
199
- 接口与 Docker 完全一致,所以 Adapter 代码一字不改就能在两种后端间切换。
200
-
201
- ## E2B 后端(云,微 VM)
202
-
203
- 需要 `E2B_API_KEY`(team 级;`e2b auth login` 后 CLI 也会用它)。要点:
204
-
205
- - `E2BSandbox.create({ template, timeout })` 起一台 [E2B](https://e2b.dev) 微 VM;省略 `template` 用 e2b 默认 `base`(自带 node20)。
206
- - 命令经 `commands.run`(走 bash,支持 `&&` / 管道);`{ root: true }` → `{ user: "root" }`。
207
- - 文件用 `files.read` / `files.write`(文本 + 二进制)。
208
- - node 版本由模板决定 —— `runtime` 字段对 e2b 仅作记录。要 node24 / 烘焙好 agent CLI,用[预制模板](../sandbox/README.md) `e2bSandbox({ template: "niceeval-agents" })`。
209
-
210
- ## 再接一个后端
211
-
212
- 两条路,取决于新后端是不是打算贡献回 niceeval:
213
-
214
- - **贡献进 niceeval**(像 docker/vercel/e2b 那样内置):实现 `Sandbox` 接口的一个类(`create()` + `workdir` + run/read/write/stop/up-down-load;路径解析直接用 `src/sandbox/paths.ts`,不要自己再写一份),在 `sandbox/resolve.ts` 的 `resolveSandbox` / `createBackend` 加一个 `case`,需要带参数就在 `types.ts` 加一个 `XxxSandboxSpec` 并在 `define.ts` 加工厂。
215
- - **只在自己项目里用,不改 niceeval**:用 [`defineSandbox`](adapters/README.md)(`niceeval/sandbox` 导出)——传 `create()` 直接产出一个实现 `Sandbox` 接口的实例,`resolve.ts` 认到 `create` 就直接调用,跳过内置 backend switch,不需要 niceeval 认识这个后端的名字:
216
-
217
- ```typescript
218
- import { defineSandbox } from "niceeval/sandbox";
219
-
220
- export default defineSandbox({
221
- name: "modal", // 只用于展示 / 日志,不参与分发
222
- recommendedConcurrency: 8, // 可选;省略默认 5
223
- create: async ({ timeout, runtime }) => {
224
- // 返回一个实现 Sandbox 接口(run/read/write/stop/...)的实例
225
- return new MyModalSandbox({ timeout, runtime });
226
- },
227
- });
228
- ```
229
-
230
- **核心定义接口,后端各自实现**,两条路都不改核心其余部分。niceeval 的沙箱抽象刻意保持小(只需 run/read/write/stop),让接一个新后端的成本最低。
231
-
232
- ## 沙箱在生命周期里的位置
233
-
234
- 一次 agent eval 中,核心只固定两头,中间全部交给这条 eval 的 `test(t)`:
235
-
236
- ```text
237
- createSandbox(backend, timeout)
238
- → git init && git commit # 打一次空基线,供之后 diff——不管 test() 里写了什么
239
- → SandboxAgent.setup?.(sandbox, ctx) # agent 自己的一次性预置(装 CLI / 写主配置)
240
- → test(t) # ← 交给 eval 作者,顺序由它自己决定:
241
- │ t.sandbox.writeFiles(...) / uploadFiles(...) / uploadDirectory(...) # 默认落到 workdir
242
- │ t.send() # 驱动 agent(Adapter 在沙箱里跑 CLI,解析成 events)
243
- │ t.sandbox.runCommand(...) # 手工跑校验命令,cwd 默认 workdir(可以晚于 t.send(),agent 天然看不到)
244
- │ 断言… # t.sandbox.fileChanged / t.sandbox.diff / t.check(commandSucceeded)
245
- → collectGeneratedFiles() # git diff HEAD
246
- → sandbox.stop() # 销毁
247
- ```
248
-
249
- 核心只固定两件事:**沙箱创建时打一次空 git 基线**,和**销毁前采一次 diff**——这两件事跟"里面放了什么文件"无关,核心不需要知道也不需要预设目录约定。中间"传什么文件、传到哪、什么时候调 agent、什么时候手工跑测试"全部是 `test(t)` 里的普通代码决定,不是核心的固定编排,详见 [Eval Authoring · 沙箱型](eval-authoring.md#沙箱型手工把文件放进沙箱)——Adapter 也只管 `t.send()` 触发的那一次"在沙箱里把 agent 跑起来"。author-facing 的 `t.sandbox` 同时承载立即 IO / 命令执行和最终 diff / 文件变化视图,但不暴露 `stop()`。后端保证 `workdir` 存在且对非 root 用户可写;命令工作目录用 `runCommand` / `runShell` 的 `cwd` option 表达,默认 `workdir`,不提供可变的 `setWorkingDirectory`。预置放哪见下节。
250
-
251
- ## 环境预置放哪
252
-
253
- 要在跑 agent 前准备环境,按职责分摊到三处已有的地方——**每一处都是普通代码,不是框架编排**:
254
-
255
- | 要准备的东西 | 放哪 | 怎么清理 |
256
- |---|---|---|
257
- | 连 agent、装 CLI、写 agent 自己的主配置(每 attempt 一次) | [`SandboxAgent.setup`](adapters/contract.md#agent-契约) | 随沙箱销毁,无需手工清 |
258
- | **这条 eval** 的沙箱预置(写 `.env`、装依赖、按 `t.flags` 注入 skill) | `test(t)` 里的普通代码(`t.sandbox.writeFiles` / `runCommand`) | 随沙箱销毁;要清沙箱外的东西用 `try/finally` |
259
- | **整轮共享**的外部服务(mock API、共享 DB、license) | 外部编排:`docker compose up -d && niceeval exp … && docker compose down`,或 CI 脚本 | 外部编排负责,URL 经 env 传入 agent / eval |
260
-
261
- 为什么这样够用:沙箱内的东西随沙箱销毁自动没了,不需要 teardown;整轮共享的外部资源用 `docker compose` / CI 脚本起停是业界通行做法,比一个 niceeval 专属钩子更少要维护、也不把资源起停逻辑锁进框架。这与"没有隐式 fixture 发现、起始文件手工写进 `test()`"是同一条纪律:**能用更基础的机制表达的,就不在框架里再造一层。**
262
-
263
- ## 性能:复用与预热
264
-
265
- 沙箱冷启动是关键路径上的大头。两个杠杆:
266
-
267
- - **预热池** —— 提前起若干沙箱挂在池里,case 来了直接领,把冷启动移出关键路径。
268
- - **跨 case 复用** —— 同 runtime 的沙箱在一个 case 跑完后重置(`git clean` 回基线)而非销毁,给下一个 case 用。需权衡"复用省时" vs "全新更干净",默认全新,可开复用。
269
-
270
- 这些是 [Runner](runner.md) 的调度职责,沙箱后端只需支持 reset / 快速 create。
271
-
272
- ## 相关阅读
273
-
274
- - [Adapter 契约](adapters/contract.md) —— Adapter 如何通过 `Sandbox` 接口驱动 agent,以及 `setup` 的义务。
275
- - [Runner](runner.md) —— 并发、预热、复用的调度。
276
- - [Vision](vision.md) —— 后端名只用于路由,不进核心行为。
package/docs/scoring.md DELETED
@@ -1,119 +0,0 @@
1
- # Scoring —— 评分器与判决
2
-
3
- 评分把"一个 attempt 的结果"折叠成一个 **Outcome**。niceeval 有五类评分手段,它们产出统一的 `Assertion`(带名字、严重级、分数),最后由判决规则汇总。**这一篇只讲断言收集完之后怎么变成判决**;每类断言具体是什么、看哪一轮、来源哪里,查 [Assertions](assertions.md)。
4
-
5
- 五类(详情见 Assertions 对应小节):
6
-
7
- 1. **值级断言** —— `t.check` / `t.require` 配 `expect` 里的匹配器。`check` 同步记录并继续收集其它断言;`require` 立即等待 matcher 结果,作为前置条件失败中止。见 [Assertions · 值级断言](assertions.md#值级断言)。
8
- 2. **作用域断言** —— `t.succeeded()` / `t.calledTool()` 等,在 `test` 结束后对本次 eval run 聚合评估;同一套断言挂在 `session` 上看单条 session,挂在 `turn` 上只看这一轮。见 [Assertions · API 分组速查](assertions.md#api-分组速查)。
9
- 3. **LLM-as-judge** —— 用一个评判模型给开放式回答打分,`t.judge` / `session.judge` 默认评对应 session,`turn.judge` 默认评当前 turn,细节见下文。
10
- 4. **测试即评分**(沙箱型) —— 手工在沙箱里跑测试与命令,把命令结果交给 `t.check`。
11
- 5. **效率 / 成本断言** —— `t.maxTokens()` / `t.maxCost()` 等,把 token 花费也变成可判的维度。见 [Assertions · 作用域断言共享词汇](assertions.md#作用域断言共享词汇)。
12
-
13
- ## 严重级:gate vs soft
14
-
15
- 每个断言有一档严重级,决定它如何影响判决(完整定义见 [Assertions · 严重级](assertions.md#严重级gate-vs-soft)):
16
-
17
- - **gate** —— 硬性要求,不过 → 整个 eval `failed`,任何时候都生效。`includes` / `equals` 等默认 gate。
18
- - **soft** —— 质量分,不会单独让 eval 立即 fail。`.atLeast(x)` 本身就是 soft 带阈值的写法:非 `--strict` 下低于 x 仍 `passed`(分数照样如实记录),`--strict` 下才改判 `failed`。不调 `.atLeast()` 也不调 `.gate()` 时,走匹配器自己的默认档:`similarity` 默认 soft、阈值 0.6;judge 默认 soft、没有阈值,纯记分,任何时候都不会 fail。
19
-
20
- > 判决只有 **passed / failed / errored / skipped** 四态,没有 `scored` 中间态。"分数不够任何时候都要 fail" → 用 `.gate()`(或默认就是 gate 的匹配器);"分数不够只在 `--strict` 下才算 fail" → 用 `.atLeast(x)`;"只想记个分,永不影响判决" → 不调 `.atLeast()`,用默认走 soft、无阈值的匹配器(如裸的 judge 调用)。
21
-
22
- 这条规则横跨值级匹配器和 judge,行为一致:
23
-
24
- ```typescript
25
- t.check(t.reply, includes("晴")); // 默认 gate
26
- t.check(t.reply, similarity(expected).atLeast(0.8)); // soft + 阈值:非 --strict 只记分;--strict 下 < 0.8 才 fail
27
- t.judge.autoevals.closedQA("礼貌"); // 无阈值 = 默认 soft、纯分数(永不挂)
28
- t.judge.autoevals.closedQA("礼貌").atLeast(0.7); // soft + 阈值:--strict 下 < 0.7 才 fail
29
- ```
30
-
31
- ## 3. LLM-as-judge
32
-
33
- 用于"对不对靠规则说不清"的开放式回答。评判模型与被测 agent **完全分离**,避免自评。
34
-
35
- ```typescript
36
- t.judge.autoevals.factuality(expected).atLeast(0.8); // 事实一致性
37
- t.judge.autoevals.closedQA("是否适合 10 岁小孩理解"); // 闭合式判断
38
- t.judge.autoevals.summarizes(source); // 是否忠实摘要
39
-
40
- const turn = await t.send("解释一下量子隧穿。");
41
- turn.judge.autoevals.closedQA("这一轮回答是否适合高中生理解");
42
- ```
43
-
44
- `closedQA`/`factuality`/`summarizes` 挂在 `t.judge.autoevals.*`、`session.judge.autoevals.*` 和 `turn.judge.autoevals.*` 下,不留平铺别名。judge 就是这三个固定形状,评什么都落进 `closedQA`/`factuality`/`summarizes` 之一,材料也可以通过 `{ on }` 显式传。
45
-
46
- `{ on }` 指定被评的值,`{ model }` 可单次覆盖评判模型。默认材料由接收者决定:`t.judge` / `session.judge` 默认评对应 session 的对话文本;`turn.judge` 默认评这一轮的 `turn.message`。
47
-
48
- > **judge 默认材料按接收者分层。** `t.judge` / `session.judge` 是 session 级,适合评当前会话的整体回答质量或跨轮一致性;`turn.judge` 是 turn 级,只评这一轮的 `turn.message`。要评 sandbox 产物/diff 或其它自定义材料,显式传 `{ on }`,例如 `t.judge.autoevals.closedQA("…", { on: t.sandbox.diff.get(path) }).atLeast(0.7)`。每条断言看哪一轮、各自来源,见 [Assertions](assertions.md)。
49
-
50
- **模型解析优先级**(高 → 低):单次调用的 `{ model }` → 这个 eval 的 `judge.model` → 配置的 `judge.model` → 环境变量 `NICEEVAL_JUDGE_MODEL`。**没有内置默认模型**:解析不到 model 而 eval 用到了 judge 断言,会报清晰错误(而不是静默打向某个写死的模型)。
51
-
52
- ```typescript
53
- // niceeval.config.ts —— 全局默认
54
- defineConfig({ judge: { model: "anthropic/claude-haiku-4-5" } });
55
-
56
- // 某个 eval 覆盖
57
- defineEval({ judge: { model: "anthropic/claude-opus-4-8" }, async test(t) { ... } });
58
- ```
59
-
60
- ## 4. 测试即评分(沙箱型)
61
-
62
- 沙箱型里,你在 `test(t)` 里手工跑的验证命令本身就是评分材料:调 `t.sandbox.runCommand(...)` 跑测试(vitest 或别的什么都行),再用 `t.check(result, commandSucceeded())` 断言退出码 0。命令执行和评分分开,不会再有 `scriptPassed()` / `testsPassed()` 这种既像执行又像断言的 API。
63
-
64
- ```typescript
65
- import { commandSucceeded } from "niceeval/expect";
66
-
67
- const test = await t.sandbox.runCommand("npm", ["test"]);
68
- t.check(test, commandSucceeded());
69
- ```
70
-
71
- 这让你用熟悉的测试语法表达"什么算对",并能断言文件内容、构建结果、甚至 agent 行为(经 `__niceeval__/results.json`)。没有另一层"validation 模式"开关——跑不跑测试、跑什么测试,都是 `test(t)` 里的普通代码决定的。详见 [Authoring](eval-authoring.md#沙箱型手工把文件放进沙箱)。
72
-
73
- ## 5. 效率 / 成本断言
74
-
75
- token 用量是评分的一等维度 —— agent 答对了但烧掉十倍 token,不该和省着用的拿一样的分。这把「质量」和「效率」拆成两组断言,跨 agent 对比时就能同时看通过率和花费。用量自动随结果带回(沙箱型从 transcript 抠,见 [Observability](observability.md#用量与成本token--计费));`maxTokens()` / `maxCost()` 是作用域断言词汇的一部分,挂在 `t` 上看整个 attempt,挂在 `session` 上看单条 session,挂在 `turn` 上看单轮。具体用法、默认严重级见 [Assertions · 作用域断言共享词汇](assertions.md#作用域断言共享词汇)。
76
-
77
- ## 判决规则
78
-
79
- 所有断言收齐后,运行器直接折叠成一个互斥的 **Outcome**——只有四态,没有中间的 `scored`(定义见 [Concepts · Outcome](concepts.md#评测核心词汇)):
80
-
81
- ```text
82
- 显式 t.skip(reason) → skipped
83
- 执行出错(超时 / 异常 / 作者错误) → errored
84
- 任一 gate 断言不过,或 --strict 下有 soft 断言低于阈值 → failed
85
- 否则 → passed
86
- ```
87
-
88
- `failed` 只表示断言 / 评分不通过,`errored` 是环境、超时、adapter、agent runtime 等执行问题——两者互斥,`summary.failed` 与 `summary.errored` 分开计数。看报告、JUnit 或 CI 判红时按这个口径区分"agent 做错了"和"环境出问题了",不要混着看。
89
-
90
- soft 断言(`.atLeast(x)`,或匹配器自己默认走 soft 档)不会单独造成 `failed`——除非开了 `--strict` 且它是带阈值的 `.atLeast(x)`。分数以 chip / 行尾徽章展示在每条 eval 详情里,供横向对比质量用。要让"分数不够"任何时候都 fail,用默认就是 gate 的匹配器,或显式 `.gate()`。
91
-
92
- 多次运行(`runs > 1`)时,eval 的汇总是**通过率**(pass 占比)与平均耗时,而非单一 Outcome。
93
-
94
- ## 自定义评分器
95
-
96
- 值级断言就是 `(value) => number | Promise<number>`,直接写:
97
-
98
- ```typescript
99
- import { makeAssertion, type Assertion } from "niceeval/expect";
100
-
101
- function jsonValid(): Assertion {
102
- return makeAssertion({
103
- name: "jsonValid",
104
- severity: "gate",
105
- score: (value) => { try { JSON.parse(String(value)); return 1; } catch { return 0; } },
106
- });
107
- }
108
-
109
- t.check(t.reply, jsonValid());
110
- ```
111
-
112
- 需要跨多次运行聚合的指标(如 pass@k、平均工具数),在 reporter 层做,见 [Observability](observability.md#reporters)。
113
-
114
- ## 相关阅读
115
-
116
- - [Assertions](assertions.md) —— 每条断言做什么、看哪一轮、来源哪里(值级 / 作用域 / sandbox 结果 / 轮级的完整速查表)。
117
- - [Authoring](eval-authoring.md) —— 断言出现在哪种 eval 里。
118
- - [Observability](observability.md) —— transcript / o11y,作用域断言的数据来源。
119
- - [Concepts](concepts.md) —— Severity / Outcome 的术语定义。
@@ -1,126 +0,0 @@
1
- # Source Map —— 文档行为 → 实现文件
2
-
3
- 文档是设计依据。这一页把每条文档行为映射回具体源码文件(参考 crabbox 的做法),
4
- 方便对照「设计 vs 实现」;如果代码实现与文档设计不一致,应进一步讨论并决定是修代码、修设计,还是记录为明确的阶段性差异。niceeval 以 TS 源码经 `tsx` 运行,无编译步骤(`bin/niceeval.mjs` 注册
5
- `tsx/esm/api` 后加载 `src/cli.ts`)。
6
-
7
- ## 总览:模块 → 文件
8
-
9
- | 设计文档里的概念 | 实现文件 |
10
- |---|---|
11
- | 核心类型契约(聚合 facade;类型按域住各自目录) | `src/types.ts`(re-export)← `src/shared/types.ts`(原子)、`src/o11y/types.ts`、`src/sandbox/types.ts`、`src/agents/types.ts`、`src/scoring/types.ts`、`src/context/types.ts`、`src/runner/types.ts` |
12
- | 公开导出(`niceeval`,eval 作者用的核心面) | `src/index.ts` |
13
- | 公开导出(`niceeval/adapter`,Agent/Adapter) | `src/agents/index.ts` |
14
- | 公开导出(`niceeval/sandbox`,Sandbox) | `src/sandbox/index.ts` |
15
- | `defineEval` / `defineConfig` / `defineExperiment` / `defineAgent` / `defineSandboxAgent` / `defineSandbox` | `src/define.ts` |
16
- | `requireEnv` / 工具 | `src/util.ts` |
17
-
18
- ## Agents 与 Adapters([adapters/contract.md](adapters/contract.md) / [adapters/authoring.md](adapters/authoring.md))
19
-
20
- | 行为 | 文件 |
21
- |---|---|
22
- | Agent 契约(`kind: "sandbox" | "remote"`,无能力位字段)/ 三类配置归属 | `src/agents/types.ts`(`Agent` / `AgentContext` / `AgentSession` / `SpanMapper`) |
23
- | 能力调用守卫(缺声明的动作第一次调用即报清晰错误;conversation gate 第二轮起) | `src/context/context.ts`(`capabilityGuard`) |
24
- | 逐 API 适配义务(send / newSession / respond 的运行器侧翻译) | `src/context/session.ts`(`SessionManager` / `RunSession`)、`src/context/context.ts` |
25
- | `defineSandboxAgent` / `defineAgent`(`kind: "sandbox" | "remote"`,无能力位字段) | `src/define.ts` |
26
- | `shared` 工具袋(ensureInstalled / captureLatestJsonl(可按 sessionId 精确定位)/ writeFile / extractJsonlFromStdout / codexThreadId / firstJsonField / shellQuote / diagnoseFailure / parseCodex·parseClaudeCode·parseBub) | `src/agents/shared.ts` |
27
- | 采集矩阵(collection.md:每 agent 的通道 / 字段来源) | `src/agents/{claude-code,codex,bub}.ts`(采集)+ `src/o11y/parsers/*.ts`(字段提取) |
28
- | `fromAiSdk`(AI SDK 结果 → 标准事件流,v4/v5/v7 字段漂移兜底;v7 tool approval → `input.requested` + `status: "waiting"`) | `src/agents/ai-sdk.ts`(+ 同目录 `.test.ts`) |
29
- | 内置 adapter(claude-code / codex / bub) | **由被测项目自带**(`agents/*.ts`),niceeval 提供 `shared` + 解析器 |
30
- | `uiMessageStreamAgent`(AI SDK UI Message Stream 协议的内置无侵入 adapter) | `src/agents/ui-message-stream.ts` |
31
-
32
- ## Coding Agent Skills / Plugins DX([adapters/coding-agent-skills-plugins.md](adapters/coding-agent-skills-plugins.md))
33
-
34
- | 行为 | 文件 |
35
- |---|---|
36
- | Claude Code skill / MCP setup | `src/agents/claude-code.ts`(`ClaudeCodeConfig.skills` / `mcpServers`) |
37
- | Codex skill / MCP setup | `src/agents/codex.ts`(`CodexConfig.skills` / `mcpServers`) |
38
- | bub Python plugin setup | `src/agents/bub.ts`(`BubConfig.pythonPlugins`) |
39
- | 本地 skill A/B 示例 | `examples/zh/coding-agent-skill/experiments/*.ts` |
40
-
41
- ## 标准事件流与可观测性([observability.md](observability.md))
42
-
43
- | 行为 | 文件 |
44
- |---|---|
45
- | 原始 transcript → 标准 `StreamEvent[]` + 用量 + 压缩计数 | `src/o11y/parsers/{codex,claude-code,bub}.ts`、`parsers/index.ts`(`ParsedTranscript`;无按名字分派的入口,adapter 直连具体 parser) |
46
- | 规范工具名归一(共享基表 + per-agent 差异叠加) | `src/o11y/tool-names.ts` |
47
- | 原生 OTLP span → canonical GenAI semconv(mapper 由 Agent 经 `spanMapper` 声明,core 不按名字分支;缺省走通用 heuristic) | `src/o11y/otlp/mappers/{codex,bub}.ts`、`src/o11y/otlp/canonical.ts`(`heuristicTag` / `mapGenericSpans`) |
48
- | run 级共享 OTLP 接收 + 逐轮归属(traceparent → `ctx.telemetry.headers`;窗口兜底 + 未确认时该 agent 轮次串行) | `src/o11y/otlp/turn-otel.ts`(`AgentOtelChannel` / `OtelReceiverPool`);接线在 `src/runner/attempt.ts`(池取通道)与 `src/context/session.ts`(`sendWithOtel`:归属 / 派生 / 合并) |
49
- | 固定端口 / 自定义接收 host 模式(`defineConfig({ telemetry: { host, port } })`,niceeval 项目内唯一入口,不读环境变量) | `src/runner/run.ts`(`OtelReceiverPool` 取 `config.telemetry.port`)、`src/runner/attempt.ts`(`config.telemetry.host`)、`src/o11y/otlp/receiver.ts`(`makeTraceReceiver(port)`,端口被占用时报 `otel.portInUse`) |
50
- | `deriveRunFacts`(toolCalls / subagents / parked / compactions) | `src/o11y/derive.ts` |
51
- | o11y 摘要(注入 `__niceeval__/results.json` 的字段) | `src/o11y/derive.ts`(`buildO11ySummary`) |
52
- | codex 用量从 `turn.completed.usage` 抠出 | `src/o11y/parsers/codex.ts` |
53
- | 用量 → 成本(实测优先 → 用户覆盖 → 内置快照) | `src/o11y/cost.ts` |
54
-
55
- ## Sandbox([sandbox.md](sandbox.md))
56
-
57
- | 行为 | 文件 |
58
- |---|---|
59
- | `Sandbox` 统一接口 | `src/sandbox/types.ts`(`Sandbox`) |
60
- | Docker 后端(dockerode,node:24-slim,非 root,tar 上传) | `src/sandbox/docker.ts`(编排)+ `src/sandbox/docker-stream.ts`(exec 流解复用 / tar 工具) |
61
- | 三后端共享工具(shellQuote / find 脚本构造 / 宿主文件遍历) | `src/sandbox/shell.ts`、`src/sandbox/local-files.ts` |
62
- | 后端选择(`auto` / `docker` / `vercel`,核心不按名字分支) | `src/sandbox/resolve.ts` |
63
- | `defineSandbox`(自定义后端逃生舱:`create()` 直接产出 `Sandbox` 实例,`resolve.ts` 里 `r.create` 优先于内置 backend switch) | `src/define.ts`、`src/sandbox/resolve.ts`(`createBackend`) |
64
- | 沙箱编排固定段(git 基线 / 采 diff;起始文件上传已改为 `test()` 里手工调用,不再是固定段) | `src/runner/sandbox-prep.ts` |
65
-
66
- ## Scoring([scoring.md](scoring.md))
67
-
68
- | 行为 | 文件 |
69
- |---|---|
70
- | 值级断言匹配器(includes / equals / matches / similarity / satisfies / makeAssertion) | `src/expect/index.ts` |
71
- | 作用域断言(succeeded / calledTool / event / fileChanged / notInDiff …) | `src/scoring/scoped.ts` |
72
- | 断言收集器(延迟评估 + 链式 gate/soft/atLeast) | `src/scoring/collector.ts` |
73
- | LLM-as-judge(OpenAI 兼容 /chat/completions) | `src/scoring/judge.ts` |
74
- | 判决规则(passed / failed / errored / skipped,无 `scored` 中间态) | `src/scoring/verdict.ts` |
75
-
76
- ## `t` 上下文与会话([eval-authoring.md](eval-authoring.md))
77
-
78
- | 行为 | 文件 |
79
- |---|---|
80
- | 构造 `t`(send / reply / newSession / check / 作用域断言 / judge / sandbox) | `src/context/context.ts` |
81
- | 会话驱动(多轮 send → agent.send,事件 / 用量累加,newSession) | `src/context/session.ts` |
82
- | 控制流信号(skip / require 失败 / turn 失败) | `src/context/control-flow.ts` |
83
- | `t.sandbox.file(path)` 延迟引用(到 finalize 才读沙箱文件) | `src/context/context.ts`(`FileRef`) |
84
-
85
- ## Runner / CLI / Experiments([runner.md](runner.md) / [cli.md](cli.md) / [experiments.md](experiments.md))
86
-
87
- | 行为 | 文件 |
88
- |---|---|
89
- | 发现(evals/ 的 *.eval.ts,experiments/ 的实验,路径推导 id) | `src/runner/discover.ts` |
90
- | 有界并发调度 + 早停 + budget 在飞预扣 | `src/runner/run.ts` |
91
- | 单 attempt 生命周期(沙箱 / OTLP 接收器 Scope、超时硬边界、沙箱编排固定段) | `src/runner/attempt.ts` |
92
- | 指纹缓存((eval 源码 + 运行配置) 哈希,跨 run 结果携入) | `src/runner/fingerprint.ts` |
93
- | reporter 编排 + 运行级汇总 + eval 级 reporter 作用域(scopeReporter / filterSummary) | `src/runner/report.ts` |
94
- | remote 占位 Sandbox / eval 级本地路径视图(Proxy) | `src/runner/remote-sandbox.ts` |
95
- | 报告器(Console / Json / JUnit / Live / 符号表) | `src/runner/reporters/{console,json,live,table,shared,index}.ts` |
96
- | Braintrust 上报(运行 → experiment,attempt → 一行) | `src/runner/reporters/braintrust.ts` |
97
- | eval 级折叠 / 计票口径(CLI 表格与 view 共用) | `src/shared/outcome.ts` |
98
- | 本地结果保存格式(`.niceeval/<run>/summary.json` + attempt 级 JSON 工件) | `src/runner/reporters/artifacts.ts`、`src/runner/types.ts`(`RunSummary` / `EvalResult`) |
99
- | CLI(exp / list / view / clean / init,--help,parseArgs 表驱动,.env 加载,NICEEVAL_* 环境变量层) | `src/cli.ts` |
100
- | 数据集加载器(loadJson / loadYaml) | `src/loaders/index.ts` |
101
-
102
- ## Results Lib 与 Reports
103
-
104
- 设计文档:[results-lib.md](results-lib.md) / [reports.md](reports.md) / [view.md](view.md) 合流一节。两个提案的首版实现:
105
-
106
- | 行为 | 文件 |
107
- |---|---|
108
- | `openResults`:目录扫描 / 版本分流(skipped)/ 懒加载句柄 / 快照切片 | `src/results/open.ts` |
109
- | 布局与版本知识(attempt 目录规则、summary 分类;与 writer 的 `artifacts.ts` 同规则) | `src/results/format.ts` |
110
- | `latestPerExperiment`(残缺警告)/ `dedupeAttempts`(身份键去重) | `src/results/select.ts` |
111
- | `copyRun`(发布原语:按工件种类瘦身复制 + summary 重建) | `src/results/copy.ts` |
112
- | 句柄契约(RunHandle / AttemptHandle 含 `.ref` / SnapshotHandle / AttemptRef) | `src/results/types.ts` |
113
- | `defineMetric` 与内置指标(outcome 逐项表态) | `src/report/metrics.ts` |
114
- | 两级聚合引擎 / 维度 / MetricCell 计算 | `src/report/aggregate.ts` |
115
- | 七个计算函数(table / matrix / scoreboard / scatter / overview / delta / cases) | `src/report/compute.ts` |
116
- | 组件数据契约(TableData … CaseListData;组件 props 的家) | `src/report/types.ts` |
117
- | 七个 React 组件 + 稳定散列配色 + styles.css | `src/report/react/`(入口 `index.tsx`;演示 `scripts/report-react-demo.tsx`) |
118
- | view attempt 深链(`#/attempt/<run>/<result>`,路由参数即 AttemptRef) | `src/view/app/lib/attempt-route.ts`、`src/view/app/App.tsx`、`src/view/loader.ts`(`withViewRefs` 注入) |
119
- | **未落地** | 写入面 `createRunWriter`、view / `Artifacts()` reporter 改吃本库、memory-evals 静态导出流水线(reports.md 场景三) |
120
-
121
- ## 与设计文档的已知差异(实现取舍)
122
-
123
- - **judge 走 OpenAI 兼容 `/chat/completions`**,base/key 解析顺序:`judge.baseUrl/apiKeyEnv` → `NICEEVAL_JUDGE_BASE`/`CODEX_BASE_URL` → OpenAI 官方。这样在只有 OpenAI 兼容代理(无 Anthropic key)的环境里 judge 自动复用代理。model 解析:eval/config 的 `judge.model` → `NICEEVAL_JUDGE_MODEL`;**没有内置默认模型**,解析不到而用到 judge 断言时报清晰错误。
124
- - **MVP 范围**:`niceeval view` 已实现为本地 web 查看器;`init`、指纹缓存、Vercel/E2B 沙箱、失败分类、budget/strict/tag/JUnit flag 已实现。`watch` 仍未实现。运行器支持 remote `defineAgent` 的会话型 eval;文件写入、diff、验证命令仍只属于沙箱型 agent。
125
- - **TestContext 类型**:用一个宽接口承载全部动作(运行时按 capability 守卫),而非文档设想的 TS 条件类型 —— 因为被测项目经 `tsx` 运行(不做类型检查),宽接口更省心且不影响运行时正确性。
126
- - **接收者与评分 API 已按目标设计落地**:作用域断言对齐 eve 的接收者模型(`t` = run 级聚合视图、`session` = 单 session snapshot、`turn` = 单轮 snapshot,同一套作用域断言词汇)、会话驱动 API 补齐到 eve 形状(`t.send(input)` / `t.sendFile(path, text?)` / `t.requireInputRequest` / `t.respond` / `t.respondAll` / `t.newSession()`)、结果读取字段按接收者分开、judge 按接收者决定默认材料、Verdict/Outcome 合并成单一 `Outcome`、链式断言收窄成 `.atLeast(x)` / `.gate(x?)`、移除 `defineEval.workspace`、`t.sandbox` 作为唯一沙箱作者 API 且不暴露 `stop()`、验证命令改成 `t.sandbox.runCommand` + `t.check(result, commandSucceeded())`、judge 收窄成固定的 `autoevals.{closedQA,factuality,summarizes}`、`t.transcript` 命名空间已移除。