@pi-unipi/background-tasks 2.16.1 → 2.18.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 +21 -27
- package/package.json +3 -4
- package/src/cards.ts +76 -0
- package/src/child-process.ts +1 -1
- package/src/config.ts +0 -42
- package/src/context-visible-conversation-v2.ts +1 -1
- package/src/delegate/artifacts.ts +1 -1
- package/src/delegate/launch.ts +17 -30
- package/src/delegate/result-package.ts +1 -1
- package/src/delegate/runner.ts +1 -20
- package/src/delegate/seed.ts +1 -1
- package/src/delegate-extension.ts +16 -168
- package/src/index.ts +53 -25
- package/src/json-utils.ts +56 -0
- package/src/package-assets.ts +51 -0
- package/src/registry.ts +8 -459
- package/src/task-manager.ts +13 -2
- package/src/tools.ts +4 -189
- package/src/types.ts +17 -70
- package/extensions/anthropic-attribution.ts +0 -1
- package/extensions/fusion-child.ts +0 -1
- package/src/anthropic-attribution-path.ts +0 -21
- package/src/anthropic-attribution.ts +0 -1983
- package/src/attested-pi-run.ts +0 -612
- package/src/fixtures/fusion-golden-bytes.json +0 -310
- package/src/fixtures/fusion-validate-golden-bytes.json +0 -282
- package/src/fusion/artifacts.ts +0 -967
- package/src/fusion/budget.ts +0 -1162
- package/src/fusion/child-protocol.ts +0 -305
- package/src/fusion/claude-cache.ts +0 -207
- package/src/fusion/clean-context.ts +0 -91
- package/src/fusion/config.ts +0 -449
- package/src/fusion/context.ts +0 -265
- package/src/fusion/evaluation.ts +0 -800
- package/src/fusion/orchestrator.ts +0 -1288
- package/src/fusion/output-contract.ts +0 -34
- package/src/fusion/pi-child.ts +0 -2373
- package/src/fusion/prompts.ts +0 -345
- package/src/fusion/result-package.ts +0 -959
- package/src/fusion/source-policy.ts +0 -257
- package/src/fusion/types.ts +0 -1139
- package/src/fusion/web-fetch.ts +0 -1060
- package/src/fusion/workflows.ts +0 -184
- package/src/fusion-child-extension.ts +0 -1052
- package/src/fusion-extension.ts +0 -1293
- package/src/ui/fusion-model-selector.ts +0 -322
package/src/fusion/budget.ts
DELETED
|
@@ -1,1162 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
TOKEN_BUDGET_AFFINE_F_TOKENS,
|
|
4
|
-
TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
5
|
-
TOKEN_BUDGET_FAMILY_CALIBRATIONS,
|
|
6
|
-
TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
7
|
-
TOKEN_BUDGET_RATE_SCALE,
|
|
8
|
-
estimateInputTokens,
|
|
9
|
-
knownTextSegment,
|
|
10
|
-
maxKnownTextBytesForTokens,
|
|
11
|
-
resolveTokenBudgetFamily,
|
|
12
|
-
unknownOutputContractSegment,
|
|
13
|
-
allowedInputTokens,
|
|
14
|
-
isUsableContextWindow,
|
|
15
|
-
} from '../context-token-budget.js';
|
|
16
|
-
import {
|
|
17
|
-
buildBlindEvaluationInput,
|
|
18
|
-
buildCandidatePrompt,
|
|
19
|
-
buildEvaluationPrompt,
|
|
20
|
-
buildEvaluationRepairPrompt,
|
|
21
|
-
buildMergeInput,
|
|
22
|
-
buildMergePrompt,
|
|
23
|
-
type AnonymousFusionCandidate,
|
|
24
|
-
} from './prompts.js';
|
|
25
|
-
import { FUSION_REASON_WORKFLOW, type FusionWorkflowProfile } from './workflows.js';
|
|
26
|
-
import {
|
|
27
|
-
FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
28
|
-
FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
29
|
-
FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
30
|
-
FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
31
|
-
} from './output-contract.js';
|
|
32
|
-
export {
|
|
33
|
-
FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
34
|
-
FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
35
|
-
FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
36
|
-
FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
37
|
-
assertChildOutputWithinContract,
|
|
38
|
-
fusionOutputContractBytes,
|
|
39
|
-
} from './output-contract.js';
|
|
40
|
-
import {
|
|
41
|
-
FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
42
|
-
FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
43
|
-
FUSION_EVALUATION_SCHEMA_VERSION,
|
|
44
|
-
FusionError,
|
|
45
|
-
type FusionBudgetBlocker,
|
|
46
|
-
type FusionBudgetCheckKind,
|
|
47
|
-
type FusionBudgetComponentBreakdown,
|
|
48
|
-
type FusionBudgetCounterfactuals,
|
|
49
|
-
type FusionBudgetEmptyRequestVerdict,
|
|
50
|
-
type FusionBudgetErrorDetail,
|
|
51
|
-
type FusionBudgetPlanV1,
|
|
52
|
-
type FusionBudgetPolicyDescriptor,
|
|
53
|
-
type FusionBudgetRouteTableEntry,
|
|
54
|
-
type FusionBudgetStage,
|
|
55
|
-
type FusionBudgetStageComposition,
|
|
56
|
-
type FusionBudgetWarning,
|
|
57
|
-
type FusionCalibrationViolation,
|
|
58
|
-
type FusionCanonicalInputV3,
|
|
59
|
-
type FusionCapability,
|
|
60
|
-
type FusionEvaluationV1,
|
|
61
|
-
type FusionRouteCapacity,
|
|
62
|
-
type FusionStage,
|
|
63
|
-
type FusionStageBudgetPlanEntry,
|
|
64
|
-
type FusionChildRunResult,
|
|
65
|
-
type ResolvedFusionModel,
|
|
66
|
-
type ResolvedFusionModels,
|
|
67
|
-
} from './types.js';
|
|
68
|
-
|
|
69
|
-
export const FUSION_CALIBRATED_BYTES_PER_TOKEN = TOKEN_BUDGET_FAMILY_CALIBRATIONS;
|
|
70
|
-
|
|
71
|
-
const FUSION_OUTPUT_RESERVE_RATE_X100 = 200;
|
|
72
|
-
export const FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS = 8000;
|
|
73
|
-
const BASIS_POINTS_DENOMINATOR = 10_000;
|
|
74
|
-
|
|
75
|
-
function ceilDiv(numerator: number, denominator: number): number {
|
|
76
|
-
if (!Number.isSafeInteger(numerator) || numerator < 0) {
|
|
77
|
-
throw new TypeError('ceilDiv numerator must be a non-negative safe integer');
|
|
78
|
-
}
|
|
79
|
-
if (!Number.isSafeInteger(denominator) || denominator <= 0) {
|
|
80
|
-
throw new TypeError('ceilDiv denominator must be a positive safe integer');
|
|
81
|
-
}
|
|
82
|
-
if (numerator === 0) return 0;
|
|
83
|
-
return Math.floor((numerator - 1) / denominator) + 1;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export const FUSION_RESERVED_OUTPUT_TOKENS = ceilDiv(
|
|
87
|
-
FUSION_MERGE_MAX_OUTPUT_BYTES * TOKEN_BUDGET_RATE_SCALE,
|
|
88
|
-
FUSION_OUTPUT_RESERVE_RATE_X100,
|
|
89
|
-
);
|
|
90
|
-
export const FUSION_FRAMING_RESERVE_TOKENS = 0;
|
|
91
|
-
export const FUSION_SAFETY_RESERVE_TOKENS = 4_096;
|
|
92
|
-
export const FUSION_MIN_CANONICAL_INPUT_TOKENS = 8_192;
|
|
93
|
-
export const FUSION_MIN_CONTEXT_WINDOW_TOKENS =
|
|
94
|
-
FUSION_MIN_CANONICAL_INPUT_TOKENS +
|
|
95
|
-
FUSION_RESERVED_OUTPUT_TOKENS +
|
|
96
|
-
FUSION_FRAMING_RESERVE_TOKENS +
|
|
97
|
-
FUSION_SAFETY_RESERVE_TOKENS;
|
|
98
|
-
|
|
99
|
-
export const FUSION_BUDGET_POLICY: FusionBudgetPolicyDescriptor = {
|
|
100
|
-
id: 'fusion-budget-policy-v4',
|
|
101
|
-
route_output_reserve_strategy: 'max_fusion_contract_or_model_max',
|
|
102
|
-
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
103
|
-
calibration_table: FUSION_CALIBRATED_BYTES_PER_TOKEN,
|
|
104
|
-
reserved_output_tokens: FUSION_RESERVED_OUTPUT_TOKENS,
|
|
105
|
-
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
106
|
-
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
107
|
-
candidate_output_contract_bytes: FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
108
|
-
evaluation_output_contract_bytes: FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
109
|
-
merge_output_contract_bytes: FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
110
|
-
diagnostics_contract_bytes: FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
111
|
-
utilization_warning_threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const REASON_EMPTY_REMEDIATION: readonly string[] = Object.freeze([
|
|
115
|
-
'Start a fresh Pi conversation, or run fusion_reason earlier in the session.',
|
|
116
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
117
|
-
'Restate only the required prior findings in the fusion_reason prompt.',
|
|
118
|
-
]);
|
|
119
|
-
|
|
120
|
-
const REASON_REQUEST_REMEDIATION: readonly string[] = Object.freeze([
|
|
121
|
-
'Provide a shorter fusion_reason prompt.',
|
|
122
|
-
'Start a fresh Pi conversation, or run fusion_reason earlier in the session.',
|
|
123
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
const INVESTIGATE_EMPTY_REMEDIATION: readonly string[] = Object.freeze([
|
|
127
|
-
'Split the repository investigation into smaller independently complete path or subsystem scopes.',
|
|
128
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
129
|
-
]);
|
|
130
|
-
const INVESTIGATE_REQUEST_REMEDIATION: readonly string[] = Object.freeze([
|
|
131
|
-
'Narrow the fusion_investigate objective, repository scope, or required evidence.',
|
|
132
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
133
|
-
]);
|
|
134
|
-
const RESEARCH_EMPTY_REMEDIATION: readonly string[] = Object.freeze([
|
|
135
|
-
'Split the research into smaller independently complete source sets.',
|
|
136
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
137
|
-
]);
|
|
138
|
-
const RESEARCH_REQUEST_REMEDIATION: readonly string[] = Object.freeze([
|
|
139
|
-
'Narrow the fusion_research question or split large declared-source sets across independent runs.',
|
|
140
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
141
|
-
]);
|
|
142
|
-
const VALIDATE_EMPTY_REMEDIATION: readonly string[] = Object.freeze([
|
|
143
|
-
'Split validation into smaller independently complete change or acceptance-criterion scopes.',
|
|
144
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
145
|
-
]);
|
|
146
|
-
const VALIDATE_REQUEST_REMEDIATION: readonly string[] = Object.freeze([
|
|
147
|
-
'Narrow the fusion_validate scope, acceptance criteria, or supplied verification evidence.',
|
|
148
|
-
"Raise the route's context window with a larger-context subscription model via /fusion-models.",
|
|
149
|
-
]);
|
|
150
|
-
|
|
151
|
-
function cleanRemediation(
|
|
152
|
-
profile: FusionWorkflowProfile,
|
|
153
|
-
requestDeterminesFeasibility: boolean,
|
|
154
|
-
): readonly string[] {
|
|
155
|
-
if (profile.id === 'investigate') {
|
|
156
|
-
return requestDeterminesFeasibility
|
|
157
|
-
? INVESTIGATE_REQUEST_REMEDIATION
|
|
158
|
-
: INVESTIGATE_EMPTY_REMEDIATION;
|
|
159
|
-
}
|
|
160
|
-
if (profile.id === 'research') {
|
|
161
|
-
return requestDeterminesFeasibility ? RESEARCH_REQUEST_REMEDIATION : RESEARCH_EMPTY_REMEDIATION;
|
|
162
|
-
}
|
|
163
|
-
if (profile.id === 'validate') {
|
|
164
|
-
return requestDeterminesFeasibility ? VALIDATE_REQUEST_REMEDIATION : VALIDATE_EMPTY_REMEDIATION;
|
|
165
|
-
}
|
|
166
|
-
return requestDeterminesFeasibility ? REASON_REQUEST_REMEDIATION : REASON_EMPTY_REMEDIATION;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const RESERVATION_REMEDIATION: readonly string[] = Object.freeze([
|
|
170
|
-
'Route the blocking stage to a model with larger byte capacity.',
|
|
171
|
-
'Keep producer output contracts intact; do not shrink or truncate child answers.',
|
|
172
|
-
'Inspect budget-plan.json for the advisory reservation component before retrying.',
|
|
173
|
-
]);
|
|
174
|
-
|
|
175
|
-
const DENSE_REMEDIATION: readonly string[] = Object.freeze([
|
|
176
|
-
'Remove or externalize low-whitespace dense ASCII payloads (base64, minified code, PEM/hex blocks), then retry.',
|
|
177
|
-
'The prompt is preserved; no content was clipped to fit.',
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
const MULTIBYTE_REMEDIATION: readonly string[] = Object.freeze([
|
|
181
|
-
'This rejection is dominated by multibyte UTF-8 content; use a larger-context route or split the non-Latin/CJK-heavy task.',
|
|
182
|
-
'The budget plan includes the stricter multibyte advisory ceiling separately from the fatal estimate.',
|
|
183
|
-
'The prompt is preserved; no content was clipped to fit.',
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
const EMPTY_CANDIDATES: readonly [
|
|
187
|
-
AnonymousFusionCandidate,
|
|
188
|
-
AnonymousFusionCandidate,
|
|
189
|
-
AnonymousFusionCandidate,
|
|
190
|
-
] = Object.freeze([
|
|
191
|
-
Object.freeze({ candidate_id: 'A', response: '' }),
|
|
192
|
-
Object.freeze({ candidate_id: 'B', response: '' }),
|
|
193
|
-
Object.freeze({ candidate_id: 'C', response: '' }),
|
|
194
|
-
]);
|
|
195
|
-
|
|
196
|
-
const EMPTY_EVALUATION: FusionEvaluationV1 = Object.freeze({
|
|
197
|
-
schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
|
|
198
|
-
candidate_assessments: Object.freeze([
|
|
199
|
-
Object.freeze({
|
|
200
|
-
candidate_id: 'A',
|
|
201
|
-
summary: '',
|
|
202
|
-
strengths: Object.freeze([]),
|
|
203
|
-
limitations: Object.freeze([]),
|
|
204
|
-
useful_contributions: Object.freeze([]),
|
|
205
|
-
risks: Object.freeze([]),
|
|
206
|
-
}),
|
|
207
|
-
Object.freeze({
|
|
208
|
-
candidate_id: 'B',
|
|
209
|
-
summary: '',
|
|
210
|
-
strengths: Object.freeze([]),
|
|
211
|
-
limitations: Object.freeze([]),
|
|
212
|
-
useful_contributions: Object.freeze([]),
|
|
213
|
-
risks: Object.freeze([]),
|
|
214
|
-
}),
|
|
215
|
-
Object.freeze({
|
|
216
|
-
candidate_id: 'C',
|
|
217
|
-
summary: '',
|
|
218
|
-
strengths: Object.freeze([]),
|
|
219
|
-
limitations: Object.freeze([]),
|
|
220
|
-
useful_contributions: Object.freeze([]),
|
|
221
|
-
risks: Object.freeze([]),
|
|
222
|
-
}),
|
|
223
|
-
]) as readonly [
|
|
224
|
-
FusionEvaluationV1['candidate_assessments'][0],
|
|
225
|
-
FusionEvaluationV1['candidate_assessments'][1],
|
|
226
|
-
FusionEvaluationV1['candidate_assessments'][2],
|
|
227
|
-
],
|
|
228
|
-
agreements: Object.freeze([]),
|
|
229
|
-
conflicts: Object.freeze([]),
|
|
230
|
-
synthesis_plan: Object.freeze({
|
|
231
|
-
must_include: Object.freeze([]),
|
|
232
|
-
must_resolve: Object.freeze([]),
|
|
233
|
-
must_avoid: Object.freeze([]),
|
|
234
|
-
}),
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
interface StageForecastDraft {
|
|
238
|
-
budget_stage: FusionBudgetStage;
|
|
239
|
-
slot?: 1 | 2 | 3;
|
|
240
|
-
route: FusionRouteCapacity;
|
|
241
|
-
conditional: boolean;
|
|
242
|
-
system_prompt: string;
|
|
243
|
-
empty_user_prompt: string;
|
|
244
|
-
upstream_output_contract_bytes: number;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function fusionTokenUpperBound(utf8Bytes: number): number {
|
|
248
|
-
return estimateInputTokens({
|
|
249
|
-
family: 'unknown',
|
|
250
|
-
scope: 'conservative',
|
|
251
|
-
segments: [{ kind: 'known_text', bytes: utf8Bytes, multibyteBytes: 0, denseBytes: 0 }],
|
|
252
|
-
}).tokens;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function utf8Bytes(value: string): number {
|
|
256
|
-
return Buffer.byteLength(value, 'utf8');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function sha256Hex(value: string): string {
|
|
260
|
-
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function requirePositiveContextWindow(model: ResolvedFusionModel, role: string): number {
|
|
264
|
-
const value = model.contextWindow;
|
|
265
|
-
if (!isUsableContextWindow(value)) {
|
|
266
|
-
throw new FusionError(
|
|
267
|
-
`fusion ${role} route ${model.qualifiedId} has no usable context window capacity`,
|
|
268
|
-
{ code: 'model_capacity_unknown', childCreated: false },
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function formatRateX100(value: number): string {
|
|
275
|
-
const whole = Math.floor(value / TOKEN_BUDGET_RATE_SCALE);
|
|
276
|
-
const frac = String(value % TOKEN_BUDGET_RATE_SCALE).padStart(2, '0');
|
|
277
|
-
return `${String(whole)}.${frac}`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function routeCapacity(
|
|
281
|
-
model: ResolvedFusionModel,
|
|
282
|
-
role: FusionRouteCapacity['role'],
|
|
283
|
-
): FusionRouteCapacity {
|
|
284
|
-
const contextWindow = requirePositiveContextWindow(model, role);
|
|
285
|
-
const reservedOutputTokens = Math.max(FUSION_RESERVED_OUTPUT_TOKENS, model.maxOutputTokens);
|
|
286
|
-
const allowed = allowedInputTokens(contextWindow, {
|
|
287
|
-
reservedOutputTokens,
|
|
288
|
-
framingReserveTokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
289
|
-
safetyReserveTokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
290
|
-
});
|
|
291
|
-
if (allowed < FUSION_MIN_CANONICAL_INPUT_TOKENS) {
|
|
292
|
-
const minimumContextWindow =
|
|
293
|
-
reservedOutputTokens +
|
|
294
|
-
FUSION_FRAMING_RESERVE_TOKENS +
|
|
295
|
-
FUSION_SAFETY_RESERVE_TOKENS +
|
|
296
|
-
FUSION_MIN_CANONICAL_INPUT_TOKENS;
|
|
297
|
-
throw new FusionError(
|
|
298
|
-
`fusion ${role} route ${model.qualifiedId} has a ${String(contextWindow)}-token context window, but Fusion requires at least ${String(minimumContextWindow)} tokens: ${String(reservedOutputTokens)} reserved for the route's configured maximum output + ${String(FUSION_FRAMING_RESERVE_TOKENS)} framing + ${String(FUSION_SAFETY_RESERVE_TOKENS)} safety + ${String(FUSION_MIN_CANONICAL_INPUT_TOKENS)} usable input. Choose a larger-context or lower-max-output subscription model for this slot with /fusion-models.`,
|
|
299
|
-
{ code: 'model_capacity_unknown', childCreated: false },
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
const family = resolveTokenBudgetFamily({ provider: model.provider, model: model.model });
|
|
303
|
-
const rateSource = estimateInputTokens({
|
|
304
|
-
family: family.family,
|
|
305
|
-
calibrationBacked: family.backed,
|
|
306
|
-
familyResolution: family.resolution,
|
|
307
|
-
allowedInputTokens: allowed,
|
|
308
|
-
scope: 'fusion',
|
|
309
|
-
segments: [
|
|
310
|
-
{
|
|
311
|
-
kind: 'known_text',
|
|
312
|
-
bytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
313
|
-
multibyteBytes: 0,
|
|
314
|
-
denseBytes: 0,
|
|
315
|
-
asciiWhitespaceBytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
}).rateSource;
|
|
319
|
-
return {
|
|
320
|
-
role,
|
|
321
|
-
provider: model.provider,
|
|
322
|
-
model: model.model,
|
|
323
|
-
qualified_id: model.qualifiedId,
|
|
324
|
-
context_window_tokens: contextWindow,
|
|
325
|
-
reserved_output_tokens: reservedOutputTokens,
|
|
326
|
-
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
327
|
-
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
328
|
-
allowed_input_tokens: allowed,
|
|
329
|
-
family: family.family,
|
|
330
|
-
rate_source: rateSource,
|
|
331
|
-
byte_capacity_utf8_bytes: Math.floor(
|
|
332
|
-
(allowed * rateSource.effective_rate_bytes_per_token_x100) / TOKEN_BUDGET_RATE_SCALE,
|
|
333
|
-
),
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export function fusionRouteCapacities(models: ResolvedFusionModels): readonly FusionRouteCapacity[] {
|
|
338
|
-
return [
|
|
339
|
-
routeCapacity(models.candidates[0], 'candidate-1'),
|
|
340
|
-
routeCapacity(models.candidates[1], 'candidate-2'),
|
|
341
|
-
routeCapacity(models.candidates[2], 'candidate-3'),
|
|
342
|
-
routeCapacity(models.evaluator, 'evaluator'),
|
|
343
|
-
routeCapacity(models.merger, 'merger'),
|
|
344
|
-
];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function fusionLimitingRoute(
|
|
348
|
-
routes: readonly FusionRouteCapacity[],
|
|
349
|
-
): FusionRouteCapacity {
|
|
350
|
-
let limiting: FusionRouteCapacity | undefined;
|
|
351
|
-
for (const route of routes) {
|
|
352
|
-
if (
|
|
353
|
-
limiting === undefined ||
|
|
354
|
-
route.byte_capacity_utf8_bytes < limiting.byte_capacity_utf8_bytes ||
|
|
355
|
-
(route.byte_capacity_utf8_bytes === limiting.byte_capacity_utf8_bytes &&
|
|
356
|
-
route.role.localeCompare(limiting.role) < 0)
|
|
357
|
-
) {
|
|
358
|
-
limiting = route;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (limiting === undefined) {
|
|
362
|
-
throw new FusionError('fusion budget planning received no configured routes', {
|
|
363
|
-
code: 'model_capacity_unknown',
|
|
364
|
-
childCreated: false,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
return limiting;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function routeByRole(
|
|
371
|
-
routes: readonly FusionRouteCapacity[],
|
|
372
|
-
role: FusionRouteCapacity['role'],
|
|
373
|
-
): FusionRouteCapacity {
|
|
374
|
-
const route = routes.find((item) => item.role === role);
|
|
375
|
-
if (route === undefined) {
|
|
376
|
-
throw new FusionError(`fusion budget route ${role} is missing`, {
|
|
377
|
-
code: 'model_capacity_unknown',
|
|
378
|
-
childCreated: false,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
return route;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function candidateRole(slot: 1 | 2 | 3): FusionRouteCapacity['role'] {
|
|
385
|
-
if (slot === 1) return 'candidate-1';
|
|
386
|
-
if (slot === 2) return 'candidate-2';
|
|
387
|
-
return 'candidate-3';
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function estimateRouteInput(
|
|
391
|
-
route: FusionRouteCapacity,
|
|
392
|
-
segments: Parameters<typeof estimateInputTokens>[0]['segments'],
|
|
393
|
-
) {
|
|
394
|
-
return estimateInputTokens({
|
|
395
|
-
family: route.family,
|
|
396
|
-
calibrationBacked: route.rate_source.backed,
|
|
397
|
-
familyResolution: route.rate_source.model_resolution,
|
|
398
|
-
allowedInputTokens: route.allowed_input_tokens,
|
|
399
|
-
scope: 'fusion',
|
|
400
|
-
segments,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function utilizationBasisPoints(tokens: number, allowed: number): number {
|
|
405
|
-
return ceilDiv(tokens * BASIS_POINTS_DENOMINATOR, allowed);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function forecastEntry(draft: StageForecastDraft): FusionStageBudgetPlanEntry {
|
|
409
|
-
const inputSegments = [knownTextSegment(draft.system_prompt), knownTextSegment(draft.empty_user_prompt)];
|
|
410
|
-
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
411
|
-
const inputOnly = estimateRouteInput(draft.route, inputSegments);
|
|
412
|
-
const reservationSegments =
|
|
413
|
-
draft.upstream_output_contract_bytes === 0
|
|
414
|
-
? inputSegments
|
|
415
|
-
: [...inputSegments, unknownOutputContractSegment(draft.upstream_output_contract_bytes)];
|
|
416
|
-
const reservation = estimateRouteInput(draft.route, reservationSegments);
|
|
417
|
-
const forecastUtf8Bytes = inputBytes + draft.upstream_output_contract_bytes;
|
|
418
|
-
const entry: FusionStageBudgetPlanEntry = {
|
|
419
|
-
budget_stage: draft.budget_stage,
|
|
420
|
-
route: draft.route,
|
|
421
|
-
conditional: draft.conditional,
|
|
422
|
-
check_kind: 'input_only_preflight',
|
|
423
|
-
input_utf8_bytes: inputBytes,
|
|
424
|
-
upstream_output_contract_bytes: draft.upstream_output_contract_bytes,
|
|
425
|
-
forecast_utf8_bytes: forecastUtf8Bytes,
|
|
426
|
-
input_only_input_tokens_upper_bound: inputOnly.tokens,
|
|
427
|
-
forecast_input_tokens_upper_bound: reservation.tokens,
|
|
428
|
-
allowed_input_tokens: draft.route.allowed_input_tokens,
|
|
429
|
-
input_only_signed_headroom_tokens: draft.route.allowed_input_tokens - inputOnly.tokens,
|
|
430
|
-
signed_headroom_tokens: draft.route.allowed_input_tokens - reservation.tokens,
|
|
431
|
-
input_only_utilization_basis_points: utilizationBasisPoints(
|
|
432
|
-
inputOnly.tokens,
|
|
433
|
-
draft.route.allowed_input_tokens,
|
|
434
|
-
),
|
|
435
|
-
utilization_basis_points: utilizationBasisPoints(
|
|
436
|
-
reservation.tokens,
|
|
437
|
-
draft.route.allowed_input_tokens,
|
|
438
|
-
),
|
|
439
|
-
input_only_estimate: inputOnly,
|
|
440
|
-
reservation_estimate: reservation,
|
|
441
|
-
fits: inputOnly.tokens <= draft.route.allowed_input_tokens,
|
|
442
|
-
reservation_fits: reservation.tokens <= draft.route.allowed_input_tokens,
|
|
443
|
-
};
|
|
444
|
-
if (draft.slot !== undefined) entry.slot = draft.slot;
|
|
445
|
-
return entry;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function maxKnownTextBytes(route: FusionRouteCapacity): number {
|
|
449
|
-
return maxKnownTextBytesForTokens({
|
|
450
|
-
family: route.family,
|
|
451
|
-
calibrationBacked: route.rate_source.backed,
|
|
452
|
-
familyResolution: route.rate_source.model_resolution,
|
|
453
|
-
allowedInputTokens: route.allowed_input_tokens,
|
|
454
|
-
scope: 'fusion',
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function blockerFromEntry(entry: FusionStageBudgetPlanEntry): FusionBudgetBlocker {
|
|
459
|
-
return {
|
|
460
|
-
...entry,
|
|
461
|
-
overage_tokens: Math.max(0, entry.input_only_input_tokens_upper_bound - entry.allowed_input_tokens),
|
|
462
|
-
bytes_over: Math.max(0, entry.input_utf8_bytes - maxKnownTextBytes(entry.route)),
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function blockerOrder(blocker: FusionBudgetBlocker): number {
|
|
467
|
-
if (blocker.budget_stage === 'candidate') return blocker.slot ?? 1;
|
|
468
|
-
if (blocker.budget_stage === 'evaluation') return 4;
|
|
469
|
-
if (blocker.budget_stage === 'merge') return 5;
|
|
470
|
-
return 6;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function selectBlockers(entries: readonly FusionStageBudgetPlanEntry[]): readonly FusionBudgetBlocker[] {
|
|
474
|
-
return entries.filter((entry) => !entry.fits).map(blockerFromEntry).sort((left, right) => {
|
|
475
|
-
const byOrder = blockerOrder(left) - blockerOrder(right);
|
|
476
|
-
if (byOrder !== 0) return byOrder;
|
|
477
|
-
return left.route.role.localeCompare(right.route.role);
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function selectPrimaryBlocker(
|
|
482
|
-
blockers: readonly FusionBudgetBlocker[],
|
|
483
|
-
): FusionBudgetBlocker | undefined {
|
|
484
|
-
const mandatory = blockers.find((blocker) => !blocker.conditional);
|
|
485
|
-
return mandatory ?? blockers[0];
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function replaceRequestText(input: FusionCanonicalInputV3, text: string): FusionCanonicalInputV3 {
|
|
489
|
-
return {
|
|
490
|
-
...input,
|
|
491
|
-
request: {
|
|
492
|
-
...input.request,
|
|
493
|
-
text,
|
|
494
|
-
sha256: sha256Hex(text),
|
|
495
|
-
},
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function visibleTextBytes(input: FusionCanonicalInputV3): number {
|
|
500
|
-
const projection = input.context?.kind === 'session_projection'
|
|
501
|
-
? input.context.conversation_projection
|
|
502
|
-
: 'conversation_projection' in input
|
|
503
|
-
? input.conversation_projection
|
|
504
|
-
: undefined;
|
|
505
|
-
if (projection === undefined) return 0;
|
|
506
|
-
let total = 0;
|
|
507
|
-
for (const entry of projection.entries) {
|
|
508
|
-
if (entry[0] === 't') total += utf8Bytes(JSON.stringify(entry[4]));
|
|
509
|
-
}
|
|
510
|
-
return total;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function omissionReceiptBytes(input: FusionCanonicalInputV3): number {
|
|
514
|
-
const projection = input.context?.kind === 'session_projection'
|
|
515
|
-
? input.context.conversation_projection
|
|
516
|
-
: 'conversation_projection' in input
|
|
517
|
-
? input.conversation_projection
|
|
518
|
-
: undefined;
|
|
519
|
-
if (projection === undefined) return 0;
|
|
520
|
-
let total = 0;
|
|
521
|
-
for (const entry of projection.entries) {
|
|
522
|
-
if (entry[0] === 'o') total += utf8Bytes(JSON.stringify(entry));
|
|
523
|
-
}
|
|
524
|
-
return total;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function warningFromEntry(entry: FusionStageBudgetPlanEntry): FusionBudgetWarning | undefined {
|
|
528
|
-
if (!entry.fits) return undefined;
|
|
529
|
-
if (!entry.reservation_fits) {
|
|
530
|
-
return {
|
|
531
|
-
...entry,
|
|
532
|
-
warning_kind: 'worst_case_reservation',
|
|
533
|
-
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (entry.utilization_basis_points >= FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS) {
|
|
537
|
-
return {
|
|
538
|
-
...entry,
|
|
539
|
-
warning_kind: 'input_utilization',
|
|
540
|
-
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
return undefined;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function warningsFor(entries: readonly FusionStageBudgetPlanEntry[]): readonly FusionBudgetWarning[] {
|
|
547
|
-
return entries.flatMap((entry) => {
|
|
548
|
-
const warning = warningFromEntry(entry);
|
|
549
|
-
return warning === undefined ? [] : [warning];
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function entryLabel(entry: FusionStageBudgetPlanEntry): string {
|
|
554
|
-
const slot = entry.slot === undefined ? '' : `-${String(entry.slot)}`;
|
|
555
|
-
const conditional = entry.conditional ? ' (conditional)' : '';
|
|
556
|
-
return `${entry.budget_stage}${slot}${conditional}`;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function blockingChildLabel(entry: FusionStageBudgetPlanEntry): string {
|
|
560
|
-
if (entry.budget_stage === 'candidate') return `candidate-${String(entry.slot ?? 1)}`;
|
|
561
|
-
if (entry.budget_stage === 'evaluation_repair') return 'evaluator-repair';
|
|
562
|
-
if (entry.budget_stage === 'evaluation') return 'evaluator';
|
|
563
|
-
return 'merger';
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
function formatTable(entries: readonly FusionStageBudgetPlanEntry[]): string {
|
|
567
|
-
const lines = entries.map(
|
|
568
|
-
(entry) =>
|
|
569
|
-
`${entryLabel(entry)} | route=${entry.route.qualified_id} | input=${String(entry.input_only_input_tokens_upper_bound)} | reserved=${String(entry.forecast_input_tokens_upper_bound)} | allowed=${String(entry.allowed_input_tokens)} | input_headroom=${String(entry.input_only_signed_headroom_tokens)} | reservation_headroom=${String(entry.signed_headroom_tokens)} | ${entry.fits ? 'input-fits' : 'input-over'} | ${entry.reservation_fits ? 'reservation-fits' : 'reservation-warn'}`,
|
|
570
|
-
);
|
|
571
|
-
return lines.join('\n');
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function formatComposition(composition: FusionBudgetStageComposition): string {
|
|
575
|
-
return [
|
|
576
|
-
`visible text ${String(composition.visible_text_bytes)} B`,
|
|
577
|
-
`omission receipts ${String(composition.omission_receipt_bytes)} B`,
|
|
578
|
-
`projection metadata ${String(composition.projection_metadata_bytes)} B`,
|
|
579
|
-
`request ${String(composition.request_bytes)} B`,
|
|
580
|
-
`static stage framing ${String(composition.static_stage_framing_bytes)} B`,
|
|
581
|
-
`upstream output contracts ${String(composition.upstream_output_contract_bytes)} B`,
|
|
582
|
-
].join('; ');
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function dominantRemediation(
|
|
586
|
-
verdict: FusionBudgetEmptyRequestVerdict,
|
|
587
|
-
composition: FusionBudgetStageComposition | undefined,
|
|
588
|
-
dominantByteClass: string,
|
|
589
|
-
profile: FusionWorkflowProfile,
|
|
590
|
-
): readonly string[] {
|
|
591
|
-
if (dominantByteClass === 'dense_ascii') return DENSE_REMEDIATION;
|
|
592
|
-
if (dominantByteClass === 'multibyte') return MULTIBYTE_REMEDIATION;
|
|
593
|
-
if (composition === undefined) return remediationFor(verdict, profile);
|
|
594
|
-
const entries = [
|
|
595
|
-
{ name: 'visible', bytes: composition.visible_text_bytes },
|
|
596
|
-
{ name: 'request', bytes: composition.request_bytes },
|
|
597
|
-
{ name: 'reservation', bytes: composition.upstream_output_contract_bytes },
|
|
598
|
-
].sort((left, right) => right.bytes - left.bytes);
|
|
599
|
-
const dominant = entries[0];
|
|
600
|
-
if (dominant?.name === 'reservation') return RESERVATION_REMEDIATION;
|
|
601
|
-
if (dominant?.name === 'request' && !verdict.still_fails_with_empty_request) {
|
|
602
|
-
return profile.contextKind === 'clean_task'
|
|
603
|
-
? cleanRemediation(profile, true)
|
|
604
|
-
: REASON_REQUEST_REMEDIATION;
|
|
605
|
-
}
|
|
606
|
-
if (profile.contextKind === 'clean_task') return cleanRemediation(profile, false);
|
|
607
|
-
return REASON_EMPTY_REMEDIATION;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function remediationFor(
|
|
611
|
-
verdict: FusionBudgetEmptyRequestVerdict,
|
|
612
|
-
profile: FusionWorkflowProfile,
|
|
613
|
-
): readonly string[] {
|
|
614
|
-
if (profile.contextKind === 'clean_task') {
|
|
615
|
-
return cleanRemediation(profile, !verdict.still_fails_with_empty_request);
|
|
616
|
-
}
|
|
617
|
-
return verdict.still_fails_with_empty_request
|
|
618
|
-
? REASON_EMPTY_REMEDIATION
|
|
619
|
-
: REASON_REQUEST_REMEDIATION;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function formatEmptyRequestVerdict(verdict: FusionBudgetEmptyRequestVerdict): string {
|
|
623
|
-
if (verdict.still_fails_with_empty_request) {
|
|
624
|
-
return `Empty-request counterfactual: still fails with ${String(verdict.blockers_with_empty_request.length)} blocking stage(s), so shortening the request cannot make this workflow fit.`;
|
|
625
|
-
}
|
|
626
|
-
if (verdict.minimum_request_byte_reduction === 0) {
|
|
627
|
-
return 'Empty-request counterfactual: fits; no request reduction is required by the current plan.';
|
|
628
|
-
}
|
|
629
|
-
return `Empty-request counterfactual: fits, so request size determines feasibility; reduce the request by at least ${String(verdict.minimum_request_byte_reduction)} UTF-8 bytes, leaving at most ${String(verdict.maximum_safe_request_utf8_bytes)} UTF-8 bytes.`;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function stageFromBudgetStage(stage: FusionBudgetStage): FusionStage {
|
|
633
|
-
if (stage === 'merge') return 'merge';
|
|
634
|
-
if (stage === 'candidate') return 'candidate';
|
|
635
|
-
return 'evaluation';
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function routeTable(routes: readonly FusionRouteCapacity[]): readonly FusionBudgetRouteTableEntry[] {
|
|
639
|
-
return [...routes]
|
|
640
|
-
.sort((left, right) => {
|
|
641
|
-
const byCapacity = left.byte_capacity_utf8_bytes - right.byte_capacity_utf8_bytes;
|
|
642
|
-
if (byCapacity !== 0) return byCapacity;
|
|
643
|
-
return left.role.localeCompare(right.role);
|
|
644
|
-
})
|
|
645
|
-
.map((route) => ({
|
|
646
|
-
role: route.role,
|
|
647
|
-
qualified_id: route.qualified_id,
|
|
648
|
-
allowed_input_tokens: route.allowed_input_tokens,
|
|
649
|
-
family: route.family,
|
|
650
|
-
effective_rate_bytes_per_token_x100: route.rate_source.effective_rate_bytes_per_token_x100,
|
|
651
|
-
byte_capacity_utf8_bytes: route.byte_capacity_utf8_bytes,
|
|
652
|
-
backed: route.rate_source.backed,
|
|
653
|
-
}));
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function segmentTokensForBytes(route: FusionRouteCapacity, bytes: number): number {
|
|
657
|
-
if (bytes <= 0) return 0;
|
|
658
|
-
const estimate = estimateRouteInput(route, [
|
|
659
|
-
{ kind: 'known_text', bytes, multibyteBytes: 0, denseBytes: 0 },
|
|
660
|
-
]);
|
|
661
|
-
const segment = estimate.perSegment[0];
|
|
662
|
-
return segment === undefined ? 0 : segment.tokens;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function contractTokens(bytes: number): number {
|
|
666
|
-
return bytes;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function componentBreakdown(
|
|
670
|
-
composition: FusionBudgetStageComposition | undefined,
|
|
671
|
-
route: FusionRouteCapacity,
|
|
672
|
-
): FusionBudgetComponentBreakdown {
|
|
673
|
-
const empty = {
|
|
674
|
-
visible_text_bytes: 0,
|
|
675
|
-
omission_receipt_bytes: 0,
|
|
676
|
-
projection_metadata_bytes: 0,
|
|
677
|
-
request_bytes: 0,
|
|
678
|
-
static_stage_framing_bytes: 0,
|
|
679
|
-
upstream_output_contract_bytes: 0,
|
|
680
|
-
} satisfies FusionBudgetStageComposition;
|
|
681
|
-
const item = composition ?? empty;
|
|
682
|
-
return {
|
|
683
|
-
visible_text: {
|
|
684
|
-
bytes: item.visible_text_bytes,
|
|
685
|
-
tokens: segmentTokensForBytes(route, item.visible_text_bytes),
|
|
686
|
-
},
|
|
687
|
-
omission_receipts: {
|
|
688
|
-
bytes: item.omission_receipt_bytes,
|
|
689
|
-
tokens: segmentTokensForBytes(route, item.omission_receipt_bytes),
|
|
690
|
-
},
|
|
691
|
-
projection_metadata: {
|
|
692
|
-
bytes: item.projection_metadata_bytes,
|
|
693
|
-
tokens: segmentTokensForBytes(route, item.projection_metadata_bytes),
|
|
694
|
-
},
|
|
695
|
-
request: {
|
|
696
|
-
bytes: item.request_bytes,
|
|
697
|
-
tokens: segmentTokensForBytes(route, item.request_bytes),
|
|
698
|
-
},
|
|
699
|
-
static_stage_framing: {
|
|
700
|
-
bytes: item.static_stage_framing_bytes,
|
|
701
|
-
tokens: segmentTokensForBytes(route, item.static_stage_framing_bytes),
|
|
702
|
-
},
|
|
703
|
-
upstream_output_contracts: {
|
|
704
|
-
bytes: item.upstream_output_contract_bytes,
|
|
705
|
-
tokens: contractTokens(item.upstream_output_contract_bytes),
|
|
706
|
-
},
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function medianCounterfactual(primary: FusionBudgetBlocker): FusionBudgetCounterfactuals['at_median_rate'] {
|
|
711
|
-
if (!primary.route.rate_source.backed) {
|
|
712
|
-
return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
713
|
-
}
|
|
714
|
-
const median = primary.route.rate_source.provenance.median_bpt_x1000;
|
|
715
|
-
if (median === null) return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
716
|
-
const variableTokens = ceilDiv(primary.input_utf8_bytes * 1000, median);
|
|
717
|
-
const tokens = variableTokens + TOKEN_BUDGET_AFFINE_F_TOKENS;
|
|
718
|
-
return {
|
|
719
|
-
forecast_input_tokens_upper_bound: tokens,
|
|
720
|
-
signed_headroom_tokens: primary.allowed_input_tokens - tokens,
|
|
721
|
-
fits: tokens <= primary.allowed_input_tokens,
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
function inputCounterfactuals(
|
|
726
|
-
primary: FusionBudgetBlocker,
|
|
727
|
-
plan: FusionBudgetPlanV1,
|
|
728
|
-
): FusionBudgetCounterfactuals {
|
|
729
|
-
return {
|
|
730
|
-
empty_request: plan.empty_request,
|
|
731
|
-
without_reservation: {
|
|
732
|
-
forecast_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
733
|
-
signed_headroom_tokens: primary.input_only_signed_headroom_tokens,
|
|
734
|
-
fits: primary.fits,
|
|
735
|
-
},
|
|
736
|
-
at_median_rate: medianCounterfactual(primary),
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function budgetErrorCode(
|
|
741
|
-
checkKind: FusionBudgetCheckKind,
|
|
742
|
-
): 'prompt_budget_exceeded_forecast' | 'prompt_budget_exceeded_measured' {
|
|
743
|
-
return checkKind === 'rendered_prompt'
|
|
744
|
-
? 'prompt_budget_exceeded_measured'
|
|
745
|
-
: 'prompt_budget_exceeded_forecast';
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
export class FusionBudget {
|
|
749
|
-
readonly routes: readonly FusionRouteCapacity[];
|
|
750
|
-
readonly limiting: FusionRouteCapacity;
|
|
751
|
-
private readonly contextPolicyId: string;
|
|
752
|
-
private readonly candidateCapability: FusionCapability;
|
|
753
|
-
private readonly profile: FusionWorkflowProfile;
|
|
754
|
-
|
|
755
|
-
constructor(
|
|
756
|
-
models: ResolvedFusionModels,
|
|
757
|
-
contextPolicyId: string,
|
|
758
|
-
candidateCapability: FusionCapability = FUSION_REASON_WORKFLOW.candidateCapability,
|
|
759
|
-
profile: FusionWorkflowProfile = FUSION_REASON_WORKFLOW,
|
|
760
|
-
) {
|
|
761
|
-
this.routes = fusionRouteCapacities(models);
|
|
762
|
-
this.limiting = fusionLimitingRoute(this.routes);
|
|
763
|
-
this.contextPolicyId = contextPolicyId;
|
|
764
|
-
this.candidateCapability = candidateCapability;
|
|
765
|
-
this.profile = profile;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
get allowedInputTokens(): number {
|
|
769
|
-
return this.limiting.allowed_input_tokens;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
get resultRateSources() {
|
|
773
|
-
return this.routes.map((route) => route.rate_source);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
get unknownProviderWarnings(): readonly string[] {
|
|
777
|
-
return this.routes.flatMap((route) => {
|
|
778
|
-
const warning = route.rate_source.warning;
|
|
779
|
-
return route.rate_source.backed || warning === null
|
|
780
|
-
? []
|
|
781
|
-
: [`${route.qualified_id}: ${warning}`];
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
private routeForStage(stage: FusionBudgetStage, slot?: 1 | 2 | 3): FusionRouteCapacity {
|
|
786
|
-
if (stage === 'candidate') return routeByRole(this.routes, candidateRole(slot ?? 1));
|
|
787
|
-
if (stage === 'merge') return routeByRole(this.routes, 'merger');
|
|
788
|
-
return routeByRole(this.routes, 'evaluator');
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
private drafts(input: FusionCanonicalInputV3): readonly StageForecastDraft[] {
|
|
792
|
-
const candidateSystemPrompt = this.profile.candidateSystemPrompt(this.candidateCapability);
|
|
793
|
-
const candidatePrompt = buildCandidatePrompt(input);
|
|
794
|
-
const blindInput = buildBlindEvaluationInput(input, EMPTY_CANDIDATES);
|
|
795
|
-
const evaluationPrompt = buildEvaluationPrompt(blindInput);
|
|
796
|
-
const repairPrompt = buildEvaluationRepairPrompt({
|
|
797
|
-
schema_version: 'pi-background-tasks.fusion-evaluation-repair-input.v1',
|
|
798
|
-
original_blind_input: blindInput,
|
|
799
|
-
invalid_output: '',
|
|
800
|
-
validation_errors: [],
|
|
801
|
-
});
|
|
802
|
-
const mergePrompt = buildMergePrompt(buildMergeInput(input, EMPTY_CANDIDATES, EMPTY_EVALUATION));
|
|
803
|
-
return [
|
|
804
|
-
{
|
|
805
|
-
budget_stage: 'candidate',
|
|
806
|
-
slot: 1,
|
|
807
|
-
route: this.routeForStage('candidate', 1),
|
|
808
|
-
conditional: false,
|
|
809
|
-
system_prompt: candidateSystemPrompt,
|
|
810
|
-
empty_user_prompt: candidatePrompt,
|
|
811
|
-
upstream_output_contract_bytes: 0,
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
budget_stage: 'candidate',
|
|
815
|
-
slot: 2,
|
|
816
|
-
route: this.routeForStage('candidate', 2),
|
|
817
|
-
conditional: false,
|
|
818
|
-
system_prompt: candidateSystemPrompt,
|
|
819
|
-
empty_user_prompt: candidatePrompt,
|
|
820
|
-
upstream_output_contract_bytes: 0,
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
budget_stage: 'candidate',
|
|
824
|
-
slot: 3,
|
|
825
|
-
route: this.routeForStage('candidate', 3),
|
|
826
|
-
conditional: false,
|
|
827
|
-
system_prompt: candidateSystemPrompt,
|
|
828
|
-
empty_user_prompt: candidatePrompt,
|
|
829
|
-
upstream_output_contract_bytes: 0,
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
budget_stage: 'evaluation',
|
|
833
|
-
route: this.routeForStage('evaluation'),
|
|
834
|
-
conditional: false,
|
|
835
|
-
system_prompt: this.profile.evaluatorSystemPrompt,
|
|
836
|
-
empty_user_prompt: evaluationPrompt,
|
|
837
|
-
upstream_output_contract_bytes: 3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES,
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
budget_stage: 'merge',
|
|
841
|
-
route: this.routeForStage('merge'),
|
|
842
|
-
conditional: false,
|
|
843
|
-
system_prompt: this.profile.mergerSystemPrompt,
|
|
844
|
-
empty_user_prompt: mergePrompt,
|
|
845
|
-
upstream_output_contract_bytes:
|
|
846
|
-
3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES + FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
847
|
-
},
|
|
848
|
-
{
|
|
849
|
-
budget_stage: 'evaluation_repair',
|
|
850
|
-
route: this.routeForStage('evaluation_repair'),
|
|
851
|
-
conditional: true,
|
|
852
|
-
system_prompt: this.profile.evaluationRepairSystemPrompt,
|
|
853
|
-
empty_user_prompt: repairPrompt,
|
|
854
|
-
upstream_output_contract_bytes:
|
|
855
|
-
3 * FUSION_CANDIDATE_MAX_OUTPUT_BYTES +
|
|
856
|
-
FUSION_EVALUATION_MAX_OUTPUT_BYTES +
|
|
857
|
-
FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
858
|
-
},
|
|
859
|
-
];
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
private entries(input: FusionCanonicalInputV3): readonly FusionStageBudgetPlanEntry[] {
|
|
863
|
-
return this.drafts(input).map(forecastEntry);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
private composition(
|
|
867
|
-
input: FusionCanonicalInputV3,
|
|
868
|
-
blocker: FusionBudgetBlocker,
|
|
869
|
-
): FusionBudgetStageComposition {
|
|
870
|
-
const canonicalBytes = utf8Bytes(buildCandidatePrompt(input));
|
|
871
|
-
const emptyRequestCanonicalBytes = utf8Bytes(buildCandidatePrompt(replaceRequestText(input, '')));
|
|
872
|
-
const requestBytes = canonicalBytes - emptyRequestCanonicalBytes;
|
|
873
|
-
const visible = visibleTextBytes(input);
|
|
874
|
-
const omissions = omissionReceiptBytes(input);
|
|
875
|
-
const projectionMetadata = canonicalBytes - requestBytes - visible - omissions;
|
|
876
|
-
const draft = this.drafts(input).find(
|
|
877
|
-
(item) => item.budget_stage === blocker.budget_stage && item.slot === blocker.slot,
|
|
878
|
-
);
|
|
879
|
-
if (draft === undefined) {
|
|
880
|
-
throw new FusionError('primary budget blocker disappeared during composition', {
|
|
881
|
-
code: 'orchestration_failed',
|
|
882
|
-
childCreated: false,
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
return {
|
|
886
|
-
visible_text_bytes: visible,
|
|
887
|
-
omission_receipt_bytes: omissions,
|
|
888
|
-
projection_metadata_bytes: projectionMetadata,
|
|
889
|
-
request_bytes: requestBytes,
|
|
890
|
-
static_stage_framing_bytes:
|
|
891
|
-
utf8Bytes(draft.system_prompt) + utf8Bytes(draft.empty_user_prompt) - canonicalBytes,
|
|
892
|
-
upstream_output_contract_bytes: draft.upstream_output_contract_bytes,
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
private emptyRequestVerdict(
|
|
897
|
-
input: FusionCanonicalInputV3,
|
|
898
|
-
entries: readonly FusionStageBudgetPlanEntry[],
|
|
899
|
-
): FusionBudgetEmptyRequestVerdict {
|
|
900
|
-
const emptyEntries = this.entries(replaceRequestText(input, ''));
|
|
901
|
-
const emptyBlockers = selectBlockers(emptyEntries);
|
|
902
|
-
let reduction = 0;
|
|
903
|
-
for (const entry of entries) {
|
|
904
|
-
reduction = Math.max(reduction, entry.input_utf8_bytes - maxKnownTextBytes(entry.route));
|
|
905
|
-
}
|
|
906
|
-
reduction = Math.max(0, reduction);
|
|
907
|
-
const requestBytes = utf8Bytes(input.request.text);
|
|
908
|
-
return {
|
|
909
|
-
request_utf8_bytes: requestBytes,
|
|
910
|
-
still_fails_with_empty_request: emptyBlockers.length > 0,
|
|
911
|
-
shortening_request_can_help: emptyBlockers.length === 0,
|
|
912
|
-
minimum_request_byte_reduction: reduction,
|
|
913
|
-
maximum_safe_request_utf8_bytes: Math.max(0, requestBytes - reduction),
|
|
914
|
-
blockers_with_empty_request: emptyBlockers,
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
private failure(
|
|
919
|
-
primary: FusionBudgetBlocker,
|
|
920
|
-
plan: FusionBudgetPlanV1,
|
|
921
|
-
artifactDir: string,
|
|
922
|
-
measurementKind: FusionBudgetErrorDetail['measurement_kind'],
|
|
923
|
-
checkKind: FusionBudgetCheckKind,
|
|
924
|
-
): FusionError {
|
|
925
|
-
const composition = plan.primary_blocker_composition;
|
|
926
|
-
const dominantByteClass = primary.input_only_estimate.rateSource.dominant_byte_class;
|
|
927
|
-
const remediation = dominantRemediation(
|
|
928
|
-
plan.empty_request,
|
|
929
|
-
composition,
|
|
930
|
-
dominantByteClass,
|
|
931
|
-
this.profile,
|
|
932
|
-
);
|
|
933
|
-
const tokensOver = primary.input_only_input_tokens_upper_bound - primary.allowed_input_tokens;
|
|
934
|
-
const budget: FusionBudgetErrorDetail = {
|
|
935
|
-
budget_stage: primary.budget_stage,
|
|
936
|
-
measurement_kind: measurementKind,
|
|
937
|
-
check_kind: checkKind,
|
|
938
|
-
measured_utf8_bytes: primary.input_utf8_bytes,
|
|
939
|
-
measured_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
940
|
-
allowed_input_tokens: primary.allowed_input_tokens,
|
|
941
|
-
limiting_model: {
|
|
942
|
-
provider: primary.route.provider,
|
|
943
|
-
model: primary.route.model,
|
|
944
|
-
qualified_id: primary.route.qualified_id,
|
|
945
|
-
context_window_tokens: primary.route.context_window_tokens,
|
|
946
|
-
},
|
|
947
|
-
rate_source: primary.input_only_estimate.rateSource,
|
|
948
|
-
backed: primary.input_only_estimate.rateSource.backed,
|
|
949
|
-
dominant_byte_class: dominantByteClass,
|
|
950
|
-
component_breakdown: componentBreakdown(composition, primary.route),
|
|
951
|
-
byte_class_breakdown: primary.input_only_estimate.byte_class_breakdown,
|
|
952
|
-
dense_regions: [],
|
|
953
|
-
bytes_over: primary.bytes_over,
|
|
954
|
-
tokens_over: Math.max(0, tokensOver),
|
|
955
|
-
required_allowed_tokens: primary.input_only_input_tokens_upper_bound,
|
|
956
|
-
route_table: routeTable(this.routes),
|
|
957
|
-
counterfactuals: inputCounterfactuals(primary, plan),
|
|
958
|
-
stage_upstream_actuals: [],
|
|
959
|
-
policy_id: FUSION_BUDGET_POLICY.id,
|
|
960
|
-
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
961
|
-
context_policy_id: this.contextPolicyId,
|
|
962
|
-
remediation,
|
|
963
|
-
blockers: plan.blockers,
|
|
964
|
-
artifact_dir: artifactDir,
|
|
965
|
-
};
|
|
966
|
-
if (primary.slot !== undefined) budget.slot = primary.slot;
|
|
967
|
-
const compositionText = composition === undefined ? 'unavailable' : formatComposition(composition);
|
|
968
|
-
const additional = plan.blockers
|
|
969
|
-
.filter((blocker) => blocker !== primary)
|
|
970
|
-
.map((blocker) => `${entryLabel(blocker)} route=${blocker.route.qualified_id}`)
|
|
971
|
-
.join('; ');
|
|
972
|
-
const rateText = `${primary.route.family} ${formatRateX100(primary.input_only_estimate.rateSource.effective_rate_bytes_per_token_x100)} B/tok + ${String(primary.input_only_estimate.rateSource.affine_f_tokens)} tokens (${primary.input_only_estimate.rateSource.source}, backed=${String(primary.input_only_estimate.rateSource.backed)}, dominant=${dominantByteClass})`;
|
|
973
|
-
const sourceWarning = primary.input_only_estimate.rateSource.warning;
|
|
974
|
-
const routeWarning = sourceWarning === null ? '' : ` Rate warning: ${sourceWarning}.`;
|
|
975
|
-
const checkText =
|
|
976
|
-
checkKind === 'input_only_preflight'
|
|
977
|
-
? 'input-only preflight forecast'
|
|
978
|
-
: 'exact rendered prompt measurement';
|
|
979
|
-
const dominantText =
|
|
980
|
-
dominantByteClass === 'multibyte'
|
|
981
|
-
? ' Dominant byte class is multibyte UTF-8; the fatal gate uses the conservative multibyte rate and the plan separately records the provable 1.00 B/tok advisory ceiling.'
|
|
982
|
-
: dominantByteClass === 'dense_ascii'
|
|
983
|
-
? ' Dominant byte class is dense ASCII/low-whitespace content; the whitespace gate is a heuristic token-density proxy, not a bound.'
|
|
984
|
-
: ` Dominant byte class is ${dominantByteClass}.`;
|
|
985
|
-
const message =
|
|
986
|
-
`Fusion prompt budget exceeded by ${checkText} before ${blockingChildLabel(primary)} child creation. Primary blocking stage: ${entryLabel(primary)} on route ${primary.route.qualified_id}. ` +
|
|
987
|
-
`Forecast ${String(primary.input_utf8_bytes)} UTF-8 bytes (<= ${String(primary.input_only_input_tokens_upper_bound)} input tokens) against ${String(primary.allowed_input_tokens)} allowed input tokens, over by ${String(Math.max(0, tokensOver))} tokens. ` +
|
|
988
|
-
`Estimator: ${rateText}.${routeWarning}${dominantText} ` +
|
|
989
|
-
`The ${blockingChildLabel(primary)} child was not created. Nothing was clipped, dropped, or substituted. Artifact directory: ${artifactDir}.\n` +
|
|
990
|
-
`Per-stage forecast table:\n${formatTable(plan.stages)}\n` +
|
|
991
|
-
`Primary blocker byte composition: ${compositionText}.\n` +
|
|
992
|
-
`Additional blockers: ${additional.length === 0 ? 'none' : additional}.\n` +
|
|
993
|
-
`${formatEmptyRequestVerdict(plan.empty_request)}\n` +
|
|
994
|
-
`Route byte-capacity order: ${routeTable(this.routes).map((route) => `${route.qualified_id}=${String(route.byte_capacity_utf8_bytes)}B`).join(', ')}.\n` +
|
|
995
|
-
`Remediation: ${remediation.join(' ')}`;
|
|
996
|
-
const details = {
|
|
997
|
-
code: budgetErrorCode(checkKind),
|
|
998
|
-
childCreated: false,
|
|
999
|
-
budget,
|
|
1000
|
-
stage: stageFromBudgetStage(primary.budget_stage),
|
|
1001
|
-
};
|
|
1002
|
-
if (primary.slot !== undefined) return new FusionError(message, { ...details, slot: primary.slot });
|
|
1003
|
-
return new FusionError(message, details);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
private planMetadata(input?: FusionCanonicalInputV3): Pick<FusionBudgetPlanV1, 'workflow' | 'context' | 'fixed_candidate_policy' | 'tool_policy'> {
|
|
1007
|
-
return {
|
|
1008
|
-
workflow: this.profile.id,
|
|
1009
|
-
context: {
|
|
1010
|
-
kind: this.profile.contextKind,
|
|
1011
|
-
policy_id: input?.context?.policy_id ?? this.contextPolicyId,
|
|
1012
|
-
},
|
|
1013
|
-
fixed_candidate_policy: {
|
|
1014
|
-
capability: this.candidateCapability,
|
|
1015
|
-
tools: this.profile.candidateTools,
|
|
1016
|
-
},
|
|
1017
|
-
tool_policy: {
|
|
1018
|
-
candidate_tools: this.profile.candidateTools,
|
|
1019
|
-
evaluation_tools: [] as readonly [],
|
|
1020
|
-
merge_tools: [] as readonly [],
|
|
1021
|
-
},
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
plan(input: FusionCanonicalInputV3): FusionBudgetPlanV1 {
|
|
1026
|
-
const stages = this.entries(input);
|
|
1027
|
-
const blockers = selectBlockers(stages);
|
|
1028
|
-
const primary = selectPrimaryBlocker(blockers);
|
|
1029
|
-
const emptyRequest = this.emptyRequestVerdict(input, stages);
|
|
1030
|
-
const base: FusionBudgetPlanV1 = {
|
|
1031
|
-
schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
1032
|
-
...this.planMetadata(input),
|
|
1033
|
-
policy: FUSION_BUDGET_POLICY,
|
|
1034
|
-
routes: this.routes,
|
|
1035
|
-
stages,
|
|
1036
|
-
blockers,
|
|
1037
|
-
empty_request: emptyRequest,
|
|
1038
|
-
warnings: warningsFor(stages),
|
|
1039
|
-
};
|
|
1040
|
-
if (primary === undefined) return base;
|
|
1041
|
-
return {
|
|
1042
|
-
...base,
|
|
1043
|
-
primary_blocker: primary,
|
|
1044
|
-
primary_blocker_composition: this.composition(input, primary),
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
assertPlanFits(plan: FusionBudgetPlanV1, artifactDir: string): void {
|
|
1049
|
-
if (plan.primary_blocker !== undefined) {
|
|
1050
|
-
throw this.failure(
|
|
1051
|
-
plan.primary_blocker,
|
|
1052
|
-
plan,
|
|
1053
|
-
artifactDir,
|
|
1054
|
-
'stage_forecast',
|
|
1055
|
-
'input_only_preflight',
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
assertStagePrompt(
|
|
1061
|
-
stage: FusionBudgetStage,
|
|
1062
|
-
systemPrompt: string,
|
|
1063
|
-
userPrompt: string,
|
|
1064
|
-
slot?: 1 | 2 | 3,
|
|
1065
|
-
): void {
|
|
1066
|
-
const route = this.routeForStage(stage, slot);
|
|
1067
|
-
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1068
|
-
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1069
|
-
const estimate = estimateRouteInput(route, inputSegments);
|
|
1070
|
-
if (estimate.tokens <= route.allowed_input_tokens) return;
|
|
1071
|
-
const entry: FusionStageBudgetPlanEntry = {
|
|
1072
|
-
budget_stage: stage,
|
|
1073
|
-
route,
|
|
1074
|
-
conditional: stage === 'evaluation_repair',
|
|
1075
|
-
check_kind: 'input_only_preflight',
|
|
1076
|
-
input_utf8_bytes: inputBytes,
|
|
1077
|
-
upstream_output_contract_bytes: 0,
|
|
1078
|
-
forecast_utf8_bytes: inputBytes,
|
|
1079
|
-
input_only_input_tokens_upper_bound: estimate.tokens,
|
|
1080
|
-
forecast_input_tokens_upper_bound: estimate.tokens,
|
|
1081
|
-
allowed_input_tokens: route.allowed_input_tokens,
|
|
1082
|
-
input_only_signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
1083
|
-
signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
1084
|
-
input_only_utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
1085
|
-
utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
1086
|
-
input_only_estimate: estimate,
|
|
1087
|
-
reservation_estimate: estimate,
|
|
1088
|
-
fits: false,
|
|
1089
|
-
reservation_fits: false,
|
|
1090
|
-
};
|
|
1091
|
-
if (slot !== undefined) entry.slot = slot;
|
|
1092
|
-
const blocker = blockerFromEntry(entry);
|
|
1093
|
-
const plan: FusionBudgetPlanV1 = {
|
|
1094
|
-
schema_version: FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
1095
|
-
...this.planMetadata(),
|
|
1096
|
-
policy: FUSION_BUDGET_POLICY,
|
|
1097
|
-
routes: this.routes,
|
|
1098
|
-
stages: [entry],
|
|
1099
|
-
blockers: [blocker],
|
|
1100
|
-
primary_blocker: blocker,
|
|
1101
|
-
empty_request: {
|
|
1102
|
-
request_utf8_bytes: 0,
|
|
1103
|
-
still_fails_with_empty_request: true,
|
|
1104
|
-
shortening_request_can_help: false,
|
|
1105
|
-
minimum_request_byte_reduction: blocker.bytes_over,
|
|
1106
|
-
maximum_safe_request_utf8_bytes: 0,
|
|
1107
|
-
blockers_with_empty_request: [blocker],
|
|
1108
|
-
},
|
|
1109
|
-
warnings: [],
|
|
1110
|
-
};
|
|
1111
|
-
throw this.failure(blocker, plan, 'stage prompt re-measurement', 'rendered_prompt', 'rendered_prompt');
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
calibrationViolationForCompletedChild(
|
|
1115
|
-
stage: FusionStage,
|
|
1116
|
-
systemPrompt: string,
|
|
1117
|
-
userPrompt: string,
|
|
1118
|
-
result: FusionChildRunResult,
|
|
1119
|
-
slot?: 1 | 2 | 3,
|
|
1120
|
-
): FusionCalibrationViolation | undefined {
|
|
1121
|
-
const route = this.routeForStage(stage, slot);
|
|
1122
|
-
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1123
|
-
const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1124
|
-
const estimate = estimateRouteInput(route, inputSegments);
|
|
1125
|
-
// The forecast is a one-request admission estimate. Compare it only with
|
|
1126
|
-
// the first provider request, never with aggregate agent-loop/cache usage.
|
|
1127
|
-
// Custom child runners predating this observation field remain compatible,
|
|
1128
|
-
// but cannot produce a calibration verdict without like-for-like evidence.
|
|
1129
|
-
const observedUsage = result.firstRequestUsage;
|
|
1130
|
-
if (observedUsage === undefined) return undefined;
|
|
1131
|
-
const billedInput = observedUsage.input + observedUsage.cacheRead + observedUsage.cacheWrite;
|
|
1132
|
-
if (billedInput <= estimate.tokens) return undefined;
|
|
1133
|
-
const violation: FusionCalibrationViolation = {
|
|
1134
|
-
schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
1135
|
-
stage,
|
|
1136
|
-
attempt: result.attempt,
|
|
1137
|
-
route: {
|
|
1138
|
-
provider: result.provider,
|
|
1139
|
-
model: result.model,
|
|
1140
|
-
qualified_id: result.qualifiedId,
|
|
1141
|
-
},
|
|
1142
|
-
family: route.family,
|
|
1143
|
-
rate_source: estimate.rateSource,
|
|
1144
|
-
prompt_utf8_bytes: promptUtf8Bytes,
|
|
1145
|
-
prompt_sha256: sha256Hex(`${systemPrompt}\u0000${userPrompt}`),
|
|
1146
|
-
observation_scope: 'first_provider_request',
|
|
1147
|
-
provider_request_count: result.providerRequestCount ?? 1,
|
|
1148
|
-
forecast_input_tokens: estimate.tokens,
|
|
1149
|
-
billed_input_tokens: billedInput,
|
|
1150
|
-
billed_input_breakdown: {
|
|
1151
|
-
input: observedUsage.input,
|
|
1152
|
-
cache_read: observedUsage.cacheRead,
|
|
1153
|
-
cache_write: observedUsage.cacheWrite,
|
|
1154
|
-
},
|
|
1155
|
-
under_forecast_tokens: billedInput - estimate.tokens,
|
|
1156
|
-
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
1157
|
-
dominant_byte_class: estimate.rateSource.dominant_byte_class,
|
|
1158
|
-
};
|
|
1159
|
-
if (slot !== undefined) violation.slot = slot;
|
|
1160
|
-
return violation;
|
|
1161
|
-
}
|
|
1162
|
-
}
|