@semiont/jobs 0.5.26 → 0.5.27
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 +13 -12
- package/dist/index.d.ts +26 -72
- package/dist/index.js +129 -75
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +159 -92
- package/dist/worker-main.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@ import { promises, mkdtempSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { jobId, deriveViews, reconcileSelector, GENERATABLE_MEDIA_TYPES, estimateTokens, chunkText, isObject, isString, getLocaleEnglishName } from '@semiont/core';
|
|
5
|
+
import { withSpan } from '@semiont/observability';
|
|
5
6
|
import { execFileSync } from 'child_process';
|
|
6
7
|
import { tmpdir } from 'os';
|
|
7
8
|
import { withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
|
|
8
9
|
import '@semiont/event-sourcing';
|
|
9
|
-
import '@semiont/observability';
|
|
10
10
|
import '@semiont/sdk';
|
|
11
11
|
import '@semiont/http-transport';
|
|
12
12
|
|
|
@@ -426,10 +426,18 @@ function isFailedJob(job) {
|
|
|
426
426
|
function isCancelledJob(job) {
|
|
427
427
|
return job.status === "cancelled";
|
|
428
428
|
}
|
|
429
|
-
|
|
430
|
-
// src/workers/inference-call.ts
|
|
431
429
|
var INFERENCE_TIMEOUT_MS = 10 * 6e4;
|
|
432
|
-
|
|
430
|
+
var INFERENCE_HEARTBEAT_MS = 15e3;
|
|
431
|
+
function spanned(client, kind, maxTokens, work) {
|
|
432
|
+
return withSpan(`inference:${kind}`, work, {
|
|
433
|
+
attrs: {
|
|
434
|
+
"inference.provider": client.type,
|
|
435
|
+
"inference.model": client.modelId,
|
|
436
|
+
"inference.max_tokens": maxTokens
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
async function withTimeout(work, label, onHeartbeat) {
|
|
433
441
|
let timer;
|
|
434
442
|
const timedOut = new Promise((_, reject) => {
|
|
435
443
|
timer = setTimeout(() => {
|
|
@@ -439,6 +447,16 @@ async function withTimeout(work, label) {
|
|
|
439
447
|
}, INFERENCE_TIMEOUT_MS);
|
|
440
448
|
timer.unref?.();
|
|
441
449
|
});
|
|
450
|
+
let heartbeat;
|
|
451
|
+
if (onHeartbeat) {
|
|
452
|
+
heartbeat = setInterval(() => {
|
|
453
|
+
try {
|
|
454
|
+
onHeartbeat();
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
457
|
+
}, INFERENCE_HEARTBEAT_MS);
|
|
458
|
+
heartbeat.unref?.();
|
|
459
|
+
}
|
|
442
460
|
try {
|
|
443
461
|
return await Promise.race([work, timedOut]);
|
|
444
462
|
} catch (err) {
|
|
@@ -447,19 +465,22 @@ async function withTimeout(work, label) {
|
|
|
447
465
|
throw err;
|
|
448
466
|
} finally {
|
|
449
467
|
clearTimeout(timer);
|
|
468
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
450
469
|
}
|
|
451
470
|
}
|
|
452
|
-
function boundedGenerate(client, prompt, maxTokens, temperature) {
|
|
453
|
-
return withTimeout(
|
|
471
|
+
function boundedGenerate(client, prompt, maxTokens, temperature, onHeartbeat) {
|
|
472
|
+
return spanned(client, "text", maxTokens, () => withTimeout(
|
|
454
473
|
client.generateText(prompt, maxTokens, temperature),
|
|
455
|
-
`${client.type}:${client.modelId}
|
|
456
|
-
|
|
474
|
+
`${client.type}:${client.modelId}`,
|
|
475
|
+
onHeartbeat
|
|
476
|
+
));
|
|
457
477
|
}
|
|
458
|
-
function boundedGenerateStructured(client, prompt, maxTokens, temperature, elementSchema) {
|
|
459
|
-
return withTimeout(
|
|
478
|
+
function boundedGenerateStructured(client, prompt, maxTokens, temperature, elementSchema, onHeartbeat) {
|
|
479
|
+
return spanned(client, "structured", maxTokens, () => withTimeout(
|
|
460
480
|
client.generateStructured(prompt, maxTokens, temperature, elementSchema),
|
|
461
|
-
`${client.type}:${client.modelId}
|
|
462
|
-
|
|
481
|
+
`${client.type}:${client.modelId}`,
|
|
482
|
+
onHeartbeat
|
|
483
|
+
));
|
|
463
484
|
}
|
|
464
485
|
|
|
465
486
|
// src/workers/detection/detection-chunking.ts
|
|
@@ -990,7 +1011,7 @@ function assertNotTruncated(response, motivation, chunk, totalChunks, outputBudg
|
|
|
990
1011
|
throw new Error(`${motivation} detection response truncated (max_tokens) on chunk ${chunk}/${totalChunks} despite the derived output budget of ${outputBudget} tokens \u2014 failing the job rather than under-reporting annotations.`);
|
|
991
1012
|
}
|
|
992
1013
|
}
|
|
993
|
-
async function detectInChunks(client, content, buildPrompt, temperature, motivation, elementSchema, parse,
|
|
1014
|
+
async function detectInChunks(client, content, buildPrompt, temperature, motivation, elementSchema, parse, onActivity) {
|
|
994
1015
|
const limits = await client.limits();
|
|
995
1016
|
const scaffoldTokens = estimateTokens(buildPrompt(""));
|
|
996
1017
|
const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens);
|
|
@@ -1002,12 +1023,14 @@ async function detectInChunks(client, content, buildPrompt, temperature, motivat
|
|
|
1002
1023
|
buildPrompt(chunks[i]),
|
|
1003
1024
|
outputBudget,
|
|
1004
1025
|
temperature,
|
|
1005
|
-
elementSchema
|
|
1026
|
+
elementSchema,
|
|
1027
|
+
// Still alive, same position (a long single call is otherwise silent).
|
|
1028
|
+
() => onActivity?.(i, chunks.length)
|
|
1006
1029
|
);
|
|
1007
1030
|
assertNotTruncated(response, motivation, i + 1, chunks.length, outputBudget);
|
|
1008
1031
|
collected.push(...parse(response.items));
|
|
1009
1032
|
if (i < chunks.length - 1) {
|
|
1010
|
-
|
|
1033
|
+
onActivity?.(i + 1, chunks.length);
|
|
1011
1034
|
}
|
|
1012
1035
|
}
|
|
1013
1036
|
return collected;
|
|
@@ -1021,7 +1044,7 @@ var AnnotationDetection = class {
|
|
|
1021
1044
|
* (source-resource locale). See `types.ts` "Locale conventions" for the
|
|
1022
1045
|
* full discussion.
|
|
1023
1046
|
*/
|
|
1024
|
-
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage,
|
|
1047
|
+
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage, onActivity) {
|
|
1025
1048
|
return detectInChunks(
|
|
1026
1049
|
client,
|
|
1027
1050
|
content,
|
|
@@ -1030,7 +1053,7 @@ var AnnotationDetection = class {
|
|
|
1030
1053
|
"comment",
|
|
1031
1054
|
COMMENT_ELEMENT_SCHEMA,
|
|
1032
1055
|
(items) => MotivationParsers.parseComments(items, content),
|
|
1033
|
-
|
|
1056
|
+
onActivity
|
|
1034
1057
|
);
|
|
1035
1058
|
}
|
|
1036
1059
|
/**
|
|
@@ -1040,7 +1063,7 @@ var AnnotationDetection = class {
|
|
|
1040
1063
|
* applies, used in the prompt so the LLM analyzes non-English source
|
|
1041
1064
|
* correctly.
|
|
1042
1065
|
*/
|
|
1043
|
-
static async detectHighlights(content, client, instructions, density, sourceLanguage,
|
|
1066
|
+
static async detectHighlights(content, client, instructions, density, sourceLanguage, onActivity) {
|
|
1044
1067
|
return detectInChunks(
|
|
1045
1068
|
client,
|
|
1046
1069
|
content,
|
|
@@ -1049,7 +1072,7 @@ var AnnotationDetection = class {
|
|
|
1049
1072
|
"highlight",
|
|
1050
1073
|
HIGHLIGHT_ELEMENT_SCHEMA,
|
|
1051
1074
|
(items) => MotivationParsers.parseHighlights(items, content),
|
|
1052
|
-
|
|
1075
|
+
onActivity
|
|
1053
1076
|
);
|
|
1054
1077
|
}
|
|
1055
1078
|
/**
|
|
@@ -1059,7 +1082,7 @@ var AnnotationDetection = class {
|
|
|
1059
1082
|
* (annotation body locale). `sourceLanguage` is the locale of the content
|
|
1060
1083
|
* being analyzed (source-resource locale).
|
|
1061
1084
|
*/
|
|
1062
|
-
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage,
|
|
1085
|
+
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage, onActivity) {
|
|
1063
1086
|
return detectInChunks(
|
|
1064
1087
|
client,
|
|
1065
1088
|
content,
|
|
@@ -1068,7 +1091,7 @@ var AnnotationDetection = class {
|
|
|
1068
1091
|
"assessment",
|
|
1069
1092
|
ASSESSMENT_ELEMENT_SCHEMA,
|
|
1070
1093
|
(items) => MotivationParsers.parseAssessments(items, content),
|
|
1071
|
-
|
|
1094
|
+
onActivity
|
|
1072
1095
|
);
|
|
1073
1096
|
}
|
|
1074
1097
|
/**
|
|
@@ -1083,7 +1106,7 @@ var AnnotationDetection = class {
|
|
|
1083
1106
|
* identifiers, not LLM-generated text — so it's consumed at the body-stamp
|
|
1084
1107
|
* site, not here.
|
|
1085
1108
|
*/
|
|
1086
|
-
static async detectTags(content, client, schema, category, sourceLanguage,
|
|
1109
|
+
static async detectTags(content, client, schema, category, sourceLanguage, onActivity) {
|
|
1087
1110
|
const categoryInfo = schema.tags.find((t) => t.name === category);
|
|
1088
1111
|
if (!categoryInfo) {
|
|
1089
1112
|
throw new Error(`Invalid category "${category}" for schema ${schema.id}`);
|
|
@@ -1105,7 +1128,7 @@ var AnnotationDetection = class {
|
|
|
1105
1128
|
"tag",
|
|
1106
1129
|
TAG_ELEMENT_SCHEMA,
|
|
1107
1130
|
(items) => MotivationParsers.parseTags(items),
|
|
1108
|
-
|
|
1131
|
+
onActivity
|
|
1109
1132
|
);
|
|
1110
1133
|
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
1111
1134
|
}
|
|
@@ -1121,7 +1144,7 @@ var ENTITY_ELEMENT_SCHEMA = {
|
|
|
1121
1144
|
required: ["exact", "entityType"],
|
|
1122
1145
|
additionalProperties: false
|
|
1123
1146
|
};
|
|
1124
|
-
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger, sourceLanguage,
|
|
1147
|
+
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger, sourceLanguage, onActivity) {
|
|
1125
1148
|
const entityTypesDescription = entityTypes.map((et) => {
|
|
1126
1149
|
if (typeof et === "string") {
|
|
1127
1150
|
return et;
|
|
@@ -1188,7 +1211,10 @@ Example output:
|
|
|
1188
1211
|
outputBudget,
|
|
1189
1212
|
0.3,
|
|
1190
1213
|
// Lower temperature for more consistent extraction
|
|
1191
|
-
ENTITY_ELEMENT_SCHEMA
|
|
1214
|
+
ENTITY_ELEMENT_SCHEMA,
|
|
1215
|
+
// Still alive, same position: a long single call would otherwise emit
|
|
1216
|
+
// nothing at all between start and finish.
|
|
1217
|
+
() => onActivity?.(i, chunks.length)
|
|
1192
1218
|
);
|
|
1193
1219
|
logger.debug("Got entity extraction response", {
|
|
1194
1220
|
chunk: i + 1,
|
|
@@ -1213,7 +1239,7 @@ Example output:
|
|
|
1213
1239
|
}
|
|
1214
1240
|
}
|
|
1215
1241
|
if (i < chunks.length - 1) {
|
|
1216
|
-
|
|
1242
|
+
onActivity?.(i + 1, chunks.length);
|
|
1217
1243
|
}
|
|
1218
1244
|
}
|
|
1219
1245
|
return collected;
|
|
@@ -1557,30 +1583,39 @@ function dedupeAnnotations(annotations) {
|
|
|
1557
1583
|
return out;
|
|
1558
1584
|
}
|
|
1559
1585
|
async function processHighlightJob(content, inferenceClient, params, buildAnnotation, onProgress) {
|
|
1560
|
-
|
|
1561
|
-
onProgress(
|
|
1586
|
+
const echo = detectionEcho(params);
|
|
1587
|
+
onProgress(10, { code: "loading" }, echo);
|
|
1588
|
+
onProgress(30, { code: "analyzing" }, echo);
|
|
1562
1589
|
const highlights = await AnnotationDetection.detectHighlights(
|
|
1563
1590
|
content,
|
|
1564
1591
|
inferenceClient,
|
|
1565
1592
|
params.instructions,
|
|
1566
1593
|
params.density,
|
|
1567
1594
|
params.sourceLanguage,
|
|
1568
|
-
//
|
|
1569
|
-
(completed, total) => onProgress(30 + Math.round(completed / total * 30),
|
|
1595
|
+
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
1596
|
+
(completed, total) => onProgress(30 + Math.round(completed / total * 30), { code: "analyzing" }, echo)
|
|
1570
1597
|
);
|
|
1571
|
-
onProgress(60,
|
|
1598
|
+
onProgress(60, { code: "creating-annotations", count: highlights.length }, echo);
|
|
1572
1599
|
const annotations = dedupeAnnotations(highlights.map(
|
|
1573
1600
|
(h) => buildAnnotation("highlighting", h)
|
|
1574
1601
|
));
|
|
1575
|
-
onProgress(100,
|
|
1602
|
+
onProgress(100, { code: "complete-created", count: annotations.length, kind: "highlight" }, echo);
|
|
1576
1603
|
return {
|
|
1577
1604
|
annotations,
|
|
1578
1605
|
result: { highlightsFound: highlights.length, highlightsCreated: annotations.length }
|
|
1579
1606
|
};
|
|
1580
1607
|
}
|
|
1608
|
+
function detectionEcho(p) {
|
|
1609
|
+
const requestParams = [];
|
|
1610
|
+
if (p.instructions?.trim()) requestParams.push({ label: "instructions", value: p.instructions.trim() });
|
|
1611
|
+
if (p.tone?.trim()) requestParams.push({ label: "tone", value: p.tone.trim() });
|
|
1612
|
+
if (p.density !== void 0) requestParams.push({ label: "density", value: String(p.density) });
|
|
1613
|
+
return requestParams.length > 0 ? { requestParams } : {};
|
|
1614
|
+
}
|
|
1581
1615
|
async function processCommentJob(content, inferenceClient, params, buildAnnotation, onProgress) {
|
|
1582
|
-
|
|
1583
|
-
onProgress(
|
|
1616
|
+
const echo = detectionEcho(params);
|
|
1617
|
+
onProgress(10, { code: "loading" }, echo);
|
|
1618
|
+
onProgress(30, { code: "analyzing" }, echo);
|
|
1584
1619
|
const comments = await AnnotationDetection.detectComments(
|
|
1585
1620
|
content,
|
|
1586
1621
|
inferenceClient,
|
|
@@ -1589,10 +1624,10 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
|
|
|
1589
1624
|
params.density,
|
|
1590
1625
|
params.language,
|
|
1591
1626
|
params.sourceLanguage,
|
|
1592
|
-
//
|
|
1593
|
-
(completed, total) => onProgress(30 + Math.round(completed / total * 30),
|
|
1627
|
+
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
1628
|
+
(completed, total) => onProgress(30 + Math.round(completed / total * 30), { code: "analyzing" }, echo)
|
|
1594
1629
|
);
|
|
1595
|
-
onProgress(60,
|
|
1630
|
+
onProgress(60, { code: "creating-annotations", count: comments.length }, echo);
|
|
1596
1631
|
const bodyLanguage = params.language ?? "en";
|
|
1597
1632
|
const annotations = dedupeAnnotations(comments.map(
|
|
1598
1633
|
(c) => (
|
|
@@ -1604,15 +1639,16 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
|
|
|
1604
1639
|
])
|
|
1605
1640
|
)
|
|
1606
1641
|
));
|
|
1607
|
-
onProgress(100,
|
|
1642
|
+
onProgress(100, { code: "complete-created", count: annotations.length, kind: "comment" }, echo);
|
|
1608
1643
|
return {
|
|
1609
1644
|
annotations,
|
|
1610
1645
|
result: { commentsFound: comments.length, commentsCreated: annotations.length }
|
|
1611
1646
|
};
|
|
1612
1647
|
}
|
|
1613
1648
|
async function processAssessmentJob(content, inferenceClient, params, buildAnnotation, onProgress) {
|
|
1614
|
-
|
|
1615
|
-
onProgress(
|
|
1649
|
+
const echo = detectionEcho(params);
|
|
1650
|
+
onProgress(10, { code: "loading" }, echo);
|
|
1651
|
+
onProgress(30, { code: "analyzing" }, echo);
|
|
1616
1652
|
const assessments = await AnnotationDetection.detectAssessments(
|
|
1617
1653
|
content,
|
|
1618
1654
|
inferenceClient,
|
|
@@ -1621,10 +1657,10 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
1621
1657
|
params.density,
|
|
1622
1658
|
params.language,
|
|
1623
1659
|
params.sourceLanguage,
|
|
1624
|
-
//
|
|
1625
|
-
(completed, total) => onProgress(30 + Math.round(completed / total * 30),
|
|
1660
|
+
// Liveness (chunk boundaries + in-flight heartbeat): 30–60 band.
|
|
1661
|
+
(completed, total) => onProgress(30 + Math.round(completed / total * 30), { code: "analyzing" }, echo)
|
|
1626
1662
|
);
|
|
1627
|
-
onProgress(60,
|
|
1663
|
+
onProgress(60, { code: "creating-annotations", count: assessments.length }, echo);
|
|
1628
1664
|
const bodyLanguage = params.language ?? "en";
|
|
1629
1665
|
const annotations = dedupeAnnotations(assessments.map(
|
|
1630
1666
|
(a) => (
|
|
@@ -1643,7 +1679,7 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
1643
1679
|
})
|
|
1644
1680
|
)
|
|
1645
1681
|
));
|
|
1646
|
-
onProgress(100,
|
|
1682
|
+
onProgress(100, { code: "complete-created", count: annotations.length, kind: "assessment" }, echo);
|
|
1647
1683
|
return {
|
|
1648
1684
|
annotations,
|
|
1649
1685
|
result: { assessmentsFound: assessments.length, assessmentsCreated: annotations.length }
|
|
@@ -1651,25 +1687,27 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
|
|
|
1651
1687
|
}
|
|
1652
1688
|
async function processReferenceJob(content, inferenceClient, params, buildAnnotation, onProgress, logger) {
|
|
1653
1689
|
const entityTypeNames = params.entityTypes.map(String);
|
|
1654
|
-
const requestParams = [{ label: "
|
|
1655
|
-
const
|
|
1690
|
+
const requestParams = [{ label: "entity-types", value: entityTypeNames.join(", ") }];
|
|
1691
|
+
const completedItems = [];
|
|
1656
1692
|
let totalFound = 0;
|
|
1657
1693
|
let totalEmitted = 0;
|
|
1658
1694
|
let errors = 0;
|
|
1659
1695
|
const allAnnotations = [];
|
|
1660
|
-
onProgress(10,
|
|
1696
|
+
onProgress(10, { code: "loading" }, { requestParams });
|
|
1661
1697
|
const bodyLanguage = params.language ?? "en";
|
|
1662
1698
|
for (let i = 0; i < entityTypeNames.length; i++) {
|
|
1663
1699
|
const entityTypeName = entityTypeNames[i];
|
|
1664
1700
|
if (!entityTypeName) continue;
|
|
1665
1701
|
const pct = 20 + Math.round(i / entityTypeNames.length * 60);
|
|
1666
|
-
onProgress(pct,
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1702
|
+
onProgress(pct, { code: "detecting-entities", entityType: entityTypeName }, {
|
|
1703
|
+
// One vocabulary for "what is in flight" (CLEAN-PROGRESS D2): the entity
|
|
1704
|
+
// type is KB data, `kind` is the code the client localizes around it.
|
|
1705
|
+
current: { kind: "entity-type", value: entityTypeName },
|
|
1706
|
+
processed: i,
|
|
1707
|
+
total: entityTypeNames.length,
|
|
1670
1708
|
entitiesFound: totalFound,
|
|
1671
1709
|
entitiesEmitted: totalEmitted,
|
|
1672
|
-
|
|
1710
|
+
completedItems: [...completedItems],
|
|
1673
1711
|
requestParams
|
|
1674
1712
|
});
|
|
1675
1713
|
const extractedEntities = await extractEntities(
|
|
@@ -1679,25 +1717,28 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
1679
1717
|
params.includeDescriptiveReferences ?? false,
|
|
1680
1718
|
logger,
|
|
1681
1719
|
params.sourceLanguage,
|
|
1682
|
-
//
|
|
1683
|
-
//
|
|
1684
|
-
//
|
|
1685
|
-
//
|
|
1720
|
+
// Liveness: fires at chunk boundaries AND every ~15 s while a single
|
|
1721
|
+
// inference call is in flight (DETECTION-HEARTBEAT). Progress feeds the
|
|
1722
|
+
// stall watchdog, the janitor, AND the client's inter-emission timeout,
|
|
1723
|
+
// so a long single-chunk call must not be silent. Percentage
|
|
1724
|
+
// interpolates within this entity type's band of the 20–80 range; a
|
|
1725
|
+
// heartbeat repeats the current position rather than inventing an
|
|
1726
|
+
// advance.
|
|
1686
1727
|
(completed, total) => {
|
|
1687
1728
|
const interpolated = 20 + Math.round((i + completed / total) / entityTypeNames.length * 60);
|
|
1688
|
-
onProgress(interpolated,
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1729
|
+
onProgress(interpolated, { code: "detecting-entities", entityType: entityTypeName }, {
|
|
1730
|
+
current: { kind: "entity-type", value: entityTypeName },
|
|
1731
|
+
processed: i,
|
|
1732
|
+
total: entityTypeNames.length,
|
|
1692
1733
|
entitiesFound: totalFound,
|
|
1693
1734
|
entitiesEmitted: totalEmitted,
|
|
1694
|
-
|
|
1735
|
+
completedItems: [...completedItems],
|
|
1695
1736
|
requestParams
|
|
1696
1737
|
});
|
|
1697
1738
|
}
|
|
1698
1739
|
);
|
|
1699
1740
|
totalFound += extractedEntities.length;
|
|
1700
|
-
|
|
1741
|
+
completedItems.push({ value: entityTypeName, foundCount: extractedEntities.length });
|
|
1701
1742
|
const unresolvedBody = [
|
|
1702
1743
|
{ type: "TextualBody", value: entityTypeName, purpose: "tagging", format: "text/plain", language: bodyLanguage }
|
|
1703
1744
|
];
|
|
@@ -1728,36 +1769,49 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
|
|
|
1728
1769
|
}
|
|
1729
1770
|
}
|
|
1730
1771
|
const annotations = dedupeAnnotations(allAnnotations);
|
|
1731
|
-
onProgress(100,
|
|
1772
|
+
onProgress(100, { code: "complete-created", count: annotations.length, kind: "reference" }, { requestParams });
|
|
1732
1773
|
return {
|
|
1733
1774
|
annotations,
|
|
1734
1775
|
result: { totalFound, totalEmitted: annotations.length, errors }
|
|
1735
1776
|
};
|
|
1736
1777
|
}
|
|
1737
1778
|
async function processTagJob(content, inferenceClient, params, buildAnnotation, onProgress) {
|
|
1738
|
-
onProgress(10,
|
|
1739
|
-
onProgress(30,
|
|
1779
|
+
onProgress(10, { code: "loading" });
|
|
1780
|
+
onProgress(30, { code: "analyzing-tags" });
|
|
1740
1781
|
const allTags = [];
|
|
1782
|
+
const completedItems = [];
|
|
1741
1783
|
for (let c = 0; c < params.categories.length; c++) {
|
|
1742
1784
|
const category = params.categories[c];
|
|
1785
|
+
const position = () => ({
|
|
1786
|
+
current: { kind: "category", value: category },
|
|
1787
|
+
processed: c,
|
|
1788
|
+
total: params.categories.length,
|
|
1789
|
+
completedItems: [...completedItems]
|
|
1790
|
+
});
|
|
1791
|
+
onProgress(
|
|
1792
|
+
30 + Math.round(c / params.categories.length * 30),
|
|
1793
|
+
{ code: "analyzing-tags" },
|
|
1794
|
+
position()
|
|
1795
|
+
);
|
|
1743
1796
|
const categoryTags = await AnnotationDetection.detectTags(
|
|
1744
1797
|
content,
|
|
1745
1798
|
inferenceClient,
|
|
1746
1799
|
params.schema,
|
|
1747
1800
|
category,
|
|
1748
1801
|
params.sourceLanguage,
|
|
1749
|
-
//
|
|
1750
|
-
//
|
|
1802
|
+
// Liveness (chunk boundaries + in-flight heartbeat): this category's
|
|
1803
|
+
// slice of the 30–60 band.
|
|
1751
1804
|
(completed, total) => onProgress(
|
|
1752
1805
|
30 + Math.round((c + completed / total) / params.categories.length * 30),
|
|
1753
|
-
|
|
1754
|
-
|
|
1806
|
+
{ code: "analyzing-tags" },
|
|
1807
|
+
position()
|
|
1755
1808
|
)
|
|
1756
1809
|
);
|
|
1810
|
+
completedItems.push({ value: category, foundCount: categoryTags.length });
|
|
1757
1811
|
allTags.push(...categoryTags);
|
|
1758
1812
|
}
|
|
1759
1813
|
const tags = allTags;
|
|
1760
|
-
onProgress(60,
|
|
1814
|
+
onProgress(60, { code: "creating-tag-annotations", count: tags.length });
|
|
1761
1815
|
const bodyLanguage = params.language ?? "en";
|
|
1762
1816
|
const annotations = dedupeAnnotations(tags.map((t) => {
|
|
1763
1817
|
const category = t.category ?? "unknown";
|
|
@@ -1772,7 +1826,7 @@ async function processTagJob(content, inferenceClient, params, buildAnnotation,
|
|
|
1772
1826
|
const category = Array.isArray(body) && typeof body[0]?.value === "string" ? body[0].value : "unknown";
|
|
1773
1827
|
byCategory[category] = (byCategory[category] ?? 0) + 1;
|
|
1774
1828
|
}
|
|
1775
|
-
onProgress(100,
|
|
1829
|
+
onProgress(100, { code: "complete-created", count: annotations.length, kind: "tag" });
|
|
1776
1830
|
return {
|
|
1777
1831
|
annotations,
|
|
1778
1832
|
result: { tagsFound: tags.length, tagsCreated: annotations.length, byCategory }
|
|
@@ -1795,7 +1849,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
|
|
|
1795
1849
|
const title = params.title ?? "Untitled";
|
|
1796
1850
|
const entityTypes = (params.entityTypes ?? []).map(String);
|
|
1797
1851
|
if (outputMediaType === "application/pdf") {
|
|
1798
|
-
onProgress(5,
|
|
1852
|
+
onProgress(5, { code: "generating-resource" });
|
|
1799
1853
|
const validIds = params.cite === true ? collectContextResourceIds(params.context) : null;
|
|
1800
1854
|
let generated2 = await generateResourceFromTopic(
|
|
1801
1855
|
title,
|
|
@@ -1860,7 +1914,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
|
|
|
1860
1914
|
);
|
|
1861
1915
|
}
|
|
1862
1916
|
assertWithinOutputBudget(compiled.pdf.byteLength);
|
|
1863
|
-
onProgress(95,
|
|
1917
|
+
onProgress(95, { code: "creating-resource" });
|
|
1864
1918
|
return {
|
|
1865
1919
|
content: compiled.pdf,
|
|
1866
1920
|
title: generated2.title ?? title,
|
|
@@ -1872,7 +1926,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
|
|
|
1872
1926
|
}
|
|
1873
1927
|
};
|
|
1874
1928
|
}
|
|
1875
|
-
onProgress(5,
|
|
1929
|
+
onProgress(5, { code: "generating-resource" });
|
|
1876
1930
|
const generated = await generateResourceFromTopic(
|
|
1877
1931
|
title,
|
|
1878
1932
|
entityTypes,
|
|
@@ -1896,7 +1950,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
|
|
|
1896
1950
|
content = resolved.content;
|
|
1897
1951
|
citations = resolved.citations;
|
|
1898
1952
|
}
|
|
1899
|
-
onProgress(95,
|
|
1953
|
+
onProgress(95, { code: "creating-resource" });
|
|
1900
1954
|
const artifact = new TextEncoder().encode(content);
|
|
1901
1955
|
assertWithinOutputBudget(artifact.byteLength);
|
|
1902
1956
|
return {
|