niceeval 0.3.0 → 0.4.1

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 (102) hide show
  1. package/README.zh.md +11 -0
  2. package/docs/README.md +2 -1
  3. package/docs/adapters/collection.md +1 -1
  4. package/docs/adapters/contract.md +6 -5
  5. package/docs/adapters/reference/claude-code-otel-telemetry.md +1 -1
  6. package/docs/assertions.md +0 -1
  7. package/docs/capabilities-by-construction.md +2 -2
  8. package/docs/cli.md +4 -2
  9. package/docs/concepts.md +2 -2
  10. package/docs/e2e-ci.md +135 -98
  11. package/docs/eval-authoring.md +3 -3
  12. package/docs/experiments.md +7 -4
  13. package/docs/observability.md +9 -5
  14. package/docs/origin-integration.md +18 -18
  15. package/docs/references.md +1 -1
  16. package/docs/reports.md +551 -0
  17. package/docs/results-format.md +3 -3
  18. package/docs/results-lib.md +191 -0
  19. package/docs/runner.md +1 -1
  20. package/docs/sandbox.md +1 -1
  21. package/docs/source-map.md +22 -2
  22. package/docs/tier-sync.md +74 -58
  23. package/docs/view.md +39 -5
  24. package/package.json +29 -3
  25. package/src/agents/ai-sdk.ts +3 -0
  26. package/src/agents/claude-code.ts +18 -1
  27. package/src/agents/codex.ts +3 -2
  28. package/src/agents/index.ts +16 -0
  29. package/src/agents/openai-compat.test.ts +56 -0
  30. package/src/agents/openai-compat.ts +151 -0
  31. package/src/agents/types.ts +3 -1
  32. package/src/cli.ts +4 -3
  33. package/src/context/context.test.ts +78 -1
  34. package/src/context/context.ts +19 -11
  35. package/src/context/session.ts +2 -0
  36. package/src/context/types.ts +11 -5
  37. package/src/i18n/en.ts +3 -4
  38. package/src/i18n/zh-CN.ts +2 -3
  39. package/src/o11y/cost.test.ts +40 -0
  40. package/src/o11y/cost.ts +27 -5
  41. package/src/o11y/derive.ts +2 -2
  42. package/src/o11y/otlp/mappers/claude-code.test.ts +31 -0
  43. package/src/o11y/otlp/mappers/claude-code.ts +24 -0
  44. package/src/o11y/otlp/mappers/index.ts +1 -0
  45. package/src/o11y/otlp/parse.test.ts +127 -0
  46. package/src/o11y/prices.json +176 -119
  47. package/src/o11y/types.ts +2 -1
  48. package/src/report/aggregate.ts +215 -0
  49. package/src/report/compute.ts +405 -0
  50. package/src/report/format.ts +47 -0
  51. package/src/report/index.ts +40 -0
  52. package/src/report/metrics.ts +96 -0
  53. package/src/report/react/CaseList.tsx +70 -0
  54. package/src/report/react/DeltaTable.tsx +79 -0
  55. package/src/report/react/MetricMatrix.tsx +75 -0
  56. package/src/report/react/MetricScatter.tsx +217 -0
  57. package/src/report/react/MetricTable.tsx +71 -0
  58. package/src/report/react/RunOverview.tsx +87 -0
  59. package/src/report/react/Scoreboard.tsx +87 -0
  60. package/src/report/react/cell.tsx +49 -0
  61. package/src/report/react/colors.ts +42 -0
  62. package/src/report/react/data.ts +17 -0
  63. package/src/report/react/fixtures.ts +236 -0
  64. package/src/report/react/format.ts +34 -0
  65. package/src/report/react/index.tsx +34 -0
  66. package/src/report/react/render.test.tsx +303 -0
  67. package/src/report/react/styles.css +302 -0
  68. package/src/report/report.test.ts +667 -0
  69. package/src/report/types.ts +210 -0
  70. package/src/results/copy.ts +200 -0
  71. package/src/results/format.ts +75 -0
  72. package/src/results/index.ts +27 -0
  73. package/src/results/open.ts +207 -0
  74. package/src/results/results.test.ts +359 -0
  75. package/src/results/select.ts +101 -0
  76. package/src/results/types.ts +98 -0
  77. package/src/runner/attempt.ts +3 -1
  78. package/src/runner/report.test.ts +111 -0
  79. package/src/runner/report.ts +52 -1
  80. package/src/runner/reporters/artifacts.ts +1 -1
  81. package/src/runner/reporters/braintrust.test.ts +106 -0
  82. package/src/runner/reporters/braintrust.ts +197 -0
  83. package/src/runner/reporters/index.ts +3 -1
  84. package/src/runner/run.ts +30 -19
  85. package/src/runner/types.ts +17 -0
  86. package/src/sandbox/types.ts +3 -3
  87. package/src/view/app/App.tsx +82 -17
  88. package/src/view/app/components/CostScoreChart.tsx +127 -0
  89. package/src/view/app/i18n.ts +10 -1
  90. package/src/view/app/lib/attempt-route.test.ts +89 -0
  91. package/src/view/app/lib/attempt-route.ts +52 -0
  92. package/src/view/app/lib/chart.ts +72 -0
  93. package/src/view/app/lib/outcome.ts +1 -1
  94. package/src/view/app/types.ts +4 -5
  95. package/src/view/client-dist/app.css +1 -1
  96. package/src/view/client-dist/app.js +18 -18
  97. package/src/view/index.ts +13 -19
  98. package/src/view/loader.test.ts +61 -0
  99. package/src/view/loader.ts +35 -12
  100. package/src/view/shared/types.ts +14 -1
  101. package/src/view/styles.css +43 -0
  102. package/src/view/template.html +0 -1
@@ -25,6 +25,7 @@ export default defineExperiment({
25
25
  description?: string; // 人读
26
26
  agent: Agent; // 跑哪个 agent(adapter 实例)
27
27
  model?: string; // 单个模型(agent 留空);省略=原生默认。跨模型对比写多个实验文件
28
+ reasoningEffort?: string; // 推理努力程度(agent 留空);省略=原生默认。经 ctx.reasoningEffort / t.reasoningEffort 透传
28
29
  flags?: Record<string, unknown>; // feature flags,透传到 ctx.flags / t.flags(见下)
29
30
  runs?: number; // 每个 (agent × model × eval) 跑几次(默认 1)
30
31
  earlyExit?: boolean; // 先过一次即停其余(默认 true)
@@ -35,22 +36,24 @@ export default defineExperiment({
35
36
  });
36
37
  ```
37
38
 
38
- > experiment 只有"运行矩阵"字段,**没有环境起停钩子**。要在跑 agent 前准备环境,放 `test(t)`(沙箱预置)、`SandboxAgent.setup`(连 agent / 装 CLI)或外部编排(整轮共享服务),见 [环境预置放哪](sandbox.md#环境预置放哪)。
39
+ > experiment 只有"运行矩阵"字段,**没有 run / experiment 级环境起停钩子**。要在跑 agent 前准备环境,放 `EvalDef.setup` / `test(t)`(单个 eval 的沙箱预置)、`SandboxAgent.setup`(连 agent / 装 CLI)或外部编排(整轮共享服务),见 [环境预置放哪](sandbox.md#环境预置放哪)。
39
40
 
40
41
  id 从**路径**推导:`experiments/compare/bub-gpt-5.4.ts` → `compare/bub-gpt-5.4`(路径即身份,和 eval 一致,禁止手写 id);其中目录段 `compare/` 就是"可对比组",见 [下节](#实验怎么组织文件夹--一组可对比的实验)。
41
42
 
42
- ### model 与 flags:agent 留空,实验决定
43
+ ### model / reasoningEffort 与 flags:agent 留空,实验决定
43
44
 
44
- agent 定义里**不写死模型、不写死开关**(那样就锁死了复用)。这两样由实验给,经 `ctx` 透传:
45
+ agent 定义里**不写死模型、不写死开关**(那样就锁死了复用)。这几样由实验给,经 `ctx` 透传:
45
46
 
46
47
  - **`model`** —— 单个模型字符串,agent 的 `send` 从 `ctx.model` 拿;省略则不传 `--model`,用 agent CLI 的原生默认。**跨模型对比写多个实验文件**(各钉一个 model),别在一个实验里塞数组。
47
- - **`flags`** —— 任意 KV feature flags,**两处可见**:agent 的 `send`(`ctx.flags`)、eval 的 `test`(`t.flags`)。用来开关联网、注入某个 skill、调 effort、或让某条 eval 只在某 flag 下断言。
48
+ - **`reasoningEffort`** —— 单个推理努力程度字符串(取值由具体模型定义,如 `"low"`/`"medium"`/`"high"`),归属与 `model` 完全一致:agent 的 `send``ctx.reasoningEffort` 拿,eval 的 `test``t.reasoningEffort` 拿;省略则用 agent 原生默认。跨档位对比同样写多个实验文件。
49
+ - **`flags`** —— 任意 KV 的 feature flags,**两处可见**:agent 的 `send`(`ctx.flags`)、eval 的 `test`(`t.flags`)。用来开关联网、注入某个 skill、或让某条 eval 只在某 flag 下断言。
48
50
 
49
51
  ```typescript
50
52
  // experiments/research-mode.experiment.ts
51
53
  export default defineExperiment({
52
54
  agent: codexAgent(),
53
55
  model: "opus", // 模型在实验给,agent 留空
56
+ reasoningEffort: "high", // → ctx.reasoningEffort / t.reasoningEffort
54
57
  flags: { webResearch: true, skill: "memory-v2" }, // → ctx.flags / t.flags
55
58
  runs: 3,
56
59
  });
@@ -136,7 +136,7 @@ otel 在 agent 定义里其实是**两个互不相干的责任,分开放**,别
136
136
 
137
137
  **为什么要分:** 导出配置是「沙箱里怎么发」,mapper 是「发回来怎么读」—— 一个需要沙箱、一个是纯函数,生命周期和测试方式都不同。混在 `setup`/`send` 里,既难单测 mapper、又让 adapter 把 otel 拼装逻辑揉进主流程。`ctx.telemetry` 则统一带上 `{ endpoint, env? }`:env-based agent 拿 `env` 直接 spread,file-based agent 在 `configure` 里用 `endpoint`。
138
138
 
139
- > **claude-code 缺口:** 它根本不发 OTLP(没有 `tracing` 块)。但它的 transcript JSONL 带时间戳,可从 `StreamEvent` + 时间戳**合成 span**(synthetic spans),再走同一个 canonical schema —— 让三个 agent 都有瀑布图、对比表不缺一角。合成器同样住 core o11y,与 mapper 并列。
139
+ > **claude-code:** 已接原生 OTLP(beta 遥测):adapter 的 `tracing.env` 注入 `CLAUDE_CODE_ENABLE_TELEMETRY=1` + `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` + `OTEL_TRACES_*`( `src/agents/claude-code.ts`)。span 层级 `claude_code.interaction llm_request / tool`,只有结构与计时、内容默认脱敏——内容与断言仍来自 transcript 旁读,trace 只补瀑布图。span 量级小(每回合个位数),`selectTraceSpans` 的 small-trace 路径整段保留,不需要专属 mapper。曾考虑过的「transcript 时间戳合成 span」方案不再需要。
140
140
 
141
141
  ### span 怎么归属到轮
142
142
 
@@ -302,18 +302,22 @@ interface Reporter {
302
302
  - **`Artifacts()`** —— 默认写 `.niceeval/<timestamp>/summary.json` 与 attempt 级 JSON 工件(`events.json`、`sources.json`、`trace.json`、`o11y.json`、`diff.json`),供 `niceeval view` 读取。具体格式见 [Results Format](results-format.md)。
303
303
  - **`JUnit(path)`** —— JUnit XML,接 CI 测试报告 UI。
304
304
  - **`Json(path)`** —— 机器可读全量。
305
- - **第三方实验跟踪** —— Braintrust 这类平台,把每次运行作为一个实验上报,跨提交比较。
305
+ - **`Braintrust(config?)`** —— 把一次运行作为一个 Braintrust experiment 上报,每个 attempt 一行:soft 断言按名字记分,gate 断言记在 `gate:` 前缀下(实验 diff 里 gate 回归和 soft 分数回归用同一套机制看);metrics 带 start/end、token 用量与估算成本,metadata 带 agent / model / experiment / flags 身份维度与失败断言明细。`braintrust` 包是可选 peer 依赖(动态 import,没装时 onRunStart 报错并提示安装);鉴权走 `BRAINTRUST_API_KEY` 或工厂参数 `apiKey`。源码 `src/runner/reporters/braintrust.ts`。
306
306
 
307
307
  配置全局或单 eval 专用:
308
308
 
309
309
  ```typescript
310
- // niceeval.config.ts —— 全局,观测所有 eval
311
- defineConfig({ reporters: [Console(), JUnit(".niceeval/junit.xml")] });
310
+ import { Braintrust, JUnit } from "niceeval/reporters";
312
311
 
313
- // 某个 eval 专用
312
+ // niceeval.config.ts —— 全局,观测所有 eval(Console / Artifacts 由 CLI 始终自带,不用写)
313
+ defineConfig({ reporters: [JUnit(".niceeval/junit.xml"), Braintrust({ project: "weather" })] });
314
+
315
+ // 某个 eval 专用:实例只观测引用它的 eval
314
316
  defineEval({ reporters: [Braintrust({ project: "weather" })], async test(t) { ... } });
315
317
  ```
316
318
 
319
+ eval 级 reporter 经作用域包装接入(`scopeReporter`,见 `src/runner/report.ts`):`onEvalComplete` 按 eval id 过滤,`onRunComplete` 收到重新计数的子集汇总;同一实例被多个 eval 引用时合并观测集(共享一个目的地,比如同一个 Braintrust 实验),已经挂在全局 `reporters` 里的实例在 eval 上再列一遍也不会重复上报。
320
+
317
321
  ## 失败分类(可选,沙箱型)
318
322
 
319
323
  沙箱型可开 AI 失败分类:跑完用一个小模型(给它只读探索结果目录的工具:list/read/grep)把失败归为三类:
@@ -1,18 +1,18 @@
1
1
  # Origin 应用接入手册
2
2
 
3
- 这份文档最初是**工单**:`examples/zh/origin/` 下五个独立应用,按应用逐个说明怎么把它们接进 niceeval。**五个应用已经全部接完**(见文末「现状」),产出在 `examples/zh/tier1/<name>/`。本文现在的作用是记录接入时踩过的坑和当前实现的形状,再接一个类似的应用时可以照抄;当前实现的权威来源永远是 `examples/zh/tier1/<name>/` 的源码和各自的 README,本文只是导读。
3
+ 这份文档最初是**工单**:`examples/zh/origin/` 下五个独立应用,按应用逐个说明怎么把它们接进 niceeval。**五个应用已经全部接完**(见文末「现状」),产出按分档铺在 `examples/zh/tier1|tier2|tier3/<name>/`(分层索引见 [examples/zh · 接入分层](../examples/zh/origin/README.md#接入分层origin--tier1--tier2--tier3))。本文现在的作用是记录接入时踩过的坑和当前实现的形状,再接一个类似的应用时可以照抄;当前实现的权威来源永远是各 tier 目录的源码和各自的 README,本文只是导读。
4
4
 
5
5
  先记住三条铁律(接类似应用时依然适用):
6
6
 
7
- 1. **不改 origin 的任何文件。** 接入产物放 `examples/zh/tier1/<同名>/`:从 origin 复制整个应用,被复制的文件保持逐字节不变(除 `package.json` / `pnpm-workspace.yaml` / `tsconfig.json` 三个集成脚手架文件),接入代码全部是**新增**文件。`pnpm run gen:diff-code` 会 diff origin 和 tier1 两个目录生成 before/after 文档页,"应用侧零改动"是这些页面的核心卖点,改一个字节都会破坏它。
7
+ 1. **不改 origin 的任何文件。** 接入产物放 `examples/zh/tier1/<同名>/`:从 origin 复制整个应用,被复制的文件保持逐字节不变(除 `package.json` / `pnpm-workspace.yaml` / `tsconfig.json` 三个集成脚手架文件,以及 `.env.example`——tier 侧要补 judge 独立凭证等 eval 变量),接入代码全部是**新增**文件。`pnpm run gen:diff-code` 会 diff origin 和 tier1 两个目录生成 before/after 文档页,"应用侧零改动"是这些页面的核心卖点,改一个字节都会破坏它;这条铁律由 CI 的 `pnpm tiers:check`(verbatim 校验)看守,见 [tier-sync](tier-sync.md)。
8
8
  2. **协议以实际输出为准。** 动手写映射之前,先把应用跑起来,`curl -N` 打一轮 `/api/chat` 把 SSE 帧看一遍。本文的帧格式描述来自当前代码,但代码会演化,别背文档。
9
9
  3. **被测应用由你自己按它的方式启动,eval 不代管进程、不另开端口。** adapter 只经环境变量(如 `CODEX_SDK_URL`)指向一个已经在跑的实例——没有 `server-lifecycle.ts` 这类"eval 侧拉起子进程"的机制,这是刻意的取舍,理由见[接入你的 Agent · 为什么不直调](../docs-site/zh/guides/connect-your-agent.mdx)同一条脉络(eval 不代管被测进程)。
10
10
 
11
- ## Tier 是什么,这次做到哪一档
11
+ ## Tier 是什么,产出长什么样
12
12
 
13
13
  接入分三档(定义见 [docs-site · Tier](../docs-site/zh/concepts/tier.mdx)):**Tier 1(只接 send)**、**Tier 2(send + OTel)**、**Tier 3(侵入改造 + experiment flags)**。
14
14
 
15
- **本工单做的是 Tier 1 + Tier 2:** 应用代码一行不改,adapter 适配应用现有的 HTTP + SSE 接口;有 OTel 输出的应用(ai-sdk-v7、langgraph、codex-sdk)额外接了 trace 瀑布图。**Tier 3 的建议改法在文末列出,但那是单独的工作,不要顺手做。**
15
+ 三档现在各有物化目录,同一个应用逐层叠 delta:`tier1/<name>`(纯无侵入,全套断言)、`tier2/<name>`(有 OTel 输出的三个应用:ai-sdk-v7、codex-sdk、langgraph——加 telemetry 配置与 spanMapper/收尾宽限,换瀑布图)、`tier3/<name>`(五个应用都有——按文末「Tier 3 侵入点」改应用内部代码,暴露 experiment flags)。哪个应用有哪几层见 [examples/zh 分层索引](../examples/zh/origin/README.md#接入分层origin--tier1--tier2--tier3);层间用 `pnpm tiers:sync` 保持同步(机制见 [tier-sync](tier-sync.md))。本文余下部分讲 Tier 1 的接入配方——那是每个应用的地基。
16
16
 
17
17
  ## 统一的接入配方
18
18
 
@@ -87,11 +87,11 @@ adapter 要这样做:
87
87
 
88
88
  没有审批流的应用(codex-sdk)跳过这一整节,永远不返回 `waiting`。
89
89
 
90
- ### OTel:只画瀑布图,和事件映射完全无关
90
+ ### OTel:只画瀑布图,和事件映射完全无关(这一节的产物在 tier2)
91
91
 
92
92
  不管应用有没有 OTel 输出,事件映射(上面几节讲的)都一样要做——**OTel 现在只喂 `niceeval view` 的调用瀑布图,不产出任何事件,也不影响任何断言**,详见 [Observability · OTLP traces](observability.md#otlp-traces--统一瀑布图)。
93
93
 
94
- 五个应用里,ai-sdk-v7、codex-sdk、langgraph 发 OTel span,claude-sdk、pi-sdk 不发(claude-sdk 的 CLI 遥测只有 metrics+logs,niceeval 只消费 trace spans;pi-agent-core 没有官方 OTel 集成)。有 span 的应用接法都一样:
94
+ 五个应用里,ai-sdk-v7、codex-sdk、langgraph 发 OTel span,claude-sdk、pi-sdk 不发(claude-sdk 的 CLI 遥测只有 metrics+logs,niceeval 只消费 trace spans;pi-agent-core 没有官方 OTel 集成)。这层接线是 Tier 2 的 delta,落在 `examples/zh/tier2/<name>/`(tier1 不配 telemetry、没有瀑布图);有 span 的应用接法都一样:
95
95
 
96
96
  1. `niceeval.config.ts` 钉住固定接收端口:`defineConfig({ telemetry: { port: 4318 } })`——写了这个配置就等于给非沙箱 agent 打开了 OTel 接收。
97
97
  2. 启动应用时,把标准 OTel 环境变量指向这个端口(`OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces` 或应用自己的等价配置)。
@@ -142,7 +142,7 @@ adapter 要这样做:
142
142
 
143
143
  ### langgraph
144
144
 
145
- - 唯一的 Python 应用,也是唯一**完全手写帧映射、零 OTel 依赖用于事件**的应用:自定义 JSON 帧(`tool-input` → `action.called`、`tool-output` → `action.result`(completed)、`tool-output-denied` → `action.result`(rejected)、`text-delta` 累积成 `message`、`tool-approval-request` → `input.requested` + `waiting`)。
145
+ - 唯一的 Python 应用,也是唯一**完全手写帧映射、零 OTel 依赖用于事件**的应用:自定义 JSON 帧(`tool-input` → `action.called`、`tool-output` → `action.result`(completed,帧带 `isError: true` 时 failed)、`tool-output-denied` → `action.result`(rejected)、`text-delta` 累积成 `message`、`tool-approval-request` → `input.requested` + `waiting`)。工具异常靠 `ToolRetryMiddleware(max_retries=0, on_failure="continue")` 落成 `status="error"` 的 ToolMessage——`create_agent` 默认让工具异常炸穿整张图,没有这层就表达不了"执行了但失败"
146
146
  - HITL 标准配方,**approve 端点字段是 `toolCallId`**(别照抄 claude/pi 的 `toolUseId`)。
147
147
  - session 是 `InMemorySaver`,同 pi:应用不要中途重启。
148
148
  - OTel:LangSmith 导出的 span 只用来画瀑布图(`niceeval.config.ts` 固定端口 + 应用侧 `LANGSMITH_TRACING` / `LANGSMITH_OTEL_ENABLED` / `LANGSMITH_OTEL_ONLY` 三个环境变量),事件断言完全不依赖它。LangSmith 的 `BatchSpanProcessor` 调度和 SSE 流关闭是两条独立时间线,adapter 在轮次结束后主动等一小段(grace period)把最后一批 span 收进瀑布图。
@@ -167,21 +167,21 @@ experiment 至少两个:单配置基线 + 一个 `compare-models/` 实验组(ai-
167
167
  - [ ] 有 OTel 的:view 瀑布图非空
168
168
  - [ ] eval README 写明这个应用做不到什么(没有 OTel 的写清楚、负断言不完全靠事件完整性证明的写清楚)
169
169
 
170
- ## Tier 3 备忘(本工单不做)
170
+ ## Tier 3 侵入点(已实现,产出在 examples/zh/tier3/)
171
171
 
172
- 将来要做 feature A/B test 时,每个应用的最小侵入点(原则:加环境变量/请求字段开关,默认行为不变):
172
+ feature A/B test 已落地:每个应用挑一个最小侵入点做成**请求体可选字段**(不是环境变量——环境变量意味着换变体要重启应用,一次 experiment 运行里 A/B 不了;默认行为不变的铁律不变):
173
173
 
174
- - ai-sdk-v7:system prompt、工具集做成请求体可选字段;
175
- - claude-sdk:`allowedTools`、system prompt 提升为环境变量;
176
- - codex-sdk:`threadOptions`(sandbox mode 等)提升为环境变量;
177
- - pi-sdk:system prompt / 注册工具集提升为环境变量;
178
- - langgraph:`HumanInTheLoopMiddleware` 开关、prompt 提升为环境变量。
174
+ - ai-sdk-v7:system prompt(`instructions`)、工具子集(`tools`)→ `tier3/ai-sdk-v7`;
175
+ - claude-sdk:system prompt → `tier3/claude-sdk`;
176
+ - codex-sdk:`threadOptions`sandbox mode → `tier3/codex-sdk`;
177
+ - pi-sdk:system prompt → `tier3/pi-sdk`;
178
+ - langgraph:system prompt(变体各自编译图、共享 checkpointer)→ `tier3/langgraph`。
179
179
 
180
- experiment 侧用 `flags` → `ctx.flags` 透传,写法见 [Experiments](experiments.md)
180
+ experiment 侧用 `flags` → `ctx.flags` 透传,写法见 [Experiments](experiments.md);每个 tier3 目录的 README 有完整的 flags 流动链路。
181
181
 
182
- ## 现状(五个应用已全部接完)
182
+ ## 现状(五个应用已全部接完,三档分层落位)
183
183
 
184
- 本工单描述的五个应用已全部接入并实测通过,产出在 `examples/zh/tier1/<name>/`。实现比本工单最初设想的更简单,主要是两处收紧:
184
+ 本工单描述的五个应用已全部接入并实测通过,Tier 1 产出在 `examples/zh/tier1/<name>/`;OTel 接线后来按分档定义剥进 `tier2/`,侵入改造落在 `tier3/`(见上文各节)。实现比本工单最初设想的更简单,主要是两处收紧:
185
185
 
186
186
  - **没有 `server-lifecycle.ts` 这类"eval 侧拉起应用子进程"的机制。** 五个应用都由使用者自己按各自方式启动(`pnpm start` / `python server.py`),adapter 只经环境变量(`<NAME>_URL`)指向一个已经在跑的实例。这与"eval 不代管被测进程"的既定原则一致,比最初设想的自动拉起子进程更简单也更贴合真实用法。
187
187
  - **OTel 只画瀑布图,不派生任何事件。** 五个应用的事件断言全部来自 `send` 里的官方转换器或手写帧映射,与有没有接 OTel 无关。
@@ -192,4 +192,4 @@ experiment 侧用 `flags` → `ctx.flags` 透传,写法见 [Experiments](experim
192
192
  - **`fromCodexThreadEvents` 补全**:ThreadEvent 的工具项(`command_execution` / `mcp_tool_call` / `file_change` → 配对的 `action.*`)与 `turn.completed` 的 usage 聚合,现在是 `tier1/codex-sdk` 事件断言的唯一数据来源(不再依赖任何 span 派生)。
193
193
  - **`mapCodexSpans`**(`src/o11y/otlp/mappers/codex.ts`,从 `niceeval/adapter` 导出):把 codex 自家 span 命名归一成 canonical GenAI 语义,只用来让瀑布图和内置 `codexAgent` 保持一致。
194
194
 
195
- 五个 `niceeval exp` 基线全绿(`ai-sdk-v7` 的多模型对比也验证过),五个 before/after 文档页已生成并挂进 `docs-site/docs.json` 导航。Tier 3(feature A/B test)仍未做,见上面「Tier 3 备忘」。
195
+ 五个 `niceeval exp` 基线在 Tier 1 阶段全绿实测过(`ai-sdk-v7` 的多模型对比也验证过),五个 before/after 文档页已生成并挂进 `docs-site/docs.json` 导航。tier3 的五组 compare-* 实验也已对着真实 key 整轮实测:四个 prompt A/B 全绿(pi-sdk 的极简变体曾让模型心算跳过工具,见 `tier3/pi-sdk/experiments/compare-prompts/concise.ts` 注释),codex-sdk compare-sandbox 按预期呈现 read-only 变体下 create-file 断言失败。这轮实测还顺带揪出并修掉了 runner 的一个丢结果 bug(`memory/runner-earlyexit-key-misses-experiment.md`)。
@@ -23,7 +23,7 @@
23
23
  ### 调研过、判断不值得抄的(及理由)
24
24
 
25
25
  1. **Tool 遥测是固定的 10 项 `ToolName` 枚举** (`file_read`/`shell`/`web_fetch`/…) + Badge 计数(`O11ySummary.tsx`)。niceeval 走的是 OTel GenAI 语义约定的 canonical trace/mapper(见 [Observability](observability.md#标准事件流与-streamevent)),覆盖面和跨 agent 一致性都更好——这块 niceeval 已经比它强,不用倒退抄。
26
- 2. **整个架构是"每次请求都读 fs 的 Next.js 多页面 live server"。** 没有数据库、没有 API 路由,但需要一个常驻的 `next start` 进程。niceeval 的 `view` 是"一次性烘焙进单个 HTML+JSON 静态产物"(`src/view/index.ts` 的 `renderHtml`),更适合当 CI 附件或单文件分享,不需要起服务就能看。这是刻意的取舍,不打算改成常驻多页应用——如果要抄 `/compare`,数据仍然要在生成 HTML 时一次性烘焙进去,不能假设前端能随时再查 fs。
26
+ 2. **整个架构是"每次请求都读 fs 的 Next.js 多页面 live server"。** 没有数据库、没有 API 路由,但需要一个常驻的 `next start` 进程。niceeval 的 `view` 是"一次性烘焙 HTML+JSON 静态产物"(`src/view/index.ts` 的 `renderHtml`),导出目录扔给任何静态托管就能看,不需要常驻进程。这是刻意的取舍,不打算改成常驻多页应用——如果要抄 `/compare`,数据仍然要在生成 HTML 时一次性烘焙进去,不能假设前端能随时再查 fs。
27
27
  3. **`bin.mjs` 的 `--watch` flag。** 只把 `WATCH=true` 塞进环境变量,代码里没有看到被消费的地方,像是半成品,没必要照抄这个具体实现。
28
28
 
29
29
  ## 相关阅读