oh-my-knowledge 0.48.0 → 0.49.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/README.md +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +18 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +28 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +30 -2
- package/dist/cli/commands/sample.d.ts +6 -3
- package/dist/cli/commands/sample.js +72 -68
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +1 -1
- package/dist/observability/inbox-view-model.js +19 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +208 -90
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { buildVariantConfig } from './execution-strategy.js';
|
|
2
|
+
import { buildExecutorRuntimesByVariant, EVALUATION_REPORT_SCHEMA_VERSION, getCliVersion, } from './evaluation-reporting.js';
|
|
3
|
+
import { hashSample } from './sample-fingerprint.js';
|
|
4
|
+
import { getJudgePromptHash } from '../grading/judge.js';
|
|
5
|
+
import { getDiagnosticPromptHash, resolveDiagnosticTarget, } from '../grading/diagnostic.js';
|
|
6
|
+
import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
|
|
7
|
+
function canonicalStringify(value) {
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
return 'undefined';
|
|
10
|
+
if (value === null || typeof value !== 'object')
|
|
11
|
+
return JSON.stringify(value) ?? 'null';
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return `[${value.map(canonicalStringify).join(',')}]`;
|
|
14
|
+
const record = value;
|
|
15
|
+
const keys = Object.keys(record)
|
|
16
|
+
.filter((key) => record[key] !== undefined)
|
|
17
|
+
.sort();
|
|
18
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(',')}}`;
|
|
19
|
+
}
|
|
20
|
+
function same(left, right) {
|
|
21
|
+
return canonicalStringify(left) === canonicalStringify(right);
|
|
22
|
+
}
|
|
23
|
+
function runtimeFingerprints(runtimes) {
|
|
24
|
+
if (!runtimes)
|
|
25
|
+
return undefined;
|
|
26
|
+
return Object.fromEntries(Object.entries(runtimes).map(([variant, runtime]) => [variant, runtime.fingerprint]));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decide whether successful entries from an earlier report can be inserted
|
|
30
|
+
* into the current measurement without mixing protocols. Every value compared
|
|
31
|
+
* here is either the report's public measurement evidence or a request option
|
|
32
|
+
* that can alter a persisted VariantResult.
|
|
33
|
+
*/
|
|
34
|
+
export function checkResumeCompatibility(report, current) {
|
|
35
|
+
const mismatches = [];
|
|
36
|
+
const check = (field, actual, expected) => {
|
|
37
|
+
if (!same(actual, expected))
|
|
38
|
+
mismatches.push(field);
|
|
39
|
+
};
|
|
40
|
+
check('meta.schemaVersion', report.meta.schemaVersion, EVALUATION_REPORT_SCHEMA_VERSION);
|
|
41
|
+
check('meta.cliVersion', report.meta.cliVersion, getCliVersion());
|
|
42
|
+
check('meta.nodeVersion', report.meta.nodeVersion, process.version);
|
|
43
|
+
check('meta.variants', report.meta.variants, current.variants);
|
|
44
|
+
check('meta.model', report.meta.model, current.model);
|
|
45
|
+
check('meta.executor', report.meta.executor, current.executorName);
|
|
46
|
+
check('meta.effort', report.meta.effort, current.effort);
|
|
47
|
+
check('meta.noJudge', report.meta.noJudge === true, current.noJudge);
|
|
48
|
+
check('meta.judgeRepeat', report.meta.judgeRepeat ?? 1, current.judgeRepeat ?? 1);
|
|
49
|
+
check('meta.sampleCount', report.meta.sampleCount, current.samples.length);
|
|
50
|
+
check('meta.taskCount', report.meta.taskCount, current.tasks.length);
|
|
51
|
+
const expectedArtifactHashes = Object.fromEntries(current.artifacts.map((artifact) => [
|
|
52
|
+
artifact.name,
|
|
53
|
+
artifact.contentHash ?? 'no-skill',
|
|
54
|
+
]));
|
|
55
|
+
check('meta.artifactHashes', report.meta.artifactHashes, expectedArtifactHashes);
|
|
56
|
+
const expectedSampleHashes = Object.fromEntries(current.samples.map((sample) => [
|
|
57
|
+
sample.sample_id,
|
|
58
|
+
hashSample(sample, current.samplesBaseDir),
|
|
59
|
+
]));
|
|
60
|
+
check('meta.sampleHashes', report.meta.sampleHashes, expectedSampleHashes);
|
|
61
|
+
check('meta.variantConfigs', report.meta.variantConfigs, current.artifacts.map(buildVariantConfig));
|
|
62
|
+
check('meta.skillIsolation', report.meta.skillIsolation, Object.fromEntries(current.artifacts.map((artifact) => [
|
|
63
|
+
artifact.name,
|
|
64
|
+
artifact.allowedSkills ?? null,
|
|
65
|
+
])));
|
|
66
|
+
const expectedExecutorRuntimes = buildExecutorRuntimesByVariant({
|
|
67
|
+
variants: current.variants,
|
|
68
|
+
model: current.model,
|
|
69
|
+
executorName: current.executorName,
|
|
70
|
+
tasks: current.tasks,
|
|
71
|
+
artifacts: current.artifacts,
|
|
72
|
+
request: {
|
|
73
|
+
skillDir: current.skillDir,
|
|
74
|
+
timeoutMs: current.timeoutMs,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
check('meta.executorRuntimes', runtimeFingerprints(report.meta.executorRuntimes), runtimeFingerprints(expectedExecutorRuntimes));
|
|
78
|
+
const actualJudges = report.meta.judgeModels.map((judge) => ({
|
|
79
|
+
executor: judge.executor,
|
|
80
|
+
model: judge.model,
|
|
81
|
+
runtime: judge.runtime?.fingerprint,
|
|
82
|
+
}));
|
|
83
|
+
const expectedJudges = current.judgeModels.map((judge) => ({
|
|
84
|
+
executor: judge.executor,
|
|
85
|
+
model: judge.model,
|
|
86
|
+
...(!current.noJudge
|
|
87
|
+
? {
|
|
88
|
+
runtime: getExecutorRuntimeFingerprint(judge.executor, judge.model, {
|
|
89
|
+
skillDir: current.skillDir,
|
|
90
|
+
}).fingerprint,
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
93
|
+
}));
|
|
94
|
+
check('meta.judgeModels', actualJudges, expectedJudges);
|
|
95
|
+
if (!current.noJudge) {
|
|
96
|
+
check('meta.judgePromptHash', report.meta.judgePromptHash, getJudgePromptHash(current.lengthDebias !== false));
|
|
97
|
+
}
|
|
98
|
+
const diagnosticEnabled = current.noDiagnostic !== true;
|
|
99
|
+
const diagnosticTarget = resolveDiagnosticTarget(current.judgeModels, current.executorName, current.model);
|
|
100
|
+
const expectedDiagnostic = diagnosticEnabled
|
|
101
|
+
? {
|
|
102
|
+
enabled: true,
|
|
103
|
+
executor: diagnosticTarget.executor,
|
|
104
|
+
model: diagnosticTarget.model,
|
|
105
|
+
runtime: getExecutorRuntimeFingerprint(diagnosticTarget.executor, diagnosticTarget.model).fingerprint,
|
|
106
|
+
promptHash: getDiagnosticPromptHash(),
|
|
107
|
+
}
|
|
108
|
+
: { enabled: false };
|
|
109
|
+
const actualDiagnostic = report.meta.diagnostic
|
|
110
|
+
? {
|
|
111
|
+
enabled: report.meta.diagnostic.enabled,
|
|
112
|
+
...(report.meta.diagnostic.executor
|
|
113
|
+
? { executor: report.meta.diagnostic.executor }
|
|
114
|
+
: {}),
|
|
115
|
+
...(report.meta.diagnostic.model ? { model: report.meta.diagnostic.model } : {}),
|
|
116
|
+
...(report.meta.diagnostic.runtime
|
|
117
|
+
? { runtime: report.meta.diagnostic.runtime.fingerprint }
|
|
118
|
+
: {}),
|
|
119
|
+
...(report.meta.diagnostic.promptHash
|
|
120
|
+
? { promptHash: report.meta.diagnostic.promptHash }
|
|
121
|
+
: {}),
|
|
122
|
+
}
|
|
123
|
+
: undefined;
|
|
124
|
+
check('meta.diagnostic', actualDiagnostic, expectedDiagnostic);
|
|
125
|
+
const request = report.meta.request;
|
|
126
|
+
if (!request) {
|
|
127
|
+
mismatches.push('meta.request');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
check('meta.request.timeoutMs', request.timeoutMs, current.timeoutMs);
|
|
131
|
+
check('meta.request.budget', request.budget, current.budget);
|
|
132
|
+
check('meta.request.retry', request.retry ?? 0, current.retry ?? 0);
|
|
133
|
+
check('meta.request.noDiagnostic', request.noDiagnostic === true, current.noDiagnostic === true);
|
|
134
|
+
}
|
|
135
|
+
if (report.meta.budgetExhausted)
|
|
136
|
+
mismatches.push('meta.budgetExhausted');
|
|
137
|
+
return {
|
|
138
|
+
compatible: mismatches.length === 0,
|
|
139
|
+
mismatches: [...new Set(mismatches)],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Sample } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Content fingerprint for files that can change executor output while the
|
|
4
|
+
* inline sample contract remains byte-for-byte identical.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hashSampleExecutionDependencies(sample: Sample, baseDir?: string): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Stable hash of the complete sample measurement contract, including external
|
|
9
|
+
* mock fixtures and the statically resolvable ESM import graph of custom
|
|
10
|
+
* assertion modules when a sample base directory is available.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hashSample(sample: Sample, baseDir?: string): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { builtinModules, createRequire } from 'node:module';
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve, } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { initSync, parse } from 'es-module-lexer';
|
|
7
|
+
const MAX_ASSERTION_MODULE_FILES = 512;
|
|
8
|
+
const MAX_ASSERTION_MODULE_BYTES = 32 * 1024 * 1024;
|
|
9
|
+
const BUILTIN_MODULES = new Set([
|
|
10
|
+
...builtinModules,
|
|
11
|
+
...builtinModules.map((name) => `node:${name}`),
|
|
12
|
+
]);
|
|
13
|
+
initSync();
|
|
14
|
+
function canonicalStringify(value) {
|
|
15
|
+
if (value === null || typeof value !== 'object')
|
|
16
|
+
return JSON.stringify(value) ?? 'null';
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return `[${value.map((entry) => entry === undefined ? 'null' : canonicalStringify(entry)).join(',')}]`;
|
|
19
|
+
}
|
|
20
|
+
const record = value;
|
|
21
|
+
const entries = Object.keys(record)
|
|
22
|
+
.filter((key) => record[key] !== undefined)
|
|
23
|
+
.sort();
|
|
24
|
+
return `{${entries.map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(',')}}`;
|
|
25
|
+
}
|
|
26
|
+
function hashString(value) {
|
|
27
|
+
return createHash('sha256').update(value).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
function dependencyContentFingerprint(path, baseDir) {
|
|
30
|
+
if (!baseDir && !isAbsolute(path))
|
|
31
|
+
return 'base-dir-unavailable';
|
|
32
|
+
const resolvedPath = isAbsolute(path) ? path : resolve(baseDir, path);
|
|
33
|
+
try {
|
|
34
|
+
return `sha256:${hashString(readFileSync(resolvedPath))}`;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const code = error.code;
|
|
38
|
+
return `unavailable:${code ?? 'unknown'}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function customAssertionPaths(assertions) {
|
|
42
|
+
if (!assertions)
|
|
43
|
+
return [];
|
|
44
|
+
return assertions.flatMap((assertion) => {
|
|
45
|
+
const record = assertion;
|
|
46
|
+
const current = assertion.type === 'custom' && typeof record.fn === 'string'
|
|
47
|
+
? [record.fn]
|
|
48
|
+
: [];
|
|
49
|
+
const children = assertion.type === 'assert-set' && Array.isArray(assertion.children)
|
|
50
|
+
? customAssertionPaths(assertion.children)
|
|
51
|
+
: [];
|
|
52
|
+
return [...current, ...children];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function displayDependencyPath(path, baseDir) {
|
|
56
|
+
if (!baseDir)
|
|
57
|
+
return path;
|
|
58
|
+
const rel = relative(baseDir, path);
|
|
59
|
+
return rel || '.';
|
|
60
|
+
}
|
|
61
|
+
function resolveImportedModule(specifier, importer) {
|
|
62
|
+
if (BUILTIN_MODULES.has(specifier))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (specifier.startsWith('file:')) {
|
|
65
|
+
try {
|
|
66
|
+
return fileURLToPath(specifier);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
73
|
+
const clean = specifier.replace(/[?#].*$/, '');
|
|
74
|
+
const path = specifier.startsWith('/') ? clean : resolve(dirname(importer), clean);
|
|
75
|
+
return existsSync(path) && statSync(path).isFile() ? path : undefined;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const resolved = createRequire(importer).resolve(specifier);
|
|
79
|
+
return BUILTIN_MODULES.has(resolved) ? undefined : resolved;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function customAssertionModuleEntries(path, baseDir) {
|
|
86
|
+
if (!baseDir && !isAbsolute(path)) {
|
|
87
|
+
return [[`module:${path}`, 'base-dir-unavailable']];
|
|
88
|
+
}
|
|
89
|
+
const entryPath = isAbsolute(path) ? path : resolve(baseDir, path);
|
|
90
|
+
const entries = [];
|
|
91
|
+
const visited = new Set();
|
|
92
|
+
let totalBytes = 0;
|
|
93
|
+
const visit = (modulePath) => {
|
|
94
|
+
if (visited.has(modulePath))
|
|
95
|
+
return;
|
|
96
|
+
if (visited.size >= MAX_ASSERTION_MODULE_FILES) {
|
|
97
|
+
throw new Error(`custom assertion module graph exceeds ${MAX_ASSERTION_MODULE_FILES} files: ${entryPath}`);
|
|
98
|
+
}
|
|
99
|
+
visited.add(modulePath);
|
|
100
|
+
let source;
|
|
101
|
+
try {
|
|
102
|
+
source = readFileSync(modulePath);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const code = error.code;
|
|
106
|
+
entries.push([
|
|
107
|
+
`module:${displayDependencyPath(modulePath, baseDir)}`,
|
|
108
|
+
`unavailable:${code ?? 'unknown'}`,
|
|
109
|
+
]);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
totalBytes += source.byteLength;
|
|
113
|
+
if (totalBytes > MAX_ASSERTION_MODULE_BYTES) {
|
|
114
|
+
throw new Error(`custom assertion module graph exceeds ${MAX_ASSERTION_MODULE_BYTES} bytes: ${entryPath}`);
|
|
115
|
+
}
|
|
116
|
+
entries.push([
|
|
117
|
+
`module:${displayDependencyPath(modulePath, baseDir)}`,
|
|
118
|
+
`sha256:${hashString(source)}`,
|
|
119
|
+
]);
|
|
120
|
+
if (!/\.[cm]?js$/i.test(modulePath))
|
|
121
|
+
return;
|
|
122
|
+
let imports;
|
|
123
|
+
try {
|
|
124
|
+
[imports] = parse(source.toString('utf8'));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
entries.push([
|
|
128
|
+
`module-parse:${displayDependencyPath(modulePath, baseDir)}`,
|
|
129
|
+
'unparseable',
|
|
130
|
+
]);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const imported of imports) {
|
|
134
|
+
if (!imported.n) {
|
|
135
|
+
if (imported.d >= 0) {
|
|
136
|
+
entries.push([
|
|
137
|
+
`module-dynamic:${displayDependencyPath(modulePath, baseDir)}:${imported.ss}`,
|
|
138
|
+
'non-literal',
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const resolvedImport = resolveImportedModule(imported.n, modulePath);
|
|
144
|
+
if (resolvedImport)
|
|
145
|
+
visit(resolvedImport);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
visit(entryPath);
|
|
149
|
+
return entries.sort(([left], [right]) => left.localeCompare(right));
|
|
150
|
+
}
|
|
151
|
+
function sampleDependencyEntries(sample, baseDir, includeCustomAssertions) {
|
|
152
|
+
const entries = [];
|
|
153
|
+
for (const [index, mock] of (sample.mocks ?? []).entries()) {
|
|
154
|
+
if (typeof mock.return_file !== 'string' || !mock.return_file)
|
|
155
|
+
continue;
|
|
156
|
+
entries.push([
|
|
157
|
+
`mock:${index}:return_file:${mock.return_file}`,
|
|
158
|
+
dependencyContentFingerprint(mock.return_file, baseDir),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
if (includeCustomAssertions) {
|
|
162
|
+
for (const [index, path] of customAssertionPaths(sample.assertions).entries()) {
|
|
163
|
+
for (const [modulePath, fingerprint] of customAssertionModuleEntries(path, baseDir)) {
|
|
164
|
+
entries.push([
|
|
165
|
+
`assertion:${index}:fn:${path}:${modulePath}`,
|
|
166
|
+
fingerprint,
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return entries.sort(([left], [right]) => left.localeCompare(right));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Content fingerprint for files that can change executor output while the
|
|
175
|
+
* inline sample contract remains byte-for-byte identical.
|
|
176
|
+
*/
|
|
177
|
+
export function hashSampleExecutionDependencies(sample, baseDir) {
|
|
178
|
+
const entries = sampleDependencyEntries(sample, baseDir, false);
|
|
179
|
+
return entries.length > 0 ? hashString(canonicalStringify(entries)).slice(0, 12) : undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Stable hash of the complete sample measurement contract, including external
|
|
183
|
+
* mock fixtures and the statically resolvable ESM import graph of custom
|
|
184
|
+
* assertion modules when a sample base directory is available.
|
|
185
|
+
*/
|
|
186
|
+
export function hashSample(sample, baseDir) {
|
|
187
|
+
const contract = Object.fromEntries(Object.entries(sample).filter(([key, value]) => key !== 'sample_id' && value !== undefined));
|
|
188
|
+
const dependencies = sampleDependencyEntries(sample, baseDir, true);
|
|
189
|
+
return hashString(canonicalStringify({
|
|
190
|
+
contract,
|
|
191
|
+
...(dependencies.length > 0 ? { dependencies } : {}),
|
|
192
|
+
})).slice(0, 12);
|
|
193
|
+
}
|
package/dist/eval-core/schema.js
CHANGED
|
@@ -3,27 +3,13 @@
|
|
|
3
3
|
* Single source of truth for object structures used across runner, renderer, and server.
|
|
4
4
|
*/
|
|
5
5
|
import { computeJudgeAgreement } from '../grading/judge.js';
|
|
6
|
+
import { isToolCallCancelled, isToolCallFailure, isToolCallSuccess, isToolCallUnknown, } from '../shared/tool-call-status.js';
|
|
7
|
+
import { incrementRecordCount } from '../shared/record-count.js';
|
|
8
|
+
import { truncateToolCallsForPersistence, truncateTurnsForPersistence, } from '../shared/trace-projection.js';
|
|
6
9
|
import { safeSliceForJson } from '../util/safe-slice.js';
|
|
7
10
|
function ratioToScore(ratio) {
|
|
8
11
|
return Number((1 + ratio * 4).toFixed(2));
|
|
9
12
|
}
|
|
10
|
-
const MAX_TURN_CONTENT = 2000;
|
|
11
|
-
const MAX_TOOL_OUTPUT = 1000;
|
|
12
|
-
function truncateTurns(turns) {
|
|
13
|
-
return turns.map((t) => ({
|
|
14
|
-
...t,
|
|
15
|
-
content: safeSliceForJson(t.content, MAX_TURN_CONTENT),
|
|
16
|
-
...(t.toolCalls && { toolCalls: truncateToolCalls(t.toolCalls) }),
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
19
|
-
function truncateToolCalls(toolCalls) {
|
|
20
|
-
return toolCalls.map((tc) => ({
|
|
21
|
-
...tc,
|
|
22
|
-
output: typeof tc.output === 'string'
|
|
23
|
-
? safeSliceForJson(tc.output, MAX_TOOL_OUTPUT)
|
|
24
|
-
: tc.output,
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
13
|
export function buildVariantResult(execResult, gradeResult, options) {
|
|
28
14
|
const execCostUSD = execResult.costUSD || 0;
|
|
29
15
|
const judgeCostUSD = gradeResult?.judgeCostUSD || 0;
|
|
@@ -31,7 +17,12 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
31
17
|
const gradeMs = options?.gradeMs || 0;
|
|
32
18
|
const assistantTurns = execResult.turns?.filter((turn) => turn.role === 'assistant').length;
|
|
33
19
|
const toolTurns = execResult.turns?.filter((turn) => turn.role === 'tool').length;
|
|
34
|
-
const numToolFailures = execResult.toolCalls?.filter(
|
|
20
|
+
const numToolFailures = execResult.toolCalls?.filter(isToolCallFailure).length;
|
|
21
|
+
const numToolCancelled = execResult.toolCalls?.filter(isToolCallCancelled).length;
|
|
22
|
+
const numToolUnknown = execResult.toolCalls?.filter(isToolCallUnknown).length;
|
|
23
|
+
const comparableToolCalls = Math.max(0, (execResult.toolCalls?.length ?? 0)
|
|
24
|
+
- (numToolCancelled ?? 0)
|
|
25
|
+
- (numToolUnknown ?? 0));
|
|
35
26
|
const traceSignals = [
|
|
36
27
|
execResult.turns && execResult.turns.length > 0,
|
|
37
28
|
execResult.toolCalls && execResult.toolCalls.length > 0,
|
|
@@ -45,9 +36,15 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
45
36
|
durationApiMs: execResult.durationApiMs,
|
|
46
37
|
inputTokens: execResult.inputTokens,
|
|
47
38
|
outputTokens: execResult.outputTokens,
|
|
48
|
-
totalTokens: execResult.inputTokens
|
|
39
|
+
totalTokens: execResult.inputTokens
|
|
40
|
+
+ execResult.outputTokens
|
|
41
|
+
+ execResult.cacheReadTokens
|
|
42
|
+
+ execResult.cacheCreationTokens,
|
|
49
43
|
cacheReadTokens: execResult.cacheReadTokens,
|
|
50
44
|
cacheCreationTokens: execResult.cacheCreationTokens,
|
|
45
|
+
...(execResult.tokenUsageReportedByExecutor === false && {
|
|
46
|
+
tokenUsageReportedByExecutor: false,
|
|
47
|
+
}),
|
|
51
48
|
execCostUSD,
|
|
52
49
|
judgeCostUSD,
|
|
53
50
|
// 初值 = exec + judge。diagnostic 在 evaluation-execution.ts 跑完后会把
|
|
@@ -61,6 +58,7 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
61
58
|
// judge 同理:任一 dim/single/async assertion 走了不报 cost 的 judge executor → grade 整体也不可信
|
|
62
59
|
...(gradeResult?.judgeCostReportedByExecutor === false && { judgeCostReportedByExecutor: false }),
|
|
63
60
|
numTurns: execResult.numTurns,
|
|
61
|
+
...(execResult.attemptCount != null && { attemptCount: execResult.attemptCount }),
|
|
64
62
|
...(execResult.fullNumTurns != null && { fullNumTurns: execResult.fullNumTurns }),
|
|
65
63
|
...(execResult.numSubAgents != null && { numSubAgents: execResult.numSubAgents }),
|
|
66
64
|
...(assistantTurns != null && { assistantTurns }),
|
|
@@ -68,10 +66,14 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
68
66
|
...(execResult.toolCalls && execResult.toolCalls.length > 0 && {
|
|
69
67
|
numToolCalls: execResult.toolCalls.length,
|
|
70
68
|
numToolFailures,
|
|
71
|
-
|
|
69
|
+
numToolCancelled,
|
|
70
|
+
numToolUnknown,
|
|
71
|
+
...(comparableToolCalls > 0 && {
|
|
72
|
+
toolSuccessRate: Number((execResult.toolCalls.filter(isToolCallSuccess).length / comparableToolCalls).toFixed(2)),
|
|
73
|
+
}),
|
|
72
74
|
toolNames: [...new Set(execResult.toolCalls.map((tc) => tc.tool))],
|
|
73
75
|
toolDistribution: execResult.toolCalls.reduce((d, tc) => {
|
|
74
|
-
d
|
|
76
|
+
incrementRecordCount(d, tc.tool);
|
|
75
77
|
return d;
|
|
76
78
|
}, {}),
|
|
77
79
|
}),
|
|
@@ -111,8 +113,12 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
111
113
|
...(options?.factCheck && options.factCheck.totalCount > 0 && { factCheck: options.factCheck }),
|
|
112
114
|
outputPreview: execResult.output ? safeSliceForJson(execResult.output, 200, '') : null,
|
|
113
115
|
...(execResult.output && { fullOutput: execResult.output }),
|
|
114
|
-
...(execResult.turns && execResult.turns.length > 0 && {
|
|
115
|
-
|
|
116
|
+
...(execResult.turns && execResult.turns.length > 0 && {
|
|
117
|
+
turns: truncateTurnsForPersistence(execResult.turns),
|
|
118
|
+
}),
|
|
119
|
+
...(execResult.toolCalls && execResult.toolCalls.length > 0 && {
|
|
120
|
+
toolCalls: truncateToolCallsForPersistence(execResult.toolCalls),
|
|
121
|
+
}),
|
|
116
122
|
...(execResult.mockStats && { mockStats: execResult.mockStats }),
|
|
117
123
|
timing: { execMs, gradeMs, totalMs: execMs + gradeMs },
|
|
118
124
|
};
|
|
@@ -122,6 +128,11 @@ export function buildVariantResult(execResult, gradeResult, options) {
|
|
|
122
128
|
*/
|
|
123
129
|
export function buildVariantSummary(entries) {
|
|
124
130
|
const ok = entries.filter((e) => e.ok);
|
|
131
|
+
const observedTokenEntries = ok.filter((entry) => entry.tokenUsageReportedByExecutor !== false);
|
|
132
|
+
const tokenUsageCoverageRate = entries.length > 0
|
|
133
|
+
? Number((entries.filter((entry) => entry.tokenUsageReportedByExecutor !== false).length
|
|
134
|
+
/ entries.length).toFixed(4))
|
|
135
|
+
: 1;
|
|
125
136
|
const compositeScores = entries.filter((e) => typeof e.compositeScore === 'number' && e.compositeScore > 0).map((e) => e.compositeScore);
|
|
126
137
|
const assertionScores = entries.filter((e) => e.assertions?.score != null && e.assertions.score > 0).map((e) => e.assertions.score);
|
|
127
138
|
const llmScores = entries.filter((e) => typeof e.llmScore === 'number' && e.llmScore > 0).map((e) => e.llmScore);
|
|
@@ -132,9 +143,16 @@ export function buildVariantSummary(entries) {
|
|
|
132
143
|
errorCount,
|
|
133
144
|
errorRate: entries.length > 0 ? Number((errorCount / entries.length * 100).toFixed(1)) : 0,
|
|
134
145
|
avgDurationMs: ok.length > 0 ? Math.round(ok.reduce((s, e) => s + (e.timing?.totalMs || e.durationMs), 0) / ok.length) : 0,
|
|
135
|
-
avgInputTokens:
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
avgInputTokens: observedTokenEntries.length > 0
|
|
147
|
+
? Math.round(observedTokenEntries.reduce((s, e) => s + e.inputTokens, 0) / observedTokenEntries.length)
|
|
148
|
+
: 0,
|
|
149
|
+
avgOutputTokens: observedTokenEntries.length > 0
|
|
150
|
+
? Math.round(observedTokenEntries.reduce((s, e) => s + e.outputTokens, 0) / observedTokenEntries.length)
|
|
151
|
+
: 0,
|
|
152
|
+
avgTotalTokens: observedTokenEntries.length > 0
|
|
153
|
+
? Math.round(observedTokenEntries.reduce((s, e) => s + e.totalTokens, 0) / observedTokenEntries.length)
|
|
154
|
+
: 0,
|
|
155
|
+
...(tokenUsageCoverageRate < 1 && { tokenUsageCoverageRate }),
|
|
138
156
|
totalCostUSD: ok.reduce((s, e) => s + (e.costUSD || 0), 0),
|
|
139
157
|
totalExecCostUSD: ok.reduce((s, e) => s + (e.execCostUSD || 0), 0),
|
|
140
158
|
totalJudgeCostUSD: ok.reduce((s, e) => s + (e.judgeCostUSD || 0), 0),
|
|
@@ -180,7 +198,40 @@ export function buildVariantSummary(entries) {
|
|
|
180
198
|
if (withTools.length === 0)
|
|
181
199
|
return {};
|
|
182
200
|
const totalToolCalls = withTools.reduce((s, e) => s + (e.numToolCalls || 0), 0);
|
|
183
|
-
const
|
|
201
|
+
const outcomeTotals = withTools.reduce((totals, entry) => {
|
|
202
|
+
const calls = entry.numToolCalls ?? 0;
|
|
203
|
+
const explicitUnknown = Math.min(calls, Math.max(0, entry.numToolUnknown ?? 0));
|
|
204
|
+
const explicitCancelled = Math.min(Math.max(0, calls - explicitUnknown), Math.max(0, entry.numToolCancelled ?? 0));
|
|
205
|
+
const comparable = Math.max(0, calls - explicitUnknown - explicitCancelled);
|
|
206
|
+
if (typeof entry.numToolFailures === 'number') {
|
|
207
|
+
const failures = Math.min(comparable, Math.max(0, entry.numToolFailures));
|
|
208
|
+
totals.comparable += comparable;
|
|
209
|
+
totals.failures += failures;
|
|
210
|
+
totals.successes += comparable - failures;
|
|
211
|
+
totals.cancelled += explicitCancelled;
|
|
212
|
+
totals.unknown += explicitUnknown;
|
|
213
|
+
return totals;
|
|
214
|
+
}
|
|
215
|
+
if (typeof entry.toolSuccessRate === 'number') {
|
|
216
|
+
const rate = Math.max(0, Math.min(1, entry.toolSuccessRate));
|
|
217
|
+
totals.comparable += comparable;
|
|
218
|
+
totals.successes += rate * comparable;
|
|
219
|
+
totals.failures += (1 - rate) * comparable;
|
|
220
|
+
totals.cancelled += explicitCancelled;
|
|
221
|
+
totals.unknown += explicitUnknown;
|
|
222
|
+
return totals;
|
|
223
|
+
}
|
|
224
|
+
// A call count without failure/success evidence only says execution was
|
|
225
|
+
// attempted. Treat the whole row as unresolved instead of 0% success.
|
|
226
|
+
totals.unknown += calls;
|
|
227
|
+
return totals;
|
|
228
|
+
}, {
|
|
229
|
+
comparable: 0,
|
|
230
|
+
successes: 0,
|
|
231
|
+
failures: 0,
|
|
232
|
+
cancelled: 0,
|
|
233
|
+
unknown: 0,
|
|
234
|
+
});
|
|
184
235
|
// sum per-sample toolDistribution(真实 call count),
|
|
185
236
|
// 不再用 dedup 后的 toolNames 累加(那只统计"出现该 tool 的 sample 数",不是调用次数)。
|
|
186
237
|
// 旧报告没 toolDistribution 字段时 fallback 到 toolNames 老语义,保兼容性。
|
|
@@ -188,19 +239,23 @@ export function buildVariantSummary(entries) {
|
|
|
188
239
|
for (const e of withTools) {
|
|
189
240
|
if (e.toolDistribution) {
|
|
190
241
|
for (const [name, count] of Object.entries(e.toolDistribution)) {
|
|
191
|
-
dist
|
|
242
|
+
incrementRecordCount(dist, name, count);
|
|
192
243
|
}
|
|
193
244
|
}
|
|
194
245
|
else {
|
|
195
246
|
for (const name of (e.toolNames || [])) {
|
|
196
|
-
dist
|
|
247
|
+
incrementRecordCount(dist, name);
|
|
197
248
|
}
|
|
198
249
|
}
|
|
199
250
|
}
|
|
200
251
|
return {
|
|
201
252
|
avgToolCalls: Number((totalToolCalls / withTools.length).toFixed(1)),
|
|
202
|
-
avgToolFailures: Number((
|
|
203
|
-
|
|
253
|
+
avgToolFailures: Number((outcomeTotals.failures / withTools.length).toFixed(1)),
|
|
254
|
+
avgToolCancelled: Number((outcomeTotals.cancelled / withTools.length).toFixed(1)),
|
|
255
|
+
avgToolUnknown: Number((outcomeTotals.unknown / withTools.length).toFixed(1)),
|
|
256
|
+
...(outcomeTotals.comparable > 0 && {
|
|
257
|
+
toolSuccessRate: Number((outcomeTotals.successes / outcomeTotals.comparable).toFixed(2)),
|
|
258
|
+
}),
|
|
204
259
|
toolDistribution: dist,
|
|
205
260
|
};
|
|
206
261
|
})(),
|
|
@@ -165,11 +165,15 @@ export declare function medianStabilityCV(report: Report): {
|
|
|
165
165
|
runs: number;
|
|
166
166
|
cv: number;
|
|
167
167
|
} | null;
|
|
168
|
+
interface VerdictTextOptions {
|
|
169
|
+
verbose?: boolean;
|
|
170
|
+
lang?: Lang;
|
|
171
|
+
/** Managed skill NAME from `omk list`; omitted when the formatter cannot know it safely. */
|
|
172
|
+
promoteTarget?: string;
|
|
173
|
+
}
|
|
168
174
|
/**
|
|
169
175
|
* Plain-text formatter for the `omk eval` verdict. Stays terse for the
|
|
170
176
|
* spec — one verdict, rationale bullets, one ship recommendation, and one next step.
|
|
171
177
|
*/
|
|
172
|
-
export declare function formatVerdictText(result: VerdictResult, options?:
|
|
173
|
-
|
|
174
|
-
lang?: Lang;
|
|
175
|
-
}): string;
|
|
178
|
+
export declare function formatVerdictText(result: VerdictResult, options?: VerdictTextOptions): string;
|
|
179
|
+
export {};
|
|
@@ -31,6 +31,7 @@ import { evaluateLayerGates } from './layer-gates.js';
|
|
|
31
31
|
import { ciLevelLabel } from './bootstrap.js';
|
|
32
32
|
import { analyzeJudgeIndependence } from './judge-independence.js';
|
|
33
33
|
import { MIN_HOLDOUT_SUBSET } from './holdout.js';
|
|
34
|
+
import { shellQuoteArg } from '../shared/shell-quote.js';
|
|
34
35
|
/**
|
|
35
36
|
* Below this sample count a non-significant diff is read as UNDERPOWERED
|
|
36
37
|
* (only large effects are detectable) rather than NOISE. Matches the
|
|
@@ -584,13 +585,26 @@ function recommendation(level, _perPair, lang = 'en') {
|
|
|
584
585
|
return 'ADD A CONTROL — single-variant report. Re-run with --control baseline --treatment <name>.';
|
|
585
586
|
}
|
|
586
587
|
}
|
|
587
|
-
function
|
|
588
|
+
function nextStepTreatmentName(result) {
|
|
589
|
+
if (result.representative?.treatment)
|
|
590
|
+
return result.representative.treatment;
|
|
591
|
+
if (result.level === 'SOLO')
|
|
592
|
+
return result.variants[0] ?? '<name>';
|
|
593
|
+
return result.variants[1] ?? '<name>';
|
|
594
|
+
}
|
|
595
|
+
function promoteCommand(target) {
|
|
596
|
+
return target ? `omk promote ${shellQuoteArg(target)}` : 'omk promote <name>';
|
|
597
|
+
}
|
|
598
|
+
function releaseNextStep(result, lang, promoteTarget) {
|
|
599
|
+
const treatment = nextStepTreatmentName(result);
|
|
600
|
+
const treatmentArg = shellQuoteArg(treatment);
|
|
601
|
+
const promote = promoteCommand(promoteTarget);
|
|
588
602
|
if (lang === 'zh') {
|
|
589
|
-
switch (level) {
|
|
603
|
+
switch (result.level) {
|
|
590
604
|
case 'PROGRESS':
|
|
591
|
-
return
|
|
605
|
+
return `可以发布:按你的正常发布流程发布,并留存本次报告作为发布证据;如果这是受管 skill,继续运行 \`${promote}\` 记录接受决定(name 以 \`omk list\` 的 NAME 为准)。`;
|
|
592
606
|
case 'CAUTIOUS':
|
|
593
|
-
return '
|
|
607
|
+
return '不要直接发布;先看触发的告警(分层门控、评委分歧、稳定性或 holdout),修完再重跑。';
|
|
594
608
|
case 'REGRESS':
|
|
595
609
|
return '不要发布;定位最差层和失败用例,修复后重跑。';
|
|
596
610
|
case 'NOISE':
|
|
@@ -598,14 +612,14 @@ function releaseNextStep(level, lang) {
|
|
|
598
612
|
case 'UNDERPOWERED':
|
|
599
613
|
return '把样本数加到至少 20,或先按当前规模 2× 扩充后重跑。';
|
|
600
614
|
case 'SOLO':
|
|
601
|
-
return
|
|
615
|
+
return `补一个 baseline 对照,再跑 \`omk eval --control baseline --treatment ${treatmentArg}\`。`;
|
|
602
616
|
}
|
|
603
617
|
}
|
|
604
|
-
switch (level) {
|
|
618
|
+
switch (result.level) {
|
|
605
619
|
case 'PROGRESS':
|
|
606
|
-
return
|
|
620
|
+
return `ship through your normal release path and keep this report as release evidence; for a managed skill, run \`${promote}\` to record acceptance (name is the NAME from \`omk list\`).`;
|
|
607
621
|
case 'CAUTIOUS':
|
|
608
|
-
return 'inspect the warnings (layer gates, judge dissent, stability, or holdout), fix them, then re-run.';
|
|
622
|
+
return 'do not ship directly; inspect the warnings (layer gates, judge dissent, stability, or holdout), fix them, then re-run.';
|
|
609
623
|
case 'REGRESS':
|
|
610
624
|
return 'do not ship; inspect the weakest layer and failing samples, fix them, then re-run.';
|
|
611
625
|
case 'NOISE':
|
|
@@ -613,7 +627,7 @@ function releaseNextStep(level, lang) {
|
|
|
613
627
|
case 'UNDERPOWERED':
|
|
614
628
|
return 'increase the sample set to at least 20, or roughly 2x the current size, then re-run.';
|
|
615
629
|
case 'SOLO':
|
|
616
|
-
return
|
|
630
|
+
return `add a baseline control and re-run \`omk eval --control baseline --treatment ${treatmentArg}\`.`;
|
|
617
631
|
}
|
|
618
632
|
}
|
|
619
633
|
/**
|
|
@@ -642,7 +656,7 @@ export function formatVerdictText(result, options = {}) {
|
|
|
642
656
|
lines.push(zh ? ` 知识缺口:${result.rationale.gapSignal}` : ` Gap signal: ${result.rationale.gapSignal}`);
|
|
643
657
|
if (result.rationale.shipRecommendation) {
|
|
644
658
|
lines.push(` ${zh ? recommendation(result.level, [], 'zh') : result.rationale.shipRecommendation}`);
|
|
645
|
-
lines.push(zh ? ` 下一步:${releaseNextStep(result
|
|
659
|
+
lines.push(zh ? ` 下一步:${releaseNextStep(result, 'zh', options.promoteTarget)}` : ` Next: ${releaseNextStep(result, 'en', options.promoteTarget)}`);
|
|
646
660
|
}
|
|
647
661
|
if (options.verbose && result.perPair && result.perPair.length > 1) {
|
|
648
662
|
lines.push('');
|
|
@@ -58,7 +58,7 @@ export declare function buildBatchVariantSpecs(entry: {
|
|
|
58
58
|
name: string;
|
|
59
59
|
skillPath: string;
|
|
60
60
|
}, variantAllowedSkills?: Record<string, string[]>): VariantSpec[];
|
|
61
|
-
export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skillEntries, skillResults, model, judgeModel, noJudge, executorName, judgeExecutorName, project, owner, tags, concurrency, timeoutMs, totalCostUSD, repeat, judgeModels, noCache, bootstrap, bootstrapSamples, lengthDebias, budget, }: {
|
|
61
|
+
export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skillEntries, skillResults, model, judgeModel, noJudge, executorName, judgeExecutorName, project, owner, tags, concurrency, timeoutMs, totalCostUSD, repeat, judgeModels, noCache, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, }: {
|
|
62
62
|
batchRunId: string;
|
|
63
63
|
skillDir: string;
|
|
64
64
|
skillEntries: Array<{
|
|
@@ -85,6 +85,7 @@ export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skill
|
|
|
85
85
|
bootstrapSamples?: number;
|
|
86
86
|
lengthDebias?: boolean;
|
|
87
87
|
budget?: import('../types/index.js').EvalBudget;
|
|
88
|
+
strictBaseline?: boolean;
|
|
88
89
|
}): {
|
|
89
90
|
report: BatchEvaluationReport;
|
|
90
91
|
job: import('../types/index.js').EvaluationJob;
|