niceeval 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +15 -5
- 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 +36 -7
- package/src/context/types.ts +140 -0
- package/src/expect/index.ts +15 -8
- package/src/i18n/en.ts +19 -6
- package/src/i18n/zh-CN.ts +19 -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/reporters/artifacts.ts +11 -3
- package/src/runner/reporters/live.ts +45 -8
- package/src/runner/run.ts +71 -21
- package/src/runner/types.ts +45 -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/results-format.md
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
# Results Format —— 结果保存格式
|
|
2
|
-
|
|
3
|
-
这篇记录 `Artifacts()` 报告器写到本地磁盘的格式,也是 `niceeval view` 的离线输入契约。实现入口是 `src/runner/reporters/artifacts.ts`;核心类型在 `src/types.ts` 的 `RunSummary`、`EvalResult`、`StreamEvent`、`TraceSpan`、`O11ySummary` 和 `DiffData`。
|
|
4
|
-
|
|
5
|
-
## 目录结构
|
|
6
|
-
|
|
7
|
-
默认输出根目录是 `.niceeval/`。每次 run 一个时间戳目录,时间戳来自 `Date#toISOString()`,并把 `:` 与 `.` 替换成 `-`:
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
.niceeval/
|
|
11
|
-
2026-07-02T03-10-24-123Z/
|
|
12
|
-
summary.json
|
|
13
|
-
<evalId>/<agent>/<model>[/<experiment>]/a<attempt>/
|
|
14
|
-
events.json
|
|
15
|
-
sources.json
|
|
16
|
-
trace.json
|
|
17
|
-
o11y.json
|
|
18
|
-
diff.json
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`<evalId>/<agent>/<model>[/<experiment>]/a<attempt>/` 是单个 eval attempt 的工件目录。`evalId` 里的 `/` 会保留为目录层级,其它不适合路径的字符会替换成 `_`;`agent` 和 `model` 里的非 `[\w.@-]` 字符也会替换成 `_`。没有 model 时目录名是 `default`。带 experimentId 的结果多一段实验目录(`/` 压成 `_`,如 `compare-prompts_concise`)——两个实验可以同 agent 同 model、只差 flags,少了这一段工件会互相覆盖。
|
|
22
|
-
|
|
23
|
-
这些文件是按需写入的:某类数据为空就不生成对应 JSON 文件。`summary.json` 在 run 结束时写入;attempt 级重数据在每个 eval 完成时增量写入,所以长 run 中途失败时通常仍能留下已经完成的 attempt 工件。
|
|
24
|
-
|
|
25
|
-
## 版本与升级设计
|
|
26
|
-
|
|
27
|
-
`summary.json` 顶层带最小的版本元数据(writer 在 `src/runner/reporters/artifacts.ts`,常量在 `src/types.ts` 的 `RESULTS_FORMAT` / `RESULTS_SCHEMA_VERSION`):
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"format": "niceeval.results",
|
|
32
|
-
"schemaVersion": 1,
|
|
33
|
-
"producer": {
|
|
34
|
-
"name": "niceeval",
|
|
35
|
-
"version": "0.12.0"
|
|
36
|
-
},
|
|
37
|
-
"startedAt": "2026-07-02T03:10:24.123Z",
|
|
38
|
-
"results": []
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
设计原则是**不做兼容机制**。没有迁移函数,没有多版本 normalize loader,没有 per-artifact 版本号:整个 run(summary + 全部 attempt 工件)共用顶层这一个 `schemaVersion`。读取器只认与自己相同的版本;版本不同就是不兼容,唯一的处理是提示用写这份报告的 niceeval 版本查看:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npx niceeval@0.3.0 view .niceeval/2026-09-10T08-00-00-000Z
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
字段规则:
|
|
49
|
-
|
|
50
|
-
- `format` 必须等于 `"niceeval.results"`。它既避免把其它工具的 `summary.json` 误读成 niceeval,也是版本不匹配时识别「这是一份 niceeval 报告」的依据。
|
|
51
|
-
- `schemaVersion` 用整数,只在**破坏兼容读取**时递增。新增可选字段、新增 artifact 文件、新增 `StreamEvent` variant 不递增;读取器必须忽略未知字段和未知 artifact 文件。
|
|
52
|
-
- `producer.version` 是写这份报告的 npm package 版本,唯一用途是拼 npx 提示;它不是 schema 判断依据。
|
|
53
|
-
- `format` / `schemaVersion` / `producer` 三个字段永久稳定:任何未来版本都不能移动、重命名或改变类型,否则版本不匹配时连 npx 提示都给不出来。
|
|
54
|
-
- 缺版本字段的存量文件等价于 `schemaVersion: 1`(引入版本号不改变其余格式)。这批文件没有 `producer.version`,将来不兼容时提示只能是模糊的「用 0.1.x 旧版查看」——所以 writer 越早开始写版本元数据越好。
|
|
55
|
-
- attempt 文件保持裸 JSON array/object。`events.json` 继续是 `StreamEvent[]`,不为塞版本号改成 `{ schemaVersion, data }` envelope;`jq`/`node` 直接读数组的体验不被打破。
|
|
56
|
-
- 不要用目录名表达 schema。`.niceeval/<timestamp>/` 和 attempt 目录只表达身份与定位;版本全部在 `summary.json` 里,复制、重命名、归档目录不影响解析。
|
|
57
|
-
|
|
58
|
-
### 版本不匹配时的读取行为
|
|
59
|
-
|
|
60
|
-
读取器不解析、不迁移、不降级渲染任何版本不同的 run,行为只分三档:
|
|
61
|
-
|
|
62
|
-
- **`schemaVersion` 相同**(或缺失,按 1 处理):正常读取渲染。
|
|
63
|
-
- **`format === "niceeval.results"` 但 `schemaVersion` 不同**(不论新旧):整个 run 视为不兼容。目录扫描时在列表里留一个占位条目,标出 run 目录和 `producer.version`,并提示:
|
|
64
|
-
|
|
65
|
-
```text
|
|
66
|
-
⚠ .niceeval/2026-09-10T08-00-00-000Z: written by niceeval 0.3.0 (schemaVersion 2);
|
|
67
|
-
this CLI reads schemaVersion 1.
|
|
68
|
-
Run `npx niceeval@0.3.0 view .niceeval/2026-09-10T08-00-00-000Z` to view it.
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
单文件模式 `niceeval view <run>/summary.json` 输出同样的提示后退出,而不是报「不是 niceeval summary」。
|
|
72
|
-
- **不能识别**(没有 `format`,也不满足 legacy 的 `results[]` + `startedAt` 启发式):当作无关 JSON 忽略。
|
|
73
|
-
|
|
74
|
-
实现入口:版本判定在 `src/view/index.ts` 的 `readSummary`(抛 `IncompatibleResultsError`);目录扫描的占位数据经 `viewData.incompatibleRuns` 进前端,由 `src/view/app/App.tsx` 的 incompatible-banner 渲染;单文件模式的提示与退出在 `src/cli.ts` 的 `exitOnIncompatibleResults`;提示文案是 i18n key `cli.view.incompatible`。
|
|
75
|
-
|
|
76
|
-
报告里最小应新增的字段是:
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
interface ResultFormatMeta {
|
|
80
|
-
format: "niceeval.results";
|
|
81
|
-
schemaVersion: number;
|
|
82
|
-
producer?: {
|
|
83
|
-
name: "niceeval";
|
|
84
|
-
version?: string;
|
|
85
|
-
commit?: string;
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
这组字段应该放进 `RunSummary`,但 eval 作者的运行时 API 不需要看见它们;它们属于 reporter / view 的持久化契约。
|
|
91
|
-
|
|
92
|
-
## `summary.json`
|
|
93
|
-
|
|
94
|
-
`summary.json` 是瘦身后的 `RunSummary`,负责让控制台、`--resume` 和 `niceeval view` 先拿到榜单级信息。前三个字段(`format` / `schemaVersion` / `producer`)是上文的版本元数据:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
interface RunSummary {
|
|
98
|
-
format?: "niceeval.results";
|
|
99
|
-
schemaVersion?: number;
|
|
100
|
-
producer?: { name: "niceeval"; version?: string; commit?: string };
|
|
101
|
-
name?: LocalizedText;
|
|
102
|
-
agent: string;
|
|
103
|
-
model?: string;
|
|
104
|
-
startedAt: string;
|
|
105
|
-
completedAt: string;
|
|
106
|
-
passed: number;
|
|
107
|
-
failed: number;
|
|
108
|
-
skipped: number;
|
|
109
|
-
errored: number;
|
|
110
|
-
durationMs: number;
|
|
111
|
-
usage?: Usage;
|
|
112
|
-
estimatedCostUSD?: number;
|
|
113
|
-
results: EvalResult[];
|
|
114
|
-
outputDir?: string;
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
`results[]` 里的每条 `EvalResult` 仍包含判决、断言、用量、成本、错误、fingerprint 和 experiment 元数据,但不会内联大字段:
|
|
119
|
-
|
|
120
|
-
- `events`
|
|
121
|
-
- `sources`
|
|
122
|
-
- `trace`
|
|
123
|
-
- `o11y`
|
|
124
|
-
- `diff`
|
|
125
|
-
- `rawTranscript`
|
|
126
|
-
|
|
127
|
-
这些字段被替换成 attempt 工件引用:
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
{
|
|
131
|
-
"artifactsDir": "weather/brooklyn/codex/gpt-5/a1",
|
|
132
|
-
"hasEvents": true,
|
|
133
|
-
"hasTrace": true,
|
|
134
|
-
"hasSources": true
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
`artifactsDir` 是相对当前 run 目录的路径。`niceeval view` 读取 `summary.json` 后,会把它补成:
|
|
139
|
-
|
|
140
|
-
- `artifactBase`: 相对 view 输入根目录的路径,供前端请求 `/artifact?p=...`;
|
|
141
|
-
- `artifactAbsBase`: 本机绝对路径,供 UI 复制或展示。
|
|
142
|
-
|
|
143
|
-
注意:当前 summary 只有 `hasEvents`、`hasTrace`、`hasSources` 三个存在标记。`o11y.json` 和 `diff.json` 会写盘,但 summary 里还没有对应 `hasO11y` / `hasDiff` 标记;读取方需要按路径尝试读取或先检查文件存在。
|
|
144
|
-
|
|
145
|
-
## Attempt 级文件
|
|
146
|
-
|
|
147
|
-
### `events.json`
|
|
148
|
-
|
|
149
|
-
类型是 `StreamEvent[]`。这是从 agent 原始 transcript 归一化后的标准事件流,也是作用域断言、transcript 展示、工具调用统计的主要来源。
|
|
150
|
-
|
|
151
|
-
常见事件包括:
|
|
152
|
-
|
|
153
|
-
- `message`: assistant / user 文本;
|
|
154
|
-
- `action.called` / `action.result`: 工具调用与结果;
|
|
155
|
-
- `subagent.called` / `subagent.completed`: 子 agent 调用;
|
|
156
|
-
- `input.requested`: HITL 输入请求;
|
|
157
|
-
- `thinking`: 思考块;
|
|
158
|
-
- `compaction`: 上下文压缩;
|
|
159
|
-
- `error`: 运行时或采集错误。
|
|
160
|
-
|
|
161
|
-
文件内容是一个 JSON array,不是 JSONL / NDJSON。
|
|
162
|
-
|
|
163
|
-
### `sources.json`
|
|
164
|
-
|
|
165
|
-
类型是 `SourceArtifact[]`:
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
interface SourceArtifact {
|
|
169
|
-
path: string;
|
|
170
|
-
content: string;
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
它只包含本次 test/断言通过 `loc` 引用到的 eval 源码片段。`niceeval view` 用它把 `t.send`、断言和运行结果叠回源码行。
|
|
175
|
-
|
|
176
|
-
### `trace.json`
|
|
177
|
-
|
|
178
|
-
类型是 `TraceSpan[]`。只有 agent 声明 tracing 能力、运行器收到 OTLP span 并成功归一化时才会生成。它回答「各步骤耗时多久、父子关系是什么」,与回答「做了什么」的 `events.json` 分开。
|
|
179
|
-
|
|
180
|
-
`TraceSpan.kind` 是 view 识别的核心字段,来自 canonical GenAI 语义角色:
|
|
181
|
-
|
|
182
|
-
- `turn`
|
|
183
|
-
- `model`
|
|
184
|
-
- `tool`
|
|
185
|
-
- `agent`
|
|
186
|
-
- `other`
|
|
187
|
-
|
|
188
|
-
原生 span 名和属性仍保留在 `name` / `attributes` 里,但 view 的分组与着色只应依赖 canonical 字段。
|
|
189
|
-
|
|
190
|
-
### `o11y.json`
|
|
191
|
-
|
|
192
|
-
类型是 `O11ySummary`。这是从标准事件流派生的行为摘要,包括工具调用计数、读写文件、shell 命令、web fetch、错误、思考块、压缩次数、耗时、usage 和估算成本。
|
|
193
|
-
|
|
194
|
-
这个文件面向人和调试脚本:当一个 attempt 失败时,先看 `summary.json` 的 `outcome` / `error`,再看 `events.json` 与 `o11y.json`,通常能分清是断言没过、agent runtime 错误,还是 adapter / provider / timeout 问题。
|
|
195
|
-
|
|
196
|
-
### `diff.json`
|
|
197
|
-
|
|
198
|
-
类型是 `DiffData`:
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
interface DiffData {
|
|
202
|
-
generatedFiles: Record<string, string>;
|
|
203
|
-
deletedFiles: string[];
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
它只存在于有沙箱 workspace diff 的运行。coding-agent eval 常用它验证文件修改结果;remote / in-process agent 不一定有 diff。
|
|
208
|
-
|
|
209
|
-
## 读取规则
|
|
210
|
-
|
|
211
|
-
读取结果时优先从 `summary.json` 开始:
|
|
212
|
-
|
|
213
|
-
1. 读 `.niceeval/<run>/summary.json`,先用 `results[]` 判断 pass / fail / error / skip、耗时、成本和断言失败。
|
|
214
|
-
2. 对需要下钻的 result,用 `artifactsDir` 拼出 attempt 目录。
|
|
215
|
-
3. 按 `hasEvents` / `hasTrace` / `hasSources` 拉取 `events.json`、`trace.json`、`sources.json`。
|
|
216
|
-
4. 需要行为摘要或 workspace diff 时,尝试读取同目录的 `o11y.json` / `diff.json`。
|
|
217
|
-
|
|
218
|
-
`niceeval view` 的本地 server 只暴露 `.json` 工件,并把请求路径限制在 view 输入根目录内。`--out` 导出时 summary 聚合数据烘焙进 `index.html`,查看器要 fetch 的工件复制到 `artifact/` 下同布局路径。
|
|
219
|
-
|
|
220
|
-
## 与其它 reporter 的边界
|
|
221
|
-
|
|
222
|
-
这篇只描述默认 `Artifacts()` reporter 的本地目录格式。`Json(path)` reporter 写的是机器可读全量 JSON,用途不同;第三方实验平台 reporter 可以把同一批 `EvalResult` / `RunSummary` 转成自己的格式。
|
|
223
|
-
|
|
224
|
-
因此,不要在文档或工具里假设本地结果有 `results.jsonl`、transcript NDJSON 或固定测试输出文件。当前稳定契约是:
|
|
225
|
-
|
|
226
|
-
- run 级: `summary.json`;
|
|
227
|
-
- attempt 级: `events.json`、`sources.json`、`trace.json`、`o11y.json`、`diff.json`;
|
|
228
|
-
- 每个文件都是 JSON,不是 JSONL。
|
package/docs/results-lib.md
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# Results Lib —— 实验结果数据的读写库(设计提案,未实现)
|
|
2
|
-
|
|
3
|
-
> 状态:读取面已首版落地——`src/results/` 的 openResults / 快照 / latestPerExperiment / 身份键去重 / copyRun(源码入口见 [Source Map](source-map.md#results-lib-与-reports));写入面 `createRunWriter` 与 view / `Artifacts()` reporter 的收编仍是提案。[Results Format](results-format.md) 是磁盘上的格式规范(已实现);本文提议把这份格式的**读与写**抽成一个专门的库 `niceeval/results`,做 runner、view、[Reports](reports.md) 和用户脚本的共同数据层。
|
|
4
|
-
|
|
5
|
-
同一份磁盘格式,今天的写和读长在两个器官里。写在 `src/runner/reporters/artifacts.ts`:时间戳目录、attempt 路径清洗(evalId 保留 `/` 层级、agent/model 非 `[\w.@-]` 全换 `_`)、大字段拆工件、`artifactsDir` / `has*` 回填、空数据不落文件——全是它的私有知识。读长在 `src/view/index.ts`:`readSummary` 的版本判定、`loadSummaries` 的目录扫描、工件路径反拼。两边靠 `src/types.ts` 共享类型,但**布局知识各自实现了一遍**:格式演进要同步改两处,谁漏改谁坏。而用户侧根本没有读取 API,想编程消费只能第三次重写这些知识:
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// 今天:想比「谁的代码短」,只能手工爬目录
|
|
9
|
-
const summary = JSON.parse(readFileSync(".niceeval/2026-07-02T.../summary.json", "utf-8"));
|
|
10
|
-
for (const r of summary.results) {
|
|
11
|
-
const diffPath = join(runDir, r.artifactsDir ?? "", "diff.json"); // 布局知识泄漏
|
|
12
|
-
if (!existsSync(diffPath)) continue; // 存在性自己判断
|
|
13
|
-
const diff = JSON.parse(readFileSync(diffPath, "utf-8")); // 类型自己想
|
|
14
|
-
}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
抽成一个专门的库,理由就是 TypeScript 最擅长的那件事:**写和读是同一组 interface 的两半,住在同一个包里,writer 的参数类型 = reader 的返回类型**——「写出去的就是读得回的」由编译器背书,布局知识(路径、清洗、拆分、版本)全宇宙只有一份实现。
|
|
18
|
-
|
|
19
|
-
## 库的边界
|
|
20
|
-
|
|
21
|
-
`niceeval/results` 拥有:
|
|
22
|
-
|
|
23
|
-
- **类型的家。** `RunSummary` / `EvalResult` / `StreamEvent` / `TraceSpan` / `O11ySummary` / `DiffData` 等结果类型和 `RESULTS_FORMAT` / `RESULTS_SCHEMA_VERSION` 常量搬进库;core 的 `src/types.ts` facade 反向 re-export,模块代码 `import type { … } from "../types.ts"` 的老习惯不破。
|
|
24
|
-
- **writer。** 目录创建、attempt 工件增量落盘、summary 收尾(见下)。
|
|
25
|
-
- **reader。** `openResults`:目录扫描、版本过滤、懒加载、快照切分、选择器(见下)。
|
|
26
|
-
- **身份。** attempt 身份键与去重规则——读写两侧对「同一个 attempt」的理解必须一致,所以住在这。
|
|
27
|
-
|
|
28
|
-
它不拥有:「看法」(聚合、指标、组件在 [Reports](reports.md))、渲染(view)、执行(runner)。库位于依赖图最底层,不 import core 的任何其它模块;必要时可以原样提成独立 npm 包(给「只想解析或产出 niceeval 结果的工具」用),但先作为子路径导出,不预设。
|
|
29
|
-
|
|
30
|
-
四个消费方:
|
|
31
|
-
|
|
32
|
-
| 消费方 | 用哪面 | 变化 |
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| runner 的 `Artifacts()` reporter | 写 | 变薄壳:订阅 reporter 事件,转手调 writer,落盘行为不变 |
|
|
35
|
-
| `niceeval view` | 读 | `readSummary` / `loadSummaries` 改吃 reader,版本判定与 skipped 姿势顺带统一 |
|
|
36
|
-
| [Reports](reports.md) 的计算函数 | 读 | 第二档的全部数据入口 |
|
|
37
|
-
| 你的脚本 / 第三方工具 | 读、写、发布 | 读:自定义分析;写:把别家平台的结果转成 niceeval 格式,`niceeval view` 直接能看;发布:`copyRun` 瘦身快照进仓库 / CDN——兼容性都由库保证,不用抄格式文档 |
|
|
38
|
-
|
|
39
|
-
## 写:`createRunWriter`
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
import { createRunWriter } from "niceeval/results";
|
|
43
|
-
|
|
44
|
-
const writer = await createRunWriter(".niceeval", {
|
|
45
|
-
producer: { name: "niceeval", version: "0.12.0" },
|
|
46
|
-
});
|
|
47
|
-
writer.dir; // .niceeval/2026-07-07T…Z/(时间戳目录,: 与 . 已替换)
|
|
48
|
-
|
|
49
|
-
await writer.writeAttempt(result); // 吃完整 EvalResult(events / diff 等大字段内联):
|
|
50
|
-
// 拆成 attempt 工件文件、算 artifactsDir(含路径清洗)、
|
|
51
|
-
// 回填 has* 引用,返回瘦身后的条目;空数据不落文件
|
|
52
|
-
|
|
53
|
-
await writer.finish(summary); // 写 summary.json,注入 format / schemaVersion / producer
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
attempt 工件按完成增量写入(与今天的行为一致):长 run 中途失败,已完成的 attempt 工件仍留在盘上。`writeAttempt` 做的「大字段拆出去、引用填回来」正是 reader 懒加载的逆操作——两个方向的同一份知识,终于写在同一个文件里。
|
|
57
|
-
|
|
58
|
-
## 复制与瘦身:`copyRun`
|
|
59
|
-
|
|
60
|
-
发布场景的第三个原语:把选中的快照(或整个 run)按格式感知地复制到另一个目录——只带指定工件、只带选中的 attempt,布局知识不外泄:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { copyRun } from "niceeval/results";
|
|
64
|
-
|
|
65
|
-
await copyRun(snapshots, "site/data/run", {
|
|
66
|
-
artifacts: ["sources", "events", "trace"], // diff 可达百 MB、o11y 查看器不读,发布时常见地不带
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
动机来自真实消费者:coding-agent-memory-evals 把最新 run 快照进仓库供静态托管,今天是 40 行手写脚本——按 `summary.json` 的 mtime 挑「最新」(口径还错了:该挑快照,不该挑 run),再按白名单拷贝工件文件(布局知识第三次泄漏)。`copyRun` 之后这段只剩上面三行,挑选交给 `latestPerExperiment`(见[静态导出场景](reports.md#dx-模拟))。复制忠实于源:不改内容、不消毒——发布消毒是自由文本的事,归 [Reports 的 `cases({ redact })`](reports.md#计算函数与数据契约)。
|
|
71
|
-
|
|
72
|
-
## 读:`openResults`
|
|
73
|
-
|
|
74
|
-
输入是 `.niceeval/` 目录(或单个 run 目录),输出是类型化句柄:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import { openResults } from "niceeval/results";
|
|
78
|
-
|
|
79
|
-
const results = await openResults(".niceeval");
|
|
80
|
-
|
|
81
|
-
results.runs; // RunHandle[]:一个落盘 run 目录一项,忠实反映磁盘,不合并
|
|
82
|
-
results.snapshots; // SnapshotHandle[]:experiment × run 的切片,选择与聚合的天然单元
|
|
83
|
-
results.skipped; // 读不了的 run:{ dir, reason, producerVersion? }[],不静默丢
|
|
84
|
-
|
|
85
|
-
const run = results.runs[0];
|
|
86
|
-
run.dir; // 绝对路径
|
|
87
|
-
run.summary; // RunSummary(与写入侧同一类型)
|
|
88
|
-
run.attempts; // AttemptHandle[]:summary.results[] 逐条包一层
|
|
89
|
-
|
|
90
|
-
const attempt = run.attempts[0];
|
|
91
|
-
attempt.result; // EvalResult 瘦身条目:判决、断言、用量、成本、experiment 元数据
|
|
92
|
-
attempt.ref; // { run, result }:证据引用(run 目录名 + results 下标),
|
|
93
|
-
// Reports 的 MetricCell.refs 与 view 深链 #/attempt/... 同一身份
|
|
94
|
-
await attempt.events(); // StreamEvent[] | null —— 重工件全部懒加载
|
|
95
|
-
await attempt.trace(); // TraceSpan[] | null
|
|
96
|
-
await attempt.o11y(); // O11ySummary | null
|
|
97
|
-
await attempt.diff(); // DiffData | null(可达百 MB,所以必须懒)
|
|
98
|
-
await attempt.sources(); // SourceArtifact[] | null
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
要点:
|
|
102
|
-
|
|
103
|
-
- **懒加载即存在性判断。** 工件缺失返回 `null`,不抛错。今天 summary 里只有 `hasEvents` / `hasTrace` / `hasSources`,连 `hasO11y` / `hasDiff` 标记都没有——这类不对称全被方法语义吸收,消费方不再碰路径。
|
|
104
|
-
- **版本过滤沿用格式规范。** 按 [Results Format 的版本规则](results-format.md#版本与升级设计)判定,不兼容的 run 进 `skipped` 并带 `producerVersion`(拼 `npx niceeval@<version> view` 提示的素材),与 [View 的报错与降级](view.md#报错与降级)同一姿势。
|
|
105
|
-
- **同一进程内按 handle 记忆化。** 两个都要读 diff 的消费方不会把「可达百 MB」的 `diff.json` 读两遍;扫全部历史仍然可能慢,但要慢得线性、可预期。
|
|
106
|
-
- **只读不写事实。** reader 的一切派生物删了随时可重算;唯一事实来源仍是磁盘上的 Results Format。
|
|
107
|
-
|
|
108
|
-
## 快照:experiment × run,不是 run
|
|
109
|
-
|
|
110
|
-
一次 CLI 调用写一个 run 目录,但一个 run 目录里可以装多个 experiment:`niceeval exp compare` 把整组对照跑进同一份 `summary.json`(runner 收的是 `agentRuns[]` 复数),顶层 `RunSummary.agent` 只是第一个配置的 agent(`src/runner/run.ts` 的 `summarize(allResults, firstAgent?.name …)`)。所以「每个 experiment 最新一次」没法用 run 粒度表达——周一跑了整组 compare,周二只重跑 `compare/bub-gpt-5.4`,bub 的最新快照在周二的 run 里,codex 的还在周一的 run 里。
|
|
111
|
-
|
|
112
|
-
reader 把这层身份显式化:**快照 = 一个 experiment 在一个 run 里的那部分 attempt**,与 [View · Compare 计划](view.md#compare-挑两次运行对比)的 `(experimentId, startedAt)` 同一口径:
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
interface SnapshotHandle {
|
|
116
|
-
experimentId: string; // 结果里缺 experimentId 时以 "<agent>/<model>" 合成键,并记入 warnings
|
|
117
|
-
run: RunHandle; // 所属物理 run
|
|
118
|
-
startedAt: string;
|
|
119
|
-
agent: string; // 本快照自己的 agent —— 不是 run 顶层那个「第一个配置」
|
|
120
|
-
model?: string;
|
|
121
|
-
attempts: AttemptHandle[];
|
|
122
|
-
evalIds: string[]; // 覆盖的 eval 集合,供选择器做残缺检测(见下)
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`results.runs` 忠实磁盘,`results.snapshots` 只切片、不合并、不去重;合并与聚合永远发生在消费方([Reports](reports.md) 的计算函数,或你自己的脚本),reader 不预设看法——这条教训来自 view 的 `aggregateRows` 把全部历史揉成一行的现状(见 [View · 已知差异](view.md#已知的文档-vs-实现差异))。
|
|
127
|
-
|
|
128
|
-
## 选择快照:`latestPerExperiment`
|
|
129
|
-
|
|
130
|
-
多数消费场景先回答「现在什么水平」,所以第一批只有一个选择器:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
function latestPerExperiment(
|
|
134
|
-
snapshots: SnapshotHandle[],
|
|
135
|
-
opts?: { experiments?: string | string[] }, // experiment id 前缀过滤,同 CLI 语义
|
|
136
|
-
): { snapshots: SnapshotHandle[]; warnings: string[] };
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
每个 experiment 取最新一次快照,最不误导。要累计历史就不调它;要更细的口径,普通 `.filter` 就够——选择器不是 DSL,只是最常用的那次筛选。
|
|
140
|
-
|
|
141
|
-
但「最新」可能残缺:位置参数允许只重跑一道题(`niceeval exp midterm algebra/quadratic` 是正常的 debug 姿势),它产出的「最新快照」只有一道题,安静吞下的话下游报表就变成按一道题打分。所以**选择器同样要诚实**:把每个选中快照的 `evalIds` 与该 experiment 历史快照的并集对比,缩水就写进 `warnings`:
|
|
142
|
-
|
|
143
|
-
```text
|
|
144
|
-
warning: snapshot "midterm/bub-gpt-5.4" @ 2026-07-05T… covers 1 of 50 evals seen in history.
|
|
145
|
-
Re-run `niceeval exp midterm/bub-gpt-5.4` for a full snapshot, or pick another via .filter().
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
`warnings` 是普通字符串数组,渲染与否在消费方,但缺口永远被算出来,不静默。
|
|
149
|
-
|
|
150
|
-
## 身份键与去重
|
|
151
|
-
|
|
152
|
-
`--resume` 会把上一轮已通过的结果**原样合入**新 run 的 summary(`RunOptions.priorResults`):这让续跑出来的最新快照天然完整(正好配合 `latestPerExperiment`),代价是同一个 attempt 存在于多份落盘,而 `EvalResult` 今天没有任何「合入」标记。
|
|
153
|
-
|
|
154
|
-
reader 忠实反映这份重复,不擅自去重;**跨快照聚合前按身份键去重是消费方的义务**:
|
|
155
|
-
|
|
156
|
-
- 身份键:`(experimentId, evalId, attempt, startedAt)`,重复时保留最新 run 里的那份;
|
|
157
|
-
- `startedAt` 缺失时宁可不去重也不误删,并记入 warnings;
|
|
158
|
-
- [Reports 的计算函数](reports.md#计算函数与数据契约)内置这条;自己写脚本跨快照累计时,要么复用计算函数,要么自己按键去重。
|
|
159
|
-
|
|
160
|
-
更根治的做法是 writer 给合入的结果打标——读写同库之后,这类格式演进只改一处实现,再同步 [Results Format](results-format.md) 规范即可;是否值得为它递增 `schemaVersion`,留给格式规范那边定。
|
|
161
|
-
|
|
162
|
-
## 直接吃读取面:一个真实脚本
|
|
163
|
-
|
|
164
|
-
折叠类的看法(表格、矩阵、成绩单、散点)去用 [Reports](reports.md) 的计算函数;直接吃 reader 服务的是连算法都自定义的场景,比如「每个 agent 的 shell 命令分布直方图」——那是分布,不是折叠:
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
import { openResults } from "niceeval/results";
|
|
168
|
-
|
|
169
|
-
const results = await openResults(".niceeval");
|
|
170
|
-
const points = [];
|
|
171
|
-
for (const snap of results.snapshots) {
|
|
172
|
-
for (const attempt of snap.attempts) {
|
|
173
|
-
const o11y = await attempt.o11y();
|
|
174
|
-
points.push({
|
|
175
|
-
agent: snap.agent,
|
|
176
|
-
eval: attempt.result.id,
|
|
177
|
-
passed: attempt.result.outcome === "passed",
|
|
178
|
-
shellCommands: o11y?.shellCommands.length ?? 0,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
即使在这条最深的路径上,用户也**不碰磁盘布局**——`artifactsDir` 拼接、存在性判断、版本过滤、快照切分都被库消化了。Results Format 若演进,全宇宙只有这一个库要改。
|
|
185
|
-
|
|
186
|
-
## 相关阅读
|
|
187
|
-
|
|
188
|
-
- [Results Format](results-format.md) —— 磁盘格式规范;本库是它唯一的官方读写实现,一格式一库,成对演进。
|
|
189
|
-
- [Reports](reports.md) —— 建立在本库读取面之上的积木:指标、计算函数、React 组件。
|
|
190
|
-
- [View](view.md) —— 内置前端;`readSummary` / `loadSummaries` / skipped 处理是本库 reader 要收编的现状。
|
|
191
|
-
- [Experiments](experiments.md) —— experimentId 与可对比组从哪来。
|
package/docs/runner.md
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# Runner —— 执行引擎
|
|
2
|
-
|
|
3
|
-
运行器是把"一批 eval"变成"一份结果"的调度引擎。它拥有对所有被测对象都一样的部分:发现、有界并发、早停、缓存、报告编排。被测对象的差异它一概不管 —— 它只对着 `Agent` 接口(统一动词 `send`)驱动。
|
|
4
|
-
|
|
5
|
-
这是 niceeval "跑得快"承诺的落点,见 [Vision](vision.md#跑得快)。
|
|
6
|
-
|
|
7
|
-
## 职责边界
|
|
8
|
-
|
|
9
|
-
运行器**做**:发现 eval、算指纹决定跳过、建 attempt 列表、有界并发调度、早停、把结果交给报告器、落盘工件、定退出码。
|
|
10
|
-
|
|
11
|
-
运行器**不做**:不知道怎么驱动 agent(那是 Agent/Adapter)、不知道怎么打分(那是 Scorer)、不知道结果存哪种格式细节(那是 Reporter)。它是协调者,不是执行者。
|
|
12
|
-
|
|
13
|
-
## 发现
|
|
14
|
-
|
|
15
|
-
`runner/discover.ts` 扫 `evals/`:
|
|
16
|
-
|
|
17
|
-
- 找所有 `*.eval.ts`,`import` 后看默认导出 —— 单个 eval 用文件 id;数组则扇出,id 加零填充索引(`sql/0000`)。没有另一种基于目录约定的隐式发现——沙箱型 eval 也必须有一个 `.eval.ts` 文件。
|
|
18
|
-
- 按相对路径排序,保证 id 稳定、输出可比。
|
|
19
|
-
- 应用过滤:`niceeval exp <组|配置>` 后的位置参数(id 前缀,如 `weather` 命中 `weather/*`)、`--tag`。
|
|
20
|
-
- `niceeval exp` 时另从 `experiments/` 扫实验文件(默认导出 `defineExperiment` 的 `.ts`),据路径推导实验 id;**目录段即"可对比组"** —— `niceeval exp <组>` 跑整个文件夹、同组互为对照(见 [实验怎么组织](experiments.md#实验怎么组织文件夹--一组可对比的实验))。实验的 `evals` 字段再筛要跑哪些 eval(见[矩阵展开](#矩阵展开与通过率))。
|
|
21
|
-
|
|
22
|
-
## 调度:有界并发
|
|
23
|
-
|
|
24
|
-
核心调度用 `Effect.forEach({ concurrency: maxConcurrency })` 实现:每个 attempt 跑在自己的 fiber,至多 `maxConcurrency` 个并发。报告回调走 **permit=1 的信号量串行化**,不阻塞执行 fiber。结果最后按**发现顺序**排序(而非完成顺序),让输出稳定可 diff。
|
|
25
|
-
|
|
26
|
-
并发上限来源:`--max-concurrency` → 配置 `maxConcurrency` → 默认。沙箱型受沙箱后端容量约束(本地 Docker 别开太高;云后端可大)。
|
|
27
|
-
|
|
28
|
-
## 矩阵展开与通过率
|
|
29
|
-
|
|
30
|
-
一次 `exp` 运行把一批配置展成 attempt:通常来自**一组文件夹里的多个单一配置**(`compare/bub-gpt-5.4` + `compare/codex-gpt-5.4`,见 [实验怎么组织](experiments.md#实验怎么组织文件夹--一组可对比的实验));再 × `eval × runs`。比如 2 个实验配置 × `runs: 5` × 3 个 eval = 30 个 attempt。汇总按 `(agent, model, eval)` 分组,不再是单一判决,而是**通过率** + 平均耗时 / token / 成本:
|
|
31
|
-
|
|
32
|
-
```text
|
|
33
|
-
fixtures/button claude-code pass@5 = 4/5 (80%) mean 34s · 58k tok · $0.44
|
|
34
|
-
fixtures/button codex pass@5 = 3/5 (60%) mean 41s · 72k tok · $0.39
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
用于衡量 agent 的稳定性(一次过 ≠ 可靠),以及跨 agent 的**质量 × 成本**对比。不写实验时退化成单 agent × `runs`。
|
|
38
|
-
|
|
39
|
-
## 早停(earlyExit)
|
|
40
|
-
|
|
41
|
-
取通过率本可以跑满 N 次,但若只关心"能不能做到",先过一次即可停其余:
|
|
42
|
-
|
|
43
|
-
- 每个 eval 配一个 `AbortController`。
|
|
44
|
-
- 某 attempt 通过且 `earlyExit` 开 → `abort()` 同 eval 其余 attempt;被 abort 的不计入分母。
|
|
45
|
-
- 某 attempt `errored`(框架/环境层面的意外,不是断言没过)且 `earlyExit` 开 → 同样 `abort()` 其余 attempt。errored 通常会确定性重复,跑满 `runs` 只是重复烧同一个错误;只有 `failed` 才是 agent 行为的样本,值得跑满 `runs` 测通过率。
|
|
46
|
-
- 默认开;`--no-early-exit` 关(想要完整通过率分布时)。
|
|
47
|
-
|
|
48
|
-
## 预算护栏(budget)
|
|
49
|
-
|
|
50
|
-
实验可设 `budget`(整轮估算成本上限 $),`--budget` 覆盖。运行器在派发每个 attempt 前检查已花成本(用量 × 价格表,见 [Observability](observability.md#用量与成本token--计费)),并给**在飞的 attempt 做预扣**:还没有任何完成样本时,同一 budget 域只放一个 attempt 在飞(先探出单次成本);有样本后按平均实测成本给每个在飞 attempt 预扣,预计总额到顶就等在飞的结算。已花到顶则**停止派发新 attempt**(在飞的跑完),整轮提前收尾并发 `run:budgetExceeded`。没有预扣的话,`maxConcurrency` 个 attempt 会在任何成本回写前全部起飞,实际花费能冲到 budget 的好几倍。借鉴 crabbox 的 spend cap,避免一次跑爆账单。
|
|
51
|
-
|
|
52
|
-
## 缓存:指纹去重
|
|
53
|
-
|
|
54
|
-
`runner/fingerprint.ts` 对每个 eval 算 `(eval 代码 + 相关配置)` 的哈希:
|
|
55
|
-
|
|
56
|
-
- 上次已 `passed` 且指纹未变 → 默认**跳过**,直接复用结果。
|
|
57
|
-
- 改了 fixture、改了配置、或 `--force` → 重跑。
|
|
58
|
-
- 失败的结果不缓存(总会重试失败项)。
|
|
59
|
-
|
|
60
|
-
让"改一个 case 重跑"只花那一个 case 的时间,而不是全量。
|
|
61
|
-
|
|
62
|
-
## 超时:双层保护
|
|
63
|
-
|
|
64
|
-
- **Adapter 内层超时** —— agent CLI 自己的超时。
|
|
65
|
-
- **运行器外层超时** —— `Promise.race` 一个 `AbortSignal.timeout`,即使 agent 卡死也能强行收尾,标记该 attempt / eval 为 `errored`(error: timeout)并触发 abort。
|
|
66
|
-
|
|
67
|
-
外层是兜底,保证一个卡死的 case 不会挂起整批。
|
|
68
|
-
|
|
69
|
-
## 环境预置不进运行器
|
|
70
|
-
|
|
71
|
-
niceeval **没有 run / experiment 级生命周期钩子**——运行器只固定"发现 → 调度 → 沙箱起停 / git 基线 / 采 diff → 评分 → 报告"这条主轴,不替用户在整轮实验前后跑任意 `setup` / `teardown`。要在跑 agent 前准备环境,分摊到三处已有职责:这条 eval 的沙箱预置走 `EvalDef.setup` 或 `test(t)`,连 agent / 装 CLI 走 [`SandboxAgent.setup`](adapters/contract.md#agent-契约),整轮共享的外部服务(mock API、DB)用外部编排(`docker compose` / CI 脚本)起停、经 env 传入。完整说明见 [环境预置放哪](sandbox.md#环境预置放哪)。
|
|
72
|
-
|
|
73
|
-
**下游分析**(二次评分、自定义指标)走 [reporter](observability.md#reporters),不另设运行钩子——这是从 agent-eval 的 `onRunComplete` 收敛过来的(见 [Experiments 砍字段](experiments.md#从-agent-eval-砍掉了什么以及为什么))。
|
|
74
|
-
|
|
75
|
-
## 运行器事件
|
|
76
|
-
|
|
77
|
-
运行器发一串事件,供 CLI dashboard、reporter、外部集成消费:
|
|
78
|
-
|
|
79
|
-
```text
|
|
80
|
-
run:start { total, agent, model }
|
|
81
|
-
eval:start { id, attempt }
|
|
82
|
-
eval:complete { id, attempt, outcome, durationMs, usage, estimatedCostUSD }
|
|
83
|
-
run:earlyExit { id }
|
|
84
|
-
run:budgetExceeded { spentUSD, budgetUSD }
|
|
85
|
-
run:saved { outputDir }
|
|
86
|
-
run:summary { passed, failed, skipped, errored, durationMs, usage, estimatedCostUSD }
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
`outcome` 是互斥的判决分类:`passed` / `failed` / `errored` / `skipped`,没有 `scored` 中间态。`run:summary.failed` 只统计断言/评分不通过,环境、超时、adapter 或 agent runtime 问题统计到 `errored`。
|
|
90
|
-
|
|
91
|
-
## 退出码
|
|
92
|
-
|
|
93
|
-
- 全 `passed` → `0`。
|
|
94
|
-
- 任一 `failed`(含 `--strict` 下 soft 未达标而改判的)→ 非零。
|
|
95
|
-
- 任一 `errored` → 非零。
|
|
96
|
-
|
|
97
|
-
供 CI 直接判红绿。
|
|
98
|
-
|
|
99
|
-
## 相关阅读
|
|
100
|
-
|
|
101
|
-
- [Architecture](architecture.md) —— 运行器在四段数据流里的位置与端到端时序。
|
|
102
|
-
- [Sandbox](sandbox.md) —— 预热与复用的后端支持,以及环境预置放哪。
|
|
103
|
-
- [Observability](observability.md) —— 运行器产出的工件与报告。
|
|
104
|
-
- [CLI](cli.md) —— 暴露这些调度行为的标志。
|