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/report/web.ts
CHANGED
|
@@ -6,12 +6,20 @@ import * as React from "react";
|
|
|
6
6
|
import type { ReactNode } from "react";
|
|
7
7
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
8
8
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
9
|
+
import type { Scope } from "../results/types.ts";
|
|
10
|
+
import {
|
|
11
|
+
resolveReportTree,
|
|
12
|
+
runWithWebContext,
|
|
13
|
+
validateReportTree,
|
|
14
|
+
ResolveMemo,
|
|
15
|
+
type WebContext,
|
|
16
|
+
} from "./tree.ts";
|
|
11
17
|
import { DEFAULT_REPORT_LOCALE, type ReportLocale } from "./locale.ts";
|
|
12
|
-
import
|
|
18
|
+
import { buildReportMeta, pickReportPage, type ReportDefinition, type ReportHostContext } from "./report.ts";
|
|
13
19
|
|
|
14
20
|
export interface StaticHtmlOptions {
|
|
21
|
+
/** 渲染哪一页;缺省第一页。未命中抛 ReportPageNotFoundError。 */
|
|
22
|
+
pageId?: string;
|
|
15
23
|
/** 证据室深链;缺省用 view 的 attempt 路由 `#/attempt/@<locator>`(单段、不透明)。 */
|
|
16
24
|
attemptHref?: (locator: AttemptLocator) => string;
|
|
17
25
|
/** 官方组件 chrome 文案的 locale;默认 "en"。 */
|
|
@@ -19,12 +27,12 @@ export interface StaticHtmlOptions {
|
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
|
-
* 挑选警告的 HTML
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* message
|
|
30
|
+
* 挑选警告的 HTML 形态:宿主级前置块(`.nre nre-report-warnings` 外壳内一个
|
|
31
|
+
* `ul.nre-warnings` + `li.nre-warning[data-kind]`,复用 styles.css 已有样式)。
|
|
32
|
+
* 带 `command` 的警告把命令渲染为可复制块(`.nre-warning-command`);无 command 的
|
|
33
|
+
* 只显示 message,不硬造动作。经 renderToStaticMarkup 走 React,文本自动转义。
|
|
26
34
|
*/
|
|
27
|
-
function
|
|
35
|
+
function renderScopeWarningsHtml(scope: Scope): string {
|
|
28
36
|
return renderToStaticMarkup(
|
|
29
37
|
React.createElement(
|
|
30
38
|
"div",
|
|
@@ -32,8 +40,15 @@ function renderSelectionWarningsHtml(warnings: SelectionWarning[]): string {
|
|
|
32
40
|
React.createElement(
|
|
33
41
|
"ul",
|
|
34
42
|
{ className: "nre-warnings" },
|
|
35
|
-
warnings.map((w, i) =>
|
|
36
|
-
React.createElement(
|
|
43
|
+
scope.warnings.map((w, i) =>
|
|
44
|
+
React.createElement(
|
|
45
|
+
"li",
|
|
46
|
+
{ key: i, className: "nre-warning", "data-kind": w.kind },
|
|
47
|
+
w.message,
|
|
48
|
+
"command" in w && w.command
|
|
49
|
+
? React.createElement("code", { className: "nre-warning-command", "data-nre-copy": w.command }, w.command)
|
|
50
|
+
: null,
|
|
51
|
+
),
|
|
37
52
|
),
|
|
38
53
|
),
|
|
39
54
|
),
|
|
@@ -41,27 +56,55 @@ function renderSelectionWarningsHtml(warnings: SelectionWarning[]): string {
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
/**
|
|
44
|
-
*
|
|
45
|
-
* web 面;
|
|
46
|
-
*
|
|
59
|
+
* web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
|
|
60
|
+
* 树校验(与 text 宿主同一遍)→ 静态渲染 web 面;Scope 有挑选警告时在报告顶部前置
|
|
61
|
+
* 一块警告 HTML(宿主是 warning 的唯一呈现者,组件数据不复制 warning)。
|
|
47
62
|
*/
|
|
48
63
|
export async function renderReportToStaticHtml(
|
|
49
64
|
definition: ReportDefinition,
|
|
50
|
-
ctx:
|
|
65
|
+
ctx: ReportHostContext,
|
|
51
66
|
options?: StaticHtmlOptions,
|
|
52
67
|
): Promise<string> {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
68
|
+
const page = pickReportPage(definition, options?.pageId);
|
|
69
|
+
const meta = buildReportMeta(definition, ctx.scope, page.id);
|
|
70
|
+
const resolved = await resolveReportTree(page.content, {
|
|
71
|
+
scope: ctx.scope,
|
|
72
|
+
results: ctx.results,
|
|
73
|
+
report: meta,
|
|
74
|
+
memo: new ResolveMemo(),
|
|
75
|
+
});
|
|
55
76
|
validateReportTree(resolved);
|
|
56
77
|
const webCtx: WebContext = {
|
|
57
78
|
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
58
79
|
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
59
80
|
};
|
|
60
81
|
const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved as ReactNode));
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
83
|
+
return warnings + body;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
|
|
88
|
+
* Scope 有挑选警告时在页顶前置警告块(带 command 的警告渲染为可复制命令)——宿主是
|
|
89
|
+
* warning 的唯一呈现者,组件数据不复制 warning。ctx.report 是宿主规范化后的声明。
|
|
90
|
+
*/
|
|
91
|
+
export async function renderReportTreeToStaticHtml(
|
|
92
|
+
tree: import("./tree.ts").ReportNode,
|
|
93
|
+
ctx: { scope: Scope; results: import("../results/types.ts").Results; report: import("./report.ts").ReportMeta },
|
|
94
|
+
options?: { attemptHref?: (locator: AttemptLocator) => string; locale?: ReportLocale },
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
const resolved = await resolveReportTree(tree, {
|
|
97
|
+
scope: ctx.scope,
|
|
98
|
+
results: ctx.results,
|
|
99
|
+
report: ctx.report,
|
|
100
|
+
memo: new ResolveMemo(),
|
|
64
101
|
});
|
|
65
|
-
|
|
102
|
+
validateReportTree(resolved);
|
|
103
|
+
const webCtx: WebContext = {
|
|
104
|
+
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
105
|
+
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
106
|
+
};
|
|
107
|
+
const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved as ReactNode));
|
|
108
|
+
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
66
109
|
return warnings + body;
|
|
67
110
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/results/cases.md
|
|
1
2
|
// buildAnnotatedEvalSource 的单测(定稿见 docs/concepts.md「标注 Eval 源码」)。
|
|
2
3
|
// 覆盖:同一行多条断言、无 loc 断言进 unmapped、loc 指向别的文件/越界行也进 unmapped
|
|
3
4
|
// (never silently dropped 的边界情况)、空断言数组、summary 计数、哈希与归一化行为。
|
|
4
5
|
|
|
5
6
|
import { describe, expect, it } from "vitest";
|
|
6
|
-
import type { AssertionResult } from "../types.ts";
|
|
7
|
-
import { buildAnnotatedEvalSource } from "./annotated-source.ts";
|
|
7
|
+
import type { AssertionResult, PhaseTiming, StreamEvent } from "../types.ts";
|
|
8
|
+
import { buildAnnotatedEvalSource, deriveSendAnnotations, type SendAnnotation } from "./annotated-source.ts";
|
|
8
9
|
import { hashEvalSource, normalizeEvalSource } from "./source-hash.ts";
|
|
9
10
|
|
|
10
11
|
const SOURCE_PATH = "evals/weather.eval.ts";
|
|
@@ -13,9 +14,9 @@ function assertion(over: Partial<AssertionResult> & Pick<AssertionResult, "name"
|
|
|
13
14
|
return {
|
|
14
15
|
severity: "soft",
|
|
15
16
|
score: 1,
|
|
16
|
-
|
|
17
|
+
outcome: "passed" as const,
|
|
17
18
|
...over,
|
|
18
|
-
};
|
|
19
|
+
} as AssertionResult;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
describe("buildAnnotatedEvalSource", () => {
|
|
@@ -94,10 +95,10 @@ describe("buildAnnotatedEvalSource", () => {
|
|
|
94
95
|
it("computes summary counts across passed/failed and gate/soft, mapped and unmapped alike", () => {
|
|
95
96
|
const content = "line 1\nline 2\n";
|
|
96
97
|
const assertions = [
|
|
97
|
-
assertion({ name: "a", severity: "gate",
|
|
98
|
-
assertion({ name: "b", severity: "gate",
|
|
99
|
-
assertion({ name: "c", severity: "soft",
|
|
100
|
-
assertion({ name: "d", severity: "soft",
|
|
98
|
+
assertion({ name: "a", severity: "gate", outcome: "passed" as const, loc: { file: SOURCE_PATH, line: 1 } }),
|
|
99
|
+
assertion({ name: "b", severity: "gate", outcome: "failed" as const, loc: { file: SOURCE_PATH, line: 1 } }),
|
|
100
|
+
assertion({ name: "c", severity: "soft", outcome: "passed" as const, loc: { file: SOURCE_PATH, line: 2 } }),
|
|
101
|
+
assertion({ name: "d", severity: "soft", outcome: "failed" as const }), // unmapped
|
|
101
102
|
];
|
|
102
103
|
|
|
103
104
|
const model = buildAnnotatedEvalSource({ path: SOURCE_PATH, content }, assertions);
|
|
@@ -129,7 +130,7 @@ describe("buildAnnotatedEvalSource", () => {
|
|
|
129
130
|
|
|
130
131
|
it("treats an empty source file as a single empty line", () => {
|
|
131
132
|
const model = buildAnnotatedEvalSource({ path: SOURCE_PATH, content: "" }, []);
|
|
132
|
-
expect(model.lines).toEqual([{ line: 1, text: "", assertions: [] }]);
|
|
133
|
+
expect(model.lines).toEqual([{ line: 1, text: "", assertions: [], sends: [] }]);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
it("sourceSha256 matches hashEvalSource(normalizeEvalSource(content)) and is stable across CRLF/LF", () => {
|
|
@@ -143,4 +144,56 @@ describe("buildAnnotatedEvalSource", () => {
|
|
|
143
144
|
expect(modelLf.sourceSha256).toBe(modelCrlf.sourceSha256);
|
|
144
145
|
expect(modelLf.lines.map((l) => l.text)).toEqual(modelCrlf.lines.map((l) => l.text));
|
|
145
146
|
});
|
|
147
|
+
|
|
148
|
+
it("send 标注按 loc 落到对应行,一行多轮逐轮保留;别的文件或越界行直接丢(全量面在 --execution)", () => {
|
|
149
|
+
const content = "await t.send('a');\nawait t.send('b');\n";
|
|
150
|
+
const sends: SendAnnotation[] = [
|
|
151
|
+
{ label: "s1/t1", status: "completed", durationMs: 1200, loc: { file: SOURCE_PATH, line: 1, column: 9 } },
|
|
152
|
+
{ label: "s1/t2", status: "failed", durationMs: 800, loc: { file: SOURCE_PATH, line: 1, column: 9 } },
|
|
153
|
+
{ label: "s1/t3", status: "completed", loc: { file: "evals/other.eval.ts", line: 1 } },
|
|
154
|
+
{ label: "s1/t4", status: "completed", loc: { file: SOURCE_PATH, line: 99 } },
|
|
155
|
+
];
|
|
156
|
+
const model = buildAnnotatedEvalSource({ path: SOURCE_PATH, content }, [], sends);
|
|
157
|
+
expect(model.lines[0]!.sends.map((s) => s.label)).toEqual(["s1/t1", "s1/t2"]);
|
|
158
|
+
expect(model.lines[1]!.sends).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("deriveSendAnnotations", () => {
|
|
163
|
+
it("第 i 条用户消息配第 i 个 turn 节点(与 --execution 分轮同一规则);无 loc 的轮不产出", () => {
|
|
164
|
+
const events: StreamEvent[] = [
|
|
165
|
+
{ type: "message", role: "user", text: "first", loc: { file: SOURCE_PATH, line: 3, column: 5 } },
|
|
166
|
+
{ type: "message", role: "assistant", text: "reply" },
|
|
167
|
+
{ type: "message", role: "user", text: "second (no loc)" },
|
|
168
|
+
{ type: "message", role: "user", text: "third", loc: { file: SOURCE_PATH, line: 9, column: 5 } },
|
|
169
|
+
];
|
|
170
|
+
const phases: PhaseTiming[] = [{
|
|
171
|
+
name: "eval.run" as PhaseTiming["name"],
|
|
172
|
+
durationMs: 5000,
|
|
173
|
+
children: [
|
|
174
|
+
{ id: "n1", kind: "command", label: "git", startOffsetMs: 0, durationMs: 10 },
|
|
175
|
+
{ id: "n2", kind: "turn", label: "s1/t1", startOffsetMs: 10, durationMs: 1500 },
|
|
176
|
+
{ id: "n3", kind: "turn", label: "s1/t2", startOffsetMs: 1510, durationMs: 900, failed: true },
|
|
177
|
+
{ id: "n4", kind: "turn", label: "s1/t3", startOffsetMs: 2410, durationMs: 300 },
|
|
178
|
+
],
|
|
179
|
+
}];
|
|
180
|
+
|
|
181
|
+
const sends = deriveSendAnnotations(events, phases);
|
|
182
|
+
expect(sends).toEqual([
|
|
183
|
+
{ label: "s1/t1", status: "completed", durationMs: 1500, loc: { file: SOURCE_PATH, line: 3, column: 5 } },
|
|
184
|
+
// 第二条用户消息没有 loc → 不产出;第三条配第 3 个 turn 节点,不因跳过而错位
|
|
185
|
+
{ label: "s1/t3", status: "completed", durationMs: 300, loc: { file: SOURCE_PATH, line: 9, column: 5 } },
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("时间树缺 turn 节点时回退 t<i> 标签、无墙钟;没有事件时为空", () => {
|
|
190
|
+
const events: StreamEvent[] = [
|
|
191
|
+
{ type: "message", role: "user", text: "only", loc: { file: SOURCE_PATH, line: 1 } },
|
|
192
|
+
];
|
|
193
|
+
expect(deriveSendAnnotations(events, undefined)).toEqual([
|
|
194
|
+
{ label: "t1", status: "completed", loc: { file: SOURCE_PATH, line: 1 } },
|
|
195
|
+
]);
|
|
196
|
+
expect(deriveSendAnnotations(null, undefined)).toEqual([]);
|
|
197
|
+
expect(deriveSendAnnotations([], undefined)).toEqual([]);
|
|
198
|
+
});
|
|
146
199
|
});
|
|
@@ -6,16 +6,33 @@
|
|
|
6
6
|
//
|
|
7
7
|
// 泛化自 src/view/app/lib/transcript-data.tsx 的 indexAsserts()(Map<string, Assertion[]>
|
|
8
8
|
// + noloc 兜底桶的先例)——同一套"按 loc 分桶、没有 loc 进兜底"的思路,换成源码行数组
|
|
9
|
-
// (不是 Map)存放,外加 sourceSha256 / summary
|
|
10
|
-
//
|
|
9
|
+
// (不是 Map)存放,外加 sourceSha256 / summary 计数。轮次的完整展开(events → ExecutionTree)
|
|
10
|
+
// 仍是 ExecutionTree 的地盘;这个模型只把每轮的头行事实标回 send 调用行(见 SendAnnotation),
|
|
11
|
+
// 作为源码页指向 --execution 的跨面指针。
|
|
11
12
|
//
|
|
12
13
|
// 本模块不 import react/jsx,纯数据 + 纯函数,可以在任何 Node 语境(CLI 的 show、
|
|
13
14
|
// view 的 server 端数据准备)调用。
|
|
14
15
|
|
|
15
|
-
import type { AssertionResult } from "../types.ts";
|
|
16
|
+
import type { AssertionResult, PhaseTiming, SourceLoc, StreamEvent } from "../types.ts";
|
|
16
17
|
import { hashEvalSource, normalizeEvalSource } from "./source-hash.ts";
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* 标回 `t.send(...)` 调用行的一轮 turn 头行事实(契约见 docs/feature/reports/show.md
|
|
21
|
+
* 「--eval:把断言放回源码」)。身份标签与 --execution / --timing / diff windows 同一套;
|
|
22
|
+
* 回复全文与轮内卡片不进这个模型——源码页只回答「这行代码对应哪一轮、这一轮成了没成」。
|
|
23
|
+
*/
|
|
24
|
+
export interface SendAnnotation {
|
|
25
|
+
/** `s<session>/t<turn>`。 */
|
|
26
|
+
label: string;
|
|
27
|
+
/** 轮的终态;时间树只记 failed 位,waiting 需要事件流佐证时由派生方给。 */
|
|
28
|
+
status: "completed" | "failed" | "waiting";
|
|
29
|
+
/** 该轮墙钟;时间树缺这一轮的节点时省略。 */
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
/** send 调用位置(用户消息事件的 loc)。 */
|
|
32
|
+
loc: SourceLoc;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 一行源码 + 映射到这一行的全部断言与 send 标注(保持原始顺序;可以是空数组)。 */
|
|
19
36
|
export interface AnnotatedSourceLine {
|
|
20
37
|
/** 1-indexed 行号,与 SourceLoc.line 同一坐标系。 */
|
|
21
38
|
line: number;
|
|
@@ -28,6 +45,11 @@ export interface AnnotatedSourceLine {
|
|
|
28
45
|
* 领域 model 的职责(与 indexAsserts() 的先例一致,它同样只分桶、不折叠)。
|
|
29
46
|
*/
|
|
30
47
|
assertions: AssertionResult[];
|
|
48
|
+
/**
|
|
49
|
+
* 映射到这一行的 send 标注,按轮次顺序;循环里的 send 一行多轮。与断言的 never-drop
|
|
50
|
+
* 契约不同,定位不到行的轮不进任何兜底桶——轮次的全量面是 --execution,这里只是指针。
|
|
51
|
+
*/
|
|
52
|
+
sends: SendAnnotation[];
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
export interface AnnotatedEvalSourceSummary {
|
|
@@ -67,6 +89,34 @@ export interface AnnotatedEvalSource {
|
|
|
67
89
|
summary: AnnotatedEvalSourceSummary;
|
|
68
90
|
}
|
|
69
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 从标准事件流与阶段时间树派生 send 标注:第 i 条用户消息开第 i 轮(与 --execution 的
|
|
94
|
+
* 分轮边界同一条规则,见 show/render.ts::executionText),头行事实取 `eval.run` 下第 i 个
|
|
95
|
+
* turn 节点;用户消息没有 loc 的轮不产出标注。纯函数,无 IO。
|
|
96
|
+
*/
|
|
97
|
+
export function deriveSendAnnotations(
|
|
98
|
+
events: readonly StreamEvent[] | null,
|
|
99
|
+
phases: readonly PhaseTiming[] | undefined,
|
|
100
|
+
): SendAnnotation[] {
|
|
101
|
+
if (!events || events.length === 0) return [];
|
|
102
|
+
const turnNodes = (phases ?? []).flatMap((p) => p.children ?? []).filter((n) => n.kind === "turn");
|
|
103
|
+
const out: SendAnnotation[] = [];
|
|
104
|
+
let turnIndex = -1;
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
if (event.type !== "message" || event.role !== "user") continue;
|
|
107
|
+
turnIndex += 1;
|
|
108
|
+
if (!event.loc) continue;
|
|
109
|
+
const turn = turnNodes[turnIndex];
|
|
110
|
+
out.push({
|
|
111
|
+
label: turn?.label ?? `t${turnIndex + 1}`,
|
|
112
|
+
status: turn?.failed ? "failed" : "completed",
|
|
113
|
+
...(turn !== undefined ? { durationMs: turn.durationMs } : {}),
|
|
114
|
+
loc: event.loc,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
/**
|
|
71
121
|
* 纯函数:给定一份源码文本(未归一化也可以,内部会 normalizeEvalSource)与一批断言,
|
|
72
122
|
* 产出标注好的 AnnotatedEvalSource。幂等 / 无 IO —— 可以在渲染路径上安全调用。
|
|
@@ -77,13 +127,21 @@ export interface AnnotatedEvalSource {
|
|
|
77
127
|
export function buildAnnotatedEvalSource(
|
|
78
128
|
source: { path: string; content: string },
|
|
79
129
|
assertions: readonly AssertionResult[],
|
|
130
|
+
sends: readonly SendAnnotation[] = [],
|
|
80
131
|
): AnnotatedEvalSource {
|
|
81
132
|
const normalized = normalizeEvalSource(source.content);
|
|
82
133
|
// 末尾的单个换行符不产出幻影空行(源码文件几乎总以换行符收尾);中间的空行原样保留为一行。
|
|
83
134
|
const body = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
|
|
84
135
|
const rawLines = body.length === 0 && normalized.length === 0 ? [""] : body.split("\n");
|
|
85
136
|
|
|
86
|
-
const lines: AnnotatedSourceLine[] = rawLines.map((text, i) => ({ line: i + 1, text, assertions: [] }));
|
|
137
|
+
const lines: AnnotatedSourceLine[] = rawLines.map((text, i) => ({ line: i + 1, text, assertions: [], sends: [] }));
|
|
138
|
+
|
|
139
|
+
for (const send of sends) {
|
|
140
|
+
// 与断言同一条映射规则;不满足的轮直接丢(全量面在 --execution,见 SendAnnotation)。
|
|
141
|
+
if (send.loc.file === source.path && send.loc.line >= 1 && send.loc.line <= lines.length) {
|
|
142
|
+
lines[send.loc.line - 1]!.sends.push(send);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
87
145
|
|
|
88
146
|
const unmapped: AssertionResult[] = [];
|
|
89
147
|
let mappedAssertions = 0;
|
|
@@ -93,7 +151,7 @@ export function buildAnnotatedEvalSource(
|
|
|
93
151
|
let soft = 0;
|
|
94
152
|
|
|
95
153
|
for (const assertion of assertions) {
|
|
96
|
-
if (assertion.passed) passed++;
|
|
154
|
+
if (assertion.outcome === "passed") passed++;
|
|
97
155
|
else failed++;
|
|
98
156
|
if (assertion.severity === "gate") gate++;
|
|
99
157
|
else soft++;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/results/cases.md
|
|
1
2
|
// loadAttemptEvidence 单测(定稿契约见 plan/attempt-evidence-feedback-loop.md「中性数据准备」、
|
|
2
3
|
// src/results/attempt-evidence.ts 的头注)。用真实 createResultsWriter → openResults 的读写链路
|
|
3
4
|
// 落一份最小 fixture(不手写 JSON 文件,理由同 loadAnnotatedEvalSource 的既有端到端测试:
|
|
@@ -7,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|
|
7
8
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { join } from "node:path";
|
|
11
|
+
import type { DiffArtifact } from "../types.ts";
|
|
10
12
|
import {
|
|
11
13
|
createResultsWriter,
|
|
12
14
|
loadAttemptEvidence,
|
|
@@ -34,7 +36,7 @@ const SOURCE_PATH = "evals/a.eval.ts";
|
|
|
34
36
|
const SOURCE_CONTENT = 'import { defineEval } from "niceeval";\nexport default defineEval({\n test() {},\n});\n';
|
|
35
37
|
|
|
36
38
|
const ASSERTIONS: EvalResult["assertions"] = [
|
|
37
|
-
{ name: "check-1",
|
|
39
|
+
{ name: "check-1", outcome: "passed" as const, severity: "gate", score: 1, loc: { file: SOURCE_PATH, line: 3 } },
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
const EVENTS: StreamEvent[] = [
|
|
@@ -47,8 +49,8 @@ const TRACE: TraceSpan[] = [
|
|
|
47
49
|
{ traceId: "t1", spanId: "s1", name: "tool.get_weather", startMs: 0, endMs: 10, attributes: { call_id: "c1" } },
|
|
48
50
|
];
|
|
49
51
|
|
|
50
|
-
const NONEMPTY_DIFF:
|
|
51
|
-
const EMPTY_DIFF:
|
|
52
|
+
const NONEMPTY_DIFF: DiffArtifact = [{ window: "s1/t1", changes: { "a.txt": { status: "added", after: "hello" } } }];
|
|
53
|
+
const EMPTY_DIFF: DiffArtifact = [{ window: "s1/t1", changes: {} }];
|
|
52
54
|
|
|
53
55
|
/** 起一个 writer,写一条 attempt,finish,再从头 openResults 读回它的 AttemptHandle。 */
|
|
54
56
|
async function seedAttempt(
|
|
@@ -68,11 +70,11 @@ async function seedAttempt(
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
describe("loadAttemptEvidence", () => {
|
|
71
|
-
it("四个 capability 全部具备:
|
|
73
|
+
it("四个 capability 全部具备:source / execution / timing / diff", async () => {
|
|
72
74
|
const root = await makeRoot();
|
|
73
75
|
const attempt = await seedAttempt(
|
|
74
76
|
root,
|
|
75
|
-
{ id: "weather/brooklyn", assertions: ASSERTIONS },
|
|
77
|
+
{ id: "weather/brooklyn", assertions: ASSERTIONS, phases: [{ name: "eval.run", durationMs: 900 }] },
|
|
76
78
|
{ events: EVENTS, trace: TRACE, diff: NONEMPTY_DIFF, sources: [{ path: SOURCE_PATH, content: SOURCE_CONTENT }] },
|
|
77
79
|
);
|
|
78
80
|
|
|
@@ -86,12 +88,12 @@ describe("loadAttemptEvidence", () => {
|
|
|
86
88
|
const actionNode = evidence.execution!.nodes.find((n) => n.kind === "action");
|
|
87
89
|
expect(actionNode).toBeDefined();
|
|
88
90
|
expect((actionNode as { span?: TraceSpan }).span).toBeDefined();
|
|
89
|
-
expect(evidence.diff).toEqual(NONEMPTY_DIFF);
|
|
91
|
+
expect(evidence.diff?.windows).toEqual(NONEMPTY_DIFF);
|
|
90
92
|
|
|
91
|
-
expect(evidence.capabilities).toEqual({
|
|
93
|
+
expect(evidence.capabilities).toEqual({ source: true, execution: true, timing: true, diff: true });
|
|
92
94
|
});
|
|
93
95
|
|
|
94
|
-
it("四个 capability 全部缺失:没有
|
|
96
|
+
it("四个 capability 全部缺失:没有 sources / events / trace / diff,不崩溃", async () => {
|
|
95
97
|
const root = await makeRoot();
|
|
96
98
|
const attempt = await seedAttempt(root, { id: "weather/queens" });
|
|
97
99
|
|
|
@@ -100,10 +102,10 @@ describe("loadAttemptEvidence", () => {
|
|
|
100
102
|
expect(evidence.evalSource).toBeNull();
|
|
101
103
|
expect(evidence.execution).toBeNull();
|
|
102
104
|
expect(evidence.diff).toBeNull();
|
|
103
|
-
expect(evidence.capabilities).toEqual({
|
|
105
|
+
expect(evidence.capabilities).toEqual({ source: false, execution: false, timing: false, diff: false });
|
|
104
106
|
});
|
|
105
107
|
|
|
106
|
-
it("有 events 没有
|
|
108
|
+
it("有 events 没有 phases(旧 runner 产出):execution 为真、timing 为假", async () => {
|
|
107
109
|
const root = await makeRoot();
|
|
108
110
|
const attempt = await seedAttempt(root, { id: "weather/albany" }, { events: EVENTS });
|
|
109
111
|
|
|
@@ -122,7 +124,7 @@ describe("loadAttemptEvidence", () => {
|
|
|
122
124
|
|
|
123
125
|
const evidence = await loadAttemptEvidence(attempt);
|
|
124
126
|
|
|
125
|
-
expect(evidence.diff).toEqual(EMPTY_DIFF);
|
|
127
|
+
expect(evidence.diff?.windows).toEqual(EMPTY_DIFF);
|
|
126
128
|
expect(evidence.capabilities.diff).toBe(false);
|
|
127
129
|
});
|
|
128
130
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// "有什么可看"这件事产生同一个答案(没有),只是原因不同,原因留给 unavailable 文案区分,
|
|
13
13
|
// 不留给 capability 位区分(capability 位只回答"要不要显示这个证据切面")。
|
|
14
14
|
//
|
|
15
|
-
//
|
|
15
|
+
// source —— evalSource 非空 AND (至少有一行源码 或 至少有一条断言);理论上
|
|
16
16
|
// buildAnnotatedEvalSource 对空文件也会产出一行 ""(split("\n") 的自然结果),
|
|
17
17
|
// 所以这条件实践中约等于"evalSource 非空",按文档口径原样写出两个子句,
|
|
18
18
|
// 不依赖这个实现细节。
|
|
@@ -31,7 +31,7 @@ import { join } from "node:path";
|
|
|
31
31
|
import type { AttemptHandle } from "./types.ts";
|
|
32
32
|
import { type AttemptIdentity, type AttemptLocator, encodeAttemptLocator } from "./locator.ts";
|
|
33
33
|
import { loadAnnotatedEvalSource } from "./attempt-source.ts";
|
|
34
|
-
import type
|
|
34
|
+
import { deriveSendAnnotations, type AnnotatedEvalSource } from "./annotated-source.ts";
|
|
35
35
|
import { buildExecutionTree, type ExecutionTree } from "../o11y/execution-tree.ts";
|
|
36
36
|
import type { DiffData, EvalResult } from "../types.ts";
|
|
37
37
|
|
|
@@ -59,11 +59,11 @@ export interface EvidencePaths {
|
|
|
59
59
|
* 对应的证据切面,不自己重新判断"这个 attempt 有没有 X"。
|
|
60
60
|
*/
|
|
61
61
|
export interface AttemptEvidenceCapabilities {
|
|
62
|
-
/** evalSource 非空且有内容(源码行或断言二选一非空)
|
|
63
|
-
|
|
62
|
+
/** evalSource 非空且有内容(源码行或断言二选一非空);对应 CLI 的 `--source` 证据切面。 */
|
|
63
|
+
source: boolean;
|
|
64
64
|
/** events 非空数组——有骨架可看。 */
|
|
65
65
|
execution: boolean;
|
|
66
|
-
/**
|
|
66
|
+
/** result.json 带 phases(阶段计时)——`--timing` 时间树可渲染。 */
|
|
67
67
|
timing: boolean;
|
|
68
68
|
/** diff 非空且至少改动/删除了一个文件。 */
|
|
69
69
|
diff: boolean;
|
|
@@ -79,13 +79,15 @@ export interface AttemptEvidence {
|
|
|
79
79
|
* 的调用方直接读结构化字段(如按 experimentId 分组、按时间排序)。 */
|
|
80
80
|
identity: AttemptIdentity;
|
|
81
81
|
result: EvalResult;
|
|
82
|
-
/** 运行时保存的 Eval 源码标注(`--
|
|
82
|
+
/** 运行时保存的 Eval 源码标注(`--source` 切面);没有 sources() 就是 null,不伪造空文档。 */
|
|
83
83
|
evalSource: AnnotatedEvalSource | null;
|
|
84
84
|
/** 标准事件流 + OTel enrichment(`--execution` 切面);没有非空 events 就是 null,
|
|
85
85
|
* 不产出一棵只剩 telemetry-only 节点、没有真实骨架的误导性树(见头注 execution 门槛)。 */
|
|
86
86
|
execution: ExecutionTree | null;
|
|
87
87
|
/** 被测 Agent 对 Sandbox 工作区的文件变化(`--diff` 切面);原样透传 attempt.diff()。 */
|
|
88
88
|
diff: DiffData | null;
|
|
89
|
+
/** OTel spans(`--timing` 把 turn 节点下的 agent/model/tool spans 挂回时间树);没有 trace 就是 null。 */
|
|
90
|
+
trace: import("../types.ts").TraceSpan[] | null;
|
|
89
91
|
artifactPaths: EvidencePaths;
|
|
90
92
|
capabilities: AttemptEvidenceCapabilities;
|
|
91
93
|
}
|
|
@@ -110,12 +112,17 @@ export async function loadAttemptEvidence(attempt: AttemptHandle): Promise<Attem
|
|
|
110
112
|
};
|
|
111
113
|
const locator = attempt.locator ?? encodeAttemptLocator(identity);
|
|
112
114
|
|
|
113
|
-
const [
|
|
114
|
-
loadAnnotatedEvalSource(attempt),
|
|
115
|
+
const [events, trace, diff] = await Promise.all([
|
|
115
116
|
attempt.events(),
|
|
116
117
|
attempt.trace(),
|
|
117
118
|
attempt.diff(),
|
|
118
119
|
]);
|
|
120
|
+
// send 标注要拿事件流里用户消息的 loc 与阶段时间树里的 turn 节点配对,所以在 events
|
|
121
|
+
// 就绪后再装配 evalSource;派生与分桶都是纯函数(annotated-source.ts),这里只接线。
|
|
122
|
+
const evalSource = await loadAnnotatedEvalSource(
|
|
123
|
+
attempt,
|
|
124
|
+
deriveSendAnnotations(events, attempt.result.phases),
|
|
125
|
+
);
|
|
119
126
|
|
|
120
127
|
// execution 的 null 边界:没有非空事件骨架就是 null,即使 trace 里有 span(那些 span 在
|
|
121
128
|
// buildExecutionTree 里只会全部落进 telemetry-only 桶,产出一棵没有骨架、只有遥测的树——
|
|
@@ -124,9 +131,8 @@ export async function loadAttemptEvidence(attempt: AttemptHandle): Promise<Attem
|
|
|
124
131
|
const execution = hasEvents ? buildExecutionTree(events, trace ?? []) : null;
|
|
125
132
|
|
|
126
133
|
const evalCapable = evalSource !== null && (evalSource.lines.length > 0 || evalSource.summary.totalAssertions > 0);
|
|
127
|
-
// 与 show/render.ts::diffText
|
|
128
|
-
|
|
129
|
-
const diffCapable = diff !== null && (Object.keys(diff.generatedFiles).length > 0 || diff.deletedFiles.length > 0);
|
|
134
|
+
// 与 show/render.ts::diffText 同一口径:任一窗口触及过任一文件才算"有变化"。
|
|
135
|
+
const diffCapable = diff !== null && Object.keys(diff.files).length > 0;
|
|
130
136
|
|
|
131
137
|
return {
|
|
132
138
|
locator,
|
|
@@ -135,11 +141,12 @@ export async function loadAttemptEvidence(attempt: AttemptHandle): Promise<Attem
|
|
|
135
141
|
evalSource,
|
|
136
142
|
execution,
|
|
137
143
|
diff,
|
|
144
|
+
trace,
|
|
138
145
|
artifactPaths: { dir: attemptArtifactDir(attempt) },
|
|
139
146
|
capabilities: {
|
|
140
|
-
|
|
147
|
+
source: evalCapable,
|
|
141
148
|
execution: execution !== null,
|
|
142
|
-
timing:
|
|
149
|
+
timing: (attempt.result.phases?.length ?? 0) > 0,
|
|
143
150
|
diff: diffCapable,
|
|
144
151
|
},
|
|
145
152
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 或参照。
|
|
9
9
|
|
|
10
10
|
import type { AttemptHandle } from "./types.ts";
|
|
11
|
-
import { buildAnnotatedEvalSource, type AnnotatedEvalSource } from "./annotated-source.ts";
|
|
11
|
+
import { buildAnnotatedEvalSource, type AnnotatedEvalSource, type SendAnnotation } from "./annotated-source.ts";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 给定一个 attempt,取回它的 eval 源码(经 sources() 解引用,可能来自本快照或
|
|
@@ -22,7 +22,10 @@ import { buildAnnotatedEvalSource, type AnnotatedEvalSource } from "./annotated-
|
|
|
22
22
|
* 只服务这条打通链路的验证目的,不是最终选择策略(后续阶段如需要为每份引用到的文件
|
|
23
23
|
* 都出一份 AnnotatedEvalSource,在这基础上很容易扩成返回数组)。
|
|
24
24
|
*/
|
|
25
|
-
export async function loadAnnotatedEvalSource(
|
|
25
|
+
export async function loadAnnotatedEvalSource(
|
|
26
|
+
attempt: AttemptHandle,
|
|
27
|
+
sends: readonly SendAnnotation[] = [],
|
|
28
|
+
): Promise<AnnotatedEvalSource | null> {
|
|
26
29
|
const sources = await attempt.sources();
|
|
27
30
|
if (!sources || sources.length === 0) return null;
|
|
28
31
|
|
|
@@ -44,5 +47,5 @@ export async function loadAnnotatedEvalSource(attempt: AttemptHandle): Promise<A
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
return buildAnnotatedEvalSource(primary, assertions);
|
|
50
|
+
return buildAnnotatedEvalSource(primary, assertions, sends);
|
|
48
51
|
}
|