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.
- package/README.md +21 -12
- package/README.zh.md +8 -9
- package/docs-site/.mintignore +13 -0
- package/docs-site/AGENTS.md +46 -0
- package/docs-site/concepts/adapter.mdx +287 -0
- package/docs-site/concepts/assert.mdx +243 -0
- package/docs-site/concepts/drive.mdx +118 -0
- package/docs-site/concepts/evals.mdx +108 -0
- package/docs-site/concepts/experiment.mdx +37 -0
- package/docs-site/concepts/hitl.mdx +83 -0
- package/docs-site/concepts/judge.mdx +129 -0
- package/docs-site/concepts/overview.mdx +98 -0
- package/docs-site/concepts/tier.mdx +42 -0
- package/docs-site/docs-ref/00-index.md +23 -0
- package/docs-site/docs-ref/01-page-types.md +43 -0
- package/docs-site/docs-ref/03-style-rules.md +45 -0
- package/docs-site/docs-ref/04-code-examples.md +37 -0
- package/docs-site/docs-ref/05-dx-failure-paths.md +45 -0
- package/docs-site/docs-ref/06-checklists.md +53 -0
- package/docs-site/docs.json +256 -0
- package/docs-site/example/ai-agent-application.mdx +148 -0
- package/docs-site/example/claude-code-codex-plugin.mdx +162 -0
- package/docs-site/example/claude-code-codex-skill.mdx +147 -0
- package/docs-site/example/showcase.mdx +39 -0
- package/docs-site/example/tier1-ai-sdk-v7.mdx +136 -0
- package/docs-site/example/tier1-claude-sdk.mdx +119 -0
- package/docs-site/example/tier1-codex-sdk.mdx +111 -0
- package/docs-site/example/tier1-langgraph.mdx +119 -0
- package/docs-site/example/tier1-pi-sdk.mdx +119 -0
- package/docs-site/favicon.svg +5 -0
- package/docs-site/github-diff.css +135 -0
- package/docs-site/github-diff.js +11 -0
- package/docs-site/guides/authoring.mdx +129 -0
- package/docs-site/guides/ci-integration.mdx +97 -0
- package/docs-site/guides/connect-otel.mdx +209 -0
- package/docs-site/guides/connect-your-agent.mdx +221 -0
- package/docs-site/guides/dataset-fanout.mdx +98 -0
- package/docs-site/guides/experiments.mdx +71 -0
- package/docs-site/guides/fixtures.mdx +147 -0
- package/docs-site/guides/reporters.mdx +113 -0
- package/docs-site/guides/runner.mdx +79 -0
- package/docs-site/guides/sandbox-agent.mdx +138 -0
- package/docs-site/guides/sandbox-backends.mdx +64 -0
- package/docs-site/guides/scoring-guide.mdx +92 -0
- package/docs-site/guides/viewing-results.mdx +195 -0
- package/docs-site/guides/write-experiment.mdx +81 -0
- package/docs-site/guides/write-send.mdx +347 -0
- package/docs-site/images/adapter-tiers-zh.svg +60 -0
- package/docs-site/images/agent-turn-roundtrip-en.svg +62 -0
- package/docs-site/images/agent-turn-roundtrip-zh.svg +77 -0
- package/docs-site/images/hitl-handshake-zh.svg +89 -0
- package/docs-site/images/logo.svg +6 -0
- package/docs-site/index.mdx +181 -0
- package/docs-site/introduction.mdx +141 -0
- package/docs-site/quickstart.mdx +136 -0
- package/docs-site/reference/builtin-agents.mdx +161 -0
- package/docs-site/reference/capabilities.mdx +76 -0
- package/docs-site/reference/cli.mdx +120 -0
- package/docs-site/reference/define-agent.mdx +168 -0
- package/docs-site/reference/define-config.mdx +41 -0
- package/docs-site/reference/define-eval.mdx +160 -0
- package/docs-site/reference/events.mdx +131 -0
- package/docs-site/reference/expect.mdx +112 -0
- package/docs-site/tracker.js +8 -0
- package/docs-site/zh/concepts/adapter.mdx +286 -0
- package/docs-site/zh/concepts/assert.mdx +243 -0
- package/docs-site/zh/concepts/drive.mdx +118 -0
- package/docs-site/zh/concepts/evals.mdx +124 -0
- package/docs-site/zh/concepts/experiment.mdx +37 -0
- package/docs-site/zh/concepts/hitl.mdx +83 -0
- package/docs-site/zh/concepts/judge.mdx +130 -0
- package/docs-site/zh/concepts/overview.mdx +109 -0
- package/docs-site/zh/concepts/tier.mdx +44 -0
- package/docs-site/zh/example/ai-agent-application.mdx +152 -0
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +163 -0
- package/docs-site/zh/example/claude-code-codex-skill.mdx +147 -0
- package/docs-site/zh/example/showcase.mdx +39 -0
- package/docs-site/zh/example/tier1-ai-sdk-v7.mdx +140 -0
- package/docs-site/zh/example/tier1-claude-sdk.mdx +122 -0
- package/docs-site/zh/example/tier1-codex-sdk.mdx +117 -0
- package/docs-site/zh/example/tier1-langgraph.mdx +123 -0
- package/docs-site/zh/example/tier1-pi-sdk.mdx +122 -0
- package/docs-site/zh/guides/agent-feedback-loop.mdx +79 -0
- package/docs-site/zh/guides/authoring.mdx +129 -0
- package/docs-site/zh/guides/ci-integration.mdx +97 -0
- package/docs-site/zh/guides/connect-otel.mdx +208 -0
- package/docs-site/zh/guides/connect-your-agent.mdx +226 -0
- package/docs-site/zh/guides/dataset-fanout.mdx +83 -0
- package/docs-site/zh/guides/experiments.mdx +72 -0
- package/docs-site/zh/guides/fixtures.mdx +147 -0
- package/docs-site/zh/guides/official-adapters.mdx +132 -0
- package/docs-site/zh/guides/reporters.mdx +113 -0
- package/docs-site/zh/guides/runner.mdx +82 -0
- package/docs-site/zh/guides/sandbox-agent.mdx +139 -0
- package/docs-site/zh/guides/sandbox-backends.mdx +75 -0
- package/docs-site/zh/guides/scoring-guide.mdx +92 -0
- package/docs-site/zh/guides/viewing-results.mdx +195 -0
- package/docs-site/zh/guides/write-experiment.mdx +101 -0
- package/docs-site/zh/guides/write-send.mdx +353 -0
- package/docs-site/zh/index.mdx +180 -0
- package/docs-site/zh/introduction.mdx +140 -0
- package/docs-site/zh/quickstart.mdx +137 -0
- package/docs-site/zh/reference/builtin-agents.mdx +364 -0
- package/docs-site/zh/reference/capabilities.mdx +76 -0
- package/docs-site/zh/reference/cli.mdx +140 -0
- package/docs-site/zh/reference/define-agent.mdx +461 -0
- package/docs-site/zh/reference/define-config.mdx +107 -0
- package/docs-site/zh/reference/define-eval.mdx +674 -0
- package/docs-site/zh/reference/events.mdx +252 -0
- package/docs-site/zh/reference/expect.mdx +208 -0
- package/package.json +3 -2
- package/src/agents/bub.ts +1 -1
- package/src/agents/claude-code.ts +8 -5
- package/src/agents/codex.ts +9 -5
- package/src/agents/sdk-streams.test.ts +4 -4
- package/src/agents/sdk-streams.ts +14 -9
- package/src/agents/types.ts +40 -1
- package/src/agents/ui-message-stream.ts +1 -0
- package/src/cli.ts +40 -10
- package/src/context/types.ts +140 -0
- package/src/expect/index.ts +15 -8
- package/src/i18n/en.ts +20 -6
- package/src/i18n/zh-CN.ts +20 -6
- package/src/o11y/parsers/bub.test.ts +71 -0
- package/src/o11y/parsers/bub.ts +5 -0
- package/src/o11y/types.ts +27 -2
- package/src/runner/attempt.ts +2 -1
- package/src/runner/reporters/artifacts.ts +11 -3
- package/src/runner/reporters/live.ts +45 -8
- package/src/runner/run.test.ts +50 -0
- package/src/runner/run.ts +135 -41
- package/src/runner/types.ts +55 -2
- package/src/sandbox/types.ts +18 -0
- package/src/scoring/types.ts +5 -0
- package/src/shared/types.ts +3 -0
- package/src/util.test.ts +26 -1
- package/src/util.ts +16 -0
- package/src/view/app/App.tsx +2 -2
- package/src/view/app/components/AttemptModal.tsx +2 -0
- package/src/view/app/components/CopyControls.tsx +87 -28
- package/src/view/app/i18n.ts +3 -3
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +18 -18
- package/docs/README.md +0 -120
- package/docs/adapters/README.md +0 -60
- package/docs/adapters/authoring.md +0 -179
- package/docs/adapters/coding-agent-skills-plugins.md +0 -324
- package/docs/adapters/collection.md +0 -128
- package/docs/adapters/contract.md +0 -264
- package/docs/adapters/reference/agent-eval.md +0 -215
- package/docs/adapters/reference/agent-loop-apis.md +0 -69
- package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
- package/docs/adapters/reference/eve-protocol.md +0 -127
- package/docs/adapters/reference/otel-genai.md +0 -107
- package/docs/adapters/reference/otel-instrumentation.md +0 -65
- package/docs/adapters/targets.md +0 -99
- package/docs/architecture.md +0 -140
- package/docs/assertions.md +0 -387
- package/docs/capabilities-by-construction.md +0 -48
- package/docs/cli.md +0 -173
- package/docs/concepts.md +0 -106
- package/docs/e2e-ci.md +0 -332
- package/docs/eval-authoring.md +0 -319
- package/docs/experiments.md +0 -157
- package/docs/getting-started.md +0 -224
- package/docs/multi-agent.md +0 -145
- package/docs/observability.md +0 -337
- package/docs/origin-integration.md +0 -195
- package/docs/references.md +0 -35
- package/docs/reports.md +0 -551
- package/docs/results-format.md +0 -228
- package/docs/results-lib.md +0 -191
- package/docs/runner.md +0 -104
- package/docs/sandbox.md +0 -276
- package/docs/scoring.md +0 -119
- package/docs/source-map.md +0 -126
- package/docs/tier-sync.md +0 -193
- package/docs/view.md +0 -194
- package/docs/vision.md +0 -88
package/docs/tier-sync.md
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# Tier Sync:examples 的 origin → tier1 → tier2 → tier3 同步维护方案
|
|
2
|
-
|
|
3
|
-
> **状态:已实现。** `scripts/sync-tiers.mjs`、`pnpm tiers:sync`、`pnpm tiers:check` 已落地,CI 在 `pnpm run typecheck` 之后跑 `tiers:check`。`examples/zh/.tier-sync.json` 登记全部目录对;链条是 origin → tier1 → tier2 → tier3(哪个应用有哪几层见 [examples/zh/README](../examples/zh/origin/README.md) 与状态文件本身)。
|
|
4
|
-
|
|
5
|
-
## 问题
|
|
6
|
-
|
|
7
|
-
`examples/zh/` 里同一个应用按接入深度存多份:`origin/<name>` 是接入 niceeval 之前的原始应用,`tier1/<name>` 是它的完整副本加上无侵入接入产物,`tier2/<name>` 在 tier1 之上把 OTel 观测接进来,`tier3/<name>` 再往上做侵入改造暴露 experiment flags。存多份是刻意的——`gen:diff-code` diff 相邻两层生成 before/after 文档页,"每一档只加一层 delta"是产品叙事的骨架,所以 tier1 里被复制的文件必须和 origin 逐字节相同(只有 `package.json` / `pnpm-workspace.yaml` / `tsconfig.json` 三个脚手架文件加 `.env.example`——tier 侧要补 judge 独立凭证等 eval 变量——例外)。以 codex-sdk 为例,tier1 的跟踪文件里大半是 origin 的逐字节副本,其余是 tier 私有新增(`agents/`、`evals/`、`experiments/`、`niceeval.config.ts`、`README.md`)。
|
|
8
|
-
|
|
9
|
-
麻烦在于 origin 改得很勤(修 bug、调演示场景、升依赖),而副本不会自己跟上。今天给 `origin/codex-sdk/src/backend/server.ts` 修个 bug,就得记得 cp 一份到 tier1 的同名路径——然后 tier2、tier3 还各有一份;要是动的是 `package.json`,连 cp 都不行——tier 那份多着 niceeval 的集成行,只能打开手工重放同一行,再进各层跑一遍 `pnpm install` 让 lockfile 跟上。这套动作对每个受影响的示例各来一遍,层数越多乘数越大,而且全程只靠改动者的记忆串着。
|
|
10
|
-
|
|
11
|
-
忘了会怎样?什么都不会发生——这正是最糟的部分。没有检查、没有报警,review 时"改了 origin 而 tier1 没动"的 diff 看起来完全正常。漂移安静地躺着,直到某天重新生成 diff 文档页,陈旧差异被当成"接入 niceeval 需要改的代码"展示出来,把"零改动"卖点砸掉;或者用户跑 tier 示例,踩到 origin 早就修掉的 bug。到那时离引入漂移的那次改动已经很远,没人记得该同步什么。
|
|
12
|
-
|
|
13
|
-
值得说清的一点:这条链上有两种性质不同的目录对。**origin → tier1 是 "副本 + 纯新增"**:tier1 对共享文件的改动只有脚手架那几个文件各 2~4 行,应用源码两边完全一致,其余差异全是 tier 私有的新增文件——它本质上就是"origin + 一小层接入 delta"。**tier1 → tier2、tier2 → tier3 是 "副本 + 修改"**:tier2 会改 tier1 带来的 eval 侧文件(`niceeval.config.ts` 加 telemetry、adapter 加 spanMapper),tier3 更是按定义要改应用的 `src/`(把内部可变点暴露成 flags)。两种目录对需要的都是同一个缺失的动词——`下游 rebase 上游`:上游动了,一条命令把本层的 delta 重放到新上游之上。只是原生 `git rebase` 作用于分支,而这里是同一棵树里的目录,原生命令用不上——本方案就是给目录对补上这个动词,顺带给"纯新增"那种目录对加上逐字节铁律的 CI 检查。
|
|
14
|
-
|
|
15
|
-
## 方案一句话
|
|
16
|
-
|
|
17
|
-
**用 rebase 的底层机制直接作用于目录树。** git 的 rebase/cherry-pick 每重放一步,内部就是一次"以共同祖先为 base 的三方合并";这个机制不要求输入是分支,`git merge-tree --write-tree --merge-base=<base>`(git ≥ 2.38)可以对任意三棵 tree 使用。于是:
|
|
18
|
-
|
|
19
|
-
- 每对目录记录一个"上次同步时上游目录的 tree hash"作为 base(相当于 rebase 里的分叉点);
|
|
20
|
-
- `tiers:sync` = 一条 `git merge-tree --write-tree --merge-base=<base> <tier树> <上游树>`,把上游自分叉点以来的全部变更重放进 tier,同时保住 tier 的 delta——**这就是 `tier rebase 上游`**,冲突体验也和 rebase 一致(就地留标记,人解完继续);
|
|
21
|
-
- 链式同步一条命令跑完:上一对的合并结果树直接作为下一对的上游输入,中途不要求提交;
|
|
22
|
-
- CI 用 tree hash 比对做秒级防漂移检查,对 origin → tier1 这种"纯新增"对额外做逐字节 verbatim 校验。
|
|
23
|
-
|
|
24
|
-
**不维护 overlay 清单**("哪些文件是 tier 私有的"由三方合并自动推导),**patch 只作为同步后自动导出的阅读产物,不作为事实源**。这也是 Google copybara / `git subtree` 解决的"目录级 vendoring + 上游追踪"问题的仓库内轻量版:合并机制 100% 是 git 自己的,脚本只做粘合。
|
|
25
|
-
|
|
26
|
-
## 为什么这样做(以及否决了什么)
|
|
27
|
-
|
|
28
|
-
### 否决:把示例做成真分支,用原生 `git rebase`
|
|
29
|
-
|
|
30
|
-
既然想要的是 rebase,最直觉的做法是让它真的可 rebase:每个示例的 origin 放一条独立分支,tier1 是它之上的接入提交,同步 = 原生 `git rebase`。否决理由:
|
|
31
|
-
|
|
32
|
-
- **示例必须同时存在于 main 的工作树里。** 文档链接指向真实目录、用户 clone 后并排浏览各层、`gen:diff-code` 直接 diff 两个目录——这些都要求全部代码在同一棵树上共存。分支化之后,还得把每条分支的内容物化回 main 的目录(否则以上全断),于是"分支"与"物化目录"变成双份记账,比现在更糟;
|
|
33
|
-
- **分支数量是 示例数 × tier 层数,** 五个示例三四层就是 15+ 条长期分支,rebase 后全部需要 force-push,历史被反复改写,协作成本失控;
|
|
34
|
-
- `git subtree split` 能把目录合成出伪分支再 rebase 回填,但那是三步咒语级的操作,比直接对目录树做三方合并绕远得多。
|
|
35
|
-
|
|
36
|
-
结论:要的是 rebase 的**机制**,不是 rebase 的**命令**。机制(同 base 三方合并)可以脱离分支直接用在目录树上,见"方案一句话"。
|
|
37
|
-
|
|
38
|
-
### 否决:patch 作为事实源(tier 只存 diff patch,apply 出目录)
|
|
39
|
-
|
|
40
|
-
即"tier1 只存对 origin 的 diff patch,tier2 存对 tier1 的 patch"(Debian quilt / 内核补丁队列模式)。patch 的"方便阅读"很诱人,但作为**存储格式**有四个硬伤:
|
|
41
|
-
|
|
42
|
-
- **双事实源,DX 崩坏。** 改接入代码(evals、agents、脚手架几行)时,要么手写 patch 文件——没人受得了;要么改物化目录再重新导出——目录和 patch 变成两份事实源,永远在打架。git history 也不可读了:改一行 eval,commit diff 是"diff 的 diff"。
|
|
43
|
-
- **示例必须是可运行、可浏览的真实目录。** 仓库规则要求文档链接指向真实目录,用户会 clone 后直接 `cd examples/zh/tier1/codex-sdk` 跑起来、在 GitHub 上直接读代码(有语法高亮和跳转,patch 文件没有)。物化产物要么提交(回到原点),要么 gitignore(文档链接与 GitHub 浏览全断)。
|
|
44
|
-
- **lockfile 无法用 patch 维护。** `pnpm-lock.yaml` 的内容随依赖版本剧烈漂移,patch 很快 apply 失败;它只能由 `pnpm install` 重新生成。
|
|
45
|
-
- **堆叠 patch 是出了名的维护地狱。** origin 改一行,可能要连修 tier1、tier2、tier3 三层 patch 的 fuzz/reject。现在的痛是"复制一遍",换成堆叠 patch 后痛变成"手工解 reject",更糟。
|
|
46
|
-
|
|
47
|
-
**"方便阅读"这个需求单独满足,不必绑架存储格式**:`gen:diff-code` 已经从两个物化目录生成 before/after 阅读页;本方案再让 `tiers:sync` 顺手导出一份 `<name>.patch` 纯阅读件(见下文)。patch 当**输出**,不当**输入**。
|
|
48
|
-
|
|
49
|
-
### 否决:纯复制 + allowDiff 清单
|
|
50
|
-
|
|
51
|
-
即维护一份清单声明"哪些文件 tier 私有、哪些允许有差异",同步 = 清单之外的文件从上游整文件复制。比 patch 好,但有两个硬伤:
|
|
52
|
-
|
|
53
|
-
- **清单要人工维护,且各示例不同**(langgraph 的 `package.json` 是 tier 私有,其它示例的是"共享但微改"),清单本身会漂移;
|
|
54
|
-
- **allowDiff 即检查盲区**:`package.json` 一旦列入 allowDiff,origin 给应用加依赖时,检查发现不了 tier1 没跟上。
|
|
55
|
-
- 对 tier2/tier3 这种"副本 + 修改"的目录对,清单会膨胀成"每个被 overlay 的文件一条",彻底退化成手工记账。
|
|
56
|
-
|
|
57
|
-
三方合并没有这些问题:私有文件自动推导,`package.json` 走真合并——origin 的新依赖能不能干净合入 tier1 的 `package.json`,取决于新依赖是否落在 tier 那 +2 行改动的同一位置(两者都紧挨着追加时会冲突,见下文"已知取舍");但即便冲突,也是显式报错,而不是像 allowDiff 清单那样检查不到。
|
|
58
|
-
|
|
59
|
-
### 采纳三方合并的正面理由
|
|
60
|
-
|
|
61
|
-
- **改 origin 后各层自动快进**:tier1/tier2 从不改应用 `src/`,所以 `src/` 的变更一路无冲突快进复制;tier3 改过的 `src/` 文件走真三方合并,只有上游改到 tier3 动过的同一区域才需要人工裁决——这正是三方合并存在的意义,覆盖日常绝大多数场景;
|
|
62
|
-
- **"origin → tier1 逐字节不变"的铁律从"靠自觉"变成"CI 保证"**:check 模式对 verbatim 契约的目录对做逐字节比对,漂移直接红(见"检查模式");
|
|
63
|
-
- **`gen:diff-code`、"接入只要 10-50 行"的验收方式完全不变**:相邻两层仍是完整目录,`diff -r` 照旧;
|
|
64
|
-
- **加一层免费获得同样机制**:上游指向上一层即可,形成 origin → tier1 → tier2 → tier3 的链,一条 `tiers:sync` 从头穿到尾。
|
|
65
|
-
|
|
66
|
-
已知取舍:
|
|
67
|
-
|
|
68
|
-
- 三方合并按行进行,两边在文件同一位置追加会冲突。这不是理论风险——沙盘验证时就复现了一例:origin 给 `dependencies` 加 `zod`、tier 在同一位置有 `niceeval`,merge-tree 就地留下标记要求人工裁决,体验与 rebase 冲突一致。按现有 diff 形状冲突频率低,且永远是**显式报错**(留标记 + check 拦截)而非静默错合。
|
|
69
|
-
- **解决冲突后不能简单重跑合并**:base 没动的情况下,同 base 三方合并会把同一处冲突原样再报一遍(这是合并语义的固有行为,git rebase 靠 `--continue` 记录裁决绕开它)。所以脚本在报冲突时把"这次要合到哪"记进状态文件的 `pending`,人解完标记、提交后重跑 `tiers:sync` 走的是**收尾**而不是重新合并(见"同步算法")。
|
|
70
|
-
- 文件重命名被视为"删除 + 新增",无 rename 检测;origin 重命名文件时 tier 侧若改过该文件会报一次冲突,人工确认即可。
|
|
71
|
-
- **lockfile 不参与合并**:`pnpm-lock.yaml` 完全由各层自己的 `pnpm install` 生成。上游只动 lockfile 而不动 `package.json` 的变更(比如区间内 `pnpm up`)不会传播到下游——需要时在下游手动 `pnpm install` 即可,这类变更不影响任何展示或断言。
|
|
72
|
-
- **verbatim 检查只覆盖 origin → tier1**。tier2/tier3 是"副本 + 修改"(overlay 契约),没有"必须逐字节一致"可言,check 对它们只能保证"上游动了必有同步动作";有人直接改 tier2 里本应跟随 tier1 的文件并提交,机器发现不了,要靠 review 时看 `diffs/<层级>-<name>.patch` 的形状。
|
|
73
|
-
|
|
74
|
-
## 如何实现
|
|
75
|
-
|
|
76
|
-
### 状态文件
|
|
77
|
-
|
|
78
|
-
单独一份 `examples/zh/.tier-sync.json`,**不放进各示例目录**——tier 目录里多任何一个文件都会出现在 `gen:diff-code` 的 before/after 页上,污染"接入只新增了这些文件"的叙事:
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
{
|
|
82
|
-
"pairs": [
|
|
83
|
-
{ "from": "examples/zh/origin/codex-sdk", "to": "examples/zh/tier1/codex-sdk", "contract": "verbatim", "baseTree": "a1b2c3..." },
|
|
84
|
-
{ "from": "examples/zh/tier1/codex-sdk", "to": "examples/zh/tier2/codex-sdk", "contract": "overlay", "baseTree": "d4e5f6..." },
|
|
85
|
-
{ "from": "examples/zh/tier2/codex-sdk", "to": "examples/zh/tier3/codex-sdk", "contract": "overlay", "baseTree": "0718ab..." }
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
- `baseTree` 是上次同步时上游目录**剥掉 `pnpm-lock.yaml` 之后**的 git tree hash(lockfile 不参与合并与比对,理由见"已知取舍";剥法是 `git ls-tree` 过滤后 `git mktree`,几毫秒)。base 的**内容**不需要另存——git 对象库天然保管。
|
|
91
|
-
- `contract` 声明这对目录的性质:`verbatim`(origin → tier1,"副本 + 纯新增",check 会做逐字节校验)或 `overlay`(tier1 → tier2、tier2 → tier3,"副本 + 修改",不做逐字节校验)。
|
|
92
|
-
- 报冲突时脚本会往该 pair 写一个 `pending` 字段(要合到的上游 tree + 是否需要重装依赖),收尾后自动删掉;它是同步中间态,不要手工编辑。
|
|
93
|
-
- pairs 的书写顺序不重要——脚本按 from/to 关系做拓扑排序;成环报错。
|
|
94
|
-
|
|
95
|
-
### 同步算法(`pnpm tiers:sync [name]`)
|
|
96
|
-
|
|
97
|
-
前置条件:**上游与 tier 两侧目录都必须无未提交改动**(lockfile 除外,它不参与比对)——合并的三个输入都取自提交过的 tree,同步才可复现、可回溯。工作流固定为:改 origin → 提交 → sync(自动穿透整条链)→ review → 一起提交。带 `name` 参数(目录 basename,如 `codex-sdk`)时只同步该应用的链。
|
|
98
|
-
|
|
99
|
-
对每一对 (from, to),核心是一条 git 命令(三棵输入树都已剥 lockfile):
|
|
100
|
-
|
|
101
|
-
```sh
|
|
102
|
-
git merge-tree --write-tree --merge-base=<baseTree> <tier树> <上游树>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
输出第一行是合并后的 tree hash(退出码非零表示有冲突,后续行给出冲突文件清单;冲突文件在结果 tree 里已含 `<<<<<<<` 标记)。脚本把结果 tree 检出到 tier 目录(`git archive <tree> | tar -x -C <to>`)即完成同步。逐文件语义与 git merge/rebase 完全一致,等价于下表:
|
|
106
|
-
|
|
107
|
-
| 上游侧(base → 现在) | tier 侧(base → 现在) | 动作 |
|
|
108
|
-
| --- | --- | --- |
|
|
109
|
-
| 没变 | 任意 | 不动 |
|
|
110
|
-
| 变了 | 未改(== base) | **快进**:整文件复制 |
|
|
111
|
-
| 变了 | 改过 | `git merge-file` 三方合并;冲突留标记并报出 |
|
|
112
|
-
| 新增 | tier 无同名文件 | 复制过来 |
|
|
113
|
-
| 新增 | tier 已有同名文件 | 报冲突(极罕见,人工裁决) |
|
|
114
|
-
| 删除 | 未改(== base) | 跟着删 |
|
|
115
|
-
| 删除 | 改过 | 报冲突 |
|
|
116
|
-
|
|
117
|
-
只出现在 tier 侧、base 与上游都没有的文件,自动视为 **tier 私有**,永远不碰——`agents/`、`evals/`、langgraph 的 `package.json` 都落在这条规则里,无需任何配置。
|
|
118
|
-
|
|
119
|
-
**链式执行**:脚本对 pairs 拓扑排序后依次跑。上一对刚合并出的结果树(已在 git 对象库里)直接作为下一对的上游输入,所以一条命令就能从 origin 穿到 tier3,不要求中途提交;某一对报冲突时,它的整条下游跳过并计入失败,解完再跑一次即可续上。
|
|
120
|
-
|
|
121
|
-
**冲突与收尾**:冲突文件就地留 `<<<<<<<` 标记,脚本把"这次要合到的上游 tree"记进该 pair 的 `pending`,列出清单并以非零码退出。人解完标记、**提交**之后重跑 `tiers:sync`:脚本看到 `pending` 且标记已清,直接把 `baseTree` 推进到当时要合的上游 tree、补上 `pnpm install` / patch 导出,**不重新合并**——重新合并会把同一处冲突再报一遍(见"已知取舍")。收尾之后若上游又前进了,同一次运行里继续正常合并追平。
|
|
122
|
-
|
|
123
|
-
其余细节:
|
|
124
|
-
|
|
125
|
-
- 合并后若 `package.json` / `pnpm-workspace.yaml` 有变动,在 tier 目录执行 `pnpm install` 重新生成 lockfile(lockfile 本身从不进合并);
|
|
126
|
-
- 二进制文件 git 无法文本合并,两侧都改过时会作为冲突报出,人工裁决;
|
|
127
|
-
- 全部干净(或冲突已收尾)后,把该 pair 的 `baseTree` 更新为上游当前(剥 lockfile 的)tree hash,写回状态文件;
|
|
128
|
-
- 收尾时导出阅读件:`git diff <上游tree> <tier tree> > examples/zh/diffs/<层级>-<name>.patch`(如 `tier2-codex-sdk.patch`,同一应用的多层不撞名),这份 patch 是自动再生的**展示产物**,供快速阅读"这一层改了什么",与 `gen:diff-code` 的文档页同源同性质,永远不作为同步输入。
|
|
129
|
-
|
|
130
|
-
### 检查模式(`pnpm tiers:check`,进 CI)
|
|
131
|
-
|
|
132
|
-
不做合并,只读,秒级完成,对每对做三件事:
|
|
133
|
-
|
|
134
|
-
1. `baseTree` ≟ 剥 lockfile 后的 `HEAD:<from>`——不等即"上游变了但 tier 未同步",红,提示跑 `pnpm tiers:sync`;pair 带着未收尾的 `pending` 时同样红;
|
|
135
|
-
2. 扫描 tier 跟踪文件中的 `<<<<<<<` 冲突标记,有则红;
|
|
136
|
-
3. `contract: "verbatim"` 的对,额外做**逐字节铁律校验**:两侧都存在的同名文件必须完全一致、上游有的文件 tier 必须有(例外:三个脚手架文件、lockfile、`.env.example`)。直接在 tier1 里改共享文件并提交——以前这种漂移完全静默,现在 CI 红。
|
|
137
|
-
|
|
138
|
-
### 实现载体
|
|
139
|
-
|
|
140
|
-
- `scripts/sync-tiers.mjs`(约 300 行粘合代码:读写状态文件、剥 lockfile、调 `git merge-tree --write-tree`、拓扑排序、检出结果、pending 收尾、跑 `pnpm install`、导出 patch 阅读件),合并机制本身 100% 由 git 提供,不引第三方依赖;要求 git ≥ 2.38(`merge-tree --write-tree` 的最低版本);
|
|
141
|
-
- `package.json` 的 `"tiers:sync"` / `"tiers:check"` 两个 script;
|
|
142
|
-
- CI(现有 lint/typecheck 步骤旁)一步 `pnpm tiers:check`。
|
|
143
|
-
|
|
144
|
-
## 日常工作流(before / after)
|
|
145
|
-
|
|
146
|
-
改 origin 应用源码——没有这套机制时:
|
|
147
|
-
|
|
148
|
-
```sh
|
|
149
|
-
vim examples/zh/origin/codex-sdk/src/backend/agent.ts
|
|
150
|
-
# 然后必须人肉记得 tier1、tier2、tier3 各有一份同样的文件:
|
|
151
|
-
cp examples/zh/origin/codex-sdk/src/backend/agent.ts examples/zh/tier1/codex-sdk/src/backend/agent.ts
|
|
152
|
-
cp examples/zh/origin/codex-sdk/src/backend/agent.ts examples/zh/tier2/codex-sdk/src/backend/agent.ts
|
|
153
|
-
# tier3 改过这个文件?那 cp 会盖掉 tier3 的侵入改造,只能打开手工对……
|
|
154
|
-
# 忘了哪一层?没有任何检查会发现,diff 文档页从此静默失真。
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
方案落地后:
|
|
158
|
-
|
|
159
|
-
```sh
|
|
160
|
-
vim examples/zh/origin/codex-sdk/src/backend/agent.ts
|
|
161
|
-
git add examples/zh/origin/codex-sdk && git commit -m "..."
|
|
162
|
-
pnpm tiers:sync codex-sdk # 一条命令穿透 tier1 → tier2 → tier3;必要时自动 pnpm install
|
|
163
|
-
git diff --stat # review 各层的机器改动
|
|
164
|
-
git add -A examples/zh && git commit -m "sync tiers"
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
改 tier 私有文件(evals / agents / README):直接改,与同步机制无关。
|
|
168
|
-
|
|
169
|
-
改 tier 层的 overlay 文件(tier2 的 `niceeval.config.ts`、tier3 的 `src/backend/agent.ts` 这类"从上游复制来但本层改过"的文件):也是直接改——这些文件本来就是本层 delta 的一部分,下次上游动到同一文件时三方合并会把两边的改动合在一起,同点冲突时显式报出。
|
|
170
|
-
|
|
171
|
-
在 tier1/tier2 里改"应该跟上游逐字节一致"的共享文件(比如临时改一行 `src/backend/server.ts` 去验证个问题):这不是方案要覆盖的路径——tier1/tier2 的前提就是不改应用源码。改完把改动誊回对应的 origin 文件、提交、跑 `tiers:sync` 让它"转正"成一次 origin 变更;忘了誊、直接提交,`tiers:check` 的 verbatim 校验会红(tier1),或者留给下次同步撞冲突(tier2)。
|
|
172
|
-
|
|
173
|
-
反过来,如果是在下游先调试出的修复,想让上游也拿到:同步方向是单向的,没有 backport 命令。得手工把改动誊回上游对应文件、提交、跑 `tiers:sync` 让链条追平——不然下次同步会把这处改动误判成冲突。
|
|
174
|
-
|
|
175
|
-
origin 给应用加依赖(动了 `package.json`)——三方合并把新依赖行合进 tier1 的 `package.json`(tier 自己的 `"niceeval": "file:../../../.."` 在另一行,不冲突),随后自动 `pnpm install` 更新 lockfile,tier2/tier3 同样各自重装。
|
|
176
|
-
|
|
177
|
-
忘了同步就提交?CI 的 `tiers:check` 红:
|
|
178
|
-
|
|
179
|
-
```text
|
|
180
|
-
✗ examples/zh/tier1/codex-sdk 落后于 examples/zh/origin/codex-sdk
|
|
181
|
-
base a1b2c3… ≠ 当前 9f8e7d…,运行 pnpm tiers:sync 后重新提交
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## 验收标准(实现时逐条核对,已全部沙盘验证)
|
|
185
|
-
|
|
186
|
-
1. 对现有目录初始化 base 后,`tiers:sync` 是无操作(各层已一致),`tiers:check` 绿;
|
|
187
|
-
2. 改 origin 任一 `src/` 文件 → **一次** sync 后 tier1/tier2/tier3 同文件全部跟上(tier3 该文件有 overlay 改动时做真合并),中途无需提交;`gen:diff-code` 的 origin↔tier1 页不含该文件;
|
|
188
|
-
3. 改 origin `package.json`(加一个依赖)→ sync 后各层 `package.json` 同时含新依赖与本层集成行,lockfile 已重装;
|
|
189
|
-
4. 上游与下游在同一文件同一区域都有改动 → sync 报冲突、留标记、记 `pending`、非零退出,该应用的下游层跳过;`tiers:check` 在标记未解前保持红;
|
|
190
|
-
5. 解完标记、提交、重跑 `tiers:sync` → 直接收尾(不重报同一冲突),baseTree 前进,同一次运行里下游层继续同步;
|
|
191
|
-
6. 直接改 tier1 的共享文件并提交 → `tiers:check` 的 verbatim 校验红,指出具体文件;
|
|
192
|
-
7. langgraph(origin 为 Python、`package.json` 为 tier 私有)全流程不误伤 tier 私有文件;
|
|
193
|
-
8. `tiers:check` 在 base 落后时红、同步后绿,全程不写任何文件。
|
package/docs/view.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# View —— 本地结果查看器(`niceeval view`)
|
|
2
|
-
|
|
3
|
-
控制台和 `summary.json` 是「当下」的;`niceeval view` 是「事后看图」——不连任何外部服务,只读 `.niceeval/<时间戳>/` 这些结构化工件。结果保存格式见 [Results Format](results-format.md),数据来源见 [Observability](observability.md#结果可视化niceeval-view)。
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
niceeval view # 起本地 web,自动打开浏览器,读 .niceeval/ 下所有历史运行
|
|
7
|
-
niceeval view .niceeval/<run>/summary.json
|
|
8
|
-
niceeval view --no-open # 只打印 URL,不打开浏览器
|
|
9
|
-
niceeval view --out site # 目录式静态导出:index.html + artifact/
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
架构上是**一次性烘焙进单个 HTML+JSON 的静态产物**(`src/view/index.ts` 的 `renderHtml`),不是常驻的多页面 server——`niceeval view` 起的 web 服务每次请求现读现渲染,`--out` 则直接导出。这是刻意的取舍,详见 [References](references.md#调研过判断不值得抄的及理由)。
|
|
13
|
-
|
|
14
|
-
`--out` 只有目录式一种形态:写 `<out>/index.html`,并把前端会 fetch 的三类工件(`sources.json` / `events.json` / `trace.json`)复制到 `<out>/artifact/<base>/`,与本地 server 的 `/artifact/<rel>` 路径路由同一布局,同一份前端产物在两种托管下用同一个相对 URL(`src/view/app/lib/artifact-url.ts`)。`diff.json` / `o11y.json` 刻意不复制:查看器从不读取,且 diff 可达上百 MB,带上只会拖垮静态部署体积。
|
|
15
|
-
|
|
16
|
-
> 单文件导出(`--out report.html`)曾经存在,已移除:代码/transcript/trace 视图依赖工件文件,单文件注定是残缺体验,而 coding eval 恰恰最依赖这些视图——这个形态的存在本身就在诱导用户导出一份看不了证据的报告。「传一个文件给同事」的需求,答案是把整站导出托管起来发链接,或用 [Reports](reports.md) 积木在 CI 里落判决数据。`--out` 目标以 `.html` 结尾时 CLI 直接报错并给出改法。
|
|
17
|
-
|
|
18
|
-
## 结果版本机制
|
|
19
|
-
|
|
20
|
-
`niceeval view` 读取的是已经落盘的报告,而报告会比 CLI 活得更久:用户可能几个月后打开 `.niceeval/<run>/summary.json`,也可能把 CI 产物下载到另一台机器上看。所以 view 的版本策略要优先保证两件事:
|
|
21
|
-
|
|
22
|
-
1. 新版本 CLI 不轻易丢下旧报告。
|
|
23
|
-
2. 真读不了时,错误信息要告诉用户「该用哪个版本看」或「这份历史结果可以删掉」。
|
|
24
|
-
|
|
25
|
-
具体设计:
|
|
26
|
-
|
|
27
|
-
- **报告自己带版本。** `summary.json` 顶层放 `format: "niceeval.results"`、`schemaVersion` 和 `producer.version`,设计见 [Results Format · 版本与升级设计](results-format.md#版本与升级设计)。没有这些字段的现有报告视为 legacy v0。
|
|
28
|
-
- **view 有一个支持区间。** 例如当前 view 支持 `schemaVersion` 0 和 1。支持区间写在 loader 常量里,不要散落在 React 组件里。
|
|
29
|
-
- **先 normalize,再渲染。** `readSummary` 不直接返回磁盘 JSON,而是走 `normalizeSummary(raw, path)`。legacy v0、v1、未来 v2 都在这里转成 view 内部统一模型;`aggregateRows`、`AttemptModal`、`TracesPage` 只吃 normalized model。
|
|
30
|
-
- **未知字段不是错误。** 新增 `git`、`environment`、`agentSetup`、`classification` 等字段时,旧 view 可以忽略;新增 artifact kind 也只是不展示。只有必需字段缺失、字段类型错误、或 `schemaVersion` 超出支持区间才算版本/格式错误。
|
|
31
|
-
|
|
32
|
-
### 报错与降级
|
|
33
|
-
|
|
34
|
-
`view` 有两种入口,错误处理不同:
|
|
35
|
-
|
|
36
|
-
- **读整个 `.niceeval/` 目录。** 遇到某个 run 读不了,不要让整个页面失败。loader 应收集 `skippedRuns[]`,继续渲染其它 run,并在页面顶部显示一条可展开提示:哪些 `summary.json` 被跳过、原因是什么、建议怎么处理。
|
|
37
|
-
- **读单个 `summary.json`。** 这是用户明确指定的目标,读不了就应该让命令失败,打印可执行的下一步,不要打开一个空页面。
|
|
38
|
-
|
|
39
|
-
错误分类:
|
|
40
|
-
|
|
41
|
-
| 场景 | 行为 | 文案要点 |
|
|
42
|
-
|---|---|---|
|
|
43
|
-
| 没有 `format` / `schemaVersion`,但有 `results[]` + `startedAt` | 按 legacy v0 读 | 可弱提示「旧格式报告」,不阻断 |
|
|
44
|
-
| `format` 不是 `niceeval.results` | 跳过或失败 | 说明这不是 niceeval 报告 |
|
|
45
|
-
| `schemaVersion` 小于最低支持版本 | 跳过或失败 | 建议用写出该报告的 `producer.version` 打开,或先导出/归档后 `niceeval clean` |
|
|
46
|
-
| `schemaVersion` 大于当前支持版本 | 跳过或失败 | 建议升级 niceeval,或用报告里的 `producer.version` 对应版本打开 |
|
|
47
|
-
| 必需字段坏了,例如 `results` 不是数组 | 跳过或失败 | 说明报告可能损坏,给出文件路径 |
|
|
48
|
-
| attempt 工件缺失,例如 `events.json` 不存在 | 页面仍可打开 | 只在展开该 attempt 时显示「artifact missing」 |
|
|
49
|
-
|
|
50
|
-
命令行错误要给到具体命令,例如:
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
Cannot open .niceeval/2026-07-02T03-10-24-123Z/summary.json.
|
|
54
|
-
This report was written by niceeval 0.12.0 with results schema 3,
|
|
55
|
-
but this viewer supports schema 0-1.
|
|
56
|
-
|
|
57
|
-
Try:
|
|
58
|
-
npx niceeval@0.12.0 view .niceeval/2026-07-02T03-10-24-123Z/summary.json
|
|
59
|
-
pnpm dlx niceeval@0.12.0 view .niceeval/2026-07-02T03-10-24-123Z/summary.json
|
|
60
|
-
|
|
61
|
-
If you no longer need historical reports:
|
|
62
|
-
niceeval clean
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
如果 `producer.version` 缺失,文案退化成「upgrade niceeval」或「try an older niceeval matching when the report was created」,不要编造版本号。
|
|
66
|
-
|
|
67
|
-
### 迁移策略
|
|
68
|
-
|
|
69
|
-
默认不做隐式迁移。理由:
|
|
70
|
-
|
|
71
|
-
- eval 结果是审计材料,原地改写会让「当时到底写出了什么」变模糊。
|
|
72
|
-
- view 是读工具,不应该因为用户想看报告而修改 `.niceeval/`。
|
|
73
|
-
- 大多数版本不需要迁移:新增字段、未知 artifact、UI 展示变化都可以通过兼容 loader 解决。
|
|
74
|
-
|
|
75
|
-
真正需要迁移时再加显式命令,并且默认写到新目录:
|
|
76
|
-
|
|
77
|
-
```sh
|
|
78
|
-
niceeval migrate-results .niceeval/<old-run>/summary.json --out .niceeval/<new-run>/
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
迁移命令只解决「旧格式还能确定转换到新格式」的情况;如果某份报告依赖旧版本 view 的展示逻辑,更好的建议仍然是:
|
|
82
|
-
|
|
83
|
-
```sh
|
|
84
|
-
npx niceeval@<producer.version> view .niceeval/<run>/summary.json
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
`niceeval clean` 不是迁移工具,只负责删除当前项目的历史运行结果。它适合用户明确表示「旧报告不要了,只想让 view 干净」的场景;不应该在 view 报错时自动执行。
|
|
88
|
-
|
|
89
|
-
## 现状(已实现)
|
|
90
|
-
|
|
91
|
-
- **三个 tab**:Experiments(按 `experimentId` 聚合的对比榜单,`GroupSelector` 选组、`ExperimentTable` 展示同组内配置并排,点开一行钻到 eval / attempt 级明细)、Runs(所有 run 打平成一张表)、Traces(trace 瀑布图)。
|
|
92
|
-
- **运行总览指标** —— pass / fail / error / skip 计数、总 token、总 $。
|
|
93
|
-
- **eval attempt 钻取** —— `AttemptModal` 点开单个 attempt 看断言、错误、耗时、用量、transcript、trace。
|
|
94
|
-
- **trace 瀑布图** —— 把 `trace.json` 画成时间轴瀑布,只读 canonical(`gen_ai.operation.name` → `kind`、`gen_ai.*`),不认任何原生 span 名,所以不同 agent 的图天然对齐、可叠加对比。
|
|
95
|
-
|
|
96
|
-
## 已知的文档 vs 实现差异
|
|
97
|
-
|
|
98
|
-
这两条之前被 [Observability](observability.md) 的能力列表当成已实现的写了,这次审查代码(`src/view/index.ts`、`src/view/app/`)发现对不上,已经从那边挪过来,归到下面「计划中」:
|
|
99
|
-
|
|
100
|
-
- **"跨运行趋势"实际是合并,不是可对比的历史。** `aggregateRows`(`src/view/index.ts`)把 `.niceeval/` 下**所有**历史 `summary.json` 按 `experimentId` 揉进同一行——通过率、平均耗时、成本都是跨全部历史 run 的累计值,不是"最新一次"或"某一次"的快照,更谈不上画成随时间变化的线。(合流后由选择器 + 跨快照去重取代,见[用 Reports 积木重建 view](#用-reports-积木重建-view设计提案)。)
|
|
101
|
-
- **"质量 × 成本散点图"没有实现。** `src/view/app` 下没有任何图表 / scatter / canvas 组件,现有可视化都是表格和文字指标。
|
|
102
|
-
|
|
103
|
-
## 外部参考
|
|
104
|
-
|
|
105
|
-
### agent-eval playground
|
|
106
|
-
|
|
107
|
-
**是什么:** Vercel `agent-eval` 项目下的 `packages/playground`,发布为 `@vercel/agent-eval-playground`。一个独立的 Next.js web 应用,`npx @vercel/agent-eval-playground` 直接跑,提供 `/`(总览)、`/experiments`、`/experiments/[name]/[timestamp]`、`/evals`、`/evals/[name]`、`/compare`、`/transcript/[...]` 几个路由。零数据库、零 API 路由——所有页面是 Server Component,`force-dynamic`,每次请求都现读 fs,永远是盘上最新数据。
|
|
108
|
-
|
|
109
|
-
**怎么做的:**
|
|
110
|
-
|
|
111
|
-
- `bin.mjs` 解析 `--results-dir` / `--evals-dir` / `--port` 几个 flag,resolve 成绝对路径塞进 `RESULTS_DIR` / `EVALS_DIR` 环境变量,再 `spawn` 包自带的 `next start -p <port>`(注意:README 写的是 `next dev`,实际跑的是 production 的 `next start`)。
|
|
112
|
-
- `lib/data.ts` 是所有数据读取的唯一入口,纯 `fs.readdirSync`/`readFileSync`,没有缓存也没有数据库:
|
|
113
|
-
- `listExperiments`/`getExperiment` 递归 walk `results/` 目录树,遇到子目录名匹配 ISO 时间戳(`/^\d{4}-\d{2}-\d{2}T/`)就判定它的父目录是一个 experiment、这些时间戳目录就是它的历史 run 列表。
|
|
114
|
-
- `getExperimentDetail(name, timestamp)` 在某次 run 目录下再递归找带 `summary.json` 的子目录(= 一个 eval 的结果),读 `summary.json` + 每个 `run-N/result.json`。
|
|
115
|
-
- `listEvals`/`getEvalDetail` 递归 walk `evals/` 目录,遇到带 `PROMPT.md` 的目录就判定是一个 eval fixture。
|
|
116
|
-
- `/compare`(`components/ComparePage.tsx`,client component)两个下拉框选"某个 experiment 的某次 run",候选项和对应的完整 `ExperimentDetail` 都由服务端预先读好、一次性传给客户端(不是选中后才 fetch)。选中两边后纯前端算 delta:整体 `avgPassRate`/`avgDuration` 对两边的 `evals[]` 取平均相减;per-eval 按 eval name 取并集,逐行对比 `passRate`/`meanDuration`,delta 用颜色区分涨跌。
|
|
117
|
-
- **关键点:** "能任意选两次运行对比"完全建立在**目录结构天然保留时间戳身份**上——`results/<experiment>/<ISO-timestamp>/` 从不合并,每次 run 落一个新目录,`getExperiment` 返回的 `timestamps: string[]` 就是完整历史列表,`/compare` 只是在这份现成的列表上做了个下拉选择器 + 前端减法。
|
|
118
|
-
|
|
119
|
-
**跟 niceeval 的差异(为什么不能直接照搬这套形状):** playground 是多页面、每次请求都读 fs 的 live Next server;niceeval `view` 是一次性烘焙进单个 HTML+JSON 的静态产物(见上文"架构上"一段)。playground 靠"存储层本来就是每次 run 一个新目录"天然拿到历史身份;niceeval 现在的 `aggregateRows` 反而是**主动把**同一个 `experimentId` 的所有历史 run **合并**成一行(见上文"已知的文档 vs 实现差异")。所以 niceeval 要做 Compare,抄的是"保留快照身份、不要提前合并"这个**原则**,不是 playground 的目录结构或 API 形状——数据仍然得在生成 HTML 那一刻就把所有候选快照的统计算好塞进 `viewData`,不能假设前端能像 playground 一样随时再去问 fs。
|
|
120
|
-
|
|
121
|
-
调研时更完整的"抄了什么 / 为什么不抄"决策记录见 [References](references.md#vercel-agent-eval--packagesplayground)。
|
|
122
|
-
|
|
123
|
-
## 计划中的小功能
|
|
124
|
-
|
|
125
|
-
### 结果版本提示与跳过列表
|
|
126
|
-
|
|
127
|
-
实现上先补 loader,再补 UI:
|
|
128
|
-
|
|
129
|
-
1. `loadSummaries` 返回 `{ loaded, skipped }`,其中 `skipped` 记录 path、reason、detected `schemaVersion`、detected `producer.version`。
|
|
130
|
-
2. `readSummary` 拆成 `parseSummary` + `normalizeSummary`。normalize 支持 legacy v0 和当前 v1。
|
|
131
|
-
3. 目录入口继续渲染已成功读取的 run,页面顶部增加可展开的 skipped report 提示。
|
|
132
|
-
4. 单文件入口遇到不兼容 schema 时直接退出,打印上文的 `npx niceeval@... view ...` 建议。
|
|
133
|
-
|
|
134
|
-
这比一开始就做 migration 更实际:多数用户只是想看当前报告;旧报告读不了时,用写出它的旧版本 viewer 打开比自动转换更可控。
|
|
135
|
-
|
|
136
|
-
### Compare —— 挑两次运行对比
|
|
137
|
-
|
|
138
|
-
跟 `experiments/compare/`(文档里"一组可对比实验"的示例文件夹名,见 [Experiments](experiments.md#实验怎么组织文件夹--一组可对比的实验))是两回事,别混——这里指 view 里一个新增的小 tab。
|
|
139
|
-
|
|
140
|
-
**动机:** 上面"已知的文档 vs 实现差异"提到的问题——现在选不出"这次 vs 上次",只有累计历史。参考对象是上面[外部参考](#agent-eval-playground)里的 playground `/compare` 页。
|
|
141
|
-
|
|
142
|
-
**数据模型:** 现有的 `rows`(累计视图)继续服务 Experiments / Runs / Traces 三个 tab——"这个 agent 整体现在什么水平"仍然是合并全部历史更有用的默认视图,不动它的语义。新增一份**不合并**的快照列表,按 `(experimentId, startedAt)` 索引,每个快照携带该次 run 里这个 experiment 的 eval 级统计(复用 `evalLevelStats` 的输出形状)。这份数据随 `viewData` 一起烘焙进静态 HTML(不像 playground 能按需查 fs)。
|
|
143
|
-
|
|
144
|
-
**UI:** 在 `src/view/app/App.tsx` 的 `navItems` 加一个 `compare`。两个下拉选"快照"(`experimentId @ startedAt`),不限制两边必须是同一个 `experimentId`;选完出整体通过率 / 平均耗时 / 总成本三个 KPI delta,加一张 per-eval 并排表(复用现成的 `outcomeOf` / `formatPercent` / `formatDuration` / `formatCost`)。只跑过一次、没有历史快照时,下拉只有一项,提示"再跑一次才能对比",不报错。
|
|
145
|
-
|
|
146
|
-
**明确不做的:** 不做时间序列折线图(历史快照一多不适合塞进单个静态 HTML,而且这次要补的是"挑两点"这个最小能力);不改 Experiments tab 现有的"累计历史"默认语义(这是另一个值得讨论的问题,不在这次一起改)。
|
|
147
|
-
|
|
148
|
-
实现形态已并入[用 Reports 积木重建 view](#用-reports-积木重建-view设计提案):两个下拉选快照的数据模型照本节,表格与 delta 单元直接用 Reports 的组件。
|
|
149
|
-
|
|
150
|
-
### 质量 × 成本散点图
|
|
151
|
-
|
|
152
|
-
之前文档写过、实际没做。没有具体设计,先记一句:每个 eval(或每个 agent)一个点,一眼看出"贵且不准"的角落——值得补,但优先级低于 Compare。点位与轴向的契约不用另起炉灶:[Reports 提案](reports.md#计算函数与数据契约)的 `scatter()` / `MetricScatter` 已经钉了「点 = 维度分组的聚合、系列连线、轴向随 better(右上恒为好)」,view 落地时直接吃同一份 `ScatterData`。
|
|
153
|
-
|
|
154
|
-
### Eval 目录页
|
|
155
|
-
|
|
156
|
-
独立于"跑过的结果",单纯浏览 `evals/` 目录下每个 fixture 的 `PROMPT.md` 和文件列表,不用先跑一次才能看"有哪些 eval、prompt 写的什么"(learnings 见 [References](references.md#vercel-agent-eval--packagesplayground))。没有具体设计,优先级低于 Compare。
|
|
157
|
-
|
|
158
|
-
## 用 Reports 积木重建 view(设计提案)
|
|
159
|
-
|
|
160
|
-
> 状态:设计提案。依赖的两个提案已部分落地(results 读取面、report 计算与组件,见 [Source Map](source-map.md#results-lib-与-reports));三步迁移(读取层 / 统计层 / 渲染层)未动,attempt 深链路由已先行实现。落地前,本文其余部分描述的现状照旧成立。
|
|
161
|
-
|
|
162
|
-
[Reports](reports.md) 把「自己搭报告页」拆成组件 + 计算函数 + 结果库三种零件之后,view 的正确定位随之改变:**view 不再是一套并行实现,而是用同一批零件搭出来的「默认报告页 + 证据室」**——用户搭页面用什么零件,view 自己就用什么。view 因此成为这套积木的第一个常驻消费者,组件与计算函数的正确性被它天天验证;反过来,上面「计划中的小功能」里的三件事(跳过列表、Compare、散点图)也全部退化成「拼现成积木」,不再是专项开发。
|
|
163
|
-
|
|
164
|
-
逐层替换:
|
|
165
|
-
|
|
166
|
-
| view 今天 | 合流后 | 顺带兑现 |
|
|
167
|
-
|---|---|---|
|
|
168
|
-
| `loadSummaries` / `readSummary`,版本判定散在 loader | [`openResults`](results-lib.md#读openresults) | 「结果版本提示与跳过列表」= 渲染 `results.skipped`,不再专门做;计划中的 normalize 层就是 results lib |
|
|
169
|
-
| `aggregateRows` 把全部历史揉成一行 | 选择器 + 计算函数(`overview` / `table(rows: "experiment")`) | 跨快照去重白捡——`--resume` 合入结果被重复计数的问题顺带修掉;「累计 vs 最新」从 loader 的既成事实变成 UI 上可切换的两次显式调用 |
|
|
170
|
-
| `ExperimentTable` / Runs 表 / 总览指标(自绘) | `MetricTable` / `RunOverview` | 覆盖率角标、缺数据渲染、better 排序这些诚实细节自动继承 |
|
|
171
|
-
| 质量 × 成本散点图(计划中,没做) | `MetricScatter` | 见上节 |
|
|
172
|
-
| Compare(计划中,没做) | 两个下拉选快照 + 时间轴 `DeltaTable` 变体 | Reports 待定的「时间轴 delta」在 view 首发,两边永远同一套「对比」语义 |
|
|
173
|
-
| AttemptModal / Traces 瀑布 / 导航壳 | **保留,这是 view 的本体** | 证据室:transcript、断言、代码视图、trace 瀑布——报告积木不重造它们 |
|
|
174
|
-
|
|
175
|
-
导出机制同样合流:
|
|
176
|
-
|
|
177
|
-
- `view --out <目录>` 的工件复制换成 [`copyRun`](results-lib.md#复制与瘦身copyrun)(sources/events/trace,diff/o11y 照旧不带);单文件导出已整个移除(见上文「静态导出」的移除记录),合流时不必再背这个形态。
|
|
178
|
-
- 烘进 HTML 的 `__NICEEVAL_VIEW_DATA__` 从私有 rows 换成**官方数据契约**(OverviewData / TableData / ScatterData + 快照元信息)。外部脚本从此没有理由扒 HTML——coding-agent-memory-evals 曾用字符串标记从 index.html 里抠内嵌 JSON、再正则消毒构建机路径,那类 hack 的存在本身就是数据契约缺位的证据;但内嵌数据仍不是承诺的持久化格式,要数据走 [Reports 场景三](reports.md#dx-模拟)自己算。
|
|
179
|
-
- 补 `#/attempt/<run>/<result>` 路由,路由参数就是 `AttemptRef`——报告页(前门)与 view(证据室)靠同一个身份契约打通,`attemptHref` 从此有确定的去处(对应 Reports 待定问题 5)。**已实现**:`src/view/app/lib/attempt-route.ts` + `App.tsx` 接线,loader 的 `withViewRefs` 给每条 result 注入 `attemptRef`;旧 `?modal=` 参数保留为只读回退。
|
|
180
|
-
|
|
181
|
-
迁移顺序即依赖顺序,每步独立可交付:
|
|
182
|
-
|
|
183
|
-
1. **换读取层。** results lib 落地,view 的 loader 删掉,skipped 列表 UI 直接上——行为中立的重构,外加诚实增强。
|
|
184
|
-
2. **换统计层。** 计算函数落地,`aggregateRows` 删掉;榜单数字会因去重而变(变得更对),在 changelog 里明说。
|
|
185
|
-
3. **换渲染层 + 补两个 tab。** 榜单换 `MetricTable`,新增 scatter,Compare 以时间轴 delta 首发;AttemptModal / Traces 原样保留。
|
|
186
|
-
|
|
187
|
-
界线不变:view 是**一份固定摆法的默认报告页**,不长配置、不长插件——想换摆法,去写自己的页面([Reports](reports.md)),零件是同一批。
|
|
188
|
-
|
|
189
|
-
## 相关阅读
|
|
190
|
-
|
|
191
|
-
- [Observability](observability.md#结果可视化niceeval-view) —— 事件流、trace、usage/cost 这些 view 渲染的数据从哪来。
|
|
192
|
-
- [Results Format](results-format.md) —— view 读取的 `.niceeval/<run>/summary.json` 与 attempt 级 JSON 工件。
|
|
193
|
-
- [References](references.md#vercel-agent-eval--packagesplayground) —— 这次调研 agent-eval playground 的完整记录。
|
|
194
|
-
- [Experiments](experiments.md) —— `experimentId`、可对比组、`niceeval exp` 怎么产生这些历史快照。
|
package/docs/vision.md
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Vision
|
|
2
|
-
|
|
3
|
-
niceeval 应该让人感觉是**一个评测工具**,而不是"一堆按被测对象形状各写一遍的脚本"。无论你评的是一个远程 HTTP agent,还是一个塞进 Docker 里跑编码任务的 Claude Code,写法和读法都应该是同一套。
|
|
4
|
-
|
|
5
|
-
为了在差异巨大的被测对象之间维持这种一致体验,代码库守一条规则:
|
|
6
|
-
|
|
7
|
-
> **核心(core)拥有通用的"评测控制面";"连到哪个 AI"由 `Agent`(自实现的 `Adapter`)拥有,"在哪里跑"由 `Sandbox` 拥有。**
|
|
8
|
-
|
|
9
|
-
这条规则是从 crabbox(一个面向 ~27 个云 provider 的远程执行层)学来的同一种纪律:核心永远不按名字分支,它对着能力(capability)分发。
|
|
10
|
-
|
|
11
|
-
## 三种"快"
|
|
12
|
-
|
|
13
|
-
niceeval 这个名字是一份承诺。把它拆开,就是三条要落到设计上的约束。
|
|
14
|
-
|
|
15
|
-
### 写得快
|
|
16
|
-
|
|
17
|
-
写一个 eval 的成本必须趋近于"写一句断言"。为此:
|
|
18
|
-
|
|
19
|
-
- **路径即身份。** `evals/weather/brooklyn.eval.ts` 的 id 自动是 `weather/brooklyn`,禁止手写 id/name。改名即改路径,不会有"id 和文件名对不上"的腐烂。
|
|
20
|
-
- **一文件一 eval,数组即扇出。** 默认导出一个 eval;默认导出一个数组就扇出成 `weather/0000`、`weather/0001`…… 数据集就是 `rows.map(row => defineEval(...))`。
|
|
21
|
-
- **线性书写,就地断言。** `async test(t)` 里顺着写 `t.send` / `t.check`,把中间结果赋给局部变量再断言。没有回调金字塔。
|
|
22
|
-
- **gate 与 atLeast 两档。** 硬性要求用 `gate`(不过即 fail),带阈值的质量分用 `atLeast`(默认记录为软失败,只在 `--strict` 下才 fail)。作者不必为"这条算不算致命"反复纠结。
|
|
23
|
-
|
|
24
|
-
### 跑得快
|
|
25
|
-
|
|
26
|
-
评测天然是 I/O 密集且昂贵(每个 case 可能是一次 LLM 调用、一次沙箱启动、一次完整测试套件)。运行器必须把这份昂贵摊薄:
|
|
27
|
-
|
|
28
|
-
- **有界并发。** 可配置的最大并发,池满则等任一完成再补位。
|
|
29
|
-
- **指纹缓存。** 用 `(eval 代码 + 配置)` 的指纹标识一次运行;已通过且指纹未变的,默认跳过。重跑只重算改动过的。
|
|
30
|
-
- **沙箱复用与预热。** 沙箱启动是大头;支持预热池、跨 case 复用,把冷启动从关键路径上挪走。
|
|
31
|
-
- **早停(earlyExit)。** 一个任务跑 N 次取通过率时,一旦先过了一次,就中止该任务剩余的重试(可关)。
|
|
32
|
-
|
|
33
|
-
### 看得快
|
|
34
|
-
|
|
35
|
-
评测的产出不只是"过/挂",而是"为什么"。失败必须能被快速归因:
|
|
36
|
-
|
|
37
|
-
- **流式控制台。** 每个 case 完成即出一行,失败的断言内联展开,不用等全部跑完。
|
|
38
|
-
- **结构化工件。** 事件流、transcript、文件 diff、断言结果都落盘成机器可读格式,可回放、可二次分析。
|
|
39
|
-
- **统一 trace。** 不同 agent 的原始 transcript 被归一化成同一套事件模型(工具调用、文件读写、shell 命令、思考块),"agent 到底干了什么"一眼可见;OTLP traces 则统一归一到 **OpenTelemetry GenAI 语义约定**(每 agent 一个薄 mapper),view 只认这套 canonical,跨 agent 瀑布图天然可比。详见 [Observability](observability.md)。
|
|
40
|
-
|
|
41
|
-
## 两个正交概念,一条承重墙
|
|
42
|
-
|
|
43
|
-
被测对象的差异被收进两个互相正交的轴。理解它们的边界,就理解了整个库。
|
|
44
|
-
|
|
45
|
-
### `Agent` —— 连到哪个 AI(以及谁来定协议)
|
|
46
|
-
|
|
47
|
-
`Agent` 回答"我把任务发给谁、怎么收结果"。这里有一个**关键的、区别于 eve 的前提**:
|
|
48
|
-
|
|
49
|
-
> **niceeval 不定义任何 agent 协议。** 每一条连到 AI 的连接 —— 你自己的 agent、你的后端服务、Claude Code / Codex —— 都是自己实现的 **Adapter**。experiment 引用一个 agent,而不是给一个 url。
|
|
50
|
-
|
|
51
|
-
eve 能用一个 url 当 target,是因为它定义了一套协议、被测 agent 恰好会说。niceeval 没有这个假设,所以**没有 `--url`、没有通用的 http target**:连你自己的服务也是你写一个 agent,URL 是它的内部配置。
|
|
52
|
-
|
|
53
|
-
- **Agent** 是抽象(niceeval 眼里"一条连到 AI 的连接")。`Agent.kind` 只有两类。
|
|
54
|
-
- **Adapter** 是它的实现,由用户编写;按 `kind` 分两类:远程(`defineAgent`,按你服务的协议发请求)、沙箱(`defineSandboxAgent`,在 `Sandbox` 里 spawn coding agent 的 CLI)。进程内直调你的函数不是独立的第三类——它只是远程型 `send` 内部的一种实现方式,而且不推荐:测函数绕过了用户实际走的链路,见[接入你的 Agent · 为什么不直调](../docs-site/zh/guides/connect-your-agent.mdx)。
|
|
55
|
-
|
|
56
|
-
核心通过 `Agent.kind` 和 `send` 的构造证据(而不是声明式能力位)决定 `t` 暴露哪些动作:会话型暴露 `t.send` / `t.calledTool`;沙箱型额外暴露 `t.sandbox`,里面按语义分成文件 IO、命令执行、结果断言 / diff。接一个新 agent(无论是你的 agent 还是 bub)= 实现一个 Adapter,**不动核心一行**。详见 [Adapter 契约](adapters/contract.md)。
|
|
57
|
-
|
|
58
|
-
### `Sandbox` —— 沙箱型 agent 在哪里跑
|
|
59
|
-
|
|
60
|
-
一个 `Sandbox` 把"隔离环境"的全部特殊性关进一个盒子:跑命令、读写文件、上传/下载、用 `cwd` 指定命令目录、起停。Docker 是一个实现,Vercel Sandbox 是一个实现,其它三方各是一个实现。后端按环境自动选择(有云 token 用云,否则用 Docker),也可显式指定。它与 Agent 正交:任意沙箱型 agent × 任意 sandbox 后端自由组合。详见 [Sandbox](sandbox.md)。
|
|
61
|
-
|
|
62
|
-
## 边界画在"行为"上
|
|
63
|
-
|
|
64
|
-
哪些地方允许出现 agent 名字 / sandbox 名字?**路由可以,行为不行。**
|
|
65
|
-
|
|
66
|
-
- **允许:** 配置 schema、CLI 标志、experiment 引用 agent / sandbox 这种路由。这是核心的活。
|
|
67
|
-
- **不允许:** 一旦代码要决定"CLI 参数怎么拼""transcript 在哪""命令怎么在容器里多路复用",这个决定就属于对应的 Adapter / Sandbox,**绝不**以 `if (name === ...)` 的形式穿过运行器、评分、报告这些核心路径。
|
|
68
|
-
|
|
69
|
-
健康的形状是这样:
|
|
70
|
-
|
|
71
|
-
- **核心** 发现 eval、收集断言、计算判决、调度并发、做缓存、写报告与工件。它对每个被测对象一视同仁。
|
|
72
|
-
- **Agent(Adapter) / Sandbox** 各自解决自己那一层的特殊性,通过接口把能力交还核心。
|
|
73
|
-
|
|
74
|
-
当一个修复看起来"需要在核心里加一个 agent-specific 分支"时,先去对应适配器加或复用一个 hook。一个中性的小 hook,几乎总比把 `name == ...` 的分支线穿过核心要干净。
|
|
75
|
-
|
|
76
|
-
## 与参考项目的关系
|
|
77
|
-
|
|
78
|
-
- **eve evals** 给了我们 DX 形状:`defineEval`、路径即身份、gate/soft 断言、LLM-as-judge、reporter 插件、有界并发运行器。niceeval 的会话型 eval 基本是这套模型的再实现。
|
|
79
|
-
- **Vercel agent-eval** 给了我们 agent 评测的工程形状:`Adapter` 接口、`Sandbox` 抽象与多后端、transcript 归一化与注入、失败分类、指纹缓存。它的 fixture(目录约定自动发现 PROMPT + EVAL 测试)没有照搬——niceeval 里沙箱要放什么文件,是 `test()` 里的手工调用,不靠目录约定隐式发现。
|
|
80
|
-
- **crabbox** 给了我们这条"核心 vs 适配器"的纪律,以及"文档是用户面真相、source-map 把行为映射回代码"的文档观。
|
|
81
|
-
|
|
82
|
-
niceeval 的新意不在任何单一机制,而在于**把这两种本来分裂的评测范式(会话型 / 沙箱型)收敛进同一套 `defineEval` + 评分器 + 运行器 + 报告器**,让"评我自己的函数"和"评一个塞进容器的 Claude Code"读起来是同一种东西。
|
|
83
|
-
|
|
84
|
-
## 相关阅读
|
|
85
|
-
|
|
86
|
-
- [Architecture](architecture.md) —— 模块分层与数据流。
|
|
87
|
-
- [Concepts](concepts.md) —— 术语表。
|
|
88
|
-
- [Agents 与 Adapters](adapters/README.md)、[Sandbox](sandbox.md) —— 连接与沙箱契约。
|