@dailephd/my-dev-kit-lab 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +6 -4
- package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
- package/dist/src/cli/help.js +13 -3
- package/dist/src/commands/runExperimentRunCommand.js +64 -4
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
- package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
- package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
- package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
- package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
- package/dist/src/experiments/plugins/contextWindowScaling/index.js +14 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
- package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
- package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
- package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
- package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
- package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
- package/dist/src/experiments/plugins/index.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
- package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
- package/dist/src/plots/index.js +1 -0
- package/dist/src/plots/writePlotArtifacts.js +12 -3
- package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +22 -2
- package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
- package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +8 -0
- package/docs/METRICS.md +27 -1
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { CONTEXT_WINDOW_SCALING_TREATMENT_IDS } from "./metadata.js";
|
|
2
|
+
export function aggregateContextWindowScaling(args) {
|
|
3
|
+
const budgets = [...args.contextBudgets].sort((a, b) => a - b);
|
|
4
|
+
const treatments = [...CONTEXT_WINDOW_SCALING_TREATMENT_IDS];
|
|
5
|
+
const treatmentsOf = (variantId) => args.cases.map((caseEvidence) => ({ caseEvidence, treatment: requireTreatment(caseEvidence, variantId, budgets) }));
|
|
6
|
+
const budgetTreatmentSummaries = [];
|
|
7
|
+
for (const contextBudgetTokens of budgets) {
|
|
8
|
+
for (const variantId of treatments) {
|
|
9
|
+
const cells = treatmentsOf(variantId).map(({ treatment }) => cellFor(treatment, contextBudgetTokens));
|
|
10
|
+
budgetTreatmentSummaries.push(summarizeCells(contextBudgetTokens, variantId, cells));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const caseTreatmentContextSummaries = args.cases.flatMap((caseEvidence) => treatments.map((variantId) => {
|
|
14
|
+
const treatment = requireTreatment(caseEvidence, variantId, budgets);
|
|
15
|
+
return {
|
|
16
|
+
caseId: caseEvidence.caseId,
|
|
17
|
+
caseName: caseEvidence.caseName,
|
|
18
|
+
variantId,
|
|
19
|
+
contextStatus: treatment.context.status,
|
|
20
|
+
characterCount: treatment.context.characterCount,
|
|
21
|
+
estimatedTokens: treatment.context.estimatedTokens,
|
|
22
|
+
tokenCountMethod: treatment.context.tokenCountMethod,
|
|
23
|
+
observedFileCount: treatment.context.observedFiles === null ? null : treatment.context.observedFiles.length,
|
|
24
|
+
relevantFileEvidence: structuredClone(treatment.relevantFileEvidence),
|
|
25
|
+
};
|
|
26
|
+
}));
|
|
27
|
+
const allCells = budgetTreatmentSummaries;
|
|
28
|
+
const relevant = caseTreatmentContextSummaries.map((summary) => summary.relevantFileEvidence);
|
|
29
|
+
const relevantCount = (status) => relevant.filter((evidence) => evidence.status === status).length;
|
|
30
|
+
const relevantFileSummary = {
|
|
31
|
+
relevantFileEvidenceAvailableCount: relevantCount("available"),
|
|
32
|
+
relevantFileEvidenceUnavailableCount: relevantCount("unavailable"),
|
|
33
|
+
relevantFileEvidenceNotApplicableCount: relevantCount("not-applicable"),
|
|
34
|
+
totalOmittedRelevantFileCount: relevant.reduce((sum, evidence) => sum + (evidence.omittedRelevantFileCount ?? 0), 0),
|
|
35
|
+
};
|
|
36
|
+
const sumOf = (pick) => allCells.reduce((sum, summary) => sum + pick(summary), 0);
|
|
37
|
+
const evaluatedBudgetCellCount = args.cases
|
|
38
|
+
.flatMap((caseEvidence) => caseEvidence.treatments)
|
|
39
|
+
.flatMap((treatment) => treatment.budgetCells)
|
|
40
|
+
.filter((cell) => cell.evaluationStatus === "evaluated").length;
|
|
41
|
+
return {
|
|
42
|
+
budgets,
|
|
43
|
+
treatments,
|
|
44
|
+
runSummary: {
|
|
45
|
+
caseCount: args.cases.length,
|
|
46
|
+
treatmentCount: treatments.length,
|
|
47
|
+
budgetCount: budgets.length,
|
|
48
|
+
totalBudgetCellCount: sumOf((s) => s.totalCellCount),
|
|
49
|
+
evaluatedBudgetCellCount,
|
|
50
|
+
contextTooLargeCellCount: sumOf((s) => s.contextTooLargeCount),
|
|
51
|
+
contextUnavailableCellCount: sumOf((s) => s.contextUnavailableCount),
|
|
52
|
+
correctnessAvailableCellCount: sumOf((s) => s.correctnessAvailableCount),
|
|
53
|
+
correctnessUnavailableCellCount: sumOf((s) => s.correctnessUnavailableCount),
|
|
54
|
+
successEvidenceAvailableCellCount: sumOf((s) => s.successEvidenceAvailableCount),
|
|
55
|
+
successEvidenceUnavailableCellCount: sumOf((s) => s.successEvidenceUnavailableCount),
|
|
56
|
+
relevantFileEvidenceAvailableTreatmentCount: relevantFileSummary.relevantFileEvidenceAvailableCount,
|
|
57
|
+
relevantFileEvidenceUnavailableTreatmentCount: relevantFileSummary.relevantFileEvidenceUnavailableCount,
|
|
58
|
+
relevantFileEvidenceNotApplicableTreatmentCount: relevantFileSummary.relevantFileEvidenceNotApplicableCount,
|
|
59
|
+
},
|
|
60
|
+
budgetTreatmentSummaries,
|
|
61
|
+
caseTreatmentContextSummaries,
|
|
62
|
+
relevantFileSummary,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function requireTreatment(caseEvidence, variantId, budgets) {
|
|
66
|
+
const matches = caseEvidence.treatments.filter((treatment) => treatment.variantId === variantId);
|
|
67
|
+
const treatment = matches[0];
|
|
68
|
+
if (matches.length !== 1 || !treatment) {
|
|
69
|
+
throw new Error(`Invalid context-window-scaling evidence: case ${caseEvidence.caseId} must have exactly one ${variantId} treatment.`);
|
|
70
|
+
}
|
|
71
|
+
const cellBudgets = treatment.budgetCells.map((cell) => cell.contextBudgetTokens);
|
|
72
|
+
if (cellBudgets.length !== budgets.length || budgets.some((budget) => cellBudgets.filter((value) => value === budget).length !== 1)) {
|
|
73
|
+
throw new Error(`Invalid context-window-scaling evidence: case ${caseEvidence.caseId} ${variantId} cells do not match the selected budgets.`);
|
|
74
|
+
}
|
|
75
|
+
return treatment;
|
|
76
|
+
}
|
|
77
|
+
function cellFor(treatment, budget) {
|
|
78
|
+
return treatment.budgetCells.find((cell) => cell.contextBudgetTokens === budget);
|
|
79
|
+
}
|
|
80
|
+
function isFiniteNumber(value) {
|
|
81
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
82
|
+
}
|
|
83
|
+
function summarizeCells(contextBudgetTokens, variantId, cells) {
|
|
84
|
+
const fitCount = cells.filter((cell) => cell.contextFitStatus === "fits").length;
|
|
85
|
+
const contextTooLargeCount = cells.filter((cell) => cell.contextFitStatus === "context-too-large").length;
|
|
86
|
+
const contextUnavailableCount = cells.filter((cell) => cell.contextFitStatus === "unavailable").length;
|
|
87
|
+
const successAvailable = cells.filter((cell) => cell.successEvidence.status === "available");
|
|
88
|
+
const successfulCellCount = successAvailable.filter((cell) => cell.successEvidence.success === true).length;
|
|
89
|
+
const scores = cells
|
|
90
|
+
.filter((cell) => cell.correctness.availability === "available")
|
|
91
|
+
.map((cell) => cell.correctness.score)
|
|
92
|
+
.filter(isFiniteNumber);
|
|
93
|
+
const utilizations = cells.map((cell) => cell.contextBudgetUtilizationPercent).filter(isFiniteNumber);
|
|
94
|
+
const mean = (values) => (values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0) / values.length);
|
|
95
|
+
return {
|
|
96
|
+
contextBudgetTokens,
|
|
97
|
+
variantId,
|
|
98
|
+
totalCellCount: cells.length,
|
|
99
|
+
contextMeasuredCount: fitCount + contextTooLargeCount,
|
|
100
|
+
fitCount,
|
|
101
|
+
contextTooLargeCount,
|
|
102
|
+
contextUnavailableCount,
|
|
103
|
+
successfulCellCount,
|
|
104
|
+
notSuccessfulCellCount: successAvailable.length - successfulCellCount,
|
|
105
|
+
successEvidenceAvailableCount: successAvailable.length,
|
|
106
|
+
successEvidenceUnavailableCount: cells.length - successAvailable.length,
|
|
107
|
+
successRatePercent: successAvailable.length === 0 ? null : (successfulCellCount / successAvailable.length) * 100,
|
|
108
|
+
correctnessAvailableCount: scores.length,
|
|
109
|
+
correctnessUnavailableCount: cells.length - scores.length,
|
|
110
|
+
meanCorrectnessScore: mean(scores),
|
|
111
|
+
utilizationAvailableCount: utilizations.length,
|
|
112
|
+
utilizationUnavailableCount: cells.length - utilizations.length,
|
|
113
|
+
meanContextBudgetUtilizationPercent: mean(utilizations),
|
|
114
|
+
minContextBudgetUtilizationPercent: utilizations.length === 0 ? null : Math.min(...utilizations),
|
|
115
|
+
maxContextBudgetUtilizationPercent: utilizations.length === 0 ? null : Math.max(...utilizations),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Run-level scalar metrics: one set per budget x treatment, scoped by variantId and budget-bearing ids. */
|
|
119
|
+
export function toAggregateExperimentMetrics(aggregate) {
|
|
120
|
+
const metrics = [
|
|
121
|
+
{ id: "context-window-scaling-case-count", name: "Context-window-scaling case count", value: aggregate.runSummary.caseCount, unit: "count" },
|
|
122
|
+
{ id: "context-window-scaling-budget-count", name: "Context budget count", value: aggregate.runSummary.budgetCount, unit: "count" },
|
|
123
|
+
];
|
|
124
|
+
for (const summary of aggregate.budgetTreatmentSummaries) {
|
|
125
|
+
const prefix = `context-budget-${summary.contextBudgetTokens}`;
|
|
126
|
+
const scoped = (suffix, name, value, unit, description) => ({
|
|
127
|
+
id: `${prefix}-${suffix}`,
|
|
128
|
+
name: `${name} (${summary.contextBudgetTokens} token budget)`,
|
|
129
|
+
value,
|
|
130
|
+
unit,
|
|
131
|
+
...(description ? { description } : {}),
|
|
132
|
+
variantId: summary.variantId,
|
|
133
|
+
});
|
|
134
|
+
metrics.push(scoped("fit-count", "Cases whose context fits", summary.fitCount, "count"), scoped("context-too-large-count", "Cases whose context is too large", summary.contextTooLargeCount, "count"), scoped("successful-cell-count", "Successful cells", summary.successfulCellCount, "count"), scoped("success-rate-percent", "Success rate", summary.successRatePercent, "percent", "Successful cells over success-evidence-available cells; null when none are available."), scoped("mean-correctness-score", "Mean correctness score", summary.meanCorrectnessScore, "score", "Mean of available existing correctness scores; unavailable correctness is excluded, not zero."), scoped("mean-context-budget-utilization-percent", "Mean context budget utilization", summary.meanContextBudgetUtilizationPercent, "percent", "Uncapped; may exceed 100."));
|
|
135
|
+
}
|
|
136
|
+
return metrics;
|
|
137
|
+
}
|
|
138
|
+
/** Per-outcome scalar metrics for one treatment, one set per budget cell. */
|
|
139
|
+
export function toOutcomeBudgetMetrics(treatment, caseId) {
|
|
140
|
+
return treatment.budgetCells.flatMap((cell) => {
|
|
141
|
+
const base = { variantId: treatment.variantId, caseId };
|
|
142
|
+
const prefix = `budget-${cell.contextBudgetTokens}`;
|
|
143
|
+
return [
|
|
144
|
+
{ id: `${prefix}-context-budget-utilization-percent`, name: `Context budget utilization (${cell.contextBudgetTokens})`, value: cell.contextBudgetUtilizationPercent, unit: "percent", ...base },
|
|
145
|
+
{ id: `${prefix}-correctness-score`, name: `Correctness score (${cell.contextBudgetTokens})`, value: cell.correctness.score, unit: "score", ...base },
|
|
146
|
+
{ id: `${prefix}-success`, name: `Success (${cell.contextBudgetTokens})`, value: cell.successEvidence.success, ...base },
|
|
147
|
+
];
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tokenCountMethod } from "../../../core/countTokens.js";
|
|
4
|
+
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
5
|
+
import { summarizeExperimentRun } from "../../results.js";
|
|
6
|
+
import { aggregateStatus } from "../warmIndexReuse/execution.js";
|
|
7
|
+
import { contextWindowScalingConfigDefinition, defaultContextWindowScalingConfig, validateContextWindowScalingConfig, } from "./config.js";
|
|
8
|
+
import { executeContextWindowScalingCases, } from "./execution.js";
|
|
9
|
+
import { buildContextWindowScalingExecutionArtifact, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE, } from "./executionArtifact.js";
|
|
10
|
+
import { CONTEXT_WINDOW_SCALING_TREATMENT_IDS, contextWindowScalingMetadata } from "./metadata.js";
|
|
11
|
+
import { aggregateContextWindowScaling, toAggregateExperimentMetrics, toOutcomeBudgetMetrics, } from "./metrics.js";
|
|
12
|
+
import { resolveScalingProjectProfiles } from "./projectProfile.js";
|
|
13
|
+
const VARIANTS = [
|
|
14
|
+
{
|
|
15
|
+
id: "raw-full-file",
|
|
16
|
+
name: "Raw full file",
|
|
17
|
+
description: "Every file matched by the case's raw include globs, constructed once per case.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "my-dev-kit-guided",
|
|
21
|
+
name: "my-dev-kit guided",
|
|
22
|
+
description: "my-dev-kit retrieval context, constructed once per case.",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/** Internal-only seam: tests inject construction/evaluation owners through inputs.dependencies. */
|
|
26
|
+
export const contextWindowScalingPlugin = {
|
|
27
|
+
metadata: contextWindowScalingMetadata,
|
|
28
|
+
defaultConfig: defaultContextWindowScalingConfig,
|
|
29
|
+
configDefinition: contextWindowScalingConfigDefinition,
|
|
30
|
+
supportedVariants: CONTEXT_WINDOW_SCALING_TREATMENT_IDS,
|
|
31
|
+
validateConfig: validateContextWindowScalingConfig,
|
|
32
|
+
async run(context) {
|
|
33
|
+
const startedAt = context.startedAt.toISOString();
|
|
34
|
+
const cases = readCasesInput(context.inputs);
|
|
35
|
+
const projectProfiles = resolveScalingProjectProfiles(cases, readProjectProfilesInput(context.inputs));
|
|
36
|
+
const outDir = context.outputRoot ?? path.resolve(context.toolRoot, "lab-output", "context-window-scaling");
|
|
37
|
+
await mkdir(outDir, { recursive: true });
|
|
38
|
+
const executionEvidence = await executeContextWindowScalingCases({
|
|
39
|
+
cases,
|
|
40
|
+
contextBudgets: context.config.contextBudgets,
|
|
41
|
+
kitCommand: context.config.kitCommand,
|
|
42
|
+
outputRoot: outDir,
|
|
43
|
+
projectProfiles,
|
|
44
|
+
cwd: context.target.targetRoot,
|
|
45
|
+
env: readEnvInput(context.inputs),
|
|
46
|
+
dependencies: readDependenciesInput(context.inputs),
|
|
47
|
+
});
|
|
48
|
+
const completedAt = new Date().toISOString();
|
|
49
|
+
const artifact = buildContextWindowScalingExecutionArtifact({
|
|
50
|
+
runId: context.runId,
|
|
51
|
+
pluginId: contextWindowScalingMetadata.id,
|
|
52
|
+
pluginSchemaVersion: contextWindowScalingMetadata.schemaVersion,
|
|
53
|
+
startedAt,
|
|
54
|
+
completedAt,
|
|
55
|
+
tokenCountMethod,
|
|
56
|
+
contextBudgets: context.config.contextBudgets,
|
|
57
|
+
cases: executionEvidence,
|
|
58
|
+
});
|
|
59
|
+
const artifactPath = resolveWithinRoot(outDir, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE);
|
|
60
|
+
await writeFile(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
61
|
+
return mapExecutionToRun({
|
|
62
|
+
runId: context.runId,
|
|
63
|
+
startedAt,
|
|
64
|
+
completedAt,
|
|
65
|
+
target: context.target,
|
|
66
|
+
contextBudgets: context.config.contextBudgets,
|
|
67
|
+
executionEvidence,
|
|
68
|
+
artifactPath,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
summarize(result) {
|
|
72
|
+
return result.summary ?? summarizeExperimentRun(result);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
export function mapExecutionToRun(args) {
|
|
76
|
+
const cases = args.executionEvidence.map((caseEvidence) => ({
|
|
77
|
+
id: caseEvidence.caseId,
|
|
78
|
+
name: caseEvidence.caseName,
|
|
79
|
+
outcomes: caseEvidence.treatments.map((treatment) => buildOutcome(caseEvidence, treatment)),
|
|
80
|
+
metadata: { benchmarkProject: caseEvidence.benchmarkProject },
|
|
81
|
+
}));
|
|
82
|
+
const aggregate = aggregateContextWindowScaling({ contextBudgets: args.contextBudgets, cases: args.executionEvidence });
|
|
83
|
+
const run = {
|
|
84
|
+
runId: args.runId,
|
|
85
|
+
pluginId: contextWindowScalingMetadata.id,
|
|
86
|
+
startedAt: args.startedAt,
|
|
87
|
+
completedAt: args.completedAt,
|
|
88
|
+
status: aggregateStatus(cases.flatMap((experimentCase) => experimentCase.outcomes.map((outcome) => outcome.status))),
|
|
89
|
+
target: args.target,
|
|
90
|
+
variants: VARIANTS.map((variant) => ({ ...variant })),
|
|
91
|
+
cases,
|
|
92
|
+
metrics: toAggregateExperimentMetrics(aggregate),
|
|
93
|
+
artifacts: [
|
|
94
|
+
{
|
|
95
|
+
id: "context-window-scaling-execution",
|
|
96
|
+
label: "Context window scaling execution evidence",
|
|
97
|
+
path: args.artifactPath,
|
|
98
|
+
kind: "artifact",
|
|
99
|
+
mimeType: "application/json",
|
|
100
|
+
description: "Per-case, per-treatment, per-budget fit, correctness, success, and omitted-file evidence without context text.",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
warnings: [],
|
|
104
|
+
failures: [],
|
|
105
|
+
metadata: { executionArtifactPath: args.artifactPath },
|
|
106
|
+
contextBudgets: [...args.contextBudgets],
|
|
107
|
+
executionEvidence: structuredClone([...args.executionEvidence]),
|
|
108
|
+
aggregate,
|
|
109
|
+
};
|
|
110
|
+
run.summary = summarizeExperimentRun(run);
|
|
111
|
+
return run;
|
|
112
|
+
}
|
|
113
|
+
function buildOutcome(caseEvidence, treatment) {
|
|
114
|
+
const { caseId } = caseEvidence;
|
|
115
|
+
const variantId = treatment.variantId;
|
|
116
|
+
const metrics = [];
|
|
117
|
+
if (treatment.context.estimatedTokens !== null) {
|
|
118
|
+
metrics.push({
|
|
119
|
+
id: "context-estimated-tokens",
|
|
120
|
+
name: "Context estimated tokens",
|
|
121
|
+
value: treatment.context.estimatedTokens,
|
|
122
|
+
unit: "tokens",
|
|
123
|
+
variantId,
|
|
124
|
+
caseId,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (treatment.relevantFileEvidence.omittedRelevantFileCount !== null) {
|
|
128
|
+
metrics.push({
|
|
129
|
+
id: "omitted-relevant-file-count",
|
|
130
|
+
name: "Omitted relevant files",
|
|
131
|
+
value: treatment.relevantFileEvidence.omittedRelevantFileCount,
|
|
132
|
+
unit: "files",
|
|
133
|
+
variantId,
|
|
134
|
+
caseId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
metrics.push(...toOutcomeBudgetMetrics(treatment, caseId));
|
|
138
|
+
const warnings = [
|
|
139
|
+
...treatment.context.warnings.map((message) => ({ code: "guided-retrieval-warning", message, variantId, caseId })),
|
|
140
|
+
...(treatment.evaluation.status === "unavailable"
|
|
141
|
+
? [{ code: "deterministic-evaluation-unavailable", message: treatment.evaluation.reason ?? "Evaluation unavailable.", variantId, caseId }]
|
|
142
|
+
: []),
|
|
143
|
+
];
|
|
144
|
+
const failures = treatment.errors.map((error) => ({
|
|
145
|
+
code: error.code,
|
|
146
|
+
message: error.message,
|
|
147
|
+
variantId,
|
|
148
|
+
caseId,
|
|
149
|
+
recoverable: true,
|
|
150
|
+
}));
|
|
151
|
+
return {
|
|
152
|
+
id: `${caseId}:${variantId}`,
|
|
153
|
+
caseId,
|
|
154
|
+
variantId,
|
|
155
|
+
status: treatment.status,
|
|
156
|
+
metrics,
|
|
157
|
+
artifacts: [],
|
|
158
|
+
warnings,
|
|
159
|
+
failures,
|
|
160
|
+
metadata: {
|
|
161
|
+
benchmarkProject: caseEvidence.benchmarkProject,
|
|
162
|
+
contextStatus: treatment.context.status,
|
|
163
|
+
evaluationStatus: treatment.evaluation.status,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function readCasesInput(inputs) {
|
|
168
|
+
const value = inputs?.cases;
|
|
169
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
170
|
+
throw new Error("Context window scaling requires a non-empty cases input.");
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
function readProjectProfilesInput(inputs) {
|
|
175
|
+
const value = inputs?.projectProfiles;
|
|
176
|
+
return Array.isArray(value) ? value : [];
|
|
177
|
+
}
|
|
178
|
+
function readEnvInput(inputs) {
|
|
179
|
+
const env = inputs?.env;
|
|
180
|
+
return env && typeof env === "object" && !Array.isArray(env) ? env : undefined;
|
|
181
|
+
}
|
|
182
|
+
function readDependenciesInput(inputs) {
|
|
183
|
+
const value = inputs?.dependencies;
|
|
184
|
+
return value && typeof value === "object" ? value : undefined;
|
|
185
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { calculateProjectComplexityScore, computeProjectComplexityMetrics, PROJECT_COMPLEXITY_FORMULA, } from "../../../evaluation/projectComplexity.js";
|
|
3
|
+
import { buildProjectFileTree } from "../../../evaluation/projectFileTree.js";
|
|
4
|
+
/**
|
|
5
|
+
* The fixed scaling project has no entry in the shared benchmark-project-profiles contract. The
|
|
6
|
+
* existing prompt owner requires a profile per case, so a missing one is derived in memory from
|
|
7
|
+
* the existing file-tree and complexity owners. Supplied profiles always win; nothing is written.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveScalingProjectProfiles(cases, supplied) {
|
|
10
|
+
const profiles = [...supplied];
|
|
11
|
+
const known = new Set(profiles.map((profile) => profile.projectId));
|
|
12
|
+
for (const evaluationCase of cases) {
|
|
13
|
+
const profileId = evaluationCase.projectProfileRef ?? evaluationCase.benchmarkProject;
|
|
14
|
+
if (known.has(profileId))
|
|
15
|
+
continue;
|
|
16
|
+
known.add(profileId);
|
|
17
|
+
profiles.push(deriveProfile(profileId, evaluationCase, cases));
|
|
18
|
+
}
|
|
19
|
+
return profiles;
|
|
20
|
+
}
|
|
21
|
+
function deriveProfile(profileId, evaluationCase, cases) {
|
|
22
|
+
const siblings = cases.filter((candidate) => (candidate.projectProfileRef ?? candidate.benchmarkProject) === profileId);
|
|
23
|
+
const fileTree = buildProjectFileTree(evaluationCase.absoluteTargetRoot);
|
|
24
|
+
const average = (values) => (values.length === 0 ? 0 : values.reduce((a, b) => a + b, 0) / values.length);
|
|
25
|
+
const complexityMetrics = computeProjectComplexityMetrics(evaluationCase.absoluteTargetRoot, fileTree, {
|
|
26
|
+
taskCount: siblings.length,
|
|
27
|
+
expectedRelevantFilesAverage: average(siblings.map((candidate) => candidate.expectedFiles.length)),
|
|
28
|
+
expectedRelevantSymbolsAverage: average(siblings.map((candidate) => candidate.expectedSymbols.length)),
|
|
29
|
+
});
|
|
30
|
+
const languages = [
|
|
31
|
+
...new Set(fileTree.entries.map((entry) => entry.language).filter((language) => Boolean(language))),
|
|
32
|
+
].sort();
|
|
33
|
+
return {
|
|
34
|
+
projectId: profileId,
|
|
35
|
+
displayName: profileId,
|
|
36
|
+
description: "Profile derived in memory for the context-window-scaling fixed corpus.",
|
|
37
|
+
languageMix: languages.length <= 1 ? `single-language ${languages[0] ?? "unknown"}` : "multi-language",
|
|
38
|
+
primaryLanguage: languages[0] ?? "unknown",
|
|
39
|
+
languages,
|
|
40
|
+
complexityLevel: "large",
|
|
41
|
+
complexityScore: calculateProjectComplexityScore(complexityMetrics),
|
|
42
|
+
complexityMetrics,
|
|
43
|
+
complexityFormula: PROJECT_COMPLEXITY_FORMULA,
|
|
44
|
+
rootPath: path.posix.normalize(evaluationCase.targetRoot.replace(/\\/g, "/")),
|
|
45
|
+
sourceRoots: [...evaluationCase.sourceRoots],
|
|
46
|
+
testRoots: ["tests"],
|
|
47
|
+
fileTree,
|
|
48
|
+
benchmarkPurpose: "Context-window scaling evaluation.",
|
|
49
|
+
expectedUseCases: ["context budget fit evaluation"],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded repository-relative identity: "\" becomes "/", leading "./" is removed, case is kept.
|
|
3
|
+
* Returns null for absolute paths, drive paths, parent traversal, empty segments, or oversized values.
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeRepositoryRelativePath(value) {
|
|
6
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
let normalized = value.replace(/\\/g, "/");
|
|
10
|
+
while (normalized.startsWith("./")) {
|
|
11
|
+
normalized = normalized.slice(2);
|
|
12
|
+
}
|
|
13
|
+
if (normalized.length === 0 || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (normalized.split("/").some((segment) => segment === ".." || segment === "")) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
/** observedFiles === null means observed-file provenance is unavailable. */
|
|
22
|
+
export function computeRelevantFileEvidence(args) {
|
|
23
|
+
if (args.expectedFiles.length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
status: "not-applicable",
|
|
26
|
+
expectedRelevantFiles: [],
|
|
27
|
+
expectedRelevantFileCount: 0,
|
|
28
|
+
observedExpectedFileCount: null,
|
|
29
|
+
omittedRelevantFileCount: null,
|
|
30
|
+
omittedRelevantFiles: [],
|
|
31
|
+
reason: "No expected relevant files are defined for this case.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const expected = [];
|
|
35
|
+
for (const file of args.expectedFiles) {
|
|
36
|
+
const normalized = normalizeRepositoryRelativePath(file);
|
|
37
|
+
if (normalized === null) {
|
|
38
|
+
return unavailable([...args.expectedFiles], "An expected relevant file is not a bounded repository-relative path.");
|
|
39
|
+
}
|
|
40
|
+
if (!expected.includes(normalized)) {
|
|
41
|
+
expected.push(normalized);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (args.observedFiles === null) {
|
|
45
|
+
return unavailable(expected, "Observed-file provenance is unavailable.");
|
|
46
|
+
}
|
|
47
|
+
const observed = new Set();
|
|
48
|
+
for (const file of args.observedFiles) {
|
|
49
|
+
const normalized = normalizeRepositoryRelativePath(file);
|
|
50
|
+
if (normalized === null) {
|
|
51
|
+
return unavailable(expected, "An observed file is not a bounded repository-relative path.");
|
|
52
|
+
}
|
|
53
|
+
observed.add(normalized);
|
|
54
|
+
}
|
|
55
|
+
const omitted = expected.filter((file) => !observed.has(file));
|
|
56
|
+
return {
|
|
57
|
+
status: "available",
|
|
58
|
+
expectedRelevantFiles: expected,
|
|
59
|
+
expectedRelevantFileCount: expected.length,
|
|
60
|
+
observedExpectedFileCount: expected.length - omitted.length,
|
|
61
|
+
omittedRelevantFileCount: omitted.length,
|
|
62
|
+
omittedRelevantFiles: omitted,
|
|
63
|
+
reason: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function unavailable(expected, reason) {
|
|
67
|
+
return {
|
|
68
|
+
status: "unavailable",
|
|
69
|
+
expectedRelevantFiles: expected,
|
|
70
|
+
expectedRelevantFileCount: expected.length,
|
|
71
|
+
observedExpectedFileCount: null,
|
|
72
|
+
omittedRelevantFileCount: null,
|
|
73
|
+
omittedRelevantFiles: [],
|
|
74
|
+
reason,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { calculateContextBudgetUtilizationPercent, classifyContextFit } from "./contextBudget.js";
|
|
2
|
+
export const UNAVAILABLE_CORRECTNESS = { availability: "unavailable", score: null, pass: null };
|
|
3
|
+
/** The existing scorer's own passed result is used as-is; no score threshold is defined here. */
|
|
4
|
+
export function toCorrectnessEvidence(scored) {
|
|
5
|
+
if (!scored.available || scored.score === null || scored.passed === null) {
|
|
6
|
+
return { ...UNAVAILABLE_CORRECTNESS };
|
|
7
|
+
}
|
|
8
|
+
return { availability: "available", score: scored.score, pass: scored.passed };
|
|
9
|
+
}
|
|
10
|
+
export function deriveSuccessEvidence(args) {
|
|
11
|
+
if (args.contextFitStatus === "unavailable") {
|
|
12
|
+
return { status: "unavailable", success: null, reason: "context-unavailable" };
|
|
13
|
+
}
|
|
14
|
+
if (args.contextFitStatus === "context-too-large") {
|
|
15
|
+
return { status: "available", success: false, reason: "context-too-large" };
|
|
16
|
+
}
|
|
17
|
+
if (args.correctness.availability !== "available" || args.correctness.pass === null) {
|
|
18
|
+
return { status: "unavailable", success: null, reason: "evaluation-unavailable" };
|
|
19
|
+
}
|
|
20
|
+
return args.correctness.pass
|
|
21
|
+
? { status: "available", success: true, reason: "correctness-pass" }
|
|
22
|
+
: { status: "available", success: false, reason: "correctness-fail" };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One cell per budget. `shared` is the single deterministic evaluation of this treatment's context
|
|
26
|
+
* (null when it was never requested because no budget fits or the context is unavailable).
|
|
27
|
+
*/
|
|
28
|
+
export function buildBudgetCell(args) {
|
|
29
|
+
if (args.contextEstimatedTokens === null) {
|
|
30
|
+
return {
|
|
31
|
+
contextBudgetTokens: args.contextBudgetTokens,
|
|
32
|
+
contextFitStatus: "unavailable",
|
|
33
|
+
contextBudgetUtilizationPercent: null,
|
|
34
|
+
evaluationStatus: "unavailable",
|
|
35
|
+
correctness: { ...UNAVAILABLE_CORRECTNESS },
|
|
36
|
+
successEvidence: deriveSuccessEvidence({ contextFitStatus: "unavailable", correctness: UNAVAILABLE_CORRECTNESS }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const fit = classifyContextFit(args.contextEstimatedTokens, args.contextBudgetTokens);
|
|
40
|
+
const utilization = calculateContextBudgetUtilizationPercent(args.contextEstimatedTokens, args.contextBudgetTokens);
|
|
41
|
+
if (fit === "context-too-large") {
|
|
42
|
+
return {
|
|
43
|
+
contextBudgetTokens: args.contextBudgetTokens,
|
|
44
|
+
contextFitStatus: fit,
|
|
45
|
+
contextBudgetUtilizationPercent: utilization,
|
|
46
|
+
evaluationStatus: "not-evaluated-context-too-large",
|
|
47
|
+
correctness: { ...UNAVAILABLE_CORRECTNESS },
|
|
48
|
+
successEvidence: deriveSuccessEvidence({ contextFitStatus: fit, correctness: UNAVAILABLE_CORRECTNESS }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const correctness = args.shared ?? { ...UNAVAILABLE_CORRECTNESS };
|
|
52
|
+
return {
|
|
53
|
+
contextBudgetTokens: args.contextBudgetTokens,
|
|
54
|
+
contextFitStatus: fit,
|
|
55
|
+
contextBudgetUtilizationPercent: utilization,
|
|
56
|
+
evaluationStatus: correctness.availability === "available" ? "evaluated" : "unavailable",
|
|
57
|
+
correctness: { ...correctness },
|
|
58
|
+
successEvidence: deriveSuccessEvidence({ contextFitStatus: fit, correctness }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -41,7 +41,7 @@ export async function evaluateWarmIndexFakeAgents(options) {
|
|
|
41
41
|
}
|
|
42
42
|
return result;
|
|
43
43
|
}
|
|
44
|
-
async function evaluateFakeSide(args) {
|
|
44
|
+
export async function evaluateFakeSide(args) {
|
|
45
45
|
const promptStrategy = SIDE_STRATEGIES[args.variantId];
|
|
46
46
|
const base = { variantId: args.variantId, agentId: "fake-agent", promptStrategy };
|
|
47
47
|
try {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE } from "../experiments/plugins/contextWindowScaling/executionArtifact.js";
|
|
5
|
+
import { parseContextWindowScalingExecutionArtifact } from "../experiments/plugins/contextWindowScaling/executionArtifactReader.js";
|
|
6
|
+
import { aggregateContextWindowScaling } from "../experiments/plugins/contextWindowScaling/metrics.js";
|
|
7
|
+
import { STANDARD_CONTEXT_BUDGETS } from "../experiments/plugins/contextWindowScaling/contextBudget.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Context-window-scaling plot data. Maps values already calculated by
|
|
10
|
+
// aggregateContextWindowScaling() (carried on ContextWindowScalingRun.aggregate) to plot
|
|
11
|
+
// points. Nothing is recalculated; a null aggregate value becomes a skipped point, never 0.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export const CONTEXT_WINDOW_SCALING_PLOT_IDS = [
|
|
14
|
+
"context-window-scaling-context-size",
|
|
15
|
+
"context-window-scaling-success-rate-by-budget",
|
|
16
|
+
"context-window-scaling-correctness-by-budget",
|
|
17
|
+
];
|
|
18
|
+
export const CONTEXT_WINDOW_SCALING_SKIP_REASONS = {
|
|
19
|
+
contextSize: "context-size-unavailable",
|
|
20
|
+
successRate: "success-rate-unavailable",
|
|
21
|
+
correctness: "correctness-unavailable",
|
|
22
|
+
};
|
|
23
|
+
/** Standard budgets read as 8k/16k/32k/64k; any other budget keeps its exact integer form. */
|
|
24
|
+
export function formatContextBudgetLabel(budget) {
|
|
25
|
+
return STANDARD_CONTEXT_BUDGETS.includes(budget) ? `${budget / 1024}k` : String(budget);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the aggregate rebuilt from `<experimentDir>/context-window-scaling-execution.json`, or null
|
|
29
|
+
* when that artifact is absent. A present artifact is validated (schema version and structure) and
|
|
30
|
+
* aggregated by the canonical aggregateContextWindowScaling(); malformed input fails, never falls back.
|
|
31
|
+
*/
|
|
32
|
+
export async function readContextWindowScalingPlotSource(experimentDir) {
|
|
33
|
+
const artifactPath = path.join(experimentDir, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE);
|
|
34
|
+
if (!existsSync(artifactPath))
|
|
35
|
+
return null;
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(await readFile(artifactPath, "utf8"));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Invalid context-window-scaling execution artifact (${artifactPath}): not valid JSON (${error instanceof Error ? error.message : String(error)}).`);
|
|
42
|
+
}
|
|
43
|
+
const artifact = parseContextWindowScalingExecutionArtifact(parsed);
|
|
44
|
+
return {
|
|
45
|
+
aggregate: aggregateContextWindowScaling({ contextBudgets: artifact.contextBudgets, cases: artifact.cases }),
|
|
46
|
+
generatedAt: artifact.completedAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function buildContextWindowScalingPlotData(args) {
|
|
50
|
+
const { aggregate } = args;
|
|
51
|
+
const skippedPoints = [];
|
|
52
|
+
const series = (id, title, xLabel, yLabel) => ({
|
|
53
|
+
id,
|
|
54
|
+
title,
|
|
55
|
+
xLabel,
|
|
56
|
+
yLabel,
|
|
57
|
+
kind: "scatter",
|
|
58
|
+
points: [],
|
|
59
|
+
warnings: [],
|
|
60
|
+
});
|
|
61
|
+
const contextSize = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[0], "Raw vs Retrieved Context Size", "Case ordinal (canonical case order)", "Estimated context tokens");
|
|
62
|
+
const successRate = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[1], "Success Rate by Context Budget", "Context budget (estimated tokens)", "Success rate (percent)");
|
|
63
|
+
const correctness = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[2], "Correctness by Context Budget", "Context budget (estimated tokens)", "Mean correctness score");
|
|
64
|
+
const add = (plot, x, value, group, label, reason, metadata) => {
|
|
65
|
+
if (value !== null && Number.isFinite(value)) {
|
|
66
|
+
plot.points.push({ x, y: value, group, label, metadata });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
skippedPoints.push({ plotId: plot.id, label, reason });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// Case ordinal follows the aggregate's canonical case order (first appearance of each caseId).
|
|
73
|
+
const caseOrdinals = new Map();
|
|
74
|
+
for (const summary of aggregate.caseTreatmentContextSummaries) {
|
|
75
|
+
if (!caseOrdinals.has(summary.caseId))
|
|
76
|
+
caseOrdinals.set(summary.caseId, caseOrdinals.size + 1);
|
|
77
|
+
}
|
|
78
|
+
for (const summary of aggregate.caseTreatmentContextSummaries) {
|
|
79
|
+
add(contextSize, caseOrdinals.get(summary.caseId), summary.contextStatus === "available" ? summary.estimatedTokens : null, summary.variantId, `${summary.caseId} ${summary.variantId}`, CONTEXT_WINDOW_SCALING_SKIP_REASONS.contextSize, { caseId: summary.caseId });
|
|
80
|
+
}
|
|
81
|
+
for (const summary of aggregate.budgetTreatmentSummaries) {
|
|
82
|
+
const label = `${summary.variantId} ${formatContextBudgetLabel(summary.contextBudgetTokens)}`;
|
|
83
|
+
const metadata = { contextBudgetTokens: summary.contextBudgetTokens };
|
|
84
|
+
add(successRate, summary.contextBudgetTokens, summary.successRatePercent, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.successRate, metadata);
|
|
85
|
+
add(correctness, summary.contextBudgetTokens, summary.meanCorrectnessScore, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.correctness, metadata);
|
|
86
|
+
}
|
|
87
|
+
const plots = [contextSize, successRate, correctness];
|
|
88
|
+
const warnings = [];
|
|
89
|
+
for (const plot of plots) {
|
|
90
|
+
if (plot.points.length === 0) {
|
|
91
|
+
plot.warnings.push("No comparable data available.");
|
|
92
|
+
warnings.push(`${plot.title}: no comparable data available.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { generatedAt: args.generatedAt, sourceExperimentDir: args.experimentDir, plots, skippedPoints, warnings };
|
|
96
|
+
}
|
package/dist/src/plots/index.js
CHANGED