niceeval 0.4.4 → 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 +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/eval-authoring.md
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
# Authoring —— 编写 eval
|
|
2
|
-
|
|
3
|
-
写一个 eval 应该像写一个测试:一个文件、一个 `test(t)` 函数,断言写在你观察结果的地方。这一篇按这个顺序教:单轮、多轮、数据集扇出,以及沙箱型 eval 怎么手工把文件放进沙箱。评分手段(judge、匹配器、gate/soft)单独成篇,见 [Scoring](scoring.md)。
|
|
4
|
-
|
|
5
|
-
## 设计依据:为什么对齐 eve 的接收者模型
|
|
6
|
-
|
|
7
|
-
以下是内部设计依据,用于解释 API 取舍;作者写 eval 的直接用法从 [`defineEval` 的形状](#defineeval-的形状) 一节开始。
|
|
8
|
-
|
|
9
|
-
<--手动维护,不允许删改本段内容,只允许添加-->
|
|
10
|
-
# 核心原因
|
|
11
|
-
1. API应该容易理解,不会有二义性
|
|
12
|
-
1.1 负面例子,`t.messageIncludes(token)` 和 `t.calledTool(name, opts?)` 其它同样的断言API应该都是有同样语义的(比如同指是最后一次t.send,返回的消息,而不是有的是全部,有的是单轮)。如果用户想对整个消息进行评估,可以自己拼接、保存每轮的回复。
|
|
13
|
-
1.2 API唯一,如无必要,不应该有两个做一样事的API。
|
|
14
|
-
|
|
15
|
-
2. 给用户自组织的能力,而不是约定大于配置。用户不想学太多约定。
|
|
16
|
-
2.1 比如能不能把fixture、workspace(拷文件。通过基本API让用户自己去处理,而不是我们给一个值,让过程黑箱)
|
|
17
|
-
2.2 用户在用 langfuse、promptfoo 这种传统的 prompt 评估,有一些问题,像 dataset、golden,不是很适用于 Agent 的 case。 Agent eval可能更关注多轮对话、同时可能不同case的评估内容也不一样。所以统一的dataset。input与execpt output不太行。
|
|
18
|
-
2.2.1 如果用户真的需要dataset,可以通过for来实现这个功能
|
|
19
|
-
eve是怎么做到这个的
|
|
20
|
-
```ts
|
|
21
|
-
import { defineEval } from "eve/evals";
|
|
22
|
-
import { loadYaml } from "eve/evals/loaders";
|
|
23
|
-
import { equals } from "eve/evals/expect";
|
|
24
|
-
const doc = await loadYaml("evals/data/cases.yaml");
|
|
25
|
-
const rows = doc.evals as readonly { task: string; prompt: string; sql: string }[];
|
|
26
|
-
export default rows.map((row) =>
|
|
27
|
-
defineEval({
|
|
28
|
-
description: row.task,
|
|
29
|
-
async test(t) {
|
|
30
|
-
await t.send(row.prompt);
|
|
31
|
-
t.succeeded();
|
|
32
|
-
t.check(t.reply, equals(row.sql));
|
|
33
|
-
},
|
|
34
|
-
}),
|
|
35
|
-
```
|
|
36
|
-
<--end-->
|
|
37
|
-
|
|
38
|
-
### 补充:作用域按接收者决定,对齐 eve
|
|
39
|
-
|
|
40
|
-
核对 eve 源码(本机 `/Users/ctrdh/Code/eve/packages/eve/src/evals/`)后,把 1.1 说的"作用域"坐实成经验证的设计,订正上一版的误读。
|
|
41
|
-
|
|
42
|
-
**eve 的真实实现**:`assertions/scoped.ts` 的 `createScopedAssertions` 是**一份实现**,导出 `succeeded` / `messageIncludes` / `calledTool` / `notCalledTool` / `toolOrder` / `usedNoTools` / `maxToolCalls` / `calledSubagent` / `noFailedActions` / `event` / `notEvent` / `eventOrder` / `eventsSatisfy` / `parked` 这一整套,靠调用时绑定的 `scope` 决定读哪份数据,一共绑在三个地方:
|
|
43
|
-
|
|
44
|
-
- `context.ts:77`:`t` 自己绑 `{ timing: "final", select: (result) => result }`。`result` 是 `EveEvalTaskResult`,由 `runner/execute-task.ts:98`(`buildTaskResult`)构造:`events: input.sessions.flatMap(session => session.events)` —— **把这次 eval run 涉及的全部 session(含 `t.newSession()` 开的)的全部轮次拍平合并**,在 `test()` 跑完、`collector.finalize(result)` 时才求值。
|
|
45
|
-
- `session.ts:73-83`:`t.newSession()` 返回的 session 也绑同一套断言,但它是 snapshot scope,只看这个 session 在断言记录时已经发生的事件。
|
|
46
|
-
- `session.ts:298-308`(`EvalTurn` 构造函数):`t.send()` 返回的 turn 对象绑 `{ timing: "snapshot", select: () => this.#assertionSubject() }`,`#assertionSubject()` 只读**这一轮自己的** `events`(`session.ts:221-243` 的 `#recordTurn` 传入的就是这次 `send()` 的 `result.events`,不含之前轮次)。
|
|
47
|
-
|
|
48
|
-
这些绑定共享**同一套完整函数**,区别只是"挂在哪个对象上",不是"叫什么名字"——eve 没有"`messageIncludes` 天生看全部、`calledTool` 天生看单轮"这种按名字区分的不一致。1.1 要避免的正是这种不一致,eve 靠"位置决定作用域、每个位置给全套词汇"解决,不是靠"取消聚合"解决。
|
|
49
|
-
|
|
50
|
-
**niceeval 对齐到这个设计,不是取消聚合**:
|
|
51
|
-
|
|
52
|
-
- `t.*` 保留"聚合整个 eval run"的语义——这次 eval 执行的全部轮次、含 `t.newSession()` 开的额外 session,直接对应 eve 的 `timing: "final"` 层。这一层聚合是有意为之,不是要移除的"黑箱"。
|
|
53
|
-
- `session.*`(`t.newSession()` 的返回值)复用 `t.*` 的同一套**作用域断言词汇**,但只看这个 session 在断言记录时已有的事件。
|
|
54
|
-
- `turn.*`(`t.send()` 的返回值)也复用同一套**作用域断言词汇**,但只看这一轮自己的事件和用量,不再是旧版文档里的 4 个手写方法。`turn.outputEquals` / `turn.outputMatches` 是 turn 独有的(只对单轮结果有意义,聚合层不需要),继续保留。
|
|
55
|
-
|
|
56
|
-
也就是:**接收者决定作用域,不是断言名字决定作用域。** author-facing 接收者是 `t` / `session` / `turn`;`Attempt` 只作为 runner/result 里的执行单位存在,不是写 eval 时要操作的一层。完整清单见 [Assertions · 作用域规则](assertions.md#作用域规则)。
|
|
57
|
-
|
|
58
|
-
## `defineEval` 的形状
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { defineEval } from "niceeval";
|
|
62
|
-
|
|
63
|
-
export default defineEval({
|
|
64
|
-
description?: string; // 人读的描述,出现在报告里
|
|
65
|
-
agent?: string; // 可选 eval-local 默认;常规运行由 experiment 选择 agent
|
|
66
|
-
tags?: string[]; // 供 --tag 过滤
|
|
67
|
-
judge?: JudgeConfig; // 覆盖默认评判模型
|
|
68
|
-
reporters?: Reporter[]; // 这个 eval 专用的报告器
|
|
69
|
-
timeoutMs?: number; // 覆盖默认超时
|
|
70
|
-
metadata?: Record<string, unknown>;
|
|
71
|
-
async test(t) { /* 交互 + 断言 */ },
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**禁止**提供 `id` / `name` —— 它们从文件路径推导:`evals/weather/brooklyn.eval.ts` → id `weather/brooklyn`。改名即改 id,不会腐烂。
|
|
76
|
-
|
|
77
|
-
## 单轮
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
// evals/weather/brooklyn.eval.ts
|
|
81
|
-
import { defineEval } from "niceeval";
|
|
82
|
-
import { includes } from "niceeval/expect";
|
|
83
|
-
|
|
84
|
-
export default defineEval({
|
|
85
|
-
description: "布鲁克林天气查询",
|
|
86
|
-
async test(t) {
|
|
87
|
-
await t.send("布鲁克林今天天气怎么样?");
|
|
88
|
-
|
|
89
|
-
// run 级聚合断言:在 test 结束后,对本次 eval run 聚合评估
|
|
90
|
-
t.succeeded();
|
|
91
|
-
t.calledTool("get_weather", { input: { city: "Brooklyn" }, count: 1 });
|
|
92
|
-
|
|
93
|
-
// 值级断言:就地、立即评估
|
|
94
|
-
t.check(t.reply, includes("晴"));
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`t.reply` 是最后一条 assistant 消息;`t.sessionId` 是当前主会话 id;`t.events` 是主 session 目前捕获到的强类型事件流。`t.send(input)` 接受字符串或结构化消息,返回一个不可变的 **Turn**,带 `message` / `data`(结构化输出)/ `toolCalls` / `status` / `events`。带本地文件的一轮用 `t.sendFile(path, text?)`,文件会作为 data URL 附加到这一轮输入里,MIME 类型按 `path` 扩展名推断。
|
|
100
|
-
|
|
101
|
-
## 多轮
|
|
102
|
-
|
|
103
|
-
把每一轮的返回赋给局部变量,顺着断言:
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// evals/draft-then-send.eval.ts
|
|
107
|
-
import { defineEval } from "niceeval";
|
|
108
|
-
import { includes } from "niceeval/expect";
|
|
109
|
-
|
|
110
|
-
export default defineEval({
|
|
111
|
-
description: "先拟稿,确认后再发送",
|
|
112
|
-
async test(t) {
|
|
113
|
-
const draft = await t.send("帮我拟一封跟进邮件。");
|
|
114
|
-
draft.succeeded(); // 只看这一轮:失败就记一条断言
|
|
115
|
-
t.check(draft.message, includes("此致"));
|
|
116
|
-
draft.judge.autoevals.closedQA("语气是否专业").atLeast(0.6);
|
|
117
|
-
|
|
118
|
-
await t.send("好,发出去。");
|
|
119
|
-
t.calledTool("send_email");
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
需要并行的独立会话时用 `t.newSession()` 开一条互不干扰的对话线。新 session 有同一套 drive API(`send` / `sendFile` / `respond` / `events`)、同一套作用域断言和 `session.judge`;它自己的 `session.*` / `session.judge` 只看这条 session,但事件仍会汇入 `t.*` run 级断言。
|
|
125
|
-
|
|
126
|
-
### HITL / 待输入请求
|
|
127
|
-
|
|
128
|
-
当 agent 停在用户输入、审批或选项选择上时,用 `requireInputRequest` 把这个状态变成 gate,再用 `respond` 或 `respondAll` 继续下一轮:
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
const draft = await t.send("先拟稿,发出前让我确认。");
|
|
132
|
-
draft.parked();
|
|
133
|
-
|
|
134
|
-
const request = t.requireInputRequest({
|
|
135
|
-
prompt: /是否发送/,
|
|
136
|
-
optionIds: ["approve", "reject"],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
await t.respond({ request, optionId: "approve" });
|
|
140
|
-
t.calledTool("send_email");
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
如果当前轮有多个同类待处理请求,并且都选同一个选项,用 `respondAll(optionId)`:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
await t.send("把这批改动逐项提交审批。");
|
|
147
|
-
t.requireInputRequest({ display: /审批/ });
|
|
148
|
-
|
|
149
|
-
await t.respondAll("approve");
|
|
150
|
-
t.succeeded();
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 多轮里评整段对话
|
|
154
|
-
|
|
155
|
-
多轮里要分清 judge 的接收者:`t.judge` / `session.judge` 是 session 级,默认评对应 session 的对话;`turn.judge` 才是 turn 级,默认只评这一轮的 `turn.message`。完整的作用域规则与每条断言看哪一轮,见 [Assertions · 作用域规则](assertions.md#作用域规则)。
|
|
156
|
-
|
|
157
|
-
要让 judge 评「整段多轮对话」(典型:跨轮一致性),直接挂在 `t.judge` 上;要只评某一轮,挂在那一轮的 `turn.judge` 上:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
const turn1 = await t.send("这张图里有什么?"); // 第一轮:看图
|
|
161
|
-
const turn2 = await t.send("背景是什么颜色?"); // 第二、三轮:纯文字追问,考跨轮记忆
|
|
162
|
-
const turn3 = await t.send("中间那个形状是什么颜色的?");
|
|
163
|
-
|
|
164
|
-
t.judge.autoevals
|
|
165
|
-
.closedQA("助手是否始终基于第一轮的图片作答?")
|
|
166
|
-
.atLeast(0.7);
|
|
167
|
-
|
|
168
|
-
turn3.judge.autoevals.closedQA("这一轮是否回答了形状颜色?").gate();
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
如果要评 sandbox diff、文件内容或其它不是会话本身的材料,仍然用 `{ on }` 显式传值。
|
|
172
|
-
|
|
173
|
-
## 数据集扇出
|
|
174
|
-
|
|
175
|
-
一个文件默认导出**一个数组**,就扇出成多个 eval。这是写数据集的规范方式:
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// evals/sql.eval.ts
|
|
179
|
-
import { defineEval } from "niceeval";
|
|
180
|
-
import { loadYaml } from "niceeval/loaders";
|
|
181
|
-
import { equals } from "niceeval/expect";
|
|
182
|
-
|
|
183
|
-
const doc = await loadYaml("evals/data/sql-cases.yaml");
|
|
184
|
-
const rows = doc.cases as { task: string; prompt: string; sql: string }[];
|
|
185
|
-
|
|
186
|
-
export default rows.map((row) =>
|
|
187
|
-
defineEval({
|
|
188
|
-
description: row.task,
|
|
189
|
-
async test(t) {
|
|
190
|
-
await t.send(row.prompt);
|
|
191
|
-
t.succeeded();
|
|
192
|
-
t.check(t.reply, equals(row.sql));
|
|
193
|
-
},
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
```yaml
|
|
199
|
-
# evals/data/sql-cases.yaml
|
|
200
|
-
cases:
|
|
201
|
-
- task: 统计用户数
|
|
202
|
-
prompt: 查出 users 表的总行数
|
|
203
|
-
sql: SELECT COUNT(*) FROM users;
|
|
204
|
-
- task: 最近订单
|
|
205
|
-
prompt: 查出最近 10 条订单
|
|
206
|
-
sql: SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
生成的 id:`sql/0000`、`sql/0001`……(零填充 4 位,稳定可引用)。`loadJson` 同理。
|
|
210
|
-
|
|
211
|
-
## Agent 由 experiment 选择
|
|
212
|
-
|
|
213
|
-
eval 默认保持 agent-neutral,只描述"测什么"和"怎么算对"。agent 由 `experiments/` 里的 `defineExperiment` 选择,它的能力决定 `t` 能干什么:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// experiments/local.ts
|
|
217
|
-
export default defineExperiment({
|
|
218
|
-
agent: myAgent,
|
|
219
|
-
runs: 1,
|
|
220
|
-
});
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
常规运行时,agent 由 experiment 提供 —— 这让同一份 eval 能换着被测对象跑(本地 vs 部署、agent A vs agent B),同时运行配置可签入、可复现。怎么写一个 agent,详见 [Adapter 写法](adapters/authoring.md)。
|
|
224
|
-
|
|
225
|
-
## 沙箱型:手工把文件放进沙箱
|
|
226
|
-
|
|
227
|
-
评一个 coding agent 时,eval 仍然是普通的 `defineEval`,只是多了沙箱能力:`test(t)` 里的 `t` 多出 `t.sandbox`(前提是 agent 声明了 sandbox capability;见 [Sandbox](sandbox.md))。
|
|
228
|
-
|
|
229
|
-
**没有自动发现,也没有隐式拷贝**——起始文件只有一种方式:在 `test(t)` 里显式调用 `t.sandbox.writeFiles` / `t.sandbox.uploadFiles` / `t.sandbox.uploadDirectory` 写进沙箱。`t.sandbox` 是 eval 作者使用的沙箱 API,分三类:文件 IO(`writeFiles` / `uploadDirectory` / `readFile`)、命令执行(`runCommand` / `runShell`)和结果断言 / diff(`fileChanged` / `diff` / `file`)。文件从哪读、写到沙箱里哪个路径,全部是你写在 `test(t)` 里的普通代码——不存在"运行器悄悄拷贝一个目录"这种黑箱,你想放哪就写哪,不想放的就不写。路径全部用相对路径写:沙箱侧相对路径解析到 workdir(agent 的工作目录,也是 git 基线和 diff 采集的锚点),省略 `targetDir` / `cwd` 就是它;不要 hardcode 某个后端的绝对路径,详见 [Sandbox · 路径与 workdir](sandbox.md#路径与-workdir一个坐标系):
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
// evals/refactor.eval.ts
|
|
233
|
-
import { defineEval } from "niceeval";
|
|
234
|
-
import { commandSucceeded, includes } from "niceeval/expect";
|
|
235
|
-
import { readFileSync } from "node:fs";
|
|
236
|
-
|
|
237
|
-
export default defineEval({
|
|
238
|
-
description: "把回调改写成 async/await",
|
|
239
|
-
async test(t) {
|
|
240
|
-
await t.sandbox.writeFiles({
|
|
241
|
-
"src/legacy.js": readFileSync("fixtures/legacy-callbacks/legacy.js", "utf-8"),
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
await t.send("把 src/legacy.js 里的回调全部改写成 async/await,保持行为不变。");
|
|
245
|
-
const test = await t.sandbox.runCommand("npm", ["test"]);
|
|
246
|
-
|
|
247
|
-
t.sandbox.fileChanged("src/legacy.js");
|
|
248
|
-
t.check(t.sandbox.diff.get("src/legacy.js"), includes("await"));
|
|
249
|
-
t.check(test, commandSucceeded());
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**要放一整个文件夹**(比如带 `package.json` + 多个源文件的起始项目),用 `uploadDirectory(localDir, targetDir?)`。第一个参数是宿主机上的本地目录(相对路径解析到 eval 定义文件所在目录);第二个参数是 sandbox 内目标目录,省略就是 workdir——上传完整起始项目时就该省略:
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
export default defineEval({
|
|
258
|
-
description: "实现 Button 组件",
|
|
259
|
-
async test(t) {
|
|
260
|
-
await t.sandbox.uploadDirectory("fixtures/button-starter");
|
|
261
|
-
|
|
262
|
-
await t.send("在 src/components/Button.tsx 导出一个 Button 组件,接受 label 和 onClick 两个 prop。");
|
|
263
|
-
// ...
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
`writeFiles` 适合少量内联文本文件;`uploadFiles` 适合已经组织成文件数组的文本 / 二进制文件;`uploadDirectory` 适合直接把宿主机上的 fixture 项目、模板项目或测试目录递归放进 sandbox。
|
|
269
|
-
|
|
270
|
-
数据集扇出时,要写入沙箱的内容跟着数据行走,仍然是同一套写法,不需要另一种"动态 fixture"概念:
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
const rows = [{ file: "a.ts", content: "..." }, { file: "b.ts", content: "..." }];
|
|
274
|
-
|
|
275
|
-
export default rows.map((row) =>
|
|
276
|
-
defineEval({
|
|
277
|
-
description: `写入 ${row.file}`,
|
|
278
|
-
async test(t) {
|
|
279
|
-
await t.sandbox.writeFiles({ [row.file]: row.content });
|
|
280
|
-
await t.send(`审查 ${row.file}`);
|
|
281
|
-
const test = await t.sandbox.runCommand("npm", ["test"]);
|
|
282
|
-
t.check(test, commandSucceeded());
|
|
283
|
-
},
|
|
284
|
-
}),
|
|
285
|
-
);
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
**防作弊靠调用顺序,不靠框架黑箱**:验证用的测试文件(比如 `button.test.ts`)在 `t.send(...)` **之后**才写进沙箱、才运行——agent 那一轮已经结束,天然看不到,不需要"source files vs test files"这种运行器自动拆分/隐藏的机制:
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
export default defineEval({
|
|
292
|
-
description: "实现 Button 组件",
|
|
293
|
-
async test(t) {
|
|
294
|
-
await t.sandbox.writeFiles({ "package.json": PACKAGE_JSON });
|
|
295
|
-
await t.send("在 src/components/Button.tsx 导出一个 Button 组件,接受 label 和 onClick 两个 prop。");
|
|
296
|
-
|
|
297
|
-
// agent 跑完之后才放测试文件、才跑测试——全程手工可见,没有隐藏逻辑
|
|
298
|
-
await t.sandbox.writeFiles({ "button.test.ts": BUTTON_TEST_SOURCE });
|
|
299
|
-
const test = await t.sandbox.runCommand("npm", ["test"]);
|
|
300
|
-
t.check(test, commandSucceeded());
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
`t.sandbox` 这一个命名空间下按语义分组:`writeFiles` / `uploadFiles` / `runCommand` 是立即 IO / 命令;`fileChanged` / `fileDeleted` / `notInDiff` / `diff` / `file` 是结果视图和延迟断言。`stop()` 这类生命周期动作不暴露给 eval 作者,由 runner 管。要用 judge 评 sandbox 产物,直接 `t.judge.autoevals.closedQA(criteria, { on: t.sandbox.diff.get(path) })`。完整列表见 [Assertions · API 分组速查](assertions.md#api-分组速查)。沙箱创建时运行器已经打好一次空 git 基线,所以 `t.sandbox.diff` / `t.sandbox.fileChanged` 不依赖你怎么写入起始文件、写了没有,随时可读。
|
|
306
|
-
|
|
307
|
-
## 命名与组织约定
|
|
308
|
-
|
|
309
|
-
- 文件名以 `.eval.ts` 结尾才会被发现。
|
|
310
|
-
- 用目录表达分组:`evals/billing/refund.eval.ts` → `billing/refund`。
|
|
311
|
-
- 数据集放 `evals/data/`,沙箱型 eval 要写入的起始文件素材可以放 `evals/fixtures/`(纯目录命名约定,运行器不会扫描或自动加载它——仍要在 `test()` 里手工 `t.sandbox.writeFiles`/`uploadFiles` 读取写入)。
|
|
312
|
-
- `description` 写给人看,id 给机器引用。
|
|
313
|
-
|
|
314
|
-
## 相关阅读
|
|
315
|
-
|
|
316
|
-
- [Assertions](assertions.md) —— `t.check` / 作用域断言的完整速查表(看哪一轮、来源哪里)。
|
|
317
|
-
- [Scoring](scoring.md) —— judge 细节、测试即评分、判决规则。
|
|
318
|
-
- [Agents 与 Adapters](adapters/README.md) —— agent 三类 transport 与 agent 适配。
|
|
319
|
-
- [CLI](cli.md) —— 过滤、重试、并发等运行标志。
|
package/docs/experiments.md
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Experiments —— 怎么跑这批 eval
|
|
2
|
-
|
|
3
|
-
一个 eval 描述**测什么**(这轮对话该发生什么、怎么算对)。一个 **experiment** 描述**怎么跑这批 eval**:用哪些 agent、跑几次、过滤哪些、预算多少。两者刻意分开。
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
evals/ # 测什么 —— agent 无关,评分逻辑都在各自的 test() 里
|
|
7
|
-
experiments/ # 怎么跑 —— 运行矩阵:agent × model × runs over 选定 evals
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
> 参考:eve **没有**实验概念(运行配置靠 `defineEvalConfig` + CLI flag,"experiment" 只是 Braintrust 上报名);experiment 这层参考的是 Vercel agent-eval 的 `ExperimentConfig`,但**砍掉了一半字段**(见下)。
|
|
11
|
-
|
|
12
|
-
## 为什么要分开
|
|
13
|
-
|
|
14
|
-
- **eval 不该知道被测的是谁。** 同一条 memory eval,既要测 claude-code 也要测 codex/bub。把 agent 写死进 eval 就废了复用。
|
|
15
|
-
- **experiment 是可签入的运行配置。** 比一串临时 CLI flag 可复现:`niceeval exp compare` 永远跑同一组对照。
|
|
16
|
-
- **跨 agent / 跨配置对比是一等公民。** 评 coding agent 最想要的就是"一条命令,几个配置并列出**质量 × 成本**"(见 [Observability](observability.md#结果可视化niceeval-view))。niceeval 用**文件夹**表达"这一组该并排比"(见 [实验怎么组织](#实验怎么组织文件夹--一组可对比的实验));每个实验文件钉一个单一配置。
|
|
17
|
-
|
|
18
|
-
## `defineExperiment` 的形状
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
import { defineExperiment } from "niceeval";
|
|
22
|
-
import type { Agent } from "niceeval/adapter";
|
|
23
|
-
|
|
24
|
-
export default defineExperiment({
|
|
25
|
-
description?: string; // 人读
|
|
26
|
-
agent: Agent; // 跑哪个 agent(adapter 实例)
|
|
27
|
-
model?: string; // 单个模型(agent 留空);省略=原生默认。跨模型对比写多个实验文件
|
|
28
|
-
reasoningEffort?: string; // 推理努力程度(agent 留空);省略=原生默认。经 ctx.reasoningEffort / t.reasoningEffort 透传
|
|
29
|
-
flags?: Record<string, unknown>; // feature flags,透传到 ctx.flags / t.flags(见下)
|
|
30
|
-
runs?: number; // 每个 (agent × model × eval) 跑几次(默认 1)
|
|
31
|
-
earlyExit?: boolean; // 先过一次即停其余(默认 true)
|
|
32
|
-
evals?: "*" | string[] | ((id: string) => boolean); // 跑哪些 eval(默认 "*")
|
|
33
|
-
timeoutMs?: number; // 单次运行超时
|
|
34
|
-
sandbox?: SandboxSpec; // 沙箱型 agent 在哪跑,工厂函数产出(dockerSandbox() 等);省略 = auto 探测。`--sandbox` 可临时覆盖
|
|
35
|
-
budget?: number; // 整个实验估算成本上限($),超了停止派发
|
|
36
|
-
});
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> experiment 只有"运行矩阵"字段,**没有 run / experiment 级环境起停钩子**。要在跑 agent 前准备环境,放 `EvalDef.setup` / `test(t)`(单个 eval 的沙箱预置)、`SandboxAgent.setup`(连 agent / 装 CLI)或外部编排(整轮共享服务),见 [环境预置放哪](sandbox.md#环境预置放哪)。
|
|
40
|
-
|
|
41
|
-
id 从**路径**推导:`experiments/compare/bub-gpt-5.4.ts` → `compare/bub-gpt-5.4`(路径即身份,和 eval 一致,禁止手写 id);其中目录段 `compare/` 就是"可对比组",见 [下节](#实验怎么组织文件夹--一组可对比的实验)。
|
|
42
|
-
|
|
43
|
-
### model / reasoningEffort 与 flags:agent 留空,实验决定
|
|
44
|
-
|
|
45
|
-
agent 定义里**不写死模型、不写死开关**(那样就锁死了复用)。这几样由实验给,经 `ctx` 透传:
|
|
46
|
-
|
|
47
|
-
- **`model`** —— 单个模型字符串,agent 的 `send` 从 `ctx.model` 拿;省略则不传 `--model`,用 agent CLI 的原生默认。**跨模型对比写多个实验文件**(各钉一个 model),别在一个实验里塞数组。
|
|
48
|
-
- **`reasoningEffort`** —— 单个推理努力程度字符串(取值由具体模型定义,如 `"low"`/`"medium"`/`"high"`),归属与 `model` 完全一致:agent 的 `send` 从 `ctx.reasoningEffort` 拿,eval 的 `test` 从 `t.reasoningEffort` 拿;省略则用 agent 原生默认。跨档位对比同样写多个实验文件。
|
|
49
|
-
- **`flags`** —— 任意 KV 的 feature flags,**两处可见**:agent 的 `send`(`ctx.flags`)、eval 的 `test`(`t.flags`)。用来开关联网、注入某个 skill、或让某条 eval 只在某 flag 下断言。
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
// experiments/research-mode.experiment.ts
|
|
53
|
-
export default defineExperiment({
|
|
54
|
-
agent: codexAgent(),
|
|
55
|
-
model: "opus", // 模型在实验给,agent 留空
|
|
56
|
-
reasoningEffort: "high", // → ctx.reasoningEffort / t.reasoningEffort
|
|
57
|
-
flags: { webResearch: true, skill: "memory-v2" }, // → ctx.flags / t.flags
|
|
58
|
-
runs: 3,
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
flag 驱动的环境差异(比如按 `flags.skill` 往沙箱注入一个 skill 文件)写在 eval 的 `test(t)` 里:`if (t.flags.skill) await t.sandbox.writeFiles({ ".agent/skill.md": loadSkill(t.flags.skill) })`——普通代码,不需要框架钩子。
|
|
63
|
-
|
|
64
|
-
详见 [Adapter 契约:三类配置的归属](adapters/contract.md#三类配置的归属本地配--实验传入--ctx-透传)。
|
|
65
|
-
|
|
66
|
-
## 实验怎么组织:文件夹 = 一组可对比的实验
|
|
67
|
-
|
|
68
|
-
experiment 借 next-evals-oss 的"一条件一文件",再用**文件夹**把"可比性"显式化:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
experiments/
|
|
72
|
-
└─ compare/ # 一组可对比实验:同一模型下 bub vs codex
|
|
73
|
-
├─ bub-gpt-5.4.ts # 单一配置,文件名 = <agent>-<model>
|
|
74
|
-
└─ codex-gpt-5.4.ts
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- **文件夹 = 一组可对比的实验**;**同一文件夹下的文件才互相对比**。`niceeval exp compare` 跑整组,`niceeval view` 把同组并列。
|
|
78
|
-
- **文件 = 单一配置**(一个 agent × 一个 model 是最干净的情形),文件名按 `<agent>-<model>[-<feature>]` 命名。同组钉住对比轴之外的一切(如同一 model),差异才干净归因到那一个轴(agent / 记忆机制 / flag)。
|
|
79
|
-
- **路径即 id**:`experiments/compare/bub-gpt-5.4.ts` → `compare/bub-gpt-5.4`;目录段 `compare` 即组名。
|
|
80
|
-
|
|
81
|
-
### 一文件一配置
|
|
82
|
-
|
|
83
|
-
**一个实验文件 = 一个配置**(一个 agent × 一个 model)。要跨模型 / 跨 agent 对比,就**写多个实验文件**,各钉对照轴之外的一切(如同一 model),差异才干净归因到那一个轴。`model` 是单个字符串,不接受数组 —— 想扫多个模型,复制一份实验文件改 `model` 即可。
|
|
84
|
-
|
|
85
|
-
这样每个配置独立成文件:可命名(`<agent>-<model>[-<feature>]`)、可 diff、可单独 review,"这一组就是对照"这层意思在文件结构上就讲清楚了。两条配置汇总都按 `(agent, model)` 分组,可在 `niceeval view` 里并排。
|
|
86
|
-
|
|
87
|
-
### 例子
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// experiments/compare/bub-gpt-5.4.ts —— 对照组的一格:bub(tape on)
|
|
91
|
-
export default defineExperiment({
|
|
92
|
-
description: "bub · gpt-5.4(tape on)",
|
|
93
|
-
agent: bubAgent(),
|
|
94
|
-
model: "gpt-5.4", // 同组钉同一 model,差异归因到 agent / 记忆机制
|
|
95
|
-
runs: 5,
|
|
96
|
-
earlyExit: false, // 要完整通过率分布(pass^k)
|
|
97
|
-
budget: 15,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// experiments/compare/codex-gpt-5.4.ts —— 同组对照的另一格(只换 agent)
|
|
101
|
-
export default defineExperiment({
|
|
102
|
-
description: "codex · gpt-5.4",
|
|
103
|
-
agent: codexAgent(),
|
|
104
|
-
model: "gpt-5.4",
|
|
105
|
-
runs: 5,
|
|
106
|
-
earlyExit: false,
|
|
107
|
-
budget: 15,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// 想扫多个 agent/model 时,复制多个实验文件;不要在单文件里塞数组。
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## 从 agent-eval 砍掉了什么(以及为什么)
|
|
114
|
-
|
|
115
|
-
agent-eval 的 `ExperimentConfig` 字段一半是它自己业务的耦合或可下放的。niceeval 的 `defineExperiment` 只留**纯运行矩阵**:
|
|
116
|
-
|
|
117
|
-
| agent-eval 字段 | niceeval | 处置 | 理由 |
|
|
118
|
-
|---|---|---|---|
|
|
119
|
-
| `agent` | `agent` | 保留,但一文件一个 agent | 沿用 agent;文件夹表达"可比组"(见 [实验怎么组织](#实验怎么组织文件夹--一组可对比的实验)) |
|
|
120
|
-
| `model` / `runs` / `earlyExit` / `evals` / `timeout` / `sandbox` | 同(`timeout`→`timeoutMs`) | 保留 | 运行矩阵的本体 |
|
|
121
|
-
| `setup` | — | **删** | 环境预置不进 experiment:沙箱预置写 `test()`、连 agent 写 `SandboxAgent.setup`、整轮共享服务用外部编排(见 [环境预置放哪](sandbox.md#环境预置放哪)) |
|
|
122
|
-
| `validation` | — | **删** | 「怎么算对」是 eval 自己的事(`test()` 里手工跑校验命令),不该由 experiment 决定 |
|
|
123
|
-
| `scripts` | — | **删** | 同上,属于 eval / fixture 的评分,不是运行配置 |
|
|
124
|
-
| `brands` | — | **删** | Vercel 品牌追踪专用,通用 evals 不需要 |
|
|
125
|
-
| `editPrompt` | — | **删** | 改写 prompt 太 niche,需要时在 agent/eval 里做 |
|
|
126
|
-
| `onRunComplete` | — | **删** | 下游**分析**交给 [reporter](observability.md#reporters);**资源起停**不由 experiment 钩子管,靠外部编排 / `SandboxAgent.setup` / `test()`(见 [环境预置放哪](sandbox.md#环境预置放哪)) |
|
|
127
|
-
| `modelPolicy` | — | **删** | 折进「`model` 省略 = 原生默认」 |
|
|
128
|
-
| `copyFiles` | — | **删** | 和 diff 冗余:git 基线是空 commit,`t.sandbox.diff.get(path)` 拿到的就是完整新文件内容,不必再单独拷一份 |
|
|
129
|
-
| `webResearch` / `agentOptions` | `flags` | **合并** | 一个通用 feature-flag 袋取代散落的开关,经 `ctx.flags` / `t.flags` 透传 |
|
|
130
|
-
| — | `budget` | **加** | 实验级成本上限,接 [用量与成本](observability.md#用量与成本token--计费) |
|
|
131
|
-
|
|
132
|
-
一句话:**experiment 只管"跑什么、跑几次、花多少",不碰"怎么算对"。** 评分细节全在 eval。
|
|
133
|
-
|
|
134
|
-
## 与 config 的关系
|
|
135
|
-
|
|
136
|
-
- **`niceeval.config.ts`(`defineConfig`)** = 项目级默认:`judge`、`reporters`、并发 / 超时、`pricing`。**沙箱后端不在 config** —— 由 experiment 的 `sandbox` 决定。
|
|
137
|
-
- **`experiments/**/*.ts`(默认导出 `defineExperiment`)** = 一次具体运行的配置,覆盖 config 默认;按文件夹聚成可对比组(`.experiment.ts` 后缀可选,位于 `experiments/` 下即识别)。
|
|
138
|
-
|
|
139
|
-
调度项覆盖优先级(高 → 低):**CLI flag → experiment → config → 内置默认**。agent、model、flags 属于 experiment,不由 CLI 覆盖。
|
|
140
|
-
|
|
141
|
-
## CLI
|
|
142
|
-
|
|
143
|
-
```sh
|
|
144
|
-
niceeval exp # 跑 experiments/ 下全部实验
|
|
145
|
-
niceeval exp compare # 跑某一组(文件夹 compare/ 内全部配置,互为对照)
|
|
146
|
-
niceeval exp compare/bub-gpt-5.4 # 跑组里某一个配置
|
|
147
|
-
niceeval exp compare memory/retention # 再用 eval id 前缀缩小到部分 eval
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
不写实验不能运行 eval。临时验证也写一个小的 `experiments/local.ts`;要换 agent 或 model,复制一个 experiment 文件改配置。
|
|
151
|
-
|
|
152
|
-
## 相关阅读
|
|
153
|
-
|
|
154
|
-
- [Authoring](eval-authoring.md) —— eval 怎么写(experiment 跑的就是它们)。
|
|
155
|
-
- [Observability](observability.md) —— 跨 agent 的质量×成本对比与 `niceeval view`。
|
|
156
|
-
- [Runner](runner.md) —— 矩阵展开、并发、早停、预算的调度。
|
|
157
|
-
- [CLI](cli.md) —— `exp` 与全部标志。
|