niceeval 0.6.0 → 0.6.1
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/dist/agents/types.d.ts +5 -1
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -1
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +3 -1
- package/dist/report/components.js +1 -1
- package/dist/report/compute.js +15 -33
- package/dist/report/format.d.ts +0 -6
- package/dist/report/format.js +1 -18
- package/dist/report/index.d.ts +2 -2
- package/dist/report/index.js +1 -1
- package/dist/report/locale.d.ts +13 -2
- package/dist/report/locale.js +26 -3
- package/dist/report/metrics.js +1 -1
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +3 -4
- package/dist/report/react/ExperimentList.d.ts +3 -2
- package/dist/report/react/ExperimentList.js +59 -7
- package/dist/report/react/MetricScatter.js +1 -5
- package/dist/report/react/fixtures.js +9 -4
- package/dist/report/react/format.d.ts +1 -1
- package/dist/report/react/format.js +1 -1
- package/dist/report/react/index.d.ts +1 -1
- package/dist/report/text/faces.js +71 -33
- package/dist/report/types.d.ts +3 -15
- package/docs-site/zh/concepts/adapter.mdx +2 -2
- package/docs-site/zh/concepts/assert.mdx +11 -10
- package/docs-site/zh/concepts/evals.mdx +7 -6
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/guides/agent-feedback-loop.mdx +9 -7
- package/docs-site/zh/guides/fixtures.mdx +3 -1
- package/docs-site/zh/guides/official-adapters.mdx +1 -1
- package/docs-site/zh/guides/publish-report.mdx +1 -1
- package/docs-site/zh/guides/report-components.mdx +13 -13
- package/docs-site/zh/guides/results-data.mdx +28 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +1 -0
- package/docs-site/zh/guides/sandbox-providers.mdx +1 -1
- package/docs-site/zh/guides/viewing-results.mdx +6 -5
- package/docs-site/zh/guides/write-send.mdx +2 -1
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-agent.mdx +3 -1
- package/docs-site/zh/reference/events.mdx +2 -2
- package/docs-site/zh/reference/expect.mdx +10 -5
- package/package.json +1 -1
- package/src/agents/types.ts +5 -1
- package/src/i18n/en.ts +1 -2
- package/src/i18n/zh-CN.ts +1 -1
- package/src/report/built-in-user-parity.test.tsx +44 -47
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +1 -1
- package/src/report/components.tsx +3 -1
- package/src/report/compute.ts +12 -36
- package/src/report/dual-render.test.tsx +33 -29
- package/src/report/format.ts +1 -16
- package/src/report/index.ts +1 -2
- package/src/report/locale.ts +26 -3
- package/src/report/metrics.ts +1 -1
- package/src/report/react/AttemptList.tsx +4 -6
- package/src/report/react/ExperimentList.tsx +167 -55
- package/src/report/react/MetricScatter.tsx +1 -11
- package/src/report/react/enhance.js +39 -0
- package/src/report/react/fixtures.ts +10 -4
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +0 -1
- package/src/report/react/render.test.tsx +24 -8
- package/src/report/react/styles.css +40 -13
- package/src/report/report.test.ts +2 -7
- package/src/report/text/faces.ts +76 -36
- package/src/report/types.ts +3 -15
- package/src/results/host-equivalence.test.ts +3 -3
- package/src/show/index.ts +8 -15
- package/src/show/render.ts +7 -204
- package/src/show/show.test.ts +22 -23
- package/src/view/data.test.ts +1 -1
- package/src/view/data.ts +6 -6
- package/src/view/index.ts +1 -1
- package/src/view/shared/types.ts +1 -1
- package/src/view/view-report.test.ts +5 -5
- package/dist/o11y/execution-tree.d.ts +0 -103
- package/dist/o11y/otlp/select.d.ts +0 -22
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
- package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
- package/dist/results/annotated-source.d.ts +0 -61
- package/dist/results/attempt-evidence.d.ts +0 -69
- package/dist/results/attempt-source.d.ts +0 -15
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
|
|
5
5
|
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典,
|
|
6
6
|
// 默认 en 与历史输出逐字一致;数据(display、键、warnings message)不本地化。
|
|
7
|
-
import { attemptItemReason,
|
|
7
|
+
import { attemptItemReason, formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD, verdictMark, } from "../format.js";
|
|
8
8
|
import { countText, localeText, resolveMetricLabel } from "../locale.js";
|
|
9
9
|
import { indentBlock, padDisplay, textBar, wrapDisplay } from "./layout.js";
|
|
10
10
|
import { renderTableText } from "./table.js";
|
|
@@ -290,10 +290,6 @@ export function scatterText(data, ctx) {
|
|
|
290
290
|
if (drawable.length === 0) {
|
|
291
291
|
return [localeText(locale, "scatter.noData", axes), ...footnotes].join("\n");
|
|
292
292
|
}
|
|
293
|
-
// 恰好 1 个可画点:成本 × 通过率的比较至少要两个实验,单点不成图。
|
|
294
|
-
if (drawable.length === 1) {
|
|
295
|
-
return [localeText(locale, "scatter.needTwo", axes), ...footnotes].join("\n");
|
|
296
|
-
}
|
|
297
293
|
// 点太密排不下时降级为坐标表,不硬挤
|
|
298
294
|
if (drawable.length > POINT_MARKS.length || ctx.width < 44) {
|
|
299
295
|
const table = renderCoordinateTable(drawable.map((r) => ({ key: r.key, x: r.x.display, y: r.y.display })), { key: data.points, x: axisLabel(data.x, locale), y: axisLabel(data.y, locale) });
|
|
@@ -400,41 +396,84 @@ export function deltaText(data, ctx) {
|
|
|
400
396
|
}
|
|
401
397
|
// ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
|
|
402
398
|
//
|
|
403
|
-
// 三面共用的紧凑标记:`locator
|
|
404
|
-
// 中间不留空格)——docs-site/zh/guides/report-components.mdx「终端输出形成反馈闭环」定的形态。
|
|
399
|
+
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
405
400
|
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的原因/耗时摘要,不重复整段
|
|
406
401
|
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`——
|
|
407
402
|
// 命令模板只在 AttemptList(叶子层)展示完整断言明细时才值得,不在中间层重复。
|
|
408
403
|
function locatorBadge(item) {
|
|
409
|
-
return `${item.locator}${verdictMark(item.verdict)}
|
|
404
|
+
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
410
405
|
}
|
|
411
406
|
// ── ExperimentList ──
|
|
412
|
-
function
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
: (
|
|
419
|
-
|
|
407
|
+
function experimentSummaryTable(items, ctx) {
|
|
408
|
+
const locale = ctx.locale;
|
|
409
|
+
const compact = ctx.width < 100;
|
|
410
|
+
const columns = [
|
|
411
|
+
{ key: "experiment", header: compact && locale === "en" ? "Exp." : localeText(locale, "experimentList.experiment") },
|
|
412
|
+
{ key: "model", header: localeText(locale, "table.model") },
|
|
413
|
+
{ key: "agent", header: localeText(locale, "table.agent") },
|
|
414
|
+
{ key: "duration", header: compact && locale === "en" ? "Avg" : localeText(locale, "experimentList.avgDuration"), align: "right" },
|
|
415
|
+
{ key: "passRate", header: compact && locale === "en" ? "Pass" : localeText(locale, "experimentList.passRate"), align: "right" },
|
|
416
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
417
|
+
{ key: "tokens", header: localeText(locale, "experimentList.tokens"), align: "right" },
|
|
418
|
+
{ key: "cost", header: compact && locale === "en" ? "Cost" : localeText(locale, "experimentList.estimatedCost"), align: "right" },
|
|
419
|
+
];
|
|
420
|
+
const rows = items.map((item) => ({
|
|
421
|
+
key: item.experimentId,
|
|
422
|
+
cells: {
|
|
423
|
+
experiment: item.experimentId,
|
|
424
|
+
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
425
|
+
agent: item.agent,
|
|
426
|
+
duration: cellText(item.duration),
|
|
427
|
+
passRate: cellText(item.passRate),
|
|
428
|
+
result: verdictTallyText(item.verdicts, locale),
|
|
429
|
+
tokens: cellText(item.tokens),
|
|
430
|
+
cost: cellText(item.cost),
|
|
431
|
+
},
|
|
432
|
+
}));
|
|
433
|
+
const metadata = items.flatMap((item) => wrapDisplay(`${item.experimentId}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`, Math.max(8, ctx.width - 2)).map((line) => ` ${line}`));
|
|
434
|
+
return [renderTableText({ columns, rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
420
435
|
}
|
|
421
|
-
|
|
436
|
+
function experimentDetailTable(item, ctx) {
|
|
422
437
|
const locale = ctx.locale;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
438
|
+
const columns = [
|
|
439
|
+
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
440
|
+
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
441
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
442
|
+
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
443
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
444
|
+
];
|
|
445
|
+
const rows = item.evalRows.flatMap((row) => {
|
|
446
|
+
const parent = {
|
|
447
|
+
key: row.evalId,
|
|
448
|
+
cells: {
|
|
449
|
+
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
450
|
+
entity: row.evalId,
|
|
451
|
+
result: "",
|
|
452
|
+
duration: "",
|
|
453
|
+
cost: "",
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
const attempts = row.attempts.map((attempt, index) => ({
|
|
457
|
+
key: attempt.locator,
|
|
458
|
+
cells: {
|
|
459
|
+
status: ` ${verdictMark(attempt.verdict)}`,
|
|
460
|
+
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
461
|
+
result: attemptItemReason(attempt) ?? MISSING_MARK,
|
|
462
|
+
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
463
|
+
cost: attempt.costUSD === undefined ? null : formatUSD(attempt.costUSD),
|
|
464
|
+
},
|
|
465
|
+
}));
|
|
466
|
+
return [parent, ...attempts];
|
|
436
467
|
});
|
|
437
|
-
|
|
468
|
+
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
469
|
+
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags).map(([key, value]) => `${key}=${String(value)}`).join(" · ")}`
|
|
470
|
+
: undefined;
|
|
471
|
+
return [item.experimentId, flags, renderTableText({ columns, rows, locale }, ctx)].filter(Boolean).join("\n");
|
|
472
|
+
}
|
|
473
|
+
export function experimentListText(items, ctx) {
|
|
474
|
+
if (items.length === 0)
|
|
475
|
+
return localeText(ctx.locale, "attemptList.empty");
|
|
476
|
+
return [experimentSummaryTable(items, ctx), ...items.map((item) => experimentDetailTable(item, ctx))].join("\n\n");
|
|
438
477
|
}
|
|
439
478
|
// ── EvalList ──
|
|
440
479
|
function evalListAttemptLine(item) {
|
|
@@ -459,7 +498,7 @@ export function evalListText(items, ctx) {
|
|
|
459
498
|
return blocks.join("\n\n");
|
|
460
499
|
}
|
|
461
500
|
// ── AttemptList ──
|
|
462
|
-
/** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 +
|
|
501
|
+
/** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 + 耗时/成本,
|
|
463
502
|
* 然后逐条断言(gate 与 soft 都列,与 web 面的 AttemptRow 同一份材料)。 */
|
|
464
503
|
function attemptListItemText(item, ctx, locale) {
|
|
465
504
|
const head = [
|
|
@@ -468,7 +507,6 @@ function attemptListItemText(item, ctx, locale) {
|
|
|
468
507
|
item.experimentId,
|
|
469
508
|
formatDurationMs(item.durationMs),
|
|
470
509
|
...(item.costUSD !== undefined ? [formatUSD(item.costUSD)] : []),
|
|
471
|
-
...(capabilityBadge(item.capabilities) ? [capabilityBadge(item.capabilities)] : []),
|
|
472
510
|
].join(" · ");
|
|
473
511
|
const lines = [head];
|
|
474
512
|
if (item.error) {
|
package/dist/report/types.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { AttemptHandle, SelectionWarning } from "../results/types.ts";
|
|
2
2
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
3
|
-
import type { AttemptEvidenceCapabilities } from "../results/attempt-evidence.ts";
|
|
4
3
|
import type { AssertionResult, Verdict } from "../types.ts";
|
|
5
4
|
import type { LocalizedLabel, ReportLocale } from "./locale.ts";
|
|
6
5
|
export type { SelectionWarning };
|
|
7
|
-
export type { AttemptLocator
|
|
6
|
+
export type { AttemptLocator };
|
|
8
7
|
export type { LocalizedLabel, ReportLocale };
|
|
9
8
|
/** 两级聚合里单级的折叠方式。 */
|
|
10
9
|
export type Aggregator = "mean" | "sum" | "min" | "max" | ((values: number[]) => number);
|
|
@@ -325,10 +324,8 @@ export interface DeltaData<K extends string = string> {
|
|
|
325
324
|
}[];
|
|
326
325
|
}
|
|
327
326
|
/**
|
|
328
|
-
* `AttemptList` 一项 = 一个 Attempt:身份、判定、断言、error
|
|
329
|
-
*
|
|
330
|
-
* 同一个类型,不重新发明四个布尔位的含义)。`ExperimentList` / `EvalList` 的下钻数组复用同一个
|
|
331
|
-
* 类型,不是各自的精简版。
|
|
327
|
+
* `AttemptList` 一项 = 一个 Attempt:身份、判定、断言、error、耗时、成本和 locator。
|
|
328
|
+
* `ExperimentList` / `EvalList` 的下钻数组复用同一个类型,不是各自的精简版。
|
|
332
329
|
*/
|
|
333
330
|
export interface AttemptListItem {
|
|
334
331
|
evalId: string;
|
|
@@ -341,15 +338,6 @@ export interface AttemptListItem {
|
|
|
341
338
|
durationMs: number;
|
|
342
339
|
costUSD?: number;
|
|
343
340
|
locator: AttemptLocator;
|
|
344
|
-
/**
|
|
345
|
-
* 证据能力标记:`eval`(运行时 Eval 源码已保存)、`execution`(执行事件非空)、
|
|
346
|
-
* `timing`(执行事件 + 这次运行接入过 OTel)、`diff`(工作区有文件改动)。逐位定义与
|
|
347
|
-
* `AttemptEvidence.capabilities` 完全一致——这里复用同一个类型,不重复声明四个布尔位的门槛。
|
|
348
|
-
* 计算函数从 `EvalResult` 上已有的 `hasEvents` / `hasSources` / `hasTrace` 摘要位与一次
|
|
349
|
-
* `attempt.diff()` 懒加载算出,不对每个 item 调用完整的 `loadAttemptEvidence`
|
|
350
|
-
* (那还会额外装配 Eval 源码标注与 ExecutionTree,这里只要四个布尔位)。
|
|
351
|
-
*/
|
|
352
|
-
capabilities: AttemptEvidenceCapabilities;
|
|
353
341
|
}
|
|
354
342
|
/**
|
|
355
343
|
* `ExperimentList` 一项里,一个 Eval 的展开行:折叠判定(`foldEvalVerdict`)、失败原因摘要
|
|
@@ -194,7 +194,7 @@ interface AgentContext {
|
|
|
194
194
|
readonly flags: Readonly<Record<string, unknown>>; // Tier 3 的 feature A/B:experiment.flags 透传;没配时是 {}
|
|
195
195
|
readonly experimentId?: string; // 路径推导的实验 id,与结果里的 experimentId 同源;不经 experiment 跑时是 undefined
|
|
196
196
|
|
|
197
|
-
readonly sandbox
|
|
197
|
+
readonly sandbox: Sandbox; // 非 sandbox 型 Agent 也会拿到一个 Sandbox(remote agent 是无操作的 stub)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
interface AgentSession {
|
|
@@ -239,7 +239,7 @@ interface Turn {
|
|
|
239
239
|
]
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
对象统共十种类型(`message`、`action.*`、`input.requested`……完整清单见[事件流参考](/zh/reference/events))。断言读的就是这个数组:`t.calledTool("get_weather")` 数 `action.called`,`t.reply` 取最后一条 assistant `message`。注意 `send` 每次只返回**本轮**的数组——跨轮拼成整条会话线是运行器的事,见下一节。多数情况下你也不手写这些对象:官方转换器的返回值就是一个填好 `events`、`usage`、`status` 的完整 `Turn`,怎么选转换器见[写 send](/zh/guides/write-send)。
|
|
243
243
|
|
|
244
244
|
`data` 不是"随便放点什么"的口袋,它只有一条规则:**要什么,由 eval 在 send 上用 schema 声明;声明了才有 data,拿到手就是声明的类型。**
|
|
245
245
|
|
|
@@ -52,25 +52,26 @@ t.maxTokens(80_000).atLeast(0.7); // 降为 soft 并带阈值
|
|
|
52
52
|
|
|
53
53
|
## 判定规则
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
所有断言收齐后,运行器按固定优先级取第一个成立项,折叠成一个结果:
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
否则 → passed
|
|
58
|
+
执行异常、超时或作者错误 → errored
|
|
59
|
+
任一 gate 断言失败,或 --strict 下 soft 低于阈值 → failed
|
|
60
|
+
显式调用了 t.skip(reason) → skipped
|
|
61
|
+
否则 → passed
|
|
63
62
|
```
|
|
64
63
|
|
|
64
|
+
`errored` 压过一切,因为执行证据已经不可信;`failed` 压过 `skipped`,避免 `t.skip()` 掩盖此前记录的硬失败。
|
|
65
|
+
|
|
65
66
|
<CardGroup cols={2}>
|
|
66
67
|
<Card title="passed" icon="circle-check" color="#22c55e">
|
|
67
|
-
没有错误,所有 gate 断言通过,所有 soft
|
|
68
|
+
没有错误,所有 gate 断言通过,所有 soft 断言都达到阈值(或未开 `--strict`)。
|
|
68
69
|
</Card>
|
|
69
70
|
<Card title="failed" icon="circle-xmark" color="#ef4444">
|
|
70
|
-
|
|
71
|
+
至少一个 gate 断言没通过,或 `--strict` 下有 soft 断言低于阈值。硬失败。
|
|
71
72
|
</Card>
|
|
72
|
-
<Card title="
|
|
73
|
-
|
|
73
|
+
<Card title="errored" icon="triangle-exclamation" color="#f59e0b">
|
|
74
|
+
执行异常、超时或作者错误,本次执行无法形成可信结论——不伪装成断言失败。
|
|
74
75
|
</Card>
|
|
75
76
|
<Card title="skipped" icon="forward" color="#6b7280">
|
|
76
77
|
调用了 `t.skip("reason")`。完全排除在通过率统计之外。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "NiceEval 中的 eval:生命周期、verdict 与文件"
|
|
3
3
|
sidebarTitle: "评估"
|
|
4
|
-
description: "eval
|
|
4
|
+
description: "eval 是一个测试用例:描述和 test 函数,agent-neutral。了解 eval 如何被发现、调度、评分和报告。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
一个 eval 是一个可运行的测试用例。它通常由一个 `*.eval.ts` 文件导出,通过 `defineEval` 声明。
|
|
@@ -28,9 +28,10 @@ export default defineEval({
|
|
|
28
28
|
| 字段 | 说明 |
|
|
29
29
|
|---|---|
|
|
30
30
|
| `description` | 给人看的描述,出现在报告里 |
|
|
31
|
-
| `agent` | 使用哪个 Adapter,可由配置或 CLI 覆盖 |
|
|
32
31
|
| `test(t)` | 交互和断言逻辑 |
|
|
33
32
|
|
|
33
|
+
eval 本身不声明用哪个 Agent——它默认保持 agent-neutral,同一个 eval 可以在不同 experiment 下跑不同 Agent。选哪个 Agent 是 experiment 的字段,不由 CLI 临时覆盖。
|
|
34
|
+
|
|
34
35
|
<Note>
|
|
35
36
|
不要手写 `id` 或 `name`。[NiceEval](https://niceeval.com/) 从文件路径推导 ID。
|
|
36
37
|
</Note>
|
|
@@ -73,13 +74,13 @@ npx niceeval exp local weather/brooklyn
|
|
|
73
74
|
|
|
74
75
|
<CardGroup cols={2}>
|
|
75
76
|
<Card title="passed" icon="circle-check" color="#22c55e">
|
|
76
|
-
所有 gate
|
|
77
|
+
所有 gate 断言通过(`--strict` 下 soft 断言也都达到阈值),并且没有执行错误。
|
|
77
78
|
</Card>
|
|
78
79
|
<Card title="failed" icon="circle-xmark" color="#ef4444">
|
|
79
|
-
至少一个 gate
|
|
80
|
+
至少一个 gate 断言失败,或 `--strict` 下有 soft 断言低于阈值。
|
|
80
81
|
</Card>
|
|
81
|
-
<Card title="
|
|
82
|
-
|
|
82
|
+
<Card title="errored" icon="triangle-exclamation" color="#f59e0b">
|
|
83
|
+
执行异常、超时或作者错误,本次执行无法形成可信结论。
|
|
83
84
|
</Card>
|
|
84
85
|
<Card title="skipped" icon="forward" color="#6b7280">
|
|
85
86
|
eval 主动跳过,通常通过 `t.skip(reason)`。
|
|
@@ -67,7 +67,7 @@ Subject under test / Sandbox backend
|
|
|
67
67
|
|
|
68
68
|
<AccordionGroup>
|
|
69
69
|
<Accordion title="Eval">
|
|
70
|
-
|
|
70
|
+
一个测试用例:描述和 `test(t)` 函数,agent-neutral——用哪个 Agent 由 experiment 决定。
|
|
71
71
|
</Accordion>
|
|
72
72
|
<Accordion title="Agent">
|
|
73
73
|
[NiceEval](https://niceeval.com/) 通过名字调用的一条连接,负责返回标准 `Turn`。
|
|
@@ -49,7 +49,7 @@ AI 通常按任务选择这些入口:
|
|
|
49
49
|
npx niceeval show @1k2m9qtr
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
第一条命令显示当前各实验的通过率、成本、耗时,以及每个 Eval 的紧凑 Attempt locator
|
|
52
|
+
第一条命令显示当前各实验的通过率、成本、耗时,以及每个 Eval 的紧凑 Attempt locator——locator 本身就是证据入口,不在列表里编码证据可用性。第二条命令直接打开选中的 Attempt,页面末尾的 `available:` 只列出这个 Attempt 实际可用的证据命令。
|
|
53
53
|
</Step>
|
|
54
54
|
<Step title="按问题读取证据">
|
|
55
55
|
```bash
|
|
@@ -68,7 +68,7 @@ AI 通常按任务选择这些入口:
|
|
|
68
68
|
eval source: evals/weather/brooklyn.eval.ts · sha256:8d47…
|
|
69
69
|
|
|
70
70
|
8 export default defineEval({
|
|
71
|
-
9
|
|
71
|
+
9 description: "Brooklyn weather query",
|
|
72
72
|
10 async test(t) {
|
|
73
73
|
11 const turn = await t.send("布鲁克林今天天气怎么样?");
|
|
74
74
|
12 ✗ turn.calledTool("get_weather");
|
|
@@ -88,7 +88,8 @@ AI 通常按任务选择这些入口:
|
|
|
88
88
|
received: 1
|
|
89
89
|
source: evals/memory/swelancer-manager-proposals.eval.ts:40:11
|
|
90
90
|
|
|
91
|
-
execution
|
|
91
|
+
execution: 3 events · 0 skill loads · 0 tool calls · 1 AI messages
|
|
92
|
+
timing: OTel spans recorded for this attempt — see --execution for per-step timing.
|
|
92
93
|
agent run ▕████████████████████▏ 41.2s
|
|
93
94
|
├─ inference ▕█████░░░░░░░░░░░░░░░▏ 10.1s · "布鲁克林今天大约 24°C,晴。"
|
|
94
95
|
├─ inference ▕░░░░░█████████░░░░░░▏ 18.3s
|
|
@@ -98,9 +99,10 @@ AI 通常按任务选择这些入口:
|
|
|
98
99
|
changes · diff unavailable
|
|
99
100
|
reason: this Attempt did not produce workspace file changes
|
|
100
101
|
|
|
101
|
-
evidence: Eval source [E] · execution [X] · OTel timing [⏱]
|
|
102
102
|
full eval source: …/weather/brooklyn/a2/eval-source.ts
|
|
103
|
-
|
|
103
|
+
available:
|
|
104
|
+
niceeval show @1k2m9qtr --eval
|
|
105
|
+
niceeval show @1k2m9qtr --execution
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
`--execution` 把 AI 消息、Skill load、工具调用和工具结果排成一棵执行树。它只展示 Agent 可理解的事件;没有关联到这些事件的 SDK / runtime span 不逐行输出,只报告省略数量并保留 `trace.json` 路径。下面的 Attempt 有 OTel,所以能关联的节点同时带相对时间与耗时:
|
|
@@ -155,7 +157,7 @@ AI 通常按任务选择这些入口:
|
|
|
155
157
|
full events: …/weather/brooklyn/a2/events.json
|
|
156
158
|
```
|
|
157
159
|
|
|
158
|
-
diff 是被测 Agent 在 Sandbox 工作区造成的文件变化,不是 Eval
|
|
160
|
+
diff 是被测 Agent 在 Sandbox 工作区造成的文件变化,不是 Eval 源码的新旧差异。只有 sandbox eval 才会收集到 diff——非 sandbox eval 或 agent 确实没碰任何文件时,attempt 页的 `available:` 列表会省略 `--diff`。默认先给文件级摘要,避免把大段补丁塞进 Agent 上下文;`--diff=<文件>` 再展开单个文件,原始 artifact 路径始终保留:
|
|
159
161
|
|
|
160
162
|
```text
|
|
161
163
|
$ niceeval show @1c3h6tbn --diff
|
|
@@ -215,7 +217,7 @@ AI 通常按任务选择这些入口:
|
|
|
215
217
|
|
|
216
218
|
```text
|
|
217
219
|
Failing:
|
|
218
|
-
✗ weather/brooklyn · @1k2m9qtr
|
|
220
|
+
✗ weather/brooklyn · @1k2m9qtr
|
|
219
221
|
gate calledTool("get_weather"): tool was never called
|
|
220
222
|
|
|
221
223
|
Results: 14 passed, 1 failed, 0 errored, 0 skipped
|
|
@@ -117,8 +117,10 @@ t.check(result.stdout.trim(), includes(/ok/));
|
|
|
117
117
|
也可以运行项目自己的 test/lint/build 脚本:
|
|
118
118
|
|
|
119
119
|
```ts
|
|
120
|
+
import { commandSucceeded } from "niceeval/expect";
|
|
121
|
+
|
|
120
122
|
const result = await t.sandbox.runCommand("npm", ["test"]);
|
|
121
|
-
t.check(result
|
|
123
|
+
t.check(result, commandSucceeded());
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
## 用 experiment 做对照组
|
|
@@ -14,7 +14,7 @@ description: "NiceEval 内置的 Sandbox 和非 Sandbox 适配器分别是什么
|
|
|
14
14
|
|
|
15
15
|
- **鉴权**:`ANTHROPIC_API_KEY`(工厂参数 `apiKey` 可覆盖),可选 `ANTHROPIC_BASE_URL`(工厂参数 `baseUrl`)。
|
|
16
16
|
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进沙箱里用户级的 `~/.claude.json`(顶层 `mcpServers` 字段)。
|
|
17
|
-
- **装 Skill**:`skills: SkillSpec[]`——本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进沙箱的 project 级 `.claude/skills/<name>/`,claude CLI 原生发现(原生 `Skill`
|
|
17
|
+
- **装 Skill**:`skills: SkillSpec[]`——本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进沙箱的 project 级 `.claude/skills/<name>/`,claude CLI 原生发现(原生 `Skill` 工具调用被 adapter 归一为 `skill.loaded` 事件,不重复记成工具调用;用 `t.loadedSkill()` 断言,不是 `t.calledTool("Skill", ...)`)。
|
|
18
18
|
- **装原生 Plugin**:`plugins: ClaudeCodePluginSpec[]`,每一项声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 claude-code,传不进 codex。
|
|
19
19
|
- **tracing**:claude CLI 的 beta 原生遥测(`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA`),span 只有结构和计时,细节见 [OTel 接入](/zh/guides/connect-otel)。
|
|
20
20
|
|
|
@@ -10,7 +10,7 @@ description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一
|
|
|
10
10
|
|
|
11
11
|
```gitignore
|
|
12
12
|
# .niceeval/ 提交进仓库:它是线上报告的数据源,CI 构建时直接从这里生成整站。
|
|
13
|
-
# 只排除 diff.json —— 单轮可达上百 MB
|
|
13
|
+
# 只排除 diff.json —— 单轮可达上百 MB,体积太大不适合进仓库;Attempt 详情仍会读取它,只是不随静态导出复制。
|
|
14
14
|
.niceeval/**/diff.json
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -32,7 +32,7 @@ description: "报告文件里能摆的全部官方双面组件:每个组件回
|
|
|
32
32
|
- **诚实渲染。** 缺数据渲染 `—` 不补 0;覆盖不全的格子带 `12/15` 角标;截断如实标注剩余数量与原始 artifact 路径。
|
|
33
33
|
- **排序与方向随指标的 `better`。** higher 的指标降序、lower 的升序,「好」的一头恒在上、在右上。
|
|
34
34
|
- **证据室深链。** 网页面的格子、点和条目深链到 Attempt 详情;终端面用同一 Attempt locator 交给 `niceeval show @<id>`。
|
|
35
|
-
- **终端输出形成反馈闭环。** 每个 Attempt 有一个以 `@` 开头的短 locator,例如 `@1k2m9qrs`。它唯一指向 experiment、结果快照、Eval 和 Attempt。`✓` / `✗` / `!` / `–` 是 passed / failed / errored / skipped
|
|
35
|
+
- **终端输出形成反馈闭环。** 每个 Attempt 有一个以 `@` 开头的短 locator,例如 `@1k2m9qrs`。它唯一指向 experiment、结果快照、Eval 和 Attempt。`✓` / `✗` / `!` / `–` 是 passed / failed / errored / skipped。列表不用字母缩写编码证据可用性——locator 本身就是证据入口;打开 attempt(`niceeval show @<locator>`)后再列出实际可用的证据命令。执行步骤统一包含消息、thinking、tool call/result 和 Skill load;OTel 只给这些步骤补时间,不另开一份输出。
|
|
36
36
|
- **两面同口径,网页面不依赖 JS 也完整。** 排序在计算时由 `sort` 定死,终端和网页看到同一份基准顺序;下钻是普通链接、展开折叠用 `<details>`。网页面另有一层浏览操作:点表头就地重排、榜单行过滤、图表点位悬停看数值——只影响眼前的视图,不改数据口径,刷新即回基准顺序;浏览器禁用 JS 时这些操作消失,内容一样不少(悬停信息退化为图内提示)。
|
|
37
37
|
|
|
38
38
|
## 排版原语
|
|
@@ -147,15 +147,15 @@ const experiments = await ExperimentList.data(selection);
|
|
|
147
147
|
```text
|
|
148
148
|
compare/bub-gpt-5.4 · bub · gpt-5.4
|
|
149
149
|
pass 87% · 13 passed / 1 failed / 1 errored · 42 attempts · 4m 12s · $0.42
|
|
150
|
-
✓ algebra/quadratic @12f9k3aq✓
|
|
151
|
-
✗ weather/brooklyn @1k2m9qrs✗
|
|
152
|
-
! fixtures/button @1c3h6twx!
|
|
150
|
+
✓ algebra/quadratic @12f9k3aq✓ @141bm7cx✓ 18s avg · $0.02 avg
|
|
151
|
+
✗ weather/brooklyn @1k2m9qrs✗ @1nx4dpqr✗ @19vq2jex✗ gate calledTool("get_weather")
|
|
152
|
+
! fixtures/button @1c3h6twx! command timed out after 120s
|
|
153
153
|
|
|
154
154
|
compare/codex-gpt-5.4 · codex · gpt-5.4
|
|
155
155
|
pass 80% · 12 passed / 3 failed · 45 attempts · 5m 03s · $0.51
|
|
156
|
-
✓ algebra/quadratic @1d8p4kwz✓
|
|
157
|
-
✗ algebra/matrix @1e2j7mxa✗
|
|
158
|
-
✗ weather/brooklyn @1h6t3vbe✗
|
|
156
|
+
✓ algebra/quadratic @1d8p4kwz✓ 16s · $0.02
|
|
157
|
+
✗ algebra/matrix @1e2j7mxa✗ @1f5r9nab✗ @1g4w8pcd✗ gate equals("42")
|
|
158
|
+
✗ weather/brooklyn @1h6t3vbe✗ @1j9m2qfg✗ @1k7c5rxh✗ gate calledTool("get_weather")
|
|
159
159
|
|
|
160
160
|
inspect: niceeval show @<id> [--eval|--execution|--diff]
|
|
161
161
|
```
|
|
@@ -181,13 +181,13 @@ const evals = await EvalList.data(selection);
|
|
|
181
181
|
```text
|
|
182
182
|
weather/brooklyn · compare/bub-gpt-5.4 · failed
|
|
183
183
|
score 0.20 · 3 attempts · 41s avg · $0.04 avg
|
|
184
|
-
@1k2m9qrs✗
|
|
185
|
-
@1nx4dpqr✗
|
|
186
|
-
@19vq2jex✗
|
|
184
|
+
@1k2m9qrs✗ · gate calledTool("get_weather") — tool was never called
|
|
185
|
+
@1nx4dpqr✗ · gate calledTool("get_weather") — tool was never called
|
|
186
|
+
@19vq2jex✗ · gate calledTool("get_weather") — tool was never called
|
|
187
187
|
|
|
188
188
|
fixtures/button · compare/bub-gpt-5.4 · errored
|
|
189
189
|
score — · 1 attempt · 2m 00s · $0.09
|
|
190
|
-
@1c3h6twx!
|
|
190
|
+
@1c3h6twx! · command timed out after 120s
|
|
191
191
|
|
|
192
192
|
inspect: niceeval show @<id> [--eval|--execution|--diff]
|
|
193
193
|
```
|
|
@@ -209,13 +209,13 @@ const attempts = await AttemptList.data(selection, { redact });
|
|
|
209
209
|
`niceeval show` 每项完整输出一个 Attempt,不再折叠到 Eval 汇总。这里已经是叶子层,只在末尾给一条与该 Attempt 可用证据对应的模板:
|
|
210
210
|
|
|
211
211
|
```text
|
|
212
|
-
✗ @1k2m9qrs · weather/brooklyn · compare/bub-gpt-5.4 · 41s · $0.04
|
|
212
|
+
✗ @1k2m9qrs · weather/brooklyn · compare/bub-gpt-5.4 · 41s · $0.04
|
|
213
213
|
gate calledTool("get_weather") · failed
|
|
214
214
|
tool was never called
|
|
215
215
|
soft judge("回答基于实时数据") · 0.2/1
|
|
216
216
|
reply invents a temperature
|
|
217
217
|
|
|
218
|
-
! @1c3h6twx · fixtures/button · compare/bub-gpt-5.4 · 2m 00s · $0.09
|
|
218
|
+
! @1c3h6twx · fixtures/button · compare/bub-gpt-5.4 · 2m 00s · $0.09
|
|
219
219
|
command timed out after 120s
|
|
220
220
|
inspect: niceeval show @<id> [--eval|--execution|--diff]
|
|
221
221
|
|
|
@@ -83,6 +83,31 @@ await attempt.sources(); // SourceArtifact[] | null
|
|
|
83
83
|
- **读不了的落盘不静默。** 版本不兼容、目录损坏的快照进 `skipped` 并带原因;要不要展示由你定,但缺口永远被算出来。
|
|
84
84
|
- **同进程内按句柄记忆化。** 两处都读同一个 `diff()` 不会把上百 MB 读两遍。
|
|
85
85
|
|
|
86
|
+
## 超大输出会被截断
|
|
87
|
+
|
|
88
|
+
Agent 跑一条命令,输出可以大得离谱——一次递归 `grep` 扫进 `node_modules`,撞上压缩过的 JS 文件,单行就有几 MB。这种输出会同时进 `events.json` 和 `trace.json`,不管的话一个 attempt 就能占上百 MB。
|
|
89
|
+
|
|
90
|
+
所以 NiceEval 落盘时会削:`events.json` 和 `trace.json` 里任何超过 256 KiB 的字符串,只保留前 256 KiB,末尾留一行说明:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
[niceeval] truncated 51467156 → 262144 bytes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**这不影响判定。** 断言读的是运行时的完整输出,截断只发生在写文件的那一刻——落盘的 artifact 是证据,不是评分的输入。断言该过还是过,该挂还是挂。
|
|
97
|
+
|
|
98
|
+
要在自己的报告里如实标出「这里少了东西」,别去匹配上面那行文本,读结构化字段:被截断的事件和 span 都带 `truncated`,里面是被截断的位置和原始字节数。
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const events = await attempt.events();
|
|
102
|
+
for (const e of events ?? []) {
|
|
103
|
+
for (const t of e.truncated ?? []) {
|
|
104
|
+
console.log(`${t.path} 原始 ${t.originalBytes} 字节,已截断`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`diff.json` 不截断——里面每个文件都要完整才能用来核对 agent 改了什么。它是唯一可能拖到上百 MB 的 artifact,所以发布时通常不带它(见下面的 `copySnapshots`)。
|
|
110
|
+
|
|
86
111
|
## 版本:谁写的、读不读得了
|
|
87
112
|
|
|
88
113
|
每个快照都带自己的出身:`producer` 是写这份结果的工具与版本(niceeval 自己,或经写入面转换的第三方 harness),`schemaVersion` 是磁盘格式版本。格式只在破坏兼容时递增版本,读取器只认相同版本——**版本不兼容的落盘不解析、不迁移、不猜**,整个 run 进 `skipped`:
|
|
@@ -205,8 +230,9 @@ import { openResults, copySnapshots } from "niceeval/results";
|
|
|
205
230
|
|
|
206
231
|
const results = await openResults(".niceeval");
|
|
207
232
|
await copySnapshots(results.latest(), "site-data/run", {
|
|
208
|
-
artifacts: ["sources", "events", "trace", "o11y"], // diff
|
|
209
|
-
}); //
|
|
233
|
+
artifacts: ["sources", "events", "trace", "o11y"], // diff 不截断、可达百 MB,发布时常见地不带;
|
|
234
|
+
}); // events / trace 有 256 KiB 上限,带上是安全的;
|
|
235
|
+
// o11y 只有几 KB,报告用到 turns 这类
|
|
210
236
|
// 读 o11y 的指标就把它带上,不然渲染成「—」
|
|
211
237
|
```
|
|
212
238
|
|
|
@@ -88,6 +88,7 @@ export default defineExperiment({
|
|
|
88
88
|
createSandbox
|
|
89
89
|
→ sandbox spec 的 .setup() 钩子? # 环境准备(按实验装东西);没挂就跳过
|
|
90
90
|
→ git init && git commit
|
|
91
|
+
→ eval 的 setup? # 这条 eval 的任务夹具(如果定义了)
|
|
91
92
|
→ adapter.setup? # 装 CLI / 写 agent 配置
|
|
92
93
|
→ test(t): uploadDirectory(...) # 写入这条 eval 的起始文件
|
|
93
94
|
→ adapter.send(input, ctx)
|
|
@@ -96,7 +96,7 @@ Vercel Sandbox 适合需要云端隔离、更多资源或更稳定环境的任
|
|
|
96
96
|
|
|
97
97
|
- 减少 fixture 依赖体积。
|
|
98
98
|
- 使用缓存或预热机制。
|
|
99
|
-
- 控制 `
|
|
99
|
+
- 控制 `maxConcurrency`(experiment 字段或 `--max-concurrency`),避免本地 Docker 资源耗尽。
|
|
100
100
|
- 把慢测试拆成必要的 gate 和可选的 soft 检查。
|
|
101
101
|
|
|
102
102
|
Warm pools 和复用属于 runner / scheduler 层面的能力,详见 [Runner](/zh/guides/runner)。
|
|
@@ -91,8 +91,8 @@ niceeval show weather/brooklyn --history # 跨 run 时间轴:抖动与回
|
|
|
91
91
|
$ niceeval show weather/brooklyn
|
|
92
92
|
weather/brooklyn — 布鲁克林天气查询
|
|
93
93
|
|
|
94
|
-
compare/bub-gpt-5.4 ✓ passed 1 attempt 38.0s $0.03 (2h ago) @1xqur9kx
|
|
95
|
-
compare/codex-gpt-5.4 ✗ failed 3 attempts 41.2s $0.12 (40s ago) @1k2m9qrs
|
|
94
|
+
compare/bub-gpt-5.4 ✓ passed 1 attempt 38.0s $0.03 (2h ago) @1xqur9kx
|
|
95
|
+
compare/codex-gpt-5.4 ✗ failed 3 attempts 41.2s $0.12 (40s ago) @1k2m9qrs gate calledTool("get_weather")
|
|
96
96
|
|
|
97
97
|
attempt 3 · compare/codex-gpt-5.4 · failed · 41.2s · 12.3k tokens · $0.04
|
|
98
98
|
✗ gate calledTool("get_weather") — tool was never called
|
|
@@ -101,7 +101,7 @@ attempt 3 · compare/codex-gpt-5.4 · failed · 41.2s · 12.3k tokens · $0.04
|
|
|
101
101
|
|
|
102
102
|
artifacts: .niceeval/compare_codex-gpt-5.4/2026-07-09T10-00-00-000Z-x1f2/weather/brooklyn/a2/
|
|
103
103
|
attempt locator: @1k2m9qrs
|
|
104
|
-
next: niceeval show @1k2m9qrs [--eval|--execution
|
|
104
|
+
next: niceeval show @1k2m9qrs [--eval|--execution]
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
每个 experiment 一行的紧凑索引末尾就带着代表 attempt 的 `@<locator>`(`compare/bub-gpt-5.4` 只有一次 attempt 就代表它自己,`compare/codex-gpt-5.4` 代表的是默认展开的那一次失败);展开的明细块结尾再重复一次精确的 `attempt locator:`,两处给的是同一个值。想看别的 attempt(比如 `compare/codex-gpt-5.4` 更早失败的那次),去 `--history` 或 artifact 目录里找到它的 locator,`niceeval show @<locator>` 直接跳过去,不需要先回到这张列表。
|
|
@@ -130,9 +130,10 @@ timing: OTel spans recorded for this attempt — see --execution for per-step ti
|
|
|
130
130
|
|
|
131
131
|
changes: diff unavailable · no workspace diff was recorded for this attempt
|
|
132
132
|
|
|
133
|
-
evidence: eval source [E] · execution [X] · OTel timing [⏱]
|
|
134
133
|
artifacts: .niceeval/compare_codex-gpt-5.4/2026-07-09T10-00-00-000Z-x1f2/weather/brooklyn/a2/
|
|
135
|
-
|
|
134
|
+
available:
|
|
135
|
+
niceeval show @1k2m9qrs --eval
|
|
136
|
+
niceeval show @1k2m9qrs --execution
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
这份全景只给摘要,不复现某个切面的完整内容——完整源码、完整事件流或完整文件列表分别要对应的证据 flag 才给。`changes` 一行永远说明 diff 不可用的具体原因:这里是「这次 Attempt 从未收集过 diff」(`weather/brooklyn` 不是 sandbox eval,压根不会有工作区改动可收集);如果是 sandbox eval 但 agent 确实没碰任何文件,会是另一句「没有产生工作区文件改动」;两种「不可用」原因不同,不共用一句含糊的提示。
|
|
@@ -137,7 +137,7 @@ export default defineAgent({
|
|
|
137
137
|
|
|
138
138
|
## 第四步:把工具解析成事件
|
|
139
139
|
|
|
140
|
-
应用的返回里不只有回复文本——Chat Completions 形返回的 `tool_calls` 记录了这轮调过什么工具。Adapter 最重要的工作就是**把接口的返回归一成标准事件流**:本轮发生的每件事一个对象,按真实发生顺序排进 `Turn.events
|
|
140
|
+
应用的返回里不只有回复文本——Chat Completions 形返回的 `tool_calls` 记录了这轮调过什么工具。Adapter 最重要的工作就是**把接口的返回归一成标准事件流**:本轮发生的每件事一个对象,按真实发生顺序排进 `Turn.events`,对象是下面十种类型之一(各字段的实际值,[契约页有一轮的完整示例](/zh/concepts/adapter)):
|
|
141
141
|
|
|
142
142
|
```ts
|
|
143
143
|
type StreamEvent =
|
|
@@ -145,6 +145,7 @@ type StreamEvent =
|
|
|
145
145
|
| { type: "action.called"; callId: string; name: string; input: JsonValue; tool?: ToolName }
|
|
146
146
|
| { type: "action.result"; callId: string; output?: JsonValue;
|
|
147
147
|
status: "completed" | "failed" | "rejected" }
|
|
148
|
+
| { type: "skill.loaded"; skill: string; callId?: string }
|
|
148
149
|
| { type: "subagent.called"; callId: string; name: string; remoteUrl?: string }
|
|
149
150
|
| { type: "subagent.completed"; callId: string; output?: JsonValue;
|
|
150
151
|
status: "completed" | "failed" }
|
|
@@ -25,7 +25,7 @@ description: "NiceEval CLI 参考:exp、show、view、init、list 和 clean
|
|
|
25
25
|
在终端读取结果;用 `@<locator>` 或 eval ID 前缀下钻断言、Eval 源码、执行事件流、diff 和历史。
|
|
26
26
|
</Card>
|
|
27
27
|
<Card title="npx niceeval view" icon="eye">
|
|
28
|
-
打开网页结果查看器,读默认报告并交互浏览 Eval
|
|
28
|
+
打开网页结果查看器,读默认报告并交互浏览 Eval 源码、执行事件流与 diff;默认范围与 `show` 相同。跨 run 时间轴(`--history`)是 `show` 专用的终端能力。
|
|
29
29
|
</Card>
|
|
30
30
|
</CardGroup>
|
|
31
31
|
|
|
@@ -138,9 +138,9 @@ npx niceeval show fixtures/button --diff
|
|
|
138
138
|
npx niceeval show weather/brooklyn --history
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
`show` 是终端结果入口,适合人直接阅读,也适合 coding agent 在上下文窗口里逐级下钻。位置参数选「看哪些 eval」(ID 前缀)或直接用 `@<locator>` 精确选一个 attempt
|
|
141
|
+
`show` 是终端结果入口,适合人直接阅读,也适合 coding agent 在上下文窗口里逐级下钻。位置参数选「看哪些 eval」(ID 前缀)或直接用 `@<locator>` 精确选一个 attempt;不带位置参数时显示同一张紧凑 attempt 表,指定 eval ID 前缀只是收窄这张表的行数,不会切换成另一种视图。
|
|
142
142
|
|
|
143
|
-
`@<locator>` 不带证据 flag 时给出该 attempt 的紧凑全景(断言摘要、执行摘要、可选 OTel 时间、diff 摘要);`--eval`、`--execution`、`--diff` 是同一 attempt 的证据切面,分别展开运行时保存的 Eval
|
|
143
|
+
`@<locator>` 不带证据 flag 时给出该 attempt 的紧凑全景(断言摘要、执行摘要、可选 OTel 时间、diff 摘要);`--eval`、`--execution`、`--diff` 是同一 attempt 的证据切面,分别展开运行时保存的 Eval 源码、标准执行事件流、工作区文件改动,因此必须搭配 `@<locator>` 精确指名一个 attempt。`--run` 钉住某次结果目录,`--history` 查看跨 run 趋势。完整的阅读顺序、输出示例和 artifact 说明见[查看结果](/zh/guides/viewing-results)。
|
|
144
144
|
|
|
145
145
|
## `--early-exit` 与 `--strict`
|
|
146
146
|
|
|
@@ -245,7 +245,9 @@ experiment 跑(如脱离 CLI、直接构造 `AgentRun` 的场景)时为 undefine
|
|
|
245
245
|
readonly sandbox: Sandbox;
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
所有 agent 都有:沙箱型是运行器按项目/experiment 配置备好的真实沙箱句柄,remote 型是
|
|
249
|
+
`createRemoteSandbox()` 产出的 stub(仅含 `workdir`/`sandboxId`/`otlpHost`/`stop` 等
|
|
250
|
+
元信息,其余方法调用即抛错)。
|
|
249
251
|
|
|
250
252
|
#### `session`
|
|
251
253
|
|