niceeval 0.9.1 → 0.10.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 +31 -33
- package/dist/agents/types.d.ts +20 -17
- package/dist/context/types.d.ts +21 -21
- package/dist/i18n/en.d.ts +10 -2
- package/dist/i18n/zh-CN.d.ts +14 -6
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/report/{react → assets}/colors.d.ts +8 -0
- package/dist/report/{react → assets}/colors.js +23 -0
- package/dist/report/built-in/index.d.ts +2 -2
- package/dist/report/built-in/index.js +2 -2
- package/dist/report/built-in/standard.d.ts +8 -1
- package/dist/report/built-in/standard.js +17 -7
- package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
- package/dist/report/components/attempt-detail/AttemptConversation.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptConversation.js +34 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
- package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
- package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptError.js +16 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
- package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptSource.js +27 -0
- package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
- package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
- package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
- package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
- package/dist/report/components/attempt-detail/compute.d.ts +24 -0
- package/dist/report/components/attempt-detail/compute.js +254 -0
- package/dist/report/components/attempt-detail/faces.d.ts +14 -0
- package/dist/report/components/attempt-detail/faces.js +235 -0
- package/dist/report/components/attempt-detail/index.d.ts +38 -0
- package/dist/report/components/attempt-detail/index.js +527 -0
- package/dist/report/{react → components}/cell.d.ts +5 -3
- package/dist/report/{react → components}/cell.js +3 -3
- package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
- package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
- package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
- package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
- package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +3 -3
- package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
- package/dist/report/components/entity-lists/compute.d.ts +23 -0
- package/dist/report/components/entity-lists/compute.js +171 -0
- package/dist/report/components/entity-lists/faces.d.ts +5 -0
- package/dist/report/components/entity-lists/faces.js +174 -0
- package/dist/report/components/entity-lists/index.d.ts +51 -0
- package/dist/report/components/entity-lists/index.js +165 -0
- package/dist/report/{react → components}/fixtures.d.ts +2 -2
- package/dist/report/{react → components}/fixtures.js +1 -1
- package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
- package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
- package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
- package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
- package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
- package/dist/report/{react → components/metric-views}/MetricScatter.js +26 -23
- package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
- package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
- package/dist/report/components/metric-views/compute.d.ts +89 -0
- package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
- package/dist/report/components/metric-views/faces.d.ts +13 -0
- package/dist/report/components/metric-views/faces.js +381 -0
- package/dist/report/components/metric-views/index.d.ts +50 -0
- package/dist/report/components/metric-views/index.js +273 -0
- package/dist/report/{text → components/metric-views}/plot.js +1 -1
- package/dist/report/components/shared-compute.d.ts +22 -0
- package/dist/report/components/shared-compute.js +47 -0
- package/dist/report/components/shared-faces.d.ts +9 -0
- package/dist/report/components/shared-faces.js +26 -0
- package/dist/report/components/shared.d.ts +68 -0
- package/dist/report/components/shared.js +125 -0
- package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
- package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
- package/dist/report/components/site-components/compute.d.ts +28 -0
- package/dist/report/components/site-components/compute.js +132 -0
- package/dist/report/components/site-components/faces.d.ts +21 -0
- package/dist/report/components/site-components/faces.js +75 -0
- package/dist/report/components/site-components/index.d.ts +74 -0
- package/dist/report/components/site-components/index.js +220 -0
- package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
- package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
- package/dist/report/components/summaries/compute.d.ts +7 -0
- package/dist/report/components/summaries/compute.js +51 -0
- package/dist/report/components/summaries/faces.d.ts +7 -0
- package/dist/report/components/summaries/faces.js +38 -0
- package/dist/report/components/summaries/index.d.ts +27 -0
- package/dist/report/components/summaries/index.js +78 -0
- package/dist/report/definition/grid-layout.d.ts +50 -0
- package/dist/report/definition/grid-layout.js +89 -0
- package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
- package/dist/report/{primitives.js → definition/primitives.js} +101 -10
- package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
- package/dist/report/{report.js → definition/report.js} +58 -90
- package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
- package/dist/report/{text/table.js → definition/table-text.js} +2 -2
- package/dist/report/{tree.d.ts → definition/tree.d.ts} +43 -11
- package/dist/report/{tree.js → definition/tree.js} +9 -11
- package/dist/report/index.d.ts +33 -18
- package/dist/report/index.js +23 -14
- package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
- package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
- package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
- package/dist/report/{flag.js → model/flag.js} +35 -0
- package/dist/report/{format.d.ts → model/format.d.ts} +11 -5
- package/dist/report/{format.js → model/format.js} +48 -4
- package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
- package/dist/report/{locale.js → model/locale.js} +31 -33
- package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
- package/dist/report/{metrics.js → model/metrics.js} +2 -2
- package/dist/report/{types.d.ts → model/types.d.ts} +140 -20
- package/dist/report/react/index.d.ts +32 -21
- package/dist/report/react/index.js +33 -21
- package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
- package/dist/report/{load.js → runtime/load.js} +1 -1
- package/dist/report/runtime/text.d.ts +67 -0
- package/dist/report/runtime/text.js +114 -0
- package/dist/report/runtime/web.d.ts +35 -0
- package/dist/report/runtime/web.js +70 -0
- package/dist/results/annotated-source.d.ts +87 -0
- package/dist/results/attempt-evidence.d.ts +74 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/select.d.ts +11 -1
- package/dist/results/select.js +27 -5
- package/dist/runner/eval-selection.d.ts +26 -0
- package/dist/runner/feedback/sink.d.ts +26 -1
- package/dist/runner/types.d.ts +149 -23
- package/dist/sandbox/docker.d.ts +6 -0
- package/dist/sandbox/types.d.ts +23 -21
- package/dist/scoring/display.d.ts +7 -2
- package/dist/scoring/display.js +42 -4
- package/dist/scoring/types.d.ts +3 -3
- package/dist/shared/aggregate.d.ts +14 -0
- package/dist/shared/aggregate.js +31 -0
- package/dist/shared/types.d.ts +6 -1
- package/docs-site/images/logo-dark.svg +7 -0
- package/docs-site/images/logo.svg +6 -5
- package/docs-site/zh/README.md +8 -7
- package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
- package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
- package/docs-site/zh/examples/index.mdx +3 -3
- package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +1 -1
- package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
- package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
- package/docs-site/zh/explanation/adapter.mdx +19 -19
- package/docs-site/zh/explanation/assert.mdx +9 -9
- package/docs-site/zh/explanation/drive.mdx +4 -4
- package/docs-site/zh/explanation/evals.mdx +8 -8
- package/docs-site/zh/explanation/experiment.mdx +8 -8
- package/docs-site/zh/explanation/hitl.mdx +13 -13
- package/docs-site/zh/explanation/judge.mdx +3 -3
- package/docs-site/zh/explanation/overview.mdx +7 -7
- package/docs-site/zh/explanation/runner.mdx +11 -11
- package/docs-site/zh/explanation/tier.mdx +6 -6
- package/docs-site/zh/index.mdx +14 -14
- package/docs-site/zh/introduction.mdx +13 -16
- package/docs-site/zh/reference/builtin-agents.mdx +61 -28
- package/docs-site/zh/reference/capabilities.mdx +7 -27
- package/docs-site/zh/reference/cli.mdx +35 -32
- package/docs-site/zh/reference/define-agent.mdx +32 -31
- package/docs-site/zh/reference/define-config.mdx +6 -6
- package/docs-site/zh/reference/define-eval.mdx +33 -20
- package/docs-site/zh/reference/events.mdx +4 -4
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/docs-site/zh/reference/official-adapters.mdx +17 -17
- package/docs-site/zh/reference/report-components.mdx +193 -121
- package/docs-site/zh/reference/results-data.mdx +13 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
- package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
- package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
- package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
- package/docs-site/zh/tutorials/authoring.mdx +211 -0
- package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
- package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
- package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
- package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
- package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
- package/docs-site/zh/tutorials/experiments.mdx +103 -0
- package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
- package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
- package/docs-site/zh/tutorials/quickstart.mdx +18 -18
- package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
- package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +49 -49
- package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
- package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
- package/package.json +8 -4
- package/src/agents/bub.ts +21 -8
- package/src/agents/claude-code.ts +23 -10
- package/src/agents/codex.test.ts +22 -7
- package/src/agents/codex.ts +22 -9
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/post-setup.ts +45 -22
- package/src/agents/streaming.ts +1 -1
- package/src/agents/types.ts +20 -17
- package/src/agents/ui-message-stream.test.ts +10 -0
- package/src/agents/ui-message-stream.ts +12 -1
- package/src/cli.ts +71 -57
- package/src/context/types.ts +21 -21
- package/src/define.ts +13 -0
- package/src/i18n/en.ts +27 -12
- package/src/i18n/zh-CN.ts +27 -12
- package/src/index.ts +2 -0
- package/src/report/{react → assets}/colors.ts +22 -0
- package/src/report/{react → assets}/enhance.js +1 -33
- package/src/report/{react → assets}/styles.css +124 -106
- package/src/report/built-in/index.tsx +2 -2
- package/src/report/built-in/standard.tsx +18 -6
- package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
- package/src/report/components/attempt-detail/AttemptConversation.tsx +110 -0
- package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
- package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
- package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
- package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
- package/src/report/components/attempt-detail/AttemptSource.tsx +80 -0
- package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
- package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
- package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
- package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
- package/src/report/components/attempt-detail/attempt-components.test.tsx +680 -0
- package/src/report/components/attempt-detail/compute.ts +287 -0
- package/src/report/components/attempt-detail/faces.ts +257 -0
- package/src/report/components/attempt-detail/index.tsx +583 -0
- package/src/report/components/attempt-detail/validate.test.ts +235 -0
- package/src/report/{react → components}/cell.tsx +6 -3
- package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
- package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
- package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
- package/src/report/{react → components/entity-lists}/ExperimentList.tsx +23 -20
- package/src/report/components/entity-lists/compute.ts +196 -0
- package/src/report/components/entity-lists/faces.ts +207 -0
- package/src/report/components/entity-lists/index.tsx +241 -0
- package/src/report/components/entity-lists/validate.test.ts +114 -0
- package/src/report/{react → components}/fixtures.ts +2 -2
- package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
- package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
- package/src/report/{react → components/metric-views}/MetricScatter.tsx +41 -33
- package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
- package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
- package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
- package/src/report/components/metric-views/faces.ts +423 -0
- package/src/report/components/metric-views/index.tsx +352 -0
- package/src/report/{text → components/metric-views}/plot.ts +1 -1
- package/src/report/components/metric-views/validate.test.ts +211 -0
- package/src/report/{react → components}/render.test.tsx +47 -17
- package/src/report/components/shared-compute.ts +59 -0
- package/src/report/components/shared-faces.ts +30 -0
- package/src/report/components/shared.ts +192 -0
- package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
- package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
- package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
- package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
- package/src/report/components/site-components/compute.ts +144 -0
- package/src/report/components/site-components/faces.ts +80 -0
- package/src/report/components/site-components/index.tsx +265 -0
- package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
- package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
- package/src/report/components/site-components/validate.test.ts +89 -0
- package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
- package/src/report/components/summaries/compute.ts +56 -0
- package/src/report/components/summaries/faces.ts +48 -0
- package/src/report/components/summaries/index.tsx +127 -0
- package/src/report/components/summaries/validate.test.ts +49 -0
- package/src/report/definition/grid-layout.test.ts +124 -0
- package/src/report/definition/grid-layout.ts +146 -0
- package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
- package/src/report/{report.ts → definition/report.ts} +98 -165
- package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
- package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
- package/src/report/{tree.ts → definition/tree.ts} +56 -20
- package/src/report/index.ts +126 -66
- package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
- package/src/report/{flag.ts → model/flag.ts} +39 -1
- package/src/report/{format.ts → model/format.ts} +59 -5
- package/src/report/{locale.ts → model/locale.ts} +34 -36
- package/src/report/{metrics.ts → model/metrics.ts} +3 -3
- package/src/report/{types.ts → model/types.ts} +143 -22
- package/src/report/react/index.tsx +52 -26
- package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
- package/src/report/runtime/host.test.ts +44 -0
- package/src/report/runtime/host.ts +138 -0
- package/src/report/{load.ts → runtime/load.ts} +1 -1
- package/src/report/runtime/text.ts +192 -0
- package/src/report/runtime/web.ts +106 -0
- package/src/results/attempt-evidence.ts +5 -1
- package/src/results/host-equivalence.test.ts +53 -2
- package/src/results/select.ts +29 -5
- package/src/runner/attempt.test.ts +48 -1
- package/src/runner/attempt.ts +62 -42
- package/src/runner/cleanup-timeout.test.ts +16 -0
- package/src/runner/cleanup-timeout.ts +23 -0
- package/src/runner/discover.ts +1 -2
- package/src/runner/eval-selection.test.ts +187 -0
- package/src/runner/eval-selection.ts +109 -0
- package/src/runner/experiment-cleanup-registry.test.ts +89 -0
- package/src/runner/experiment-cleanup-registry.ts +39 -0
- package/src/runner/experiment-labels.test.ts +71 -0
- package/src/runner/feedback/agent.test.ts +33 -1
- package/src/runner/feedback/agent.ts +12 -16
- package/src/runner/feedback/ci.test.ts +35 -1
- package/src/runner/feedback/ci.ts +16 -18
- package/src/runner/feedback/coordinator.ts +28 -0
- package/src/runner/feedback/human.test.ts +108 -1
- package/src/runner/feedback/human.ts +71 -21
- package/src/runner/feedback/reducer.test.ts +64 -0
- package/src/runner/feedback/reducer.ts +26 -0
- package/src/runner/feedback/sink.ts +40 -1
- package/src/runner/fingerprint.ts +2 -1
- package/src/runner/report.test.ts +14 -0
- package/src/runner/report.ts +3 -1
- package/src/runner/run.test.ts +390 -12
- package/src/runner/run.ts +259 -26
- package/src/runner/sandbox-selection.test.ts +13 -3
- package/src/runner/sandbox-selection.ts +4 -2
- package/src/runner/types.ts +158 -23
- package/src/sandbox/docker.ts +7 -0
- package/src/sandbox/e2b.ts +12 -13
- package/src/sandbox/types.ts +23 -21
- package/src/scoring/display.test.ts +28 -2
- package/src/scoring/display.ts +38 -5
- package/src/scoring/types.ts +3 -3
- package/src/shared/aggregate.test.ts +52 -0
- package/src/shared/aggregate.ts +29 -0
- package/src/shared/types.ts +6 -1
- package/src/show/command.test.ts +34 -0
- package/src/show/command.ts +16 -0
- package/src/show/compose.ts +1 -1
- package/src/show/index.ts +56 -20
- package/src/show/render.ts +15 -239
- package/src/show/show.test.ts +127 -34
- package/src/view/app/App.test.tsx +1 -1
- package/src/view/app/App.tsx +119 -47
- package/src/view/app/i18n.ts +7 -147
- package/src/view/app/lib/attempt-dialog.test.ts +65 -0
- package/src/view/app/lib/attempt-dialog.ts +69 -0
- package/src/view/app/main.tsx +0 -1
- package/src/view/app/types.ts +3 -86
- package/src/view/artifact-serving.test.ts +22 -38
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +15 -22
- package/src/view/data.test.ts +48 -36
- package/src/view/data.ts +105 -83
- package/src/view/index.ts +1 -1
- package/src/view/server.ts +32 -2
- package/src/view/shared/types.ts +6 -31
- package/src/view/site-parity.test.ts +24 -1
- package/src/view/site.ts +144 -17
- package/src/view/styles.css +0 -805
- package/src/view/view-report.test.ts +141 -22
- package/dist/report/components.d.ts +0 -179
- package/dist/report/components.js +0 -544
- package/dist/report/compute.d.ts +0 -139
- package/dist/report/react/ExperimentComparison.d.ts +0 -10
- package/dist/report/react/ExperimentComparison.js +0 -12
- package/dist/report/react/MetricScatter.d.ts +0 -9
- package/dist/report/react/format.d.ts +0 -3
- package/dist/report/react/format.js +0 -7
- package/dist/report/text/faces.d.ts +0 -45
- package/dist/report/text/faces.js +0 -671
- package/dist/report/web.d.ts +0 -32
- package/dist/report/web.js +0 -48
- package/docs-site/zh/how-to/authoring.mdx +0 -162
- package/docs-site/zh/how-to/custom-reports.mdx +0 -414
- package/docs-site/zh/how-to/experiments.mdx +0 -86
- package/docs-site/zh/how-to/write-experiment.mdx +0 -164
- package/src/report/components.tsx +0 -925
- package/src/report/react/ExperimentComparison.tsx +0 -73
- package/src/report/react/format.ts +0 -9
- package/src/report/text/faces.ts +0 -767
- package/src/report/web.ts +0 -77
- package/src/show/report-host.test.ts +0 -205
- package/src/show/report-host.ts +0 -389
- package/src/view/app/components/AttemptModal.tsx +0 -496
- package/src/view/app/components/CodeView.test.tsx +0 -142
- package/src/view/app/components/CodeView.tsx +0 -310
- package/src/view/app/components/CopyControls.tsx +0 -106
- package/src/view/app/components/Trace.tsx +0 -100
- package/src/view/app/components/Transcript.tsx +0 -157
- package/src/view/app/components/ui/badge.tsx +0 -21
- package/src/view/app/lib/artifact-url.ts +0 -17
- package/src/view/app/lib/attempt-route.test.ts +0 -80
- package/src/view/app/lib/attempt-route.ts +0 -52
- package/src/view/app/lib/format.ts +0 -70
- package/src/view/app/lib/guards.test.ts +0 -108
- package/src/view/app/lib/guards.ts +0 -71
- package/src/view/app/lib/rows.ts +0 -22
- package/src/view/app/lib/transcript-data.tsx +0 -151
- package/src/view/app/lib/verdict.ts +0 -23
- package/src/view/app/shared.ts +0 -8
- /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
- /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
- /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
- /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
- /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
- /package/dist/report/{types.js → model/types.js} +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
- /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
- /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// 计算函数(*Data):ReportInput → 一份组件数据。实体列表族(ExperimentList / EvalList /
|
|
2
|
+
// AttemptList / FailureList)的 *Data 都住在这里(docs/feature/reports/library/entity-lists.md)。
|
|
3
|
+
//
|
|
4
|
+
// 共同约定(docs/feature/reports/architecture.md「指标聚合不变量」):
|
|
5
|
+
// - 第一参收 ReportInput = Scope | readonly Snapshot[];warnings 不进组件数据(宿主统一显示);
|
|
6
|
+
// - 聚合前按身份键去重(dedupeAttempts;missing-startedAt 不去重、如实保留、不透出警告);
|
|
7
|
+
// - null ≠ 0:缺数据不编数,覆盖率经 samples/total 如实暴露;
|
|
8
|
+
// - core 中立:只认 Metric / Dimension 接口,不出现具体 agent 名的分支。
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AttemptListItem,
|
|
12
|
+
EvalListItem,
|
|
13
|
+
ExperimentListEvalRow,
|
|
14
|
+
ExperimentListItem,
|
|
15
|
+
ReportInput,
|
|
16
|
+
} from "../../model/types.ts";
|
|
17
|
+
import type { EvalResult } from "../../../types.ts";
|
|
18
|
+
import type { Snapshot } from "../../../results/types.ts";
|
|
19
|
+
import { comparabilityConfigOf, deepEqualJson } from "../../../results/select.ts";
|
|
20
|
+
import { foldEvalVerdict } from "../../../shared/verdict.ts";
|
|
21
|
+
import {
|
|
22
|
+
collectItems,
|
|
23
|
+
computeCell,
|
|
24
|
+
evalIdOf,
|
|
25
|
+
experimentIdOf,
|
|
26
|
+
fullEvalKey,
|
|
27
|
+
groupItems,
|
|
28
|
+
locatorOf,
|
|
29
|
+
resolveInput,
|
|
30
|
+
type Item,
|
|
31
|
+
} from "../../model/aggregate.ts";
|
|
32
|
+
import { attemptCostUSD, costUSD, durationMs, endToEndPassRate, examScore, tokens } from "../../model/metrics.ts";
|
|
33
|
+
import { compactAssertionSummary, primaryAssertionSummary, summaryText } from "../../../scoring/display.ts";
|
|
34
|
+
import { selectedEvalsOnly, summarizeItems } from "../shared-compute.ts";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 一次 attempt 的单行结果摘要(Scoring display 契约):failed 取主失败断言摘要(不含
|
|
38
|
+
* "+N more",N 单独进 moreFailures),errored 取结构化 error 的一层摘要
|
|
39
|
+
* (phase · code · message),passed / skipped 为 null。
|
|
40
|
+
*/
|
|
41
|
+
export function failureSummaryOf(result: EvalResult): { summary: string | null; more: number } {
|
|
42
|
+
if (result.verdict === "errored" && result.error !== undefined) {
|
|
43
|
+
const parts = [result.error.phase, result.error.code, result.error.message].filter(
|
|
44
|
+
(part): part is string => typeof part === "string" && part.length > 0,
|
|
45
|
+
);
|
|
46
|
+
return { summary: summaryText(parts.join(" · ")), more: 0 };
|
|
47
|
+
}
|
|
48
|
+
if (result.verdict === "failed" || result.verdict === "errored") {
|
|
49
|
+
const primary = primaryAssertionSummary(result.assertions, result.verdict);
|
|
50
|
+
if (primary !== undefined) {
|
|
51
|
+
return {
|
|
52
|
+
summary: compactAssertionSummary({ ...primary, additionalFailures: 0 }),
|
|
53
|
+
more: primary.additionalFailures,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (result.verdict === "errored" && result.skipReason !== undefined) {
|
|
57
|
+
return { summary: summaryText(result.skipReason), more: 0 };
|
|
58
|
+
}
|
|
59
|
+
return { summary: null, more: 0 };
|
|
60
|
+
}
|
|
61
|
+
return { summary: null, more: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** AttemptList / ExperimentList / EvalList 共用的叶子构造:一个 Item → 一个 AttemptListItem。 */
|
|
65
|
+
async function attemptListItemOf(item: Item): Promise<AttemptListItem> {
|
|
66
|
+
const result = item.attempt.result;
|
|
67
|
+
const { summary, more } = failureSummaryOf(result);
|
|
68
|
+
return {
|
|
69
|
+
experimentId: experimentIdOf(item),
|
|
70
|
+
evalId: evalIdOf(item),
|
|
71
|
+
attempt: result.attempt,
|
|
72
|
+
agent: result.agent,
|
|
73
|
+
verdict: result.verdict,
|
|
74
|
+
failureSummary: summary,
|
|
75
|
+
moreFailures: more,
|
|
76
|
+
examScore: await computeCell(examScore, [item]),
|
|
77
|
+
durationMs: result.durationMs,
|
|
78
|
+
costUSD: attemptCostUSD(result),
|
|
79
|
+
locator: locatorOf(item),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `attemptListData(input)`:每个 Attempt 一项,顺序取自 Scope 展平顺序(不重排)。 */
|
|
84
|
+
export async function attemptListData(input: ReportInput): Promise<AttemptListItem[]> {
|
|
85
|
+
const { snapshots } = resolveInput(input);
|
|
86
|
+
const items = collectItems(snapshots);
|
|
87
|
+
return Promise.all(items.map((item) => attemptListItemOf(item)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `evalListData(input)`:每个 `experimentId + evalId` 一项,按 evalId 再按 experimentId 升序。 */
|
|
91
|
+
export async function evalListData(input: ReportInput): Promise<EvalListItem[]> {
|
|
92
|
+
const { snapshots } = resolveInput(input);
|
|
93
|
+
const items = collectItems(snapshots);
|
|
94
|
+
const groups = new Map<string, Item[]>();
|
|
95
|
+
for (const item of items) {
|
|
96
|
+
const key = fullEvalKey(item);
|
|
97
|
+
const list = groups.get(key);
|
|
98
|
+
if (list) list.push(item);
|
|
99
|
+
else groups.set(key, [item]);
|
|
100
|
+
}
|
|
101
|
+
const out: EvalListItem[] = [];
|
|
102
|
+
for (const group of groups.values()) {
|
|
103
|
+
const sorted = [...group].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
104
|
+
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
105
|
+
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item)));
|
|
106
|
+
out.push({
|
|
107
|
+
experimentId: experimentIdOf(sorted[0]!),
|
|
108
|
+
evalId: evalIdOf(sorted[0]!),
|
|
109
|
+
verdict,
|
|
110
|
+
examScore: await computeCell(examScore, sorted),
|
|
111
|
+
durationMs: await computeCell(durationMs, sorted),
|
|
112
|
+
costUSD: await computeCell(costUSD, sorted),
|
|
113
|
+
attempts,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
out.sort((a, b) => a.evalId.localeCompare(b.evalId) || a.experimentId.localeCompare(b.experimentId));
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `experimentListData(input)`:每个 experiment 一项,展开到每道 Eval;初始按端到端通过率
|
|
122
|
+
* 从高到低(缺数据沉底,同分按 id)。一行只有一套 agent / model / flags 是输入约束:
|
|
123
|
+
* 宿主注入的 current() Scope 保证每个 experiment 只由可比性配置一致的快照拼成;作者自选
|
|
124
|
+
* Snapshot[] 时若同一 experiment 混入不一致的可比性配置,按完整用户反馈失败并指引——
|
|
125
|
+
* 看跨配置演化用 snapshot 维度或 MetricLine,不把两套配置拼成一行冒充单一配置。
|
|
126
|
+
*/
|
|
127
|
+
export async function experimentListData(input: ReportInput): Promise<ExperimentListItem[]> {
|
|
128
|
+
const { snapshots: rawSnapshots } = resolveInput(input);
|
|
129
|
+
const snapshots = selectedEvalsOnly(rawSnapshots);
|
|
130
|
+
|
|
131
|
+
// 可比性配置单义检查:同一 experiment 的输入快照必须共享一套可比性配置。
|
|
132
|
+
const configByExperiment = new Map<string, { snapshot: Snapshot; config: unknown }>();
|
|
133
|
+
for (const snapshot of snapshots) {
|
|
134
|
+
const config = comparabilityConfigOf(snapshot);
|
|
135
|
+
const existing = configByExperiment.get(snapshot.experimentId);
|
|
136
|
+
if (existing === undefined) {
|
|
137
|
+
configByExperiment.set(snapshot.experimentId, { snapshot, config });
|
|
138
|
+
} else if (!deepEqualJson(existing.config, config)) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`experimentListData got inconsistent comparability configs for experiment "${snapshot.experimentId}" ` +
|
|
141
|
+
`(snapshots ${existing.snapshot.startedAt} and ${snapshot.startedAt} differ in agent/model/reasoningEffort/flags/budget/timeoutMs/sandbox). ` +
|
|
142
|
+
"One row shows one configuration — it cannot honestly merge two. To chart evolution across configs, " +
|
|
143
|
+
'use the "snapshot" dimension or MetricLine; to show the current level, pass results.current() which selects a single config per experiment.',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const items = collectItems(snapshots);
|
|
149
|
+
const groups = groupItems(items, "experiment");
|
|
150
|
+
const out: ExperimentListItem[] = [];
|
|
151
|
+
for (const [experimentId, group] of groups) {
|
|
152
|
+
const stats = summarizeItems(group);
|
|
153
|
+
const newest = [...group].sort((a, b) => b.snapshot.startedAt.localeCompare(a.snapshot.startedAt))[0]!;
|
|
154
|
+
const evalGroups = groupItems(group, "eval");
|
|
155
|
+
const evalRows: ExperimentListEvalRow[] = [];
|
|
156
|
+
for (const [evalId, evalItems] of evalGroups) {
|
|
157
|
+
const sorted = [...evalItems].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
158
|
+
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
159
|
+
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item)));
|
|
160
|
+
evalRows.push({
|
|
161
|
+
evalId,
|
|
162
|
+
verdict,
|
|
163
|
+
durationMs: await computeCell(durationMs, sorted),
|
|
164
|
+
costUSD: await computeCell(costUSD, sorted),
|
|
165
|
+
attempts,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const experiment = newest.snapshot.experiment ?? newest.attempt.result.experiment;
|
|
169
|
+
const model = newest.attempt.result.model ?? newest.snapshot.model;
|
|
170
|
+
out.push({
|
|
171
|
+
experimentId,
|
|
172
|
+
agent: newest.snapshot.agent || newest.attempt.result.agent,
|
|
173
|
+
...(model !== undefined ? { model } : {}),
|
|
174
|
+
...(experiment?.flags ? { flags: experiment.flags } : {}),
|
|
175
|
+
evalVerdicts: stats.verdicts,
|
|
176
|
+
endToEndPassRate: await computeCell(endToEndPassRate, group),
|
|
177
|
+
costUSD: await computeCell(costUSD, group),
|
|
178
|
+
durationMs: await computeCell(durationMs, group),
|
|
179
|
+
tokens: await computeCell(tokens, group),
|
|
180
|
+
evals: stats.evals,
|
|
181
|
+
attempts: stats.attempts,
|
|
182
|
+
lastRunAt: stats.lastRunAt!,
|
|
183
|
+
evalRows,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// 初始态按端到端通过率(endToEndPassRate)从高到低,缺数据沉底;同分按 experiment id 稳定排序。
|
|
187
|
+
out.sort((a, b) => {
|
|
188
|
+
const va = a.endToEndPassRate.value;
|
|
189
|
+
const vb = b.endToEndPassRate.value;
|
|
190
|
+
if (va === null && vb === null) return a.experimentId.localeCompare(b.experimentId);
|
|
191
|
+
if (va === null) return 1;
|
|
192
|
+
if (vb === null) return -1;
|
|
193
|
+
return vb - va || a.experimentId.localeCompare(b.experimentId);
|
|
194
|
+
});
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// 实体列表族(ExperimentList / EvalList / AttemptList)的 text 面:同一份算好的数据,
|
|
2
|
+
// 渲染成终端字符(niceeval show 的形态)。三面共用的紧凑标记:`locator✓`(判定符紧跟
|
|
3
|
+
// locator,中间不留空格)。ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的
|
|
4
|
+
// 摘要,不重复整段 niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼
|
|
5
|
+
// `niceeval show <locator>`。零 react、零 IO、纯同步。
|
|
6
|
+
|
|
7
|
+
import type { AttemptListItem, EvalListItem, ExperimentListItem } from "../../model/types.ts";
|
|
8
|
+
import type { TextContext } from "../../definition/tree.ts";
|
|
9
|
+
import type { TableColumn, TableRow } from "../../definition/primitives.tsx";
|
|
10
|
+
import {
|
|
11
|
+
experimentDisplayName,
|
|
12
|
+
fitFailureSummary,
|
|
13
|
+
formatDurationMs,
|
|
14
|
+
formatUSD,
|
|
15
|
+
verdictMark,
|
|
16
|
+
} from "../../model/format.ts";
|
|
17
|
+
import { countText, localeText, type ReportLocale } from "../../model/locale.ts";
|
|
18
|
+
import { stringWidth, wrapDisplay } from "../../model/text-layout.ts";
|
|
19
|
+
import { renderTableText } from "../../definition/table-text.ts";
|
|
20
|
+
import { cellText, missingText, verdictTallyText, MISSING_MARK } from "../shared-faces.ts";
|
|
21
|
+
|
|
22
|
+
// ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
|
|
23
|
+
//
|
|
24
|
+
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
25
|
+
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的摘要,不重复整段
|
|
26
|
+
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`。
|
|
27
|
+
|
|
28
|
+
function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict"] }): string {
|
|
29
|
+
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* failureSummary + moreFailures 的展示形态:摘要在计算侧已按 Scoring display 契约折好,
|
|
34
|
+
* 这里只加 "+N more failures" 计数与宽度收口,不重算摘要。
|
|
35
|
+
*/
|
|
36
|
+
function attemptReasonText(item: AttemptListItem, locale: ReportLocale, maxChars: number): string | undefined {
|
|
37
|
+
if (item.failureSummary === null) return undefined;
|
|
38
|
+
const withMore =
|
|
39
|
+
item.moreFailures > 0
|
|
40
|
+
? `${item.failureSummary} · ${countText(locale, "entityList.moreFailures", item.moreFailures)}`
|
|
41
|
+
: item.failureSummary;
|
|
42
|
+
return fitFailureSummary(withMore, Math.max(24, maxChars));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── ExperimentList ──
|
|
46
|
+
|
|
47
|
+
function experimentSummaryTable(
|
|
48
|
+
items: readonly ExperimentListItem[],
|
|
49
|
+
ctx: TextContext,
|
|
50
|
+
relativeTo?: string,
|
|
51
|
+
): string {
|
|
52
|
+
const locale = ctx.locale;
|
|
53
|
+
const compact = ctx.width < 100;
|
|
54
|
+
const columns: TableColumn[] = [
|
|
55
|
+
{ key: "experiment", header: compact && locale === "en" ? "Exp." : localeText(locale, "experimentList.experiment") },
|
|
56
|
+
{ key: "model", header: localeText(locale, "table.model") },
|
|
57
|
+
{ key: "agent", header: localeText(locale, "table.agent") },
|
|
58
|
+
{ key: "duration", header: compact && locale === "en" ? "Avg" : localeText(locale, "experimentList.avgDuration"), align: "right" },
|
|
59
|
+
{ key: "passRate", header: compact && locale === "en" ? "Pass" : localeText(locale, "experimentList.passRate"), align: "right" },
|
|
60
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
61
|
+
{ key: "tokens", header: localeText(locale, "experimentList.tokens"), align: "right" },
|
|
62
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
63
|
+
];
|
|
64
|
+
const rows: TableRow[] = items.map((item) => ({
|
|
65
|
+
key: item.experimentId,
|
|
66
|
+
cells: {
|
|
67
|
+
experiment: experimentDisplayName(item.experimentId, relativeTo),
|
|
68
|
+
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
69
|
+
agent: item.agent,
|
|
70
|
+
duration: cellText(item.durationMs, locale),
|
|
71
|
+
passRate: cellText(item.endToEndPassRate, locale),
|
|
72
|
+
result: verdictTallyText(item.evalVerdicts, locale),
|
|
73
|
+
tokens: cellText(item.tokens, locale),
|
|
74
|
+
cost: cellText(item.costUSD, locale),
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
const metadata = items.flatMap((item) =>
|
|
78
|
+
wrapDisplay(
|
|
79
|
+
`${experimentDisplayName(item.experimentId, relativeTo)}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`,
|
|
80
|
+
Math.max(8, ctx.width - 2),
|
|
81
|
+
).map((line) => ` ${line}`),
|
|
82
|
+
);
|
|
83
|
+
return [renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function experimentDetailTable(item: ExperimentListItem, ctx: TextContext, relativeTo?: string): string {
|
|
87
|
+
const locale = ctx.locale;
|
|
88
|
+
const columns: TableColumn[] = [
|
|
89
|
+
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
90
|
+
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
91
|
+
// Result 是可扫读的失败预览,不是证据面:两行放不下的以 … 收口,完整值走 locator 下钻。
|
|
92
|
+
{ key: "result", header: localeText(locale, "experimentList.result"), maxLines: 2 },
|
|
93
|
+
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
94
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
95
|
+
];
|
|
96
|
+
// Result 的字符预算 ≈ 两行 × 它能分到的列宽(总宽减其它列的自然宽与列距)。这里只做
|
|
97
|
+
// 粗预算;精确的按宽度收口由列的 maxLines 兜底。
|
|
98
|
+
const statusWidth = Math.max(
|
|
99
|
+
stringWidth(localeText(locale, "experimentList.status")),
|
|
100
|
+
...item.evalRows.map((row) => stringWidth(`${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`)),
|
|
101
|
+
);
|
|
102
|
+
const entityWidth = Math.max(
|
|
103
|
+
stringWidth(localeText(locale, "experimentList.evalAttempt")),
|
|
104
|
+
...item.evalRows.flatMap((row) => [stringWidth(row.evalId), ...row.attempts.map((a) => stringWidth(a.locator) + 3)]),
|
|
105
|
+
);
|
|
106
|
+
const fixedWidth = statusWidth + entityWidth + 8 /* duration */ + 6 /* cost */ + 3 * 4; /* 4 段列距 */
|
|
107
|
+
const resultBudget = Math.max(24, (ctx.width - fixedWidth) * 2);
|
|
108
|
+
const rows: TableRow[] = item.evalRows.flatMap((row) => {
|
|
109
|
+
// Eval 父行只承载折叠判定与题级聚合;失败摘要只在 Attempt 子行出现。
|
|
110
|
+
const parent: TableRow = {
|
|
111
|
+
key: row.evalId,
|
|
112
|
+
cells: {
|
|
113
|
+
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
114
|
+
entity: row.evalId,
|
|
115
|
+
result: "",
|
|
116
|
+
duration: localeText(locale, "entityList.average", { value: cellText(row.durationMs, locale) }),
|
|
117
|
+
cost: localeText(locale, "entityList.average", { value: cellText(row.costUSD, locale) }),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const attempts: TableRow[] = row.attempts.map((attempt, index) => ({
|
|
121
|
+
key: attempt.locator,
|
|
122
|
+
cells: {
|
|
123
|
+
status: ` ${verdictMark(attempt.verdict)}`,
|
|
124
|
+
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
125
|
+
result: attemptReasonText(attempt, locale, resultBudget) ?? MISSING_MARK,
|
|
126
|
+
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
127
|
+
cost: attempt.costUSD === null ? null : formatUSD(attempt.costUSD),
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
return [parent, ...attempts];
|
|
131
|
+
});
|
|
132
|
+
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
133
|
+
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags)
|
|
134
|
+
.map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`)
|
|
135
|
+
.join(" · ")}`
|
|
136
|
+
: undefined;
|
|
137
|
+
return [
|
|
138
|
+
experimentDisplayName(item.experimentId, relativeTo),
|
|
139
|
+
flags,
|
|
140
|
+
renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx),
|
|
141
|
+
]
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function experimentListText(items: readonly ExperimentListItem[], ctx: TextContext, relativeTo?: string): string {
|
|
147
|
+
if (items.length === 0) return localeText(ctx.locale, "attemptList.empty");
|
|
148
|
+
return [
|
|
149
|
+
experimentSummaryTable(items, ctx, relativeTo),
|
|
150
|
+
...items.map((item) => experimentDetailTable(item, ctx, relativeTo)),
|
|
151
|
+
].join("\n\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── EvalList ──
|
|
155
|
+
|
|
156
|
+
function evalListAttemptLine(item: AttemptListItem, ctx: TextContext): string {
|
|
157
|
+
// 行式列表同守「Result 最多两行」:预算 = 两行终端宽,超出按尾截收口。
|
|
158
|
+
const reason = attemptReasonText(item, ctx.locale, ctx.width * 2 - stringWidth(locatorBadge(item)) - 6);
|
|
159
|
+
return ` ${locatorBadge(item)}${reason ? ` · ${reason}` : ""}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function evalListText(items: readonly EvalListItem[], ctx: TextContext): string {
|
|
163
|
+
const locale = ctx.locale;
|
|
164
|
+
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
165
|
+
const blocks = items.map((item) => {
|
|
166
|
+
const identity = `${item.evalId} · ${item.experimentId} · ${localeText(locale, `verdict.${item.verdict}`)}`;
|
|
167
|
+
const summary = [
|
|
168
|
+
localeText(locale, "attemptList.score", { score: cellText(item.examScore, locale) }),
|
|
169
|
+
localeText(locale, "overview.attemptsCount", { n: item.attempts.length }),
|
|
170
|
+
localeText(locale, "entityList.average", {
|
|
171
|
+
value: item.durationMs.value === null ? missingText(locale) : formatDurationMs(item.durationMs.value),
|
|
172
|
+
}),
|
|
173
|
+
localeText(locale, "entityList.average", {
|
|
174
|
+
value: item.costUSD.value === null ? missingText(locale) : formatUSD(item.costUSD.value),
|
|
175
|
+
}),
|
|
176
|
+
].join(" · ");
|
|
177
|
+
const attemptLines = item.attempts.map((attempt) => evalListAttemptLine(attempt, ctx));
|
|
178
|
+
return [identity, ` ${summary}`, ...attemptLines].join("\n");
|
|
179
|
+
});
|
|
180
|
+
return blocks.join("\n\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── AttemptList ──
|
|
184
|
+
|
|
185
|
+
/** Attempt 比较卡片:只显示一条主失败摘要(至多两行终端宽);完整 assertions 走 locator 下钻。 */
|
|
186
|
+
function attemptListItemText(item: AttemptListItem, ctx: TextContext): string {
|
|
187
|
+
const head = [
|
|
188
|
+
`${verdictMark(item.verdict)} ${item.locator}`,
|
|
189
|
+
item.evalId,
|
|
190
|
+
item.experimentId,
|
|
191
|
+
formatDurationMs(item.durationMs),
|
|
192
|
+
...(item.costUSD !== null ? [formatUSD(item.costUSD)] : []),
|
|
193
|
+
].join(" · ");
|
|
194
|
+
const lines = [head];
|
|
195
|
+
const reason = attemptReasonText(item, ctx.locale, ctx.width * 2 - 4);
|
|
196
|
+
if (reason) lines.push(` ${reason}`);
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function attemptListText(items: readonly AttemptListItem[], total: number | undefined, ctx: TextContext): string {
|
|
201
|
+
const locale = ctx.locale;
|
|
202
|
+
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
203
|
+
const blocks = items.map((item) => attemptListItemText(item, ctx));
|
|
204
|
+
const remaining = (total ?? items.length) - items.length;
|
|
205
|
+
if (remaining > 0) blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
|
|
206
|
+
return blocks.join("\n\n");
|
|
207
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// 官方双面组件的装配点:web 面(./ExperimentList.tsx / EvalList.tsx / AttemptList.tsx 的纯
|
|
2
|
+
// React 组件)+ text 面(./faces.ts)+ resolve 解析面(spec 形态由管线代调配套 ./compute.ts)。
|
|
3
|
+
// FailureList 是组合组件,内部就是 attemptListData → 过滤 → AttemptList data 形态,不产生
|
|
4
|
+
// 自己的 data。
|
|
5
|
+
|
|
6
|
+
import { defineComponent, type ReportComponent } from "../../definition/tree.ts";
|
|
7
|
+
import type { AttemptListItem, EvalListItem, ExperimentListItem, ReportInput } from "../../model/types.ts";
|
|
8
|
+
import type { AttemptLocator } from "../../../results/locator.ts";
|
|
9
|
+
import { collectItems, locatorOf, resolveInput } from "../../model/aggregate.ts";
|
|
10
|
+
import type { ReportLocale } from "../../model/locale.ts";
|
|
11
|
+
import {
|
|
12
|
+
arrayProblem,
|
|
13
|
+
cellProblem,
|
|
14
|
+
isObject,
|
|
15
|
+
makeDataComponent,
|
|
16
|
+
hrefOf,
|
|
17
|
+
tallyProblem,
|
|
18
|
+
type ChromeProps,
|
|
19
|
+
type DataProps,
|
|
20
|
+
type Validator,
|
|
21
|
+
} from "../shared.ts";
|
|
22
|
+
import { attemptListData, evalListData, experimentListData } from "./compute.ts";
|
|
23
|
+
import { attemptListText, evalListText, experimentListText } from "./faces.ts";
|
|
24
|
+
import { AttemptList as AttemptListWeb } from "./AttemptList.tsx";
|
|
25
|
+
import { EvalList as EvalListWeb } from "./EvalList.tsx";
|
|
26
|
+
import { ExperimentList as ExperimentListWeb } from "./ExperimentList.tsx";
|
|
27
|
+
|
|
28
|
+
/** AttemptListItem(src/report/model/types.ts):三个组件族共用的叶子形状(独立 data 或嵌套在 evalRows/attempts 里)。 */
|
|
29
|
+
function attemptListItemProblem(value: unknown, path: string): string | null {
|
|
30
|
+
if (!isObject(value)) return `"${path}" must be an object`;
|
|
31
|
+
if (typeof value.experimentId !== "string") return `"${path}.experimentId" must be a string`;
|
|
32
|
+
if (typeof value.evalId !== "string") return `"${path}.evalId" must be a string`;
|
|
33
|
+
if (typeof value.attempt !== "number") return `"${path}.attempt" must be a number`;
|
|
34
|
+
if (typeof value.agent !== "string") return `"${path}.agent" must be a string`;
|
|
35
|
+
if (typeof value.verdict !== "string") return `"${path}.verdict" must be a string`;
|
|
36
|
+
if (!(value.failureSummary === null || typeof value.failureSummary === "string")) {
|
|
37
|
+
return `"${path}.failureSummary" must be a string or null`;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value.moreFailures !== "number") return `"${path}.moreFailures" must be a number`;
|
|
40
|
+
const examScoreProblem = cellProblem(value.examScore, `${path}.examScore`);
|
|
41
|
+
if (examScoreProblem !== null) return examScoreProblem;
|
|
42
|
+
if (typeof value.durationMs !== "number") return `"${path}.durationMs" must be a number`;
|
|
43
|
+
if (!(value.costUSD === null || typeof value.costUSD === "number")) return `"${path}.costUSD" must be a number or null`;
|
|
44
|
+
if (typeof value.locator !== "string") return `"${path}.locator" must be a string`;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const validateExperimentListData: Validator = (data) =>
|
|
49
|
+
arrayProblem(data, "data", (item, path) => {
|
|
50
|
+
if (!isObject(item)) return `"${path}" must be an object`;
|
|
51
|
+
if (typeof item.experimentId !== "string") return `"${path}.experimentId" must be a string`;
|
|
52
|
+
if (typeof item.agent !== "string") return `"${path}.agent" must be a string`;
|
|
53
|
+
const verdictsProblem = tallyProblem(item.evalVerdicts, `${path}.evalVerdicts`);
|
|
54
|
+
if (verdictsProblem !== null) return verdictsProblem;
|
|
55
|
+
const passRateProblem = cellProblem(item.endToEndPassRate, `${path}.endToEndPassRate`);
|
|
56
|
+
if (passRateProblem !== null) return passRateProblem;
|
|
57
|
+
const costProblem = cellProblem(item.costUSD, `${path}.costUSD`);
|
|
58
|
+
if (costProblem !== null) return costProblem;
|
|
59
|
+
const durationProblem = cellProblem(item.durationMs, `${path}.durationMs`);
|
|
60
|
+
if (durationProblem !== null) return durationProblem;
|
|
61
|
+
const tokensProblem = cellProblem(item.tokens, `${path}.tokens`);
|
|
62
|
+
if (tokensProblem !== null) return tokensProblem;
|
|
63
|
+
if (typeof item.evals !== "number") return `"${path}.evals" must be a number`;
|
|
64
|
+
if (typeof item.attempts !== "number") return `"${path}.attempts" must be a number`;
|
|
65
|
+
if (typeof item.lastRunAt !== "string") return `"${path}.lastRunAt" must be a string`;
|
|
66
|
+
return arrayProblem(item.evalRows, `${path}.evalRows`, (row, rowPath) => {
|
|
67
|
+
if (!isObject(row) || typeof row.evalId !== "string") {
|
|
68
|
+
return `"${rowPath}" must be an object with a string "evalId"`;
|
|
69
|
+
}
|
|
70
|
+
const rowDurationProblem = cellProblem(row.durationMs, `${rowPath}.durationMs`);
|
|
71
|
+
if (rowDurationProblem !== null) return rowDurationProblem;
|
|
72
|
+
const rowCostProblem = cellProblem(row.costUSD, `${rowPath}.costUSD`);
|
|
73
|
+
if (rowCostProblem !== null) return rowCostProblem;
|
|
74
|
+
return arrayProblem(row.attempts, `${rowPath}.attempts`, attemptListItemProblem);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
export const validateEvalListData: Validator = (data) =>
|
|
78
|
+
arrayProblem(data, "data", (item, path) => {
|
|
79
|
+
if (!isObject(item)) return `"${path}" must be an object`;
|
|
80
|
+
if (typeof item.experimentId !== "string") return `"${path}.experimentId" must be a string`;
|
|
81
|
+
if (typeof item.evalId !== "string") return `"${path}.evalId" must be a string`;
|
|
82
|
+
if (typeof item.verdict !== "string") return `"${path}.verdict" must be a string`;
|
|
83
|
+
const examScoreProblem = cellProblem(item.examScore, `${path}.examScore`);
|
|
84
|
+
if (examScoreProblem !== null) return examScoreProblem;
|
|
85
|
+
const durationProblem = cellProblem(item.durationMs, `${path}.durationMs`);
|
|
86
|
+
if (durationProblem !== null) return durationProblem;
|
|
87
|
+
const costProblem = cellProblem(item.costUSD, `${path}.costUSD`);
|
|
88
|
+
if (costProblem !== null) return costProblem;
|
|
89
|
+
return arrayProblem(item.attempts, `${path}.attempts`, attemptListItemProblem);
|
|
90
|
+
});
|
|
91
|
+
export const validateAttemptListData: Validator = (data) => arrayProblem(data, "data", attemptListItemProblem);
|
|
92
|
+
|
|
93
|
+
// ───────────────────────── 实体列表 ─────────────────────────
|
|
94
|
+
|
|
95
|
+
interface EntityListChrome extends ChromeProps {
|
|
96
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type ExperimentListProps = DataProps<
|
|
100
|
+
readonly ExperimentListItem[],
|
|
101
|
+
Record<never, never>,
|
|
102
|
+
EntityListChrome & {
|
|
103
|
+
/** web 面在比较表前显示实验过滤框;text 面忽略。 */
|
|
104
|
+
filter?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* 可选父路径:两面的行标签去掉与它相同的前缀,只显示 experiment id 末段。自定义报告
|
|
107
|
+
* 显式传入使用;默认 `ExperimentComparison` 不传,完整 id 始终可见。完整 id 仍是
|
|
108
|
+
* 排序 / 着色 / 过滤 / 折叠的键。
|
|
109
|
+
*/
|
|
110
|
+
relativeTo?: string;
|
|
111
|
+
}
|
|
112
|
+
>;
|
|
113
|
+
|
|
114
|
+
/** 实验列表:每项一个 experiment,固定八列比较表 + 展开到 Eval / Attempt。 */
|
|
115
|
+
export const ExperimentList = makeDataComponent<
|
|
116
|
+
readonly ExperimentListItem[],
|
|
117
|
+
Record<never, never>,
|
|
118
|
+
EntityListChrome & { filter?: boolean; relativeTo?: string }
|
|
119
|
+
>({
|
|
120
|
+
name: "ExperimentList",
|
|
121
|
+
dataFnName: "experimentListData",
|
|
122
|
+
shapeName: "ExperimentListItem[]",
|
|
123
|
+
dataFn: (input) => experimentListData(input),
|
|
124
|
+
specKeys: [],
|
|
125
|
+
validate: validateExperimentListData,
|
|
126
|
+
web: (props, ctx) => (
|
|
127
|
+
<ExperimentListWeb
|
|
128
|
+
data={props.data}
|
|
129
|
+
filter={props.filter}
|
|
130
|
+
relativeTo={props.relativeTo}
|
|
131
|
+
locale={props.locale ?? ctx.locale}
|
|
132
|
+
attemptHref={hrefOf(props, ctx)}
|
|
133
|
+
className={props.className}
|
|
134
|
+
/>
|
|
135
|
+
),
|
|
136
|
+
text: (props, ctx) => experimentListText(props.data, ctx, props.relativeTo),
|
|
137
|
+
}) as unknown as ReportComponent<ExperimentListProps>;
|
|
138
|
+
|
|
139
|
+
export type EvalListProps = DataProps<readonly EvalListItem[], Record<never, never>, EntityListChrome>;
|
|
140
|
+
|
|
141
|
+
/** Eval 列表:每项一个 experimentId + evalId,展开到这道题的 Attempt。 */
|
|
142
|
+
export const EvalList = makeDataComponent<readonly EvalListItem[], Record<never, never>, EntityListChrome>({
|
|
143
|
+
name: "EvalList",
|
|
144
|
+
dataFnName: "evalListData",
|
|
145
|
+
shapeName: "EvalListItem[]",
|
|
146
|
+
dataFn: (input) => evalListData(input),
|
|
147
|
+
specKeys: [],
|
|
148
|
+
validate: validateEvalListData,
|
|
149
|
+
web: (props, ctx) => (
|
|
150
|
+
<EvalListWeb
|
|
151
|
+
data={props.data}
|
|
152
|
+
locale={props.locale ?? ctx.locale}
|
|
153
|
+
attemptHref={hrefOf(props, ctx)}
|
|
154
|
+
className={props.className}
|
|
155
|
+
/>
|
|
156
|
+
),
|
|
157
|
+
text: (props, ctx) => evalListText(props.data, ctx),
|
|
158
|
+
}) as unknown as ReportComponent<EvalListProps>;
|
|
159
|
+
|
|
160
|
+
export type AttemptListProps = DataProps<
|
|
161
|
+
readonly AttemptListItem[],
|
|
162
|
+
Record<never, never>,
|
|
163
|
+
EntityListChrome & {
|
|
164
|
+
/** 过滤 / 截断前的总数;省略时等于 data 长度。 */
|
|
165
|
+
total?: number;
|
|
166
|
+
/** web 面加过滤输入框(按 experiment、eval、agent、verdict 或摘要文本收窄行);渐进增强,不改变数据与 text 面。 */
|
|
167
|
+
filter?: boolean;
|
|
168
|
+
}
|
|
169
|
+
>;
|
|
170
|
+
|
|
171
|
+
/** Attempt 列表:实体列表的叶子层,每项一次 attempt 的判定、单行摘要与 locator。 */
|
|
172
|
+
export const AttemptList = makeDataComponent<
|
|
173
|
+
readonly AttemptListItem[],
|
|
174
|
+
Record<never, never>,
|
|
175
|
+
EntityListChrome & { total?: number; filter?: boolean }
|
|
176
|
+
>({
|
|
177
|
+
name: "AttemptList",
|
|
178
|
+
dataFnName: "attemptListData",
|
|
179
|
+
shapeName: "AttemptListItem[]",
|
|
180
|
+
dataFn: (input) => attemptListData(input),
|
|
181
|
+
specKeys: [],
|
|
182
|
+
validate: validateAttemptListData,
|
|
183
|
+
web: (props, ctx) => (
|
|
184
|
+
<AttemptListWeb
|
|
185
|
+
data={props.data}
|
|
186
|
+
total={props.total}
|
|
187
|
+
filter={props.filter}
|
|
188
|
+
locale={props.locale ?? ctx.locale}
|
|
189
|
+
attemptHref={hrefOf(props, ctx)}
|
|
190
|
+
className={props.className}
|
|
191
|
+
/>
|
|
192
|
+
),
|
|
193
|
+
text: (props, ctx) => attemptListText(props.data, props.total, ctx),
|
|
194
|
+
}) as unknown as ReportComponent<AttemptListProps>;
|
|
195
|
+
|
|
196
|
+
// ───────────────────────── FailureList(官方组合件)─────────────────────────
|
|
197
|
+
|
|
198
|
+
export interface FailureListProps {
|
|
199
|
+
/** 显示的最大条数;默认 20。 */
|
|
200
|
+
limit?: number;
|
|
201
|
+
/** 默认宿主注入的 Scope。 */
|
|
202
|
+
input?: ReportInput;
|
|
203
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
204
|
+
locale?: ReportLocale;
|
|
205
|
+
className?: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 「现在有哪些失败要处理」的成品组合件:内部就是 attemptListData → 过滤 → AttemptList
|
|
210
|
+
* data 形态,与手写组合严格等价、没有私有能力(docs/feature/reports/library/entity-lists.md)。
|
|
211
|
+
* verdict ∈ failed / errored,按 attempt 开始时间降序(同刻按 locator 字典序),
|
|
212
|
+
* 截断到 limit(默认 20),total 报告截断前总数。
|
|
213
|
+
*/
|
|
214
|
+
export const FailureList = defineComponent<FailureListProps>(async (props, ctx) => {
|
|
215
|
+
const input = props.input ?? ctx.scope;
|
|
216
|
+
const all = await attemptListData(input);
|
|
217
|
+
// attempt 开始时间不在列表条目里(它不是列表展示字段);从同一 input 的读取面按 locator 对回。
|
|
218
|
+
const startedAtByLocator = new Map<string, string>();
|
|
219
|
+
for (const item of collectItems(resolveInput(input).snapshots)) {
|
|
220
|
+
startedAtByLocator.set(locatorOf(item), item.attempt.result.startedAt ?? "");
|
|
221
|
+
}
|
|
222
|
+
const failures = all
|
|
223
|
+
.filter((item) => item.verdict === "failed" || item.verdict === "errored")
|
|
224
|
+
.sort((a, b) => {
|
|
225
|
+
const ta = startedAtByLocator.get(a.locator) ?? "";
|
|
226
|
+
const tb = startedAtByLocator.get(b.locator) ?? "";
|
|
227
|
+
if (ta !== tb) return ta < tb ? 1 : -1; // 最近的失败在前
|
|
228
|
+
return a.locator < b.locator ? -1 : a.locator > b.locator ? 1 : 0;
|
|
229
|
+
});
|
|
230
|
+
const limit = props.limit ?? 20;
|
|
231
|
+
return (
|
|
232
|
+
<AttemptList
|
|
233
|
+
data={failures.slice(0, limit)}
|
|
234
|
+
total={failures.length}
|
|
235
|
+
attemptHref={props.attemptHref}
|
|
236
|
+
locale={props.locale}
|
|
237
|
+
className={props.className}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
FailureList.displayName = "FailureList";
|