@semiont/jobs 0.5.34 → 0.5.35
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/dist/index.d.ts +130 -18
- package/dist/index.js +241 -108
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +281 -154
- package/dist/worker-main.js.map +1 -1
- package/package.json +8 -8
package/dist/worker-main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, busRequest, isArray, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, textSourceOf, yieldsGeometryOf, decodeRepresentation, GENERATABLE_MEDIA_TYPES, BusRequestError, locate, createFragmentSelector, deriveViews, annotationId, estimateTokens,
|
|
1
|
+
import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, RETRY_RULES, busRequest, isArray, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, textSourceOf, yieldsGeometryOf, decodeRepresentation, GENERATABLE_MEDIA_TYPES, BusRequestError, locate, createFragmentSelector, deriveViews, annotationId, estimateTokens, reconcileSelector, getLocaleEnglishName, cutChunk, chunkText } from '@semiont/core';
|
|
2
2
|
import { archivistContentReads, extractPdfTextLayer, withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
|
|
3
3
|
import { withSpan, SpanKind, recordJobOutcome, recordAnchorOutcome, recordDetectionCall } from '@semiont/observability';
|
|
4
4
|
import { createInferenceClient, StructuredReadError } from '@semiont/inference';
|
|
@@ -9208,31 +9208,22 @@ var require_cjs = __commonJS({
|
|
|
9208
9208
|
|
|
9209
9209
|
// src/job-claim-adapter.ts
|
|
9210
9210
|
var import_rxjs = __toESM(require_cjs());
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
}
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
}
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
// exists for (.plans/BUS-ATTACH-GATE.md).
|
|
9224
|
-
state$: bus.state$,
|
|
9225
|
-
// Pass the subscription probe through so a request against a transport
|
|
9226
|
-
// whose narrowed channel set omits the replies fails fast
|
|
9227
|
-
// (`bus.unsubscribed`) instead of timing out.
|
|
9228
|
-
...bus.isSubscribed ? { isSubscribed: (channel) => bus.isSubscribed(channel) } : {}
|
|
9229
|
-
};
|
|
9211
|
+
function readUnitCursors(raw, completedUnits) {
|
|
9212
|
+
if (!isObject(raw)) return {};
|
|
9213
|
+
const done = new Set(completedUnits);
|
|
9214
|
+
const cursors = {};
|
|
9215
|
+
for (const [unit, value] of Object.entries(raw)) {
|
|
9216
|
+
if (done.has(unit) || !isObject(value)) continue;
|
|
9217
|
+
const { next, size, found, emitted } = value;
|
|
9218
|
+
if (!isNumber(next) || !isNumber(size) || next < 0 || size < 1) continue;
|
|
9219
|
+
if (!isNumber(found) || !isNumber(emitted) || found < 0 || emitted < 0) continue;
|
|
9220
|
+
cursors[unit] = { next, size, found, emitted };
|
|
9221
|
+
}
|
|
9222
|
+
return cursors;
|
|
9230
9223
|
}
|
|
9231
|
-
|
|
9232
|
-
// src/job-claim-adapter.ts
|
|
9233
9224
|
function createJobClaimAdapter(options) {
|
|
9234
9225
|
const { bus, jobTypes } = options;
|
|
9235
|
-
const requestBus =
|
|
9226
|
+
const requestBus = bus;
|
|
9236
9227
|
const activeJob$ = new import_rxjs.BehaviorSubject(null);
|
|
9237
9228
|
const isProcessing$ = new import_rxjs.BehaviorSubject(false);
|
|
9238
9229
|
const jobsCompleted$ = new import_rxjs.BehaviorSubject(0);
|
|
@@ -9249,6 +9240,7 @@ function createJobClaimAdapter(options) {
|
|
|
9249
9240
|
try {
|
|
9250
9241
|
const job = await busRequest(requestBus, "job:claim", { jobId: assignment.jobId }, 1e4);
|
|
9251
9242
|
const completedUnits = isArray(job.metadata?.completedUnits) ? job.metadata.completedUnits.filter(isString) : [];
|
|
9243
|
+
const unitCursors = readUnitCursors(job.metadata?.unitCursors, completedUnits);
|
|
9252
9244
|
return {
|
|
9253
9245
|
jobId: assignment.jobId,
|
|
9254
9246
|
type: assignment.type,
|
|
@@ -9256,6 +9248,7 @@ function createJobClaimAdapter(options) {
|
|
|
9256
9248
|
userId: job.metadata?.userId ?? "",
|
|
9257
9249
|
params: job.params ?? {},
|
|
9258
9250
|
completedUnits,
|
|
9251
|
+
unitCursors,
|
|
9259
9252
|
// Absent or malformed metadata reads as "no budget left" — a worker
|
|
9260
9253
|
// that cannot see the budget must not claim a retry is coming.
|
|
9261
9254
|
retryCount: isNumber(job.metadata?.retryCount) ? job.metadata.retryCount : 0,
|
|
@@ -9274,7 +9267,7 @@ function createJobClaimAdapter(options) {
|
|
|
9274
9267
|
if (started) return;
|
|
9275
9268
|
started = true;
|
|
9276
9269
|
bus.addChannels?.(["job:queued"]);
|
|
9277
|
-
jobSubscription = bus.
|
|
9270
|
+
jobSubscription = bus.stream("job:queued").subscribe((event) => {
|
|
9278
9271
|
lastQueuedEventAt = Date.now();
|
|
9279
9272
|
const jobType = event.jobType;
|
|
9280
9273
|
if (jobTypes.length > 0 && !jobTypes.includes(jobType)) return;
|
|
@@ -9452,16 +9445,39 @@ function classifyFailure(error) {
|
|
|
9452
9445
|
if (name === "APIUserAbortError" || name === "AbortError") return "transient";
|
|
9453
9446
|
const status = isNumber(error.status) ? error.status : void 0;
|
|
9454
9447
|
if (status !== void 0) {
|
|
9455
|
-
if (
|
|
9448
|
+
if (RETRY_RULES.job.retryable({ status })) return "transient";
|
|
9456
9449
|
if (status >= 400) return "deterministic";
|
|
9457
9450
|
}
|
|
9458
9451
|
return void 0;
|
|
9459
9452
|
}
|
|
9460
9453
|
|
|
9454
|
+
// src/workers/detection/chunk-size-controller.ts
|
|
9455
|
+
var DEFAULT_CHUNK_SIZING_POLICY = {
|
|
9456
|
+
growBelow: 0.5,
|
|
9457
|
+
shrinkAbove: 0.8,
|
|
9458
|
+
growFactor: 1.5,
|
|
9459
|
+
shrinkFactor: 0.7
|
|
9460
|
+
};
|
|
9461
|
+
function clamp(value, lo, hi) {
|
|
9462
|
+
return Math.max(lo, Math.min(hi, value));
|
|
9463
|
+
}
|
|
9464
|
+
function nextChunkSize(outcome, current, bounds, policy = DEFAULT_CHUNK_SIZING_POLICY) {
|
|
9465
|
+
const grow = () => clamp(Math.floor(current * policy.growFactor), bounds.floor, bounds.ceiling);
|
|
9466
|
+
const shrink = () => clamp(Math.floor(current * policy.shrinkFactor), bounds.floor, bounds.ceiling);
|
|
9467
|
+
const hold = () => clamp(current, bounds.floor, bounds.ceiling);
|
|
9468
|
+
if (outcome.truncated) return shrink();
|
|
9469
|
+
if (outcome.outputTokens === void 0) return hold();
|
|
9470
|
+
if (bounds.outputBudget <= 0) return hold();
|
|
9471
|
+
const utilization = outcome.outputTokens / bounds.outputBudget;
|
|
9472
|
+
if (utilization < policy.growBelow) return grow();
|
|
9473
|
+
if (utilization > policy.shrinkAbove) return shrink();
|
|
9474
|
+
return hold();
|
|
9475
|
+
}
|
|
9476
|
+
|
|
9461
9477
|
// src/workers/detection/detection-chunking.ts
|
|
9462
|
-
function assertNotTruncated(response, label,
|
|
9478
|
+
function assertNotTruncated(response, label, at, totalChars, outputBudget) {
|
|
9463
9479
|
if (response.stopReason === "max_tokens") {
|
|
9464
|
-
throw new DeterministicJobError(`${label} response truncated (max_tokens)
|
|
9480
|
+
throw new DeterministicJobError(`${label} response truncated (max_tokens) at character ${at} of ${totalChars} despite the derived output budget of ${outputBudget} tokens \u2014 failing the job rather than under-reporting annotations.`);
|
|
9465
9481
|
}
|
|
9466
9482
|
}
|
|
9467
9483
|
var SELECTOR_CONTEXT_CHARS = 64;
|
|
@@ -9512,6 +9528,7 @@ function deriveDetectionBudget(limits, scaffoldTokens, typesPerCall) {
|
|
|
9512
9528
|
outputBudget = durationSafeOutput;
|
|
9513
9529
|
}
|
|
9514
9530
|
}
|
|
9531
|
+
const capacityInput = contextTokens - scaffoldTokens - outputBudget;
|
|
9515
9532
|
inputBudget = Math.min(inputBudget, Math.floor(outputBudget / (2 * typesPerCall)));
|
|
9516
9533
|
if (inputBudget <= OVERLAP_TOKENS) {
|
|
9517
9534
|
throw new Error(
|
|
@@ -9520,15 +9537,41 @@ function deriveDetectionBudget(limits, scaffoldTokens, typesPerCall) {
|
|
|
9520
9537
|
}
|
|
9521
9538
|
return {
|
|
9522
9539
|
chunking: { chunkSize: inputBudget, overlap: OVERLAP_TOKENS },
|
|
9523
|
-
outputBudget
|
|
9540
|
+
outputBudget,
|
|
9541
|
+
bounds: {
|
|
9542
|
+
// Two overlaps: at the floor a chunk is still half text it has not seen
|
|
9543
|
+
// before, so the cursor keeps making progress rather than re-reading its
|
|
9544
|
+
// own tail. Clamped under the opening size, because a cramped window can
|
|
9545
|
+
// derive an opening below even that — and a floor above the opening would
|
|
9546
|
+
// clamp every proposal upward, silently reversing the sizer.
|
|
9547
|
+
floor: Math.min(inputBudget, 2 * OVERLAP_TOKENS),
|
|
9548
|
+
// Never below the opening: a degenerate window can make the fit
|
|
9549
|
+
// arithmetic smaller than the size already chosen, and a ceiling under
|
|
9550
|
+
// the opening would clamp every proposal DOWN on the first call.
|
|
9551
|
+
ceiling: Math.max(inputBudget, capacityInput),
|
|
9552
|
+
outputBudget
|
|
9553
|
+
}
|
|
9524
9554
|
};
|
|
9525
9555
|
}
|
|
9556
|
+
async function runAdaptiveChunks(text, budget, onChunk, resume) {
|
|
9557
|
+
let at = resume?.next ?? 0;
|
|
9558
|
+
let size = resume ? nextChunkSize({ truncated: true }, resume.size, budget.bounds) : budget.chunking.chunkSize;
|
|
9559
|
+
while (at < text.length) {
|
|
9560
|
+
const { piece, next } = cutChunk(text, at, { chunkSize: size, overlap: budget.chunking.overlap });
|
|
9561
|
+
const outcome = await onChunk({ piece, size, at, next, totalChars: text.length });
|
|
9562
|
+
at = next;
|
|
9563
|
+
size = nextChunkSize(outcome, size, budget.bounds);
|
|
9564
|
+
}
|
|
9565
|
+
}
|
|
9526
9566
|
var MAX_SUBDIVISION_DEPTH = 2;
|
|
9527
9567
|
function unknownUnreadable(error) {
|
|
9528
9568
|
return error instanceof StructuredReadError && error.stopReason === "unknown";
|
|
9529
9569
|
}
|
|
9570
|
+
function unreadableDespiteFinishing(error) {
|
|
9571
|
+
return error instanceof StructuredReadError && error.stopReason === "end_turn";
|
|
9572
|
+
}
|
|
9530
9573
|
function subdividable(error) {
|
|
9531
|
-
return error instanceof InferenceTimeoutError || truncation(error) || unknownUnreadable(error);
|
|
9574
|
+
return error instanceof InferenceTimeoutError || truncation(error) || unknownUnreadable(error) || unreadableDespiteFinishing(error);
|
|
9532
9575
|
}
|
|
9533
9576
|
function truncation(error) {
|
|
9534
9577
|
return error instanceof DeterministicJobError || error instanceof StructuredReadError && error.stopReason === "max_tokens";
|
|
@@ -9540,10 +9583,19 @@ function outcomeOf(error) {
|
|
|
9540
9583
|
return "error";
|
|
9541
9584
|
}
|
|
9542
9585
|
async function callChunkSubdividing(label, chunk, chunking, call, logger2, onUnderReport, onCounted) {
|
|
9586
|
+
let outputTokens = 0;
|
|
9587
|
+
let sizeShaped = false;
|
|
9588
|
+
let callsMade = 0;
|
|
9589
|
+
let callsMeasured = 0;
|
|
9543
9590
|
async function recorded(piece, depth, reroll) {
|
|
9544
9591
|
const start = performance.now();
|
|
9545
9592
|
try {
|
|
9546
9593
|
const result = await call(piece);
|
|
9594
|
+
callsMade += 1;
|
|
9595
|
+
if (result.usage) {
|
|
9596
|
+
callsMeasured += 1;
|
|
9597
|
+
outputTokens += result.usage.outputTokens;
|
|
9598
|
+
}
|
|
9547
9599
|
recordDetectionCall({
|
|
9548
9600
|
label,
|
|
9549
9601
|
pieceChars: piece.length,
|
|
@@ -9574,6 +9626,7 @@ async function callChunkSubdividing(label, chunk, chunking, call, logger2, onUnd
|
|
|
9574
9626
|
return (await recorded(piece, depth, false)).items;
|
|
9575
9627
|
} catch (error) {
|
|
9576
9628
|
if (!subdividable(error)) throw error;
|
|
9629
|
+
sizeShaped = true;
|
|
9577
9630
|
const half = Math.floor(chunkSize / 2);
|
|
9578
9631
|
const pieces = chunkText(piece, { chunkSize: half, overlap: chunking.overlap });
|
|
9579
9632
|
const shrinks = pieces.length > 1 || pieces[0] !== piece;
|
|
@@ -9609,7 +9662,12 @@ async function callChunkSubdividing(label, chunk, chunking, call, logger2, onUnd
|
|
|
9609
9662
|
return collected;
|
|
9610
9663
|
}
|
|
9611
9664
|
}
|
|
9612
|
-
|
|
9665
|
+
const items = await attempt(chunk, chunking.chunkSize, 0);
|
|
9666
|
+
const measured = callsMade > 0 && callsMeasured === callsMade;
|
|
9667
|
+
return {
|
|
9668
|
+
items,
|
|
9669
|
+
outcome: { truncated: sizeShaped, ...measured ? { outputTokens } : {} }
|
|
9670
|
+
};
|
|
9613
9671
|
}
|
|
9614
9672
|
function languageName(tag) {
|
|
9615
9673
|
return getLocaleEnglishName(tag) || tag;
|
|
@@ -10110,33 +10168,37 @@ function logAnchorMethod(motivation, exact, anchorMethod) {
|
|
|
10110
10168
|
}
|
|
10111
10169
|
|
|
10112
10170
|
// src/workers/annotation-detection.ts
|
|
10113
|
-
async function detectInChunks(client, content, buildPrompt, motivation, elementSchema, parse, onActivity, onChunkResults) {
|
|
10171
|
+
async function detectInChunks(client, content, buildPrompt, motivation, elementSchema, parse, onActivity, resume, onChunkResults) {
|
|
10114
10172
|
const limits = await client.limits();
|
|
10115
10173
|
const scaffoldTokens = estimateTokens(buildPrompt(""));
|
|
10116
|
-
const
|
|
10117
|
-
const
|
|
10174
|
+
const budget = deriveDetectionBudget(limits, scaffoldTokens, 1);
|
|
10175
|
+
const { outputBudget } = budget;
|
|
10118
10176
|
const collected = [];
|
|
10119
|
-
|
|
10120
|
-
const items = await callChunkSubdividing(
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10177
|
+
await runAdaptiveChunks(content, budget, async ({ piece: chunk, size, at, next, totalChars }) => {
|
|
10178
|
+
const { items, outcome } = await callChunkSubdividing(
|
|
10179
|
+
motivation,
|
|
10180
|
+
chunk,
|
|
10181
|
+
{ chunkSize: size, overlap: budget.chunking.overlap },
|
|
10182
|
+
async (piece) => {
|
|
10183
|
+
const response = await boundedGenerateStructured(
|
|
10184
|
+
client,
|
|
10185
|
+
buildPrompt(piece),
|
|
10186
|
+
outputBudget,
|
|
10187
|
+
DETECTION_TEMPERATURE,
|
|
10188
|
+
elementSchema,
|
|
10189
|
+
// Still alive, same position (a long single call is otherwise silent).
|
|
10190
|
+
() => onActivity?.(at, totalChars)
|
|
10191
|
+
);
|
|
10192
|
+
assertNotTruncated(response, `${motivation} detection`, at, totalChars, outputBudget);
|
|
10193
|
+
return { items: response.items, ...response.usage ? { usage: response.usage } : {} };
|
|
10194
|
+
}
|
|
10195
|
+
);
|
|
10133
10196
|
const fromChunk = parse(items);
|
|
10134
10197
|
collected.push(...fromChunk);
|
|
10135
|
-
await onChunkResults?.(fromChunk);
|
|
10136
|
-
if (
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
}
|
|
10198
|
+
await onChunkResults?.(fromChunk, { next, size });
|
|
10199
|
+
if (next < totalChars) onActivity?.(next, totalChars);
|
|
10200
|
+
return outcome;
|
|
10201
|
+
}, resume);
|
|
10140
10202
|
return collected;
|
|
10141
10203
|
}
|
|
10142
10204
|
var AnnotationDetection = class {
|
|
@@ -10148,7 +10210,7 @@ var AnnotationDetection = class {
|
|
|
10148
10210
|
* (source-resource locale). See `types.ts` "Locale conventions" for the
|
|
10149
10211
|
* full discussion.
|
|
10150
10212
|
*/
|
|
10151
|
-
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage, onActivity, onChunkResults) {
|
|
10213
|
+
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage, onActivity, resume, onChunkResults) {
|
|
10152
10214
|
return detectInChunks(
|
|
10153
10215
|
client,
|
|
10154
10216
|
content,
|
|
@@ -10157,6 +10219,7 @@ var AnnotationDetection = class {
|
|
|
10157
10219
|
COMMENT_ELEMENT_SCHEMA,
|
|
10158
10220
|
(items) => MotivationParsers.parseComments(items, content),
|
|
10159
10221
|
onActivity,
|
|
10222
|
+
resume,
|
|
10160
10223
|
onChunkResults
|
|
10161
10224
|
);
|
|
10162
10225
|
}
|
|
@@ -10167,7 +10230,7 @@ var AnnotationDetection = class {
|
|
|
10167
10230
|
* applies, used in the prompt so the LLM analyzes non-English source
|
|
10168
10231
|
* correctly.
|
|
10169
10232
|
*/
|
|
10170
|
-
static async detectHighlights(content, client, instructions, density, sourceLanguage, onActivity, onChunkResults) {
|
|
10233
|
+
static async detectHighlights(content, client, instructions, density, sourceLanguage, onActivity, resume, onChunkResults) {
|
|
10171
10234
|
return detectInChunks(
|
|
10172
10235
|
client,
|
|
10173
10236
|
content,
|
|
@@ -10176,6 +10239,7 @@ var AnnotationDetection = class {
|
|
|
10176
10239
|
HIGHLIGHT_ELEMENT_SCHEMA,
|
|
10177
10240
|
(items) => MotivationParsers.parseHighlights(items, content),
|
|
10178
10241
|
onActivity,
|
|
10242
|
+
resume,
|
|
10179
10243
|
onChunkResults
|
|
10180
10244
|
);
|
|
10181
10245
|
}
|
|
@@ -10186,7 +10250,7 @@ var AnnotationDetection = class {
|
|
|
10186
10250
|
* (annotation body locale). `sourceLanguage` is the locale of the content
|
|
10187
10251
|
* being analyzed (source-resource locale).
|
|
10188
10252
|
*/
|
|
10189
|
-
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage, onActivity, onChunkResults) {
|
|
10253
|
+
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage, onActivity, resume, onChunkResults) {
|
|
10190
10254
|
return detectInChunks(
|
|
10191
10255
|
client,
|
|
10192
10256
|
content,
|
|
@@ -10195,6 +10259,7 @@ var AnnotationDetection = class {
|
|
|
10195
10259
|
ASSESSMENT_ELEMENT_SCHEMA,
|
|
10196
10260
|
(items) => MotivationParsers.parseAssessments(items, content),
|
|
10197
10261
|
onActivity,
|
|
10262
|
+
resume,
|
|
10198
10263
|
onChunkResults
|
|
10199
10264
|
);
|
|
10200
10265
|
}
|
|
@@ -10210,7 +10275,7 @@ var AnnotationDetection = class {
|
|
|
10210
10275
|
* identifiers, not LLM-generated text — so it's consumed at the body-stamp
|
|
10211
10276
|
* site, not here.
|
|
10212
10277
|
*/
|
|
10213
|
-
static async detectTags(content, client, schema, category, sourceLanguage, onActivity, onChunkResults) {
|
|
10278
|
+
static async detectTags(content, client, schema, category, sourceLanguage, onActivity, resume, onChunkResults) {
|
|
10214
10279
|
const categoryInfo = schema.tags.find((t) => t.name === category);
|
|
10215
10280
|
if (!categoryInfo) {
|
|
10216
10281
|
throw new Error(`Invalid category "${category}" for schema ${schema.id}`);
|
|
@@ -10232,7 +10297,8 @@ var AnnotationDetection = class {
|
|
|
10232
10297
|
TAG_ELEMENT_SCHEMA,
|
|
10233
10298
|
(items) => MotivationParsers.parseTags(items),
|
|
10234
10299
|
onActivity,
|
|
10235
|
-
|
|
10300
|
+
resume,
|
|
10301
|
+
onChunkResults ? async (raw, cursor) => onChunkResults(MotivationParsers.validateTagOffsets(raw, content, category), cursor) : void 0
|
|
10236
10302
|
);
|
|
10237
10303
|
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
10238
10304
|
}
|
|
@@ -10284,7 +10350,7 @@ ${piece}
|
|
|
10284
10350
|
}
|
|
10285
10351
|
return counted;
|
|
10286
10352
|
}
|
|
10287
|
-
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger2, sourceLanguage, onActivity, onUnderReport, onCounted, onChunkResults) {
|
|
10353
|
+
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger2, sourceLanguage, onActivity, onUnderReport, onCounted, resume, onChunkResults) {
|
|
10288
10354
|
const entityTypesDescription = entityTypes.map((et) => {
|
|
10289
10355
|
if (typeof et === "string") {
|
|
10290
10356
|
return et;
|
|
@@ -10336,42 +10402,55 @@ Example output:
|
|
|
10336
10402
|
const limits = await client.limits();
|
|
10337
10403
|
const verifyYield = client.verifyDetectionYield;
|
|
10338
10404
|
const scaffoldTokens = estimateTokens(buildPrompt(""));
|
|
10339
|
-
const
|
|
10340
|
-
const
|
|
10405
|
+
const budget = deriveDetectionBudget(limits, scaffoldTokens, entityTypes.length);
|
|
10406
|
+
const { chunking, outputBudget } = budget;
|
|
10341
10407
|
logger2.debug("Sending entity extraction request", {
|
|
10342
10408
|
entityTypes: entityTypesDescription,
|
|
10343
|
-
|
|
10344
|
-
|
|
10409
|
+
chars: exact.length,
|
|
10410
|
+
// The size the run OPENS at, and how far measured yield may move it. The
|
|
10411
|
+
// chunk COUNT is deliberately absent: with sizing decided as the run goes,
|
|
10412
|
+
// there is no honest total until the cursor reaches the end.
|
|
10413
|
+
openingChunkSizeTokens: chunking.chunkSize,
|
|
10414
|
+
ceilingChunkSizeTokens: budget.bounds.ceiling,
|
|
10345
10415
|
outputBudget
|
|
10346
10416
|
});
|
|
10347
10417
|
const collected = [];
|
|
10348
|
-
|
|
10349
|
-
const items = await callChunkSubdividing(
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10418
|
+
await runAdaptiveChunks(exact, budget, async ({ piece: chunk, size, at, next, totalChars }) => {
|
|
10419
|
+
const { items, outcome } = await callChunkSubdividing(
|
|
10420
|
+
"reference",
|
|
10421
|
+
chunk,
|
|
10422
|
+
{ chunkSize: size, overlap: chunking.overlap },
|
|
10423
|
+
async (piece) => {
|
|
10424
|
+
const response = await boundedGenerateStructured(
|
|
10425
|
+
client,
|
|
10426
|
+
buildPrompt(piece),
|
|
10427
|
+
outputBudget,
|
|
10428
|
+
DETECTION_TEMPERATURE,
|
|
10429
|
+
ENTITY_ELEMENT_SCHEMA,
|
|
10430
|
+
// Still alive, same position: a long single call would otherwise emit
|
|
10431
|
+
// nothing at all between start and finish.
|
|
10432
|
+
() => onActivity?.(at, totalChars),
|
|
10433
|
+
logger2
|
|
10434
|
+
);
|
|
10435
|
+
logger2.debug("Got entity extraction response", {
|
|
10436
|
+
at,
|
|
10437
|
+
totalChars,
|
|
10438
|
+
chunkSizeTokens: size,
|
|
10439
|
+
pieceChars: piece.length,
|
|
10440
|
+
items: response.items.length
|
|
10441
|
+
});
|
|
10442
|
+
assertNotTruncated(response, "Entity extraction", at, totalChars, outputBudget);
|
|
10443
|
+
const counted = verifyYield ? await assertYieldNotCollapsed(client, piece, response.items, entityTypesDescription, logger2) : void 0;
|
|
10444
|
+
return {
|
|
10445
|
+
items: response.items,
|
|
10446
|
+
...response.usage ? { usage: response.usage } : {},
|
|
10447
|
+
...counted !== void 0 ? { counted } : {}
|
|
10448
|
+
};
|
|
10449
|
+
},
|
|
10450
|
+
logger2,
|
|
10451
|
+
onUnderReport,
|
|
10452
|
+
onCounted
|
|
10453
|
+
);
|
|
10375
10454
|
const fromChunk = [];
|
|
10376
10455
|
for (const e of items) {
|
|
10377
10456
|
if (isObject(e) && isString(e.exact) && isString(e.entityType)) {
|
|
@@ -10386,11 +10465,10 @@ Example output:
|
|
|
10386
10465
|
}
|
|
10387
10466
|
}
|
|
10388
10467
|
collected.push(...fromChunk);
|
|
10389
|
-
await onChunkResults?.(fromChunk);
|
|
10390
|
-
if (
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
}
|
|
10468
|
+
await onChunkResults?.(fromChunk, { next, size });
|
|
10469
|
+
if (next < totalChars) onActivity?.(next, totalChars);
|
|
10470
|
+
return outcome;
|
|
10471
|
+
}, resume);
|
|
10394
10472
|
return collected;
|
|
10395
10473
|
}
|
|
10396
10474
|
function getLanguageName(locale) {
|
|
@@ -10855,13 +10933,14 @@ function buildPdfAnnotation(anchored, resourceId, userId, generator, motivation,
|
|
|
10855
10933
|
...body !== void 0 ? { body } : {}
|
|
10856
10934
|
};
|
|
10857
10935
|
}
|
|
10858
|
-
async function processHighlightJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete) {
|
|
10936
|
+
async function processHighlightJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete, resumeCursors) {
|
|
10859
10937
|
const echo = detectionEcho(params);
|
|
10860
10938
|
onProgress(10, { code: "loading" }, echo);
|
|
10861
10939
|
onProgress(30, { code: "analyzing" }, echo);
|
|
10862
10940
|
const dedupe = makeSpanDeduper();
|
|
10863
|
-
|
|
10864
|
-
let
|
|
10941
|
+
const prior = resumeCursors?.["highlighting"];
|
|
10942
|
+
let found = prior?.found ?? 0;
|
|
10943
|
+
let created = prior?.emitted ?? 0;
|
|
10865
10944
|
await AnnotationDetection.detectHighlights(
|
|
10866
10945
|
content,
|
|
10867
10946
|
inferenceClient,
|
|
@@ -10869,13 +10948,14 @@ async function processHighlightJob(content, inferenceClient, params, buildAnnota
|
|
|
10869
10948
|
params.density,
|
|
10870
10949
|
params.sourceLanguage,
|
|
10871
10950
|
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
10872
|
-
(
|
|
10873
|
-
|
|
10951
|
+
(consumedChars, totalChars) => onProgress(30 + Math.round(consumedChars / totalChars * 30), { code: "analyzing" }, echo),
|
|
10952
|
+
resumeCursors?.["highlighting"],
|
|
10953
|
+
async (matches, cursor) => {
|
|
10874
10954
|
found += matches.length;
|
|
10875
10955
|
const fresh = dedupe(matches.map((h) => buildAnnotation("highlighting", h)));
|
|
10876
10956
|
created += fresh.length;
|
|
10877
10957
|
onProgress(60, { code: "creating-annotations", count: created }, echo);
|
|
10878
|
-
await onChunkComplete(fresh);
|
|
10958
|
+
await onChunkComplete(fresh, { unit: "highlighting", cursor: { ...cursor, found, emitted: created } });
|
|
10879
10959
|
}
|
|
10880
10960
|
);
|
|
10881
10961
|
onProgress(100, { code: "complete-created", count: created, kind: "highlight" }, echo);
|
|
@@ -10890,14 +10970,15 @@ function detectionEcho(p) {
|
|
|
10890
10970
|
if (p.density !== void 0) requestParams.push({ label: "density", value: String(p.density) });
|
|
10891
10971
|
return requestParams.length > 0 ? { requestParams } : {};
|
|
10892
10972
|
}
|
|
10893
|
-
async function processCommentJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete) {
|
|
10973
|
+
async function processCommentJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete, resumeCursors) {
|
|
10894
10974
|
const echo = detectionEcho(params);
|
|
10895
10975
|
onProgress(10, { code: "loading" }, echo);
|
|
10896
10976
|
onProgress(30, { code: "analyzing" }, echo);
|
|
10897
10977
|
const bodyLanguage = params.language ?? "en";
|
|
10898
10978
|
const dedupe = makeSpanDeduper();
|
|
10899
|
-
|
|
10900
|
-
let
|
|
10979
|
+
const prior = resumeCursors?.["commenting"];
|
|
10980
|
+
let found = prior?.found ?? 0;
|
|
10981
|
+
let created = prior?.emitted ?? 0;
|
|
10901
10982
|
await AnnotationDetection.detectComments(
|
|
10902
10983
|
content,
|
|
10903
10984
|
inferenceClient,
|
|
@@ -10907,8 +10988,9 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
|
|
|
10907
10988
|
params.language,
|
|
10908
10989
|
params.sourceLanguage,
|
|
10909
10990
|
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
10910
|
-
(
|
|
10911
|
-
|
|
10991
|
+
(consumedChars, totalChars) => onProgress(30 + Math.round(consumedChars / totalChars * 30), { code: "analyzing" }, echo),
|
|
10992
|
+
resumeCursors?.["commenting"],
|
|
10993
|
+
async (comments, cursor) => {
|
|
10912
10994
|
found += comments.length;
|
|
10913
10995
|
const fresh = dedupe(comments.map(
|
|
10914
10996
|
(c) => (
|
|
@@ -10922,7 +11004,7 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
|
|
|
10922
11004
|
));
|
|
10923
11005
|
created += fresh.length;
|
|
10924
11006
|
onProgress(60, { code: "creating-annotations", count: created }, echo);
|
|
10925
|
-
await onChunkComplete(fresh);
|
|
11007
|
+
await onChunkComplete(fresh, { unit: "commenting", cursor: { ...cursor, found, emitted: created } });
|
|
10926
11008
|
}
|
|
10927
11009
|
);
|
|
10928
11010
|
onProgress(100, { code: "complete-created", count: created, kind: "comment" }, echo);
|
|
@@ -10930,14 +11012,15 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
|
|
|
10930
11012
|
result: { kind: "comment-annotation", commentsFound: found, commentsCreated: created }
|
|
10931
11013
|
};
|
|
10932
11014
|
}
|
|
10933
|
-
async function processAssessmentJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete) {
|
|
11015
|
+
async function processAssessmentJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete, resumeCursors) {
|
|
10934
11016
|
const echo = detectionEcho(params);
|
|
10935
11017
|
onProgress(10, { code: "loading" }, echo);
|
|
10936
11018
|
onProgress(30, { code: "analyzing" }, echo);
|
|
10937
11019
|
const bodyLanguage = params.language ?? "en";
|
|
10938
11020
|
const dedupe = makeSpanDeduper();
|
|
10939
|
-
|
|
10940
|
-
let
|
|
11021
|
+
const prior = resumeCursors?.["assessing"];
|
|
11022
|
+
let found = prior?.found ?? 0;
|
|
11023
|
+
let created = prior?.emitted ?? 0;
|
|
10941
11024
|
await AnnotationDetection.detectAssessments(
|
|
10942
11025
|
content,
|
|
10943
11026
|
inferenceClient,
|
|
@@ -10947,8 +11030,9 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
10947
11030
|
params.language,
|
|
10948
11031
|
params.sourceLanguage,
|
|
10949
11032
|
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
10950
|
-
(
|
|
10951
|
-
|
|
11033
|
+
(consumedChars, totalChars) => onProgress(30 + Math.round(consumedChars / totalChars * 30), { code: "analyzing" }, echo),
|
|
11034
|
+
resumeCursors?.["assessing"],
|
|
11035
|
+
async (assessments, cursor) => {
|
|
10952
11036
|
found += assessments.length;
|
|
10953
11037
|
const fresh = dedupe(assessments.map(
|
|
10954
11038
|
(a) => (
|
|
@@ -10969,7 +11053,7 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
10969
11053
|
));
|
|
10970
11054
|
created += fresh.length;
|
|
10971
11055
|
onProgress(60, { code: "creating-annotations", count: created }, echo);
|
|
10972
|
-
await onChunkComplete(fresh);
|
|
11056
|
+
await onChunkComplete(fresh, { unit: "assessing", cursor: { ...cursor, found, emitted: created } });
|
|
10973
11057
|
}
|
|
10974
11058
|
);
|
|
10975
11059
|
onProgress(100, { code: "complete-created", count: created, kind: "assessment" }, echo);
|
|
@@ -10977,12 +11061,12 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
10977
11061
|
result: { kind: "assessment-annotation", assessmentsFound: found, assessmentsCreated: created }
|
|
10978
11062
|
};
|
|
10979
11063
|
}
|
|
10980
|
-
async function processReferenceJob(content, inferenceClient, params, buildAnnotation, onProgress, logger2, onUnitComplete, signal, onChunkComplete) {
|
|
11064
|
+
async function processReferenceJob(content, inferenceClient, params, buildAnnotation, onProgress, logger2, onUnitComplete, signal, onChunkComplete, resumeCursors) {
|
|
10981
11065
|
const entityTypeNames = params.entityTypes.map(String);
|
|
10982
11066
|
const requestParams = [{ label: "entity-types", value: entityTypeNames.join(", ") }];
|
|
10983
11067
|
const completedItems = [];
|
|
10984
|
-
let totalFound = 0;
|
|
10985
|
-
let totalEmitted = 0;
|
|
11068
|
+
let totalFound = Object.values(resumeCursors ?? {}).reduce((n, c) => n + c.found, 0);
|
|
11069
|
+
let totalEmitted = Object.values(resumeCursors ?? {}).reduce((n, c) => n + c.emitted, 0);
|
|
10986
11070
|
let errors = 0;
|
|
10987
11071
|
let totalUnderReportedPieces = 0;
|
|
10988
11072
|
let totalExpected = 0;
|
|
@@ -11011,8 +11095,9 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
11011
11095
|
{ type: "TextualBody", value: entityTypeName, purpose: "tagging", format: "text/plain", language: bodyLanguage }
|
|
11012
11096
|
];
|
|
11013
11097
|
const dedupe = makeSpanDeduper();
|
|
11014
|
-
|
|
11015
|
-
let
|
|
11098
|
+
const priorUnit = resumeCursors?.[entityTypeName];
|
|
11099
|
+
let unitFound = priorUnit?.found ?? 0;
|
|
11100
|
+
let unitPersisted = priorUnit?.emitted ?? 0;
|
|
11016
11101
|
let underReported;
|
|
11017
11102
|
await extractEntities(
|
|
11018
11103
|
content,
|
|
@@ -11038,7 +11123,8 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
11038
11123
|
totalExpected += counted;
|
|
11039
11124
|
emitTypeProgress(entityTypeName);
|
|
11040
11125
|
},
|
|
11041
|
-
|
|
11126
|
+
resumeCursors?.[entityTypeName],
|
|
11127
|
+
async (chunkEntities, cursor) => {
|
|
11042
11128
|
const built = [];
|
|
11043
11129
|
for (const entity of chunkEntities) {
|
|
11044
11130
|
const reconciled = reconcileSelector(content, {
|
|
@@ -11058,9 +11144,14 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
11058
11144
|
built.push(buildAnnotation("linking", toMatch(reconciled), unresolvedBody));
|
|
11059
11145
|
}
|
|
11060
11146
|
const fresh = dedupe(built);
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11147
|
+
const nextFound = unitFound + chunkEntities.length;
|
|
11148
|
+
const nextEmitted = unitPersisted + fresh.length;
|
|
11149
|
+
await onChunkComplete?.(fresh, {
|
|
11150
|
+
unit: entityTypeName,
|
|
11151
|
+
cursor: { ...cursor, found: nextFound, emitted: nextEmitted }
|
|
11152
|
+
});
|
|
11153
|
+
unitFound = nextFound;
|
|
11154
|
+
unitPersisted = nextEmitted;
|
|
11064
11155
|
totalFound += chunkEntities.length;
|
|
11065
11156
|
totalEmitted += fresh.length;
|
|
11066
11157
|
emitTypeProgress(entityTypeName);
|
|
@@ -11092,13 +11183,13 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
11092
11183
|
}
|
|
11093
11184
|
};
|
|
11094
11185
|
}
|
|
11095
|
-
async function processTagJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete) {
|
|
11186
|
+
async function processTagJob(content, inferenceClient, params, buildAnnotation, onProgress, onChunkComplete, resumeCursors) {
|
|
11096
11187
|
onProgress(10, { code: "loading" });
|
|
11097
11188
|
onProgress(30, { code: "analyzing-tags" });
|
|
11098
11189
|
const bodyLanguage = params.language ?? "en";
|
|
11099
11190
|
const dedupe = makeSpanDeduper();
|
|
11100
11191
|
let found = 0;
|
|
11101
|
-
let created = 0;
|
|
11192
|
+
let created = Object.values(resumeCursors ?? {}).reduce((n, c) => n + c.emitted, 0);
|
|
11102
11193
|
const byCategory = {};
|
|
11103
11194
|
const completedItems = [];
|
|
11104
11195
|
for (let c = 0; c < params.categories.length; c++) {
|
|
@@ -11114,7 +11205,9 @@ async function processTagJob(content, inferenceClient, params, buildAnnotation,
|
|
|
11114
11205
|
{ code: "analyzing-tags" },
|
|
11115
11206
|
position()
|
|
11116
11207
|
);
|
|
11117
|
-
|
|
11208
|
+
const priorCategory = resumeCursors?.[category];
|
|
11209
|
+
let categoryFound = priorCategory?.found ?? 0;
|
|
11210
|
+
let categoryCreated = priorCategory?.emitted ?? 0;
|
|
11118
11211
|
await AnnotationDetection.detectTags(
|
|
11119
11212
|
content,
|
|
11120
11213
|
inferenceClient,
|
|
@@ -11123,12 +11216,13 @@ async function processTagJob(content, inferenceClient, params, buildAnnotation,
|
|
|
11123
11216
|
params.sourceLanguage,
|
|
11124
11217
|
// Liveness (chunk boundaries + in-flight heartbeat): this category's
|
|
11125
11218
|
// slice of the 30–60 band.
|
|
11126
|
-
(
|
|
11127
|
-
30 + Math.round((c +
|
|
11219
|
+
(consumedChars, totalChars) => onProgress(
|
|
11220
|
+
30 + Math.round((c + consumedChars / totalChars) / params.categories.length * 30),
|
|
11128
11221
|
{ code: "analyzing-tags" },
|
|
11129
11222
|
position()
|
|
11130
11223
|
),
|
|
11131
|
-
|
|
11224
|
+
resumeCursors?.[category],
|
|
11225
|
+
async (matches, cursor) => {
|
|
11132
11226
|
categoryFound += matches.length;
|
|
11133
11227
|
const fresh = dedupe(matches.map((t) => {
|
|
11134
11228
|
const cat = t.category ?? "unknown";
|
|
@@ -11144,7 +11238,11 @@ async function processTagJob(content, inferenceClient, params, buildAnnotation,
|
|
|
11144
11238
|
byCategory[cat] = (byCategory[cat] ?? 0) + 1;
|
|
11145
11239
|
}
|
|
11146
11240
|
onProgress(60, { code: "creating-tag-annotations", count: created });
|
|
11147
|
-
|
|
11241
|
+
categoryCreated += fresh.length;
|
|
11242
|
+
await onChunkComplete(fresh, {
|
|
11243
|
+
unit: category,
|
|
11244
|
+
cursor: { ...cursor, found: categoryFound, emitted: categoryCreated }
|
|
11245
|
+
});
|
|
11148
11246
|
}
|
|
11149
11247
|
);
|
|
11150
11248
|
found += categoryFound;
|
|
@@ -11356,7 +11454,7 @@ async function commitAnnotations(session, resourceId, annotations) {
|
|
|
11356
11454
|
if (annotations.length === 0) return void 0;
|
|
11357
11455
|
try {
|
|
11358
11456
|
await busRequest(
|
|
11359
|
-
|
|
11457
|
+
session.client.transport.actor,
|
|
11360
11458
|
"mark:commit",
|
|
11361
11459
|
{ resourceId, annotations },
|
|
11362
11460
|
MARK_COMMIT_TIMEOUT_MS
|
|
@@ -11399,6 +11497,7 @@ function startWorkerProcess(config) {
|
|
|
11399
11497
|
jobTypes: config.jobTypes
|
|
11400
11498
|
});
|
|
11401
11499
|
const completedUnitsByJob = /* @__PURE__ */ new Map();
|
|
11500
|
+
const unitCursorsByJob = /* @__PURE__ */ new Map();
|
|
11402
11501
|
let activeCancel = null;
|
|
11403
11502
|
httpTransport.actor.addChannels?.(["job:cancel-requested"]);
|
|
11404
11503
|
httpTransport.on("job:cancel-requested", (event) => {
|
|
@@ -11413,14 +11512,17 @@ function startWorkerProcess(config) {
|
|
|
11413
11512
|
logger2.info("Processing job", { jobId: job.jobId, type: job.type, resourceId: job.resourceId });
|
|
11414
11513
|
const controller = new AbortController();
|
|
11415
11514
|
activeCancel = { jobId: job.jobId, controller };
|
|
11416
|
-
handleJob(adapter, config, job, completedUnitsByJob, controller.signal).then(() => {
|
|
11515
|
+
handleJob(adapter, config, job, completedUnitsByJob, controller.signal, unitCursorsByJob).then(() => {
|
|
11417
11516
|
completedUnitsByJob.delete(job.jobId);
|
|
11517
|
+
unitCursorsByJob.delete(job.jobId);
|
|
11418
11518
|
}).catch((error) => {
|
|
11419
11519
|
const message = error instanceof Error ? error.message : String(error);
|
|
11420
11520
|
const failureClass = classifyFailure(error);
|
|
11421
11521
|
logger2.error("Job failed", { jobId: job.jobId, error: message, failureClass, stack: error instanceof Error ? error.stack : void 0 });
|
|
11422
11522
|
const completedUnits = completedUnitsByJob.get(job.jobId);
|
|
11523
|
+
const unitCursors = unitCursorsByJob.get(job.jobId);
|
|
11423
11524
|
completedUnitsByJob.delete(job.jobId);
|
|
11525
|
+
unitCursorsByJob.delete(job.jobId);
|
|
11424
11526
|
const failAnnotationId = referenceIdOf(job);
|
|
11425
11527
|
if (isJobType(job.type)) {
|
|
11426
11528
|
emitEvent(session, "job:fail", {
|
|
@@ -11430,6 +11532,10 @@ function startWorkerProcess(config) {
|
|
|
11430
11532
|
...failAnnotationId ? { annotationId: failAnnotationId } : {},
|
|
11431
11533
|
error: message,
|
|
11432
11534
|
...completedUnits && completedUnits.length > 0 ? { completedUnits } : {},
|
|
11535
|
+
// Where each unfinished unit got to. Absent rather than `{}` when
|
|
11536
|
+
// nothing was reached: an empty object would claim units were
|
|
11537
|
+
// tracked and none progressed.
|
|
11538
|
+
...unitCursors && Object.keys(unitCursors).length > 0 ? { unitCursors } : {},
|
|
11433
11539
|
...failureClass !== void 0 ? { failureClass } : {},
|
|
11434
11540
|
// What the commit path OBSERVED about durability, when the failure
|
|
11435
11541
|
// came from a commit at all. Present only on that path: absent means
|
|
@@ -11452,13 +11558,13 @@ function startWorkerProcess(config) {
|
|
|
11452
11558
|
adapter.start();
|
|
11453
11559
|
return adapter;
|
|
11454
11560
|
}
|
|
11455
|
-
async function handleJob(adapter, config, job, completedUnitsByJob = /* @__PURE__ */ new Map(), signal) {
|
|
11561
|
+
async function handleJob(adapter, config, job, completedUnitsByJob = /* @__PURE__ */ new Map(), signal, unitCursorsByJob = /* @__PURE__ */ new Map()) {
|
|
11456
11562
|
const start = performance.now();
|
|
11457
11563
|
let outcome = "completed";
|
|
11458
11564
|
try {
|
|
11459
11565
|
return await withSpan(
|
|
11460
11566
|
`job:${job.type}`,
|
|
11461
|
-
() => handleJobInner(adapter, config, job, completedUnitsByJob, signal),
|
|
11567
|
+
() => handleJobInner(adapter, config, job, completedUnitsByJob, signal, unitCursorsByJob),
|
|
11462
11568
|
{
|
|
11463
11569
|
kind: SpanKind.CONSUMER,
|
|
11464
11570
|
attrs: {
|
|
@@ -11475,7 +11581,7 @@ async function handleJob(adapter, config, job, completedUnitsByJob = /* @__PURE_
|
|
|
11475
11581
|
recordJobOutcome(job.type, outcome, performance.now() - start);
|
|
11476
11582
|
}
|
|
11477
11583
|
}
|
|
11478
|
-
async function handleJobInner(adapter, config, job, completedUnitsByJob, signal) {
|
|
11584
|
+
async function handleJobInner(adapter, config, job, completedUnitsByJob, signal, unitCursorsByJob = /* @__PURE__ */ new Map()) {
|
|
11479
11585
|
const { session, inferenceClient, generator } = config;
|
|
11480
11586
|
const { userId, jobId } = job;
|
|
11481
11587
|
if (!isJobType(job.type)) {
|
|
@@ -11485,10 +11591,12 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11485
11591
|
const jobType = job.type;
|
|
11486
11592
|
const resourceId$1 = resourceId(job.resourceId);
|
|
11487
11593
|
const annotationId = referenceIdOf(job);
|
|
11594
|
+
const attempt = job.retryCount + 1;
|
|
11488
11595
|
const lifecycleBase = {
|
|
11489
11596
|
resourceId: resourceId$1,
|
|
11490
11597
|
jobId,
|
|
11491
11598
|
jobType,
|
|
11599
|
+
attempt,
|
|
11492
11600
|
...annotationId ? { annotationId } : {}
|
|
11493
11601
|
};
|
|
11494
11602
|
let durability;
|
|
@@ -11548,6 +11656,17 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11548
11656
|
}).catch(() => {
|
|
11549
11657
|
});
|
|
11550
11658
|
};
|
|
11659
|
+
const unitCursors = /* @__PURE__ */ new Map();
|
|
11660
|
+
const commitChunk = async (annotations, checkpoint) => {
|
|
11661
|
+
record(await commitAnnotations(session, String(resourceId$1), annotations));
|
|
11662
|
+
unitCursors.set(checkpoint.unit, checkpoint.cursor);
|
|
11663
|
+
unitCursorsByJob.set(job.jobId, Object.fromEntries(unitCursors));
|
|
11664
|
+
await emitEvent(session, "job:checkpoint", {
|
|
11665
|
+
jobId: job.jobId,
|
|
11666
|
+
completedUnits: [...completedUnitsByJob.get(job.jobId) ?? []],
|
|
11667
|
+
unitCursors: Object.fromEntries(unitCursors)
|
|
11668
|
+
});
|
|
11669
|
+
};
|
|
11551
11670
|
if (jobType === "highlight-annotation") {
|
|
11552
11671
|
const { result } = await processHighlightJob(
|
|
11553
11672
|
ready.text,
|
|
@@ -11556,10 +11675,11 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11556
11675
|
ready.buildAnnotation,
|
|
11557
11676
|
onProgress,
|
|
11558
11677
|
// The durability write, per chunk, awaited; folds into the terminal
|
|
11559
|
-
// durability evidence like every commit.
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11678
|
+
// durability evidence like every commit, and carries the unit's cursor.
|
|
11679
|
+
commitChunk,
|
|
11680
|
+
// …and the other direction: where an earlier attempt left each unit
|
|
11681
|
+
// (CHUNK-GRAIN-RESUME P3). Empty on a first attempt.
|
|
11682
|
+
job.unitCursors
|
|
11563
11683
|
);
|
|
11564
11684
|
await emitEvent(session, "job:complete", {
|
|
11565
11685
|
...terminalBase(),
|
|
@@ -11574,10 +11694,11 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11574
11694
|
ready.buildAnnotation,
|
|
11575
11695
|
onProgress,
|
|
11576
11696
|
// The durability write, per chunk, awaited; folds into the terminal
|
|
11577
|
-
// durability evidence like every commit.
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11697
|
+
// durability evidence like every commit, and carries the unit's cursor.
|
|
11698
|
+
commitChunk,
|
|
11699
|
+
// …and the other direction: where an earlier attempt left each unit
|
|
11700
|
+
// (CHUNK-GRAIN-RESUME P3). Empty on a first attempt.
|
|
11701
|
+
job.unitCursors
|
|
11581
11702
|
);
|
|
11582
11703
|
await emitEvent(session, "job:complete", {
|
|
11583
11704
|
...terminalBase(),
|
|
@@ -11592,10 +11713,11 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11592
11713
|
ready.buildAnnotation,
|
|
11593
11714
|
onProgress,
|
|
11594
11715
|
// The durability write, per chunk, awaited; folds into the terminal
|
|
11595
|
-
// durability evidence like every commit.
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11716
|
+
// durability evidence like every commit, and carries the unit's cursor.
|
|
11717
|
+
commitChunk,
|
|
11718
|
+
// …and the other direction: where an earlier attempt left each unit
|
|
11719
|
+
// (CHUNK-GRAIN-RESUME P3). Empty on a first attempt.
|
|
11720
|
+
job.unitCursors
|
|
11599
11721
|
);
|
|
11600
11722
|
await emitEvent(session, "job:complete", {
|
|
11601
11723
|
...terminalBase(),
|
|
@@ -11620,17 +11742,21 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11620
11742
|
config.logger,
|
|
11621
11743
|
async (unit) => {
|
|
11622
11744
|
committed.push(unit);
|
|
11745
|
+
unitCursors.delete(unit);
|
|
11746
|
+
unitCursorsByJob.set(job.jobId, Object.fromEntries(unitCursors));
|
|
11623
11747
|
await emitEvent(session, "job:checkpoint", {
|
|
11624
11748
|
jobId: job.jobId,
|
|
11625
|
-
completedUnits: [...committed]
|
|
11749
|
+
completedUnits: [...committed],
|
|
11750
|
+
...unitCursors.size > 0 ? { unitCursors: Object.fromEntries(unitCursors) } : {}
|
|
11626
11751
|
});
|
|
11627
11752
|
},
|
|
11628
11753
|
signal,
|
|
11629
11754
|
// The durability write, per chunk, awaited; folds into the terminal
|
|
11630
|
-
// durability evidence like every commit.
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11755
|
+
// durability evidence like every commit, and carries the unit's cursor.
|
|
11756
|
+
commitChunk,
|
|
11757
|
+
// …and the other direction: where an earlier attempt left each unit
|
|
11758
|
+
// (CHUNK-GRAIN-RESUME P3). Empty on a first attempt.
|
|
11759
|
+
job.unitCursors
|
|
11634
11760
|
);
|
|
11635
11761
|
if (signal?.aborted) {
|
|
11636
11762
|
await emitEvent(session, "job:cancel", {
|
|
@@ -11653,10 +11779,11 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal)
|
|
|
11653
11779
|
ready.buildAnnotation,
|
|
11654
11780
|
onProgress,
|
|
11655
11781
|
// The durability write, per chunk, awaited; folds into the terminal
|
|
11656
|
-
// durability evidence like every commit.
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11782
|
+
// durability evidence like every commit, and carries the unit's cursor.
|
|
11783
|
+
commitChunk,
|
|
11784
|
+
// …and the other direction: where an earlier attempt left each unit
|
|
11785
|
+
// (CHUNK-GRAIN-RESUME P3). Empty on a first attempt.
|
|
11786
|
+
job.unitCursors
|
|
11660
11787
|
);
|
|
11661
11788
|
await emitEvent(session, "job:complete", {
|
|
11662
11789
|
...terminalBase(),
|