niceeval 0.8.1 → 0.9.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/INDEX.md +77 -45
- package/dist/agents/types.d.ts +28 -6
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts +3 -1
- package/dist/report/built-in/index.d.ts +3 -2
- package/dist/report/built-in/index.js +7 -8
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/built-in/standard.js +30 -0
- package/dist/report/components.d.ts +69 -2
- package/dist/report/components.js +152 -3
- package/dist/report/compute.d.ts +28 -1
- package/dist/report/compute.js +123 -0
- package/dist/report/index.d.ts +4 -4
- package/dist/report/index.js +3 -2
- package/dist/report/locale.d.ts +39 -1
- package/dist/report/locale.js +69 -0
- package/dist/report/react/AttemptList.d.ts +3 -1
- package/dist/report/react/AttemptList.js +3 -3
- package/dist/report/react/CopyFixPrompt.d.ts +12 -0
- package/dist/report/react/CopyFixPrompt.js +12 -0
- package/dist/report/react/HeroCard.d.ts +13 -0
- package/dist/report/react/HeroCard.js +35 -0
- package/dist/report/react/PoweredBy.d.ts +5 -0
- package/dist/report/react/PoweredBy.js +7 -0
- package/dist/report/react/ScopeWarnings.d.ts +12 -0
- package/dist/report/react/ScopeWarnings.js +18 -0
- package/dist/report/react/TraceWaterfall.d.ts +14 -0
- package/dist/report/react/TraceWaterfall.js +22 -0
- package/dist/report/react/index.d.ts +6 -1
- package/dist/report/react/index.js +6 -0
- package/dist/report/report.d.ts +22 -6
- package/dist/report/report.js +68 -55
- package/dist/report/scope-warnings.d.ts +28 -0
- package/dist/report/scope-warnings.js +101 -0
- package/dist/report/text/faces.d.ts +19 -1
- package/dist/report/text/faces.js +61 -0
- package/dist/report/tree.d.ts +1 -1
- package/dist/report/tree.js +7 -2
- package/dist/report/types.d.ts +45 -0
- package/dist/report/web.d.ts +5 -4
- package/dist/report/web.js +7 -22
- package/dist/results/select.d.ts +17 -4
- package/dist/results/select.js +76 -11
- package/dist/results/types.d.ts +26 -0
- package/dist/runner/fingerprint.d.ts +3 -3
- package/dist/runner/sandbox-selection.d.ts +12 -0
- package/dist/runner/types.d.ts +18 -6
- package/dist/sandbox/types.d.ts +12 -0
- package/dist/shared/aggregate.d.ts +1 -1
- package/dist/shared/aggregate.js +1 -1
- package/docs-site/zh/explanation/evals.mdx +2 -1
- package/docs-site/zh/explanation/experiment.mdx +2 -0
- package/docs-site/zh/how-to/custom-reports.mdx +23 -7
- package/docs-site/zh/how-to/experiments.mdx +2 -2
- package/docs-site/zh/how-to/publish-report.mdx +11 -4
- package/docs-site/zh/how-to/viewing-results.mdx +17 -8
- package/docs-site/zh/how-to/write-experiment.mdx +40 -1
- package/docs-site/zh/reference/builtin-agents.mdx +40 -3
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-eval.mdx +8 -0
- package/docs-site/zh/reference/official-adapters.mdx +10 -5
- package/docs-site/zh/troubleshooting/debugging.mdx +3 -3
- package/package.json +2 -1
- package/src/agents/bub.ts +13 -1
- package/src/agents/claude-code.test.ts +43 -1
- package/src/agents/claude-code.ts +32 -14
- package/src/agents/codex.test.ts +168 -1
- package/src/agents/codex.ts +51 -15
- package/src/agents/mcp.ts +31 -0
- package/src/agents/post-setup.ts +33 -0
- package/src/agents/types.ts +28 -7
- package/src/cli.ts +15 -14
- package/src/define.ts +3 -0
- package/src/i18n/en.ts +8 -5
- package/src/i18n/zh-CN.ts +8 -4
- package/src/index.ts +1 -0
- package/src/report/built-in/index.tsx +8 -7
- package/src/report/built-in/standard.tsx +59 -0
- package/src/report/components.tsx +218 -2
- package/src/report/compute.ts +138 -1
- package/src/report/dual-render.test.tsx +141 -14
- package/src/report/index.ts +20 -0
- package/src/report/locale.ts +83 -1
- package/src/report/react/AttemptList.tsx +13 -1
- package/src/report/react/CopyFixPrompt.tsx +37 -0
- package/src/report/react/HeroCard.tsx +59 -0
- package/src/report/react/PoweredBy.tsx +20 -0
- package/src/report/react/ScopeWarnings.tsx +74 -0
- package/src/report/react/TraceWaterfall.tsx +78 -0
- package/src/report/react/enhance.js +14 -0
- package/src/report/react/index.tsx +11 -0
- package/src/report/react/styles.css +193 -7
- package/src/report/report.ts +99 -64
- package/src/report/scope-warnings.ts +155 -0
- package/src/report/site-components.test.tsx +526 -0
- package/src/report/text/faces.ts +66 -0
- package/src/report/tree.ts +10 -3
- package/src/report/types.ts +51 -0
- package/src/report/web.ts +7 -40
- package/src/results/host-equivalence.test.ts +6 -2
- package/src/results/open.ts +5 -4
- package/src/results/results.test.ts +78 -1
- package/src/results/select.ts +80 -12
- package/src/results/types.ts +27 -0
- package/src/runner/attempt.ts +10 -9
- package/src/runner/discover.test.ts +9 -1
- package/src/runner/discover.ts +3 -3
- package/src/runner/fingerprint.ts +9 -4
- package/src/runner/ledger.test.ts +30 -1
- package/src/runner/ledger.ts +26 -4
- package/src/runner/run.ts +5 -1
- package/src/runner/sandbox-selection.test.ts +131 -0
- package/src/runner/sandbox-selection.ts +110 -0
- package/src/runner/types.ts +19 -2
- package/src/sandbox/types.ts +6 -0
- package/src/shared/aggregate.ts +1 -1
- package/src/show/index.ts +19 -12
- package/src/show/render.ts +12 -12
- package/src/show/report-host.test.ts +34 -17
- package/src/show/report-host.ts +6 -5
- package/src/show/show.test.ts +141 -4
- package/src/view/app/App.test.tsx +79 -17
- package/src/view/app/App.tsx +25 -74
- package/src/view/app/components/CopyControls.tsx +4 -42
- package/src/view/app/i18n.ts +5 -227
- package/src/view/app/lib/rows.ts +3 -21
- package/src/view/app/shared.ts +1 -3
- package/src/view/app/types.ts +2 -2
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.ts +21 -10
- package/src/view/index.ts +2 -12
- package/src/view/server.ts +4 -4
- package/src/view/shared/types.ts +11 -6
- package/src/view/styles.css +9 -252
- package/src/view/view-report.test.ts +99 -33
- package/src/view/app/components/LazyArtifact.tsx +0 -51
- package/src/view/app/components/SkippedRunsBanner.tsx +0 -140
- package/src/view/app/pages/AttemptsPage.tsx +0 -80
- package/src/view/app/pages/TracesPage.tsx +0 -35
package/src/i18n/zh-CN.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const zhCN = {
|
|
|
22
22
|
"skill.repoAmbiguous": "repo skill {{source}} 里有多个 skill,必须用 `skills: [...]` 明确选择要启用哪些。可选:{{available}}。",
|
|
23
23
|
"skill.repoUnknownSkill": "repo skill {{source}}(ref: {{ref}})里没有名为 \"{{skill}}\" 的 skill。可选:{{available}}。",
|
|
24
24
|
"skill.copyFailed": "skill \"{{name}}\" 装进 {{dest}} 失败:\n{{tail}}",
|
|
25
|
+
"mcp.ambiguousTransport":
|
|
26
|
+
"MCP server \"{{name}}\" 同时给出了 command 和 url——二选一:本地 stdio 进程写 command,远程 Streamable HTTP 端点写 url。",
|
|
25
27
|
"plugin.marketplaceFailed": "{{agent}} marketplace \"{{name}}\" 连接失败(source: {{source}}, ref: {{ref}}):\n{{tail}}",
|
|
26
28
|
"plugin.marketplaceVerifyFailed": "{{agent}} marketplace \"{{name}}\" add 后回读注册列表失败({{command}}):\n{{tail}}",
|
|
27
29
|
"plugin.marketplaceNameMismatch":
|
|
@@ -78,13 +80,14 @@ export const zhCN = {
|
|
|
78
80
|
" --timing 整个 attempt 的统一时间树(阶段 + hook/命令/turn + 轮内 OTel)\n" +
|
|
79
81
|
" --diff[=文件] agent 归因的文件改动摘要;=文件 按窗口展开单个文件\n" +
|
|
80
82
|
" --history 逐 experiment × eval 的执行时间轴(与 --report 互斥)\n" +
|
|
81
|
-
" --results <目录> 钉死结果根 --
|
|
82
|
-
" --report <文件> 自定义报告 --page <id>
|
|
83
|
+
" --results <目录> 钉死结果根 --exp <id> 只看该实验\n" +
|
|
84
|
+
" --report <文件> 自定义报告 --page <id> 定初始页(多页报告渲染该页,\n" +
|
|
85
|
+
" 尾部再附其余页索引)\n" +
|
|
83
86
|
" niceeval list 列出发现到的 eval\n" +
|
|
84
87
|
" niceeval view [eval-id 前缀…] [--out 目录] [--port n] [--no-open]\n" +
|
|
85
88
|
" 报告页 + 证据室;--report <文件> 整槽换成自定义报告(与 show 同一文件)\n" +
|
|
86
89
|
" --page <id> 定初始页 --results <目录> 钉死结果根\n" +
|
|
87
|
-
" --snapshot <文件> 只打开这一份快照 --
|
|
90
|
+
" --snapshot <文件> 只打开这一份快照 --exp <id> 只看该实验\n" +
|
|
88
91
|
" --out <目录> 静态导出:index.html 连同查看器 artifact,可直接静态托管\n" +
|
|
89
92
|
" niceeval sandbox list|enter|history|diff|stop 查看与销毁 --keep-sandbox 留下的现场\n" +
|
|
90
93
|
" niceeval clean 删除 .niceeval/ 历史 artifact\n" +
|
|
@@ -101,7 +104,7 @@ export const zhCN = {
|
|
|
101
104
|
"cli.show.noResults": "{{root}} 下没有结果。先 `niceeval exp` 跑一轮,再 `niceeval show`。\n",
|
|
102
105
|
"cli.show.runDirMissing": "Results directory not found: {{dir}}\n",
|
|
103
106
|
"cli.show.noEvalMatch": "No results matched: {{patterns}}. Evals with results: {{evals}}\n",
|
|
104
|
-
"cli.show.noExperimentMatch": "No experiment matched --
|
|
107
|
+
"cli.show.noExperimentMatch": "No experiment matched --exp {{arg}}. Experiments with results: {{experiments}}\n",
|
|
105
108
|
"cli.show.historyReportConflict":
|
|
106
109
|
"`--history` and `--report` are mutually exclusive: both take over the main output. --history is the host's per-attempt execution timeline; for snapshot-level trends, compose exp.snapshots inside your report file instead.\n",
|
|
107
110
|
"cli.show.evidenceNeedsEval":
|
|
@@ -145,6 +148,7 @@ export const zhCN = {
|
|
|
145
148
|
"context.turnFailedDefault": "本轮 send 返回 failed(turn status = failed)",
|
|
146
149
|
"define.agentNameRequired": "defineAgent 需要 name。",
|
|
147
150
|
"define.evalIdRejected": "defineEval 不接受 id —— id 由文件路径推导。",
|
|
151
|
+
"define.evalEnvironmentEmpty": "defineEval 的 environment 如有提供,必须是非空的 profile id。",
|
|
148
152
|
"define.evalTestRequired": "defineEval 需要一个 async test(t) 函数。",
|
|
149
153
|
"define.experimentAgentRequired": "defineExperiment 需要 agent。",
|
|
150
154
|
"define.experimentFlagNotJson": "experiment.flags.{{key}} 不是可 JSON 序列化的值(函数 / undefined / 循环引用 / bigint 不允许);flags 会原样进入结果快照,必须是纯 JSON。",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// niceeval/report/built-in ——
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// 只是宿主默认拿哪个值的事实。
|
|
1
|
+
// niceeval/report/built-in —— 内建视图的家:每个内建视图是一份普通 defineReport 成品,
|
|
2
|
+
// 有自己的名字、一个源文件,按名字具名导出;默认导出恒等于 standard——裸宿主装载的那份
|
|
3
|
+
// (docs/feature/reports/library/built-in.md)。新增内建视图 = 新文件 + 新具名导出,
|
|
4
|
+
// 不需要注册表,也不改变装载管线。
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { standard } from "./standard.tsx";
|
|
8
7
|
|
|
9
|
-
export
|
|
8
|
+
export { standard };
|
|
9
|
+
|
|
10
|
+
export default standard;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// standard —— 内建视图之一:报告 / Attempts / 追踪三页的通用结果站,没有任何私有钩子
|
|
2
|
+
// (docs/feature/reports/library/built-in.md)。裸 `niceeval show` 与 `niceeval view`
|
|
3
|
+
// 装载的就是它:一份普通 defineReport,与用户的 --report 文件同层、走同一条
|
|
4
|
+
// 装载 → resolve → validate → render 管线。「builtin」不是装载逻辑里的类别,
|
|
5
|
+
// 只是宿主默认拿哪个值的事实;用户报告经 defineReport({ extends: standard, … }) 整站复用。
|
|
6
|
+
//
|
|
7
|
+
// 三页站点:report(Hero + 警告 + 批量修复 prompt + 实验对比)、attempts(带过滤的
|
|
8
|
+
// attempt 全列表)、traces(执行瀑布)。裸宿主导航上能看到的一切内容都在这份定义里;
|
|
9
|
+
// 宿主保留的只有机器(docs/feature/reports/architecture.md「宿主保留的只有机器」)。
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
AttemptList,
|
|
13
|
+
Col,
|
|
14
|
+
CopyFixPrompt,
|
|
15
|
+
ExperimentComparison,
|
|
16
|
+
Hero,
|
|
17
|
+
ScopeWarnings,
|
|
18
|
+
TraceWaterfall,
|
|
19
|
+
defineReport,
|
|
20
|
+
} from "../index.ts";
|
|
21
|
+
|
|
22
|
+
export const standard = defineReport({
|
|
23
|
+
pages: [
|
|
24
|
+
{
|
|
25
|
+
id: "report",
|
|
26
|
+
title: { en: "Report", "zh-CN": "报告" },
|
|
27
|
+
content: (
|
|
28
|
+
<Col>
|
|
29
|
+
<Hero />
|
|
30
|
+
<ScopeWarnings />
|
|
31
|
+
<CopyFixPrompt />
|
|
32
|
+
<ExperimentComparison />
|
|
33
|
+
</Col>
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "attempts",
|
|
38
|
+
title: "Attempts",
|
|
39
|
+
content: (
|
|
40
|
+
<Col>
|
|
41
|
+
<Hero />
|
|
42
|
+
<ScopeWarnings />
|
|
43
|
+
<AttemptList filter />
|
|
44
|
+
</Col>
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "traces",
|
|
49
|
+
title: { en: "Traces", "zh-CN": "追踪" },
|
|
50
|
+
content: (
|
|
51
|
+
<Col>
|
|
52
|
+
<Hero />
|
|
53
|
+
<ScopeWarnings />
|
|
54
|
+
<TraceWaterfall />
|
|
55
|
+
</Col>
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
@@ -21,34 +21,42 @@ import {
|
|
|
21
21
|
type TextContext,
|
|
22
22
|
type WebContext,
|
|
23
23
|
} from "./tree.ts";
|
|
24
|
-
import type { ReportLocale } from "./locale.ts";
|
|
24
|
+
import type { LocalizedText, ReportLocale } from "./locale.ts";
|
|
25
25
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
26
26
|
import type {
|
|
27
27
|
AttemptListItem,
|
|
28
|
+
CopyFixPromptData,
|
|
28
29
|
DeltaData,
|
|
29
30
|
EvalListItem,
|
|
30
31
|
ExperimentComparisonData,
|
|
31
32
|
ExperimentListItem,
|
|
33
|
+
HeroData,
|
|
32
34
|
LineData,
|
|
33
35
|
MatrixData,
|
|
34
36
|
ReportInput,
|
|
35
37
|
ScatterData,
|
|
36
38
|
ScopeSummaryData,
|
|
39
|
+
ScopeWarning,
|
|
37
40
|
ScoreboardData,
|
|
38
41
|
TableData,
|
|
42
|
+
TraceWaterfallRow,
|
|
39
43
|
} from "./types.ts";
|
|
40
44
|
import {
|
|
41
45
|
attemptListData,
|
|
46
|
+
copyFixPromptData,
|
|
42
47
|
deltaTableData,
|
|
43
48
|
evalListData,
|
|
44
49
|
experimentComparisonData,
|
|
45
50
|
experimentListData,
|
|
51
|
+
heroData,
|
|
46
52
|
metricLineData,
|
|
47
53
|
metricMatrixData,
|
|
48
54
|
metricScatterData,
|
|
49
55
|
metricTableData,
|
|
50
56
|
scopeSummaryData,
|
|
57
|
+
scopeWarningsData,
|
|
51
58
|
scoreboardData,
|
|
59
|
+
traceWaterfallData,
|
|
52
60
|
type DeltaTableOptions,
|
|
53
61
|
type MetricLineOptions,
|
|
54
62
|
type MetricMatrixOptions,
|
|
@@ -64,18 +72,26 @@ import {
|
|
|
64
72
|
experimentComparisonText,
|
|
65
73
|
experimentListText,
|
|
66
74
|
barsText,
|
|
75
|
+
heroCardText,
|
|
67
76
|
lineText,
|
|
68
77
|
matrixText,
|
|
69
78
|
scatterText,
|
|
79
|
+
scopeWarningsText,
|
|
70
80
|
scoreboardText,
|
|
71
81
|
scopeSummaryText,
|
|
72
82
|
tableText,
|
|
83
|
+
traceWaterfallText,
|
|
73
84
|
} from "./text/faces.ts";
|
|
74
85
|
import { ScopeSummary as ScopeSummaryWeb } from "./react/ScopeSummary.tsx";
|
|
75
86
|
import { ExperimentComparisonView } from "./react/ExperimentComparison.tsx";
|
|
76
87
|
import { ExperimentList as ExperimentListWeb } from "./react/ExperimentList.tsx";
|
|
77
88
|
import { EvalList as EvalListWeb } from "./react/EvalList.tsx";
|
|
78
89
|
import { AttemptList as AttemptListWeb } from "./react/AttemptList.tsx";
|
|
90
|
+
import { HeroCard as HeroCardWeb } from "./react/HeroCard.tsx";
|
|
91
|
+
import { PoweredBy as PoweredByWeb } from "./react/PoweredBy.tsx";
|
|
92
|
+
import { ScopeWarnings as ScopeWarningsWeb } from "./react/ScopeWarnings.tsx";
|
|
93
|
+
import { CopyFixPrompt as CopyFixPromptWeb } from "./react/CopyFixPrompt.tsx";
|
|
94
|
+
import { TraceWaterfall as TraceWaterfallWeb } from "./react/TraceWaterfall.tsx";
|
|
79
95
|
import { MetricTable as MetricTableWeb } from "./react/MetricTable.tsx";
|
|
80
96
|
import { MetricMatrix as MetricMatrixWeb } from "./react/MetricMatrix.tsx";
|
|
81
97
|
import { MetricBars as MetricBarsWeb } from "./react/MetricBars.tsx";
|
|
@@ -257,6 +273,55 @@ const validateAttemptListData: Validator = (data) => {
|
|
|
257
273
|
return null;
|
|
258
274
|
};
|
|
259
275
|
|
|
276
|
+
const validateHeroData: Validator = (data) => {
|
|
277
|
+
if (!isObject(data)) return "expected an object";
|
|
278
|
+
if (!("latestStartedAt" in data) || (data.latestStartedAt !== null && typeof data.latestStartedAt !== "string")) {
|
|
279
|
+
return 'missing "latestStartedAt" (string | null)';
|
|
280
|
+
}
|
|
281
|
+
if (typeof data.snapshots !== "number") return 'missing "snapshots" (number)';
|
|
282
|
+
return null;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const validateScopeWarningsData: Validator = (data) => {
|
|
286
|
+
if (!Array.isArray(data)) return "expected an array of ScopeWarning";
|
|
287
|
+
for (const item of data as unknown[]) {
|
|
288
|
+
if (!isObject(item) || typeof item.kind !== "string" || typeof item.message !== "string") {
|
|
289
|
+
return "each warning needs { kind, message, … }";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const validateCopyFixPromptData: Validator = (data) => {
|
|
296
|
+
if (!isObject(data)) return "expected an object";
|
|
297
|
+
if (typeof data.prompt !== "string") return 'missing "prompt" (string)';
|
|
298
|
+
if (typeof data.failures !== "number") return 'missing "failures" (number)';
|
|
299
|
+
return null;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const validateTraceWaterfallData: Validator = (data) => {
|
|
303
|
+
if (!Array.isArray(data)) return "expected an array of TraceWaterfallRow";
|
|
304
|
+
for (const row of data as unknown[]) {
|
|
305
|
+
if (
|
|
306
|
+
!isObject(row) ||
|
|
307
|
+
typeof row.experimentId !== "string" ||
|
|
308
|
+
typeof row.evalId !== "string" ||
|
|
309
|
+
typeof row.locator !== "string" ||
|
|
310
|
+
!("durationMs" in row) ||
|
|
311
|
+
(row.durationMs !== null && typeof row.durationMs !== "number") ||
|
|
312
|
+
!Array.isArray(row.spans)
|
|
313
|
+
) {
|
|
314
|
+
return "each row needs { experimentId, evalId, locator, durationMs: number | null, spans }";
|
|
315
|
+
}
|
|
316
|
+
for (const span of row.spans as unknown[]) {
|
|
317
|
+
if (!isObject(span) || typeof span.name !== "string" || typeof span.startOffsetMs !== "number") {
|
|
318
|
+
return "each span needs { name, kind, startOffsetMs, durationMs, failed }";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
};
|
|
324
|
+
|
|
260
325
|
// ───────────────────────── spec / data 双形态的通用装配 ─────────────────────────
|
|
261
326
|
|
|
262
327
|
interface DataComponentDef<Data, Options, Presentation> {
|
|
@@ -463,6 +528,8 @@ export type AttemptListProps = DataProps<
|
|
|
463
528
|
EntityListChrome & {
|
|
464
529
|
/** 过滤 / 截断前的总数;省略时等于 data 长度。 */
|
|
465
530
|
total?: number;
|
|
531
|
+
/** web 面加过滤输入框(按 experiment、eval、agent、verdict 或摘要文本收窄行);渐进增强,不改变数据与 text 面。 */
|
|
532
|
+
filter?: boolean;
|
|
466
533
|
}
|
|
467
534
|
>;
|
|
468
535
|
|
|
@@ -470,7 +537,7 @@ export type AttemptListProps = DataProps<
|
|
|
470
537
|
export const AttemptList = makeDataComponent<
|
|
471
538
|
readonly AttemptListItem[],
|
|
472
539
|
Record<never, never>,
|
|
473
|
-
EntityListChrome & { total?: number }
|
|
540
|
+
EntityListChrome & { total?: number; filter?: boolean }
|
|
474
541
|
>({
|
|
475
542
|
name: "AttemptList",
|
|
476
543
|
dataFnName: "attemptListData",
|
|
@@ -482,6 +549,7 @@ export const AttemptList = makeDataComponent<
|
|
|
482
549
|
<AttemptListWeb
|
|
483
550
|
data={props.data}
|
|
484
551
|
total={props.total}
|
|
552
|
+
filter={props.filter}
|
|
485
553
|
locale={props.locale ?? ctx.locale}
|
|
486
554
|
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
487
555
|
className={props.className}
|
|
@@ -537,6 +605,154 @@ export const FailureList = defineComponent<FailureListProps>(async (props, ctx)
|
|
|
537
605
|
});
|
|
538
606
|
FailureList.displayName = "FailureList";
|
|
539
607
|
|
|
608
|
+
// ───────────────────────── 站点组件(Hero / PoweredBy / ScopeWarnings / CopyFixPrompt / TraceWaterfall)─────────────────────────
|
|
609
|
+
|
|
610
|
+
/** `Hero` 的 props:标题缺省取 `ctx.report.title`(回退链后的站点标题)。 */
|
|
611
|
+
export interface HeroProps {
|
|
612
|
+
/** 覆盖标题;省略时取 ctx.report.title(回退链后的站点标题)。 */
|
|
613
|
+
title?: LocalizedText;
|
|
614
|
+
className?: string;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** HeroCard 的 data 校验入口(它不经 makeDataComponent,数据形态是唯一形态)。 */
|
|
618
|
+
const assertHeroData = (data: unknown): HeroData => {
|
|
619
|
+
const problem = validateHeroData(data);
|
|
620
|
+
if (problem !== null) throw dataShapeError("HeroCard", "heroData", "HeroData", problem);
|
|
621
|
+
return data as HeroData;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* `HeroCard`:Hero 的渲染件,双面组件,只收 data 形态——标题输入是站点声明与 Scope 的
|
|
626
|
+
* 合成物,没有单独的 spec 等价形。web 面渲染 hero 标题(h1)、按渲染 locale 格式化的运行
|
|
627
|
+
* meta(latestStartedAt 为 null 时内置「暂无运行」文案)与品牌行(等同 PoweredBy,恒含、
|
|
628
|
+
* 无拆除 prop);text 面输出标题行与 meta 行,不含品牌行
|
|
629
|
+
* (docs/feature/reports/library/site-components.md「HeroCard」)。
|
|
630
|
+
*/
|
|
631
|
+
export const HeroCard = defineComponent<HeroCardProps>({
|
|
632
|
+
web: (props, ctx) => {
|
|
633
|
+
assertHeroData(props.data);
|
|
634
|
+
return <HeroCardWeb title={props.title} data={props.data} className={props.className} locale={ctx.locale} />;
|
|
635
|
+
},
|
|
636
|
+
text: (props, ctx) => {
|
|
637
|
+
assertHeroData(props.data);
|
|
638
|
+
return heroCardText(props.title, props.data, ctx);
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
HeroCard.displayName = "HeroCard";
|
|
642
|
+
|
|
643
|
+
/** `HeroCard` 的 props:标题 + `heroData()` 的产物,只有 data 形态。 */
|
|
644
|
+
export interface HeroCardProps {
|
|
645
|
+
title: LocalizedText;
|
|
646
|
+
data: HeroData;
|
|
647
|
+
className?: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* `Hero`:页首的站点标题区——标题、最后运行时间、快照合成来源,恒含品牌行。官方组合组件,
|
|
652
|
+
* 与手写 `<HeroCard title={title ?? ctx.report.title} data={await heroData(ctx.scope)} />`
|
|
653
|
+
* 严格等价、没有私有能力;读 `ctx.report` 意味着输出跟随站点,要站点无关的标题区直接用
|
|
654
|
+
* `HeroCard` 显式传值(docs/feature/reports/library/site-components.md「Hero」)。
|
|
655
|
+
*/
|
|
656
|
+
export const Hero = defineComponent<HeroProps>(async ({ title, className }, ctx) => (
|
|
657
|
+
<HeroCard title={title ?? ctx.report.title} data={await heroData(ctx.scope)} className={className} />
|
|
658
|
+
));
|
|
659
|
+
Hero.displayName = "Hero";
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* `PoweredBy`:唯一的品牌件,无 props 双面组件。web 面渲染指向 niceeval 官网的一行品牌色
|
|
663
|
+
* 小字(`utm_source=report&utm_medium=powered-by`,`rel` 仅 `noopener` 以保留 Referer);
|
|
664
|
+
* text 面零输出。没有任何配置——品牌契约是「提供一个组件,不给开关」:不想要品牌就不用
|
|
665
|
+
* 这些组件、自己写替代组件(docs/feature/reports/library/site-components.md「PoweredBy」)。
|
|
666
|
+
*/
|
|
667
|
+
export const PoweredBy = defineComponent<Record<never, never>>({
|
|
668
|
+
web: () => <PoweredByWeb />,
|
|
669
|
+
text: () => "",
|
|
670
|
+
});
|
|
671
|
+
PoweredBy.displayName = "PoweredBy";
|
|
672
|
+
|
|
673
|
+
/** `ScopeWarnings` 的 props:spec 形态取宿主 Scope 的 warnings,data 形态收 `ScopeWarning[]`。 */
|
|
674
|
+
export type ScopeWarningsProps = DataProps<readonly ScopeWarning[], Record<never, never>, ChromeProps>;
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* `ScopeWarnings`:选择警告区,警告的唯一呈现组件。把 Scope 携带的 `ScopeWarning[]`
|
|
678
|
+
* 按「下一步动作」聚合渲染(带 experimentId 的按实验聚合、非实验作用域按 kind 聚合;
|
|
679
|
+
* integrity 组在前);web 面组头带去重后的可复制命令、明细收原生 `<details>`(总条数 ≤ 3
|
|
680
|
+
* 默认展开),text 面同构但不折叠。空警告集与裸 `Snapshot[]` 输入两面零输出
|
|
681
|
+
* (docs/feature/reports/library/site-components.md「ScopeWarnings」)。
|
|
682
|
+
*/
|
|
683
|
+
export const ScopeWarnings = makeDataComponent<readonly ScopeWarning[], Record<never, never>, ChromeProps>({
|
|
684
|
+
name: "ScopeWarnings",
|
|
685
|
+
dataFnName: "scopeWarningsData",
|
|
686
|
+
shapeName: "ScopeWarning[]",
|
|
687
|
+
dataFn: (input) => scopeWarningsData(input),
|
|
688
|
+
specKeys: [],
|
|
689
|
+
validate: validateScopeWarningsData,
|
|
690
|
+
web: (props, ctx) =>
|
|
691
|
+
props.data.length === 0 ? null : (
|
|
692
|
+
<ScopeWarningsWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
693
|
+
),
|
|
694
|
+
text: (props, ctx) => scopeWarningsText(props.data, ctx),
|
|
695
|
+
}) as unknown as ReportComponent<ScopeWarningsProps>;
|
|
696
|
+
|
|
697
|
+
/** `CopyFixPrompt` 的 props:spec 形态无选项,data 形态收 `copyFixPromptData()` 的产物。 */
|
|
698
|
+
export type CopyFixPromptProps = DataProps<CopyFixPromptData, Record<never, never>, ChromeProps>;
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* `CopyFixPrompt`:把当前范围的全部失败整理成一段可交给 coding agent 的修复 prompt。
|
|
702
|
+
* prompt 在 resolve 阶段算好、烘进静态 HTML,无 JS 时在折叠块里完整可读,「复制」是增强层
|
|
703
|
+
* 行为;`failures` 为 0 时两面零输出;text 面恒零输出——终端里的等价能力是 `show` 的
|
|
704
|
+
* attempt 下钻命令本身(docs/feature/reports/library/site-components.md「CopyFixPrompt」)。
|
|
705
|
+
*/
|
|
706
|
+
export const CopyFixPrompt = makeDataComponent<CopyFixPromptData, Record<never, never>, ChromeProps>({
|
|
707
|
+
name: "CopyFixPrompt",
|
|
708
|
+
dataFnName: "copyFixPromptData",
|
|
709
|
+
shapeName: "CopyFixPromptData",
|
|
710
|
+
dataFn: (input) => copyFixPromptData(input),
|
|
711
|
+
specKeys: [],
|
|
712
|
+
validate: validateCopyFixPromptData,
|
|
713
|
+
web: (props, ctx) =>
|
|
714
|
+
props.data.failures === 0 ? null : (
|
|
715
|
+
<CopyFixPromptWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
716
|
+
),
|
|
717
|
+
text: () => "",
|
|
718
|
+
}) as unknown as ReportComponent<CopyFixPromptProps>;
|
|
719
|
+
|
|
720
|
+
/** `TraceWaterfall` 的 props:spec 形态无选项,data 形态收 `traceWaterfallData()` 的产物。 */
|
|
721
|
+
export type TraceWaterfallProps = DataProps<
|
|
722
|
+
readonly TraceWaterfallRow[],
|
|
723
|
+
Record<never, never>,
|
|
724
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
725
|
+
>;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* `TraceWaterfall`:每个 attempt 一行的执行时间瀑布,用 canonical OTel 字段显示被测 agent
|
|
729
|
+
* 的原始 span(agent / model / tool)。web 面静态渲染顶层 span 分解条(失败 span 带失败
|
|
730
|
+
* 标记),行链接 attempt 详情;text 面每 attempt 一行(locator、总耗时、span 计数与失败
|
|
731
|
+
* 标记)+ 可复制的 `--timing` 下钻命令。trace 缺失的行照常出现并如实显示缺失;runner
|
|
732
|
+
* 生命周期节点不进瀑布(docs/feature/reports/library/site-components.md「TraceWaterfall」)。
|
|
733
|
+
*/
|
|
734
|
+
export const TraceWaterfall = makeDataComponent<
|
|
735
|
+
readonly TraceWaterfallRow[],
|
|
736
|
+
Record<never, never>,
|
|
737
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
738
|
+
>({
|
|
739
|
+
name: "TraceWaterfall",
|
|
740
|
+
dataFnName: "traceWaterfallData",
|
|
741
|
+
shapeName: "TraceWaterfallRow[]",
|
|
742
|
+
dataFn: (input) => traceWaterfallData(input),
|
|
743
|
+
specKeys: [],
|
|
744
|
+
validate: validateTraceWaterfallData,
|
|
745
|
+
web: (props, ctx) => (
|
|
746
|
+
<TraceWaterfallWeb
|
|
747
|
+
data={props.data}
|
|
748
|
+
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
749
|
+
locale={props.locale ?? ctx.locale}
|
|
750
|
+
className={props.className}
|
|
751
|
+
/>
|
|
752
|
+
),
|
|
753
|
+
text: (props, ctx) => traceWaterfallText(props.data, ctx),
|
|
754
|
+
}) as unknown as ReportComponent<TraceWaterfallProps>;
|
|
755
|
+
|
|
540
756
|
// ───────────────────────── 指标组件 ─────────────────────────
|
|
541
757
|
|
|
542
758
|
export type MetricTableProps = DataProps<
|
package/src/report/compute.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type {
|
|
15
15
|
AttemptListItem,
|
|
16
16
|
AttemptLocator,
|
|
17
|
+
CopyFixPromptData,
|
|
17
18
|
DeltaData,
|
|
18
19
|
DeltaPair,
|
|
19
20
|
DimensionInput,
|
|
@@ -23,6 +24,7 @@ import type {
|
|
|
23
24
|
ExperimentListEvalRow,
|
|
24
25
|
ExperimentListItem,
|
|
25
26
|
FlagPairs,
|
|
27
|
+
HeroData,
|
|
26
28
|
LineData,
|
|
27
29
|
MatrixData,
|
|
28
30
|
Metric,
|
|
@@ -31,11 +33,14 @@ import type {
|
|
|
31
33
|
ReportInput,
|
|
32
34
|
ScatterData,
|
|
33
35
|
ScopeSummaryData,
|
|
36
|
+
ScopeWarning,
|
|
34
37
|
ScoreboardData,
|
|
35
38
|
TableData,
|
|
39
|
+
TraceSpanSummary,
|
|
40
|
+
TraceWaterfallRow,
|
|
36
41
|
VerdictTally,
|
|
37
42
|
} from "./types.ts";
|
|
38
|
-
import type { EvalResult, JsonValue } from "../types.ts";
|
|
43
|
+
import type { EvalResult, JsonValue, TraceSpan } from "../types.ts";
|
|
39
44
|
import type { Snapshot } from "../results/types.ts";
|
|
40
45
|
import { comparabilityConfigOf, deepEqualJson } from "../results/select.ts";
|
|
41
46
|
import { evalLevelStats, foldEvalVerdict } from "../shared/verdict.ts";
|
|
@@ -969,3 +974,135 @@ function deltaOutcome(metric: Metric, delta: number | null): "improved" | "regre
|
|
|
969
974
|
const better = metric.better ?? "higher";
|
|
970
975
|
return (delta > 0) === (better === "higher") ? "improved" : "regressed";
|
|
971
976
|
}
|
|
977
|
+
|
|
978
|
+
// ───────────────────────── 站点组件的计算函数(hero / warnings / fix prompt / trace)─────────────────────────
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* `heroData(input)`:站点标题区的运行 meta——`latestStartedAt` 取范围内最新快照的开始时间
|
|
982
|
+
* (空范围为 null,不编造当前时间),`snapshots` 计贡献当前水位的快照数
|
|
983
|
+
* (docs/feature/reports/library/site-components.md「HeroCard」)。
|
|
984
|
+
*/
|
|
985
|
+
export async function heroData(input: ReportInput): Promise<HeroData> {
|
|
986
|
+
const { snapshots } = resolveInput(input);
|
|
987
|
+
let latest: string | null = null;
|
|
988
|
+
for (const snapshot of snapshots) {
|
|
989
|
+
if (latest === null || snapshot.startedAt > latest) latest = snapshot.startedAt;
|
|
990
|
+
}
|
|
991
|
+
return { latestStartedAt: latest, snapshots: snapshots.length };
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* `scopeWarningsData(input)`:Scope 携带的挑选警告原样透出;`input` 是裸 `Snapshot[]` 时
|
|
996
|
+
* 没有挑选过程、没有警告,返回空数组,也如实
|
|
997
|
+
* (docs/feature/reports/library/site-components.md「ScopeWarnings」)。
|
|
998
|
+
*/
|
|
999
|
+
export async function scopeWarningsData(input: ReportInput): Promise<readonly ScopeWarning[]> {
|
|
1000
|
+
return resolveInput(input).warnings;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* `copyFixPromptData(input)`:把范围内全部失败(verdict 为 failed / errored 的 attempt)
|
|
1005
|
+
* 整理成一段可交给 coding agent 的修复 prompt——逐失败含 eval id、主失败摘要与 attempt
|
|
1006
|
+
* 下钻命令(`niceeval show @<locator>`)。prompt 面向 agent,固定英文
|
|
1007
|
+
* (docs/feature/reports/library/site-components.md「CopyFixPrompt」)。
|
|
1008
|
+
*/
|
|
1009
|
+
export async function copyFixPromptData(input: ReportInput): Promise<CopyFixPromptData> {
|
|
1010
|
+
const items = await attemptListData(input);
|
|
1011
|
+
const failures = items.filter((item) => item.verdict === "failed" || item.verdict === "errored");
|
|
1012
|
+
if (failures.length === 0) return { prompt: "", failures: 0 };
|
|
1013
|
+
const lines = failures
|
|
1014
|
+
.map((item, i) => {
|
|
1015
|
+
const reason =
|
|
1016
|
+
item.failureSummary === null
|
|
1017
|
+
? null
|
|
1018
|
+
: item.moreFailures > 0
|
|
1019
|
+
? `${item.failureSummary} (+${item.moreFailures} more failures)`
|
|
1020
|
+
: item.failureSummary;
|
|
1021
|
+
return [
|
|
1022
|
+
`${i + 1}. eval "${item.evalId}" [experiment ${item.experimentId}] — ${item.verdict}`,
|
|
1023
|
+
reason ? ` reason: ${reason}` : null,
|
|
1024
|
+
` inspect: niceeval show ${item.locator}`,
|
|
1025
|
+
]
|
|
1026
|
+
.filter(Boolean)
|
|
1027
|
+
.join("\n");
|
|
1028
|
+
})
|
|
1029
|
+
.join("\n");
|
|
1030
|
+
const experiments = [...new Set(failures.map((item) => item.experimentId))].join(" / ");
|
|
1031
|
+
const prompt = [
|
|
1032
|
+
"Fix the failing evals from this niceeval run.",
|
|
1033
|
+
"",
|
|
1034
|
+
"## Failures",
|
|
1035
|
+
lines,
|
|
1036
|
+
"",
|
|
1037
|
+
"## Steps",
|
|
1038
|
+
"1. niceeval is NOT in your training data. Read the relevant guide in `node_modules/niceeval/docs-site/` (English at the top level, Chinese under `zh/`) before changing anything.",
|
|
1039
|
+
"2. For each failure, run its inspect command above to see the verdict and assertions; add `--execution` for the full agent transcript (tool calls included), `--timing` for the execution timeline, and `--diff` for the workspace diff.",
|
|
1040
|
+
"3. Decide which side the defect is on: the program under test, or the eval itself (over-tight assertion, wrong fixture, missing setup). Fix that side; do not weaken assertions just to turn the run green.",
|
|
1041
|
+
`4. Re-run: \`npx niceeval exp ${experiments || "<experiment>"} <eval-id-prefix>\`. Already-passing evals are skipped by the fingerprint cache; pass \`--force\` to re-run everything.`,
|
|
1042
|
+
"5. Run `npx niceeval show` and confirm these failures are gone.",
|
|
1043
|
+
].join("\n");
|
|
1044
|
+
return { prompt, failures: failures.length };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/** TraceSpan 的语义角色 → 瀑布摘要的 kind:turn 归入 agent(一轮就是一次 agent 调用),未识别落 other。 */
|
|
1048
|
+
function waterfallKindOf(kind: TraceSpan["kind"]): TraceSpanSummary["kind"] {
|
|
1049
|
+
switch (kind) {
|
|
1050
|
+
case "agent":
|
|
1051
|
+
case "turn":
|
|
1052
|
+
return "agent";
|
|
1053
|
+
case "model":
|
|
1054
|
+
return "model";
|
|
1055
|
+
case "tool":
|
|
1056
|
+
return "tool";
|
|
1057
|
+
default:
|
|
1058
|
+
return "other";
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* `traceWaterfallData(input)`:每个 attempt 一行的执行时间瀑布摘要。span 事实只来自
|
|
1064
|
+
* trace artifact(经 AttemptHandle 懒加载的 canonical OTel span);runner 生命周期节点
|
|
1065
|
+
* (`result.phases`)不进瀑布。行内只汇总顶层 span(parentSpanId 缺失或不在本 trace 内),
|
|
1066
|
+
* 按 startOffsetMs 升序;trace 缺失或为空时 `durationMs` 为 null、行照常出现
|
|
1067
|
+
* (docs/feature/reports/library/site-components.md「TraceWaterfall」)。
|
|
1068
|
+
*/
|
|
1069
|
+
export async function traceWaterfallData(input: ReportInput): Promise<readonly TraceWaterfallRow[]> {
|
|
1070
|
+
const { snapshots } = resolveInput(input);
|
|
1071
|
+
const items = collectItems(snapshots);
|
|
1072
|
+
return Promise.all(
|
|
1073
|
+
items.map(async (item): Promise<TraceWaterfallRow> => {
|
|
1074
|
+
const spans = await item.attempt.trace();
|
|
1075
|
+
if (spans === null || spans.length === 0) {
|
|
1076
|
+
return {
|
|
1077
|
+
experimentId: experimentIdOf(item),
|
|
1078
|
+
evalId: evalIdOf(item),
|
|
1079
|
+
locator: locatorOf(item),
|
|
1080
|
+
durationMs: null,
|
|
1081
|
+
spans: [],
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
const t0 = Math.min(...spans.map((s) => s.startMs));
|
|
1085
|
+
const t1 = Math.max(...spans.map((s) => s.endMs));
|
|
1086
|
+
const ids = new Set(spans.map((s) => s.spanId));
|
|
1087
|
+
const topLevel = spans.filter((s) => s.parentSpanId === undefined || !ids.has(s.parentSpanId));
|
|
1088
|
+
const summaries = topLevel
|
|
1089
|
+
.map(
|
|
1090
|
+
(s): TraceSpanSummary => ({
|
|
1091
|
+
name: s.name,
|
|
1092
|
+
kind: waterfallKindOf(s.kind),
|
|
1093
|
+
startOffsetMs: s.startMs - t0,
|
|
1094
|
+
durationMs: s.endMs - s.startMs,
|
|
1095
|
+
failed: s.status === "error",
|
|
1096
|
+
}),
|
|
1097
|
+
)
|
|
1098
|
+
.sort((a, b) => a.startOffsetMs - b.startOffsetMs);
|
|
1099
|
+
return {
|
|
1100
|
+
experimentId: experimentIdOf(item),
|
|
1101
|
+
evalId: evalIdOf(item),
|
|
1102
|
+
locator: locatorOf(item),
|
|
1103
|
+
durationMs: Math.max(0, t1 - t0),
|
|
1104
|
+
spans: summaries,
|
|
1105
|
+
};
|
|
1106
|
+
}),
|
|
1107
|
+
);
|
|
1108
|
+
}
|