niceeval 0.5.4 → 0.6.0
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 +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +5 -5
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +11 -11
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +12 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +54 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +34 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Quiet 报告器:--quiet 下的最小结果流。进度流照旧(attempt 无 onProgress 时直写 stderr),
|
|
2
|
+
// 这里只补「坏结果」:verdict 为 errored / failed 的结果各写一行 stderr,passed / skipped
|
|
3
|
+
// 静默 —— 保证 --quiet 下起沙箱失败这类执行错不会全程无声、只能事后读 summary.json。
|
|
4
|
+
// 结果流仍走统一的 Reporter 管线,不在 attempt 里散写。
|
|
5
|
+
|
|
6
|
+
import type { EvalResult, Reporter } from "../../types.ts";
|
|
7
|
+
import { t } from "../../i18n/index.ts";
|
|
8
|
+
import { runWho } from "../types.ts";
|
|
9
|
+
import { verdictSymbol } from "./shared.ts";
|
|
10
|
+
|
|
11
|
+
/** error / 断言 detail 的截断上限;比 Console 的 400 更紧,--quiet 只要能定位问题。 */
|
|
12
|
+
const DETAIL_MAX = 200;
|
|
13
|
+
|
|
14
|
+
/** 纯函数:一条结果 → 该写 stderr 的行;passed / skipped 返回 undefined(静默)。 */
|
|
15
|
+
export function quietLine(result: EvalResult): string | undefined {
|
|
16
|
+
if (result.verdict !== "errored" && result.verdict !== "failed") return undefined;
|
|
17
|
+
// [who] 与 attempt 进度行同源(runWho),两条流才能对上同一个运行配置。
|
|
18
|
+
const who = runWho({ agentName: result.agent, model: result.model, experimentId: result.experimentId });
|
|
19
|
+
const verdict = result.verdict === "errored" ? t("report.errored") : t("report.failed");
|
|
20
|
+
const detail =
|
|
21
|
+
result.error !== undefined
|
|
22
|
+
? `${t("report.error")}: ${truncate(result.error, DETAIL_MAX)}`
|
|
23
|
+
: firstFailedAssertion(result);
|
|
24
|
+
return ` ${verdictSymbol(result.verdict)} ${result.id} ${verdict} [${who}]${detail ? ` ${detail}` : ""}\n`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function firstFailedAssertion(result: EvalResult): string | undefined {
|
|
28
|
+
const a = result.assertions.find((x) => !x.passed);
|
|
29
|
+
if (!a) return undefined;
|
|
30
|
+
const sev = a.severity === "gate" ? t("report.gate") : t("report.soft");
|
|
31
|
+
const thr =
|
|
32
|
+
a.threshold !== undefined
|
|
33
|
+
? t("report.assertionThreshold", { score: a.score.toFixed(2), threshold: a.threshold })
|
|
34
|
+
: "";
|
|
35
|
+
return `${sev}: ${a.name}${thr}${a.detail ? ` — ${truncate(a.detail, DETAIL_MAX)}` : ""}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function truncate(s: string, n: number): string {
|
|
39
|
+
return s.length > n ? s.slice(0, n) + "…" : s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function Quiet(): Reporter {
|
|
43
|
+
return {
|
|
44
|
+
onEvalComplete(result: EvalResult) {
|
|
45
|
+
const line = quietLine(result);
|
|
46
|
+
if (line !== undefined) process.stderr.write(line);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// reporter 共用的展示常量。符号表只此一份 —— console/live/table 各抄一份时,
|
|
2
2
|
// 改一个符号要同步三处且不报错。
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { Verdict } from "../../types.ts";
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const VERDICT_SYM: Record<Verdict, string> = {
|
|
7
7
|
passed: "✓",
|
|
8
8
|
failed: "✗",
|
|
9
9
|
errored: "!",
|
|
10
10
|
skipped: "○",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export function
|
|
14
|
-
return
|
|
13
|
+
export function verdictSymbol(verdict: string): string {
|
|
14
|
+
return VERDICT_SYM[verdict as Verdict] ?? "?";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** live 表格里「还没抢到并发名额」的行:和转圈的 SPINNER、完成后的
|
|
17
|
+
/** live 表格里「还没抢到并发名额」的行:和转圈的 SPINNER、完成后的 VERDICT_SYM 三态区分开。 */
|
|
18
18
|
export const WAITING_SYM = "·";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { EvalResult, RunSummary, Usage } from "../../types.ts";
|
|
2
2
|
import { t } from "../../i18n/index.ts";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { verdictSymbol } from "./shared.ts";
|
|
4
|
+
import { foldEvalVerdict, evalLevelStats as sharedEvalLevelStats } from "../../shared/verdict.ts";
|
|
5
5
|
// 展示口径(耗时/成本/百分比)与聚合小工具(标签/求和/排序)与 view 共用一份实现;
|
|
6
6
|
// console.ts 经这里 re-export formatDuration。
|
|
7
7
|
import { formatCost, formatDuration, formatPercent } from "../../shared/format.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { VERDICT_ORDER, avg, displayExperimentName, fallbackExperimentLabel, sumMaybe, totalTokens } from "../../shared/aggregate.ts";
|
|
9
9
|
|
|
10
10
|
export { formatCost, formatDuration } from "../../shared/format.ts";
|
|
11
11
|
|
|
@@ -29,7 +29,7 @@ interface ExperimentRow {
|
|
|
29
29
|
|
|
30
30
|
interface EvalRow {
|
|
31
31
|
id: string;
|
|
32
|
-
|
|
32
|
+
verdict: EvalResult["verdict"];
|
|
33
33
|
reason: string;
|
|
34
34
|
durationMs: number;
|
|
35
35
|
tokens: number;
|
|
@@ -77,7 +77,7 @@ export function renderRunReport(summary: RunSummary): string {
|
|
|
77
77
|
t("report.table.runs"),
|
|
78
78
|
],
|
|
79
79
|
aggregateEvalRows(row.results).map((evalRow) => [
|
|
80
|
-
|
|
80
|
+
formatVerdict(evalRow.verdict),
|
|
81
81
|
evalRow.id,
|
|
82
82
|
evalRow.reason,
|
|
83
83
|
formatDuration(evalRow.durationMs),
|
|
@@ -114,14 +114,14 @@ export function formatTokens(n: number): string {
|
|
|
114
114
|
return String(n);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
export function
|
|
118
|
-
const sym =
|
|
119
|
-
switch (
|
|
117
|
+
export function formatVerdict(verdict: string): string {
|
|
118
|
+
const sym = verdictSymbol(verdict);
|
|
119
|
+
switch (verdict) {
|
|
120
120
|
case "passed": return `${sym} ${t("report.passed")}`;
|
|
121
121
|
case "failed": return `${sym} ${t("report.failed")}`;
|
|
122
122
|
case "errored": return `${sym} ${t("report.errored")}`;
|
|
123
123
|
case "skipped": return `${sym} ${t("report.skipped")}`;
|
|
124
|
-
default: return `${sym} ${
|
|
124
|
+
default: return `${sym} ${verdict}`;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -162,26 +162,26 @@ function aggregateEvalRows(results: EvalResult[]): EvalRow[] {
|
|
|
162
162
|
for (const result of results) byEval.set(result.id, [...(byEval.get(result.id) ?? []), result]);
|
|
163
163
|
return [...byEval.entries()]
|
|
164
164
|
.map(([id, evalResults]) => {
|
|
165
|
-
const
|
|
165
|
+
const verdict = foldEvalVerdict(evalResults);
|
|
166
166
|
const representative =
|
|
167
|
-
evalResults.find((r) => r.
|
|
168
|
-
evalResults.find((r) => r.
|
|
167
|
+
evalResults.find((r) => r.verdict === verdict) ??
|
|
168
|
+
evalResults.find((r) => r.verdict !== "passed") ??
|
|
169
169
|
evalResults[0]!;
|
|
170
170
|
return {
|
|
171
171
|
id,
|
|
172
|
-
|
|
173
|
-
reason:
|
|
172
|
+
verdict,
|
|
173
|
+
reason: verdict === "passed" ? t("report.passed") : reasonFor(representative),
|
|
174
174
|
durationMs: avg(evalResults.map((r) => r.durationMs)),
|
|
175
175
|
tokens: totalTokens(evalResults.map((r) => r.usage)),
|
|
176
176
|
cost: sumMaybe(evalResults.map((r) => r.estimatedCostUSD)),
|
|
177
|
-
passedAttempts: evalResults.filter((r) => r.
|
|
177
|
+
passedAttempts: evalResults.filter((r) => r.verdict === "passed").length,
|
|
178
178
|
attempts: evalResults.length,
|
|
179
179
|
};
|
|
180
180
|
})
|
|
181
|
-
.sort((a, b) =>
|
|
181
|
+
.sort((a, b) => VERDICT_ORDER[a.verdict] - VERDICT_ORDER[b.verdict] || a.id.localeCompare(b.id));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
// 折叠 / 计票口径与 view 同一份实现(src/shared/
|
|
184
|
+
// 折叠 / 计票口径与 view 同一份实现(src/shared/verdict.ts),CLI 表格和网页榜单永不打架。
|
|
185
185
|
function evalLevelStats(results: EvalResult[]) {
|
|
186
186
|
return sharedEvalLevelStats(results, (r) => r.id);
|
|
187
187
|
}
|
package/src/runner/run.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
// reporter 编排 / 汇总在 report.ts,Sandbox 适配器在 remote-sandbox.ts。
|
|
4
4
|
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { Effect, Cause,
|
|
6
|
+
import { Effect, Cause, Exit } from "effect";
|
|
7
7
|
import { probeJudge } from "../scoring/judge.ts";
|
|
8
8
|
import { t } from "../i18n/index.ts";
|
|
9
|
-
import { cacheKey,
|
|
9
|
+
import { cacheKey, planCarry } from "./fingerprint.ts";
|
|
10
10
|
import { OtelReceiverPool } from "../o11y/otlp/turn-otel.ts";
|
|
11
11
|
import { runAttemptEffect } from "./attempt.ts";
|
|
12
12
|
import { runReporter, emitReporterEvent, scopeReporter, summarize } from "./report.ts";
|
|
13
|
+
import { writeStderrLine } from "../tty-line.ts";
|
|
13
14
|
import type { Agent, EvalResult, JudgeConfig, Reporter, RunShape, RunSummary } from "../types.ts";
|
|
14
15
|
import type { AgentRun, Attempt, RunOptions } from "./types.ts";
|
|
15
16
|
|
|
@@ -54,42 +55,14 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
54
55
|
return p;
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
await Promise.all(jobs);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 跨实验结果复用:只有上次 passed 且 fingerprint 匹配的 (experimentId, evalId) 组合直接携入。
|
|
73
|
-
// 失败/错误/跳过/fingerprint 不匹配都会重跑。--force 跳过此逻辑。
|
|
74
|
-
const priorRunKeys = new Set<string>();
|
|
75
|
-
const carriedResults: EvalResult[] = [];
|
|
76
|
-
if (opts.priorResults?.length) {
|
|
77
|
-
for (const r of opts.priorResults) {
|
|
78
|
-
if (!r.experimentId) continue;
|
|
79
|
-
const key = `${r.experimentId}|${r.id}`;
|
|
80
|
-
if (r.outcome === "passed" && r.fingerprint !== undefined && r.fingerprint === plannedFingerprints.get(key)) {
|
|
81
|
-
priorRunKeys.add(key);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const r of opts.priorResults) {
|
|
85
|
-
if (!r.experimentId || !priorRunKeys.has(`${r.experimentId}|${r.id}`)) continue;
|
|
86
|
-
// artifactsDir 是相对"本次 run 自己目录"的路径,新 summary 换了目录就会失效,必须清掉。
|
|
87
|
-
// artifactBase 不一样:它已经是 loadLatestResultsPerEval(经 withViewRefs)拼好的、
|
|
88
|
-
// 相对稳定的 .niceeval 根的路径,指向旧 run 的产物目录依然可解析,原样带过来
|
|
89
|
-
// ——不然 view 就再也找不到这条携带结果的源码/转录/trace 了。
|
|
90
|
-
carriedResults.push({ ...r, artifactsDir: undefined });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
58
|
+
// 跨实验结果复用:上次 passed 或 failed 且 fingerprint 匹配的 (experimentId, evalId) 组合
|
|
59
|
+
// 直接携入 —— 两者都是"跑完了、判定确定"的终态,没有理由重花一次 agent/sandbox 成本去
|
|
60
|
+
// 复现同一个已知结果。errored 是框架/环境层面的不确定失败(超时、沙箱挂了、judge 探测失败
|
|
61
|
+
// 等),判定本身不可信,必须重跑。跳过/fingerprint 不匹配同样重跑。--force 跳过此逻辑
|
|
62
|
+
// (cli.ts 在 --force 时不传 priorResults,也不算 carryPlan)。
|
|
63
|
+
// carryPlan 优先用调用方(cli.ts,为了 live 表格)已经算好的那份,不重算一遍。
|
|
64
|
+
const { plannedFingerprints, priorRunKeys, carriedResults } =
|
|
65
|
+
opts.carryPlan ?? (await planCarry(opts.evals, opts.agentRuns, opts.priorResults));
|
|
93
66
|
|
|
94
67
|
// 展开 attempts
|
|
95
68
|
// 外层按「round」(run index)迭代,内层按 eval 迭代:同一 key 的第 i+1 次 attempt 排在
|
|
@@ -207,17 +180,16 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
207
180
|
// 真的没过)才代表 agent 行为的样本,值得跑满 runs 去测通过率。earlyExit 开时两者都提前收尾。
|
|
208
181
|
const erroredKeys = new Set<string>();
|
|
209
182
|
|
|
210
|
-
// budget
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
183
|
+
// budget 护栏:只按「已完成 attempt 的实测花费」判断,不做预测性节流。之前的实现会按
|
|
184
|
+
// 「平均成本 × 在飞数」预扣,快到顶就让还没起飞的 attempt 排队等——这在探测阶段(还没有任何
|
|
185
|
+
// 成本样本时)等价于把同一 budgetKey 的并发摁到一个很小的数,且完全没有文档承诺过这个副作用
|
|
186
|
+
// (`docs-site/zh/guides/write-experiment.mdx` 对 `budget` 的描述只有一句「这一格配置的预算
|
|
187
|
+
// 上限」)。新语义:已完成 attempt 的花费加总一旦到顶,就不再放新 attempt 起飞(已经在飞的
|
|
188
|
+
// 照常跑完,不会被中途打断);到顶之前不做任何预测性限流,并发完全由 globalSem / runSem 决定。
|
|
189
|
+
// 代价是「已花 + 在飞未结算」的总花费可能短暂超出 budget——这是有意识的取舍:budget 是防止
|
|
190
|
+
// 无限烧钱的安全网,不是精确计费闸,不应该反过来限制吞吐。
|
|
217
191
|
interface BudgetState {
|
|
218
192
|
spent: number;
|
|
219
|
-
inflight: number;
|
|
220
|
-
costSamples: number;
|
|
221
193
|
completedNoCost: number;
|
|
222
194
|
unenforceableWarned: boolean;
|
|
223
195
|
}
|
|
@@ -225,7 +197,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
225
197
|
const budgetState = (key: string): BudgetState => {
|
|
226
198
|
let s = budgetStates.get(key);
|
|
227
199
|
if (!s) {
|
|
228
|
-
s = { spent: 0,
|
|
200
|
+
s = { spent: 0, completedNoCost: 0, unenforceableWarned: false };
|
|
229
201
|
budgetStates.set(key, s);
|
|
230
202
|
}
|
|
231
203
|
return s;
|
|
@@ -236,7 +208,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
236
208
|
// 并发 worker 交错写 → 用一个 permit=1 的信号量串起来(替代原先手搓的 reportQueue 链)。
|
|
237
209
|
const reportMutex = Effect.runSync(Effect.makeSemaphore(1));
|
|
238
210
|
// 沙箱启动单独限流:与 agent 并发(maxConcurrency)解耦,防高并发下 daemon/API 过载。
|
|
239
|
-
// 未显式指定时跟 maxConcurrency 走——各
|
|
211
|
+
// 未显式指定时跟 maxConcurrency 走——各 provider 的推荐值已在 cli 层写进 maxConcurrency 默认值。
|
|
240
212
|
const sandboxSem = Effect.runSync(Effect.makeSemaphore(opts.maxConcurrency));
|
|
241
213
|
|
|
242
214
|
// 两级并发闸:全局(opts.maxConcurrency)+ 实验级(AgentRun.maxConcurrency,可选)。
|
|
@@ -286,10 +258,15 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
286
258
|
Effect.forEach(
|
|
287
259
|
attempts,
|
|
288
260
|
(a) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
261
|
+
const budgetKey = a.run.experimentId ?? a.run.agent.name;
|
|
262
|
+
|
|
263
|
+
// preflight:「要不要开始跑」的许可判断(首过即停 + budget 上限检查),不持有
|
|
264
|
+
// globalSem——两类判断都是即时返回,不该占着全局并发槽位做无谓等待。runSem(实验自己的
|
|
265
|
+
// maxConcurrency)例外:它是实验私有资源,preflight 占着不影响别的实验,且和 mempal
|
|
266
|
+
// 那类「必须串行」的语义一致,所以仍然把 preflight 包在 runSem 里面(见下方)。
|
|
267
|
+
const preflight = Effect.gen(function* () {
|
|
268
|
+
// 首过即停:同 key 已通过,或已 errored(重跑只会重复同一个框架错误)且开了 earlyExit
|
|
269
|
+
// → 跳过未启动的 attempt。
|
|
293
270
|
if (a.run.earlyExit && (passedKeys.has(a.key) || erroredKeys.has(a.key))) {
|
|
294
271
|
yield* reportMutex.withPermits(1)(
|
|
295
272
|
Effect.promise(() =>
|
|
@@ -300,49 +277,32 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
300
277
|
}),
|
|
301
278
|
),
|
|
302
279
|
);
|
|
303
|
-
return;
|
|
280
|
+
return false;
|
|
304
281
|
}
|
|
305
282
|
|
|
306
283
|
const budget = a.run.budget;
|
|
307
|
-
const budgetKey = a.run.experimentId ?? a.run.agent.name;
|
|
308
284
|
if (budget !== undefined) {
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
),
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (s.costSamples === 0 && s.completedNoCost >= 3 && !s.unenforceableWarned) {
|
|
326
|
-
// 连续几次完成都拿不到成本:budget 对这个 agent 不可执行,说清楚再放行。
|
|
327
|
-
s.unenforceableWarned = true;
|
|
328
|
-
process.stderr.write(t("runner.budgetUnenforceable", { budgetKey }));
|
|
329
|
-
}
|
|
330
|
-
if (s.costSamples === 0 && s.unenforceableWarned) {
|
|
331
|
-
s.inflight += 1;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
const avg = s.costSamples > 0 ? s.spent / s.costSamples : undefined;
|
|
335
|
-
const projected =
|
|
336
|
-
avg === undefined ? (s.inflight > 0 ? Number.POSITIVE_INFINITY : 0) : s.spent + s.inflight * avg;
|
|
337
|
-
if (projected < budget) {
|
|
338
|
-
s.inflight += 1;
|
|
339
|
-
break;
|
|
285
|
+
// 只看已完成 attempt 的实测花费(见上方 BudgetState 注释),到顶就跳过新 attempt,
|
|
286
|
+
// 没到顶就立即放行——不等待、不做预测性节流。
|
|
287
|
+
const s = budgetState(budgetKey);
|
|
288
|
+
if (s.spent >= budget) {
|
|
289
|
+
if (!budgetReported.has(budgetKey)) {
|
|
290
|
+
budgetReported.add(budgetKey);
|
|
291
|
+
yield* reportMutex.withPermits(1)(
|
|
292
|
+
Effect.promise(() =>
|
|
293
|
+
emitReporterEvent(reporters, { type: "run:budgetExceeded", budget, spent: s.spent }),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
340
296
|
}
|
|
341
|
-
|
|
297
|
+
return false;
|
|
342
298
|
}
|
|
343
299
|
}
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
344
302
|
|
|
345
|
-
|
|
303
|
+
// body:preflight 放行之后才跑,只有这一段真正占用全局并发槽位(globalSem)。
|
|
304
|
+
const body = Effect.gen(function* () {
|
|
305
|
+
// 合并全局信号与本 eval 的首过即停信号:任一 abort → 本 attempt 的信号 abort。
|
|
346
306
|
const evalAc = evalAbortControllers.get(a.key);
|
|
347
307
|
const attemptSignal =
|
|
348
308
|
evalAc && opts.signal
|
|
@@ -361,34 +321,29 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
361
321
|
}),
|
|
362
322
|
),
|
|
363
323
|
);
|
|
364
|
-
const result = yield* runAttemptEffect(a, opts, sandboxSem, attemptSignal)
|
|
365
|
-
Effect.ensuring(
|
|
366
|
-
Effect.sync(() => {
|
|
367
|
-
if (a.run.budget !== undefined) {
|
|
368
|
-
const s = budgetState(budgetKey);
|
|
369
|
-
s.inflight = Math.max(0, s.inflight - 1);
|
|
370
|
-
}
|
|
371
|
-
}),
|
|
372
|
-
),
|
|
373
|
-
);
|
|
324
|
+
const result = yield* runAttemptEffect(a, opts, sandboxSem, attemptSignal);
|
|
374
325
|
if (a.run.budget !== undefined) {
|
|
375
326
|
const s = budgetState(budgetKey);
|
|
376
327
|
if (result.estimatedCostUSD !== undefined) {
|
|
377
328
|
s.spent += result.estimatedCostUSD;
|
|
378
|
-
s.costSamples += 1;
|
|
379
329
|
} else {
|
|
380
330
|
s.completedNoCost += 1;
|
|
331
|
+
if (s.spent === 0 && s.completedNoCost >= 3 && !s.unenforceableWarned) {
|
|
332
|
+
// 连续几次完成都拿不到成本:budget 对这个 agent 不可执行,说清楚一次。
|
|
333
|
+
s.unenforceableWarned = true;
|
|
334
|
+
writeStderrLine(t("runner.budgetUnenforceable", { budgetKey }));
|
|
335
|
+
}
|
|
381
336
|
}
|
|
382
337
|
}
|
|
383
338
|
|
|
384
|
-
if (result.
|
|
339
|
+
if (result.verdict === "passed") {
|
|
385
340
|
passedKeys.add(a.key);
|
|
386
341
|
evalAc?.abort(); // 让同 key 并发 attempt 尽早退出
|
|
387
342
|
} else if (a.run.earlyExit && (passedKeys.has(a.key) || erroredKeys.has(a.key))) {
|
|
388
343
|
// 并发情况:同 key 另一个 attempt 已通过/已 errored 后本 attempt 才完成
|
|
389
344
|
// (被 abort 后产出 errored),不计入结果。
|
|
390
345
|
return;
|
|
391
|
-
} else if (result.
|
|
346
|
+
} else if (result.verdict === "errored") {
|
|
392
347
|
erroredKeys.add(a.key);
|
|
393
348
|
evalAc?.abort(); // 框架层面的错误会确定性重复,让同 key 剩余 attempt 尽早退出
|
|
394
349
|
}
|
|
@@ -413,7 +368,11 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
413
368
|
Effect.promise(() => emitReporterEvent(reporters, { type: "eval:complete", result })),
|
|
414
369
|
);
|
|
415
370
|
});
|
|
416
|
-
const gated =
|
|
371
|
+
const gated = Effect.gen(function* () {
|
|
372
|
+
const proceed = yield* preflight;
|
|
373
|
+
if (!proceed) return;
|
|
374
|
+
yield* globalSem.withPermits(1)(body);
|
|
375
|
+
});
|
|
417
376
|
const runSem = runSems.get(a.run);
|
|
418
377
|
return runSem ? runSem.withPermits(1)(gated) : gated;
|
|
419
378
|
},
|
package/src/runner/types.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Cleanup, LocalizedText, SourceArtifact } from "../shared/types.ts";
|
|
5
5
|
import type { O11ySummary, StreamEvent, TraceSpan, Usage } from "../o11y/types.ts";
|
|
6
|
-
import type { Agent } from "../agents/types.ts";
|
|
6
|
+
import type { Agent, AgentSetupManifest } from "../agents/types.ts";
|
|
7
7
|
import type { Sandbox, SandboxOption } from "../sandbox/types.ts";
|
|
8
|
-
import type { AssertionResult, DiffData, JudgeConfig,
|
|
8
|
+
import type { AssertionResult, DiffData, JudgeConfig, Verdict } from "../scoring/types.ts";
|
|
9
9
|
import type { TestContext } from "../context/types.ts";
|
|
10
|
+
import type { CapturedEvalSource } from "./eval-source.ts";
|
|
10
11
|
|
|
11
12
|
// ───────────────────────── 结果 / 报告 ─────────────────────────
|
|
12
13
|
|
|
@@ -27,11 +28,19 @@ export interface EvalResult {
|
|
|
27
28
|
experiment?: ExperimentRunInfo;
|
|
28
29
|
agent: string;
|
|
29
30
|
model?: string;
|
|
30
|
-
|
|
31
|
+
verdict: Verdict;
|
|
31
32
|
fingerprint?: string;
|
|
32
33
|
attempt: number;
|
|
33
34
|
/** 本 attempt 开始的墙钟时刻(ISO);view 按 eval 粒度展示「何时跑的」。 */
|
|
34
35
|
startedAt?: string;
|
|
36
|
+
/**
|
|
37
|
+
* 不透明的 Attempt 定位符(`@` 前缀短确定性编码,见 `src/results/locator.ts` 的 AttemptLocator),
|
|
38
|
+
* 由 {experimentId, 快照 startedAt, evalId, attempt} 身份元组派生。非携带条目由 writer 在落盘时
|
|
39
|
+
* 算出;携带条目(`--resume` 合入)原样复制上一轮的值,从不重算——原快照的 startedAt 已经不在
|
|
40
|
+
* 当前快照里,重算会算出不同的字符串,详见 writer.ts 对携带分支的说明。省略时读取面按当前
|
|
41
|
+
* 已知身份兜底算出(第三方 harness 未实现 locator 时的降级,不保证跨 --resume 稳定)。
|
|
42
|
+
*/
|
|
43
|
+
locator?: string;
|
|
35
44
|
durationMs: number;
|
|
36
45
|
assertions: AssertionResult[];
|
|
37
46
|
usage?: Usage;
|
|
@@ -44,33 +53,29 @@ export interface EvalResult {
|
|
|
44
53
|
o11y?: O11ySummary;
|
|
45
54
|
/** agent 经 OpenTelemetry 导出的运行追踪(有 tracing 能力且收到 span 时)。 */
|
|
46
55
|
trace?: TraceSpan[];
|
|
56
|
+
/** 本 attempt 的 agent setup 实际装了什么(Skill / native plugin / MCP / Python plugin);不参与评分。 */
|
|
57
|
+
agentSetup?: AgentSetupManifest;
|
|
47
58
|
diff?: DiffData;
|
|
48
59
|
rawTranscript?: string;
|
|
49
|
-
|
|
50
|
-
/** 本 attempt 工件目录(相对 run 根),下有 events/trace/o11y/diff.json。 */
|
|
51
|
-
artifactsDir?: string;
|
|
52
|
-
/** 工件目录(相对结果根,供前端 fetch 与懒加载回退);view 的 loader 注入,--resume 携带条目落盘时保留。 */
|
|
60
|
+
/** 携带条目(--resume 合入)专用:artifact 目录(相对结果根目录),指向原快照里的落盘。 */
|
|
53
61
|
artifactBase?: string;
|
|
54
|
-
/** 工件目录的绝对路径;历史字段,当前读取面不再注入。 */
|
|
55
|
-
artifactAbsBase?: string;
|
|
56
62
|
hasTrace?: boolean;
|
|
57
63
|
hasEvents?: boolean;
|
|
58
64
|
hasSources?: boolean;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
/** `
|
|
67
|
+
/** `snapshot.json` 的格式标记;把 niceeval 报告和其它工具的同名文件区分开。 */
|
|
62
68
|
export const RESULTS_FORMAT = "niceeval.results";
|
|
63
|
-
/**
|
|
64
|
-
|
|
69
|
+
/**
|
|
70
|
+
* 结果格式版本,只在破坏兼容读取时递增;读取器只认相同版本。见 docs/feature/results/architecture.md。
|
|
71
|
+
* `5`(见 memory 的 attempt-locator-and-source-dedup 条目)= result.json 新增 `locator` 字段;
|
|
72
|
+
* `sources.json` 从逐 attempt 内联全量内容改为「attempt 级引用 + 快照级 `sources/<sha256>.json`
|
|
73
|
+
* 去重仓库」,`AttemptHandle.sources()` 的公开返回形状不变(仍是 `SourceArtifact[] | null`)。
|
|
74
|
+
*/
|
|
75
|
+
export const RESULTS_SCHEMA_VERSION = 5;
|
|
65
76
|
|
|
77
|
+
/** 一次运行的纯运行时内存聚合(reporter 契约用);落盘格式契约在 niceeval/results 的 SnapshotMeta / AttemptRecord,见 docs/feature/results/architecture.md。 */
|
|
66
78
|
export interface RunSummary {
|
|
67
|
-
/** 恒为 "niceeval.results";和 schemaVersion、producer 一起构成持久化契约,永不移动或改名。 */
|
|
68
|
-
format?: typeof RESULTS_FORMAT;
|
|
69
|
-
/** 结果格式版本;与读取器不同即视为不兼容,提示用 producer.version 对应的 niceeval 查看。 */
|
|
70
|
-
schemaVersion?: number;
|
|
71
|
-
/** 写这份报告的工具:niceeval 自己,或经 niceeval/results 写入面转换的第三方 harness。
|
|
72
|
-
* name === "niceeval" 时 version 用于拼 `npx niceeval@<version> view` 提示;其它 name 如实报出。 */
|
|
73
|
-
producer?: { name: string; version?: string; commit?: string };
|
|
74
79
|
/** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */
|
|
75
80
|
name?: LocalizedText;
|
|
76
81
|
agent: string;
|
|
@@ -87,12 +92,6 @@ export interface RunSummary {
|
|
|
87
92
|
usage?: Usage;
|
|
88
93
|
estimatedCostUSD?: number;
|
|
89
94
|
results: EvalResult[];
|
|
90
|
-
outputDir?: string;
|
|
91
|
-
/** 快照级元数据,按 experiment 键(缺 experimentId 时为 "<agent>/<model>" 合成键)存放:
|
|
92
|
-
* startedAt 只在与顶层 startedAt 不同(一个 run 装多份不同时刻的快照)时需要;
|
|
93
|
-
* knownEvalIds 是写入时刻该实验已知的 eval 并集(copySnapshots 自动补记,writer.snapshot 可声明)。
|
|
94
|
-
* 可选新增字段,不递增 schemaVersion(docs/results-format.md 版本规则)。 */
|
|
95
|
-
snapshots?: Record<string, { startedAt?: string; knownEvalIds?: string[] }>;
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
/** onRunStart 的运行规模:去重后 eval 数 × 配置(agent×model×flags)数 → 总运行(attempt)数。 */
|
|
@@ -144,7 +143,7 @@ export interface EvalDef {
|
|
|
144
143
|
/**
|
|
145
144
|
* eval 级预置:拿到沙箱(已上传 workspace + git 基线 + 装好依赖前)。
|
|
146
145
|
* 默认命令以非 root 跑(agent 的自然环境);装系统依赖时给 `runCommand` 传 `{ root: true }`
|
|
147
|
-
* (如 `runCommand("apt-get", ["install", …], { root: true })`)
|
|
146
|
+
* (如 `runCommand("apt-get", ["install", …], { root: true })`),跨 provider 语义一致。
|
|
148
147
|
*/
|
|
149
148
|
setup?: (sandbox: Sandbox) => Promise<void | Cleanup> | void | Cleanup;
|
|
150
149
|
/** eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。 */
|
|
@@ -158,6 +157,11 @@ export interface DiscoveredEval extends EvalDef {
|
|
|
158
157
|
baseDir: string;
|
|
159
158
|
/** 定义文件绝对路径,用于内容指纹缓存。 */
|
|
160
159
|
sourcePath: string;
|
|
160
|
+
/**
|
|
161
|
+
* discovery 时捕获的规范化源码(归一化文本 + 项目相对路径 + SHA-256),见 `eval-source.ts`。
|
|
162
|
+
* 同一文件里多个 eval(数组默认导出)共享同一份引用——哈希与内容天然相同,不重复读盘。
|
|
163
|
+
*/
|
|
164
|
+
source: CapturedEvalSource;
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
export interface ExperimentDef {
|
|
@@ -196,7 +200,7 @@ export interface ExperimentDef {
|
|
|
196
200
|
* 本实验自己的并发上限:调度器只对这个实验的 attempt 限流,同批其它实验不受影响,
|
|
197
201
|
* 仍按全局并发(CLI / env / config / 沙箱默认)跑。用于串行化有共享状态的实验
|
|
198
202
|
* (如跨 eval 累积记忆:`maxConcurrency: 1` 保证 attempt 按 eval 顺序一个个跑),
|
|
199
|
-
*
|
|
203
|
+
* 或给撞 provider 限额的实验单独降速。
|
|
200
204
|
*/
|
|
201
205
|
maxConcurrency?: number;
|
|
202
206
|
}
|
|
@@ -212,7 +216,7 @@ export interface Config {
|
|
|
212
216
|
* 可传字符串,或按 locale 提供多语言(如 `{ en: "...", "zh-CN": "..." }`),随 view 语言切换。
|
|
213
217
|
*/
|
|
214
218
|
name?: LocalizedText;
|
|
215
|
-
/**
|
|
219
|
+
/** 项目级默认沙箱 provider(docker / vercel / e2b / custom);experiment 可覆盖。 */
|
|
216
220
|
sandbox?: SandboxOption;
|
|
217
221
|
/** 上传进沙箱的工作区根目录,省略则用项目根;eval 的 sandbox 视图从这里起步。 */
|
|
218
222
|
workspace?: string;
|
|
@@ -262,9 +266,9 @@ export interface PriceOverride {
|
|
|
262
266
|
* 能区分同 agent 同 model 的实验变体,如 xxx 与 xxx--agents-md;与汇总表口径一致);
|
|
263
267
|
* 无 experiment 时退回 agent/model。live display 以它作行聚合 key,两处必须同源。
|
|
264
268
|
*/
|
|
265
|
-
export function runWho(run:
|
|
269
|
+
export function runWho(run: { agentName: string; model?: string; experimentId?: string }): string {
|
|
266
270
|
if (run.experimentId) return run.experimentId.split("/").pop()!;
|
|
267
|
-
return run.model ? `${run.
|
|
271
|
+
return run.model ? `${run.agentName}/${run.model}` : run.agentName;
|
|
268
272
|
}
|
|
269
273
|
|
|
270
274
|
/** 一个 (agent, model, flags) 的运行配置 —— 由 CLI / 实验展开。 */
|
|
@@ -295,8 +299,15 @@ export interface RunOptions {
|
|
|
295
299
|
signal?: AbortSignal;
|
|
296
300
|
/** TTY live display 的进度回调;设置后 attempt 的 log 消息路由到它而不是 stderr。 */
|
|
297
301
|
onProgress?: (evalId: string, who: string, msg: string) => void;
|
|
298
|
-
/** 上次运行的结果。
|
|
302
|
+
/** 上次运行的结果。verdict 为 passed/failed 的 (experimentId, evalId) 组合跳过重跑,结果直接合入本次汇总。 */
|
|
299
303
|
priorResults?: EvalResult[];
|
|
304
|
+
/**
|
|
305
|
+
* 预算好的携入计划(见 fingerprint.ts 的 planCarry)。cli.ts 为了让 live 表格提前知道
|
|
306
|
+
* 哪些行会被携入,必须在构建 liveRows 之前算一次;传进来后 runEvals 直接复用,不重算
|
|
307
|
+
* 一遍(否则两处各自算一次,不仅重复 I/O,还留下"两边判断可能不一致"的隐患)。
|
|
308
|
+
* 省略时 runEvals 自己算(测试直调等场景)。
|
|
309
|
+
*/
|
|
310
|
+
carryPlan?: import("./fingerprint.ts").CarryPlan;
|
|
300
311
|
/**
|
|
301
312
|
* 非沙箱 tracing agent 的 run 级共享 OTLP 接收池(runEvals 创建并回收;
|
|
302
313
|
* 每个 agent 一个 receiver,attempt 之间共享 —— 被测应用是长驻进程,端点不能随 attempt 换)。
|
|
@@ -309,7 +320,7 @@ export interface Attempt {
|
|
|
309
320
|
evalDef: DiscoveredEval;
|
|
310
321
|
run: AgentRun;
|
|
311
322
|
attempt: number;
|
|
312
|
-
/** agent+model+evalId
|
|
323
|
+
/** agent+model+evalId,用于首过即停。 */
|
|
313
324
|
key: string;
|
|
314
325
|
fingerprint: string;
|
|
315
326
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// restore: uploadFile → /tmp/__fe_rs_<uuid>.tar.gz → tar xzf -C /
|
|
9
9
|
//
|
|
10
10
|
// tar / binary file I/O 在所有 Linux sandbox(Docker、Vercel、e2b、Modal…)里都支持,
|
|
11
|
-
// 这段代码对任意
|
|
11
|
+
// 这段代码对任意 provider 的 Sandbox 实现无改动即可使用。
|
|
12
12
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import type { Sandbox } from "../types.ts";
|
package/src/sandbox/docker.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Docker
|
|
1
|
+
// Docker 沙箱 provider:用 dockerode 把容器当隔离工作区跑 eval。
|
|
2
2
|
// 改编自 agent-eval 的 docker-sandbox.ts,签名对齐 ../types.ts 的 Sandbox 契约
|
|
3
3
|
//(runShell/runCommand 的 opts 一律是选项对象,不再用位置参数)。
|
|
4
4
|
|
|
@@ -24,6 +24,17 @@ import { buildFindScript, shellQuote } from "./shell.ts";
|
|
|
24
24
|
import { createExecDemuxer, extractFileFromTar, packFilesToTar, readableToBuffer } from "./docker-stream.ts";
|
|
25
25
|
import { resolveSandboxPath } from "./paths.ts";
|
|
26
26
|
import { t } from "../i18n/index.ts";
|
|
27
|
+
import { beforeExternalTerminalWrite } from "../tty-line.ts";
|
|
28
|
+
import type { SandboxProvisionErrorKind } from "./errors.ts";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* dockerode 对镜像拉取限流没有专门的错误类型;Docker Hub 429 体现在错误 message 里
|
|
32
|
+
* (如 "toomanyrequests: You have reached your pull rate limit")。
|
|
33
|
+
*/
|
|
34
|
+
export function classifyProvisionError(e: unknown): SandboxProvisionErrorKind {
|
|
35
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
36
|
+
return /toomanyrequests|rate limit exceeded|429/i.test(msg) ? "rate_limit" : "unknown";
|
|
37
|
+
}
|
|
27
38
|
|
|
28
39
|
// 行首哨兵:源码文件几乎不可能出现这一串,用来切分单次 shell 输出里的多份文件。
|
|
29
40
|
const SOURCE_FILE_MARKER = "::FE-SRC-7b3f9c::";
|
|
@@ -167,8 +178,10 @@ export class DockerSandbox implements Sandbox {
|
|
|
167
178
|
await image.inspect();
|
|
168
179
|
} catch {
|
|
169
180
|
// 镜像不存在,拉取。
|
|
181
|
+
beforeExternalTerminalWrite();
|
|
170
182
|
console.log(t("docker.imagePullStart", { image: imageName }));
|
|
171
183
|
await this.pullImage(imageName);
|
|
184
|
+
beforeExternalTerminalWrite();
|
|
172
185
|
console.log(t("docker.imagePullDone", { image: imageName }));
|
|
173
186
|
}
|
|
174
187
|
}
|
|
@@ -240,7 +253,7 @@ export class DockerSandbox implements Sandbox {
|
|
|
240
253
|
});
|
|
241
254
|
}
|
|
242
255
|
|
|
243
|
-
/** 以 root 跑命令(
|
|
256
|
+
/** 以 root 跑命令(provider 内部用:容器初始化、属主收敛)。 */
|
|
244
257
|
private async runCommandAsRoot(
|
|
245
258
|
cmd: string,
|
|
246
259
|
args: string[] = [],
|