@deepthonk/core 0.1.5 → 0.2.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 -9
- package/dist/artifacts.js +20 -1
- package/dist/artifacts.js.map +1 -1
- package/dist/bradleyTerry.d.ts +1 -0
- package/dist/bradleyTerry.js +27 -2
- package/dist/bradleyTerry.js.map +1 -1
- package/dist/checkpointStore.d.ts +56 -0
- package/dist/checkpointStore.js +351 -0
- package/dist/checkpointStore.js.map +1 -0
- package/dist/critique.d.ts +3 -1
- package/dist/critique.js +24 -9
- package/dist/critique.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +1 -0
- package/dist/phaseRunner.d.ts +6 -1
- package/dist/phaseRunner.js +53 -19
- package/dist/phaseRunner.js.map +1 -1
- package/dist/resume.d.ts +6 -1
- package/dist/resume.js +56 -24
- package/dist/resume.js.map +1 -1
- package/dist/runner.d.ts +1 -0
- package/dist/runner.js +705 -276
- package/dist/runner.js.map +1 -1
- package/dist/schemas.d.ts +95 -0
- package/dist/schemas.js +43 -3
- package/dist/schemas.js.map +1 -1
- package/dist/services.d.ts +8 -0
- package/dist/services.js +77 -12
- package/dist/services.js.map +1 -1
- package/dist/traceStore.d.ts +27 -1
- package/dist/traceStore.js +143 -9
- package/dist/traceStore.js.map +1 -1
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -2,19 +2,19 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { fitBradleyTerry } from "./bradleyTerry.js";
|
|
5
|
+
import { canonicalizeComparisons, fitBradleyTerry } from "./bradleyTerry.js";
|
|
6
6
|
import { BudgetTracker } from "./budgetTracker.js";
|
|
7
|
+
import { readValidatedPhaseReceiptUsage, runCheckpointedPhase, traceSchemaVersion, validatePhaseCommit, validatePhaseCommitIfPresent, writePhaseCommit } from "./checkpointStore.js";
|
|
7
8
|
import { planBudget, validateProfile } from "./budget.js";
|
|
8
9
|
import { BudgetExceededError, CancelledError, ConfigError, DeepThonkError } from "./errors.js";
|
|
9
10
|
import { runArtifactFiles } from "./artifacts.js";
|
|
10
11
|
import { parseJsonObject } from "./json.js";
|
|
11
12
|
import { claimRunLockOwnership, emptyUsage, releaseRunLock, verifyRunLockClaim } from "./lifecycle.js";
|
|
12
13
|
import { makeKRegularPairs } from "./pairScheduler.js";
|
|
13
|
-
import { runLimitedPhase } from "./phaseRunner.js";
|
|
14
14
|
import { comparePrompt, finalizePrompt, generatePrompt, mutatePrompt } from "./prompts.js";
|
|
15
15
|
import { createRng } from "./rng.js";
|
|
16
|
-
import { assertNoPruneInProgress, assertNoLegacyRedactedBudget, buildPopulationMap, buildResumePlan, groupComparisons, persistPrunedTrace, pruneTraceToPlan, readResumeTrace, reconstructScores, replayBudgetUsage, resolveResumeRunId, resumeConfigError, toResumePlanStatus } from "./resume.js";
|
|
17
|
-
import { runConfigSchema, builtInProfiles, phaseCompletedEventSchema } from "./schemas.js";
|
|
16
|
+
import { assertNoPruneInProgress, assertNoLegacyRedactedBudget, applyUsageRecords, buildPopulationMap, buildResumePlan, groupComparisons, persistPrunedTrace, pruneTraceToPlan, readResumeTrace, reconstructScores, replayBudgetUsage, resolveResumeRunId, resumeConfigError, toResumePlanStatus, validateUsageRecords, validatedReceiptUsageIds } from "./resume.js";
|
|
17
|
+
import { runConfigSchema, resolveRunConfigDefaults, builtInProfiles, phaseCompletedEventSchema } from "./schemas.js";
|
|
18
18
|
import { aggregateCritiques } from "./critique.js";
|
|
19
19
|
import { TraceStore } from "./traceStore.js";
|
|
20
20
|
const compareOutputSchema = z.object({
|
|
@@ -27,7 +27,7 @@ const compareOutputSchema = z.object({
|
|
|
27
27
|
selection_reason: z.string().default("")
|
|
28
28
|
});
|
|
29
29
|
export async function runDeepThonk(configInput, driver, control = {}, resumeState) {
|
|
30
|
-
const config = runConfigSchema.parse(configInput);
|
|
30
|
+
const config = resolveRunConfigDefaults(runConfigSchema.parse(configInput));
|
|
31
31
|
validateProfile(config.profile);
|
|
32
32
|
enforceBudget(config);
|
|
33
33
|
const runId = resumeState?.runId ??
|
|
@@ -81,7 +81,7 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
81
81
|
});
|
|
82
82
|
};
|
|
83
83
|
const assertNotCancelled = async (phase) => {
|
|
84
|
-
if (await control.shouldCancel?.()) {
|
|
84
|
+
if (control.signal?.aborted || (await control.shouldCancel?.())) {
|
|
85
85
|
throw new CancelledError(`Run cancelled before ${phase}.`, {
|
|
86
86
|
code: "run.cancelled",
|
|
87
87
|
retryable: false,
|
|
@@ -134,9 +134,13 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
136
|
await assertNotCancelled("initial population");
|
|
137
|
-
population = await generateInitialPopulation(config, driver, trace, rng, runId, tracker, guards);
|
|
137
|
+
population = await generateInitialPopulation(config, driver, trace, rng, runId, tracker, guards, control.signal);
|
|
138
138
|
await trace.writePopulation(0, population);
|
|
139
139
|
await assertBudget("initial population");
|
|
140
|
+
await writePhaseCommit(config.runDir, "initial_generation", {
|
|
141
|
+
population,
|
|
142
|
+
usage: await phaseUsageRows(trace, "initial_generation")
|
|
143
|
+
});
|
|
140
144
|
await markPhaseCompleted(trace, "initial_generation");
|
|
141
145
|
await writeStatus("running", "population_completed", { generation: 0 });
|
|
142
146
|
}
|
|
@@ -166,11 +170,17 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
166
170
|
pairs,
|
|
167
171
|
population,
|
|
168
172
|
tracker,
|
|
169
|
-
guards
|
|
173
|
+
guards,
|
|
174
|
+
signal: control.signal
|
|
170
175
|
});
|
|
171
176
|
scores = fitBradleyTerry(population, comparisons, config.profile.lambda, gen);
|
|
172
177
|
await trace.writeScores(gen, scores);
|
|
173
178
|
await assertBudget(`generation ${gen} comparisons`);
|
|
179
|
+
await writePhaseCommit(config.runDir, `generation_judging:${gen}`, {
|
|
180
|
+
comparisons,
|
|
181
|
+
scores,
|
|
182
|
+
usage: await phaseUsageRows(trace, `generation_judging:${gen}`)
|
|
183
|
+
});
|
|
174
184
|
await markPhaseCompleted(trace, "generation_judging", gen);
|
|
175
185
|
}
|
|
176
186
|
const ranked = rankPopulation(population, scores);
|
|
@@ -178,7 +188,9 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
178
188
|
const discarded = bottomQuartile(ranked);
|
|
179
189
|
const survivors = ranked.filter((candidate) => !discarded.has(candidate.id));
|
|
180
190
|
const mutationParents = survivors.slice(0, config.profile.n - elites.length);
|
|
181
|
-
const critiquesByCandidate = aggregateCritiques(population, comparisons
|
|
191
|
+
const critiquesByCandidate = aggregateCritiques(population, comparisons, {
|
|
192
|
+
aggregateChars: config.critiqueLimits.aggregateChars
|
|
193
|
+
});
|
|
182
194
|
await assertNotCancelled(`generation ${gen} mutation`);
|
|
183
195
|
await writeStatus("running", "generation_mutation", { generation: gen });
|
|
184
196
|
await traceDiscardedCandidates(trace, ranked, new Set([...elites, ...mutationParents].map((candidate) => candidate.id)), discarded, gen);
|
|
@@ -190,7 +202,8 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
190
202
|
survivors: mutationParents,
|
|
191
203
|
critiquesByCandidate,
|
|
192
204
|
tracker,
|
|
193
|
-
guards
|
|
205
|
+
guards,
|
|
206
|
+
signal: control.signal
|
|
194
207
|
});
|
|
195
208
|
const eliteCopies = copyElites(elites, gen);
|
|
196
209
|
for (const elite of eliteCopies) {
|
|
@@ -200,6 +213,10 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
200
213
|
population = keepPopulationSize([...eliteCopies, ...mutants], config.profile.n);
|
|
201
214
|
await trace.writePopulation(gen, population);
|
|
202
215
|
await assertBudget(`generation ${gen} mutation`);
|
|
216
|
+
await writePhaseCommit(config.runDir, `generation_mutation:${gen}`, {
|
|
217
|
+
population,
|
|
218
|
+
usage: await phaseUsageRows(trace, `generation_mutation:${gen}`)
|
|
219
|
+
});
|
|
203
220
|
await markPhaseCompleted(trace, "generation_mutation", gen);
|
|
204
221
|
await writeStatus("running", "generation_completed", { generation: gen });
|
|
205
222
|
}
|
|
@@ -212,8 +229,12 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
212
229
|
else {
|
|
213
230
|
await assertNotCancelled("final ranking");
|
|
214
231
|
await writeStatus("running", "final_ranking", { generation: "final" });
|
|
215
|
-
const finalRng =
|
|
216
|
-
|
|
232
|
+
const finalRng = config.rank
|
|
233
|
+
? createRng(config.rank.seed ?? hashSeed(`${config.seed}:final_judging:`))
|
|
234
|
+
: phaseRng(config.seed, "final_judging");
|
|
235
|
+
const finalPairs = config.rank
|
|
236
|
+
? configuredFinalPairs(population.map((candidate) => candidate.id), config.rank, finalRng)
|
|
237
|
+
: makeKRegularPairs(population.map((candidate) => candidate.id), config.profile.m, finalRng);
|
|
217
238
|
finalComparisons = await judgePairs({
|
|
218
239
|
config,
|
|
219
240
|
driver,
|
|
@@ -224,11 +245,17 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
224
245
|
pairs: finalPairs,
|
|
225
246
|
population,
|
|
226
247
|
tracker,
|
|
227
|
-
guards
|
|
248
|
+
guards,
|
|
249
|
+
signal: control.signal
|
|
228
250
|
});
|
|
229
251
|
finalScores = fitBradleyTerry(population, finalComparisons, config.profile.lambda, "final");
|
|
230
252
|
await trace.writeScores("final", finalScores);
|
|
231
253
|
await assertBudget("final ranking");
|
|
254
|
+
await writePhaseCommit(config.runDir, "final_judging", {
|
|
255
|
+
comparisons: finalComparisons,
|
|
256
|
+
scores: finalScores,
|
|
257
|
+
usage: await phaseUsageRows(trace, "final_judging")
|
|
258
|
+
});
|
|
232
259
|
await markPhaseCompleted(trace, "final_judging");
|
|
233
260
|
}
|
|
234
261
|
const winner = rankPopulation(population, finalScores)[0];
|
|
@@ -236,7 +263,7 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
236
263
|
throw new ConfigError("Run produced no winner.");
|
|
237
264
|
await assertNotCancelled("finalization");
|
|
238
265
|
await writeStatus("running", "finalizing", { generation: "final" });
|
|
239
|
-
const finalAnswer = await maybeFinalize(winner, config, driver, trace, tracker, guards);
|
|
266
|
+
const finalAnswer = await maybeFinalize(winner, config, driver, trace, tracker, guards, control.signal);
|
|
240
267
|
await assertBudget("finalization");
|
|
241
268
|
const completedAt = new Date().toISOString();
|
|
242
269
|
const summary = {
|
|
@@ -255,6 +282,12 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
255
282
|
completed_at: completedAt
|
|
256
283
|
};
|
|
257
284
|
await trace.writeSummary(summary, finalAnswer, winner.content);
|
|
285
|
+
await writePhaseCommit(config.runDir, "finalizing", {
|
|
286
|
+
summary,
|
|
287
|
+
finalAnswer,
|
|
288
|
+
winner: winner.content,
|
|
289
|
+
usage: await phaseUsageRows(trace, "finalizing")
|
|
290
|
+
});
|
|
258
291
|
await markPhaseCompleted(trace, "finalizing");
|
|
259
292
|
await trace.event({ type: "run.completed", winner_id: winner.id, completed_at: completedAt });
|
|
260
293
|
if (resumeState)
|
|
@@ -278,9 +311,19 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
278
311
|
throw error;
|
|
279
312
|
}
|
|
280
313
|
finally {
|
|
281
|
-
|
|
314
|
+
let closeError;
|
|
315
|
+
try {
|
|
316
|
+
await trace.close();
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (caughtError !== undefined)
|
|
320
|
+
process.stderr.write(`deepthonk: failed to close trace handles: ${error.message}\n`);
|
|
321
|
+
else
|
|
322
|
+
closeError = error;
|
|
323
|
+
}
|
|
324
|
+
if (ownsLock || verifiedPreclaim) {
|
|
282
325
|
try {
|
|
283
|
-
await releaseRunLock(config.runDir, ownedLockClaimId);
|
|
326
|
+
await releaseRunLock(config.runDir, ownsLock ? ownedLockClaimId : control.lockClaim?.claimId);
|
|
284
327
|
}
|
|
285
328
|
catch (error) {
|
|
286
329
|
if (caughtError !== undefined) {
|
|
@@ -291,13 +334,11 @@ export async function runDeepThonk(configInput, driver, control = {}, resumeStat
|
|
|
291
334
|
}
|
|
292
335
|
}
|
|
293
336
|
}
|
|
337
|
+
if (closeError !== undefined)
|
|
338
|
+
throw closeError;
|
|
294
339
|
}
|
|
295
340
|
}
|
|
296
341
|
export async function resumeDeepThonk(runDir, driver, options = {}) {
|
|
297
|
-
const existingSummary = await readOptionalJson(runDir, runArtifactFiles.summary);
|
|
298
|
-
if (existingSummary) {
|
|
299
|
-
resumeConfigError("Run already has summary.json; nothing to resume.", "resume.already_complete", "Use deepthonk inspect/result to read the completed run.");
|
|
300
|
-
}
|
|
301
342
|
const rawConfig = await readRequiredConfig(runDir);
|
|
302
343
|
assertNoLegacyRedactedBudget(rawConfig);
|
|
303
344
|
const currentVersion = await currentPackageVersion();
|
|
@@ -306,7 +347,7 @@ export async function resumeDeepThonk(runDir, driver, options = {}) {
|
|
|
306
347
|
}
|
|
307
348
|
assertStoredOutputConfigComplete(rawConfig);
|
|
308
349
|
const parsedConfig = runConfigSchema.parse(rawConfig);
|
|
309
|
-
const config = { ...parsedConfig, runDir };
|
|
350
|
+
const config = resolveRunConfigDefaults({ ...parsedConfig, runDir });
|
|
310
351
|
validateProfile(config.profile);
|
|
311
352
|
enforceBudget(config);
|
|
312
353
|
assertResumeProviderMatches(rawConfig, config, driver, options);
|
|
@@ -321,6 +362,7 @@ export async function resumeDeepThonk(runDir, driver, options = {}) {
|
|
|
321
362
|
});
|
|
322
363
|
}
|
|
323
364
|
try {
|
|
365
|
+
const existingSummary = await readOptionalFinalSummary(runDir, rawConfig.traceSchemaVersion === traceSchemaVersion);
|
|
324
366
|
const status = await readOptionalJson(runDir, runArtifactFiles.status);
|
|
325
367
|
if ((status?.state === "running" || status?.state === "pending") && isLiveWorker(status.worker_pid)) {
|
|
326
368
|
resumeConfigError(`Run is still in flight at phase ${status.phase}.`, "resume.in_flight", "Wait for the worker to finish, or cancel/stop it before resuming.");
|
|
@@ -328,15 +370,39 @@ export async function resumeDeepThonk(runDir, driver, options = {}) {
|
|
|
328
370
|
const trace = await readResumeTrace(runDir);
|
|
329
371
|
const runId = resolveResumeRunId(config, status, trace.events);
|
|
330
372
|
const plan = buildResumePlan(config, trace.events);
|
|
331
|
-
if (
|
|
332
|
-
resumeConfigError("
|
|
373
|
+
if (existingSummary && rawConfig.traceSchemaVersion !== traceSchemaVersion) {
|
|
374
|
+
resumeConfigError("Run already has summary.json; nothing to resume.", "resume.already_complete", "Use deepthonk inspect/result to read the completed run.");
|
|
375
|
+
}
|
|
376
|
+
if (existingSummary &&
|
|
377
|
+
rawConfig.traceSchemaVersion === traceSchemaVersion &&
|
|
378
|
+
plan.nextPhase.phase !== "finalizing" &&
|
|
379
|
+
plan.nextPhase.phase !== "summary") {
|
|
380
|
+
resumeConfigError(`summary.json exists before the finalizing phase boundary (${plan.nextPhase.phase}).`, "resume.trace_v2_integrity", "Restore the matching finalization artifacts or start a fresh run.");
|
|
333
381
|
}
|
|
334
382
|
const planStatus = toResumePlanStatus(runDir, runId, plan);
|
|
335
|
-
|
|
383
|
+
let receiptUsageIds;
|
|
384
|
+
if (rawConfig.traceSchemaVersion === traceSchemaVersion) {
|
|
385
|
+
const receiptUsage = await readValidatedPhaseReceiptUsage(runDir, phaseCursorKey(plan.nextPhase));
|
|
386
|
+
receiptUsageIds = validatedReceiptUsageIds(receiptUsage);
|
|
387
|
+
}
|
|
388
|
+
const pruned = pruneTraceToPlan(trace, plan, { dropUsageIds: receiptUsageIds });
|
|
336
389
|
assertResumeRunIdsAgree(runId, config, status, pruned);
|
|
337
390
|
const populationByGeneration = buildPopulationMap(config, pruned.populations, pruned.candidates, plan);
|
|
338
391
|
const comparisonsByGeneration = groupComparisons(pruned.comparisons);
|
|
339
392
|
const scoresByGeneration = reconstructScores(config, populationByGeneration, comparisonsByGeneration, pruned.scores, plan, runId);
|
|
393
|
+
validateUsageRecords(pruned.usage);
|
|
394
|
+
if (rawConfig.traceSchemaVersion === traceSchemaVersion) {
|
|
395
|
+
await validateTraceV2CompletedPhases(runDir, config, plan.completed, populationByGeneration, comparisonsByGeneration, scoresByGeneration, pruned.usage);
|
|
396
|
+
if (plan.nextPhase.phase === "finalizing" && !plan.completed.has("finalizing")) {
|
|
397
|
+
await validatePhaseCommitIfPresent(runDir, "finalizing", await finalizingArtifacts(runDir, trace.usage));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (plan.nextPhase.phase === "summary") {
|
|
401
|
+
if (existingSummary) {
|
|
402
|
+
resumeConfigError("Run already has a validated summary.json and finalizing commit; nothing to resume.", "resume.already_complete", "Use deepthonk inspect/result to read the completed run.");
|
|
403
|
+
}
|
|
404
|
+
resumeConfigError("Trace says finalizing completed, but summary.json is missing or invalid.", rawConfig.traceSchemaVersion === traceSchemaVersion ? "resume.trace_v2_integrity" : "resume.inconsistent_trace", "Inspect the run directory and restore the matching finalization artifacts, or start a fresh run.");
|
|
405
|
+
}
|
|
340
406
|
if (options.dryRun)
|
|
341
407
|
return planStatus;
|
|
342
408
|
const tracker = replayBudgetUsage(config, pruned.usage);
|
|
@@ -418,7 +484,7 @@ function resumeComparisons(resumeState, generation) {
|
|
|
418
484
|
fix: "Inspect the run directory for missing comparison trace rows."
|
|
419
485
|
});
|
|
420
486
|
}
|
|
421
|
-
return comparisons;
|
|
487
|
+
return canonicalizeComparisons(comparisons);
|
|
422
488
|
}
|
|
423
489
|
function resumeScores(resumeState, generation, population, comparisons, lambda) {
|
|
424
490
|
return resumeState?.scoresByGeneration.get(generation) ?? fitBradleyTerry(population, comparisons, lambda, generation);
|
|
@@ -439,6 +505,13 @@ function resumePhaseKey(phase, generation) {
|
|
|
439
505
|
return `${phase}:${generation}`;
|
|
440
506
|
return phase;
|
|
441
507
|
}
|
|
508
|
+
function phaseCursorKey(cursor) {
|
|
509
|
+
if ((cursor.phase === "generation_judging" || cursor.phase === "generation_mutation") &&
|
|
510
|
+
typeof cursor.generation === "number") {
|
|
511
|
+
return `${cursor.phase}:${cursor.generation}`;
|
|
512
|
+
}
|
|
513
|
+
return cursor.phase;
|
|
514
|
+
}
|
|
442
515
|
function providerLabel(driver) {
|
|
443
516
|
const value = driver;
|
|
444
517
|
if (typeof value.provider === "string")
|
|
@@ -670,6 +743,20 @@ async function readRequiredConfig(runDir) {
|
|
|
670
743
|
}
|
|
671
744
|
return config;
|
|
672
745
|
}
|
|
746
|
+
async function readOptionalFinalSummary(runDir, traceV2) {
|
|
747
|
+
try {
|
|
748
|
+
return await readOptionalJson(runDir, runArtifactFiles.summary);
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
if (!traceV2)
|
|
752
|
+
throw error;
|
|
753
|
+
throw new ConfigError(`Could not parse summary.json: ${error.message}`, {
|
|
754
|
+
code: "resume.trace_v2_integrity",
|
|
755
|
+
retryable: false,
|
|
756
|
+
fix: "Restore summary.json from the same completed run, or resume from the pre-finalization artifacts."
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
673
760
|
async function readOptionalJson(runDir, fileName) {
|
|
674
761
|
try {
|
|
675
762
|
return JSON.parse(await readFile(join(runDir, fileName), "utf8"));
|
|
@@ -680,6 +767,16 @@ async function readOptionalJson(runDir, fileName) {
|
|
|
680
767
|
throw error;
|
|
681
768
|
}
|
|
682
769
|
}
|
|
770
|
+
async function readOptionalText(runDir, fileName) {
|
|
771
|
+
try {
|
|
772
|
+
return await readFile(join(runDir, fileName), "utf8");
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
if (error.code === "ENOENT")
|
|
776
|
+
return undefined;
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
683
780
|
function isLiveWorker(pid) {
|
|
684
781
|
if (!pid)
|
|
685
782
|
return true;
|
|
@@ -713,10 +810,81 @@ export function copyElites(elites, generation) {
|
|
|
713
810
|
status: "elite",
|
|
714
811
|
metadata: {
|
|
715
812
|
...candidate.metadata,
|
|
716
|
-
createdAt:
|
|
813
|
+
createdAt: candidate.metadata.createdAt
|
|
717
814
|
}
|
|
718
815
|
}));
|
|
719
816
|
}
|
|
817
|
+
async function validateTraceV2CompletedPhases(runDir, config, completed, populations, comparisons, scores, usage) {
|
|
818
|
+
if (completed.has("initial_generation")) {
|
|
819
|
+
await validatePhaseCommit(runDir, "initial_generation", {
|
|
820
|
+
population: populations.get(0),
|
|
821
|
+
usage: usage.filter((record) => usagePhaseKey(record.phase) === "initial_generation")
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
for (let generation = 1; generation <= config.profile.t; generation += 1) {
|
|
825
|
+
if (completed.has(`generation_judging:${generation}`)) {
|
|
826
|
+
const phaseKey = `generation_judging:${generation}`;
|
|
827
|
+
await validatePhaseCommit(runDir, phaseKey, {
|
|
828
|
+
comparisons: comparisons.get(generation),
|
|
829
|
+
scores: scores.get(generation),
|
|
830
|
+
usage: usage.filter((record) => usagePhaseKey(record.phase) === phaseKey)
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
if (completed.has(`generation_mutation:${generation}`)) {
|
|
834
|
+
const phaseKey = `generation_mutation:${generation}`;
|
|
835
|
+
await validatePhaseCommit(runDir, phaseKey, {
|
|
836
|
+
population: populations.get(generation),
|
|
837
|
+
usage: usage.filter((record) => usagePhaseKey(record.phase) === phaseKey)
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (completed.has("final_judging")) {
|
|
842
|
+
await validatePhaseCommit(runDir, "final_judging", {
|
|
843
|
+
comparisons: comparisons.get("final"),
|
|
844
|
+
scores: scores.get("final"),
|
|
845
|
+
usage: usage.filter((record) => usagePhaseKey(record.phase) === "final_judging")
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
if (completed.has("finalizing")) {
|
|
849
|
+
await validatePhaseCommit(runDir, "finalizing", await finalizingArtifacts(runDir, usage));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async function finalizingArtifacts(runDir, usage) {
|
|
853
|
+
try {
|
|
854
|
+
return {
|
|
855
|
+
summary: await readOptionalJson(runDir, runArtifactFiles.summary),
|
|
856
|
+
finalAnswer: await readOptionalText(runDir, runArtifactFiles.final),
|
|
857
|
+
winner: await readOptionalText(runDir, runArtifactFiles.winner),
|
|
858
|
+
usage: usage.filter((record) => usagePhaseKey(record.phase) === "finalizing")
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
catch (error) {
|
|
862
|
+
throw new ConfigError(`Could not read finalization artifacts: ${error.message}`, {
|
|
863
|
+
code: "resume.trace_v2_integrity",
|
|
864
|
+
retryable: false,
|
|
865
|
+
fix: "Restore summary.json, final.txt, and winner.txt from the same completed run."
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async function phaseUsageRows(trace, phaseKey) {
|
|
870
|
+
const usage = await trace.readJsonl(runArtifactFiles.usage).catch(() => []);
|
|
871
|
+
return usage.filter((record) => usagePhaseKey(record.phase) === phaseKey);
|
|
872
|
+
}
|
|
873
|
+
function usagePhaseKey(phase) {
|
|
874
|
+
if (phase === "gen0_initial")
|
|
875
|
+
return "initial_generation";
|
|
876
|
+
if (phase === "final_judge")
|
|
877
|
+
return "final_judging";
|
|
878
|
+
if (phase === "finalize")
|
|
879
|
+
return "finalizing";
|
|
880
|
+
const judge = /^gen(\d+)_judge$/.exec(phase);
|
|
881
|
+
if (judge)
|
|
882
|
+
return `generation_judging:${judge[1]}`;
|
|
883
|
+
const mutation = /^gen(\d+)_mutate$/.exec(phase);
|
|
884
|
+
if (mutation)
|
|
885
|
+
return `generation_mutation:${mutation[1]}`;
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
720
888
|
export function keepPopulationSize(population, n) {
|
|
721
889
|
return population.slice(0, n);
|
|
722
890
|
}
|
|
@@ -737,60 +905,92 @@ async function traceDiscardedCandidates(trace, ranked, carriedIds, bottomQuartil
|
|
|
737
905
|
await trace.event({ type: "candidate.discarded", candidate_id: candidate.id, generation, reason: discardReason });
|
|
738
906
|
}
|
|
739
907
|
}
|
|
740
|
-
async function generateInitialPopulation(config, driver, trace, rng, runId, tracker, guards) {
|
|
908
|
+
async function generateInitialPopulation(config, driver, trace, rng, runId, tracker, guards, signal) {
|
|
741
909
|
const jobs = Array.from({ length: config.profile.n }, (_, index) => ({
|
|
742
910
|
index,
|
|
743
911
|
id: `g0_c${index}_${rng.id("c")}`,
|
|
744
912
|
prompt: generatePrompt(config.task, config.rubric, config.promptStyle, config.promptOverrides?.generate)
|
|
745
913
|
}));
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
914
|
+
const promptRef = config.output.includePrompts && jobs[0] ? await trace.writePrompt(jobs[0].prompt) : undefined;
|
|
915
|
+
const candidates = await runCheckpointedPhase({
|
|
916
|
+
runDir: config.runDir,
|
|
917
|
+
phaseKey: "initial_generation",
|
|
918
|
+
jobs: jobs.map((job) => {
|
|
919
|
+
const receiptUsage = [];
|
|
920
|
+
return {
|
|
921
|
+
id: job.id,
|
|
922
|
+
input: {
|
|
923
|
+
task: config.task,
|
|
924
|
+
rubric: config.rubric,
|
|
925
|
+
model: config.generatorModel,
|
|
926
|
+
temperature: config.profile.sampleTemperature,
|
|
927
|
+
candidateIndex: job.index,
|
|
928
|
+
prompt: job.prompt,
|
|
929
|
+
maxOutputTokens: config.modelOutputTokens?.generation ?? 4_096
|
|
930
|
+
},
|
|
931
|
+
receiptUsage: () => receiptUsage,
|
|
932
|
+
run: async (phaseSignal) => {
|
|
933
|
+
const reservation = await guards.beforeCall("initial population call");
|
|
934
|
+
let result;
|
|
935
|
+
try {
|
|
936
|
+
result = await driver.generate({
|
|
937
|
+
task: config.task,
|
|
938
|
+
rubric: config.rubric,
|
|
939
|
+
model: config.generatorModel,
|
|
940
|
+
temperature: config.profile.sampleTemperature,
|
|
941
|
+
candidateIndex: job.index,
|
|
942
|
+
prompt: job.prompt,
|
|
943
|
+
signal: phaseSignal,
|
|
944
|
+
maxOutputTokens: config.modelOutputTokens?.generation ?? 4_096
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
await recordFailedInvocation({
|
|
949
|
+
tracker,
|
|
950
|
+
reservation,
|
|
951
|
+
trace,
|
|
952
|
+
phase: "gen0_initial",
|
|
953
|
+
role: "generator",
|
|
954
|
+
provider: config.provider,
|
|
955
|
+
model: config.generatorModel,
|
|
956
|
+
error
|
|
957
|
+
});
|
|
958
|
+
throw error;
|
|
959
|
+
}
|
|
960
|
+
const delta = tracker.record(result, { provider: config.provider, model: config.generatorModel }, reservation);
|
|
961
|
+
const usageRecord = buildUsageRecord({
|
|
962
|
+
phase: "gen0_initial",
|
|
963
|
+
role: "generator",
|
|
964
|
+
result,
|
|
965
|
+
delta,
|
|
966
|
+
provider: result.provider ?? config.provider,
|
|
967
|
+
model: result.model ?? config.generatorModel
|
|
968
|
+
});
|
|
969
|
+
receiptUsage.push(usageRecord);
|
|
970
|
+
await trace.writeUsage(usageRecord);
|
|
971
|
+
await guards.afterCall("initial population call");
|
|
972
|
+
const candidate = {
|
|
973
|
+
id: job.id,
|
|
974
|
+
generation: 0,
|
|
975
|
+
kind: "initial",
|
|
976
|
+
content: result.text,
|
|
977
|
+
status: "generated",
|
|
978
|
+
metadata: resultMetadata(result, config, promptRef)
|
|
979
|
+
};
|
|
980
|
+
return candidate;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
}),
|
|
984
|
+
concurrency: config.concurrency.generate,
|
|
985
|
+
signal,
|
|
986
|
+
validateOutput: isCandidateCheckpoint,
|
|
987
|
+
onResult: async (candidate, context) => {
|
|
988
|
+
if (context.reused)
|
|
989
|
+
await restoreCheckpointUsage(context.receiptUsage, tracker, trace);
|
|
990
|
+
await trace.writeCandidate(candidate);
|
|
991
|
+
await trace.event({ type: "candidate.generated", run_id: runId, candidate_id: candidate.id });
|
|
771
992
|
}
|
|
772
|
-
|
|
773
|
-
await trace.writeUsage(buildUsageRecord({
|
|
774
|
-
phase: "gen0_initial",
|
|
775
|
-
role: "generator",
|
|
776
|
-
result,
|
|
777
|
-
delta,
|
|
778
|
-
provider: result.provider ?? config.provider,
|
|
779
|
-
model: result.model ?? config.generatorModel
|
|
780
|
-
}));
|
|
781
|
-
await guards.afterCall("initial population call");
|
|
782
|
-
const candidate = {
|
|
783
|
-
id: job.id,
|
|
784
|
-
generation: 0,
|
|
785
|
-
kind: "initial",
|
|
786
|
-
content: result.text,
|
|
787
|
-
status: "generated",
|
|
788
|
-
metadata: resultMetadata(result, config, job.prompt)
|
|
789
|
-
};
|
|
790
|
-
await trace.writeCandidate(candidate);
|
|
791
|
-
await trace.event({ type: "candidate.generated", run_id: runId, candidate_id: candidate.id });
|
|
792
|
-
return candidate;
|
|
793
|
-
}), config.concurrency.generate);
|
|
993
|
+
});
|
|
794
994
|
return candidates;
|
|
795
995
|
}
|
|
796
996
|
async function judgePairs(args) {
|
|
@@ -810,223 +1010,340 @@ async function judgePairs(args) {
|
|
|
810
1010
|
presentedA,
|
|
811
1011
|
presentedB,
|
|
812
1012
|
id: `${args.generation}_cmp_${pairIndex}_${args.rng.id("cmp")}`,
|
|
813
|
-
prompt: comparePrompt(args.config.task, presentedA, presentedB, args.config.rubric, args.config.promptStyle, args.config.promptOverrides?.compare)
|
|
1013
|
+
prompt: comparePrompt(args.config.task, presentedA, presentedB, args.config.rubric, args.config.promptStyle, args.config.promptOverrides?.compare),
|
|
1014
|
+
promptRef: undefined
|
|
814
1015
|
};
|
|
815
1016
|
});
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
let inputCacheHitTokens = 0;
|
|
826
|
-
let inputCacheMissTokens = 0;
|
|
827
|
-
let outputTokens = 0;
|
|
828
|
-
let totalTokens = 0;
|
|
829
|
-
for (let attempt = 0; attempt <= args.config.retry.invalidJsonRetries; attempt += 1) {
|
|
830
|
-
const reservation = await args.guards.beforeCall(`${args.generation} comparison call`);
|
|
831
|
-
let result;
|
|
832
|
-
try {
|
|
833
|
-
result = await args.driver.compare({
|
|
1017
|
+
const phaseKey = args.generation === "final" ? "final_judging" : `generation_judging:${args.generation}`;
|
|
1018
|
+
const comparisons = await runCheckpointedPhase({
|
|
1019
|
+
runDir: args.config.runDir,
|
|
1020
|
+
phaseKey,
|
|
1021
|
+
jobs: jobs.map((job) => {
|
|
1022
|
+
const receiptUsage = [];
|
|
1023
|
+
return {
|
|
1024
|
+
id: job.id,
|
|
1025
|
+
input: {
|
|
834
1026
|
task: args.config.task,
|
|
835
1027
|
rubric: args.config.rubric,
|
|
836
1028
|
model: args.config.judgeModel,
|
|
837
1029
|
temperature: args.config.profile.judgeTemperature,
|
|
838
1030
|
candidateA: job.presentedA,
|
|
839
1031
|
candidateB: job.presentedB,
|
|
840
|
-
prompt: job.prompt
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1032
|
+
prompt: job.prompt,
|
|
1033
|
+
invalidJsonRetries: args.config.retry.invalidJsonRetries,
|
|
1034
|
+
maxOutputTokens: args.config.modelOutputTokens?.judge ?? 1_024
|
|
1035
|
+
},
|
|
1036
|
+
receiptUsage: () => receiptUsage,
|
|
1037
|
+
run: async (phaseSignal) => {
|
|
1038
|
+
if (args.config.output.includePrompts && !job.promptRef)
|
|
1039
|
+
job.promptRef = await args.trace.writePrompt(job.prompt);
|
|
1040
|
+
let parsed;
|
|
1041
|
+
let rawOutput;
|
|
1042
|
+
let invalidJson = false;
|
|
1043
|
+
let resultModel;
|
|
1044
|
+
let resultProvider;
|
|
1045
|
+
let resultRetryCount = 0;
|
|
1046
|
+
let jsonParseFailures = 0;
|
|
1047
|
+
let inputTokens = 0;
|
|
1048
|
+
let inputCacheHitTokens = 0;
|
|
1049
|
+
let inputCacheMissTokens = 0;
|
|
1050
|
+
let outputTokens = 0;
|
|
1051
|
+
let totalTokens = 0;
|
|
1052
|
+
for (let attempt = 0; attempt <= args.config.retry.invalidJsonRetries; attempt += 1) {
|
|
1053
|
+
const reservation = await args.guards.beforeCall(`${args.generation} comparison call`);
|
|
1054
|
+
let result;
|
|
1055
|
+
try {
|
|
1056
|
+
result = await args.driver.compare({
|
|
1057
|
+
task: args.config.task,
|
|
1058
|
+
rubric: args.config.rubric,
|
|
1059
|
+
model: args.config.judgeModel,
|
|
1060
|
+
temperature: args.config.profile.judgeTemperature,
|
|
1061
|
+
candidateA: job.presentedA,
|
|
1062
|
+
candidateB: job.presentedB,
|
|
1063
|
+
prompt: job.prompt,
|
|
1064
|
+
signal: phaseSignal,
|
|
1065
|
+
maxOutputTokens: args.config.modelOutputTokens?.judge ?? 1_024
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
await recordFailedInvocation({
|
|
1070
|
+
tracker: args.tracker,
|
|
1071
|
+
reservation,
|
|
1072
|
+
trace: args.trace,
|
|
1073
|
+
phase: args.generation === "final" ? "final_judge" : `gen${args.generation}_judge`,
|
|
1074
|
+
role: "judge",
|
|
1075
|
+
provider: args.config.provider,
|
|
1076
|
+
model: args.config.judgeModel,
|
|
1077
|
+
error
|
|
1078
|
+
});
|
|
1079
|
+
throw error;
|
|
1080
|
+
}
|
|
1081
|
+
const delta = args.tracker.record(result, { provider: args.config.provider, model: args.config.judgeModel }, reservation);
|
|
1082
|
+
const usageRecord = buildUsageRecord({
|
|
1083
|
+
phase: args.generation === "final" ? "final_judge" : `gen${args.generation}_judge`,
|
|
1084
|
+
role: "judge",
|
|
1085
|
+
result,
|
|
1086
|
+
delta,
|
|
1087
|
+
provider: result.provider ?? args.config.provider,
|
|
1088
|
+
model: result.model ?? args.config.judgeModel
|
|
1089
|
+
});
|
|
1090
|
+
receiptUsage.push(usageRecord);
|
|
1091
|
+
await args.trace.writeUsage(usageRecord);
|
|
1092
|
+
await args.guards.afterCall(`${args.generation} comparison call`);
|
|
1093
|
+
inputTokens += result.usage?.inputTokens ?? 0;
|
|
1094
|
+
inputCacheHitTokens += result.usage?.inputCacheHitTokens ?? 0;
|
|
1095
|
+
inputCacheMissTokens += result.usage?.inputCacheMissTokens ?? 0;
|
|
1096
|
+
outputTokens += result.usage?.outputTokens ?? 0;
|
|
1097
|
+
totalTokens += result.usage?.totalTokens ?? (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0);
|
|
1098
|
+
rawOutput = args.config.output.includeRawModelOutputs ? result.raw ?? result.text : undefined;
|
|
1099
|
+
resultModel = result.model;
|
|
1100
|
+
resultProvider = result.provider;
|
|
1101
|
+
resultRetryCount += result.retryCount ?? 0;
|
|
1102
|
+
try {
|
|
1103
|
+
parsed = compareOutputSchema.parse(parseJsonObject(result.text));
|
|
1104
|
+
invalidJson = false;
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
invalidJson = true;
|
|
1109
|
+
jsonParseFailures += 1;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (invalidJson || !parsed) {
|
|
1113
|
+
throw new ConfigError(`Judge produced ${jsonParseFailures} consecutive invalid-JSON responses (${args.config.retry.invalidJsonRetries + 1} attempts) for comparison ${job.id}. Refusing to synthesize a tie and pollute the ranking.`, {
|
|
1114
|
+
code: "judge.persistent_invalid_json",
|
|
1115
|
+
retryable: false,
|
|
1116
|
+
fix: "The judge model is producing unparseable output. Inspect the raw response (set output.includeRawModelOutputs: true), switch judge models, or raise retry.invalidJsonRetries if the failures are transient."
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
const comparison = {
|
|
1120
|
+
id: job.id,
|
|
1121
|
+
runId: args.runId,
|
|
1122
|
+
generation: args.generation,
|
|
1123
|
+
candidateAId: job.presentedA.id,
|
|
1124
|
+
candidateBId: job.presentedB.id,
|
|
1125
|
+
presentedAOriginalId: job.presentedA.id,
|
|
1126
|
+
presentedBOriginalId: job.presentedB.id,
|
|
1127
|
+
winner: parsed.winner,
|
|
1128
|
+
confidence: parsed.confidence,
|
|
1129
|
+
critiqueForA: parsed.feedback_a ?? parsed.critique_for_A ?? "",
|
|
1130
|
+
critiqueForB: parsed.feedback_b ?? parsed.critique_for_B ?? "",
|
|
1131
|
+
selectionReason: parsed.selection_reason ?? "",
|
|
1132
|
+
rawOutput,
|
|
1133
|
+
model: resultModel,
|
|
1134
|
+
provider: resultProvider,
|
|
1135
|
+
metadata: compactMetadata({
|
|
1136
|
+
invalid_json_tie: invalidJson ? true : undefined,
|
|
1137
|
+
json_parse_failures: jsonParseFailures || undefined,
|
|
1138
|
+
model_call_count: jsonParseFailures + (parsed ? 1 : 0),
|
|
1139
|
+
input_tokens: inputTokens || undefined,
|
|
1140
|
+
input_cache_hit_tokens: inputCacheHitTokens || undefined,
|
|
1141
|
+
input_cache_miss_tokens: inputCacheMissTokens || undefined,
|
|
1142
|
+
output_tokens: outputTokens || undefined,
|
|
1143
|
+
total_tokens: totalTokens || undefined,
|
|
1144
|
+
scheduled_candidate_a_id: job.originalA.id,
|
|
1145
|
+
scheduled_candidate_b_id: job.originalB.id,
|
|
1146
|
+
provider_retry_count: resultRetryCount || undefined,
|
|
1147
|
+
promptRef: job.promptRef
|
|
1148
|
+
})
|
|
1149
|
+
};
|
|
1150
|
+
return comparison;
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
}),
|
|
1154
|
+
concurrency: args.config.concurrency.judge,
|
|
1155
|
+
signal: args.signal,
|
|
1156
|
+
validateOutput: isComparisonCheckpoint,
|
|
1157
|
+
onResult: async (comparison, context) => {
|
|
1158
|
+
if (context.reused)
|
|
1159
|
+
await restoreCheckpointUsage(context.receiptUsage, args.tracker, args.trace);
|
|
1160
|
+
await args.trace.writeComparison(comparison);
|
|
1161
|
+
await args.trace.event({
|
|
1162
|
+
type: "comparison.completed",
|
|
1163
|
+
run_id: args.runId,
|
|
1164
|
+
comparison_id: comparison.id,
|
|
1165
|
+
generation: args.generation
|
|
890
1166
|
});
|
|
891
1167
|
}
|
|
892
|
-
|
|
893
|
-
id: job.id,
|
|
894
|
-
runId: args.runId,
|
|
895
|
-
generation: args.generation,
|
|
896
|
-
candidateAId: job.presentedA.id,
|
|
897
|
-
candidateBId: job.presentedB.id,
|
|
898
|
-
presentedAOriginalId: job.presentedA.id,
|
|
899
|
-
presentedBOriginalId: job.presentedB.id,
|
|
900
|
-
winner: parsed.winner,
|
|
901
|
-
confidence: parsed.confidence,
|
|
902
|
-
critiqueForA: parsed.feedback_a ?? parsed.critique_for_A ?? "",
|
|
903
|
-
critiqueForB: parsed.feedback_b ?? parsed.critique_for_B ?? "",
|
|
904
|
-
selectionReason: parsed.selection_reason ?? "",
|
|
905
|
-
rawOutput,
|
|
906
|
-
model: resultModel,
|
|
907
|
-
provider: resultProvider,
|
|
908
|
-
metadata: compactMetadata({
|
|
909
|
-
invalid_json_tie: invalidJson ? true : undefined,
|
|
910
|
-
json_parse_failures: jsonParseFailures || undefined,
|
|
911
|
-
model_call_count: jsonParseFailures + (parsed ? 1 : 0),
|
|
912
|
-
input_tokens: inputTokens || undefined,
|
|
913
|
-
input_cache_hit_tokens: inputCacheHitTokens || undefined,
|
|
914
|
-
input_cache_miss_tokens: inputCacheMissTokens || undefined,
|
|
915
|
-
output_tokens: outputTokens || undefined,
|
|
916
|
-
total_tokens: totalTokens || undefined,
|
|
917
|
-
scheduled_candidate_a_id: job.originalA.id,
|
|
918
|
-
scheduled_candidate_b_id: job.originalB.id,
|
|
919
|
-
provider_retry_count: resultRetryCount || undefined,
|
|
920
|
-
prompt: args.config.output.includePrompts ? job.prompt : undefined
|
|
921
|
-
})
|
|
922
|
-
};
|
|
923
|
-
await args.trace.writeComparison(comparison);
|
|
924
|
-
await args.trace.event({
|
|
925
|
-
type: "comparison.completed",
|
|
926
|
-
run_id: args.runId,
|
|
927
|
-
comparison_id: comparison.id,
|
|
928
|
-
generation: args.generation
|
|
929
|
-
});
|
|
930
|
-
return comparison;
|
|
931
|
-
}), args.config.concurrency.judge);
|
|
1168
|
+
});
|
|
932
1169
|
return comparisons;
|
|
933
1170
|
}
|
|
934
1171
|
async function mutateSurvivors(args) {
|
|
935
|
-
const mutants = await
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1172
|
+
const mutants = await runCheckpointedPhase({
|
|
1173
|
+
runDir: args.config.runDir,
|
|
1174
|
+
phaseKey: `generation_mutation:${args.generation}`,
|
|
1175
|
+
jobs: args.survivors.map((candidate, index) => {
|
|
1176
|
+
const critique = args.critiquesByCandidate.get(candidate.id) ?? "";
|
|
1177
|
+
const prompt = mutatePrompt(args.config.task, candidate, critique, args.config.rubric, args.config.promptStyle, args.config.promptOverrides?.mutate);
|
|
1178
|
+
const receiptUsage = [];
|
|
1179
|
+
let promptRef;
|
|
1180
|
+
return {
|
|
1181
|
+
id: `g${args.generation}_m${index}_${candidate.id}`,
|
|
1182
|
+
input: {
|
|
1183
|
+
task: args.config.task,
|
|
1184
|
+
rubric: args.config.rubric,
|
|
1185
|
+
model: args.config.mutatorModel,
|
|
1186
|
+
temperature: args.config.profile.mutateTemperature,
|
|
1187
|
+
candidate,
|
|
1188
|
+
critique,
|
|
1189
|
+
prompt,
|
|
1190
|
+
maxOutputTokens: args.config.modelOutputTokens?.mutation ?? 4_096
|
|
1191
|
+
},
|
|
1192
|
+
receiptUsage: () => receiptUsage,
|
|
1193
|
+
run: async (phaseSignal) => {
|
|
1194
|
+
if (args.config.output.includePrompts && !promptRef)
|
|
1195
|
+
promptRef = await args.trace.writePrompt(prompt);
|
|
1196
|
+
const reservation = await args.guards.beforeCall(`generation ${args.generation} mutation call`);
|
|
1197
|
+
let result;
|
|
1198
|
+
try {
|
|
1199
|
+
result = await args.driver.mutate({
|
|
1200
|
+
task: args.config.task,
|
|
1201
|
+
rubric: args.config.rubric,
|
|
1202
|
+
model: args.config.mutatorModel,
|
|
1203
|
+
temperature: args.config.profile.mutateTemperature,
|
|
1204
|
+
candidate,
|
|
1205
|
+
critique,
|
|
1206
|
+
prompt,
|
|
1207
|
+
signal: phaseSignal,
|
|
1208
|
+
maxOutputTokens: args.config.modelOutputTokens?.mutation ?? 4_096
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
catch (error) {
|
|
1212
|
+
await recordFailedInvocation({
|
|
1213
|
+
tracker: args.tracker,
|
|
1214
|
+
reservation,
|
|
1215
|
+
trace: args.trace,
|
|
1216
|
+
phase: `gen${args.generation}_mutate`,
|
|
1217
|
+
role: "mutator",
|
|
1218
|
+
provider: args.config.provider,
|
|
1219
|
+
model: args.config.mutatorModel,
|
|
1220
|
+
error
|
|
1221
|
+
});
|
|
1222
|
+
throw error;
|
|
1223
|
+
}
|
|
1224
|
+
const delta = args.tracker.record(result, { provider: args.config.provider, model: args.config.mutatorModel }, reservation);
|
|
1225
|
+
const usageRecord = buildUsageRecord({
|
|
1226
|
+
phase: `gen${args.generation}_mutate`,
|
|
1227
|
+
role: "mutator",
|
|
1228
|
+
result,
|
|
1229
|
+
delta,
|
|
1230
|
+
provider: result.provider ?? args.config.provider,
|
|
1231
|
+
model: result.model ?? args.config.mutatorModel
|
|
1232
|
+
});
|
|
1233
|
+
receiptUsage.push(usageRecord);
|
|
1234
|
+
await args.trace.writeUsage(usageRecord);
|
|
1235
|
+
await args.guards.afterCall(`generation ${args.generation} mutation call`);
|
|
1236
|
+
const mutant = {
|
|
1237
|
+
id: `g${args.generation}_m${index}_${candidate.id}`,
|
|
1238
|
+
generation: args.generation,
|
|
1239
|
+
parentId: candidate.id,
|
|
1240
|
+
kind: "mutation",
|
|
1241
|
+
content: result.text,
|
|
1242
|
+
status: "mutated",
|
|
1243
|
+
metadata: resultMetadata(result, args.config, promptRef)
|
|
1244
|
+
};
|
|
1245
|
+
return mutant;
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
}),
|
|
1249
|
+
concurrency: args.config.concurrency.mutate,
|
|
1250
|
+
signal: args.signal,
|
|
1251
|
+
validateOutput: isCandidateCheckpoint,
|
|
1252
|
+
onResult: async (mutant, context) => {
|
|
1253
|
+
if (context.reused)
|
|
1254
|
+
await restoreCheckpointUsage(context.receiptUsage, args.tracker, args.trace);
|
|
1255
|
+
await args.trace.writeCandidate(mutant);
|
|
1256
|
+
await args.trace.event({ type: "candidate.mutated", candidate_id: mutant.id, parent_id: mutant.parentId });
|
|
963
1257
|
}
|
|
964
|
-
|
|
965
|
-
await args.trace.writeUsage(buildUsageRecord({
|
|
966
|
-
phase: `gen${args.generation}_mutate`,
|
|
967
|
-
role: "mutator",
|
|
968
|
-
result,
|
|
969
|
-
delta,
|
|
970
|
-
provider: result.provider ?? args.config.provider,
|
|
971
|
-
model: result.model ?? args.config.mutatorModel
|
|
972
|
-
}));
|
|
973
|
-
await args.guards.afterCall(`generation ${args.generation} mutation call`);
|
|
974
|
-
const mutant = {
|
|
975
|
-
id: `g${args.generation}_m${index}_${candidate.id}`,
|
|
976
|
-
generation: args.generation,
|
|
977
|
-
parentId: candidate.id,
|
|
978
|
-
kind: "mutation",
|
|
979
|
-
content: result.text,
|
|
980
|
-
status: "mutated",
|
|
981
|
-
metadata: resultMetadata(result, args.config, prompt)
|
|
982
|
-
};
|
|
983
|
-
await args.trace.writeCandidate(mutant);
|
|
984
|
-
await args.trace.event({ type: "candidate.mutated", candidate_id: mutant.id, parent_id: mutant.parentId });
|
|
985
|
-
return mutant;
|
|
986
|
-
}), args.config.concurrency.mutate);
|
|
1258
|
+
});
|
|
987
1259
|
return mutants;
|
|
988
1260
|
}
|
|
989
|
-
async function maybeFinalize(winner, config, driver, trace, tracker, guards) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
let
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1261
|
+
async function maybeFinalize(winner, config, driver, trace, tracker, guards, signal) {
|
|
1262
|
+
const enabled = Boolean(config.finalizerModel && driver.finalize);
|
|
1263
|
+
const prompt = finalizePrompt(config.task, winner, config.rubric, config.promptStyle, config.promptOverrides?.finalize);
|
|
1264
|
+
const promptRef = enabled && config.output.includePrompts ? await trace.writePrompt(prompt) : undefined;
|
|
1265
|
+
let finalModel;
|
|
1266
|
+
let finalProvider;
|
|
1267
|
+
const receiptUsage = [];
|
|
1268
|
+
const [finalized] = await runCheckpointedPhase({
|
|
1269
|
+
runDir: config.runDir,
|
|
1270
|
+
phaseKey: "finalizing",
|
|
1271
|
+
jobs: [
|
|
1272
|
+
{
|
|
1273
|
+
id: `finalizer:${winner.id}`,
|
|
1274
|
+
input: {
|
|
1275
|
+
enabled,
|
|
1276
|
+
task: config.task,
|
|
1277
|
+
rubric: config.rubric,
|
|
1278
|
+
model: config.finalizerModel,
|
|
1279
|
+
candidate: winner,
|
|
1280
|
+
prompt,
|
|
1281
|
+
maxOutputTokens: config.modelOutputTokens?.finalizer ?? 4_096
|
|
1282
|
+
},
|
|
1283
|
+
receiptUsage: () => receiptUsage,
|
|
1284
|
+
run: async (phaseSignal) => {
|
|
1285
|
+
if (!enabled || !config.finalizerModel || !driver.finalize)
|
|
1286
|
+
return { answer: winner.content, promptRef: undefined };
|
|
1287
|
+
const reservation = await guards.beforeCall("finalization call");
|
|
1288
|
+
let result;
|
|
1289
|
+
try {
|
|
1290
|
+
result = await driver.finalize({
|
|
1291
|
+
task: config.task,
|
|
1292
|
+
rubric: config.rubric,
|
|
1293
|
+
model: config.finalizerModel,
|
|
1294
|
+
candidate: winner,
|
|
1295
|
+
prompt,
|
|
1296
|
+
signal: phaseSignal,
|
|
1297
|
+
maxOutputTokens: config.modelOutputTokens?.finalizer ?? 4_096
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
catch (error) {
|
|
1301
|
+
await recordFailedInvocation({
|
|
1302
|
+
tracker,
|
|
1303
|
+
reservation,
|
|
1304
|
+
trace,
|
|
1305
|
+
phase: "finalize",
|
|
1306
|
+
role: "finalizer",
|
|
1307
|
+
provider: config.provider,
|
|
1308
|
+
model: config.finalizerModel,
|
|
1309
|
+
error
|
|
1310
|
+
});
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
const delta = tracker.record(result, { provider: config.provider, model: config.finalizerModel }, reservation);
|
|
1314
|
+
const usageRecord = buildUsageRecord({
|
|
1315
|
+
phase: "finalize",
|
|
1316
|
+
role: "finalizer",
|
|
1317
|
+
result,
|
|
1318
|
+
delta,
|
|
1319
|
+
provider: result.provider ?? config.provider,
|
|
1320
|
+
model: result.model ?? config.finalizerModel
|
|
1321
|
+
});
|
|
1322
|
+
receiptUsage.push(usageRecord);
|
|
1323
|
+
await trace.writeUsage(usageRecord);
|
|
1324
|
+
await guards.afterCall("finalization call");
|
|
1325
|
+
finalModel = result.model;
|
|
1326
|
+
finalProvider = result.provider;
|
|
1327
|
+
return { answer: result.text, promptRef };
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
],
|
|
1331
|
+
concurrency: 1,
|
|
1332
|
+
signal,
|
|
1333
|
+
validateOutput: (output) => Boolean(output &&
|
|
1334
|
+
typeof output === "object" &&
|
|
1335
|
+
typeof output.answer === "string"),
|
|
1336
|
+
onResult: async (_output, context) => {
|
|
1337
|
+
if (context.reused)
|
|
1338
|
+
await restoreCheckpointUsage(context.receiptUsage, tracker, trace);
|
|
1339
|
+
if (enabled) {
|
|
1340
|
+
await trace.event({ type: "finalized", winner_id: winner.id, model: finalModel, provider: finalProvider, prompt_ref: promptRef });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
return finalized.answer;
|
|
1028
1345
|
}
|
|
1029
|
-
function resultMetadata(result, config,
|
|
1346
|
+
function resultMetadata(result, config, promptRef) {
|
|
1030
1347
|
return compactMetadata({
|
|
1031
1348
|
model: result.model,
|
|
1032
1349
|
provider: result.provider,
|
|
@@ -1038,13 +1355,65 @@ function resultMetadata(result, config, prompt) {
|
|
|
1038
1355
|
latencyMs: result.latencyMs,
|
|
1039
1356
|
retryCount: result.retryCount,
|
|
1040
1357
|
rawOutput: config.output.includeRawModelOutputs ? result.raw : undefined,
|
|
1041
|
-
|
|
1358
|
+
promptRef: config.output.includePrompts ? promptRef : undefined,
|
|
1042
1359
|
createdAt: new Date().toISOString()
|
|
1043
1360
|
});
|
|
1044
1361
|
}
|
|
1045
1362
|
function compactMetadata(metadata) {
|
|
1046
1363
|
return Object.fromEntries(Object.entries(metadata).filter(([, value]) => value !== undefined));
|
|
1047
1364
|
}
|
|
1365
|
+
const restoredUsageByTracker = new WeakMap();
|
|
1366
|
+
async function restoreCheckpointUsage(usage, tracker, trace) {
|
|
1367
|
+
if (usage === undefined)
|
|
1368
|
+
return;
|
|
1369
|
+
if (!Array.isArray(usage)) {
|
|
1370
|
+
throw new ConfigError("Trace-v2 checkpoint usage must be an array of UsageRecord rows.", {
|
|
1371
|
+
code: "resume.usage_invalid",
|
|
1372
|
+
retryable: false
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
const ids = validatedReceiptUsageIds(usage);
|
|
1376
|
+
const restored = restoredUsageByTracker.get(tracker) ?? new Set();
|
|
1377
|
+
for (const id of ids) {
|
|
1378
|
+
if (restored.has(id)) {
|
|
1379
|
+
throw new ConfigError(`Trace-v2 usage ${id} was restored more than once.`, {
|
|
1380
|
+
code: "resume.usage_invalid",
|
|
1381
|
+
retryable: false
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
restored.add(id);
|
|
1385
|
+
}
|
|
1386
|
+
restoredUsageByTracker.set(tracker, restored);
|
|
1387
|
+
const records = usage;
|
|
1388
|
+
applyUsageRecords(tracker, records);
|
|
1389
|
+
for (const record of records)
|
|
1390
|
+
await trace.writeUsage(record);
|
|
1391
|
+
}
|
|
1392
|
+
function isCandidateCheckpoint(output) {
|
|
1393
|
+
if (!output || typeof output !== "object" || Array.isArray(output))
|
|
1394
|
+
return false;
|
|
1395
|
+
const candidate = output;
|
|
1396
|
+
return (typeof candidate.id === "string" &&
|
|
1397
|
+
candidate.id.length > 0 &&
|
|
1398
|
+
Number.isInteger(candidate.generation) &&
|
|
1399
|
+
typeof candidate.content === "string" &&
|
|
1400
|
+
typeof candidate.kind === "string" &&
|
|
1401
|
+
Boolean(candidate.metadata && typeof candidate.metadata === "object" && typeof candidate.metadata.createdAt === "string"));
|
|
1402
|
+
}
|
|
1403
|
+
function isComparisonCheckpoint(output) {
|
|
1404
|
+
if (!output || typeof output !== "object" || Array.isArray(output))
|
|
1405
|
+
return false;
|
|
1406
|
+
const comparison = output;
|
|
1407
|
+
return (typeof comparison.id === "string" &&
|
|
1408
|
+
typeof comparison.runId === "string" &&
|
|
1409
|
+
(typeof comparison.generation === "number" || comparison.generation === "final") &&
|
|
1410
|
+
typeof comparison.candidateAId === "string" &&
|
|
1411
|
+
typeof comparison.candidateBId === "string" &&
|
|
1412
|
+
(comparison.winner === "A" || comparison.winner === "B" || comparison.winner === "tie") &&
|
|
1413
|
+
typeof comparison.critiqueForA === "string" &&
|
|
1414
|
+
typeof comparison.critiqueForB === "string" &&
|
|
1415
|
+
typeof comparison.selectionReason === "string");
|
|
1416
|
+
}
|
|
1048
1417
|
function summaryProfile(profile) {
|
|
1049
1418
|
return {
|
|
1050
1419
|
n: profile.n,
|
|
@@ -1085,6 +1454,7 @@ function summaryModels(config) {
|
|
|
1085
1454
|
function buildUsageRecord(args) {
|
|
1086
1455
|
return {
|
|
1087
1456
|
schema_version: 1,
|
|
1457
|
+
usage_id: randomUUID(),
|
|
1088
1458
|
ts: new Date().toISOString(),
|
|
1089
1459
|
phase: args.phase,
|
|
1090
1460
|
role: args.role,
|
|
@@ -1122,7 +1492,8 @@ function enforceBudget(config) {
|
|
|
1122
1492
|
invalidJsonRetries: config.retry.invalidJsonRetries,
|
|
1123
1493
|
includeFinalizer: Boolean(config.finalizerModel)
|
|
1124
1494
|
});
|
|
1125
|
-
const
|
|
1495
|
+
const explicitFinalCalls = config.rank ? configuredFinalPairCount(config.profile.n, config.rank) : plan.final_judge_calls;
|
|
1496
|
+
const plannedCalls = plan.calls - plan.final_judge_calls + explicitFinalCalls + plan.finalizer_calls;
|
|
1126
1497
|
if (config.budget?.maxCalls !== undefined && plannedCalls > config.budget.maxCalls) {
|
|
1127
1498
|
throw new BudgetExceededError(`Planned call count ${plannedCalls} exceeds maxCalls ${config.budget.maxCalls}.`, {
|
|
1128
1499
|
code: "budget.planned_calls_exceeded",
|
|
@@ -1130,6 +1501,64 @@ function enforceBudget(config) {
|
|
|
1130
1501
|
});
|
|
1131
1502
|
}
|
|
1132
1503
|
}
|
|
1504
|
+
function configuredFinalPairs(candidateIds, rank, rng) {
|
|
1505
|
+
if (rank.mode === "k-regular") {
|
|
1506
|
+
if (rank.k === undefined) {
|
|
1507
|
+
throw new ConfigError("rank.mode k-regular requires rank.k.", { code: "rank.k_required", retryable: false });
|
|
1508
|
+
}
|
|
1509
|
+
const pairs = makeKRegularPairs(candidateIds, rank.k, rng);
|
|
1510
|
+
assertRankCallLimit(pairs.length, rank.maxCalls);
|
|
1511
|
+
return pairs;
|
|
1512
|
+
}
|
|
1513
|
+
const pairs = [];
|
|
1514
|
+
for (let left = 0; left < candidateIds.length; left += 1) {
|
|
1515
|
+
for (let right = left + 1; right < candidateIds.length; right += 1) {
|
|
1516
|
+
pairs.push({ a: candidateIds[left], b: candidateIds[right] });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (pairs.length > 100 && (rank.maxCalls === undefined || rank.maxCalls < pairs.length)) {
|
|
1520
|
+
throw new ConfigError(`All-pairs final ranking requires explicit rank.maxCalls >= ${pairs.length}.`, {
|
|
1521
|
+
code: "rank.max_calls_required",
|
|
1522
|
+
retryable: false,
|
|
1523
|
+
fix: "Raise rank.maxCalls or select k-regular mode."
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
assertRankCallLimit(pairs.length, rank.maxCalls);
|
|
1527
|
+
return rng.shuffle(pairs);
|
|
1528
|
+
}
|
|
1529
|
+
function configuredFinalPairCount(n, rank) {
|
|
1530
|
+
if (rank.mode === "all-pairs") {
|
|
1531
|
+
const calls = (n * (n - 1)) / 2;
|
|
1532
|
+
if (calls > 100 && (rank.maxCalls === undefined || rank.maxCalls < calls)) {
|
|
1533
|
+
throw new ConfigError(`All-pairs final ranking requires explicit rank.maxCalls >= ${calls}.`, {
|
|
1534
|
+
code: "rank.max_calls_required",
|
|
1535
|
+
retryable: false
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
assertRankCallLimit(calls, rank.maxCalls);
|
|
1539
|
+
return calls;
|
|
1540
|
+
}
|
|
1541
|
+
if (rank.k === undefined) {
|
|
1542
|
+
throw new ConfigError("rank.mode k-regular requires rank.k.", { code: "rank.k_required", retryable: false });
|
|
1543
|
+
}
|
|
1544
|
+
if (rank.k >= n || (n * rank.k) % 2 !== 0) {
|
|
1545
|
+
throw new ConfigError(`Invalid k-regular final ranking for n=${n}, k=${rank.k}.`, {
|
|
1546
|
+
code: "rank.k_invalid",
|
|
1547
|
+
retryable: false
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
const calls = (n * rank.k) / 2;
|
|
1551
|
+
assertRankCallLimit(calls, rank.maxCalls);
|
|
1552
|
+
return calls;
|
|
1553
|
+
}
|
|
1554
|
+
function assertRankCallLimit(calls, maxCalls) {
|
|
1555
|
+
if (maxCalls !== undefined && calls > maxCalls) {
|
|
1556
|
+
throw new ConfigError(`Final ranking requires ${calls} calls, exceeding rank.maxCalls ${maxCalls}.`, {
|
|
1557
|
+
code: "rank.max_calls_exceeded",
|
|
1558
|
+
retryable: false
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1133
1562
|
function cloneUsage(usage) {
|
|
1134
1563
|
return {
|
|
1135
1564
|
...emptyUsage(),
|