@tangle-network/agent-eval 0.79.0 → 0.80.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 -19
- package/dist/adapters/http.d.ts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/otel.d.ts +1 -1
- package/dist/analyst/index.d.ts +3 -3
- package/dist/belief-state/index.d.ts +188 -0
- package/dist/belief-state/index.js +486 -0
- package/dist/belief-state/index.js.map +1 -0
- package/dist/calibration-Cpr3WaX3.d.ts +101 -0
- package/dist/campaign/index.d.ts +5 -5
- package/dist/chunk-4DIJWVUT.js +131 -0
- package/dist/chunk-4DIJWVUT.js.map +1 -0
- package/dist/chunk-NPCTHQIO.js +91 -0
- package/dist/chunk-NPCTHQIO.js.map +1 -0
- package/dist/contract/index.d.ts +123 -10
- package/dist/contract/index.js +116 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/governance/index.d.ts +1 -1
- package/dist/hosted/index.d.ts +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/meta-eval/index.d.ts +5 -98
- package/dist/meta-eval/index.js +7 -76
- package/dist/meta-eval/index.js.map +1 -1
- package/dist/off-policy-DiwuKKg7.d.ts +132 -0
- package/dist/openapi.json +1 -1
- package/dist/{outcome-store-D6KWmYvj.d.ts → outcome-store-rnXLEqSn.d.ts} +1 -1
- package/dist/{provenance-CEAJI9rm.d.ts → provenance-jG-Gngg8.d.ts} +2 -2
- package/dist/{registry-BmEuU94S.d.ts → registry-BK0Zee01.d.ts} +1 -1
- package/dist/reporting.d.ts +2 -2
- package/dist/rl.d.ts +6 -136
- package/dist/rl.js +6 -120
- package/dist/rl.js.map +1 -1
- package/dist/{rubric-predictive-validity-CWyWWLBg.d.ts → rubric-predictive-validity-CLPuwiUw.d.ts} +1 -1
- package/dist/{run-improvement-loop-Bgu4C59E.d.ts → run-improvement-loop-BAl_aVOZ.d.ts} +1 -1
- package/dist/{semantic-concept-judge-Du4ZVyef.d.ts → semantic-concept-judge-qXEUV2w7.d.ts} +1 -1
- package/dist/{types-QHG0KnkF.d.ts → types-4mm2msnR.d.ts} +1 -1
- package/docs/research/belief-state-agent-eval-roadmap.md +558 -0
- package/docs/research/research-roadmap.md +1 -0
- package/package.json +7 -2
package/dist/campaign/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { A as AnalyzeTracesOptions, a as AnalyzeTracesInput, b as AnalyzeTracesResult } from '../analyst-t7zZS3TV.js';
|
|
2
|
-
import { S as Scenario, M as MutableSurface, b as DispatchContext, a as JudgeConfig, I as ImprovementDriver, q as ProposeContext, J as JudgeScore, L as LabeledScenarioStore, r as LabeledScenarioWrite, s as LabeledScenarioSampleArgs, t as LabeledScenarioRecord, u as LabelTrust, v as LabeledScenarioSource,
|
|
3
|
-
export { C as CampaignAggregates,
|
|
4
|
-
import { a as RunCampaignOptions, b as RunImprovementLoopOptions, C as CampaignStorage } from '../run-improvement-loop-
|
|
5
|
-
export { d as GepaDriverConstraints, G as GepaDriverOptions, O as OpenAutoPrOptions, e as OpenAutoPrResult, R as RunImprovementLoopResult, h as RunOptimizationOptions, j as RunOptimizationResult, k as countSentenceEdits, l as defaultRenderDiff, m as extractH2Sections, f as fsCampaignStorage, g as gepaDriver, i as inMemoryCampaignStorage, o as openAutoPr, r as runCampaign, c as runImprovementLoop, n as runOptimization, s as surfaceHash } from '../run-improvement-loop-
|
|
6
|
-
export { A as AxisEvidence, a as AxisVerdict, B as BuildEvidenceVectorOptions, k as BuildLoopProvenanceArgs, D as DefaultProductionGateOptions, l as EmitLoopProvenanceArgs, m as EmitLoopProvenanceResult, E as EvidenceVector, b as EvolutionaryDriverOptions, H as HeldOutGateOptions, n as LoopProvenanceBackend, o as LoopProvenanceCandidate, L as LoopProvenanceRecord, O as ObjectiveSource, P as ParetoSignificanceGateOptions, c as PromotionObjective, d as PromotionPolicy, R as RunEvalOptions, e as buildEvidenceVector, q as buildLoopProvenanceRecord, f as composeGate, g as defaultProductionGate, s as emitLoopProvenance, h as evolutionaryDriver, i as heldOutGate, t as loopProvenanceSpans, p as paretoPolicy, j as paretoSignificanceGate, u as provenanceRecordPath, v as provenanceSpansPath, r as runEval, w as surfaceContentHash } from '../provenance-
|
|
2
|
+
import { S as Scenario, M as MutableSurface, b as DispatchContext, a as JudgeConfig, I as ImprovementDriver, q as ProposeContext, J as JudgeScore, L as LabeledScenarioStore, r as LabeledScenarioWrite, s as LabeledScenarioSampleArgs, t as LabeledScenarioRecord, u as LabelTrust, v as LabeledScenarioSource, g as CampaignResult, i as CodeSurface } from '../types-4mm2msnR.js';
|
|
3
|
+
export { C as CampaignAggregates, d as CampaignArtifactWriter, e as CampaignCellResult, f as CampaignCostMeter, w as CampaignTokenUsage, h as CampaignTraceWriter, D as DispatchFn, G as Gate, j as GateContext, c as GateDecision, k as GateResult, l as GenerationCandidate, m as GenerationRecord, x as JudgeAggregate, n as JudgeDimension, o as Mutator, O as OptimizerConfig, P as ParetoParent, y as ProposedCandidate, R as RedactionStatus, z as ScenarioAggregate, p as SessionScript, T as TraceSpan, A as isProposedCandidate, B as labelTrustRank } from '../types-4mm2msnR.js';
|
|
4
|
+
import { a as RunCampaignOptions, b as RunImprovementLoopOptions, C as CampaignStorage } from '../run-improvement-loop-BAl_aVOZ.js';
|
|
5
|
+
export { d as GepaDriverConstraints, G as GepaDriverOptions, O as OpenAutoPrOptions, e as OpenAutoPrResult, R as RunImprovementLoopResult, h as RunOptimizationOptions, j as RunOptimizationResult, k as countSentenceEdits, l as defaultRenderDiff, m as extractH2Sections, f as fsCampaignStorage, g as gepaDriver, i as inMemoryCampaignStorage, o as openAutoPr, r as runCampaign, c as runImprovementLoop, n as runOptimization, s as surfaceHash } from '../run-improvement-loop-BAl_aVOZ.js';
|
|
6
|
+
export { A as AxisEvidence, a as AxisVerdict, B as BuildEvidenceVectorOptions, k as BuildLoopProvenanceArgs, D as DefaultProductionGateOptions, l as EmitLoopProvenanceArgs, m as EmitLoopProvenanceResult, E as EvidenceVector, b as EvolutionaryDriverOptions, H as HeldOutGateOptions, n as LoopProvenanceBackend, o as LoopProvenanceCandidate, L as LoopProvenanceRecord, O as ObjectiveSource, P as ParetoSignificanceGateOptions, c as PromotionObjective, d as PromotionPolicy, R as RunEvalOptions, e as buildEvidenceVector, q as buildLoopProvenanceRecord, f as composeGate, g as defaultProductionGate, s as emitLoopProvenance, h as evolutionaryDriver, i as heldOutGate, t as loopProvenanceSpans, p as paretoPolicy, j as paretoSignificanceGate, u as provenanceRecordPath, v as provenanceSpansPath, r as runEval, w as surfaceContentHash } from '../provenance-jG-Gngg8.js';
|
|
7
7
|
import { L as LlmClientOptions } from '../llm-client-DbjLfz-K.js';
|
|
8
8
|
import { c as TraceAnalystKindSpec } from '../kind-factory-DqV2t1Xk.js';
|
|
9
9
|
import { c as AnalystFinding } from '../types-DRvV0zRo.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidationError
|
|
3
|
+
} from "./chunk-3BFEG2F6.js";
|
|
4
|
+
|
|
5
|
+
// src/rl/off-policy.ts
|
|
6
|
+
function inverseProbabilityWeighting(trajectories, opts = {}) {
|
|
7
|
+
const cap = opts.weightCap ?? Infinity;
|
|
8
|
+
const clip = opts.rewardClip ?? { low: 0, high: 1 };
|
|
9
|
+
if (trajectories.length === 0) {
|
|
10
|
+
return zeroEstimate();
|
|
11
|
+
}
|
|
12
|
+
const weights = [];
|
|
13
|
+
const weightedRewards = [];
|
|
14
|
+
let maxW = 0;
|
|
15
|
+
for (const t of trajectories) {
|
|
16
|
+
if (t.behaviorProb <= 0) {
|
|
17
|
+
throw new ValidationError(
|
|
18
|
+
`inverseProbabilityWeighting: behaviorProb must be > 0 (runId=${t.runId})`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const w = Math.min(cap, t.targetProb / t.behaviorProb);
|
|
22
|
+
const r = clamp(t.reward, clip.low, clip.high);
|
|
23
|
+
weights.push(w);
|
|
24
|
+
weightedRewards.push(w * r);
|
|
25
|
+
if (w > maxW) maxW = w;
|
|
26
|
+
}
|
|
27
|
+
const n = weights.length;
|
|
28
|
+
const value = weightedRewards.reduce((s, x) => s + x, 0) / n;
|
|
29
|
+
const variance = weightedRewards.reduce((s, x) => s + (x - value) ** 2, 0) / Math.max(1, n - 1);
|
|
30
|
+
const sumW = weights.reduce((s, w) => s + w, 0);
|
|
31
|
+
const sumW2 = weights.reduce((s, w) => s + w * w, 0);
|
|
32
|
+
const effN = sumW === 0 ? 0 : sumW * sumW / sumW2;
|
|
33
|
+
return {
|
|
34
|
+
value,
|
|
35
|
+
standardError: Math.sqrt(variance / n),
|
|
36
|
+
effectiveSampleSize: effN,
|
|
37
|
+
n,
|
|
38
|
+
maxImportanceWeight: maxW
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function selfNormalizedImportanceWeighting(trajectories, opts = {}) {
|
|
42
|
+
const cap = opts.weightCap ?? Infinity;
|
|
43
|
+
const clip = opts.rewardClip ?? { low: 0, high: 1 };
|
|
44
|
+
if (trajectories.length === 0) return zeroEstimate();
|
|
45
|
+
const weights = [];
|
|
46
|
+
const rewards = [];
|
|
47
|
+
let maxW = 0;
|
|
48
|
+
for (const t of trajectories) {
|
|
49
|
+
if (t.behaviorProb <= 0) {
|
|
50
|
+
throw new ValidationError(
|
|
51
|
+
`selfNormalizedImportanceWeighting: behaviorProb must be > 0 (runId=${t.runId})`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const w = Math.min(cap, t.targetProb / t.behaviorProb);
|
|
55
|
+
weights.push(w);
|
|
56
|
+
rewards.push(clamp(t.reward, clip.low, clip.high));
|
|
57
|
+
if (w > maxW) maxW = w;
|
|
58
|
+
}
|
|
59
|
+
const sumW = weights.reduce((s, w) => s + w, 0);
|
|
60
|
+
const sumWR = weights.reduce((s, w, i) => s + w * rewards[i], 0);
|
|
61
|
+
const value = sumW === 0 ? 0 : sumWR / sumW;
|
|
62
|
+
const sumW2 = weights.reduce((s, w) => s + w * w, 0);
|
|
63
|
+
const effN = sumW === 0 ? 0 : sumW * sumW / sumW2;
|
|
64
|
+
const phi = weights.map((w, i) => w * (rewards[i] - value));
|
|
65
|
+
const variance = phi.reduce((s, x) => s + x * x, 0) / Math.max(1, sumW * sumW);
|
|
66
|
+
return {
|
|
67
|
+
value,
|
|
68
|
+
standardError: Math.sqrt(variance),
|
|
69
|
+
effectiveSampleSize: effN,
|
|
70
|
+
n: trajectories.length,
|
|
71
|
+
maxImportanceWeight: maxW
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function doublyRobust(trajectories, opts = {}) {
|
|
75
|
+
const cap = opts.weightCap ?? Infinity;
|
|
76
|
+
const clip = opts.rewardClip ?? { low: 0, high: 1 };
|
|
77
|
+
if (trajectories.length === 0) return zeroEstimate();
|
|
78
|
+
const contributions = [];
|
|
79
|
+
let maxW = 0;
|
|
80
|
+
let sumW = 0;
|
|
81
|
+
let sumW2 = 0;
|
|
82
|
+
for (const t of trajectories) {
|
|
83
|
+
if (t.behaviorProb <= 0) {
|
|
84
|
+
throw new ValidationError(`doublyRobust: behaviorProb must be > 0 (runId=${t.runId})`);
|
|
85
|
+
}
|
|
86
|
+
const w = Math.min(cap, t.targetProb / t.behaviorProb);
|
|
87
|
+
const r = clamp(t.reward, clip.low, clip.high);
|
|
88
|
+
const q = typeof t.qHat === "number" && Number.isFinite(t.qHat) ? clamp(t.qHat, clip.low, clip.high) : null;
|
|
89
|
+
if (q === null) {
|
|
90
|
+
contributions.push(w * r);
|
|
91
|
+
} else {
|
|
92
|
+
contributions.push(q + w * (r - q));
|
|
93
|
+
}
|
|
94
|
+
if (w > maxW) maxW = w;
|
|
95
|
+
sumW += w;
|
|
96
|
+
sumW2 += w * w;
|
|
97
|
+
}
|
|
98
|
+
const n = contributions.length;
|
|
99
|
+
const value = contributions.reduce((s, x) => s + x, 0) / n;
|
|
100
|
+
const variance = contributions.reduce((s, x) => s + (x - value) ** 2, 0) / Math.max(1, n - 1);
|
|
101
|
+
const effN = sumW === 0 ? 0 : sumW * sumW / sumW2;
|
|
102
|
+
return {
|
|
103
|
+
value,
|
|
104
|
+
standardError: Math.sqrt(variance / n),
|
|
105
|
+
effectiveSampleSize: effN,
|
|
106
|
+
n,
|
|
107
|
+
maxImportanceWeight: maxW
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function offPolicyEstimateAll(trajectories, opts = {}) {
|
|
111
|
+
return {
|
|
112
|
+
ips: inverseProbabilityWeighting(trajectories, opts),
|
|
113
|
+
snips: selfNormalizedImportanceWeighting(trajectories, opts),
|
|
114
|
+
dr: doublyRobust(trajectories, opts)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function zeroEstimate() {
|
|
118
|
+
return { value: 0, standardError: 0, effectiveSampleSize: 0, n: 0, maxImportanceWeight: 0 };
|
|
119
|
+
}
|
|
120
|
+
function clamp(x, lo, hi) {
|
|
121
|
+
if (!Number.isFinite(x)) return lo;
|
|
122
|
+
return Math.max(lo, Math.min(hi, x));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
inverseProbabilityWeighting,
|
|
127
|
+
selfNormalizedImportanceWeighting,
|
|
128
|
+
doublyRobust,
|
|
129
|
+
offPolicyEstimateAll
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=chunk-4DIJWVUT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rl/off-policy.ts"],"sourcesContent":["/**\n * Off-policy evaluation primitives.\n *\n * Standard inverse-probability-weighted (IPS), self-normalized\n * importance-weighted (SNIPS), and doubly-robust (DR) estimators for the\n * value of a *target* policy given trajectories collected under a\n * *behavior* policy. This is the canonical RL eval task: \"we have last\n * week's runs, we changed the policy — how would the new one do without\n * re-running?\"\n *\n * The math here is textbook (Dudík, Langford, Li 2011 for DR; Swaminathan\n * & Joachims 2015 for SNIPS) but the *application* to LLM-agent\n * evaluation needs care:\n *\n * - The \"policy\" is the (prompt, tool config, model snapshot) triple.\n * Two policies have the same probability over an action *iff* their\n * LLM call would emit the same token with the same probability —\n * which is generally unknowable without the model log-probs.\n * - For LLM agents, propensity scores must be supplied by the caller\n * (logged in the trace, recovered from token log-probs, or estimated\n * via a learned propensity model). We do NOT estimate propensity here.\n * - Doubly-robust requires a Q-function (model-based reward predictor).\n * We accept any callable; consumers pass either a tabular average,\n * a regression fit, or a learned reward model.\n *\n * Bias / variance tradeoffs:\n * - IPS: unbiased; high variance for small overlap, infinite variance\n * when target has support outside behavior.\n * - SNIPS: lower variance, slight bias; usually preferred in practice.\n * - DR: doubly-robust — unbiased if either propensity OR Q-function is\n * correct. Lowest practical variance when Q is decent. Use this.\n *\n * Caveat the panel will land: on the LLM-agent setting, propensity scores\n * recovered from token log-probs are noisy, the action space is enormous,\n * and overlap is often poor. These estimators are useful but not magic;\n * complement with `replayCampaign` (exact replay where the request hashes\n * match) for high-confidence answers and OPE for the gap.\n */\n\nimport { ValidationError } from '../errors'\n\nexport interface OffPolicyTrajectory {\n /** Stable id, for traceability through the dataset. */\n runId: string\n /** Reward observed under the behavior policy (the realized outcome). */\n reward: number\n /**\n * Behavior-policy probability of the action that was taken. For LLM\n * agents this is typically `exp(sum(token_log_probs))` over the chosen\n * trajectory. Must be in (0, 1].\n */\n behaviorProb: number\n /**\n * Target-policy probability of the same action. For replay-style\n * counterfactual evaluation this is what the *new* policy would have\n * assigned to the *old* trajectory. Must be in [0, 1].\n */\n targetProb: number\n /**\n * Optional model-based reward prediction at the same context. Used by\n * `doublyRobust`. Set to `null` for IPS-only evaluation.\n */\n qHat?: number | null\n}\n\nexport interface OffPolicyEstimate {\n /** Estimated value of the target policy. */\n value: number\n /** Standard error of the estimate. */\n standardError: number\n /** Effective sample size (Kong 1992). Lower = more reliance on a few high-weight samples. */\n effectiveSampleSize: number\n /** Number of trajectories used. */\n n: number\n /**\n * Diagnostic: maximum importance weight observed. Large values (>>10x\n * mean) are a red flag — variance is dominated by a few outliers.\n */\n maxImportanceWeight: number\n}\n\nexport interface OffPolicyOptions {\n /**\n * Cap importance weights at this value (Ionides 2008 truncated IS) to\n * trade unbiasedness for variance reduction. Default `Infinity` (no cap).\n * Set e.g. `10` for stable estimates when the policies are close.\n */\n weightCap?: number\n /** Reward clipping range. Default `[0, 1]`. */\n rewardClip?: { low: number; high: number }\n}\n\n/**\n * Inverse Probability Weighting (Horvitz-Thompson). Unbiased estimator\n * of E[reward under target policy]. Variance scales with the spread of\n * target/behavior ratios.\n */\nexport function inverseProbabilityWeighting(\n trajectories: OffPolicyTrajectory[],\n opts: OffPolicyOptions = {},\n): OffPolicyEstimate {\n const cap = opts.weightCap ?? Infinity\n const clip = opts.rewardClip ?? { low: 0, high: 1 }\n\n if (trajectories.length === 0) {\n return zeroEstimate()\n }\n\n const weights: number[] = []\n const weightedRewards: number[] = []\n let maxW = 0\n for (const t of trajectories) {\n if (t.behaviorProb <= 0) {\n throw new ValidationError(\n `inverseProbabilityWeighting: behaviorProb must be > 0 (runId=${t.runId})`,\n )\n }\n const w = Math.min(cap, t.targetProb / t.behaviorProb)\n const r = clamp(t.reward, clip.low, clip.high)\n weights.push(w)\n weightedRewards.push(w * r)\n if (w > maxW) maxW = w\n }\n const n = weights.length\n const value = weightedRewards.reduce((s, x) => s + x, 0) / n\n const variance = weightedRewards.reduce((s, x) => s + (x - value) ** 2, 0) / Math.max(1, n - 1)\n const sumW = weights.reduce((s, w) => s + w, 0)\n const sumW2 = weights.reduce((s, w) => s + w * w, 0)\n const effN = sumW === 0 ? 0 : (sumW * sumW) / sumW2\n\n return {\n value,\n standardError: Math.sqrt(variance / n),\n effectiveSampleSize: effN,\n n,\n maxImportanceWeight: maxW,\n }\n}\n\n/**\n * Self-Normalized Importance Sampling. Lower variance than vanilla IPS at\n * the cost of small bias (vanishing as N grows). The right default for\n * LLM-agent evaluation where overlap is often poor.\n */\nexport function selfNormalizedImportanceWeighting(\n trajectories: OffPolicyTrajectory[],\n opts: OffPolicyOptions = {},\n): OffPolicyEstimate {\n const cap = opts.weightCap ?? Infinity\n const clip = opts.rewardClip ?? { low: 0, high: 1 }\n if (trajectories.length === 0) return zeroEstimate()\n\n const weights: number[] = []\n const rewards: number[] = []\n let maxW = 0\n for (const t of trajectories) {\n if (t.behaviorProb <= 0) {\n throw new ValidationError(\n `selfNormalizedImportanceWeighting: behaviorProb must be > 0 (runId=${t.runId})`,\n )\n }\n const w = Math.min(cap, t.targetProb / t.behaviorProb)\n weights.push(w)\n rewards.push(clamp(t.reward, clip.low, clip.high))\n if (w > maxW) maxW = w\n }\n const sumW = weights.reduce((s, w) => s + w, 0)\n const sumWR = weights.reduce((s, w, i) => s + w * rewards[i]!, 0)\n const value = sumW === 0 ? 0 : sumWR / sumW\n const sumW2 = weights.reduce((s, w) => s + w * w, 0)\n const effN = sumW === 0 ? 0 : (sumW * sumW) / sumW2\n // Influence-function-based SE for SNIPS (Owen 2013, Ch. 9).\n const phi = weights.map((w, i) => w * (rewards[i]! - value))\n const variance = phi.reduce((s, x) => s + x * x, 0) / Math.max(1, sumW * sumW)\n return {\n value,\n standardError: Math.sqrt(variance),\n effectiveSampleSize: effN,\n n: trajectories.length,\n maxImportanceWeight: maxW,\n }\n}\n\n/**\n * Doubly-robust off-policy estimator (Dudík, Langford, Li 2011).\n *\n * V_DR = (1/N) * sum_i [ q_hat_i + (target_prob_i / behavior_prob_i) * (r_i - q_hat_i) ]\n *\n * Unbiased if EITHER:\n * - the importance ratios are correct (IPS-style validity), OR\n * - the Q-hat function is correct (model-based validity).\n *\n * In practice both are imperfect, but the residual bias is the *product*\n * of both errors — much smaller than either alone. This is why DR is the\n * default in production OPE pipelines.\n *\n * Requires `qHat` on every trajectory. If any are `null`, the estimator\n * falls back to SNIPS for those entries (loud-fallback behavior; the\n * report's `n` reflects the full set but `effectiveSampleSize` accounts\n * for the lost variance reduction).\n */\nexport function doublyRobust(\n trajectories: OffPolicyTrajectory[],\n opts: OffPolicyOptions = {},\n): OffPolicyEstimate {\n const cap = opts.weightCap ?? Infinity\n const clip = opts.rewardClip ?? { low: 0, high: 1 }\n if (trajectories.length === 0) return zeroEstimate()\n\n const contributions: number[] = []\n let maxW = 0\n let sumW = 0\n let sumW2 = 0\n for (const t of trajectories) {\n if (t.behaviorProb <= 0) {\n throw new ValidationError(`doublyRobust: behaviorProb must be > 0 (runId=${t.runId})`)\n }\n const w = Math.min(cap, t.targetProb / t.behaviorProb)\n const r = clamp(t.reward, clip.low, clip.high)\n const q =\n typeof t.qHat === 'number' && Number.isFinite(t.qHat)\n ? clamp(t.qHat, clip.low, clip.high)\n : null\n if (q === null) {\n contributions.push(w * r) // fallback: IPS for this entry\n } else {\n contributions.push(q + w * (r - q))\n }\n if (w > maxW) maxW = w\n sumW += w\n sumW2 += w * w\n }\n const n = contributions.length\n const value = contributions.reduce((s, x) => s + x, 0) / n\n const variance = contributions.reduce((s, x) => s + (x - value) ** 2, 0) / Math.max(1, n - 1)\n const effN = sumW === 0 ? 0 : (sumW * sumW) / sumW2\n return {\n value,\n standardError: Math.sqrt(variance / n),\n effectiveSampleSize: effN,\n n,\n maxImportanceWeight: maxW,\n }\n}\n\n/**\n * Convenience: run all three estimators and return them side-by-side.\n * The recommended diagnostic — agreement across estimators is a much\n * stronger signal than any single one.\n */\nexport function offPolicyEstimateAll(\n trajectories: OffPolicyTrajectory[],\n opts: OffPolicyOptions = {},\n): { ips: OffPolicyEstimate; snips: OffPolicyEstimate; dr: OffPolicyEstimate } {\n return {\n ips: inverseProbabilityWeighting(trajectories, opts),\n snips: selfNormalizedImportanceWeighting(trajectories, opts),\n dr: doublyRobust(trajectories, opts),\n }\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\nfunction zeroEstimate(): OffPolicyEstimate {\n return { value: 0, standardError: 0, effectiveSampleSize: 0, n: 0, maxImportanceWeight: 0 }\n}\n\nfunction clamp(x: number, lo: number, hi: number): number {\n if (!Number.isFinite(x)) return lo\n return Math.max(lo, Math.min(hi, x))\n}\n"],"mappings":";;;;;AAiGO,SAAS,4BACd,cACA,OAAyB,CAAC,GACP;AACnB,QAAM,MAAM,KAAK,aAAa;AAC9B,QAAM,OAAO,KAAK,cAAc,EAAE,KAAK,GAAG,MAAM,EAAE;AAElD,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO,aAAa;AAAA,EACtB;AAEA,QAAM,UAAoB,CAAC;AAC3B,QAAM,kBAA4B,CAAC;AACnC,MAAI,OAAO;AACX,aAAW,KAAK,cAAc;AAC5B,QAAI,EAAE,gBAAgB,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,gEAAgE,EAAE,KAAK;AAAA,MACzE;AAAA,IACF;AACA,UAAM,IAAI,KAAK,IAAI,KAAK,EAAE,aAAa,EAAE,YAAY;AACrD,UAAM,IAAI,MAAM,EAAE,QAAQ,KAAK,KAAK,KAAK,IAAI;AAC7C,YAAQ,KAAK,CAAC;AACd,oBAAgB,KAAK,IAAI,CAAC;AAC1B,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AACA,QAAM,IAAI,QAAQ;AAClB,QAAM,QAAQ,gBAAgB,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AAC3D,QAAM,WAAW,gBAAgB,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;AAC9F,QAAM,OAAO,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC9C,QAAM,QAAQ,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,GAAG,CAAC;AACnD,QAAM,OAAO,SAAS,IAAI,IAAK,OAAO,OAAQ;AAE9C,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK,KAAK,WAAW,CAAC;AAAA,IACrC,qBAAqB;AAAA,IACrB;AAAA,IACA,qBAAqB;AAAA,EACvB;AACF;AAOO,SAAS,kCACd,cACA,OAAyB,CAAC,GACP;AACnB,QAAM,MAAM,KAAK,aAAa;AAC9B,QAAM,OAAO,KAAK,cAAc,EAAE,KAAK,GAAG,MAAM,EAAE;AAClD,MAAI,aAAa,WAAW,EAAG,QAAO,aAAa;AAEnD,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,MAAI,OAAO;AACX,aAAW,KAAK,cAAc;AAC5B,QAAI,EAAE,gBAAgB,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,sEAAsE,EAAE,KAAK;AAAA,MAC/E;AAAA,IACF;AACA,UAAM,IAAI,KAAK,IAAI,KAAK,EAAE,aAAa,EAAE,YAAY;AACrD,YAAQ,KAAK,CAAC;AACd,YAAQ,KAAK,MAAM,EAAE,QAAQ,KAAK,KAAK,KAAK,IAAI,CAAC;AACjD,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AACA,QAAM,OAAO,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC9C,QAAM,QAAQ,QAAQ,OAAO,CAAC,GAAG,GAAG,MAAM,IAAI,IAAI,QAAQ,CAAC,GAAI,CAAC;AAChE,QAAM,QAAQ,SAAS,IAAI,IAAI,QAAQ;AACvC,QAAM,QAAQ,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,GAAG,CAAC;AACnD,QAAM,OAAO,SAAS,IAAI,IAAK,OAAO,OAAQ;AAE9C,QAAM,MAAM,QAAQ,IAAI,CAAC,GAAG,MAAM,KAAK,QAAQ,CAAC,IAAK,MAAM;AAC3D,QAAM,WAAW,IAAI,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,OAAO,IAAI;AAC7E,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK,KAAK,QAAQ;AAAA,IACjC,qBAAqB;AAAA,IACrB,GAAG,aAAa;AAAA,IAChB,qBAAqB;AAAA,EACvB;AACF;AAoBO,SAAS,aACd,cACA,OAAyB,CAAC,GACP;AACnB,QAAM,MAAM,KAAK,aAAa;AAC9B,QAAM,OAAO,KAAK,cAAc,EAAE,KAAK,GAAG,MAAM,EAAE;AAClD,MAAI,aAAa,WAAW,EAAG,QAAO,aAAa;AAEnD,QAAM,gBAA0B,CAAC;AACjC,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,QAAQ;AACZ,aAAW,KAAK,cAAc;AAC5B,QAAI,EAAE,gBAAgB,GAAG;AACvB,YAAM,IAAI,gBAAgB,iDAAiD,EAAE,KAAK,GAAG;AAAA,IACvF;AACA,UAAM,IAAI,KAAK,IAAI,KAAK,EAAE,aAAa,EAAE,YAAY;AACrD,UAAM,IAAI,MAAM,EAAE,QAAQ,KAAK,KAAK,KAAK,IAAI;AAC7C,UAAM,IACJ,OAAO,EAAE,SAAS,YAAY,OAAO,SAAS,EAAE,IAAI,IAChD,MAAM,EAAE,MAAM,KAAK,KAAK,KAAK,IAAI,IACjC;AACN,QAAI,MAAM,MAAM;AACd,oBAAc,KAAK,IAAI,CAAC;AAAA,IAC1B,OAAO;AACL,oBAAc,KAAK,IAAI,KAAK,IAAI,EAAE;AAAA,IACpC;AACA,QAAI,IAAI,KAAM,QAAO;AACrB,YAAQ;AACR,aAAS,IAAI;AAAA,EACf;AACA,QAAM,IAAI,cAAc;AACxB,QAAM,QAAQ,cAAc,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AACzD,QAAM,WAAW,cAAc,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;AAC5F,QAAM,OAAO,SAAS,IAAI,IAAK,OAAO,OAAQ;AAC9C,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK,KAAK,WAAW,CAAC;AAAA,IACrC,qBAAqB;AAAA,IACrB;AAAA,IACA,qBAAqB;AAAA,EACvB;AACF;AAOO,SAAS,qBACd,cACA,OAAyB,CAAC,GACmD;AAC7E,SAAO;AAAA,IACL,KAAK,4BAA4B,cAAc,IAAI;AAAA,IACnD,OAAO,kCAAkC,cAAc,IAAI;AAAA,IAC3D,IAAI,aAAa,cAAc,IAAI;AAAA,EACrC;AACF;AAIA,SAAS,eAAkC;AACzC,SAAO,EAAE,OAAO,GAAG,eAAe,GAAG,qBAAqB,GAAG,GAAG,GAAG,qBAAqB,EAAE;AAC5F;AAEA,SAAS,MAAM,GAAW,IAAY,IAAoB;AACxD,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,SAAO,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC;AACrC;","names":[]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/meta-eval/calibration.ts
|
|
2
|
+
async function calibrationCurve(traceStore, outcomeStore, evalMetric, outcomeMetric, options = {}) {
|
|
3
|
+
const runs = await traceStore.listRuns();
|
|
4
|
+
const outcomes = await outcomeStore.list();
|
|
5
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const o of outcomes) {
|
|
7
|
+
const arr = byRun.get(o.runId) ?? [];
|
|
8
|
+
arr.push(o);
|
|
9
|
+
byRun.set(o.runId, arr);
|
|
10
|
+
}
|
|
11
|
+
const extract = evalMetric.extract ?? defaultExtract(evalMetric.id);
|
|
12
|
+
const pairs = [];
|
|
13
|
+
for (const run of runs) {
|
|
14
|
+
const os = byRun.get(run.runId);
|
|
15
|
+
if (!os?.length) continue;
|
|
16
|
+
const x = await extract(run, traceStore);
|
|
17
|
+
if (x === null || !Number.isFinite(x)) continue;
|
|
18
|
+
const latest = [...os].sort((a, b) => b.capturedAt - a.capturedAt)[0];
|
|
19
|
+
const y = latest.metrics[outcomeMetric];
|
|
20
|
+
if (typeof y !== "number" || !Number.isFinite(y)) continue;
|
|
21
|
+
pairs.push({ x, y });
|
|
22
|
+
}
|
|
23
|
+
if (pairs.length < 2) return null;
|
|
24
|
+
return calibrationFromPairs(
|
|
25
|
+
pairs.map((p) => ({ evalScore: p.x, outcome: p.y })),
|
|
26
|
+
evalMetric.id,
|
|
27
|
+
outcomeMetric,
|
|
28
|
+
options
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function calibrationFromPairs(inputPairs, evalMetric, outcomeMetric, options = {}) {
|
|
32
|
+
const pairs = inputPairs.filter(
|
|
33
|
+
(pair) => Number.isFinite(pair.evalScore) && Number.isFinite(pair.outcome)
|
|
34
|
+
);
|
|
35
|
+
if (pairs.length < 2) return null;
|
|
36
|
+
const numBins = options.bins ?? 10;
|
|
37
|
+
const binning = options.binning ?? "equal-width";
|
|
38
|
+
const xs = pairs.map((p) => p.evalScore);
|
|
39
|
+
const lo = options.range?.lo ?? Math.min(...xs);
|
|
40
|
+
const hi = options.range?.hi ?? Math.max(...xs);
|
|
41
|
+
const bins = [];
|
|
42
|
+
if (binning === "equal-frequency") {
|
|
43
|
+
const sorted = [...pairs].sort((a, b) => a.evalScore - b.evalScore);
|
|
44
|
+
const perBin = Math.max(1, Math.floor(sorted.length / numBins));
|
|
45
|
+
for (let i = 0; i < sorted.length; i += perBin) {
|
|
46
|
+
const chunk = sorted.slice(i, i + perBin);
|
|
47
|
+
if (chunk.length === 0) continue;
|
|
48
|
+
bins.push(toBin(chunk));
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
const width = (hi - lo) / numBins;
|
|
52
|
+
if (width === 0) return null;
|
|
53
|
+
for (let i = 0; i < numBins; i++) {
|
|
54
|
+
const binLo = lo + i * width;
|
|
55
|
+
const binHi = i === numBins - 1 ? hi + 1e-9 : lo + (i + 1) * width;
|
|
56
|
+
const chunk = pairs.filter((p) => p.evalScore >= binLo && p.evalScore < binHi);
|
|
57
|
+
if (chunk.length === 0) continue;
|
|
58
|
+
bins.push(toBin(chunk, binLo, binHi));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const total = bins.reduce((a, b) => a + b.n, 0);
|
|
62
|
+
const ece = bins.reduce((a, b) => a + b.n / total * b.gap, 0);
|
|
63
|
+
const maxGap = bins.reduce((a, b) => Math.max(a, b.gap), 0);
|
|
64
|
+
return { evalMetric, outcomeMetric, n: pairs.length, bins, ece, maxGap };
|
|
65
|
+
}
|
|
66
|
+
function toBin(chunk, lower, upper) {
|
|
67
|
+
const xs = chunk.map((c) => c.evalScore);
|
|
68
|
+
const ys = chunk.map((c) => c.outcome);
|
|
69
|
+
const evalMean = mean(xs);
|
|
70
|
+
const outcomeMean = mean(ys);
|
|
71
|
+
return {
|
|
72
|
+
lower: lower ?? Math.min(...xs),
|
|
73
|
+
upper: upper ?? Math.max(...xs),
|
|
74
|
+
n: chunk.length,
|
|
75
|
+
evalMean,
|
|
76
|
+
outcomeMean,
|
|
77
|
+
gap: Math.abs(outcomeMean - evalMean)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function mean(xs) {
|
|
81
|
+
return xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
82
|
+
}
|
|
83
|
+
function defaultExtract(metric) {
|
|
84
|
+
return async (run) => run.outcome?.score ?? (metric === "pass" ? run.outcome?.pass === true ? 1 : 0 : null);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
calibrationCurve,
|
|
89
|
+
calibrationFromPairs
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=chunk-NPCTHQIO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/meta-eval/calibration.ts"],"sourcesContent":["/**\n * Calibration curve — binned \"if eval says X, what does reality show?\"\n *\n * Companion to correlationStudy. Raw correlation is a single number;\n * the calibration curve shows *where* the eval is well-calibrated vs\n * overconfident / underconfident. Buckets the eval metric, computes\n * mean outcome per bucket, reports expected-calibration-error (ECE).\n */\n\nimport type { Run } from '../trace/schema'\nimport type { TraceStore } from '../trace/store'\nimport type { EvalMetricSpec } from './correlation-study'\nimport type { DeploymentOutcome, OutcomeStore } from './outcome-store'\n\nexport interface CalibrationBin {\n lower: number\n upper: number\n n: number\n evalMean: number\n outcomeMean: number\n /** |outcomeMean − evalMean|; contributes to ECE weighted by n/total. */\n gap: number\n}\n\nexport interface CalibrationReport {\n evalMetric: string\n outcomeMetric: string\n n: number\n bins: CalibrationBin[]\n /** Expected Calibration Error — Σ (n_i/N) × |outcomeMean_i − evalMean_i|. */\n ece: number\n /** Max bin gap — upper bound on miscalibration. */\n maxGap: number\n}\n\nexport interface CalibrationOptions {\n bins?: number\n /** Equal-width (fixed bin edges) or equal-frequency (quantile bins). */\n binning?: 'equal-width' | 'equal-frequency'\n /** Clip eval values to [lo, hi] before binning. */\n range?: { lo: number; hi: number }\n}\n\nexport interface CalibrationPair {\n evalScore: number\n outcome: number\n}\n\nexport async function calibrationCurve(\n traceStore: TraceStore,\n outcomeStore: OutcomeStore,\n evalMetric: EvalMetricSpec,\n outcomeMetric: string,\n options: CalibrationOptions = {},\n): Promise<CalibrationReport | null> {\n const runs = await traceStore.listRuns()\n const outcomes = await outcomeStore.list()\n const byRun = new Map<string, DeploymentOutcome[]>()\n for (const o of outcomes) {\n const arr = byRun.get(o.runId) ?? []\n arr.push(o)\n byRun.set(o.runId, arr)\n }\n\n const extract = evalMetric.extract ?? defaultExtract(evalMetric.id)\n const pairs: Array<{ x: number; y: number }> = []\n for (const run of runs) {\n const os = byRun.get(run.runId)\n if (!os?.length) continue\n const x = await extract(run, traceStore)\n if (x === null || !Number.isFinite(x)) continue\n const latest = [...os].sort((a, b) => b.capturedAt - a.capturedAt)[0]!\n const y = latest.metrics[outcomeMetric]\n if (typeof y !== 'number' || !Number.isFinite(y)) continue\n pairs.push({ x, y })\n }\n if (pairs.length < 2) return null\n\n return calibrationFromPairs(\n pairs.map((p) => ({ evalScore: p.x, outcome: p.y })),\n evalMetric.id,\n outcomeMetric,\n options,\n )\n}\n\nexport function calibrationFromPairs(\n inputPairs: CalibrationPair[],\n evalMetric: string,\n outcomeMetric: string,\n options: CalibrationOptions = {},\n): CalibrationReport | null {\n const pairs = inputPairs.filter(\n (pair) => Number.isFinite(pair.evalScore) && Number.isFinite(pair.outcome),\n )\n if (pairs.length < 2) return null\n\n const numBins = options.bins ?? 10\n const binning = options.binning ?? 'equal-width'\n const xs = pairs.map((p) => p.evalScore)\n const lo = options.range?.lo ?? Math.min(...xs)\n const hi = options.range?.hi ?? Math.max(...xs)\n\n const bins: CalibrationBin[] = []\n if (binning === 'equal-frequency') {\n const sorted = [...pairs].sort((a, b) => a.evalScore - b.evalScore)\n const perBin = Math.max(1, Math.floor(sorted.length / numBins))\n for (let i = 0; i < sorted.length; i += perBin) {\n const chunk = sorted.slice(i, i + perBin)\n if (chunk.length === 0) continue\n bins.push(toBin(chunk))\n }\n } else {\n const width = (hi - lo) / numBins\n if (width === 0) return null\n for (let i = 0; i < numBins; i++) {\n const binLo = lo + i * width\n const binHi = i === numBins - 1 ? hi + 1e-9 : lo + (i + 1) * width\n const chunk = pairs.filter((p) => p.evalScore >= binLo && p.evalScore < binHi)\n if (chunk.length === 0) continue\n bins.push(toBin(chunk, binLo, binHi))\n }\n }\n\n const total = bins.reduce((a, b) => a + b.n, 0)\n const ece = bins.reduce((a, b) => a + (b.n / total) * b.gap, 0)\n const maxGap = bins.reduce((a, b) => Math.max(a, b.gap), 0)\n\n return { evalMetric, outcomeMetric, n: pairs.length, bins, ece, maxGap }\n}\n\nfunction toBin(chunk: CalibrationPair[], lower?: number, upper?: number): CalibrationBin {\n const xs = chunk.map((c) => c.evalScore)\n const ys = chunk.map((c) => c.outcome)\n const evalMean = mean(xs)\n const outcomeMean = mean(ys)\n return {\n lower: lower ?? Math.min(...xs),\n upper: upper ?? Math.max(...xs),\n n: chunk.length,\n evalMean,\n outcomeMean,\n gap: Math.abs(outcomeMean - evalMean),\n }\n}\n\nfunction mean(xs: number[]): number {\n return xs.reduce((a, b) => a + b, 0) / xs.length\n}\n\nfunction defaultExtract(metric: string): (run: Run, store: TraceStore) => Promise<number | null> {\n return async (run) =>\n run.outcome?.score ?? (metric === 'pass' ? (run.outcome?.pass === true ? 1 : 0) : null)\n}\n"],"mappings":"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","names":[]}
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { S as Scenario, M as MutableSurface, b as DispatchContext, a as JudgeConfig, I as ImprovementDriver, G as Gate } from '../types-
|
|
2
|
-
export { C as CampaignAggregates,
|
|
3
|
-
import { L as LoopProvenanceRecord } from '../provenance-
|
|
4
|
-
export { A as AxisEvidence, a as AxisVerdict, B as BuildEvidenceVectorOptions, D as DefaultProductionGateOptions, E as EvidenceVector, b as EvolutionaryDriverOptions, H as HeldOutGateOptions, O as ObjectiveSource, P as ParetoSignificanceGateOptions, c as PromotionObjective, d as PromotionPolicy, R as RunEvalOptions, e as buildEvidenceVector, f as composeGate, g as defaultProductionGate, h as evolutionaryDriver, i as heldOutGate, p as paretoPolicy, j as paretoSignificanceGate, r as runEval } from '../provenance-
|
|
5
|
-
import { C as CampaignStorage, R as RunImprovementLoopResult } from '../run-improvement-loop-
|
|
6
|
-
export { G as GepaDriverOptions, a as RunCampaignOptions, b as RunImprovementLoopOptions, f as fsCampaignStorage, g as gepaDriver, i as inMemoryCampaignStorage, r as runCampaign, c as runImprovementLoop } from '../run-improvement-loop-
|
|
7
|
-
export { D as DeploymentOutcome, F as FileSystemOutcomeStore,
|
|
8
|
-
import { HostedTenant, TraceSpanEvent } from '../hosted/index.js';
|
|
1
|
+
import { S as Scenario, M as MutableSurface, b as DispatchContext, a as JudgeConfig, I as ImprovementDriver, G as Gate, c as GateDecision } from '../types-4mm2msnR.js';
|
|
2
|
+
export { C as CampaignAggregates, d as CampaignArtifactWriter, e as CampaignCellResult, f as CampaignCostMeter, g as CampaignResult, h as CampaignTraceWriter, i as CodeSurface, D as Dispatch, j as GateContext, k as GateResult, l as GenerationCandidate, m as GenerationRecord, n as JudgeDimension, J as JudgeScore, o as Mutator, O as OptimizerConfig, p as SessionScript } from '../types-4mm2msnR.js';
|
|
3
|
+
import { L as LoopProvenanceRecord } from '../provenance-jG-Gngg8.js';
|
|
4
|
+
export { A as AxisEvidence, a as AxisVerdict, B as BuildEvidenceVectorOptions, D as DefaultProductionGateOptions, E as EvidenceVector, b as EvolutionaryDriverOptions, H as HeldOutGateOptions, O as ObjectiveSource, P as ParetoSignificanceGateOptions, c as PromotionObjective, d as PromotionPolicy, R as RunEvalOptions, e as buildEvidenceVector, f as composeGate, g as defaultProductionGate, h as evolutionaryDriver, i as heldOutGate, p as paretoPolicy, j as paretoSignificanceGate, r as runEval } from '../provenance-jG-Gngg8.js';
|
|
5
|
+
import { C as CampaignStorage, R as RunImprovementLoopResult } from '../run-improvement-loop-BAl_aVOZ.js';
|
|
6
|
+
export { G as GepaDriverOptions, a as RunCampaignOptions, b as RunImprovementLoopOptions, f as fsCampaignStorage, g as gepaDriver, i as inMemoryCampaignStorage, r as runCampaign, c as runImprovementLoop } from '../run-improvement-loop-BAl_aVOZ.js';
|
|
7
|
+
export { D as DeploymentOutcome, F as FileSystemOutcomeStore, a as FileSystemOutcomeStoreOptions, I as InMemoryOutcomeStore, b as OutcomeStore } from '../outcome-store-rnXLEqSn.js';
|
|
8
|
+
import { HostedTenant, EvalRunCellScore, EvalRunGenerationSnapshot, EvalRunEvent, TraceSpanEvent } from '../hosted/index.js';
|
|
9
9
|
import { R as RunRecord, b as RunSplitTag } from '../run-record-sItO5ftF.js';
|
|
10
10
|
import { I as InsightReport } from '../insight-report-dlpEzQDi.js';
|
|
11
11
|
export { F as FailureClusterInsight, a as InterRaterInsight, J as JudgeInsight, L as LiftInsight, O as OutcomeCorrelationInsight, R as Recommendation, b as ReleaseSummary, S as ScalarDistribution } from '../insight-report-dlpEzQDi.js';
|
|
12
|
-
import {
|
|
12
|
+
import { a as AnalystRegistry } from '../registry-BK0Zee01.js';
|
|
13
13
|
import { a as DatasetScenario } from '../dataset-B2kL-fSM.js';
|
|
14
14
|
import '../red-team-DW9Ca_tj.js';
|
|
15
15
|
import '../store-CKUAgsJz.js';
|
|
@@ -351,6 +351,119 @@ interface AnalyzeRunsOptions {
|
|
|
351
351
|
}
|
|
352
352
|
declare function analyzeRuns(opts: AnalyzeRunsOptions): Promise<InsightReport>;
|
|
353
353
|
|
|
354
|
+
/**
|
|
355
|
+
* # `@tangle-network/agent-eval/contract` — eval-run diff primitive.
|
|
356
|
+
*
|
|
357
|
+
* The substrate side of the v-N-versus-v-N+1 dashboard view. Given two
|
|
358
|
+
* `EvalRunEvent`s (or two `EvalRunGenerationSnapshot`s from one run), this
|
|
359
|
+
* returns a normalised diff: per-cell composite + per-judge/per-dimension
|
|
360
|
+
* deltas, surface-hash change, aggregate cost + duration shifts.
|
|
361
|
+
*
|
|
362
|
+
* Consumed by:
|
|
363
|
+
* - The hosted-tier dashboard (intelligence-web) — renders v3 vs v4
|
|
364
|
+
* comparisons of cells × judges × dimensions.
|
|
365
|
+
* - CI reporting — emits a "shipped: composite +0.07, cost +$1.20" line
|
|
366
|
+
* in PR review for autonomous-improvement runs.
|
|
367
|
+
* - Any downstream consumer that needs "what actually changed" without
|
|
368
|
+
* reimplementing the matching + arithmetic.
|
|
369
|
+
*
|
|
370
|
+
* Cells are matched on the natural composite key `(scenarioId, rep)`.
|
|
371
|
+
* Unmatched cells surface as `removed` / `added` so callers can tell
|
|
372
|
+
* "this cell got worse" from "this cell wasn't run."
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
/** Per-dimension delta. `before` / `after` are null when the judge did not
|
|
376
|
+
* emit a value for that side. `delta` is `after - before`; null when
|
|
377
|
+
* either side is null. */
|
|
378
|
+
interface EvalDimensionDelta {
|
|
379
|
+
before: number | null;
|
|
380
|
+
after: number | null;
|
|
381
|
+
delta: number | null;
|
|
382
|
+
}
|
|
383
|
+
/** Per-cell delta, keyed on `(scenarioId, rep)`. */
|
|
384
|
+
interface EvalCellScoreDelta {
|
|
385
|
+
scenarioId: string;
|
|
386
|
+
rep: number;
|
|
387
|
+
compositeBefore: number;
|
|
388
|
+
compositeAfter: number;
|
|
389
|
+
compositeDelta: number;
|
|
390
|
+
/** Per-judge → per-dimension deltas. Outer key = judge name from
|
|
391
|
+
* `EvalRunCellScore.dimensions`; inner key = dimension name. */
|
|
392
|
+
dimensions: Record<string, Record<string, EvalDimensionDelta>>;
|
|
393
|
+
}
|
|
394
|
+
/** Diff between two generation snapshots — the unit the dashboard renders
|
|
395
|
+
* for a single "v3 vs v4" comparison. */
|
|
396
|
+
interface EvalGenerationDiff {
|
|
397
|
+
beforeIndex: number;
|
|
398
|
+
afterIndex: number;
|
|
399
|
+
beforeSurfaceHash: string;
|
|
400
|
+
afterSurfaceHash: string;
|
|
401
|
+
surfaceChanged: boolean;
|
|
402
|
+
/** Cells present in both snapshots, matched on `(scenarioId, rep)`. */
|
|
403
|
+
matched: EvalCellScoreDelta[];
|
|
404
|
+
/** Cells present in `before` but missing from `after`. */
|
|
405
|
+
removed: EvalRunCellScore[];
|
|
406
|
+
/** Cells present in `after` but missing from `before`. */
|
|
407
|
+
added: EvalRunCellScore[];
|
|
408
|
+
/** Aggregate composite mean across all cells in the snapshot. */
|
|
409
|
+
compositeBefore: number;
|
|
410
|
+
compositeAfter: number;
|
|
411
|
+
compositeDelta: number;
|
|
412
|
+
costUsdBefore: number;
|
|
413
|
+
costUsdAfter: number;
|
|
414
|
+
costUsdDelta: number;
|
|
415
|
+
durationMsBefore: number;
|
|
416
|
+
durationMsAfter: number;
|
|
417
|
+
durationMsDelta: number;
|
|
418
|
+
}
|
|
419
|
+
/** Diff between two full eval-runs. Includes both baseline-vs-baseline and
|
|
420
|
+
* winner-vs-winner generation diffs when both sides expose them, plus
|
|
421
|
+
* run-level metadata. */
|
|
422
|
+
interface EvalRunDiff {
|
|
423
|
+
beforeRunId: string;
|
|
424
|
+
afterRunId: string;
|
|
425
|
+
beforeTimestamp: string;
|
|
426
|
+
afterTimestamp: string;
|
|
427
|
+
beforeGateDecision: GateDecision | null;
|
|
428
|
+
afterGateDecision: GateDecision | null;
|
|
429
|
+
beforeHoldoutLift: number | null;
|
|
430
|
+
afterHoldoutLift: number | null;
|
|
431
|
+
holdoutLiftDelta: number | null;
|
|
432
|
+
beforeTotalCostUsd: number;
|
|
433
|
+
afterTotalCostUsd: number;
|
|
434
|
+
totalCostUsdDelta: number;
|
|
435
|
+
beforeTotalDurationMs: number;
|
|
436
|
+
afterTotalDurationMs: number;
|
|
437
|
+
totalDurationMsDelta: number;
|
|
438
|
+
/** Baseline-vs-baseline diff. Null when either run has no baseline. */
|
|
439
|
+
baselineDiff: EvalGenerationDiff | null;
|
|
440
|
+
/** Highest-index-generation comparison. Null when either run has no
|
|
441
|
+
* recorded generations (e.g. baseline-only or errored before any
|
|
442
|
+
* generation completed). */
|
|
443
|
+
winnersDiff: EvalGenerationDiff | null;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Diff two generation snapshots. Cells are matched on `(scenarioId, rep)`;
|
|
447
|
+
* unmatched cells surface in `added` / `removed`. Aggregate fields are
|
|
448
|
+
* recomputed from the snapshot's stored fields, not re-derived from cells —
|
|
449
|
+
* this keeps the diff consistent with whatever aggregation the substrate
|
|
450
|
+
* actually reported.
|
|
451
|
+
*/
|
|
452
|
+
declare function diffGenerations(before: EvalRunGenerationSnapshot, after: EvalRunGenerationSnapshot): EvalGenerationDiff;
|
|
453
|
+
/**
|
|
454
|
+
* Diff two full eval-runs. Produces baseline-vs-baseline and
|
|
455
|
+
* winner-vs-winner generation diffs when both sides expose them, plus
|
|
456
|
+
* run-level cost / lift / gate-decision deltas.
|
|
457
|
+
*/
|
|
458
|
+
declare function diffRuns(before: EvalRunEvent, after: EvalRunEvent): EvalRunDiff;
|
|
459
|
+
/**
|
|
460
|
+
* Within-run baseline → winning-generation diff. The natural "what did the
|
|
461
|
+
* improvement loop produce" view for a single run. Returns null when the
|
|
462
|
+
* run never reached a generation past baseline (errored early, or the gate
|
|
463
|
+
* shipped the baseline as-is).
|
|
464
|
+
*/
|
|
465
|
+
declare function diffRunBaselineToWinner(run: EvalRunEvent): EvalGenerationDiff | null;
|
|
466
|
+
|
|
354
467
|
/**
|
|
355
468
|
* `fromAgentTrace` — provenance correlation from Cursor's Agent Trace spec
|
|
356
469
|
* (https://github.com/cursor/agent-trace, RFC v0.1.0).
|
|
@@ -567,4 +680,4 @@ interface FromOtelSpansOptions {
|
|
|
567
680
|
}
|
|
568
681
|
declare function fromOtelSpans(opts: FromOtelSpansOptions): RunRecord[];
|
|
569
682
|
|
|
570
|
-
export { type AgentTraceContributor, type AgentTraceContributorType, type AgentTraceConversation, type AgentTraceFile, type AgentTraceIndex, type AgentTraceRange, type AgentTraceRecord, type AnalyzeRunsOptions, type AuthoringProvenance, CampaignStorage, DispatchContext, type FeedbackTableMeta, type FeedbackTableRow, type FromFeedbackTableOptions, type FromFeedbackTableResult, type FromOtelSpansOptions, Gate, ImprovementDriver, InsightReport, JudgeConfig, MutableSurface, type PartitionByAuthoringModelResult, RunImprovementLoopResult, Scenario, type SelfImproveBudget, type SelfImproveLlm, type SelfImproveOptions, type SelfImproveProgressEvent, type SelfImproveResult, analyzeRuns, fromFeedbackTable, fromOtelSpans, parseAgentTrace, partitionRunsByAuthoringModel, selfImprove };
|
|
683
|
+
export { type AgentTraceContributor, type AgentTraceContributorType, type AgentTraceConversation, type AgentTraceFile, type AgentTraceIndex, type AgentTraceRange, type AgentTraceRecord, type AnalyzeRunsOptions, type AuthoringProvenance, CampaignStorage, DispatchContext, type EvalCellScoreDelta, type EvalDimensionDelta, type EvalGenerationDiff, type EvalRunDiff, type FeedbackTableMeta, type FeedbackTableRow, type FromFeedbackTableOptions, type FromFeedbackTableResult, type FromOtelSpansOptions, Gate, GateDecision, ImprovementDriver, InsightReport, JudgeConfig, MutableSurface, type PartitionByAuthoringModelResult, RunImprovementLoopResult, Scenario, type SelfImproveBudget, type SelfImproveLlm, type SelfImproveOptions, type SelfImproveProgressEvent, type SelfImproveResult, analyzeRuns, diffGenerations, diffRunBaselineToWinner, diffRuns, fromFeedbackTable, fromOtelSpans, parseAgentTrace, partitionRunsByAuthoringModel, selfImprove };
|
package/dist/contract/index.js
CHANGED
|
@@ -1087,6 +1087,119 @@ function cellsToRunRecords(cells, candidateId, runId, surface) {
|
|
|
1087
1087
|
});
|
|
1088
1088
|
}
|
|
1089
1089
|
|
|
1090
|
+
// src/contract/diff.ts
|
|
1091
|
+
function keyForCell(cell) {
|
|
1092
|
+
return JSON.stringify([cell.scenarioId, cell.rep]);
|
|
1093
|
+
}
|
|
1094
|
+
function diffDimensions(before, after) {
|
|
1095
|
+
const out = {};
|
|
1096
|
+
const judges = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
1097
|
+
for (const judge of judges) {
|
|
1098
|
+
const beforeDims = before[judge] ?? {};
|
|
1099
|
+
const afterDims = after[judge] ?? {};
|
|
1100
|
+
const dims = /* @__PURE__ */ new Set([...Object.keys(beforeDims), ...Object.keys(afterDims)]);
|
|
1101
|
+
const judgeOut = {};
|
|
1102
|
+
for (const dim of dims) {
|
|
1103
|
+
const rawBefore = beforeDims[dim];
|
|
1104
|
+
const rawAfter = afterDims[dim];
|
|
1105
|
+
const b = typeof rawBefore === "number" && Number.isFinite(rawBefore) ? rawBefore : null;
|
|
1106
|
+
const a = typeof rawAfter === "number" && Number.isFinite(rawAfter) ? rawAfter : null;
|
|
1107
|
+
judgeOut[dim] = {
|
|
1108
|
+
before: b,
|
|
1109
|
+
after: a,
|
|
1110
|
+
delta: b !== null && a !== null ? a - b : null
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
out[judge] = judgeOut;
|
|
1114
|
+
}
|
|
1115
|
+
return out;
|
|
1116
|
+
}
|
|
1117
|
+
function diffGenerations(before, after) {
|
|
1118
|
+
const beforeMap = new Map(before.cells.map((c) => [keyForCell(c), c]));
|
|
1119
|
+
const afterMap = new Map(after.cells.map((c) => [keyForCell(c), c]));
|
|
1120
|
+
const matched = [];
|
|
1121
|
+
const removed = [];
|
|
1122
|
+
const added = [];
|
|
1123
|
+
for (const [key, beforeCell] of beforeMap) {
|
|
1124
|
+
const afterCell = afterMap.get(key);
|
|
1125
|
+
if (!afterCell) {
|
|
1126
|
+
removed.push(beforeCell);
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
matched.push({
|
|
1130
|
+
scenarioId: beforeCell.scenarioId,
|
|
1131
|
+
rep: beforeCell.rep,
|
|
1132
|
+
compositeBefore: beforeCell.compositeMean,
|
|
1133
|
+
compositeAfter: afterCell.compositeMean,
|
|
1134
|
+
compositeDelta: afterCell.compositeMean - beforeCell.compositeMean,
|
|
1135
|
+
dimensions: diffDimensions(beforeCell.dimensions, afterCell.dimensions)
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
for (const [key, afterCell] of afterMap) {
|
|
1139
|
+
if (!beforeMap.has(key)) added.push(afterCell);
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
beforeIndex: before.index,
|
|
1143
|
+
afterIndex: after.index,
|
|
1144
|
+
beforeSurfaceHash: before.surfaceHash,
|
|
1145
|
+
afterSurfaceHash: after.surfaceHash,
|
|
1146
|
+
surfaceChanged: before.surfaceHash !== after.surfaceHash,
|
|
1147
|
+
matched,
|
|
1148
|
+
removed,
|
|
1149
|
+
added,
|
|
1150
|
+
compositeBefore: before.compositeMean,
|
|
1151
|
+
compositeAfter: after.compositeMean,
|
|
1152
|
+
compositeDelta: after.compositeMean - before.compositeMean,
|
|
1153
|
+
costUsdBefore: before.costUsd,
|
|
1154
|
+
costUsdAfter: after.costUsd,
|
|
1155
|
+
costUsdDelta: after.costUsd - before.costUsd,
|
|
1156
|
+
durationMsBefore: before.durationMs,
|
|
1157
|
+
durationMsAfter: after.durationMs,
|
|
1158
|
+
durationMsDelta: after.durationMs - before.durationMs
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function winnerOf(run) {
|
|
1162
|
+
if (run.generations.length === 0) return null;
|
|
1163
|
+
let winner = run.generations[0];
|
|
1164
|
+
for (const gen of run.generations) {
|
|
1165
|
+
if (gen.index > winner.index) winner = gen;
|
|
1166
|
+
}
|
|
1167
|
+
return winner;
|
|
1168
|
+
}
|
|
1169
|
+
function diffRuns(before, after) {
|
|
1170
|
+
const beforeWinner = winnerOf(before);
|
|
1171
|
+
const afterWinner = winnerOf(after);
|
|
1172
|
+
const baselineDiff = before.baseline && after.baseline ? diffGenerations(before.baseline, after.baseline) : null;
|
|
1173
|
+
const winnersDiff = beforeWinner && afterWinner ? diffGenerations(beforeWinner, afterWinner) : null;
|
|
1174
|
+
const beforeLift = before.holdoutLift ?? null;
|
|
1175
|
+
const afterLift = after.holdoutLift ?? null;
|
|
1176
|
+
return {
|
|
1177
|
+
beforeRunId: before.runId,
|
|
1178
|
+
afterRunId: after.runId,
|
|
1179
|
+
beforeTimestamp: before.timestamp,
|
|
1180
|
+
afterTimestamp: after.timestamp,
|
|
1181
|
+
beforeGateDecision: before.gateDecision ?? null,
|
|
1182
|
+
afterGateDecision: after.gateDecision ?? null,
|
|
1183
|
+
beforeHoldoutLift: beforeLift,
|
|
1184
|
+
afterHoldoutLift: afterLift,
|
|
1185
|
+
holdoutLiftDelta: beforeLift !== null && afterLift !== null ? afterLift - beforeLift : null,
|
|
1186
|
+
beforeTotalCostUsd: before.totalCostUsd,
|
|
1187
|
+
afterTotalCostUsd: after.totalCostUsd,
|
|
1188
|
+
totalCostUsdDelta: after.totalCostUsd - before.totalCostUsd,
|
|
1189
|
+
beforeTotalDurationMs: before.totalDurationMs,
|
|
1190
|
+
afterTotalDurationMs: after.totalDurationMs,
|
|
1191
|
+
totalDurationMsDelta: after.totalDurationMs - before.totalDurationMs,
|
|
1192
|
+
baselineDiff,
|
|
1193
|
+
winnersDiff
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function diffRunBaselineToWinner(run) {
|
|
1197
|
+
if (!run.baseline) return null;
|
|
1198
|
+
const winner = winnerOf(run);
|
|
1199
|
+
if (!winner || winner.index === run.baseline.index) return null;
|
|
1200
|
+
return diffGenerations(run.baseline, winner);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1090
1203
|
// src/contract/intake/agent-trace.ts
|
|
1091
1204
|
function rangeLines(r) {
|
|
1092
1205
|
return Math.max(0, r.end_line - r.start_line + 1);
|
|
@@ -1328,6 +1441,9 @@ export {
|
|
|
1328
1441
|
buildEvidenceVector,
|
|
1329
1442
|
composeGate,
|
|
1330
1443
|
defaultProductionGate,
|
|
1444
|
+
diffGenerations,
|
|
1445
|
+
diffRunBaselineToWinner,
|
|
1446
|
+
diffRuns,
|
|
1331
1447
|
evolutionaryDriver,
|
|
1332
1448
|
fromFeedbackTable,
|
|
1333
1449
|
fromOtelSpans,
|