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