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
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "NiceEval 中的 eval:生命周期、outcome 与文件"
|
|
3
|
+
sidebarTitle: "评估"
|
|
4
|
+
description: "eval 是一个测试用例:描述、agent 引用和 test 函数。了解 eval 如何被发现、调度、评分和报告。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
一个 eval 是一个可运行的测试用例。它通常由一个 `*.eval.ts` 文件导出,通过 `defineEval` 声明。
|
|
8
|
+
|
|
9
|
+
## eval 的组成
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineEval } from "niceeval";
|
|
13
|
+
import { includes } from "niceeval/expect";
|
|
14
|
+
|
|
15
|
+
export default defineEval({
|
|
16
|
+
description: "Brooklyn weather query",
|
|
17
|
+
async test(t) {
|
|
18
|
+
await t.send("What's the weather like in Brooklyn today?");
|
|
19
|
+
t.succeeded();
|
|
20
|
+
t.calledTool("get_weather", { input: { city: "Brooklyn" }, count: 1 });
|
|
21
|
+
t.check(t.reply, includes("sunny"));
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
核心字段:
|
|
27
|
+
|
|
28
|
+
| 字段 | 说明 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `description` | 给人看的描述,出现在报告里 |
|
|
31
|
+
| `agent` | 使用哪个 Adapter,可由配置或 CLI 覆盖 |
|
|
32
|
+
| `test(t)` | 交互和断言逻辑 |
|
|
33
|
+
|
|
34
|
+
<Note>
|
|
35
|
+
不要手写 `id` 或 `name`。[NiceEval](https://niceeval.com/) 从文件路径推导 ID。
|
|
36
|
+
</Note>
|
|
37
|
+
|
|
38
|
+
## 路径即身份
|
|
39
|
+
|
|
40
|
+
`evals/weather/brooklyn.eval.ts` 的 ID 是 `weather/brooklyn`。experiment 名之后的位置参数按 ID 前缀过滤:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx niceeval exp local weather
|
|
44
|
+
npx niceeval exp local weather/brooklyn
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
这种方式让 ID 稳定、可读,并自然跟目录结构保持一致。
|
|
48
|
+
|
|
49
|
+
## 生命周期
|
|
50
|
+
|
|
51
|
+
<Steps>
|
|
52
|
+
<Step title="Discovery">
|
|
53
|
+
runner 加载 `evals/` 下的 `*.eval.ts` 文件和 fixture 目录。
|
|
54
|
+
</Step>
|
|
55
|
+
<Step title="Scheduling">
|
|
56
|
+
结合并发、缓存、runs、attempt 和 early-exit 生成执行计划。
|
|
57
|
+
</Step>
|
|
58
|
+
<Step title="agent.send">
|
|
59
|
+
`t.send()` 调用被选中的 Adapter,并得到标准 `Turn`。
|
|
60
|
+
</Step>
|
|
61
|
+
<Step title="Scoring">
|
|
62
|
+
[NiceEval](https://niceeval.com/) 收集值断言、作用域断言、judge 分数和测试结果。
|
|
63
|
+
</Step>
|
|
64
|
+
<Step title="Outcome">
|
|
65
|
+
所有断言结果折叠成一个最终 outcome。
|
|
66
|
+
</Step>
|
|
67
|
+
<Step title="Report">
|
|
68
|
+
控制台和 reporters 输出结果,同时写入 `.niceeval/` artifacts。
|
|
69
|
+
</Step>
|
|
70
|
+
</Steps>
|
|
71
|
+
|
|
72
|
+
## Outcome 类型
|
|
73
|
+
|
|
74
|
+
<CardGroup cols={2}>
|
|
75
|
+
<Card title="passed" icon="circle-check" color="#22c55e">
|
|
76
|
+
所有 gate 断言通过,并且没有未满足的硬失败。
|
|
77
|
+
</Card>
|
|
78
|
+
<Card title="failed" icon="circle-xmark" color="#ef4444">
|
|
79
|
+
至少一个 gate 断言失败,或运行本身失败。
|
|
80
|
+
</Card>
|
|
81
|
+
<Card title="passed" icon="chart-bar" color="#f59e0b">
|
|
82
|
+
没有 gate 失败,但 soft 分数需要保留为分值。
|
|
83
|
+
</Card>
|
|
84
|
+
<Card title="skipped" icon="forward" color="#6b7280">
|
|
85
|
+
eval 主动跳过,通常通过 `t.skip(reason)`。
|
|
86
|
+
</Card>
|
|
87
|
+
</CardGroup>
|
|
88
|
+
|
|
89
|
+
## gate 与 soft
|
|
90
|
+
|
|
91
|
+
`gate` 是硬门槛,失败会让 eval 失败;`soft` 参与打分,但不一定让 eval 失败。完整规则见 [Assert](/zh/concepts/assert)。
|
|
92
|
+
|
|
93
|
+
## `*.eval.ts` 约定
|
|
94
|
+
|
|
95
|
+
只有以 `.eval.ts` 结尾的文件会被发现。用目录表达分组:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
evals/
|
|
99
|
+
└─ billing/
|
|
100
|
+
└─ refund.eval.ts # id: billing/refund
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 数组导出与数据驱动测试(dataset fan-out)
|
|
104
|
+
|
|
105
|
+
一个文件也可以默认导出 `defineEval(...)` 数组,用同一套逻辑生成多个 case:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
export default rows.map((row) =>
|
|
109
|
+
defineEval({
|
|
110
|
+
description: row.task,
|
|
111
|
+
async test(t) {
|
|
112
|
+
await t.send(row.prompt);
|
|
113
|
+
t.check(t.reply, equals(row.expected));
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
生成 ID 类似 `sql/0000`、`sql/0001`。详见 [数据驱动测试](/zh/guides/dataset-fanout)。
|
|
120
|
+
|
|
121
|
+
## 相关阅读
|
|
122
|
+
|
|
123
|
+
- [实验](/zh/concepts/experiment) — 另一半:评谁、怎么跑;为什么和 eval 分开(晚绑定)。
|
|
124
|
+
- [Assert](/zh/concepts/assert) — gate 与 soft 的完整判决规则。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "实验(Experiment):评谁、怎么跑"
|
|
3
|
+
sidebarTitle: "实验"
|
|
4
|
+
description: "Experiment 是可签入的运行配置:同一批 eval 对着哪个 agent、哪个模型、哪些 flags、跑几次。eval 与 experiment 分开的原因——晚绑定,让同一份 eval 换着对象跑。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**experiment** 对着哪个 agent、用哪个模型、开哪些 flags、跑几次。用于做 AB 测试、模型对比、feature 对比。
|
|
8
|
+
|
|
9
|
+
## 一个 experiment 里有什么
|
|
10
|
+
|
|
11
|
+
experiment 是 `experiments/` 下可签入的 TypeScript 文件,核心就几个字段:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// experiments/concise.ts
|
|
15
|
+
export default defineExperiment({
|
|
16
|
+
description: "简洁 prompt 变体",
|
|
17
|
+
agent: webAgent({ baseUrl: "https://staging.example.com" }), // 已配置好的 agent 实例
|
|
18
|
+
model: "gpt-5.4", // 经 ctx.model 透传给 Adapter
|
|
19
|
+
flags: { promptVariant: "concise" }, // 经 ctx.flags 透传;eval 里是 t.flags
|
|
20
|
+
runs: 3, // 每条 eval 跑几次(对抗随机性)
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `agent`:评谁。放的是已经配置好的实例——被测系统的 URL、鉴权传给 Adapter 工厂,不进 experiment 的其它字段。
|
|
25
|
+
- `model` / `flags`:透传语义。[NiceEval](https://niceeval.com/) 不解释它们的含义,原样经 `ctx` 递给 Adapter,由 Adapter 随请求转发、应用按需切换——这正是 [Tier](/zh/concepts/tier) 里模型对比(Tier 1)和 feature A/B(Tier 3)的通道。
|
|
26
|
+
- `runs`、`budget`、并发、`sandbox` 等运行参数:怎么跑、跑多少。完整字段见[写实验](/zh/guides/write-experiment)。
|
|
27
|
+
|
|
28
|
+
## 矩阵对比
|
|
29
|
+
|
|
30
|
+
要比较的每个变体写一个 experiment 文件:两个模型就是两个文件,只差 `model` 一行;prompt A/B 就是只差一个 flag。同一批 eval 在多个 experiment 下各跑一遍,pass rate、成本、延迟就有了可比的横截面——`niceeval view` 里叠着看。适合比什么、结果怎么读,见[实验矩阵](/zh/guides/experiments)。
|
|
31
|
+
|
|
32
|
+
## 相关阅读
|
|
33
|
+
|
|
34
|
+
- [写实验](/zh/guides/write-experiment) — `defineExperiment` 的完整字段:runs、预算、并发与 sandbox。
|
|
35
|
+
- [实验矩阵](/zh/guides/experiments) — 跨 agent / model / flags 的对比怎么组织、怎么读结果。
|
|
36
|
+
- [评估](/zh/concepts/evals) — 另一半:eval 是什么、生命周期与 outcome。
|
|
37
|
+
- [Tier](/zh/concepts/tier) — `model` / `flags` 各在哪一档生效。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "人工介入(HITL):停轮、回答、继续"
|
|
3
|
+
sidebarTitle: "HITL"
|
|
4
|
+
description: "什么是 Human-in-the-loop,Claude Code / Codex / AI SDK 应用里它长什么样;NiceEval 如何把它变成可评的路径——waiting 停轮、input.requested 说明在等什么、t.respond 替人回答后同一轮继续。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
HITL(human-in-the-loop,人工介入)指 agent 在执行中间停下来,把决定权交回给人:拿到回答之前不继续。它不是异常,而是 agent 产品刻意设计的安全门与信息门——敏感操作前要人批准,信息不够时要人补充,多个方案时要人拍板。
|
|
8
|
+
|
|
9
|
+
你大概率已经每天在用它:
|
|
10
|
+
|
|
11
|
+
- **Claude Code**:跑一条有风险的命令、改一个权限外的文件之前,会停下来问"允许吗?"——批准了才执行,拒绝了就换路。
|
|
12
|
+
- **Codex**:审批模式(suggest / auto-edit 等)决定哪些动作要先经人确认,本质是同一扇门。
|
|
13
|
+
- **AI SDK 应用**:工具不带 `execute` 时,工具调用会浮到前端,由界面上的人确认后把结果交回(`addToolResult`),模型再继续——这是自研应用里最常见的审批门写法。
|
|
14
|
+
|
|
15
|
+
对被测对象来说,这条"停下来等人"的分支和别的行为一样需要回归:批准后该发生的事发生了吗?拒绝后真的没做吗?问错了人、停错了地方呢?但 eval 是自动跑的,里面没有人。[NiceEval](https://niceeval.com/) 的做法是把"人"折叠成三个可编排的动作:看到 agent 停了(`t.parked()`)、检查它在等什么(`t.requireInputRequest()`)、替人回答(`t.respond()`)。批准、拒绝、补充信息,从此都是能写进 eval 的路径。
|
|
16
|
+
|
|
17
|
+
## 停轮如何表达:waiting + input.requested
|
|
18
|
+
|
|
19
|
+
`send` 返回的 `Turn.status` 有三个值:`"completed"`(跑完了)、`"failed"`(出错了)、`"waiting"`——**这一轮没跑完,agent 停在等人输入上**。waiting 不是失败,是挂起:回答到达后同一轮接着跑,不重发请求、不开新对话。
|
|
20
|
+
|
|
21
|
+
停下的轮次还要同时说明"在等什么":每个待回答的问题吐一条 `input.requested` 事件,带一个稳定的请求 `id`、停在哪个动作上(`action`)、可选的裁决项(`options`,如 approve / deny)。状态和事件缺一不可——`waiting` 让 eval 知道该回答了,`input.requested` 让回答有的放矢。
|
|
22
|
+
|
|
23
|
+
## 一次完整握手
|
|
24
|
+
|
|
25
|
+
`t.respond` 底层没有专用通道——它只是**又一次普通的 `send`**,人的裁决以结构化形式随输入到达:
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
两个容易想歪的地方:
|
|
30
|
+
|
|
31
|
+
- **回答轮不是新对话。** 它发生在同一条会话线上,`ctx.session` 还是同一个——Adapter 靠它找回上一轮挂起的现场。
|
|
32
|
+
- **回答不用从文本里猜。** `input.responses` 逐请求带着 `{ requestId, optionId }`(自由文本回答则是 `{ requestId, text }`),命中选项的 `optionId` 在 eval 侧已校验过存在,打错的字直接抛错而不是静默传给应用。每种回答到 Adapter 长什么样,见[不同回答的入参](/zh/concepts/adapter#不同回答的入参)。
|
|
33
|
+
|
|
34
|
+
## eval 侧:三个动作
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const draft = await t.send("给老板发一封周报邮件。");
|
|
38
|
+
draft.parked(); // ① 断言这一轮停下了
|
|
39
|
+
|
|
40
|
+
const req = t.requireInputRequest({ action: "send_email" }); // ② 取出待答请求
|
|
41
|
+
|
|
42
|
+
await t.respond({ request: req, optionId: "approve" }); // ③ 替人回答,发出下一轮
|
|
43
|
+
t.calledTool("send_email", { status: "completed" });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `t.parked()` 断言这一轮确实以 waiting 停下;反过来 `t.succeeded()` 在 waiting 轮上会失败——停轮是显式状态,不会被当成"跑完了"。
|
|
47
|
+
- `t.requireInputRequest(filter)` 从待处理请求里精确取一个(按 `id` / `prompt` / `action` / `optionIds` 等字段匹配),匹配到 0 个或超过 1 个都会抛,多个请求并停时靠它消歧。
|
|
48
|
+
- `t.respond(...)` 回答并发出下一轮;同类请求要给同一个答案时(比如逐个批准一批改动),`t.respondAll(optionId)` 一次处理完。
|
|
49
|
+
|
|
50
|
+
批准和拒绝是同一扇门的两个分支,各写一条 eval 才算评完:批准后该发生的发生了,拒绝后该发生的没发生。逐个 API 的完整用法见 [Drive](/zh/concepts/drive#人工介入-hitl)。
|
|
51
|
+
|
|
52
|
+
## Adapter 侧:两条义务,一次续跑
|
|
53
|
+
|
|
54
|
+
HITL 对 Adapter 的全部要求就三件事——前两件发生在停下的那一轮,第三件发生在回答到达的下一轮:
|
|
55
|
+
|
|
56
|
+
1. **停轮时如实返回 `status: "waiting"`**,不要把挂起报成 `"completed"`,否则 `parked()` 失效、`succeeded()` 假通过;
|
|
57
|
+
2. **每个待回答的问题吐一条 `input.requested`**,`id` 稳定、字段尽量填全——eval 侧的检查和对位全靠它们;
|
|
58
|
+
3. **下一次 `send` 先交裁决、再续跑**:从 `input.responses` 按 `requestId` 把裁决交回应用(不要按顺序猜),然后接着上一轮挂起的地方继续,而不是重发请求。
|
|
59
|
+
|
|
60
|
+
"挂起的现场"(比如读了一半的 SSE 流)存在本会话线的 `ctx.session` 上——停轮时 `ctx.session.hold(现场)`,回答轮 `ctx.session.take()` 取回,取到即清除。怎么和流式驱动拼起来,见[写 send](/zh/guides/write-send#第五步:hitl) 第五步的完整骨架。
|
|
61
|
+
|
|
62
|
+
和 [NiceEval](https://niceeval.com/) 的其它能力一样,HITL 没有布尔声明:**做到了就是有**。send 返回过 `"waiting"` 并吐了 `input.requested`,`t.respond` 就能工作;没做到,eval 会在第一个 `parked()` 或 `requireInputRequest()` 上明确失败,而不是静默假通过。能力如何从构造中来,见 [Adapter](/zh/concepts/adapter#能力从哪来:构造证明,不是问卷)。
|
|
63
|
+
|
|
64
|
+
## 拒绝不是故障
|
|
65
|
+
|
|
66
|
+
人否决和工具报错是两回事,事件流里用不同的 `status` 区分:被人拒绝的调用,`action.result` 的 `status` 是 `"rejected"` 而不是 `"failed"`。这样 `t.noFailedActions()` 不会被一次正当的人工否决误伤,而 `t.calledTool("deploy", { status: "rejected" })` 可以精确断言"发起了调用、被人拦下了"——这正是拒绝分支的 eval 要写的断言。
|
|
67
|
+
|
|
68
|
+
## 哪些 agent 用不到 HITL
|
|
69
|
+
|
|
70
|
+
不是每个 agent 都有这条分支,没有就整个跳过——Adapter 不用管 `waiting`,eval 里也不会出现 `t.respond`:
|
|
71
|
+
|
|
72
|
+
- **每轮一口气跑完的 agent**:问答、检索、翻译这类单发即回的服务,执行中没有等人的环节。
|
|
73
|
+
- **审批门被关掉的运行形态**:Claude Code、Codex 产品本身是 HITL 的,但作为被测对象在沙箱里通常全自动跑(跳过权限确认)——内置的 `claude-code` / `codex` / `bub` sandbox agent 就是这样,所以它们都不做 HITL。
|
|
74
|
+
- **审批发生在执行循环之外**:比如工单系统里人工复核 agent 的产出。那是另一个系统的流程,不经过 `send`,eval 评不到也不该评。
|
|
75
|
+
|
|
76
|
+
判断标准只有一条:**你的 agent 执行循环里存在"停下来等人、拿到回答再继续"的分支,而你想把批准和拒绝写进 eval。**
|
|
77
|
+
|
|
78
|
+
## 相关阅读
|
|
79
|
+
|
|
80
|
+
- [Drive](/zh/concepts/drive#人工介入-hitl) — eval 侧的完整用法:`t.parked()`、`t.requireInputRequest()`、`t.respond()` / `t.respondAll()`。
|
|
81
|
+
- [Adapter](/zh/concepts/adapter#不同回答的入参) — 回答到 Adapter 的结构化入参,四种典型形态。
|
|
82
|
+
- [写 send](/zh/guides/write-send) — Adapter 侧实操:`ctx.session.hold` / `take` 与流式 + HITL 的完整骨架。
|
|
83
|
+
- [接入你的 Agent](/zh/guides/connect-your-agent) — 接入全景:最小接入、参数通道与增量地图。
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "NiceEval 里的 LLM-as-judge: 给开放式输出打分"
|
|
3
|
+
sidebarTitle: "评判"
|
|
4
|
+
description: "t.judge / session.judge / turn.judge 如何用独立评判模型评估事实性、闭合式质量和摘要忠实度,以及模型解析优先级和严重级。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
judge 断言是第五种评分机制,和 [Assert](/zh/concepts/assert) 里的四种并列。用在“对不对靠规则说不清”的地方——开放式行文、语气、事实一致性、摘要质量。评判模型和被测 agent **完全分离**,避免自评:同一个模型给自己的输出打分,天然会打得偏高。
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
t.judge.autoevals.factuality(reference).atLeast(0.8); // 与参考文本的事实一致性
|
|
11
|
+
t.judge.autoevals.closedQA("回答是否适合 10 岁小孩理解?");
|
|
12
|
+
t.judge.autoevals.summarizes(sourceDocument); // 是否忠实摘要
|
|
13
|
+
t.judge.autoevals.closedQA("自定义评分标准描述", { on: t.reply });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## judge 的三种固定形状
|
|
17
|
+
|
|
18
|
+
`t.judge.autoevals` 只暴露三种固定形状,评什么都落进其中之一:
|
|
19
|
+
|
|
20
|
+
- **`closedQA(question, opts?)`** —— 闭合式判断:输出满不满足这条标准?适合打分标准明确的检查("语气是否礼貌、具体""是否没有编造政策细节")。
|
|
21
|
+
- **`factuality(reference, opts?)`** —— 输出和你给的参考文本之间的事实一致性。
|
|
22
|
+
- **`summarizes(source, opts?)`** —— 输出是否忠实摘要了某个源文档。
|
|
23
|
+
|
|
24
|
+
`{ on }` 指定被评的值,默认值按接收者决定(见下文);`{ model }` 单次覆盖评判模型。
|
|
25
|
+
|
|
26
|
+
## judge 挂在哪:`t`、`session`、`turn`
|
|
27
|
+
|
|
28
|
+
和作用域断言一样,judge 调用遵守同一条规则:**接收者决定默认材料,不是调用方式决定**:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
t.judge.autoevals.closedQA("...") // 默认评主 session 的完整对话文本
|
|
32
|
+
session.judge.autoevals.closedQA("...") // (t.newSession() 的返回值)默认评这条 session 的对话文本
|
|
33
|
+
turn.judge.autoevals.closedQA("...") // (t.send() 的返回值)默认只评这一轮的 turn.message
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`t.judge` / `session.judge` 是 session 级,适合评整段对话的回答质量或跨轮一致性;`turn.judge` 是 turn 级,只看这一轮的消息——多轮 eval 里不同轮次需要不同评分标准时用它:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const turn1 = await t.send("这张图里有什么?"); // 第一轮:看图
|
|
40
|
+
const turn2 = await t.send("背景是什么颜色?"); // 第二、三轮:纯文字追问,考跨轮记忆
|
|
41
|
+
const turn3 = await t.send("中间那个形状是什么颜色的?");
|
|
42
|
+
|
|
43
|
+
t.judge.autoevals
|
|
44
|
+
.closedQA("助手是否始终基于第一轮的图片作答?")
|
|
45
|
+
.atLeast(0.7);
|
|
46
|
+
|
|
47
|
+
turn3.judge.autoevals.closedQA("这一轮是否回答了形状颜色?").gate();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
要评的不是对话本身——沙箱 diff、文件内容或其它材料——不管挂在哪个接收者上,都用 `{ on }` 显式传:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
t.judge.autoevals.closedQA("生成的代码是否是地道的 TypeScript?", {
|
|
54
|
+
on: t.sandbox.diff.get("src/handler.ts"),
|
|
55
|
+
}).atLeast(0.7);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## judge 模型解析优先级
|
|
59
|
+
|
|
60
|
+
评判模型按从具体到笼统解析:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
单次调用的 { model } 选项
|
|
64
|
+
↓
|
|
65
|
+
这个 eval 的 judge.model
|
|
66
|
+
↓
|
|
67
|
+
全局配置的 judge.model
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// niceeval.config.ts —— 全局默认
|
|
72
|
+
defineConfig({ judge: { model: "anthropic/claude-haiku-4-5" } });
|
|
73
|
+
|
|
74
|
+
// 某个需要更强模型的 eval
|
|
75
|
+
defineEval({
|
|
76
|
+
judge: { model: "anthropic/claude-opus-4-8" },
|
|
77
|
+
async test(t) {
|
|
78
|
+
t.judge.autoevals.factuality(reference); // 这个 eval 用 claude-opus-4-8
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 单次调用覆盖
|
|
83
|
+
t.judge.autoevals.closedQA("rubric", { on: t.reply, model: "openai/gpt-4o" });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
三级都没配时,最后还会读环境变量 `NICEEVAL_JUDGE_MODEL`;连它也没有就是配置错误,调用点直接报错——judge 没有内置默认模型。
|
|
87
|
+
|
|
88
|
+
## 评判端点与 key:OpenAI 兼容协议
|
|
89
|
+
|
|
90
|
+
评判模型走 **OpenAI 兼容的 `/chat/completions`** 协议。OpenAI 官方、DeepSeek,以及任何兼容这个协议的网关都能当评判端点,在 `judge` 配置里指定:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// niceeval.config.ts
|
|
94
|
+
defineConfig({
|
|
95
|
+
judge: {
|
|
96
|
+
model: "deepseek-chat",
|
|
97
|
+
baseUrl: "https://api.deepseek.com/v1", // 不配默认 OpenAI 官方端点
|
|
98
|
+
apiKeyEnv: "DEEPSEEK_API_KEY", // key 从这个环境变量读
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`baseUrl` 和 key 都按从具体到笼统解析,配置字段优先、环境变量兜底:
|
|
104
|
+
|
|
105
|
+
| 项 | 解析顺序 |
|
|
106
|
+
|---|---|
|
|
107
|
+
| 端点 | `judge.baseUrl` → `NICEEVAL_JUDGE_BASE` → `CODEX_BASE_URL` → `OPENAI_BASE_URL` → `https://api.openai.com/v1` |
|
|
108
|
+
| key | `judge.apiKeyEnv` 指定的环境变量 → `NICEEVAL_JUDGE_KEY` → `CODEX_API_KEY` → `OPENAI_API_KEY` |
|
|
109
|
+
|
|
110
|
+
<Warning>
|
|
111
|
+
key 一个都解析不到时,judge 断言**静默跳过**——不报错、不记分,eval 照常跑。全绿不代表 judge 真的评过。配完 key 先跑一条带 `t.judge` 的 eval,在 `niceeval view` 里确认出现了 judge 分数。
|
|
112
|
+
</Warning>
|
|
113
|
+
|
|
114
|
+
## 严重级:judge 默认 soft
|
|
115
|
+
|
|
116
|
+
judge 调用和其它断言一样是评分函数,遵守 [Assert · gate 与 soft 严重级](/zh/concepts/assert#gate-与-soft-严重级) 同一套机制——只是**默认值**和大多数值匹配器不同:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
t.judge.autoevals.closedQA("语气是否礼貌?"); // 不带阈值 → soft,纯记分,永不让 eval 失败
|
|
120
|
+
t.judge.autoevals.closedQA("语气是否礼貌?").atLeast(0.7); // soft + 阈值 → 只在 --strict 下才算失败
|
|
121
|
+
t.judge.autoevals.closedQA("语气是否礼貌?").gate(); // 提升为 gate → 低于阈值立刻失败
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
不调 `.atLeast()` 也不调 `.gate()` 时,裸的 judge 调用纯粹记一个质量分——会显示在 eval 的分数徽章上,但单独永远不会让运行失败。这是 judge 合理的默认值:一个概率模型在给另一个概率模型的输出打分,把每次没打满分都当硬失败会让整套 eval 变得脆弱、动不动就红。只有当被评的属性真的是硬底线时(比如"回复不能编造账户信息"),才用 `.gate()`。
|
|
125
|
+
|
|
126
|
+
## 相关阅读
|
|
127
|
+
|
|
128
|
+
- [Assert](/zh/concepts/assert) — gate / soft 严重级的完整规则,以及 judge 分数最终折进的判决规则。
|
|
129
|
+
- [Drive](/zh/concepts/drive) — `t.send()`、`t.newSession()`,以及 `t.judge` / `session.judge` / `turn.judge` 各自挂在哪个 handle 上。
|
|
130
|
+
- [Evals](/zh/concepts/evals) — judge 分数如何折进 eval 生命周期和 outcome 类型。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "NiceEval 架构:evals、agents 与 sandboxes"
|
|
3
|
+
sidebarTitle: "概览"
|
|
4
|
+
description: "理解 NiceEval、Adapter 和 Sandbox backend 如何配合,用统一 API 评估任意 AI agent。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[NiceEval](https://niceeval.com/) 的核心设计是把“评测逻辑”与“如何连接被测对象”分开。[NiceEval](https://niceeval.com/) 负责发现、调度、评分和报告;Adapter 负责调用被测系统;Sandbox backend 负责隔离文件系统。
|
|
8
|
+
|
|
9
|
+
## 四层架构
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Eval files / fixtures
|
|
13
|
+
↓
|
|
14
|
+
NiceEval
|
|
15
|
+
↓
|
|
16
|
+
Adapter
|
|
17
|
+
↓
|
|
18
|
+
Subject under test / Sandbox backend
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## NiceEval 负责什么
|
|
22
|
+
|
|
23
|
+
<CardGroup cols={2}>
|
|
24
|
+
<Card title="Eval 发现" icon="magnifying-glass">
|
|
25
|
+
发现 `*.eval.ts` 文件和 fixture 目录,并从路径推导稳定 ID。
|
|
26
|
+
</Card>
|
|
27
|
+
<Card title="并发调度" icon="timeline">
|
|
28
|
+
控制运行池大小、重试、attempt 和 early-exit。
|
|
29
|
+
</Card>
|
|
30
|
+
<Card title="断言与评分" icon="chart-bar">
|
|
31
|
+
收集 `t.check`、作用域断言、judge 分数和测试结果。
|
|
32
|
+
</Card>
|
|
33
|
+
<Card title="缓存" icon="database">
|
|
34
|
+
用 fingerprint 跳过已通过且输入未变的 case。
|
|
35
|
+
</Card>
|
|
36
|
+
<Card title="报告" icon="file-lines">
|
|
37
|
+
输出控制台、JSON、JUnit 等报告。
|
|
38
|
+
</Card>
|
|
39
|
+
<Card title="Artifacts" icon="folder-open">
|
|
40
|
+
保存 summary、event stream、transcript、diff 和测试输出。
|
|
41
|
+
</Card>
|
|
42
|
+
</CardGroup>
|
|
43
|
+
|
|
44
|
+
## Adapter
|
|
45
|
+
|
|
46
|
+
<Note>
|
|
47
|
+
`Agent` 是 [NiceEval](https://niceeval.com/) 看到的抽象;`Adapter` 是你写的具体实现。[NiceEval](https://niceeval.com/) 不知道你的 agent 私有协议、CLI 参数或鉴权方式。
|
|
48
|
+
</Note>
|
|
49
|
+
|
|
50
|
+
这条边界让 [NiceEval](https://niceeval.com/) 保持通用。评估你自己的 AI agent、Claude Code、Codex 或自定义 agent 时,runner 和 scorers 的逻辑不需要改变。
|
|
51
|
+
|
|
52
|
+
## Sandbox
|
|
53
|
+
|
|
54
|
+
<Tabs>
|
|
55
|
+
<Tab title="Docker">
|
|
56
|
+
本地容器后端,适合开发和 CI 中的 coding-agent eval。
|
|
57
|
+
</Tab>
|
|
58
|
+
<Tab title="Vercel Sandbox">
|
|
59
|
+
云端 sandbox 后端,适合更强隔离或更大的运行资源。
|
|
60
|
+
</Tab>
|
|
61
|
+
<Tab title="第三方后端">
|
|
62
|
+
只要实现 `Sandbox` 接口,就可以接入其他沙箱服务。
|
|
63
|
+
</Tab>
|
|
64
|
+
</Tabs>
|
|
65
|
+
|
|
66
|
+
## 关键术语
|
|
67
|
+
|
|
68
|
+
<AccordionGroup>
|
|
69
|
+
<Accordion title="Eval">
|
|
70
|
+
一个测试用例:描述、agent 引用和 `test(t)` 函数。
|
|
71
|
+
</Accordion>
|
|
72
|
+
<Accordion title="Agent">
|
|
73
|
+
[NiceEval](https://niceeval.com/) 通过名字调用的一条连接,负责返回标准 `Turn`。
|
|
74
|
+
</Accordion>
|
|
75
|
+
<Accordion title="Adapter">
|
|
76
|
+
`Agent` 的具体实现,知道如何调用你的 runtime 或 CLI。
|
|
77
|
+
</Accordion>
|
|
78
|
+
<Accordion title="Sandbox">
|
|
79
|
+
给 coding agent 使用的隔离运行环境。
|
|
80
|
+
</Accordion>
|
|
81
|
+
<Accordion title="Turn">
|
|
82
|
+
一次 `t.send()` 的不可变结果快照。
|
|
83
|
+
</Accordion>
|
|
84
|
+
<Accordion title="Artifact">
|
|
85
|
+
运行后落盘的结构化结果文件。
|
|
86
|
+
</Accordion>
|
|
87
|
+
<Accordion title="Experiment">
|
|
88
|
+
用矩阵方式比较多个 agent、model 或 flags 的运行配置。
|
|
89
|
+
</Accordion>
|
|
90
|
+
</AccordionGroup>
|
|
91
|
+
|
|
92
|
+
## 端到端流程
|
|
93
|
+
|
|
94
|
+
<Steps>
|
|
95
|
+
<Step title="Discovery">发现 eval 文件和 fixture。</Step>
|
|
96
|
+
<Step title="Scheduling">根据并发、缓存和 attempt 计划运行。</Step>
|
|
97
|
+
<Step title="Agent send">调用 adapter,让被测对象产出 `Turn`。</Step>
|
|
98
|
+
<Step title="Scoring">执行断言、judge 和测试。</Step>
|
|
99
|
+
<Step title="Outcome">把所有结果折叠为 `passed`、`failed`、`errored` 或 `skipped`。</Step>
|
|
100
|
+
<Step title="Reporting & artifacts">输出报告并保存结构化文件。</Step>
|
|
101
|
+
</Steps>
|
|
102
|
+
|
|
103
|
+
## 相关阅读
|
|
104
|
+
|
|
105
|
+
- [Evals](/zh/concepts/evals) — eval 是什么,以及生命周期细节。
|
|
106
|
+
- [Adapter](/zh/concepts/adapter) — 如何写 adapter,并在 experiment 中引用它。
|
|
107
|
+
- [Drive](/zh/concepts/drive) — `t.send()`、session 与 HITL:如何产出断言要读的 `Turn` 数据。
|
|
108
|
+
- [Assert](/zh/concepts/assert) — 断言词汇和判决规则。
|
|
109
|
+
- [Judge](/zh/concepts/judge) — LLM-as-judge,评开放式质量。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "接入 Tier:三档投入,三组能力"
|
|
3
|
+
sidebarTitle: "Tier"
|
|
4
|
+
description: "按「Adapter 接到哪里、额外拿到什么观测数据」接入分三档:Tier 1 只接 send,Tier 2 加 OTel,Tier 3 侵入改造暴露 experiment flags。每档买到什么、什么时候升级。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
接入 [NiceEval](https://niceeval.com/) 不是全有或全无的一次性工程。按「Adapter 接到哪里、额外拿到什么观测数据」,接入分三档——每一档在上一档之上加一种投入、换一组新能力,已写的 eval 全程复用,不用改。
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Tier 1:只接 send
|
|
12
|
+
|
|
13
|
+
应用代码一行不改,接入只有一个 Adapter 文件——`send` 对着用户前端本来就在用的那个接口收发。
|
|
14
|
+
|
|
15
|
+
**全套断言都在这一档**:文本断言与 judge、结构化输出校验、多轮与会话隔离、HITL 审批流、事件映射给到的工具断言,以及 `send` 返回 usage 带来的用量断言——判决依据全部来自 `send` 返回的 `Turn`,后面两档不会再多出任何断言。应用接口本身暴露模型选择的话,**模型对比** experiment 也在这一档——experiment 的 `model` 经 `ctx.model` 传给 Adapter,随请求转发即可。
|
|
16
|
+
|
|
17
|
+
大多数团队从这里开始,很多也停在这里就够了。
|
|
18
|
+
|
|
19
|
+
## Tier 2:send + OTel
|
|
20
|
+
|
|
21
|
+
还是同一个 `send`、同一套事件映射,只是让应用把 OTel span 也发给 [NiceEval](https://niceeval.com/) 一份。应用已埋点(AI SDK telemetry、LangGraph、OpenLLMetry / OpenInference、自埋 gen_ai)就零改动;没埋点补的是一段通用 OTel 初始化——这属于可观测性建设,不是为 eval 定制的改造。
|
|
22
|
+
|
|
23
|
+
这一档买到的是**观测**:`niceeval view` 的调用瀑布图——应用内部每次模型调用、每次工具执行、各自的耗时与 token,按轮铺成时间线。断言不受影响:span 只进瀑布图,不进事件流、不喂断言。走法见[OTel 接入](/zh/guides/connect-otel)。
|
|
24
|
+
|
|
25
|
+
## Tier 3:侵入改造 + experiment flags
|
|
26
|
+
|
|
27
|
+
要评「改 prompt / 换工具集 / 开关某个 feature 谁更好」这类 **feature A/B test** 时,对照点在应用内部,前两档拿不到。这一档改应用内部代码,把变体暴露成外部可选的配置:experiment 的 `flags` 经 `ctx.flags` 透传给 Adapter,Adapter 随请求交给应用(HTTP header、请求体字段、环境变量都行),应用按 flag 切换变体。
|
|
28
|
+
|
|
29
|
+
注意侵入的是应用(把变体暴露成配置),不是接入面——Adapter 本身不变,依然只经对外接口调用应用。
|
|
30
|
+
|
|
31
|
+
## 无侵入是前两档的底线
|
|
32
|
+
|
|
33
|
+
前两档都是**无侵入**的:应用由你按它自己的方式启动(`pnpm start`、部署在哪都行),eval 侧不 spawn 应用进程、不另开端口——Adapter 只对着**用户前端本来就在用的那个接口**(HTTP 端点、SSE 流)收发,连不上时报"先起应用"的明确错误。两档的区别只在观测数据:Tier 1 只有 `send` 返回的事件,Tier 2 多一份发给 [NiceEval](https://niceeval.com/) 的 span、瀑布图随之出现。
|
|
34
|
+
|
|
35
|
+
## 怎么升级
|
|
36
|
+
|
|
37
|
+
三档递进不互斥:先用 Tier 1 跑通基线和模型对比;要调用瀑布图,升 Tier 2;要对照应用内部变体,再升 Tier 3。每次升级都只是给 Adapter 或应用加东西,experiment 侧怎么组织对比见[实验](/zh/concepts/experiment)。
|
|
38
|
+
|
|
39
|
+
## 相关阅读
|
|
40
|
+
|
|
41
|
+
- [接入你的 Agent](/zh/guides/connect-your-agent) — 接入全景:最小接入与参数通道。
|
|
42
|
+
- [Adapter](/zh/concepts/adapter) — 契约本身:`send` 传入什么返回什么,`ctx.model` / `ctx.telemetry` / `ctx.flags` 按档位出现。
|
|
43
|
+
- [OTel 接入](/zh/guides/connect-otel) — Tier 2 的完整走法。
|
|
44
|
+
- [实验](/zh/concepts/experiment) — model / flags 对比在 experiment 侧怎么声明。
|