@semiont/jobs 0.5.25 → 0.5.26
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 +1 -0
- package/dist/index.js +87 -77
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +93 -83
- package/dist/worker-main.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -579,6 +579,7 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
|
|
|
579
579
|
* NOTE: These are static utility methods without logger access.
|
|
580
580
|
* Console statements kept for debugging - consider adding logger parameter in future.
|
|
581
581
|
*/
|
|
582
|
+
|
|
582
583
|
/**
|
|
583
584
|
* Represents a detected comment with validated position
|
|
584
585
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { promises, mkdtempSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import { jobId, deriveViews, reconcileSelector, GENERATABLE_MEDIA_TYPES, estimateTokens, chunkText, isObject, isString, getLocaleEnglishName
|
|
4
|
+
import { jobId, deriveViews, reconcileSelector, GENERATABLE_MEDIA_TYPES, estimateTokens, chunkText, isObject, isString, getLocaleEnglishName } from '@semiont/core';
|
|
5
5
|
import { execFileSync } from 'child_process';
|
|
6
6
|
import { tmpdir } from 'os';
|
|
7
7
|
import { withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
|
|
@@ -449,15 +449,15 @@ async function withTimeout(work, label) {
|
|
|
449
449
|
clearTimeout(timer);
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
|
-
function boundedGenerate(client, prompt, maxTokens, temperature
|
|
452
|
+
function boundedGenerate(client, prompt, maxTokens, temperature) {
|
|
453
453
|
return withTimeout(
|
|
454
|
-
client.generateText(prompt, maxTokens, temperature
|
|
454
|
+
client.generateText(prompt, maxTokens, temperature),
|
|
455
455
|
`${client.type}:${client.modelId}`
|
|
456
456
|
);
|
|
457
457
|
}
|
|
458
|
-
function
|
|
458
|
+
function boundedGenerateStructured(client, prompt, maxTokens, temperature, elementSchema) {
|
|
459
459
|
return withTimeout(
|
|
460
|
-
client.
|
|
460
|
+
client.generateStructured(prompt, maxTokens, temperature, elementSchema),
|
|
461
461
|
`${client.type}:${client.modelId}`
|
|
462
462
|
);
|
|
463
463
|
}
|
|
@@ -791,32 +791,57 @@ Example format:
|
|
|
791
791
|
return prompt;
|
|
792
792
|
}
|
|
793
793
|
};
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
}
|
|
794
|
+
var COMMENT_ELEMENT_SCHEMA = {
|
|
795
|
+
type: "object",
|
|
796
|
+
properties: {
|
|
797
|
+
exact: { type: "string" },
|
|
798
|
+
prefix: { type: "string" },
|
|
799
|
+
suffix: { type: "string" },
|
|
800
|
+
comment: { type: "string" }
|
|
801
|
+
},
|
|
802
|
+
required: ["exact", "comment"],
|
|
803
|
+
additionalProperties: false
|
|
804
|
+
};
|
|
805
|
+
var HIGHLIGHT_ELEMENT_SCHEMA = {
|
|
806
|
+
type: "object",
|
|
807
|
+
properties: {
|
|
808
|
+
exact: { type: "string" },
|
|
809
|
+
prefix: { type: "string" },
|
|
810
|
+
suffix: { type: "string" }
|
|
811
|
+
},
|
|
812
|
+
required: ["exact"],
|
|
813
|
+
additionalProperties: false
|
|
814
|
+
};
|
|
815
|
+
var ASSESSMENT_ELEMENT_SCHEMA = {
|
|
816
|
+
type: "object",
|
|
817
|
+
properties: {
|
|
818
|
+
exact: { type: "string" },
|
|
819
|
+
prefix: { type: "string" },
|
|
820
|
+
suffix: { type: "string" },
|
|
821
|
+
assessment: { type: "string" }
|
|
822
|
+
},
|
|
823
|
+
required: ["exact", "assessment"],
|
|
824
|
+
additionalProperties: false
|
|
825
|
+
};
|
|
826
|
+
var TAG_ELEMENT_SCHEMA = {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
exact: { type: "string" },
|
|
830
|
+
prefix: { type: "string" },
|
|
831
|
+
suffix: { type: "string" }
|
|
832
|
+
},
|
|
833
|
+
required: ["exact"],
|
|
834
|
+
additionalProperties: false
|
|
835
|
+
};
|
|
809
836
|
var MotivationParsers = class {
|
|
810
837
|
/**
|
|
811
|
-
*
|
|
838
|
+
* Validate and reconcile structured comment elements.
|
|
812
839
|
*
|
|
813
|
-
* @param
|
|
840
|
+
* @param parsed - Already-parsed elements from the structured surface
|
|
814
841
|
* @param content - Original content to validate offsets against
|
|
815
842
|
* @returns Array of validated comment matches
|
|
816
|
-
* @throws if the response is not a parseable JSON array
|
|
817
843
|
*/
|
|
818
|
-
static parseComments(
|
|
819
|
-
const parsed = parseJsonArray(response, "comment");
|
|
844
|
+
static parseComments(parsed, content) {
|
|
820
845
|
const valid = parsed.filter(
|
|
821
846
|
(c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
|
|
822
847
|
);
|
|
@@ -845,15 +870,13 @@ var MotivationParsers = class {
|
|
|
845
870
|
return validatedComments;
|
|
846
871
|
}
|
|
847
872
|
/**
|
|
848
|
-
*
|
|
873
|
+
* Validate and reconcile structured highlight elements.
|
|
849
874
|
*
|
|
850
|
-
* @param
|
|
875
|
+
* @param parsed - Already-parsed elements from the structured surface
|
|
851
876
|
* @param content - Original content to validate offsets against
|
|
852
877
|
* @returns Array of validated highlight matches
|
|
853
|
-
* @throws if the response is not a parseable JSON array
|
|
854
878
|
*/
|
|
855
|
-
static parseHighlights(
|
|
856
|
-
const parsed = parseJsonArray(response, "highlight");
|
|
879
|
+
static parseHighlights(parsed, content) {
|
|
857
880
|
const highlights = parsed.filter(
|
|
858
881
|
(h) => isObject(h) && isString(h.exact)
|
|
859
882
|
);
|
|
@@ -880,15 +903,13 @@ var MotivationParsers = class {
|
|
|
880
903
|
return validatedHighlights;
|
|
881
904
|
}
|
|
882
905
|
/**
|
|
883
|
-
*
|
|
906
|
+
* Validate and reconcile structured assessment elements.
|
|
884
907
|
*
|
|
885
|
-
* @param
|
|
908
|
+
* @param parsed - Already-parsed elements from the structured surface
|
|
886
909
|
* @param content - Original content to validate offsets against
|
|
887
910
|
* @returns Array of validated assessment matches
|
|
888
|
-
* @throws if the response is not a parseable JSON array
|
|
889
911
|
*/
|
|
890
|
-
static parseAssessments(
|
|
891
|
-
const parsed = parseJsonArray(response, "assessment");
|
|
912
|
+
static parseAssessments(parsed, content) {
|
|
892
913
|
const assessments = parsed.filter(
|
|
893
914
|
(a) => isObject(a) && isString(a.exact) && isString(a.assessment)
|
|
894
915
|
);
|
|
@@ -916,14 +937,13 @@ var MotivationParsers = class {
|
|
|
916
937
|
return validatedAssessments;
|
|
917
938
|
}
|
|
918
939
|
/**
|
|
919
|
-
*
|
|
940
|
+
* Validate structured tag elements into raw, pre-reconciliation tag inputs.
|
|
920
941
|
* Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
|
|
921
942
|
* by anchoring `exact` against the source content.
|
|
922
943
|
*
|
|
923
|
-
* @
|
|
944
|
+
* @param parsed - Already-parsed elements from the structured surface
|
|
924
945
|
*/
|
|
925
|
-
static parseTags(
|
|
926
|
-
const parsed = parseJsonArray(response, "tag");
|
|
946
|
+
static parseTags(parsed) {
|
|
927
947
|
const valid = parsed.filter(
|
|
928
948
|
(t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
|
|
929
949
|
);
|
|
@@ -970,22 +990,22 @@ function assertNotTruncated(response, motivation, chunk, totalChunks, outputBudg
|
|
|
970
990
|
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.`);
|
|
971
991
|
}
|
|
972
992
|
}
|
|
973
|
-
async function detectInChunks(client, content, buildPrompt, temperature, motivation, parse, onChunk) {
|
|
993
|
+
async function detectInChunks(client, content, buildPrompt, temperature, motivation, elementSchema, parse, onChunk) {
|
|
974
994
|
const limits = await client.limits();
|
|
975
995
|
const scaffoldTokens = estimateTokens(buildPrompt(""));
|
|
976
996
|
const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens);
|
|
977
997
|
const chunks = chunkText(content, chunking);
|
|
978
998
|
const collected = [];
|
|
979
999
|
for (let i = 0; i < chunks.length; i++) {
|
|
980
|
-
const response = await
|
|
1000
|
+
const response = await boundedGenerateStructured(
|
|
981
1001
|
client,
|
|
982
1002
|
buildPrompt(chunks[i]),
|
|
983
1003
|
outputBudget,
|
|
984
1004
|
temperature,
|
|
985
|
-
|
|
1005
|
+
elementSchema
|
|
986
1006
|
);
|
|
987
1007
|
assertNotTruncated(response, motivation, i + 1, chunks.length, outputBudget);
|
|
988
|
-
collected.push(...parse(response.
|
|
1008
|
+
collected.push(...parse(response.items));
|
|
989
1009
|
if (i < chunks.length - 1) {
|
|
990
1010
|
onChunk?.(i + 1, chunks.length);
|
|
991
1011
|
}
|
|
@@ -1008,7 +1028,8 @@ var AnnotationDetection = class {
|
|
|
1008
1028
|
(chunk) => MotivationPrompts.buildCommentPrompt(chunk, instructions, tone, density, language, sourceLanguage),
|
|
1009
1029
|
0.4,
|
|
1010
1030
|
"comment",
|
|
1011
|
-
|
|
1031
|
+
COMMENT_ELEMENT_SCHEMA,
|
|
1032
|
+
(items) => MotivationParsers.parseComments(items, content),
|
|
1012
1033
|
onChunk
|
|
1013
1034
|
);
|
|
1014
1035
|
}
|
|
@@ -1026,7 +1047,8 @@ var AnnotationDetection = class {
|
|
|
1026
1047
|
(chunk) => MotivationPrompts.buildHighlightPrompt(chunk, instructions, density, sourceLanguage),
|
|
1027
1048
|
0.3,
|
|
1028
1049
|
"highlight",
|
|
1029
|
-
|
|
1050
|
+
HIGHLIGHT_ELEMENT_SCHEMA,
|
|
1051
|
+
(items) => MotivationParsers.parseHighlights(items, content),
|
|
1030
1052
|
onChunk
|
|
1031
1053
|
);
|
|
1032
1054
|
}
|
|
@@ -1044,7 +1066,8 @@ var AnnotationDetection = class {
|
|
|
1044
1066
|
(chunk) => MotivationPrompts.buildAssessmentPrompt(chunk, instructions, tone, density, language, sourceLanguage),
|
|
1045
1067
|
0.3,
|
|
1046
1068
|
"assessment",
|
|
1047
|
-
|
|
1069
|
+
ASSESSMENT_ELEMENT_SCHEMA,
|
|
1070
|
+
(items) => MotivationParsers.parseAssessments(items, content),
|
|
1048
1071
|
onChunk
|
|
1049
1072
|
);
|
|
1050
1073
|
}
|
|
@@ -1080,12 +1103,24 @@ var AnnotationDetection = class {
|
|
|
1080
1103
|
),
|
|
1081
1104
|
0.2,
|
|
1082
1105
|
"tag",
|
|
1083
|
-
|
|
1106
|
+
TAG_ELEMENT_SCHEMA,
|
|
1107
|
+
(items) => MotivationParsers.parseTags(items),
|
|
1084
1108
|
onChunk
|
|
1085
1109
|
);
|
|
1086
1110
|
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
1087
1111
|
}
|
|
1088
1112
|
};
|
|
1113
|
+
var ENTITY_ELEMENT_SCHEMA = {
|
|
1114
|
+
type: "object",
|
|
1115
|
+
properties: {
|
|
1116
|
+
exact: { type: "string" },
|
|
1117
|
+
entityType: { type: "string" },
|
|
1118
|
+
prefix: { type: "string" },
|
|
1119
|
+
suffix: { type: "string" }
|
|
1120
|
+
},
|
|
1121
|
+
required: ["exact", "entityType"],
|
|
1122
|
+
additionalProperties: false
|
|
1123
|
+
};
|
|
1089
1124
|
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger, sourceLanguage, onChunk) {
|
|
1090
1125
|
const entityTypesDescription = entityTypes.map((et) => {
|
|
1091
1126
|
if (typeof et === "string") {
|
|
@@ -1147,50 +1182,25 @@ Example output:
|
|
|
1147
1182
|
});
|
|
1148
1183
|
const collected = [];
|
|
1149
1184
|
for (let i = 0; i < chunks.length; i++) {
|
|
1150
|
-
const response = await
|
|
1185
|
+
const response = await boundedGenerateStructured(
|
|
1151
1186
|
client,
|
|
1152
1187
|
buildPrompt(chunks[i]),
|
|
1153
1188
|
outputBudget,
|
|
1154
1189
|
0.3,
|
|
1155
1190
|
// Lower temperature for more consistent extraction
|
|
1156
|
-
|
|
1157
|
-
// periodically emit malformed JSON (truncated brackets, mid-token
|
|
1158
|
-
// breaks at higher token counts) which silently parse-fails into
|
|
1159
|
-
// [] downstream. The prompt's schema (which keys, what types) still
|
|
1160
|
-
// governs *what* the JSON contains; `format: 'json'` governs that
|
|
1161
|
-
// it's syntactically valid.
|
|
1162
|
-
{ format: "json" }
|
|
1191
|
+
ENTITY_ELEMENT_SCHEMA
|
|
1163
1192
|
);
|
|
1164
1193
|
logger.debug("Got entity extraction response", {
|
|
1165
1194
|
chunk: i + 1,
|
|
1166
1195
|
chunks: chunks.length,
|
|
1167
|
-
|
|
1196
|
+
items: response.items.length
|
|
1168
1197
|
});
|
|
1169
1198
|
if (response.stopReason === "max_tokens") {
|
|
1170
1199
|
const errorMsg = `Entity extraction response truncated (max_tokens) on chunk ${i + 1}/${chunks.length} despite the derived output budget of ${outputBudget} tokens \u2014 failing the job rather than dropping annotations.`;
|
|
1171
|
-
logger.error(errorMsg, {
|
|
1200
|
+
logger.error(errorMsg, { items: response.items.length });
|
|
1172
1201
|
throw new Error(errorMsg);
|
|
1173
1202
|
}
|
|
1174
|
-
|
|
1175
|
-
try {
|
|
1176
|
-
entities = JSON.parse(response.text.trim());
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
logger.error("Failed to parse entity extraction response", {
|
|
1179
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1180
|
-
response: response.text.slice(0, 500)
|
|
1181
|
-
});
|
|
1182
|
-
throw new Error("Failed to parse entity extraction response", {
|
|
1183
|
-
cause: error instanceof Error ? error : new Error(String(error))
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
if (!isArray(entities)) {
|
|
1187
|
-
logger.error("Failed to parse entity extraction response: expected a JSON array", {
|
|
1188
|
-
response: response.text.slice(0, 500)
|
|
1189
|
-
});
|
|
1190
|
-
throw new Error("Failed to parse entity extraction response: expected a JSON array");
|
|
1191
|
-
}
|
|
1192
|
-
logger.debug("Parsed entities from AI response", { chunk: i + 1, count: entities.length });
|
|
1193
|
-
for (const e of entities) {
|
|
1203
|
+
for (const e of response.items) {
|
|
1194
1204
|
if (isObject(e) && isString(e.exact) && isString(e.entityType)) {
|
|
1195
1205
|
collected.push({
|
|
1196
1206
|
exact: e.exact,
|