pi-background-tasks 0.7.6 → 0.7.7
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/PUBLISHING.md +7 -7
- package/README.md +177 -13
- package/TESTING.md +94 -0
- package/TEST_PLAN.md +21 -4
- package/extensions/delegate-child.ts +1 -0
- package/package.json +4 -3
- package/src/core/common.ts +41 -0
- package/src/core/context/parent-snapshot.ts +142 -0
- package/src/core/context/token-budget.ts +890 -0
- package/src/core/context/visible-conversation-v2.ts +551 -0
- package/src/core/delegate/artifacts.ts +479 -0
- package/src/core/delegate/budget.ts +370 -0
- package/src/core/delegate/hook-contract-evidence.json +18 -0
- package/src/core/delegate/hook-contract.ts +153 -0
- package/src/core/delegate/launch.ts +459 -0
- package/src/core/delegate/result-package.ts +443 -0
- package/src/core/delegate/runner.ts +406 -0
- package/src/core/delegate/seed.ts +411 -0
- package/src/core/delegate/types.ts +304 -0
- package/src/core/fusion/artifacts.ts +15 -0
- package/src/core/fusion/budget.ts +444 -54
- package/src/core/fusion/context.ts +108 -509
- package/src/core/fusion/orchestrator.ts +116 -5
- package/src/core/fusion/prompts.ts +5 -1
- package/src/core/fusion/types.ts +154 -36
- package/src/core/registry.ts +174 -0
- package/src/delegate-child-extension.ts +673 -0
- package/src/delegate-extension.ts +587 -0
- package/src/extension.ts +10 -0
- package/src/fusion-extension.ts +2 -0
|
@@ -1,4 +1,18 @@
|
|
|
1
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';
|
|
2
16
|
import {
|
|
3
17
|
FUSION_CANDIDATE_SYSTEM_PROMPT,
|
|
4
18
|
FUSION_EVALUATION_REPAIR_SYSTEM_PROMPT,
|
|
@@ -14,36 +28,59 @@ import {
|
|
|
14
28
|
} from './prompts.js';
|
|
15
29
|
import {
|
|
16
30
|
FUSION_BUDGET_PLAN_SCHEMA_VERSION,
|
|
31
|
+
FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
17
32
|
FUSION_EVALUATION_SCHEMA_VERSION,
|
|
18
33
|
FusionError,
|
|
19
34
|
type FusionBudgetBlocker,
|
|
35
|
+
type FusionBudgetCheckKind,
|
|
36
|
+
type FusionBudgetComponentBreakdown,
|
|
37
|
+
type FusionBudgetCounterfactuals,
|
|
20
38
|
type FusionBudgetEmptyRequestVerdict,
|
|
21
39
|
type FusionBudgetErrorDetail,
|
|
22
40
|
type FusionBudgetPlanV1,
|
|
23
41
|
type FusionBudgetPolicyDescriptor,
|
|
42
|
+
type FusionBudgetRouteTableEntry,
|
|
24
43
|
type FusionBudgetStage,
|
|
25
44
|
type FusionBudgetStageComposition,
|
|
26
45
|
type FusionBudgetWarning,
|
|
46
|
+
type FusionCalibrationViolation,
|
|
27
47
|
type FusionCanonicalInputV3,
|
|
28
48
|
type FusionEvaluationV1,
|
|
29
49
|
type FusionRouteCapacity,
|
|
30
50
|
type FusionStage,
|
|
31
51
|
type FusionStageBudgetPlanEntry,
|
|
52
|
+
type FusionChildRunResult,
|
|
32
53
|
type ResolvedFusionModel,
|
|
33
54
|
type ResolvedFusionModels,
|
|
34
55
|
} from './types.js';
|
|
35
56
|
|
|
36
|
-
export const
|
|
57
|
+
export const FUSION_CALIBRATED_BYTES_PER_TOKEN = TOKEN_BUDGET_FAMILY_CALIBRATIONS;
|
|
37
58
|
|
|
38
59
|
export const FUSION_CANDIDATE_MAX_OUTPUT_BYTES = 48 * 1024;
|
|
39
60
|
export const FUSION_EVALUATION_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
40
61
|
export const FUSION_MERGE_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
41
62
|
export const FUSION_DIAGNOSTICS_MAX_BYTES = 8 * 1024;
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
const FUSION_OUTPUT_RESERVE_RATE_X100 = 200;
|
|
65
|
+
export const FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS = 8000;
|
|
66
|
+
const BASIS_POINTS_DENOMINATOR = 10_000;
|
|
67
|
+
|
|
68
|
+
function ceilDiv(numerator: number, denominator: number): number {
|
|
69
|
+
if (!Number.isSafeInteger(numerator) || numerator < 0) {
|
|
70
|
+
throw new TypeError('ceilDiv numerator must be a non-negative safe integer');
|
|
71
|
+
}
|
|
72
|
+
if (!Number.isSafeInteger(denominator) || denominator <= 0) {
|
|
73
|
+
throw new TypeError('ceilDiv denominator must be a positive safe integer');
|
|
74
|
+
}
|
|
75
|
+
if (numerator === 0) return 0;
|
|
76
|
+
return Math.floor((numerator - 1) / denominator) + 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const FUSION_RESERVED_OUTPUT_TOKENS = ceilDiv(
|
|
80
|
+
FUSION_MERGE_MAX_OUTPUT_BYTES * TOKEN_BUDGET_RATE_SCALE,
|
|
81
|
+
FUSION_OUTPUT_RESERVE_RATE_X100,
|
|
45
82
|
);
|
|
46
|
-
export const FUSION_FRAMING_RESERVE_TOKENS =
|
|
83
|
+
export const FUSION_FRAMING_RESERVE_TOKENS = 0;
|
|
47
84
|
export const FUSION_SAFETY_RESERVE_TOKENS = 4_096;
|
|
48
85
|
export const FUSION_MIN_CANONICAL_INPUT_TOKENS = 8_192;
|
|
49
86
|
export const FUSION_MIN_CONTEXT_WINDOW_TOKENS =
|
|
@@ -51,11 +88,11 @@ export const FUSION_MIN_CONTEXT_WINDOW_TOKENS =
|
|
|
51
88
|
FUSION_RESERVED_OUTPUT_TOKENS +
|
|
52
89
|
FUSION_FRAMING_RESERVE_TOKENS +
|
|
53
90
|
FUSION_SAFETY_RESERVE_TOKENS;
|
|
54
|
-
export const FUSION_UTILIZATION_WARNING_THRESHOLD = 0.8;
|
|
55
91
|
|
|
56
92
|
export const FUSION_BUDGET_POLICY: FusionBudgetPolicyDescriptor = {
|
|
57
|
-
id: 'fusion-budget-policy-
|
|
58
|
-
|
|
93
|
+
id: 'fusion-budget-policy-v3',
|
|
94
|
+
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
95
|
+
calibration_table: FUSION_CALIBRATED_BYTES_PER_TOKEN,
|
|
59
96
|
reserved_output_tokens: FUSION_RESERVED_OUTPUT_TOKENS,
|
|
60
97
|
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
61
98
|
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
@@ -63,7 +100,7 @@ export const FUSION_BUDGET_POLICY: FusionBudgetPolicyDescriptor = {
|
|
|
63
100
|
evaluation_output_contract_bytes: FUSION_EVALUATION_MAX_OUTPUT_BYTES,
|
|
64
101
|
merge_output_contract_bytes: FUSION_MERGE_MAX_OUTPUT_BYTES,
|
|
65
102
|
diagnostics_contract_bytes: FUSION_DIAGNOSTICS_MAX_BYTES,
|
|
66
|
-
|
|
103
|
+
utilization_warning_threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
67
104
|
};
|
|
68
105
|
|
|
69
106
|
const EMPTY_REMEDIATION: readonly string[] = Object.freeze([
|
|
@@ -78,6 +115,23 @@ const REQUEST_REMEDIATION: readonly string[] = Object.freeze([
|
|
|
78
115
|
"Raise the route's context window with a larger-context model via /fusion-models.",
|
|
79
116
|
]);
|
|
80
117
|
|
|
118
|
+
const RESERVATION_REMEDIATION: readonly string[] = Object.freeze([
|
|
119
|
+
'Route the blocking stage to a model with larger byte capacity.',
|
|
120
|
+
'Keep producer output contracts intact; do not shrink or truncate child answers.',
|
|
121
|
+
'Inspect budget-plan.json for the advisory reservation component before retrying.',
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const DENSE_REMEDIATION: readonly string[] = Object.freeze([
|
|
125
|
+
'Remove or externalize low-whitespace dense ASCII payloads (base64, minified code, PEM/hex blocks), then retry.',
|
|
126
|
+
'The prompt is preserved; no content was clipped to fit.',
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const MULTIBYTE_REMEDIATION: readonly string[] = Object.freeze([
|
|
130
|
+
'This rejection is dominated by multibyte UTF-8 content; use a larger-context route or split the non-Latin/CJK-heavy task.',
|
|
131
|
+
'The budget plan includes the stricter multibyte advisory ceiling separately from the fatal estimate.',
|
|
132
|
+
'The prompt is preserved; no content was clipped to fit.',
|
|
133
|
+
]);
|
|
134
|
+
|
|
81
135
|
const EMPTY_CANDIDATES: readonly [
|
|
82
136
|
AnonymousFusionCandidate,
|
|
83
137
|
AnonymousFusionCandidate,
|
|
@@ -140,7 +194,11 @@ interface StageForecastDraft {
|
|
|
140
194
|
}
|
|
141
195
|
|
|
142
196
|
export function fusionTokenUpperBound(utf8Bytes: number): number {
|
|
143
|
-
return
|
|
197
|
+
return estimateInputTokens({
|
|
198
|
+
family: 'unknown',
|
|
199
|
+
scope: 'conservative',
|
|
200
|
+
segments: [{ kind: 'known_text', bytes: utf8Bytes, multibyteBytes: 0, denseBytes: 0 }],
|
|
201
|
+
}).tokens;
|
|
144
202
|
}
|
|
145
203
|
|
|
146
204
|
export function fusionOutputContractBytes(stage: FusionStage): number {
|
|
@@ -169,7 +227,7 @@ function sha256Hex(value: string): string {
|
|
|
169
227
|
|
|
170
228
|
function requirePositiveContextWindow(model: ResolvedFusionModel, role: string): number {
|
|
171
229
|
const value = model.contextWindow;
|
|
172
|
-
if (!
|
|
230
|
+
if (!isUsableContextWindow(value)) {
|
|
173
231
|
throw new FusionError(
|
|
174
232
|
`fusion ${role} route ${model.qualifiedId} has no usable context window capacity`,
|
|
175
233
|
{ code: 'model_capacity_unknown', childCreated: false },
|
|
@@ -178,22 +236,45 @@ function requirePositiveContextWindow(model: ResolvedFusionModel, role: string):
|
|
|
178
236
|
return value;
|
|
179
237
|
}
|
|
180
238
|
|
|
239
|
+
function formatRateX100(value: number): string {
|
|
240
|
+
const whole = Math.floor(value / TOKEN_BUDGET_RATE_SCALE);
|
|
241
|
+
const frac = String(value % TOKEN_BUDGET_RATE_SCALE).padStart(2, '0');
|
|
242
|
+
return `${String(whole)}.${frac}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
181
245
|
function routeCapacity(
|
|
182
246
|
model: ResolvedFusionModel,
|
|
183
247
|
role: FusionRouteCapacity['role'],
|
|
184
248
|
): FusionRouteCapacity {
|
|
185
249
|
const contextWindow = requirePositiveContextWindow(model, role);
|
|
186
|
-
const allowed =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
250
|
+
const allowed = allowedInputTokens(contextWindow, {
|
|
251
|
+
reservedOutputTokens: FUSION_RESERVED_OUTPUT_TOKENS,
|
|
252
|
+
framingReserveTokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
253
|
+
safetyReserveTokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
254
|
+
});
|
|
191
255
|
if (allowed < FUSION_MIN_CANONICAL_INPUT_TOKENS) {
|
|
192
256
|
throw new FusionError(
|
|
193
257
|
`fusion ${role} route ${model.qualifiedId} has a ${String(contextWindow)}-token context window, but Fusion requires at least ${String(FUSION_MIN_CONTEXT_WINDOW_TOKENS)} tokens per configured route: ${String(FUSION_RESERVED_OUTPUT_TOKENS)} 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 model for this slot with /fusion-models.`,
|
|
194
258
|
{ code: 'model_capacity_unknown', childCreated: false },
|
|
195
259
|
);
|
|
196
260
|
}
|
|
261
|
+
const family = resolveTokenBudgetFamily({ provider: model.provider, model: model.model });
|
|
262
|
+
const rateSource = estimateInputTokens({
|
|
263
|
+
family: family.family,
|
|
264
|
+
calibrationBacked: family.backed,
|
|
265
|
+
familyResolution: family.resolution,
|
|
266
|
+
allowedInputTokens: allowed,
|
|
267
|
+
scope: 'fusion',
|
|
268
|
+
segments: [
|
|
269
|
+
{
|
|
270
|
+
kind: 'known_text',
|
|
271
|
+
bytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
272
|
+
multibyteBytes: 0,
|
|
273
|
+
denseBytes: 0,
|
|
274
|
+
asciiWhitespaceBytes: TOKEN_BUDGET_LARGE_PROMPT_MIN_BYTES,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
}).rateSource;
|
|
197
278
|
return {
|
|
198
279
|
role,
|
|
199
280
|
provider: model.provider,
|
|
@@ -204,6 +285,11 @@ function routeCapacity(
|
|
|
204
285
|
framing_reserve_tokens: FUSION_FRAMING_RESERVE_TOKENS,
|
|
205
286
|
safety_reserve_tokens: FUSION_SAFETY_RESERVE_TOKENS,
|
|
206
287
|
allowed_input_tokens: allowed,
|
|
288
|
+
family: family.family,
|
|
289
|
+
rate_source: rateSource,
|
|
290
|
+
byte_capacity_utf8_bytes: Math.floor(
|
|
291
|
+
(allowed * rateSource.effective_rate_bytes_per_token_x100) / TOKEN_BUDGET_RATE_SCALE,
|
|
292
|
+
),
|
|
207
293
|
};
|
|
208
294
|
}
|
|
209
295
|
|
|
@@ -222,7 +308,12 @@ export function fusionLimitingRoute(
|
|
|
222
308
|
): FusionRouteCapacity {
|
|
223
309
|
let limiting: FusionRouteCapacity | undefined;
|
|
224
310
|
for (const route of routes) {
|
|
225
|
-
if (
|
|
311
|
+
if (
|
|
312
|
+
limiting === undefined ||
|
|
313
|
+
route.byte_capacity_utf8_bytes < limiting.byte_capacity_utf8_bytes ||
|
|
314
|
+
(route.byte_capacity_utf8_bytes === limiting.byte_capacity_utf8_bytes &&
|
|
315
|
+
route.role.localeCompare(limiting.role) < 0)
|
|
316
|
+
) {
|
|
226
317
|
limiting = route;
|
|
227
318
|
}
|
|
228
319
|
}
|
|
@@ -255,29 +346,80 @@ function candidateRole(slot: 1 | 2 | 3): FusionRouteCapacity['role'] {
|
|
|
255
346
|
return 'candidate-3';
|
|
256
347
|
}
|
|
257
348
|
|
|
349
|
+
function estimateRouteInput(
|
|
350
|
+
route: FusionRouteCapacity,
|
|
351
|
+
segments: Parameters<typeof estimateInputTokens>[0]['segments'],
|
|
352
|
+
) {
|
|
353
|
+
return estimateInputTokens({
|
|
354
|
+
family: route.family,
|
|
355
|
+
calibrationBacked: route.rate_source.backed,
|
|
356
|
+
familyResolution: route.rate_source.model_resolution,
|
|
357
|
+
allowedInputTokens: route.allowed_input_tokens,
|
|
358
|
+
scope: 'fusion',
|
|
359
|
+
segments,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function utilizationBasisPoints(tokens: number, allowed: number): number {
|
|
364
|
+
return ceilDiv(tokens * BASIS_POINTS_DENOMINATOR, allowed);
|
|
365
|
+
}
|
|
366
|
+
|
|
258
367
|
function forecastEntry(draft: StageForecastDraft): FusionStageBudgetPlanEntry {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
368
|
+
const inputSegments = [knownTextSegment(draft.system_prompt), knownTextSegment(draft.empty_user_prompt)];
|
|
369
|
+
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
370
|
+
const inputOnly = estimateRouteInput(draft.route, inputSegments);
|
|
371
|
+
const reservationSegments =
|
|
372
|
+
draft.upstream_output_contract_bytes === 0
|
|
373
|
+
? inputSegments
|
|
374
|
+
: [...inputSegments, unknownOutputContractSegment(draft.upstream_output_contract_bytes)];
|
|
375
|
+
const reservation = estimateRouteInput(draft.route, reservationSegments);
|
|
376
|
+
const forecastUtf8Bytes = inputBytes + draft.upstream_output_contract_bytes;
|
|
264
377
|
const entry: FusionStageBudgetPlanEntry = {
|
|
265
378
|
budget_stage: draft.budget_stage,
|
|
266
379
|
route: draft.route,
|
|
267
380
|
conditional: draft.conditional,
|
|
381
|
+
check_kind: 'input_only_preflight',
|
|
382
|
+
input_utf8_bytes: inputBytes,
|
|
383
|
+
upstream_output_contract_bytes: draft.upstream_output_contract_bytes,
|
|
268
384
|
forecast_utf8_bytes: forecastUtf8Bytes,
|
|
269
|
-
|
|
385
|
+
input_only_input_tokens_upper_bound: inputOnly.tokens,
|
|
386
|
+
forecast_input_tokens_upper_bound: reservation.tokens,
|
|
270
387
|
allowed_input_tokens: draft.route.allowed_input_tokens,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
388
|
+
input_only_signed_headroom_tokens: draft.route.allowed_input_tokens - inputOnly.tokens,
|
|
389
|
+
signed_headroom_tokens: draft.route.allowed_input_tokens - reservation.tokens,
|
|
390
|
+
input_only_utilization_basis_points: utilizationBasisPoints(
|
|
391
|
+
inputOnly.tokens,
|
|
392
|
+
draft.route.allowed_input_tokens,
|
|
393
|
+
),
|
|
394
|
+
utilization_basis_points: utilizationBasisPoints(
|
|
395
|
+
reservation.tokens,
|
|
396
|
+
draft.route.allowed_input_tokens,
|
|
397
|
+
),
|
|
398
|
+
input_only_estimate: inputOnly,
|
|
399
|
+
reservation_estimate: reservation,
|
|
400
|
+
fits: inputOnly.tokens <= draft.route.allowed_input_tokens,
|
|
401
|
+
reservation_fits: reservation.tokens <= draft.route.allowed_input_tokens,
|
|
274
402
|
};
|
|
275
403
|
if (draft.slot !== undefined) entry.slot = draft.slot;
|
|
276
404
|
return entry;
|
|
277
405
|
}
|
|
278
406
|
|
|
407
|
+
function maxKnownTextBytes(route: FusionRouteCapacity): number {
|
|
408
|
+
return maxKnownTextBytesForTokens({
|
|
409
|
+
family: route.family,
|
|
410
|
+
calibrationBacked: route.rate_source.backed,
|
|
411
|
+
familyResolution: route.rate_source.model_resolution,
|
|
412
|
+
allowedInputTokens: route.allowed_input_tokens,
|
|
413
|
+
scope: 'fusion',
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
279
417
|
function blockerFromEntry(entry: FusionStageBudgetPlanEntry): FusionBudgetBlocker {
|
|
280
|
-
return {
|
|
418
|
+
return {
|
|
419
|
+
...entry,
|
|
420
|
+
overage_tokens: Math.max(0, entry.input_only_input_tokens_upper_bound - entry.allowed_input_tokens),
|
|
421
|
+
bytes_over: Math.max(0, entry.input_utf8_bytes - maxKnownTextBytes(entry.route)),
|
|
422
|
+
};
|
|
281
423
|
}
|
|
282
424
|
|
|
283
425
|
function blockerOrder(blocker: FusionBudgetBlocker): number {
|
|
@@ -316,7 +458,7 @@ function replaceRequestText(input: FusionCanonicalInputV3, text: string): Fusion
|
|
|
316
458
|
function visibleTextBytes(input: FusionCanonicalInputV3): number {
|
|
317
459
|
let total = 0;
|
|
318
460
|
for (const entry of input.conversation_projection.entries) {
|
|
319
|
-
if (entry
|
|
461
|
+
if (entry[0] === 't') total += utf8Bytes(JSON.stringify(entry[4]));
|
|
320
462
|
}
|
|
321
463
|
return total;
|
|
322
464
|
}
|
|
@@ -324,19 +466,35 @@ function visibleTextBytes(input: FusionCanonicalInputV3): number {
|
|
|
324
466
|
function omissionReceiptBytes(input: FusionCanonicalInputV3): number {
|
|
325
467
|
let total = 0;
|
|
326
468
|
for (const entry of input.conversation_projection.entries) {
|
|
327
|
-
if (entry
|
|
469
|
+
if (entry[0] === 'o') total += utf8Bytes(JSON.stringify(entry));
|
|
328
470
|
}
|
|
329
471
|
return total;
|
|
330
472
|
}
|
|
331
473
|
|
|
332
|
-
function warningFromEntry(entry: FusionStageBudgetPlanEntry): FusionBudgetWarning {
|
|
333
|
-
|
|
474
|
+
function warningFromEntry(entry: FusionStageBudgetPlanEntry): FusionBudgetWarning | undefined {
|
|
475
|
+
if (!entry.fits) return undefined;
|
|
476
|
+
if (!entry.reservation_fits) {
|
|
477
|
+
return {
|
|
478
|
+
...entry,
|
|
479
|
+
warning_kind: 'worst_case_reservation',
|
|
480
|
+
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (entry.utilization_basis_points >= FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS) {
|
|
484
|
+
return {
|
|
485
|
+
...entry,
|
|
486
|
+
warning_kind: 'input_utilization',
|
|
487
|
+
threshold_basis_points: FUSION_UTILIZATION_WARNING_THRESHOLD_BASIS_POINTS,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return undefined;
|
|
334
491
|
}
|
|
335
492
|
|
|
336
493
|
function warningsFor(entries: readonly FusionStageBudgetPlanEntry[]): readonly FusionBudgetWarning[] {
|
|
337
|
-
return entries
|
|
338
|
-
|
|
339
|
-
|
|
494
|
+
return entries.flatMap((entry) => {
|
|
495
|
+
const warning = warningFromEntry(entry);
|
|
496
|
+
return warning === undefined ? [] : [warning];
|
|
497
|
+
});
|
|
340
498
|
}
|
|
341
499
|
|
|
342
500
|
function entryLabel(entry: FusionStageBudgetPlanEntry): string {
|
|
@@ -348,7 +506,7 @@ function entryLabel(entry: FusionStageBudgetPlanEntry): string {
|
|
|
348
506
|
function formatTable(entries: readonly FusionStageBudgetPlanEntry[]): string {
|
|
349
507
|
const lines = entries.map(
|
|
350
508
|
(entry) =>
|
|
351
|
-
`${entryLabel(entry)} | route=${entry.route.qualified_id} |
|
|
509
|
+
`${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'}`,
|
|
352
510
|
);
|
|
353
511
|
return lines.join('\n');
|
|
354
512
|
}
|
|
@@ -364,6 +522,27 @@ function formatComposition(composition: FusionBudgetStageComposition): string {
|
|
|
364
522
|
].join('; ');
|
|
365
523
|
}
|
|
366
524
|
|
|
525
|
+
function dominantRemediation(
|
|
526
|
+
verdict: FusionBudgetEmptyRequestVerdict,
|
|
527
|
+
composition: FusionBudgetStageComposition | undefined,
|
|
528
|
+
dominantByteClass: string,
|
|
529
|
+
): readonly string[] {
|
|
530
|
+
if (dominantByteClass === 'dense_ascii') return DENSE_REMEDIATION;
|
|
531
|
+
if (dominantByteClass === 'multibyte') return MULTIBYTE_REMEDIATION;
|
|
532
|
+
if (composition === undefined) return remediationFor(verdict);
|
|
533
|
+
const entries = [
|
|
534
|
+
{ name: 'visible', bytes: composition.visible_text_bytes },
|
|
535
|
+
{ name: 'request', bytes: composition.request_bytes },
|
|
536
|
+
{ name: 'reservation', bytes: composition.upstream_output_contract_bytes },
|
|
537
|
+
].sort((left, right) => right.bytes - left.bytes);
|
|
538
|
+
const dominant = entries[0];
|
|
539
|
+
if (dominant?.name === 'reservation') return RESERVATION_REMEDIATION;
|
|
540
|
+
if (dominant?.name === 'request' && !verdict.still_fails_with_empty_request) {
|
|
541
|
+
return REQUEST_REMEDIATION;
|
|
542
|
+
}
|
|
543
|
+
return EMPTY_REMEDIATION;
|
|
544
|
+
}
|
|
545
|
+
|
|
367
546
|
function remediationFor(verdict: FusionBudgetEmptyRequestVerdict): readonly string[] {
|
|
368
547
|
return verdict.still_fails_with_empty_request ? EMPTY_REMEDIATION : REQUEST_REMEDIATION;
|
|
369
548
|
}
|
|
@@ -384,6 +563,116 @@ function stageFromBudgetStage(stage: FusionBudgetStage): FusionStage {
|
|
|
384
563
|
return 'evaluation';
|
|
385
564
|
}
|
|
386
565
|
|
|
566
|
+
function routeTable(routes: readonly FusionRouteCapacity[]): readonly FusionBudgetRouteTableEntry[] {
|
|
567
|
+
return [...routes]
|
|
568
|
+
.sort((left, right) => {
|
|
569
|
+
const byCapacity = left.byte_capacity_utf8_bytes - right.byte_capacity_utf8_bytes;
|
|
570
|
+
if (byCapacity !== 0) return byCapacity;
|
|
571
|
+
return left.role.localeCompare(right.role);
|
|
572
|
+
})
|
|
573
|
+
.map((route) => ({
|
|
574
|
+
role: route.role,
|
|
575
|
+
qualified_id: route.qualified_id,
|
|
576
|
+
allowed_input_tokens: route.allowed_input_tokens,
|
|
577
|
+
family: route.family,
|
|
578
|
+
effective_rate_bytes_per_token_x100: route.rate_source.effective_rate_bytes_per_token_x100,
|
|
579
|
+
byte_capacity_utf8_bytes: route.byte_capacity_utf8_bytes,
|
|
580
|
+
backed: route.rate_source.backed,
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function segmentTokensForBytes(route: FusionRouteCapacity, bytes: number): number {
|
|
585
|
+
if (bytes <= 0) return 0;
|
|
586
|
+
const estimate = estimateRouteInput(route, [
|
|
587
|
+
{ kind: 'known_text', bytes, multibyteBytes: 0, denseBytes: 0 },
|
|
588
|
+
]);
|
|
589
|
+
const segment = estimate.perSegment[0];
|
|
590
|
+
return segment === undefined ? 0 : segment.tokens;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function contractTokens(bytes: number): number {
|
|
594
|
+
return bytes;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function componentBreakdown(
|
|
598
|
+
composition: FusionBudgetStageComposition | undefined,
|
|
599
|
+
route: FusionRouteCapacity,
|
|
600
|
+
): FusionBudgetComponentBreakdown {
|
|
601
|
+
const empty = {
|
|
602
|
+
visible_text_bytes: 0,
|
|
603
|
+
omission_receipt_bytes: 0,
|
|
604
|
+
projection_metadata_bytes: 0,
|
|
605
|
+
request_bytes: 0,
|
|
606
|
+
static_stage_framing_bytes: 0,
|
|
607
|
+
upstream_output_contract_bytes: 0,
|
|
608
|
+
} satisfies FusionBudgetStageComposition;
|
|
609
|
+
const item = composition ?? empty;
|
|
610
|
+
return {
|
|
611
|
+
visible_text: {
|
|
612
|
+
bytes: item.visible_text_bytes,
|
|
613
|
+
tokens: segmentTokensForBytes(route, item.visible_text_bytes),
|
|
614
|
+
},
|
|
615
|
+
omission_receipts: {
|
|
616
|
+
bytes: item.omission_receipt_bytes,
|
|
617
|
+
tokens: segmentTokensForBytes(route, item.omission_receipt_bytes),
|
|
618
|
+
},
|
|
619
|
+
projection_metadata: {
|
|
620
|
+
bytes: item.projection_metadata_bytes,
|
|
621
|
+
tokens: segmentTokensForBytes(route, item.projection_metadata_bytes),
|
|
622
|
+
},
|
|
623
|
+
request: {
|
|
624
|
+
bytes: item.request_bytes,
|
|
625
|
+
tokens: segmentTokensForBytes(route, item.request_bytes),
|
|
626
|
+
},
|
|
627
|
+
static_stage_framing: {
|
|
628
|
+
bytes: item.static_stage_framing_bytes,
|
|
629
|
+
tokens: segmentTokensForBytes(route, item.static_stage_framing_bytes),
|
|
630
|
+
},
|
|
631
|
+
upstream_output_contracts: {
|
|
632
|
+
bytes: item.upstream_output_contract_bytes,
|
|
633
|
+
tokens: contractTokens(item.upstream_output_contract_bytes),
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function medianCounterfactual(primary: FusionBudgetBlocker): FusionBudgetCounterfactuals['at_median_rate'] {
|
|
639
|
+
if (!primary.route.rate_source.backed) {
|
|
640
|
+
return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
641
|
+
}
|
|
642
|
+
const median = primary.route.rate_source.provenance.median_bpt_x1000;
|
|
643
|
+
if (median === null) return { forecast_input_tokens_upper_bound: null, signed_headroom_tokens: null, fits: null };
|
|
644
|
+
const variableTokens = ceilDiv(primary.input_utf8_bytes * 1000, median);
|
|
645
|
+
const tokens = variableTokens + TOKEN_BUDGET_AFFINE_F_TOKENS;
|
|
646
|
+
return {
|
|
647
|
+
forecast_input_tokens_upper_bound: tokens,
|
|
648
|
+
signed_headroom_tokens: primary.allowed_input_tokens - tokens,
|
|
649
|
+
fits: tokens <= primary.allowed_input_tokens,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function inputCounterfactuals(
|
|
654
|
+
primary: FusionBudgetBlocker,
|
|
655
|
+
plan: FusionBudgetPlanV1,
|
|
656
|
+
): FusionBudgetCounterfactuals {
|
|
657
|
+
return {
|
|
658
|
+
empty_request: plan.empty_request,
|
|
659
|
+
without_reservation: {
|
|
660
|
+
forecast_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
661
|
+
signed_headroom_tokens: primary.input_only_signed_headroom_tokens,
|
|
662
|
+
fits: primary.fits,
|
|
663
|
+
},
|
|
664
|
+
at_median_rate: medianCounterfactual(primary),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function budgetErrorCode(
|
|
669
|
+
checkKind: FusionBudgetCheckKind,
|
|
670
|
+
): 'prompt_budget_exceeded_forecast' | 'prompt_budget_exceeded_measured' {
|
|
671
|
+
return checkKind === 'rendered_prompt'
|
|
672
|
+
? 'prompt_budget_exceeded_measured'
|
|
673
|
+
: 'prompt_budget_exceeded_forecast';
|
|
674
|
+
}
|
|
675
|
+
|
|
387
676
|
export class FusionBudget {
|
|
388
677
|
readonly routes: readonly FusionRouteCapacity[];
|
|
389
678
|
readonly limiting: FusionRouteCapacity;
|
|
@@ -399,6 +688,19 @@ export class FusionBudget {
|
|
|
399
688
|
return this.limiting.allowed_input_tokens;
|
|
400
689
|
}
|
|
401
690
|
|
|
691
|
+
get resultRateSources() {
|
|
692
|
+
return this.routes.map((route) => route.rate_source);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
get unknownProviderWarnings(): readonly string[] {
|
|
696
|
+
return this.routes.flatMap((route) => {
|
|
697
|
+
const warning = route.rate_source.warning;
|
|
698
|
+
return route.rate_source.backed || warning === null
|
|
699
|
+
? []
|
|
700
|
+
: [`${route.qualified_id}: ${warning}`];
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
402
704
|
private routeForStage(stage: FusionBudgetStage, slot?: 1 | 2 | 3): FusionRouteCapacity {
|
|
403
705
|
if (stage === 'candidate') return routeByRole(this.routes, candidateRole(slot ?? 1));
|
|
404
706
|
if (stage === 'merge') return routeByRole(this.routes, 'merger');
|
|
@@ -517,8 +819,7 @@ export class FusionBudget {
|
|
|
517
819
|
const emptyBlockers = selectBlockers(emptyEntries);
|
|
518
820
|
let reduction = 0;
|
|
519
821
|
for (const entry of entries) {
|
|
520
|
-
|
|
521
|
-
reduction = Math.max(reduction, entry.forecast_utf8_bytes - byteLimit);
|
|
822
|
+
reduction = Math.max(reduction, entry.input_utf8_bytes - maxKnownTextBytes(entry.route));
|
|
522
823
|
}
|
|
523
824
|
reduction = Math.max(0, reduction);
|
|
524
825
|
const requestBytes = utf8Bytes(input.request.text);
|
|
@@ -537,13 +838,18 @@ export class FusionBudget {
|
|
|
537
838
|
plan: FusionBudgetPlanV1,
|
|
538
839
|
artifactDir: string,
|
|
539
840
|
measurementKind: FusionBudgetErrorDetail['measurement_kind'],
|
|
841
|
+
checkKind: FusionBudgetCheckKind,
|
|
540
842
|
): FusionError {
|
|
541
|
-
const
|
|
843
|
+
const composition = plan.primary_blocker_composition;
|
|
844
|
+
const dominantByteClass = primary.input_only_estimate.rateSource.dominant_byte_class;
|
|
845
|
+
const remediation = dominantRemediation(plan.empty_request, composition, dominantByteClass);
|
|
846
|
+
const tokensOver = primary.input_only_input_tokens_upper_bound - primary.allowed_input_tokens;
|
|
542
847
|
const budget: FusionBudgetErrorDetail = {
|
|
543
848
|
budget_stage: primary.budget_stage,
|
|
544
849
|
measurement_kind: measurementKind,
|
|
545
|
-
|
|
546
|
-
|
|
850
|
+
check_kind: checkKind,
|
|
851
|
+
measured_utf8_bytes: primary.input_utf8_bytes,
|
|
852
|
+
measured_input_tokens_upper_bound: primary.input_only_input_tokens_upper_bound,
|
|
547
853
|
allowed_input_tokens: primary.allowed_input_tokens,
|
|
548
854
|
limiting_model: {
|
|
549
855
|
provider: primary.route.provider,
|
|
@@ -551,30 +857,57 @@ export class FusionBudget {
|
|
|
551
857
|
qualified_id: primary.route.qualified_id,
|
|
552
858
|
context_window_tokens: primary.route.context_window_tokens,
|
|
553
859
|
},
|
|
860
|
+
rate_source: primary.input_only_estimate.rateSource,
|
|
861
|
+
backed: primary.input_only_estimate.rateSource.backed,
|
|
862
|
+
dominant_byte_class: dominantByteClass,
|
|
863
|
+
component_breakdown: componentBreakdown(composition, primary.route),
|
|
864
|
+
byte_class_breakdown: primary.input_only_estimate.byte_class_breakdown,
|
|
865
|
+
dense_regions: [],
|
|
866
|
+
bytes_over: primary.bytes_over,
|
|
867
|
+
tokens_over: Math.max(0, tokensOver),
|
|
868
|
+
required_allowed_tokens: primary.input_only_input_tokens_upper_bound,
|
|
869
|
+
route_table: routeTable(this.routes),
|
|
870
|
+
counterfactuals: inputCounterfactuals(primary, plan),
|
|
871
|
+
stage_upstream_actuals: [],
|
|
872
|
+
policy_id: FUSION_BUDGET_POLICY.id,
|
|
873
|
+
calibration_version: TOKEN_BUDGET_CALIBRATION_VERSION,
|
|
554
874
|
context_policy_id: this.contextPolicyId,
|
|
555
875
|
remediation,
|
|
556
876
|
blockers: plan.blockers,
|
|
557
877
|
artifact_dir: artifactDir,
|
|
558
878
|
};
|
|
559
879
|
if (primary.slot !== undefined) budget.slot = primary.slot;
|
|
560
|
-
const composition = plan.primary_blocker_composition;
|
|
561
880
|
const compositionText = composition === undefined ? 'unavailable' : formatComposition(composition);
|
|
562
881
|
const additional = plan.blockers
|
|
563
882
|
.filter((blocker) => blocker !== primary)
|
|
564
883
|
.map((blocker) => `${entryLabel(blocker)} route=${blocker.route.qualified_id}`)
|
|
565
884
|
.join('; ');
|
|
566
|
-
const
|
|
885
|
+
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})`;
|
|
886
|
+
const sourceWarning = primary.input_only_estimate.rateSource.warning;
|
|
887
|
+
const routeWarning = sourceWarning === null ? '' : ` Rate warning: ${sourceWarning}.`;
|
|
888
|
+
const checkText =
|
|
889
|
+
checkKind === 'input_only_preflight'
|
|
890
|
+
? 'input-only preflight forecast'
|
|
891
|
+
: 'exact rendered prompt measurement';
|
|
892
|
+
const dominantText =
|
|
893
|
+
dominantByteClass === 'multibyte'
|
|
894
|
+
? ' 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.'
|
|
895
|
+
: dominantByteClass === 'dense_ascii'
|
|
896
|
+
? ' Dominant byte class is dense ASCII/low-whitespace content; the whitespace gate is a heuristic token-density proxy, not a bound.'
|
|
897
|
+
: ` Dominant byte class is ${dominantByteClass}.`;
|
|
567
898
|
const message =
|
|
568
|
-
`Fusion prompt budget exceeded before child creation. Primary blocking stage: ${entryLabel(primary)} on route ${primary.route.qualified_id}. ` +
|
|
569
|
-
`Forecast ${String(primary.
|
|
899
|
+
`Fusion prompt budget exceeded by ${checkText} before child creation. Primary blocking stage: ${entryLabel(primary)} on route ${primary.route.qualified_id}. ` +
|
|
900
|
+
`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. ` +
|
|
901
|
+
`Estimator: ${rateText}.${routeWarning}${dominantText} ` +
|
|
570
902
|
`No child was created. Nothing was clipped, dropped, or substituted. Artifact directory: ${artifactDir}.\n` +
|
|
571
903
|
`Per-stage forecast table:\n${formatTable(plan.stages)}\n` +
|
|
572
904
|
`Primary blocker byte composition: ${compositionText}.\n` +
|
|
573
905
|
`Additional blockers: ${additional.length === 0 ? 'none' : additional}.\n` +
|
|
574
906
|
`${formatEmptyRequestVerdict(plan.empty_request)}\n` +
|
|
907
|
+
`Route byte-capacity order: ${routeTable(this.routes).map((route) => `${route.qualified_id}=${String(route.byte_capacity_utf8_bytes)}B`).join(', ')}.\n` +
|
|
575
908
|
`Remediation: ${remediation.join(' ')}`;
|
|
576
909
|
const details = {
|
|
577
|
-
code:
|
|
910
|
+
code: budgetErrorCode(checkKind),
|
|
578
911
|
childCreated: false,
|
|
579
912
|
budget,
|
|
580
913
|
stage: stageFromBudgetStage(primary.budget_stage),
|
|
@@ -607,7 +940,13 @@ export class FusionBudget {
|
|
|
607
940
|
|
|
608
941
|
assertPlanFits(plan: FusionBudgetPlanV1, artifactDir: string): void {
|
|
609
942
|
if (plan.primary_blocker !== undefined) {
|
|
610
|
-
throw this.failure(
|
|
943
|
+
throw this.failure(
|
|
944
|
+
plan.primary_blocker,
|
|
945
|
+
plan,
|
|
946
|
+
artifactDir,
|
|
947
|
+
'stage_forecast',
|
|
948
|
+
'input_only_preflight',
|
|
949
|
+
);
|
|
611
950
|
}
|
|
612
951
|
}
|
|
613
952
|
|
|
@@ -618,19 +957,29 @@ export class FusionBudget {
|
|
|
618
957
|
slot?: 1 | 2 | 3,
|
|
619
958
|
): void {
|
|
620
959
|
const route = this.routeForStage(stage, slot);
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
|
|
960
|
+
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
961
|
+
const inputBytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
962
|
+
const estimate = estimateRouteInput(route, inputSegments);
|
|
963
|
+
if (estimate.tokens <= route.allowed_input_tokens) return;
|
|
624
964
|
const entry: FusionStageBudgetPlanEntry = {
|
|
625
965
|
budget_stage: stage,
|
|
626
966
|
route,
|
|
627
967
|
conditional: stage === 'evaluation_repair',
|
|
628
|
-
|
|
629
|
-
|
|
968
|
+
check_kind: 'input_only_preflight',
|
|
969
|
+
input_utf8_bytes: inputBytes,
|
|
970
|
+
upstream_output_contract_bytes: 0,
|
|
971
|
+
forecast_utf8_bytes: inputBytes,
|
|
972
|
+
input_only_input_tokens_upper_bound: estimate.tokens,
|
|
973
|
+
forecast_input_tokens_upper_bound: estimate.tokens,
|
|
630
974
|
allowed_input_tokens: route.allowed_input_tokens,
|
|
631
|
-
|
|
632
|
-
|
|
975
|
+
input_only_signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
976
|
+
signed_headroom_tokens: route.allowed_input_tokens - estimate.tokens,
|
|
977
|
+
input_only_utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
978
|
+
utilization_basis_points: utilizationBasisPoints(estimate.tokens, route.allowed_input_tokens),
|
|
979
|
+
input_only_estimate: estimate,
|
|
980
|
+
reservation_estimate: estimate,
|
|
633
981
|
fits: false,
|
|
982
|
+
reservation_fits: false,
|
|
634
983
|
};
|
|
635
984
|
if (slot !== undefined) entry.slot = slot;
|
|
636
985
|
const blocker = blockerFromEntry(entry);
|
|
@@ -645,12 +994,53 @@ export class FusionBudget {
|
|
|
645
994
|
request_utf8_bytes: 0,
|
|
646
995
|
still_fails_with_empty_request: true,
|
|
647
996
|
shortening_request_can_help: false,
|
|
648
|
-
minimum_request_byte_reduction:
|
|
997
|
+
minimum_request_byte_reduction: blocker.bytes_over,
|
|
649
998
|
maximum_safe_request_utf8_bytes: 0,
|
|
650
999
|
blockers_with_empty_request: [blocker],
|
|
651
1000
|
},
|
|
652
1001
|
warnings: [],
|
|
653
1002
|
};
|
|
654
|
-
throw this.failure(blocker, plan, 'stage prompt re-measurement', 'rendered_prompt');
|
|
1003
|
+
throw this.failure(blocker, plan, 'stage prompt re-measurement', 'rendered_prompt', 'rendered_prompt');
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
calibrationViolationForCompletedChild(
|
|
1007
|
+
stage: FusionStage,
|
|
1008
|
+
systemPrompt: string,
|
|
1009
|
+
userPrompt: string,
|
|
1010
|
+
result: FusionChildRunResult,
|
|
1011
|
+
slot?: 1 | 2 | 3,
|
|
1012
|
+
): FusionCalibrationViolation | undefined {
|
|
1013
|
+
const route = this.routeForStage(stage, slot);
|
|
1014
|
+
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1015
|
+
const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1016
|
+
const estimate = estimateRouteInput(route, inputSegments);
|
|
1017
|
+
const billedInput = result.usage.input + result.usage.cacheRead + result.usage.cacheWrite;
|
|
1018
|
+
if (billedInput <= estimate.tokens) return undefined;
|
|
1019
|
+
const violation: FusionCalibrationViolation = {
|
|
1020
|
+
schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
1021
|
+
stage,
|
|
1022
|
+
attempt: result.attempt,
|
|
1023
|
+
route: {
|
|
1024
|
+
provider: result.provider,
|
|
1025
|
+
model: result.model,
|
|
1026
|
+
qualified_id: result.qualifiedId,
|
|
1027
|
+
},
|
|
1028
|
+
family: route.family,
|
|
1029
|
+
rate_source: estimate.rateSource,
|
|
1030
|
+
prompt_utf8_bytes: promptUtf8Bytes,
|
|
1031
|
+
prompt_sha256: sha256Hex(`${systemPrompt}\u0000${userPrompt}`),
|
|
1032
|
+
forecast_input_tokens: estimate.tokens,
|
|
1033
|
+
billed_input_tokens: billedInput,
|
|
1034
|
+
billed_input_breakdown: {
|
|
1035
|
+
input: result.usage.input,
|
|
1036
|
+
cache_read: result.usage.cacheRead,
|
|
1037
|
+
cache_write: result.usage.cacheWrite,
|
|
1038
|
+
},
|
|
1039
|
+
under_forecast_tokens: billedInput - estimate.tokens,
|
|
1040
|
+
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
1041
|
+
dominant_byte_class: estimate.rateSource.dominant_byte_class,
|
|
1042
|
+
};
|
|
1043
|
+
if (slot !== undefined) violation.slot = slot;
|
|
1044
|
+
return violation;
|
|
655
1045
|
}
|
|
656
1046
|
}
|