niceeval 0.6.1 → 0.7.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 +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +69 -7
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +57 -3
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +32 -24
- package/dist/report/aggregate.js +158 -50
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +93 -160
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +87 -81
- package/dist/report/compute.js +597 -417
- package/dist/report/flag.d.ts +32 -6
- package/dist/report/flag.js +92 -4
- package/dist/report/format.d.ts +19 -11
- package/dist/report/format.js +30 -13
- package/dist/report/index.d.ts +16 -16
- package/dist/report/index.js +20 -21
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +57 -33
- package/dist/report/locale.js +122 -56
- package/dist/report/metrics.d.ts +23 -4
- package/dist/report/metrics.js +110 -25
- package/dist/report/primitives.d.ts +48 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +9 -7
- package/dist/report/react/AttemptList.js +17 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +4 -4
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +10 -0
- package/dist/report/react/ExperimentComparison.js +12 -0
- package/dist/report/react/ExperimentList.d.ts +4 -3
- package/dist/report/react/ExperimentList.js +17 -18
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +59 -28
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +110 -147
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -16
- package/dist/report/report.js +259 -28
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +253 -184
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +38 -7
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +247 -284
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +49 -14
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +4 -2
- package/dist/shared/aggregate.js +8 -7
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
- package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
- package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
- package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
- package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
- package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
- package/docs-site/zh/how-to/publish-report.mdx +105 -0
- package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
- package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
- package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
- package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
- package/docs-site/zh/index.mdx +24 -26
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +32 -5
- package/docs-site/zh/reference/capabilities.mdx +8 -8
- package/docs-site/zh/reference/cli.mdx +40 -12
- package/docs-site/zh/reference/define-agent.mdx +58 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
- package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
- package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +10 -2
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +13 -2
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +71 -8
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +446 -124
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +162 -30
- package/src/context/session.test.ts +2 -1
- package/src/context/session.ts +115 -7
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +81 -17
- package/src/i18n/zh-CN.ts +80 -17
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +178 -61
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -279
- package/src/report/compute.ts +723 -491
- package/src/report/dual-render.test.tsx +741 -1024
- package/src/report/flag.ts +104 -12
- package/src/report/format.ts +32 -12
- package/src/report/index.ts +119 -46
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +136 -65
- package/src/report/metrics.ts +108 -25
- package/src/report/primitives.tsx +196 -45
- package/src/report/react/AttemptList.tsx +30 -43
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +73 -0
- package/src/report/react/ExperimentList.tsx +50 -32
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +86 -34
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +89 -5
- package/src/report/react/fixtures.ts +114 -154
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +138 -158
- package/src/report/react/styles.css +243 -82
- package/src/report/report.test.ts +779 -841
- package/src/report/report.ts +423 -41
- package/src/report/text/faces.ts +290 -193
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +44 -7
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +261 -271
- package/src/report/web.ts +63 -20
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +13 -11
- package/src/results/attempt-evidence.ts +20 -13
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +150 -60
- package/src/results/host-equivalence.test.ts +34 -20
- package/src/results/index.ts +12 -4
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +15 -5
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +89 -54
- package/src/results/select.ts +104 -34
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +43 -14
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +243 -37
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +8 -6
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +127 -56
- package/src/show/render.ts +662 -131
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +320 -54
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +423 -11
- package/src/view/app/components/CodeView.tsx +41 -14
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +37 -17
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +10 -3
- package/src/view/data.ts +155 -49
- package/src/view/index.ts +56 -41
- package/src/view/server.ts +37 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +227 -0
- package/src/view/view-report.test.ts +167 -62
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
- package/dist/report/built-ins/experiment-comparison.js +0 -13
- package/dist/report/built-ins/index.d.ts +0 -1
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/docs-site/zh/guides/publish-report.mdx +0 -91
- package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
- package/src/report/built-in-user-parity.test.tsx +0 -640
- package/src/report/built-ins/experiment-comparison.tsx +0 -19
- package/src/report/built-ins/index.ts +0 -2
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
package/src/runner/run.ts
CHANGED
|
@@ -10,9 +10,28 @@ 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 {
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import {
|
|
14
|
+
reportActivity,
|
|
15
|
+
reportAttemptLifecycle,
|
|
16
|
+
reportBudgetExhausted,
|
|
17
|
+
reportDiagnostic,
|
|
18
|
+
reportFailure,
|
|
19
|
+
reportInterrupted,
|
|
20
|
+
} from "./feedback/sink.ts";
|
|
21
|
+
import { failureDetailFromResult } from "./feedback/failure.ts";
|
|
22
|
+
import { encodeAttemptLocator, type AttemptLocator } from "../results/locator.ts";
|
|
23
|
+
import { runWho } from "./types.ts";
|
|
24
|
+
import type { Agent, EvalResult, JudgeConfig, Reporter, ReporterRegistration, RunShape, RunSummary } from "../types.ts";
|
|
25
|
+
import type { AgentRun, Attempt, LifecyclePhase, AttemptRef, RunOptions } from "./types.ts";
|
|
26
|
+
|
|
27
|
+
/** 反馈层的 attempt 身份 + 展示 label,两个 sink.ts lifecycle 调用点共用,避免各自手写
|
|
28
|
+
* 同一组字段(见 memory 的 live-who-key-mismatch-freezes-rows —— 手写副本漏改是真实事故源)。 */
|
|
29
|
+
function feedbackIdentity(a: Attempt): AttemptRef {
|
|
30
|
+
return { experimentId: a.run.experimentId, evalId: a.evalDef.id, attempt: a.attempt };
|
|
31
|
+
}
|
|
32
|
+
function feedbackWho(a: Attempt): string {
|
|
33
|
+
return runWho({ agentName: a.run.agent.name, model: a.run.model, experimentId: a.run.experimentId });
|
|
34
|
+
}
|
|
16
35
|
|
|
17
36
|
export type { AgentRun, RunOptions } from "./types.ts";
|
|
18
37
|
|
|
@@ -41,6 +60,13 @@ export function judgeProbeTargets(
|
|
|
41
60
|
|
|
42
61
|
export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
43
62
|
const startedAt = new Date().toISOString();
|
|
63
|
+
// 本次 invocation 的快照身份锚点:在展开/调度任何 attempt 之前确定一次,不同 experiment
|
|
64
|
+
// 共享它(locator 身份还含 experimentId,不会碰撞)。fresh EvalResult 的 locator(见下方
|
|
65
|
+
// attempt 完成处)与 Artifacts writer 写进 snapshot.json 的 startedAt 必须用同一个值——
|
|
66
|
+
// 复用刚建立的 startedAt,不是另起一次 new Date(),避免两者出现毫秒级漂移
|
|
67
|
+
// (docs/feature/experiments/cli.md「Locator 必须在 result 发布前确定」)。经 RunShape
|
|
68
|
+
// 传给 reporter(见下方 shape 构造),run.ts 之外没有第二个入口能改这份身份。
|
|
69
|
+
const snapshotStartedAt = startedAt;
|
|
44
70
|
const t0 = Date.now();
|
|
45
71
|
|
|
46
72
|
// 按 sourcePath 缓存文件内容,fingerprint 与 judge 预检共用:
|
|
@@ -64,6 +90,19 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
64
90
|
const { plannedFingerprints, priorRunKeys, carriedResults } =
|
|
65
91
|
opts.carryPlan ?? (await planCarry(opts.evals, opts.agentRuns, opts.priorResults));
|
|
66
92
|
|
|
93
|
+
// 携入覆盖计数:priorRunKeys 只回答「这个 (experimentId, evalId) 组合有没有可携入的终态
|
|
94
|
+
// 结果」,不回答「携入了几条」。runs 被调大(或实验改成更大的 runs)时,上次可能只留下比
|
|
95
|
+
// 这次请求更少的终态结果(如上次 runs:1、这次改成 runs:5),不能因为"有过携入"就把这次
|
|
96
|
+
// 请求的差额序号也整段跳过——那会让 pass@N 里的 N 被携入悄悄砍短,运行还照样报 PASSED/exit 0
|
|
97
|
+
// (见 docs/runner.md「不能在 CI 里伪装成全绿」)。下面按序号只跳过携入能覆盖到的前
|
|
98
|
+
// carriedCount 个,差额必须真正进 attempts 数组、走正常调度(含 earlyExit/budget 判断)。
|
|
99
|
+
const carriedCountByKey = new Map<string, number>();
|
|
100
|
+
for (const r of carriedResults) {
|
|
101
|
+
if (!r.experimentId) continue;
|
|
102
|
+
const key = `${r.experimentId}|${r.id}`;
|
|
103
|
+
carriedCountByKey.set(key, (carriedCountByKey.get(key) ?? 0) + 1);
|
|
104
|
+
}
|
|
105
|
+
|
|
67
106
|
// 展开 attempts
|
|
68
107
|
// 外层按「round」(run index)迭代,内层按 eval 迭代:同一 key 的第 i+1 次 attempt 排在
|
|
69
108
|
// 所有 eval 的第 i 次之后,earlyExit 开启时第 0 轮通过的 eval,其后续轮大多还没入池就被跳过。
|
|
@@ -72,9 +111,15 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
72
111
|
const attempts: Attempt[] = [];
|
|
73
112
|
for (const run of opts.agentRuns) {
|
|
74
113
|
const evals = opts.evals.filter((e) => run.evalFilter(e.id));
|
|
114
|
+
// 解析后实际选中的 eval id 全集(evals 过滤器的求值结果)进 ExperimentRunInfo.selectedEvalIds,
|
|
115
|
+
// 与 evalFilterFingerprint 一起取代过滤器本身落盘(见 docs/feature/results/architecture.md)。
|
|
116
|
+
run.selectedEvalIds = evals.map((e) => e.id);
|
|
75
117
|
for (let i = 0; i < run.runs; i++) {
|
|
76
118
|
for (const evalDef of evals) {
|
|
77
|
-
|
|
119
|
+
const carryKey = `${run.experimentId ?? ""}|${evalDef.id}`;
|
|
120
|
+
// 只跳过携入能覆盖到的前 carriedCount 个序号(见上面 carriedCountByKey 的注释);
|
|
121
|
+
// i 超出携入数量的部分必须真正调度,不能因为同一组合"有过携入"就整段跳过。
|
|
122
|
+
if (run.experimentId && priorRunKeys.has(carryKey) && i < (carriedCountByKey.get(carryKey) ?? 0)) continue;
|
|
78
123
|
// key 标识「同一个运行配置下的同一条 eval」,earlyExit 的跳过/abort 只应作用于
|
|
79
124
|
// 同 key 的重试轮。experimentId 必须进 key:两个实验可以同 agent 同 model、只差
|
|
80
125
|
// flags(feature A/B 正是这种形状),漏掉它会让先过的实验把其它实验的同名 eval
|
|
@@ -86,6 +131,16 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
86
131
|
attempt: i,
|
|
87
132
|
key,
|
|
88
133
|
fingerprint: plannedFingerprints.get(cacheKey(run, evalDef.id)) ?? "",
|
|
134
|
+
// locator 在构造 fresh attempt plan 时即算好并作为身份贯穿执行、留存登记与落盘
|
|
135
|
+
// (不是完成后写回,见 docs/cli.md);裸 run(无 experimentId)不产出。
|
|
136
|
+
locator: run.experimentId
|
|
137
|
+
? encodeAttemptLocator({
|
|
138
|
+
experimentId: run.experimentId,
|
|
139
|
+
snapshotStartedAt,
|
|
140
|
+
evalId: evalDef.id,
|
|
141
|
+
attempt: i,
|
|
142
|
+
})
|
|
143
|
+
: undefined,
|
|
89
144
|
});
|
|
90
145
|
}
|
|
91
146
|
}
|
|
@@ -102,7 +157,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
102
157
|
opts.config.judge,
|
|
103
158
|
);
|
|
104
159
|
if (toProbe.length > 0) {
|
|
105
|
-
|
|
160
|
+
reportActivity(t("runner.judgePrecheck").trimEnd());
|
|
106
161
|
for (const jc of toProbe) {
|
|
107
162
|
const err = await probeJudge(jc, opts.signal);
|
|
108
163
|
if (err) throw new Error(err);
|
|
@@ -112,7 +167,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
112
167
|
|
|
113
168
|
if (carriedResults.length > 0) {
|
|
114
169
|
const retryCount = new Set(attempts.map((a) => `${a.run.experimentId ?? ""}|${a.evalDef.id}`)).size;
|
|
115
|
-
|
|
170
|
+
reportActivity(t("runner.resumeCarry", { carried: carriedResults.length, retry: retryCount }).trimEnd());
|
|
116
171
|
// 按 experiment 分组列出被复用(跳过)的 eval:不列清单的话,用户只看到数量,
|
|
117
172
|
// 无法核对「跳过的是不是我以为已经过了的那些」。同一 key 多个 run 去重。
|
|
118
173
|
const carriedByExperiment = new Map<string, Set<string>>();
|
|
@@ -122,7 +177,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
122
177
|
carriedByExperiment.set(r.experimentId!, ids);
|
|
123
178
|
}
|
|
124
179
|
for (const [experiment, ids] of [...carriedByExperiment].sort(([a], [b]) => a.localeCompare(b))) {
|
|
125
|
-
|
|
180
|
+
reportActivity(t("runner.resumeCarryDetail", { experiment, evals: [...ids].sort().join(", ") }).trimEnd());
|
|
126
181
|
}
|
|
127
182
|
}
|
|
128
183
|
|
|
@@ -135,6 +190,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
135
190
|
configs: opts.agentRuns.length,
|
|
136
191
|
totalRuns: attempts.length,
|
|
137
192
|
maxConcurrency: opts.maxConcurrency,
|
|
193
|
+
snapshotStartedAt,
|
|
138
194
|
};
|
|
139
195
|
// eval 级 reporters:实例只观测引用它的 eval(经 scopeReporter 过滤转发)。
|
|
140
196
|
// 已经挂在全局 reporters 里的同一实例不重复挂;同一实例被多个 eval 引用时合并观测集
|
|
@@ -142,29 +198,39 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
142
198
|
const scopedSets = new Map<Reporter, Set<string>>();
|
|
143
199
|
for (const e of opts.evals) {
|
|
144
200
|
for (const r of e.reporters ?? []) {
|
|
145
|
-
if (opts.reporters.
|
|
201
|
+
if (opts.reporters.some((reg) => reg.reporter === r)) continue;
|
|
146
202
|
let ids = scopedSets.get(r);
|
|
147
203
|
if (!ids) scopedSets.set(r, (ids = new Set()));
|
|
148
204
|
ids.add(e.id);
|
|
149
205
|
}
|
|
150
206
|
}
|
|
151
|
-
const reporters:
|
|
207
|
+
const reporters: ReporterRegistration[] = [...opts.reporters];
|
|
208
|
+
// EvalDef.reporters 是用户在单个 eval 上挂的补充观测(如「这个 eval 单独也发一份到某个
|
|
209
|
+
// dashboard」),不是 CLI 显式注册的默认/机器出口——与 Config.reporters 同样默认
|
|
210
|
+
// best-effort(见 ReporterRegistration 的字段注释:required 只留给 artifacts / --json /
|
|
211
|
+
// --junit)。name 用「scope 内第几个」编号,足以在诊断里区分「哪一个 eval 级 reporter」,
|
|
212
|
+
// 不需要用户自己起名字。
|
|
213
|
+
let evalReporterIndex = 0;
|
|
152
214
|
for (const [r, ids] of scopedSets) {
|
|
153
215
|
const scopedRuns = attempts.filter((a) => ids.has(a.evalDef.id)).length;
|
|
154
216
|
if (scopedRuns === 0) continue;
|
|
155
|
-
reporters.push(
|
|
156
|
-
scopeReporter(r, ids, {
|
|
217
|
+
reporters.push({
|
|
218
|
+
reporter: scopeReporter(r, ids, {
|
|
157
219
|
evals: [...ids].filter((id) => runningIds.has(id)).length,
|
|
158
220
|
configs: opts.agentRuns.length,
|
|
159
221
|
totalRuns: scopedRuns,
|
|
160
222
|
maxConcurrency: opts.maxConcurrency,
|
|
223
|
+
snapshotStartedAt,
|
|
161
224
|
}),
|
|
162
|
-
|
|
225
|
+
name: `eval-reporter-${evalReporterIndex++}`,
|
|
226
|
+
required: false,
|
|
227
|
+
});
|
|
163
228
|
}
|
|
164
229
|
|
|
165
|
-
for (const
|
|
166
|
-
// reporter 只是结果消费方:单个 reporter 抛错记 diagnostic
|
|
167
|
-
|
|
230
|
+
for (const reg of reporters) {
|
|
231
|
+
// reporter 只是结果消费方:单个 reporter 抛错记 diagnostic,不能让整次调度崩,也不阻断
|
|
232
|
+
// 其它 reporter 的必要收尾(required/best-effort 的判定权重在 runReporter 内部处理)。
|
|
233
|
+
await runReporter(reg, "onRunStart", () => reg.reporter.onRunStart?.(runningEvals, firstAgent as Agent, shape));
|
|
168
234
|
}
|
|
169
235
|
await emitReporterEvent(reporters, {
|
|
170
236
|
type: "run:start",
|
|
@@ -178,26 +244,47 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
178
244
|
// errored = 框架/环境层面的意外(超时、adapter 崩、eval 脚本抛异常……),不是 agent 表现的信号。
|
|
179
245
|
// 同 key 一旦 errored 就会确定性地重复 error,再跑 runs 里剩下的次数纯烧钱;只有 failed(断言
|
|
180
246
|
// 真的没过)才代表 agent 行为的样本,值得跑满 runs 去测通过率。earlyExit 开时两者都提前收尾。
|
|
181
|
-
|
|
247
|
+
// run 级 fail-fast(见 docs/runner.md「首过即停」):同一错误 code 在同一 key 连续复现
|
|
248
|
+
// 即判定确定性错误,停止派发受同一配置影响的后续 attempt(如实报 errored 的结果保留;
|
|
249
|
+
// 这是止损,不是「首过即停」,两个机制互不混用)。
|
|
250
|
+
const lastErrorCode = new Map<string, { code: string; streak: number }>();
|
|
251
|
+
const failFastKeys = new Map<string, { code: string; skipped: number }>();
|
|
252
|
+
// 携入的 passed 结果预置进 passedKeys:上面按序号回填的差额 attempt(carriedCount < run.runs
|
|
253
|
+
// 那部分)如果不预置这个,会在明明已经拿到过 passed 结果的情况下真的再调度一次 agent——
|
|
254
|
+
// earlyExit 的语义是「已知会通过就不用再跑」,携入的 passed 同样是「已知会通过」,理应同等对待
|
|
255
|
+
// (下面 preflight/body 的 earlyExit 判断本来就只在 a.run.earlyExit 为真时读这两个 Set,所以
|
|
256
|
+
// 这里无条件预置对 --no-early-exit 场景没有副作用)。携入的 failed 故意不预置——failed 本来
|
|
257
|
+
// 就不触发 earlyExit,回填的差额必须真的重跑,才对得起用户调大 runs 的意图(想看这次是不是
|
|
258
|
+
// 还失败,或想凑够 pass@N 的样本量)。
|
|
259
|
+
for (const r of carriedResults) {
|
|
260
|
+
if (r.verdict === "passed" && r.experimentId) {
|
|
261
|
+
passedKeys.add(`${r.experimentId}|${r.agent}|${r.model ?? ""}|${r.id}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
182
264
|
|
|
183
265
|
// budget 护栏:只按「已完成 attempt 的实测花费」判断,不做预测性节流。之前的实现会按
|
|
184
266
|
// 「平均成本 × 在飞数」预扣,快到顶就让还没起飞的 attempt 排队等——这在探测阶段(还没有任何
|
|
185
267
|
// 成本样本时)等价于把同一 budgetKey 的并发摁到一个很小的数,且完全没有文档承诺过这个副作用
|
|
186
|
-
// (`docs-site/zh/
|
|
268
|
+
// (`docs-site/zh/how-to/write-experiment.mdx` 对 `budget` 的描述只有一句「这一格配置的预算
|
|
187
269
|
// 上限」)。新语义:已完成 attempt 的花费加总一旦到顶,就不再放新 attempt 起飞(已经在飞的
|
|
188
270
|
// 照常跑完,不会被中途打断);到顶之前不做任何预测性限流,并发完全由 globalSem / runSem 决定。
|
|
189
271
|
// 代价是「已花 + 在飞未结算」的总花费可能短暂超出 budget——这是有意识的取舍:budget 是防止
|
|
190
272
|
// 无限烧钱的安全网,不是精确计费闸,不应该反过来限制吞吐。
|
|
191
273
|
interface BudgetState {
|
|
192
274
|
spent: number;
|
|
193
|
-
|
|
275
|
+
/** 已经真正发起过 agent turn、但仍拿不到成本的 attempt 数。provider/setup 在 agent
|
|
276
|
+
* 运行前失败不计入——这种结果没有可执行的计费事实,不能据此声称 adapter 不报成本。 */
|
|
277
|
+
completedAgentRunsNoCost: number;
|
|
194
278
|
unenforceableWarned: boolean;
|
|
279
|
+
/** 因这个 budgetKey 预算到顶而未派发的 attempt 累计数——反馈层 "budget-exhausted" 事件
|
|
280
|
+
* (见 sink.ts 的 `BudgetExhaustedInput`)要求 emitter 自己维护这个累计值,reducer 不推导。 */
|
|
281
|
+
unstartedCount: number;
|
|
195
282
|
}
|
|
196
283
|
const budgetStates = new Map<string, BudgetState>();
|
|
197
284
|
const budgetState = (key: string): BudgetState => {
|
|
198
285
|
let s = budgetStates.get(key);
|
|
199
286
|
if (!s) {
|
|
200
|
-
s = { spent: 0,
|
|
287
|
+
s = { spent: 0, completedAgentRunsNoCost: 0, unenforceableWarned: false, unstartedCount: 0 };
|
|
201
288
|
budgetStates.set(key, s);
|
|
202
289
|
}
|
|
203
290
|
return s;
|
|
@@ -265,9 +352,9 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
265
352
|
// maxConcurrency)例外:它是实验私有资源,preflight 占着不影响别的实验,且和 mempal
|
|
266
353
|
// 那类「必须串行」的语义一致,所以仍然把 preflight 包在 runSem 里面(见下方)。
|
|
267
354
|
const preflight = Effect.gen(function* () {
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
if (a.run.earlyExit &&
|
|
355
|
+
// 首过即停:只由 passed 触发(errored 不中止其余样本,见 docs/feature/experiments/
|
|
356
|
+
// architecture.md「调度接口」)。
|
|
357
|
+
if (a.run.earlyExit && passedKeys.has(a.key)) {
|
|
271
358
|
yield* reportMutex.withPermits(1)(
|
|
272
359
|
Effect.promise(() =>
|
|
273
360
|
emitReporterEvent(reporters, {
|
|
@@ -277,6 +364,33 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
277
364
|
}),
|
|
278
365
|
),
|
|
279
366
|
);
|
|
367
|
+
reportAttemptLifecycle({
|
|
368
|
+
type: "attempt:early-exit",
|
|
369
|
+
at: Date.now(),
|
|
370
|
+
identity: feedbackIdentity(a),
|
|
371
|
+
who: feedbackWho(a),
|
|
372
|
+
});
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// run 级 fail-fast:确定性错误(同一 code 在同一 key 连续复现)已识别 → 停止派发,
|
|
377
|
+
// 未派发计入 unstarted(结论落 incomplete,不伪装成全绿;与首过即停互不混用)。
|
|
378
|
+
const failFast = failFastKeys.get(a.key);
|
|
379
|
+
if (failFast !== undefined) {
|
|
380
|
+
failFast.skipped += 1;
|
|
381
|
+
reportAttemptLifecycle({
|
|
382
|
+
type: "attempt:early-exit",
|
|
383
|
+
at: Date.now(),
|
|
384
|
+
identity: feedbackIdentity(a),
|
|
385
|
+
who: feedbackWho(a),
|
|
386
|
+
});
|
|
387
|
+
reportDiagnostic({
|
|
388
|
+
key: `fail-fast:${a.key}`,
|
|
389
|
+
severity: "warning",
|
|
390
|
+
message: t("runner.failFast", { evalId: a.evalDef.id, code: failFast.code }).trimEnd(),
|
|
391
|
+
identity: feedbackIdentity(a),
|
|
392
|
+
data: { evalId: a.evalDef.id, code: failFast.code, ...(a.run.experimentId ? { experimentId: a.run.experimentId } : {}) },
|
|
393
|
+
});
|
|
280
394
|
return false;
|
|
281
395
|
}
|
|
282
396
|
|
|
@@ -294,6 +408,21 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
294
408
|
),
|
|
295
409
|
);
|
|
296
410
|
}
|
|
411
|
+
// 反馈层:对每一个因预算到顶而不派发的 attempt 各发一次(与上面的
|
|
412
|
+
// attempt:early-exit 同构),让 RunFeedbackState 的 queued/completed 计数与
|
|
413
|
+
// cli.ts 的 assembleRunCompletion() 都能感知到「有 attempt 因预算未派发」——
|
|
414
|
+
// 上面 emitReporterEvent 的 run:budgetExceeded 只对旧版 Reporter 接口每
|
|
415
|
+
// budgetKey 报一次,不满足反馈层「每个未派发 attempt 各一条」的计数契约,两者
|
|
416
|
+
// 独立并存。只在挂靠 experiment 时报(budget-exhausted 事件要求真实
|
|
417
|
+
// experimentId;裸 run 不产出这类永久事件,与 locator 的省略规则一致)。
|
|
418
|
+
if (a.run.experimentId) {
|
|
419
|
+
s.unstartedCount += 1;
|
|
420
|
+
reportBudgetExhausted({
|
|
421
|
+
experimentId: a.run.experimentId,
|
|
422
|
+
spent: s.spent,
|
|
423
|
+
unstarted: s.unstartedCount,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
297
426
|
return false;
|
|
298
427
|
}
|
|
299
428
|
}
|
|
@@ -321,42 +450,114 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
321
450
|
}),
|
|
322
451
|
),
|
|
323
452
|
);
|
|
453
|
+
// attempt:start 是这个 attempt 从 queued 移进 running 的唯一时刻(见
|
|
454
|
+
// src/runner/feedback/reducer.ts 的 attempt:start 分支),必须与 eval:start 同一
|
|
455
|
+
// 调用点、恰好发生一次 —— 否则 RunFeedbackState 的守恒计数会被破坏。phase 只是粗粒度
|
|
456
|
+
// 占位(sandbox 型 attempt 恒为 sandbox.queue,一定正确;非 sandbox 型给
|
|
457
|
+
// eval.run,attempt.ts 内部一旦跑到第一个真实边界会用 attempt:phase 立即纠正,见
|
|
458
|
+
// attempt.ts 的 enterPhase)——attempt.ts 自己不再发 attempt:start,只发
|
|
459
|
+
// attempt:phase,避免两处各发一次导致计数翻倍。
|
|
460
|
+
const initialPhase: LifecyclePhase = a.run.agent.kind === "sandbox" ? "sandbox.queue" : "eval.run";
|
|
461
|
+
reportAttemptLifecycle({
|
|
462
|
+
type: "attempt:start",
|
|
463
|
+
at: Date.now(),
|
|
464
|
+
identity: feedbackIdentity(a),
|
|
465
|
+
who: feedbackWho(a),
|
|
466
|
+
phase: initialPhase,
|
|
467
|
+
});
|
|
324
468
|
const result = yield* runAttemptEffect(a, opts, sandboxSem, attemptSignal);
|
|
469
|
+
// locator 在这里确定 —— 早于本 attempt 触发的任何 reporter 回调 / 事件
|
|
470
|
+
// (onEvalComplete、eval:complete),所以每一个观察者看到的都已经是最终值,
|
|
471
|
+
// 和落盘 result.json 完全一致(writer.ts 的 entry.locator ?? 兜底分支因此
|
|
472
|
+
// 对 niceeval 自己的运行永不触发,只服务第三方直调 SnapshotWriter 的场景)。
|
|
473
|
+
// 没有 experimentId 的裸 run(非 exp 命令)不产出 locator,与
|
|
474
|
+
// writer.writeAttemptFor() 要求 experimentId 的既有约束一致 ——
|
|
475
|
+
// encodeAttemptLocator 本身也会在 experimentId 为空时直接抛错。
|
|
476
|
+
// 单独存一份本地变量(而不是只写 result.locator 再读回来):下面报 "failure" 永久
|
|
477
|
+
// 事件时需要一个已知是 AttemptLocator 品牌类型的值,result.locator 字段本身是
|
|
478
|
+
// 落盘/reporter 契约用的裸 string(见 EvalResult.locator 的类型注释)。
|
|
479
|
+
// locator 在 attempt plan 构造时已算好(见 attempts 构建处);这里只把同一个值写进
|
|
480
|
+
// 结果,早于本 attempt 触发的任何 reporter 回调 / 事件。
|
|
481
|
+
const locator: AttemptLocator | undefined = a.locator;
|
|
482
|
+
if (locator) result.locator = locator;
|
|
483
|
+
// attempt:complete 与上面的 attempt:start 严格一一配对(同一个 body Effect,唯一
|
|
484
|
+
// 出口),覆盖每一个真正跑过 runAttemptEffect 的 attempt(包括之后被下面的并发去重
|
|
485
|
+
// 分支丢弃、不计入 results 的那些)——reducer 的 attempt:complete 无条件
|
|
486
|
+
// running-1/completed+1,少配对一次就会让 running 计数漂移。
|
|
487
|
+
reportAttemptLifecycle({
|
|
488
|
+
type: "attempt:complete",
|
|
489
|
+
at: Date.now(),
|
|
490
|
+
identity: feedbackIdentity(a),
|
|
491
|
+
who: feedbackWho(a),
|
|
492
|
+
verdict: result.verdict,
|
|
493
|
+
tokenCount: result.usage
|
|
494
|
+
? (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0)
|
|
495
|
+
: undefined,
|
|
496
|
+
estimatedCostUSD: result.estimatedCostUSD,
|
|
497
|
+
});
|
|
325
498
|
if (a.run.budget !== undefined) {
|
|
326
499
|
const s = budgetState(budgetKey);
|
|
327
500
|
if (result.estimatedCostUSD !== undefined) {
|
|
328
501
|
s.spent += result.estimatedCostUSD;
|
|
329
|
-
} else {
|
|
330
|
-
s.
|
|
331
|
-
if (s.spent === 0 && s.
|
|
332
|
-
//
|
|
502
|
+
} else if (result.phases?.some((phase) => phase.children?.some((child) => child.kind === "turn"))) {
|
|
503
|
+
s.completedAgentRunsNoCost += 1;
|
|
504
|
+
if (s.spent === 0 && s.completedAgentRunsNoCost >= 3 && !s.unenforceableWarned) {
|
|
505
|
+
// 连续几次真正跑过 agent 的 attempt 都拿不到成本:budget 对这个 agent
|
|
506
|
+
// 不可执行,说清楚一次。sandbox.create/setup 等前置失败没有 turn,跳过这里:
|
|
507
|
+
// 此时应由 attempt error 回答根因,不能再用计费 warning 抢走注意力。
|
|
508
|
+
// s.unenforceableWarned 已经是 per-budgetKey 的一次性闸门;稳定 key 上的
|
|
509
|
+
// reportDiagnostic 去重是双保险,不依赖它单独生效。
|
|
333
510
|
s.unenforceableWarned = true;
|
|
334
|
-
|
|
511
|
+
reportDiagnostic({
|
|
512
|
+
key: `budget-unenforceable:${budgetKey}`,
|
|
513
|
+
severity: "warning",
|
|
514
|
+
message: t("runner.budgetUnenforceable", { budgetKey }).trimEnd(),
|
|
515
|
+
data: { budgetKey },
|
|
516
|
+
});
|
|
335
517
|
}
|
|
336
518
|
}
|
|
337
519
|
}
|
|
338
520
|
|
|
339
521
|
if (result.verdict === "passed") {
|
|
340
522
|
passedKeys.add(a.key);
|
|
523
|
+
lastErrorCode.delete(a.key);
|
|
341
524
|
evalAc?.abort(); // 让同 key 并发 attempt 尽早退出
|
|
342
|
-
} else if (a.run.earlyExit &&
|
|
343
|
-
// 并发情况:同 key 另一个 attempt
|
|
344
|
-
//
|
|
525
|
+
} else if (a.run.earlyExit && passedKeys.has(a.key)) {
|
|
526
|
+
// 并发情况:同 key 另一个 attempt 已通过后本 attempt 才完成(被 abort 后产出
|
|
527
|
+
// errored),不计入结果。
|
|
345
528
|
return;
|
|
346
529
|
} else if (result.verdict === "errored") {
|
|
347
|
-
|
|
348
|
-
|
|
530
|
+
// errored 不中止其余样本(基建可能自愈);只有同一错误 code 连续复现才判定为
|
|
531
|
+
// 确定性错误,进 run 级 fail-fast 停止派发(不 abort 已在飞的 attempt)。
|
|
532
|
+
const code = result.error?.code ?? "unexpected-error";
|
|
533
|
+
const prev = lastErrorCode.get(a.key);
|
|
534
|
+
const streak = prev?.code === code ? prev.streak + 1 : 1;
|
|
535
|
+
lastErrorCode.set(a.key, { code, streak });
|
|
536
|
+
if (streak >= 2 && !failFastKeys.has(a.key)) {
|
|
537
|
+
failFastKeys.set(a.key, { code, skipped: 0 });
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
lastErrorCode.delete(a.key);
|
|
349
541
|
}
|
|
350
542
|
|
|
351
543
|
results.push(result);
|
|
544
|
+
// 反馈层的永久失败通知(见 sink.ts 的 FailureInput / docs/feature/experiments/
|
|
545
|
+
// cli.md「什么动态更新,什么逐条追加」表的「failed / errored + locator」行)——
|
|
546
|
+
// 只在拿到 locator 时报(裸 run 没有 locator,不产出这类事件,与上面 result.locator
|
|
547
|
+
// 的省略规则一致),且只报真正计入 results 的 attempt(上面的并发去重分支已经
|
|
548
|
+
// return 掉、不会走到这里,不会为一条被丢弃的重复 attempt 误报失败)。
|
|
549
|
+
if (locator) {
|
|
550
|
+
const failure = failureDetailFromResult(result);
|
|
551
|
+
if (failure) reportFailure(failure);
|
|
552
|
+
}
|
|
352
553
|
yield* reportMutex.withPermits(1)(
|
|
353
554
|
// 每个 reporter 单独兜错:一个写文件失败 / 自定义 reporter 抛错只记 diagnostic,
|
|
354
555
|
// 不让 Promise.all 整体 reject —— 否则 Effect.promise 把它当 defect,fail 掉 forEach、
|
|
355
556
|
// 停掉后续 attempt(P2)。
|
|
356
557
|
Effect.promise(() =>
|
|
357
558
|
Promise.all(
|
|
358
|
-
reporters.map((
|
|
359
|
-
runReporter("onEvalComplete", () =>
|
|
559
|
+
reporters.map((reg) =>
|
|
560
|
+
runReporter(reg, "onEvalComplete", () => reg.reporter.onEvalComplete?.(result)),
|
|
360
561
|
),
|
|
361
562
|
),
|
|
362
563
|
),
|
|
@@ -399,7 +600,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
399
600
|
throw Cause.squash(exit.cause);
|
|
400
601
|
}
|
|
401
602
|
}
|
|
402
|
-
if (interrupted)
|
|
603
|
+
if (interrupted) reportInterrupted();
|
|
403
604
|
|
|
404
605
|
// 稳定排序:按发现顺序 + attempt;携带结果并入后一起排
|
|
405
606
|
const order = new Map(opts.evals.map((e, i) => [e.id, i]));
|
|
@@ -413,8 +614,13 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
413
614
|
|
|
414
615
|
const summary = summarize(allResults, firstAgent?.name ?? "", startedAt, Date.now() - t0, opts.config.name);
|
|
415
616
|
await emitReporterEvent(reporters, { type: "run:summary", summary });
|
|
416
|
-
for (const
|
|
417
|
-
|
|
617
|
+
for (const reg of reporters) {
|
|
618
|
+
// required reporter(默认 artifacts、显式 --json/--junit)在这一步失败,不能中断其它
|
|
619
|
+
// reporter 的收尾——继续跑完剩下的循环,让每个 reporter 都拿到 onRunComplete 的机会;
|
|
620
|
+
// 失败本身经 runReporter → reportReporterError 折成诊断,由调用方(cli.ts)读取
|
|
621
|
+
// RunFeedbackState 组装成 RunCompletion,让最终 completion/退出码判红(见
|
|
622
|
+
// docs/feature/experiments/cli.md「运行完成状态不只看 verdict 计数」)。
|
|
623
|
+
await runReporter(reg, "onRunComplete", () => reg.reporter.onRunComplete?.(summary));
|
|
418
624
|
}
|
|
419
625
|
await emitReporterEvent(reporters, { type: "run:saved", summary });
|
|
420
626
|
return summary;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// 沙箱编排的固定段(对所有沙箱型 agent 一致):收集 workspace
|
|
2
|
-
//
|
|
1
|
+
// 沙箱编排的固定段(对所有沙箱型 agent 一致):收集 workspace 文件。
|
|
2
|
+
// 变更归因(私有 git ledger、send 窗口)见 ledger.ts;adapter 只管「把 agent 跑起来」那一段。
|
|
3
3
|
|
|
4
4
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
5
5
|
import { join, relative, sep } from "node:path";
|
|
6
|
-
import type {
|
|
6
|
+
import type { SandboxFile } from "../types.ts";
|
|
7
7
|
|
|
8
8
|
const IGNORE_DIRS = new Set([
|
|
9
9
|
"node_modules",
|
|
@@ -50,42 +50,3 @@ export async function isDirectory(path: string): Promise<boolean> {
|
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
/** 打 git 基线:gitignore 掉依赖 / 构建产物,提交一版,供之后 diff HEAD 采改动。 */
|
|
55
|
-
export async function initGitAndCommit(sandbox: Sandbox): Promise<void> {
|
|
56
|
-
await sandbox.writeFiles({
|
|
57
|
-
".gitignore": "node_modules/\n.next/\ndist/\npackage-lock.json\n.niceeval/\n__niceeval__/\n",
|
|
58
|
-
});
|
|
59
|
-
await sandbox.runShell(
|
|
60
|
-
'git init -q && git config user.email "niceeval@localhost" && git config user.name "niceeval" && git add -A && git commit -q -m "baseline" || true',
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** git diff HEAD 采 agent 生成 / 删除的文件。 */
|
|
65
|
-
export async function captureGeneratedFiles(sandbox: Sandbox): Promise<DiffData> {
|
|
66
|
-
const generatedFiles: Record<string, string> = {};
|
|
67
|
-
const deletedFiles: string[] = [];
|
|
68
|
-
try {
|
|
69
|
-
const res = await sandbox.runShell("git add -A && git diff HEAD --name-status");
|
|
70
|
-
const lines = res.stdout.trim().split("\n").filter(Boolean);
|
|
71
|
-
for (const line of lines) {
|
|
72
|
-
const tab = line.indexOf("\t");
|
|
73
|
-
if (tab === -1) continue;
|
|
74
|
-
const status = line.slice(0, tab).trim();
|
|
75
|
-
const path = line.slice(tab + 1).trim();
|
|
76
|
-
if (!path) continue;
|
|
77
|
-
if (status.startsWith("D")) {
|
|
78
|
-
deletedFiles.push(path);
|
|
79
|
-
} else {
|
|
80
|
-
try {
|
|
81
|
-
generatedFiles[path] = await sandbox.readFile(path);
|
|
82
|
-
} catch {
|
|
83
|
-
// 二进制 / 不可读跳过
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
// 采集失败返回空
|
|
89
|
-
}
|
|
90
|
-
return { generatedFiles, deletedFiles };
|
|
91
|
-
}
|