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/README.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# ⚡ niceeval 文档
|
|
2
|
-
|
|
3
|
-
**写得快、跑得快、看得快的 TypeScript evals 库。**
|
|
4
|
-
|
|
5
|
-
## niceeval 是什么
|
|
6
|
-
|
|
7
|
-
niceeval 是一个用 TypeScript 写的评测(evals)库。它用一套声明式的 API 让你定义"什么是好结果",然后把这套判断施加到两类被测对象上:
|
|
8
|
-
|
|
9
|
-
- **coding agent** —— 把现成的 agent CLI(Claude Code、bub、Codex…)放进沙箱里跑一个编码任务,再用测试和评分器验证它干得怎么样;
|
|
10
|
-
- **现成的软件 / 服务** —— 对着一个已经部署的 HTTP 接口或 agent 端点发请求,断言它的回复、工具调用、结构化输出。
|
|
11
|
-
|
|
12
|
-
边缘场景下,也可以让 adapter 在进程内直调你自己的一个纯函数,把 evals 当成"语义级单元测试"跑在 CI 里——但这不测生产链路,不是推荐的默认写法,详见[接入你的 Agent · 为什么不直调](../docs-site/zh/guides/connect-your-agent.mdx)。
|
|
13
|
-
|
|
14
|
-
一句话:**niceeval 把"agent 评测"和"普通函数评测"收敛到同一套 `defineEval` + 评分器 + 运行器 + 报告器里**。"连到哪个 AI、用什么协议连"被收进一个 `Agent` 抽象(每个 agent 都是自实现的 adapter,niceeval 不定义通用协议,`Agent.kind` 只有 `"remote"` / `"sandbox"` 两类),"沙箱型 agent 在哪里跑"则由 `Sandbox` 负责。
|
|
15
|
-
|
|
16
|
-
DX 的灵感来自 [eve.dev 的 evals](https://eve.dev/docs/evals/overview)(声明式、路径即身份、gate/soft 分层断言、LLM-as-judge);适配器与沙箱的灵感来自 Vercel 的 agent-eval(fixture = PROMPT + EVAL 测试、Docker / 三方沙箱、transcript 可观测性)。
|
|
17
|
-
|
|
18
|
-
## 为什么叫 "fast"
|
|
19
|
-
|
|
20
|
-
`fast` 指三件事,对应三种"快":
|
|
21
|
-
|
|
22
|
-
- **写得快(fast to author)** —— 一个文件一个 eval,id 由路径自动推导;`async test(t)` 线性书写,断言就地声明;数据集一行 `.map` 扇出成几十个 case。没有样板,没有回调地狱。
|
|
23
|
-
- **跑得快(fast to run)** —— 有界并发调度;基于指纹的结果缓存跳过已通过的 case;沙箱可复用、可预热;`earlyExit` 在第一次通过后停掉同一任务的其余重试。
|
|
24
|
-
- **看得快(fast to read)** —— 流式控制台输出即时反馈;结构化工件(事件流、transcript、diff、断言结果)落盘;统一的 trace 让"agent 到底干了什么"一目了然。
|
|
25
|
-
|
|
26
|
-
完整论证见 [Vision](vision.md)。
|
|
27
|
-
|
|
28
|
-
## 整体形状
|
|
29
|
-
|
|
30
|
-
```text
|
|
31
|
-
你的 evals/ 目录 niceeval 核心 连到 AI(自实现的 agent)
|
|
32
|
-
---------------- -------------- ------------------------
|
|
33
|
-
weather.eval.ts --discover--> Runner --send--> Agent ┬─ 远程 adapter (你的服务)
|
|
34
|
-
sql.eval.ts │ └─ 沙箱 adapter ── Sandbox
|
|
35
|
-
fixtures/button.eval.ts │ (claude-code (docker /
|
|
36
|
-
(test() 里手工写入文件) │ codex / bub …) 三方)
|
|
37
|
-
▼
|
|
38
|
-
Scorers ── Reporters ── .niceeval/<run>/
|
|
39
|
-
(expect / scoped / (summary.json / 事件流 /
|
|
40
|
-
judge / 测试) transcript / diff)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
- **核心(core)** 拥有对所有被测对象都一样的部分:eval 发现、断言收集、评分判决、并发调度、缓存、报告、工件落盘。
|
|
44
|
-
- **Agent** 是"一条连到 AI 的连接"的抽象,由 experiment 引用;**Adapter** 是它的具体实现,**由你编写**(claude-code / codex 等内置)。niceeval **不定义任何 agent 协议** —— 连你自己的服务也是你写一个 agent,URL 是它的内部配置,没有 `--url`。
|
|
45
|
-
- **Sandbox** 拥有"沙箱型 agent 在哪里跑、如何隔离"的全部细节(Docker、Vercel Sandbox、其它三方)。
|
|
46
|
-
|
|
47
|
-
核心从不直接 `if (agent === "claude-code")` 或 `if (sandbox === "docker")` —— 它对着接口分发。这条边界是整个设计的承重墙,详见 [Architecture](architecture.md) 和 [Vision](vision.md)。
|
|
48
|
-
|
|
49
|
-
## 快速开始
|
|
50
|
-
|
|
51
|
-
```sh
|
|
52
|
-
npm install -D niceeval
|
|
53
|
-
npx niceeval init # 生成 evals/ 与 niceeval.config.ts
|
|
54
|
-
npx niceeval list # 只检查 eval 发现
|
|
55
|
-
npx niceeval exp local # 通过 experiment 运行 eval
|
|
56
|
-
npx niceeval exp local weather # exp 后的位置参数 = eval id 前缀
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
> 执行 eval 必须通过 experiment:experiment 选「对着哪个 agent / model / flags 怎么跑」,`exp` 后的位置参数只筛「跑哪些 eval」。详见 [CLI](cli.md)。
|
|
60
|
-
|
|
61
|
-
最小的一个 eval:
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
// evals/weather/brooklyn.eval.ts
|
|
65
|
-
import { defineEval } from "niceeval";
|
|
66
|
-
import { includes } from "niceeval/expect";
|
|
67
|
-
|
|
68
|
-
export default defineEval({
|
|
69
|
-
description: "布鲁克林天气查询",
|
|
70
|
-
async test(t) {
|
|
71
|
-
await t.send("布鲁克林今天天气怎么样?");
|
|
72
|
-
t.succeeded(); // 作用域断言:运行没失败
|
|
73
|
-
t.calledTool("get_weather", { input: { city: "Brooklyn" } });
|
|
74
|
-
t.check(t.reply, includes("晴")); // 值级断言:就地评估
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
一个沙箱里的 coding-agent eval——起始文件手工放进沙箱,没有目录约定自动发现:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// evals/fixtures/button.eval.ts
|
|
83
|
-
import { defineEval } from "niceeval";
|
|
84
|
-
import { includes } from "niceeval/expect";
|
|
85
|
-
|
|
86
|
-
export default defineEval({
|
|
87
|
-
description: "实现一个 Button 组件",
|
|
88
|
-
async test(t) {
|
|
89
|
-
await t.sandbox.writeFiles({ "package.json": BUTTON_PACKAGE_JSON });
|
|
90
|
-
await t.send("用项目现有的样式系统,在 src/components/Button.tsx 导出一个 Button 组件,接受 label 和 onClick 两个 prop,并实现 hover 态。");
|
|
91
|
-
|
|
92
|
-
t.sandbox.fileChanged("src/components/Button.tsx");
|
|
93
|
-
const src = t.sandbox.diff.get("src/components/Button.tsx");
|
|
94
|
-
t.check(src, includes("label"));
|
|
95
|
-
t.check(src, includes("onClick"));
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
```sh
|
|
101
|
-
npx niceeval exp local fixtures/button
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
`local` 实验文件里给这个 agent 配一个 `sandbox: dockerSandbox()`(从 `niceeval/sandbox` 导入)——沙箱型 agent 必须显式指定后端,没有默认值,也没有 `--sandbox` 这种 CLI 覆盖。完整流程见 [Getting Started](getting-started.md)。
|
|
105
|
-
|
|
106
|
-
## 接着读哪一篇
|
|
107
|
-
|
|
108
|
-
按你的意图挑:
|
|
109
|
-
|
|
110
|
-
- **建立心智模型:** [Vision](vision.md)、[Concepts(术语表)](concepts.md)、[Architecture](architecture.md)。
|
|
111
|
-
- **写 eval:** [Eval Authoring(编写 eval)](eval-authoring.md)、[Assertions(断言参考:作用域 + 来源)](assertions.md)、[Scoring(评分器)](scoring.md)。
|
|
112
|
-
- **连 AI / 接 agent:** [Agents 与 Adapters(定位与导航)](adapters/README.md)、[Origin 应用接入手册(五个应用的接入记录)](origin-integration.md)、[Tier Sync(examples origin→tier 同步维护工具)](tier-sync.md)、[Adapter 契约(逐 API 适配义务)](adapters/contract.md)、[Adapter 写法(递进式写法)](adapters/authoring.md)、[Capabilities by Construction(能力由构造证明)](capabilities-by-construction.md)、[采集设计(三条外部路线对比)](adapters/collection.md)、[接入目标矩阵(12 个被测对象调研)](adapters/targets.md)、[Coding Agent Skills / Plugins DX](adapters/coding-agent-skills-plugins.md)、[Sandbox](sandbox.md)、[Observability(含 OTel trace 瀑布图)](observability.md)。
|
|
113
|
-
- **设计提案(未实现):** [Multi-Agent(多 agent eval 的三种场景)](multi-agent.md)、[Reports(自己搭报告页:React 组件 × 查询模型两档积木)](reports.md)、[Results Lib(结果数据的读写库,Reports 的数据层)](results-lib.md)。
|
|
114
|
-
- **框架自测:** [E2E CI(全链路 e2e 测试方案;`e2e/` 的 tier1 矩阵已落地,workflow 未建)](e2e-ci.md)。
|
|
115
|
-
- **跑与看:** [Experiments(实验/运行矩阵)](experiments.md)、[Runner(执行引擎)](runner.md)、[CLI 参考](cli.md)、[Observability(可观测性)](observability.md)、[Results Format(结果保存格式)](results-format.md)、[View(本地查看器)](view.md)。
|
|
116
|
-
- **背景调研:** [References(从其它项目学到什么)](references.md)、[agent-eval 是怎么做适配的](adapters/reference/agent-eval.md)、[OTel GenAI 等「行为怎么记」标准](adapters/reference/otel-genai.md)、[四个主流 agent loop 接入面调研](adapters/reference/agent-loop-apis.md)、[应用侧 OTel 埋点生态调研](adapters/reference/otel-instrumentation.md)、[eve 的协议机制](adapters/reference/eve-protocol.md)、[Claude Code 自带 OTel 遥测能不能提前拿结果](adapters/reference/claude-code-otel-telemetry.md)。
|
|
117
|
-
|
|
118
|
-
## 关于这些文档
|
|
119
|
-
|
|
120
|
-
这些是**内部设计与实现协作文档**:记录 niceeval 的目标 DX、架构边界和实现协作约定。[Source Map](source-map.md) 用来帮助把设计行为定位到源码;如果代码实现与文档设计不一致,应进一步讨论并决定是修代码、修设计,还是记录为明确的阶段性差异。文档全部用中文书写。
|
package/docs/adapters/README.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Agents 与 Adapters
|
|
2
|
-
|
|
3
|
-
这一组文档讲 niceeval 如何"连到一个被测对象"。这是整个库最容易被想歪的地方,先把两个核心论点摆出来:
|
|
4
|
-
|
|
5
|
-
> 1. **niceeval 不定义任何 agent 协议。** 每一个被测对象——你自己的 agent、你的后端服务、Claude Code / Codex 这种 coding agent——**都是自己实现的 adapter**。experiment 引用 agent,而不是给一个 url。
|
|
6
|
-
> 2. **adapter 真正的难点不是"连上",而是把各 agent 五花八门的原始返回归一化成一套标准的事件流。** 归一化之后,整套断言都是免费的、与 agent 无关的。
|
|
7
|
-
|
|
8
|
-
两个词:
|
|
9
|
-
|
|
10
|
-
- **Agent** —— 抽象。niceeval 眼里"一个被测对象",由 experiment 引用。`t` 上暴露什么由构造证据决定(见[契约 · 能力从哪来](contract.md#能力从哪来构造证明不是问卷)),不是声明式的能力位。运行器只认 Agent 契约。
|
|
11
|
-
- **Adapter** —— 实现。某个 agent 的具体代码,**由用户编写**(niceeval 也内置几个常用 coding agent)。一个 Adapter 实现一个 Agent。
|
|
12
|
-
|
|
13
|
-
按 `kind` 分两种,但**契约完全一样**(都是 `send` 进、`Turn` 出):
|
|
14
|
-
|
|
15
|
-
- **remote agent**(`defineAgent`,`kind: "remote"`)—— 你在 `send` 里按你服务的协议发 HTTP。不建议把 `send` 写成进程内直调你的函数,理由见[接入你的 Agent · 为什么不直调](../../docs-site/zh/guides/connect-your-agent.mdx)。
|
|
16
|
-
- **sandbox agent**(`defineSandboxAgent`,`kind: "sandbox"`)—— 在沙箱里 spawn 一个 coding agent 的 CLI,跑完读回 transcript。
|
|
17
|
-
|
|
18
|
-
## 这组文档怎么分
|
|
19
|
-
|
|
20
|
-
| 文档 | 回答什么问题 |
|
|
21
|
-
|---|---|
|
|
22
|
-
| [Adapter 契约](contract.md) | **规范参考**:Agent / Turn / 事件流的精确形状;逐 `t` API 的适配义务——eval 调 `t.respond` / `t.newSession` / `t.notCalledTool`…时,adapter 必须返回什么、违约怎么暴露 |
|
|
23
|
-
| [Adapter 写法](authoring.md) | **怎么做出来**:递进式写法(收发消息 → 事件流 → 多轮会话 → HITL → tracing)、remote / sandbox 参考实现、采集层技巧、`shared` 工具袋 |
|
|
24
|
-
| [采集设计](collection.md) | **从哪采、字段从哪来**:三条外部路线对比、双轨四通道、claude-code / codex / bub / AI SDK 的采集矩阵、接新对象的决策树 |
|
|
25
|
-
| [Coding Agent Skills / Plugins DX](coding-agent-skills-plugins.md) | 沙箱型 coding agent 怎么安装 skill / plugin,并组织 A/B 实验 |
|
|
26
|
-
| [Capabilities by Construction](../capabilities-by-construction.md) | 能力从哪来的设计动机与源码落点:为什么没有 `capabilities` 声明式字段、`t` 上每个能力各自的构造证据是什么 |
|
|
27
|
-
| [Observability · OTLP traces](../observability.md#otlp-traces--统一瀑布图) | OTel 接入怎么喂 `niceeval view` 的调用瀑布图——只画图,不产出事件、不参与任何断言 |
|
|
28
|
-
| [agent-eval 参考](reference/agent-eval.md) | Vercel agent-eval 怎么做同一件事的源码阅读记录——学习资料,不是 niceeval 的实现 |
|
|
29
|
-
| [OTel GenAI 等标准参考](reference/otel-genai.md) | "agent 行为怎么记"的行业标准调研:OTel GenAI semconv 对比 agent-eval 自定义方案,附 OpenInference / OpenLLMetry / OpenAI Agents SDK / AG-UI / Langfuse |
|
|
30
|
-
| [agent loop 接入面](reference/agent-loop-apis.md) | 四个主流 agent loop(OpenAI Agents SDK / Claude Agent SDK / LangGraph / pi)的原生 API / 会话 / HITL / 遥测面调研 |
|
|
31
|
-
| [OTel 埋点生态](reference/otel-instrumentation.md) | 应用侧现成埋点(AI SDK telemetry / OpenLLMetry / OpenInference / 官方 contrib)里到底有没有 eval 要的数据 |
|
|
32
|
-
| [eve 协议机制](reference/eve-protocol.md) | 第三条路线:自有运行时原生吐协议(`HandleMessageStreamEvent` 26 种事件、三级坐标、requestId 回答)——`StreamEvent` 演进的上限参照 |
|
|
33
|
-
| [Claude Code 自带 OTel 遥测](reference/claude-code-otel-telemetry.md) | Claude Code CLI 自己的 OTLP 导出(metrics/logs GA、traces beta)能不能让 claude-code adapter 提前拿到中间结果,而不是等 `--print` 进程退出才读整份 transcript |
|
|
34
|
-
|
|
35
|
-
## 为什么是 experiment 选 agent,不是 `--url`
|
|
36
|
-
|
|
37
|
-
eve 能用一个 url 当被测对象,是因为它**定义了一套自己的协议**、被测 agent 恰好会说——于是"连哪"退化成"哪个 url"。niceeval 没有这个前提:不存在一套通用协议让任意 agent 都会说。所以:
|
|
38
|
-
|
|
39
|
-
- **没有 `--url`、没有通用的 "http agent"。** 要连你的 HTTP 服务,你写一个 agent,它内部知道你服务的协议(URL、鉴权、消息格式都是它的私事)。
|
|
40
|
-
- **agent 写进 experiment。** "评本地 vs 评线上"靠 agent 自己读 env,或写成两个 experiment / 两个 agent 配置:
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npx niceeval exp local weather # 评本地
|
|
44
|
-
npx niceeval exp prod weather # 评线上
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Agent × Sandbox 正交
|
|
48
|
-
|
|
49
|
-
```text
|
|
50
|
-
experiment.agent 选「连哪个被测对象」(自实现的 adapter)
|
|
51
|
-
--sandbox <backend> 选「沙箱型 agent 在哪跑」(docker / vercel / 三方)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
任意沙箱型 agent × 任意 sandbox 后端自由组合:`claude-code` 可跑 docker 也可跑 vercel;同一个 docker 沙箱可跑 `claude-code` 也可跑 `bub`。运行器按 experiment / `--sandbox` 备好 `Sandbox` 交给 `ctx.sandbox`,二者只通过 `Sandbox` 接口交互。remote agent 忽略 sandbox。详见 [Sandbox](../sandbox.md)。
|
|
55
|
-
|
|
56
|
-
## 相关阅读
|
|
57
|
-
|
|
58
|
-
- [Observability](../observability.md) —— transcript → 标准事件流的归一化、o11y、用量。
|
|
59
|
-
- [Scoring](../scoring.md) / [Assertions](../assertions.md) —— 这套事件流之上的全部断言。
|
|
60
|
-
- [Vision](../vision.md) —— 为什么名字只能用于路由、为什么没有通用协议。
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# Adapter 写法 —— 递进式写出来,不用一次做全套
|
|
2
|
-
|
|
3
|
-
这一篇讲怎么把一个 adapter **写出来**:递进(每一步要写什么代码、解锁什么断言)、remote / sandbox 两个参考实现、采集层技巧、`shared` 工具袋。每个 `t` API 的精确适配义务(返回什么、违约怎么暴露)是规范内容,见 [Adapter 契约](contract.md)——写每一步之前先把对应义务读一遍。
|
|
4
|
-
|
|
5
|
-
## 递进:写什么解锁什么
|
|
6
|
-
|
|
7
|
-
写 adapter 不需要一次做全套。`t` 上解锁什么完全由**构造证据**决定(见[契约 · 能力从哪来](contract.md#能力从哪来构造证明不是问卷)),不是声明式的能力位——先做你已经做到的,其余留到需要时再补。**每一步解锁的是具体的 `t` 函数和断言,不是笼统的"更多能力"**,晚做后面几步不影响先跑起来的 eval,也不用改核心一行。
|
|
8
|
-
|
|
9
|
-
| 步骤 | adapter 要做什么 | 解锁的 `t` 函数 / 断言 |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| **1 · 收发消息** | 只实现 `send`,返回 `{ events: [], data, status }`。remote agent 把响应塞进 `data`;sandbox agent 按 `res.exitCode` 定 `status` | `t.send()`(单发一轮)、`t.sendFile()`(能调;文件是否起作用取决于 adapter 读不读 `TurnInput.files`)、`turn.outputEquals` / `outputMatches`、按 `status` 判的 `t.succeeded()` |
|
|
12
|
-
| **2 · 事件流** | 把原始返回映射成**完整**的 `StreamEvent[]`(callId 配对、时序保真、双名字,纪律见[契约 · 标准事件流](contract.md#标准事件流))。remote 在 `send` 里直接翻译;sandbox 加一个 transcript 解析器(`o11y/parsers/<name>.ts`) | 整套作用域断言:`calledTool` / `toolOrder` / `usedNoTools` / `maxToolCalls` / `messageIncludes` / `noFailedActions` / `calledSubagent` / `event` / `eventOrder` / `eventsSatisfy`;**负断言(`notCalledTool` / `notEvent`)的可信度取决于事件来源是否带完整性证明**,见[契约 · 负断言完整性规则](contract.md#负断言的完整性规则) |
|
|
13
|
-
| **3 · 多轮会话** | `send` 里接上 `ctx.session` 的续接存取器:`history<TMsg>()`(客户端带全量历史)或 `id` + `capture(id)`(服务端记历史)。新会话线的"第一轮"是存取器的自然空态(`history().get()` 空数组、`id` 为 `undefined`),不需要写判断分支 | `t.send()` 可多次、`t.reply`、`t.newSession()` |
|
|
14
|
-
| **4 · HITL** | 在第 3 步之上加两条行为:agent 停下等人时 `status` 置 `"waiting"`、每个待回答问题吐一条 `input.requested`(字段填法见[契约 · InputRequest](contract.md#inputrequesthitl-请求要填哪些字段));回答轮按 `input.responses` 里的 `requestId` 交回应用,读了一半的流现场用 `ctx.session.hold()` / `take()` 存取 | `t.parked()`、`t.requireInputRequest(filter?)`、`t.respond(...)` / `t.respondAll(optionId)` |
|
|
15
|
-
| **5 · tracing / OTel** | 一个 `tracing` 块(env-based 或 file-based 导出配置)+ 一个 span mapper(`o11y/otlp/mappers/<name>.ts`,归一到 canonical GenAI semconv)。remote agent 少见(没有独立进程可挂 exporter,一般跳过);没有 OTel 输出的 CLI 可从 transcript 时间戳合成 span,见 [Observability · OTLP traces](../observability.md#otlp-traces--统一瀑布图) | `EvalResult.trace`、`niceeval view` 瀑布图,可跨 agent 叠加对比 |
|
|
16
|
-
|
|
17
|
-
一点提醒:**HITL 不是"事件流 + 多轮会话的交集"。** `input.requested` 是独立事件类型,不需要完整的工具可观测;HITL 真正依赖的是第 3 步的续接(`respond` 本质是拿着回答再发一轮,得靠同一条 session 续上)+ 上表那两条专门行为。三个义务缺一的具体后果,见[契约 · HITL 握手](contract.md#hitl-握手一次完整时序)。
|
|
18
|
-
|
|
19
|
-
以上五步是"接得上"到"要更细信号"的递进路径,不是要背的固定档位名字——**接入应用整体投入多大(要不要改应用内部代码)是另一个正交的问题,见 [docs-site · Tier](../../docs-site/zh/concepts/tier.mdx)**:那三档(Tier 1 只接 send / Tier 2 + OTel / Tier 3 侵入改造 + experiment flags)说的是"改不改被测应用",本篇这五步说的是"adapter 这一个文件里写多少代码"——同一个 Tier 1(应用零改动)的 adapter,也可以只做到第 1 步,或者一路做到第 5 步。两种坐标轴不要混着用同一套名字。
|
|
20
|
-
|
|
21
|
-
remote 和 sandbox 两种 `kind` 都不动核心一行——这是设计承重墙,见 [Vision](../vision.md)。
|
|
22
|
-
|
|
23
|
-
## 三段式:一个手写 send 里其实是三件互不相干的事
|
|
24
|
-
|
|
25
|
-
早期的 remote adapter 写法容易长成"一个 send 函数 + 一两百行手写 parser"——不是因为这件事本身复杂,是因为三类不同性质的问题被拧在同一个函数的局部变量和控制流里,谁都理不清、谁都没法单独复用。摊开看,真正互不相干的只有三段:
|
|
26
|
-
|
|
27
|
-
1. **transport(怎么发)。** 真做不掉,也不需要抽象——每家的 URL、鉴权、请求体形状本来就不一样。它应该只剩"发一个请求"这么单薄,不背着"顺便解析返回、顺便判断带不带 session id"这些逻辑。
|
|
28
|
-
2. **reduce(原始数据 → `StreamEvent[]`)。** 按数据到达的形状,收窄成两种官方 reducer,不用每接一个新后端就重写一个状态机:
|
|
29
|
-
- **整段落地**——一帧就是一个完整单元,不需要拼接(Claude Agent SDK 的 `assistant` 帧、Codex SDK 的 `item.completed`、pi-agent-core 的 `message_end` 都是这种)。官方转换器 `fromClaudeSdkMessages` / `fromPiAgentEvents` / `fromCodexThreadEvents`(`sdk-streams.ts`)就是这一类,一种帧类型对应一段映射代码。
|
|
30
|
-
- **逐 token / 逐参数增量**——文本和工具参数要靠 `callId`/index 拼接,遇到收尾信号才落地(原始 OpenAI/Anthropic 流式 API、自己手写的 token-by-token SSE 后端都是这个形状)。这类**优先用协议方自己的官方 reducer**(`uiMessageStreamAgent` 借用 `ai` 包的 `readUIMessageStream`,不是自己写状态机);没有官方 reducer 才用 `deltaStream(spec)`(`niceeval/adapter` 导出,见下)——一个通用的 buffer-by-id 累加器,你只声明"这一帧对应哪个操作",拼接时机它自己管。
|
|
31
|
-
- OTel span **不是**第三种数据来源。事件流永远只从这两种官方 reducer 或手写映射来,OTel 只负责画 trace 瀑布图,和事件流完全独立(下文单独说)。
|
|
32
|
-
3. **编排:会话续接 + HITL 暂停恢复。** 这两件事和任何具体协议无关,纯粹是控制流模式,业界统共就两种会话模式、一种暂停恢复模式,不该每接一个新后端就重写一遍 `Map<sessionId, …>` + `if/else`。这两件事的存取器**直接挂在 `ctx.session`(`AgentSession`)上**,adapter 取用即可,不需要额外导入什么件或声明什么能力:
|
|
33
|
-
|
|
34
|
-
| 存取器 | 解决什么 | 用在哪种后端 |
|
|
35
|
-
|---|---|---|
|
|
36
|
-
| `ctx.session.history<TMsg>()` | 会话续接·**客户端带全量历史**——服务端无状态、每轮发完整消息列表:`get()` 取当前历史(新会话线自然是空数组)、`commit(messages)` 写回最新完整列表 | 后端无状态、协议要求整段重发(OpenAI Chat Completions 这类;`uiMessageStreamAgent` 内部就是这个模式的一个特化) |
|
|
37
|
-
| `ctx.session.id` + `ctx.session.capture(id)` | 会话续接·**服务端记历史**——发请求带 `ctx.session.id`(新会话线自然是 `undefined`)、后端回传 id 时 `capture(id)` 写回(只在还没记过时落地,不会被 resume 轮的重复回传覆盖) | 后端自己管会话历史(OpenAI Responses API、多数 SDK 的原生 session/thread) |
|
|
38
|
-
| `ctx.session.hold(state)` / `ctx.session.take<T>()` | HITL 暂停恢复——`t.respond(...)` 对 adapter 而言就是一次带着回答的普通 `send`,adapter 得自己认出"这轮是不是接上次挂起的地方"。停轮时 `hold(state)` 存住现场(读了一半的流、待批准的 callId…),回答轮开头 `take()` 取回——取到即清除,一次消费 | 有 HITL 的后端(不管会话续接走哪种模式) |
|
|
39
|
-
|
|
40
|
-
配合通用的 `driveFrameStream(cursor, reducer, ctx, onFrame?)`(逐帧喂 reducer、处理协议里混入的传输帧、检测 HITL 暂停信号,`niceeval/adapter` 导出),一个有 HITL 的无侵入 adapter 通常只剩这个形状(完整可跑参考见 [Tier 1 claude-sdk / pi-sdk 示例](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1)):
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
function readStream(cursor: SseFrameCursor<Frame>, ctx: AgentContext, stream: MyStream) {
|
|
44
|
-
return driveFrameStream(cursor, stream, ctx, (frame) => {
|
|
45
|
-
if (isApprovalRequest(frame)) {
|
|
46
|
-
ctx.session.hold({ cursor, stream, requestId: frame.id });
|
|
47
|
-
return { pause: { id: frame.id, action: frame.action, options: [{ id: "approve" }, { id: "deny" }] } };
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function send(input: TurnInput, ctx: AgentContext): Promise<Turn> {
|
|
53
|
-
const held = ctx.session.take<{ cursor: SseFrameCursor<Frame>; stream: MyStream; requestId: string }>();
|
|
54
|
-
if (held) {
|
|
55
|
-
const optionId = input.responses?.find((r) => r.requestId === held.requestId)?.optionId;
|
|
56
|
-
await postApprove(held.requestId, optionId === "approve");
|
|
57
|
-
return readStream(held.cursor, ctx, held.stream);
|
|
58
|
-
}
|
|
59
|
-
const res = await fetch(/* … */);
|
|
60
|
-
return readStream(sseJsonFrames<Frame>(res.body!), ctx, myReducer());
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
作者只写两件事:transport(`fetch` 那几行)、`onFrame` 里"这一帧要不要额外处理"的判断——循环、Map、状态机全部不用手写。
|
|
65
|
-
|
|
66
|
-
**OTel 完全不参与这三段,和事件流是两条独立的轨。** `Turn.events`(断言的唯一数据源)永远来自这三段——transport 发请求、reduce 把原始返回映射成事件、编排管会话续接和 HITL。OTel 只负责另一件事:把这一轮的 span 收集起来画进 `niceeval view` 的瀑布图,不产出任何事件、不影响任何断言、也不改变 `send` 的控制流。接 OTel 时 `send` 里唯一要碰的是把 `ctx.telemetry?.headers`(本轮的 W3C traceparent)spread 进请求头,让 span 能精确挂到这一轮——除此之外,`send` 该怎么读流、怎么处理审批帧、怎么续接会话,和有没有接 OTel 完全一样。详见 [Observability · OTLP traces](../observability.md#otlp-traces--统一瀑布图) 与 [OTel 接入指南](../../docs-site/zh/guides/connect-otel.mdx)。
|
|
67
|
-
|
|
68
|
-
## remote agent:评你自己的服务
|
|
69
|
-
|
|
70
|
-
`send` 里发个 fetch,调你服务的接口。remote agent 都是 `defineAgent`:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// agents/support-bot.ts —— URL 是它的私事(niceeval 不定协议)
|
|
74
|
-
import { defineAgent } from "niceeval/adapter";
|
|
75
|
-
|
|
76
|
-
export default defineAgent({
|
|
77
|
-
name: "support-bot",
|
|
78
|
-
async send(input, ctx) {
|
|
79
|
-
const r = await fetch(`${process.env.SUPPORT_BOT_URL}/chat`, {
|
|
80
|
-
method: "POST", body: JSON.stringify({ message: input.text }), signal: ctx.signal,
|
|
81
|
-
});
|
|
82
|
-
const body = await r.json();
|
|
83
|
-
return { events: toStreamEvents(body), data: body.output, status: "completed" };
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
`toStreamEvents` 是你写的小映射:把你服务的"它说了啥、调了哪些工具"翻成标准事件。这是 remote agent 作者唯一的活——不需要写 `capabilities` 字段,`t` 上解锁什么由 `send` 实际做到了什么决定(见[契约 · 能力从哪来](contract.md#能力从哪来构造证明不是问卷))。
|
|
89
|
-
|
|
90
|
-
对是否该把 `send` 换成进程内直调你的函数(而不是走 HTTP),取舍见[接入你的 Agent · 为什么不直调](../../docs-site/zh/guides/connect-your-agent.mdx)——简言之:直调绕过了用户实际走的链路、进程不隔离导致结果不可复现、`ctx.signal` 的超时取消对进程内函数调用失效。niceeval 不禁止这么写(`Agent.kind` 仍然是 `"remote"`,契约不变),但官方文档和内置示例都不推荐它作为默认路径。
|
|
91
|
-
|
|
92
|
-
## sandbox agent:评 coding agent(claude-code / codex / bub)
|
|
93
|
-
|
|
94
|
-
它的"连接"不是 wire 协议,而是:在沙箱里 spawn agent 的 CLI、把 prompt 当参数丢进去、让它在沙箱文件系统上自己跑工具、跑完读回 transcript。
|
|
95
|
-
|
|
96
|
-
claude-code 和 codex 用**同一套**模型,绝大部分**共享**,只有 5 个点因 agent 而异:
|
|
97
|
-
|
|
98
|
-
| 步骤 | 共享? | claude-code | codex |
|
|
99
|
-
|---|---|---|---|
|
|
100
|
-
| 起沙箱 + 写入起始文件 + `git` 基线 | ✅ 共享(runner 固定段) | — | — |
|
|
101
|
-
| 装 CLI | ✗ | `npm i -g @anthropic-ai/claude-code` | `npm i -g @openai/codex` |
|
|
102
|
-
| 鉴权 | ✗ | env `ANTHROPIC_API_KEY` | `codex login --with-api-key` + profile |
|
|
103
|
-
| 拼调用 + 丢 prompt | ✗ | `claude --print [--model <m>] --dangerously-skip-permissions <prompt>` | `codex exec --profile default --json <prompt>` |
|
|
104
|
-
| 模型/参数 | ✗ | CLI flag `--model`(来自 `ctx.model`) | 写 `~/.codex/default.config.toml`(同) |
|
|
105
|
-
| 读 transcript → 解析成 events | ✗ | `~/.claude/projects/.../{session}.jsonl` 最新一个 | `--json` stdout 即 JSONL |
|
|
106
|
-
| 归一化 events → diff → 验证 | ✅ 共享 | — | — |
|
|
107
|
-
|
|
108
|
-
"共享"的部分由 niceeval 提供(runner 固定段 + 下文 [shared](#shared沙箱型-adapter-的工具袋)),所以**每个沙箱 adapter 真正要写的,就是中间那 5 行差异 + 一个 transcript 解析器**。内置 `claude-code` 的真实实现(`src/agents/claude-code.ts`)骨架:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
import { defineSandboxAgent, shared } from "niceeval/adapter";
|
|
112
|
-
import { requireEnv } from "niceeval";
|
|
113
|
-
|
|
114
|
-
// 鉴权是 agent 本地配置(见契约 · 三类配置的归属);注意「没有 model」——留空,实验决定
|
|
115
|
-
const auth = () => ({ ANTHROPIC_API_KEY: requireEnv("ANTHROPIC_API_KEY") });
|
|
116
|
-
|
|
117
|
-
export default defineSandboxAgent({
|
|
118
|
-
name: "claude-code",
|
|
119
|
-
// 装 CLI 放 setup:每个沙箱一次,不随每轮 send 重装
|
|
120
|
-
async setup(sb) {
|
|
121
|
-
await sb.runShell("command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code");
|
|
122
|
-
},
|
|
123
|
-
// 运行器已备好沙箱(上传 / git 基线),经 ctx.sandbox 传入
|
|
124
|
-
async send(input, ctx) {
|
|
125
|
-
const sb = ctx.sandbox;
|
|
126
|
-
|
|
127
|
-
const args = ["--print", "--dangerously-skip-permissions"];
|
|
128
|
-
if (ctx.model) args.push("--model", ctx.model); // 实验给了才传;否则用 CLI 原生默认
|
|
129
|
-
if (ctx.flags.webResearch) args.push("--allowedTools", "WebSearch,WebFetch"); // 读实验 feature flag
|
|
130
|
-
if (ctx.session.id) args.push("--resume", ctx.session.id); // 新会话线 id 为 undefined,自然不传
|
|
131
|
-
|
|
132
|
-
const res = await sb.runCommand("claude", args, { env: auth() });
|
|
133
|
-
|
|
134
|
-
// 采集(脏):磁盘旁读最新 JSONL;转换(净):shared.parseClaudeCode 只吃 raw 字符串
|
|
135
|
-
const raw = await shared.captureLatestJsonl(sb, "~/.claude/projects");
|
|
136
|
-
ctx.session.capture(shared.sessionIdFromClaudeTranscript(raw)); // 回传供下轮续接(只在还没记过时落地)
|
|
137
|
-
const parsed = shared.parseClaudeCode(raw); // → { events, usage, … }
|
|
138
|
-
return { events: parsed.events, usage: parsed.usage, status: res.exitCode === 0 ? "completed" : "failed" };
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
接一个新 coding agent(比如 bub 之外的下一个)照抄此形状:换装法、换鉴权、换拼参、换 transcript 位置,再写一个解析器。
|
|
144
|
-
|
|
145
|
-
## 采集层:原始数据怎么从 agent CLI 弄到手
|
|
146
|
-
|
|
147
|
-
上面示例里 sandbox agent 靠 transcript JSONL 产事件流;但"怎么弄到这份原始数据"因 CLI 而异,是 `send` 里除了拼调用之外的另一半活。这里讲通用纪律;**每个 agent 具体走哪条路径、每个字段从原始数据的哪里抠,见 [采集矩阵](collection.md)**。三条实际路径,互不排斥,一个 agent 可能同时踩中好几条:
|
|
148
|
-
|
|
149
|
-
1. **磁盘旁读。** 多数 coding agent CLI 出于自己的会话续接(resume)需求,本来就会把完整交互写到磁盘——这不是它们为你设计的可观测性接口,只是"顺手"能读出来。claude-code 写 `~/.claude/projects/<project>/<session>.jsonl`,adapter 在 `send` 跑完后用 `shared.captureLatestJsonl(sandbox, dir)` 找最新一份读出来当 transcript。
|
|
150
|
-
2. **stdout/stderr 结构化捕获。** 给 CLI 传一个能让它打结构化日志的 flag(`--json` / `--format json`),把 `stdout + stderr` 拼起来当 transcript——本质上和"跑一条命令、把输出重定向到文件"没有区别。codex `codex exec --json` 走的是这条(配 `shared.extractJsonlFromStdout`)。
|
|
151
|
-
3. **OTLP 网络接收。** 前两条给的是"做了什么"(`StreamEvent[]`);trace 走独立通道——agent 经 OpenTelemetry 把 span **推**给运行器起的本机接收器,不是从文件/输出里"读"出来的(见 [Observability · OTLP traces](../observability.md#otlp-traces--统一瀑布图))。
|
|
152
|
-
|
|
153
|
-
**采集层允许脏,转换层必须干净。** 具体路径、CLI flag、文件在哪、版本升级导致的格式漂移——这些都是没办法的事,注定要写一堆 per-agent 的 hack 和 fallback。但只要采集层的产出**统一收窄成"一个原始字符串 + 一个 agent 标识"**再交给转换层,转换层就可以完全不碰沙箱、不碰 CLI 细节,只做纯数据变换、可独立单测——`o11y/parsers/<agent>.ts` 的解析函数只接收 `raw: string`,签名里没有 `sandbox` 或 `ctx`,就是这条边界的直接体现。这条边界不是 niceeval 独创的直觉,是从分析 Vercel agent-eval 的实现里得到印证的模式,细节见 [agent-eval 参考:采集 / 转换 / 落地三层](reference/agent-eval.md)。
|
|
154
|
-
|
|
155
|
-
**容错:一行解析失败,不拖垮整份 transcript。** JSONL 逐行 `JSON.parse`,每一行单独 try/catch;某一行失败只把 `parseSuccess` 标 `false`,不中断——继续解析剩下的行,已经解析出的事件照常保留(见 [Observability](../observability.md#transcript--标准事件流))。
|
|
156
|
-
|
|
157
|
-
**一个 agent 的采集可以是多条互不相干的通道。** codex 用 stdout 当主 transcript,却另从磁盘 session 文件里读"实际用的模型"——按"这份数据要用来干什么"分别决定怎么采,不要假设一种机制满足所有需求(教训来自 [agent-eval 参考笔记](reference/agent-eval.md#采集层两份-parser-之前原始数据从哪来))。
|
|
158
|
-
|
|
159
|
-
## shared:沙箱型 adapter 的工具袋
|
|
160
|
-
|
|
161
|
-
跨沙箱 adapter 复用、不属于任何单个 agent 的逻辑,由 niceeval 从 `niceeval/adapter` 导出(`src/agents/shared.ts`):
|
|
162
|
-
|
|
163
|
-
- **采集辅助** —— `captureLatestJsonl(sandbox, dir)`(磁盘旁读最新 JSONL)、`extractJsonlFromStdout(stdout)`(stdout 捕获过滤)、`writeFile` / `ensureInstalled`(setup 常用)。
|
|
164
|
-
- **会话 id 抽取** —— `sessionIdFromClaudeTranscript(raw)`、`codexThreadId(raw)`、`firstJsonField(raw, field)`(通用兜底)。
|
|
165
|
-
- **转换器** —— `parseClaudeCode(raw)` / `parseCodex(raw)` / `parseBub(raw)`:原始 JSONL → `{ events, usage, compactions, … }`,adapter 直接把结果铺进 `Turn` 返回。
|
|
166
|
-
|
|
167
|
-
git 基线与 diff 采集**不在 shared 里**——那是 runner 的固定段(沙箱创建时打一次基线、销毁前采一次 diff,见 `src/runner/sandbox-prep.ts`),对所有 agent 严格一致,adapter 不碰。中间"什么时候写入文件、什么时候调 `t.send()`、什么时候手工跑校验命令"全部由 eval 的 `test(t)` 自己决定,见 [Eval Authoring · 沙箱型](../eval-authoring.md#沙箱型手工把文件放进沙箱)。
|
|
168
|
-
|
|
169
|
-
## 没有注册表
|
|
170
|
-
|
|
171
|
-
没有按名字选 agent 的注册表——一个 config 文件对应一个(或一组固定的)agent,内置 coding-agent(`claude-code` / `codex` / `bub`)从 `niceeval/adapter` 导出,在 experiment 文件里直接引用。要换 agent 或 model,复制一个 experiment 文件改配置,不是靠 `--agent` 这类运行时选择器。
|
|
172
|
-
|
|
173
|
-
## 相关阅读
|
|
174
|
-
|
|
175
|
-
- [Adapter 契约](contract.md) —— 逐 API 的适配义务、事件流纪律、违约暴露方式。
|
|
176
|
-
- [Coding Agent Skills / Plugins DX](coding-agent-skills-plugins.md) —— 沙箱型 adapter 怎么装 skill / plugin 并组织 A/B 实验。
|
|
177
|
-
- [Observability](../observability.md) —— transcript 归一化、规范工具名、OTLP trace、usage / cost。
|
|
178
|
-
- [Sandbox](../sandbox.md) —— 沙箱接口与后端;adapter 只通过 `Sandbox` 接口和它交互。
|
|
179
|
-
- [agent-eval 参考](reference/agent-eval.md) —— 别人怎么做同一件事的源码阅读记录。
|