@semiont/jobs 0.5.3 → 0.5.4
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/fs-job-queue.d.ts +79 -0
- package/dist/fs-job-queue.d.ts.map +1 -0
- package/dist/index.d.ts +20 -632
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -56
- package/dist/index.js.map +1 -1
- package/dist/job-claim-adapter.d.ts +76 -0
- package/dist/job-claim-adapter.d.ts.map +1 -0
- package/dist/job-queue-interface.d.ts +19 -0
- package/dist/job-queue-interface.d.ts.map +1 -0
- package/dist/job-queue-state-unit.d.ts +26 -0
- package/dist/job-queue-state-unit.d.ts.map +1 -0
- package/dist/job-worker.d.ts +67 -0
- package/dist/job-worker.d.ts.map +1 -0
- package/dist/processors.d.ts +41 -0
- package/dist/processors.d.ts.map +1 -0
- package/dist/types.d.ts +319 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/worker-main.d.ts +22 -2
- package/dist/worker-main.d.ts.map +1 -0
- package/dist/worker-main.js +165 -114
- package/dist/worker-main.js.map +1 -1
- package/dist/worker-process.d.ts +47 -0
- package/dist/worker-process.d.ts.map +1 -0
- package/dist/workers/annotation-detection.d.ts +61 -0
- package/dist/workers/annotation-detection.d.ts.map +1 -0
- package/dist/workers/detection/entity-extractor.d.ts +42 -0
- package/dist/workers/detection/entity-extractor.d.ts.map +1 -0
- package/dist/workers/detection/motivation-parsers.d.ts +116 -0
- package/dist/workers/detection/motivation-parsers.d.ts.map +1 -0
- package/dist/workers/detection/motivation-prompts.d.ts +57 -0
- package/dist/workers/detection/motivation-prompts.d.ts.map +1 -0
- package/dist/workers/generation/resource-generation.d.ts +23 -0
- package/dist/workers/generation/resource-generation.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/worker-main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTomlConfigLoader, baseUrl, RESOURCE_BROADCAST_TYPES, resourceId, validateAndCorrectOffsets, didToAgent, getLocaleEnglishName } from '@semiont/core';
|
|
1
|
+
import { createTomlConfigLoader, softwareToAgent, baseUrl, RESOURCE_BROADCAST_TYPES, resourceId, validateAndCorrectOffsets, didToAgent, getLocaleEnglishName } from '@semiont/core';
|
|
2
2
|
import { deriveStorageUri } from '@semiont/content';
|
|
3
3
|
import { withSpan, SpanKind, recordJobOutcome } from '@semiont/observability';
|
|
4
4
|
import { generateAnnotationId } from '@semiont/event-sourcing';
|
|
@@ -6388,9 +6388,9 @@ var require_groupBy = __commonJS({
|
|
|
6388
6388
|
} else {
|
|
6389
6389
|
duration = elementOrOptions.duration, element = elementOrOptions.element, connector = elementOrOptions.connector;
|
|
6390
6390
|
}
|
|
6391
|
-
var
|
|
6391
|
+
var groups2 = /* @__PURE__ */ new Map();
|
|
6392
6392
|
var notify = function(cb) {
|
|
6393
|
-
|
|
6393
|
+
groups2.forEach(cb);
|
|
6394
6394
|
cb(subscriber);
|
|
6395
6395
|
};
|
|
6396
6396
|
var handleError = function(err) {
|
|
@@ -6403,9 +6403,9 @@ var require_groupBy = __commonJS({
|
|
|
6403
6403
|
var groupBySourceSubscriber = new OperatorSubscriber_1.OperatorSubscriber(subscriber, function(value) {
|
|
6404
6404
|
try {
|
|
6405
6405
|
var key_1 = keySelector(value);
|
|
6406
|
-
var group_1 =
|
|
6406
|
+
var group_1 = groups2.get(key_1);
|
|
6407
6407
|
if (!group_1) {
|
|
6408
|
-
|
|
6408
|
+
groups2.set(key_1, group_1 = connector ? connector() : new Subject_1.Subject());
|
|
6409
6409
|
var grouped = createGroupedObservable(key_1, group_1);
|
|
6410
6410
|
subscriber.next(grouped);
|
|
6411
6411
|
if (duration) {
|
|
@@ -6413,7 +6413,7 @@ var require_groupBy = __commonJS({
|
|
|
6413
6413
|
group_1.complete();
|
|
6414
6414
|
durationSubscriber_1 === null || durationSubscriber_1 === void 0 ? void 0 : durationSubscriber_1.unsubscribe();
|
|
6415
6415
|
}, void 0, void 0, function() {
|
|
6416
|
-
return
|
|
6416
|
+
return groups2.delete(key_1);
|
|
6417
6417
|
});
|
|
6418
6418
|
groupBySourceSubscriber.add(innerFrom_1.innerFrom(duration(grouped)).subscribe(durationSubscriber_1));
|
|
6419
6419
|
}
|
|
@@ -6427,7 +6427,7 @@ var require_groupBy = __commonJS({
|
|
|
6427
6427
|
return consumer.complete();
|
|
6428
6428
|
});
|
|
6429
6429
|
}, handleError, function() {
|
|
6430
|
-
return
|
|
6430
|
+
return groups2.clear();
|
|
6431
6431
|
}, function() {
|
|
6432
6432
|
teardownAttempted = true;
|
|
6433
6433
|
return activeGroups === 0;
|
|
@@ -9831,7 +9831,7 @@ var AnnotationDetection = class {
|
|
|
9831
9831
|
*/
|
|
9832
9832
|
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
|
|
9833
9833
|
const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density, language, sourceLanguage);
|
|
9834
|
-
const response = await client.generateText(prompt, 3e3, 0.4);
|
|
9834
|
+
const response = await client.generateText(prompt, 3e3, 0.4, { format: "json" });
|
|
9835
9835
|
return MotivationParsers.parseComments(response, content);
|
|
9836
9836
|
}
|
|
9837
9837
|
/**
|
|
@@ -9843,7 +9843,7 @@ var AnnotationDetection = class {
|
|
|
9843
9843
|
*/
|
|
9844
9844
|
static async detectHighlights(content, client, instructions, density, sourceLanguage) {
|
|
9845
9845
|
const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density, sourceLanguage);
|
|
9846
|
-
const response = await client.generateText(prompt, 2e3, 0.3);
|
|
9846
|
+
const response = await client.generateText(prompt, 2e3, 0.3, { format: "json" });
|
|
9847
9847
|
return MotivationParsers.parseHighlights(response, content);
|
|
9848
9848
|
}
|
|
9849
9849
|
/**
|
|
@@ -9855,7 +9855,7 @@ var AnnotationDetection = class {
|
|
|
9855
9855
|
*/
|
|
9856
9856
|
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
|
|
9857
9857
|
const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density, language, sourceLanguage);
|
|
9858
|
-
const response = await client.generateText(prompt, 3e3, 0.3);
|
|
9858
|
+
const response = await client.generateText(prompt, 3e3, 0.3, { format: "json" });
|
|
9859
9859
|
return MotivationParsers.parseAssessments(response, content);
|
|
9860
9860
|
}
|
|
9861
9861
|
/**
|
|
@@ -9885,12 +9885,12 @@ var AnnotationDetection = class {
|
|
|
9885
9885
|
categoryInfo.examples,
|
|
9886
9886
|
sourceLanguage
|
|
9887
9887
|
);
|
|
9888
|
-
const response = await client.generateText(prompt, 4e3, 0.2);
|
|
9888
|
+
const response = await client.generateText(prompt, 4e3, 0.2, { format: "json" });
|
|
9889
9889
|
const parsedTags = MotivationParsers.parseTags(response);
|
|
9890
9890
|
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
9891
9891
|
}
|
|
9892
9892
|
};
|
|
9893
|
-
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences
|
|
9893
|
+
async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger2, sourceLanguage) {
|
|
9894
9894
|
const entityTypesDescription = entityTypes.map((et) => {
|
|
9895
9895
|
if (typeof et === "string") {
|
|
9896
9896
|
return et;
|
|
@@ -9941,48 +9941,57 @@ If no entities are found, respond with an empty array [].
|
|
|
9941
9941
|
|
|
9942
9942
|
Example output:
|
|
9943
9943
|
[{"exact":"Alice","entityType":"Person","startOffset":0,"endOffset":5,"prefix":"","suffix":" went to"},{"exact":"Paris","entityType":"Location","startOffset":20,"endOffset":25,"prefix":"went to ","suffix":" yesterday"}]`;
|
|
9944
|
+
logger2.debug("Sending entity extraction request", { entityTypes: entityTypesDescription });
|
|
9944
9945
|
const response = await client.generateTextWithMetadata(
|
|
9945
9946
|
prompt,
|
|
9946
9947
|
4e3,
|
|
9947
9948
|
// Increased to handle many entities without truncation
|
|
9948
|
-
0.3
|
|
9949
|
+
0.3,
|
|
9949
9950
|
// Lower temperature for more consistent extraction
|
|
9951
|
+
// Force grammar-constrained JSON output. Without this, Ollama models
|
|
9952
|
+
// periodically emit malformed JSON (truncated brackets, mid-token
|
|
9953
|
+
// breaks at higher token counts) which silently parse-fails into
|
|
9954
|
+
// [] downstream. The prompt's schema (which keys, what types) still
|
|
9955
|
+
// governs *what* the JSON contains; `format: 'json'` governs that
|
|
9956
|
+
// it's syntactically valid.
|
|
9957
|
+
{ format: "json" }
|
|
9950
9958
|
);
|
|
9959
|
+
logger2.debug("Got entity extraction response", { responseLength: response.text.length });
|
|
9951
9960
|
try {
|
|
9952
9961
|
let jsonStr = response.text.trim();
|
|
9953
9962
|
if (jsonStr.startsWith("```")) {
|
|
9954
9963
|
jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
9955
9964
|
}
|
|
9956
9965
|
const entities = JSON.parse(jsonStr);
|
|
9957
|
-
logger2
|
|
9966
|
+
logger2.debug("Parsed entities from AI response", { count: entities.length });
|
|
9958
9967
|
if (response.stopReason === "max_tokens") {
|
|
9959
9968
|
const errorMsg = `AI response truncated: Found ${entities.length} entities but response hit max_tokens limit. Increase max_tokens or reduce resource size.`;
|
|
9960
|
-
logger2
|
|
9969
|
+
logger2.error(errorMsg);
|
|
9961
9970
|
throw new Error(errorMsg);
|
|
9962
9971
|
}
|
|
9963
9972
|
return entities.map((entity, idx) => {
|
|
9964
|
-
let
|
|
9965
|
-
let
|
|
9966
|
-
logger2
|
|
9973
|
+
let start = entity.startOffset;
|
|
9974
|
+
let end = entity.endOffset;
|
|
9975
|
+
logger2.debug("Processing entity", {
|
|
9967
9976
|
index: idx + 1,
|
|
9968
9977
|
total: entities.length,
|
|
9969
9978
|
type: entity.entityType,
|
|
9970
9979
|
text: entity.exact,
|
|
9971
|
-
offsetsFromAI: `[${
|
|
9980
|
+
offsetsFromAI: `[${start}:${end}]`
|
|
9972
9981
|
});
|
|
9973
|
-
const extractedText = exact.substring(
|
|
9982
|
+
const extractedText = exact.substring(start, end);
|
|
9974
9983
|
let anchorMethod;
|
|
9975
9984
|
if (extractedText === entity.exact) {
|
|
9976
9985
|
anchorMethod = "llm-exact";
|
|
9977
|
-
logger2
|
|
9986
|
+
logger2.debug("Entity anchored", {
|
|
9978
9987
|
text: entity.exact,
|
|
9979
9988
|
entityType: entity.entityType,
|
|
9980
9989
|
anchorMethod
|
|
9981
9990
|
});
|
|
9982
9991
|
} else {
|
|
9983
|
-
logger2
|
|
9992
|
+
logger2.debug("LLM offsets mismatch \u2014 attempting re-anchor", {
|
|
9984
9993
|
expected: entity.exact,
|
|
9985
|
-
llmOffsets: `[${
|
|
9994
|
+
llmOffsets: `[${start}:${end}]`,
|
|
9986
9995
|
foundAtLlmOffsets: extractedText
|
|
9987
9996
|
});
|
|
9988
9997
|
let occurrenceCount = 0;
|
|
@@ -9995,10 +10004,10 @@ Example output:
|
|
|
9995
10004
|
}
|
|
9996
10005
|
if (occurrenceCount === 0) {
|
|
9997
10006
|
anchorMethod = "dropped";
|
|
9998
|
-
logger2
|
|
10007
|
+
logger2.error("Entity text not found in resource \u2014 dropping", {
|
|
9999
10008
|
text: entity.exact,
|
|
10000
10009
|
entityType: entity.entityType,
|
|
10001
|
-
llmOffsets: `[${
|
|
10010
|
+
llmOffsets: `[${start}:${end}]`,
|
|
10002
10011
|
anchorMethod,
|
|
10003
10012
|
resourceStart: exact.substring(0, 200)
|
|
10004
10013
|
});
|
|
@@ -10024,9 +10033,9 @@ Example output:
|
|
|
10024
10033
|
}
|
|
10025
10034
|
if (recoveredOffset !== -1) {
|
|
10026
10035
|
anchorMethod = "context-recovered";
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
logger2
|
|
10036
|
+
start = recoveredOffset;
|
|
10037
|
+
end = recoveredOffset + entity.exact.length;
|
|
10038
|
+
logger2.debug("Entity anchored", {
|
|
10030
10039
|
text: entity.exact,
|
|
10031
10040
|
entityType: entity.entityType,
|
|
10032
10041
|
anchorMethod,
|
|
@@ -10034,9 +10043,9 @@ Example output:
|
|
|
10034
10043
|
});
|
|
10035
10044
|
} else if (occurrenceCount === 1) {
|
|
10036
10045
|
anchorMethod = "unique-match";
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
logger2
|
|
10046
|
+
start = firstOccurrence;
|
|
10047
|
+
end = firstOccurrence + entity.exact.length;
|
|
10048
|
+
logger2.debug("Entity anchored", {
|
|
10040
10049
|
text: entity.exact,
|
|
10041
10050
|
entityType: entity.entityType,
|
|
10042
10051
|
anchorMethod,
|
|
@@ -10044,9 +10053,9 @@ Example output:
|
|
|
10044
10053
|
});
|
|
10045
10054
|
} else {
|
|
10046
10055
|
anchorMethod = "first-of-many";
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
logger2
|
|
10056
|
+
start = firstOccurrence;
|
|
10057
|
+
end = firstOccurrence + entity.exact.length;
|
|
10058
|
+
logger2.warn("Entity anchored at first of multiple occurrences \u2014 may be wrong", {
|
|
10050
10059
|
text: entity.exact,
|
|
10051
10060
|
entityType: entity.entityType,
|
|
10052
10061
|
anchorMethod,
|
|
@@ -10061,58 +10070,71 @@ Example output:
|
|
|
10061
10070
|
return {
|
|
10062
10071
|
exact: entity.exact,
|
|
10063
10072
|
entityType: entity.entityType,
|
|
10064
|
-
|
|
10065
|
-
|
|
10073
|
+
start,
|
|
10074
|
+
end,
|
|
10066
10075
|
prefix: entity.prefix,
|
|
10067
10076
|
suffix: entity.suffix
|
|
10068
10077
|
};
|
|
10069
10078
|
}).filter((entity) => {
|
|
10070
10079
|
if (entity === null) {
|
|
10071
|
-
logger2
|
|
10080
|
+
logger2.debug("Filtered entity: null");
|
|
10072
10081
|
return false;
|
|
10073
10082
|
}
|
|
10074
|
-
if (entity.
|
|
10075
|
-
logger2
|
|
10083
|
+
if (entity.start === void 0 || entity.end === void 0) {
|
|
10084
|
+
logger2.warn("Filtered entity: missing offsets", { text: entity.exact });
|
|
10076
10085
|
return false;
|
|
10077
10086
|
}
|
|
10078
|
-
if (entity.
|
|
10079
|
-
logger2
|
|
10087
|
+
if (entity.start < 0) {
|
|
10088
|
+
logger2.warn("Filtered entity: negative start", {
|
|
10080
10089
|
text: entity.exact,
|
|
10081
|
-
|
|
10090
|
+
start: entity.start
|
|
10082
10091
|
});
|
|
10083
10092
|
return false;
|
|
10084
10093
|
}
|
|
10085
|
-
if (entity.
|
|
10086
|
-
logger2
|
|
10094
|
+
if (entity.end > exact.length) {
|
|
10095
|
+
logger2.warn("Filtered entity: end exceeds text length", {
|
|
10087
10096
|
text: entity.exact,
|
|
10088
|
-
|
|
10097
|
+
end: entity.end,
|
|
10089
10098
|
textLength: exact.length
|
|
10090
10099
|
});
|
|
10091
10100
|
return false;
|
|
10092
10101
|
}
|
|
10093
|
-
const extractedText = exact.substring(entity.
|
|
10102
|
+
const extractedText = exact.substring(entity.start, entity.end);
|
|
10094
10103
|
if (extractedText !== entity.exact) {
|
|
10095
|
-
logger2
|
|
10104
|
+
logger2.warn("Filtered entity: offset mismatch", {
|
|
10096
10105
|
expected: entity.exact,
|
|
10097
10106
|
got: extractedText,
|
|
10098
|
-
offsets: `[${entity.
|
|
10107
|
+
offsets: `[${entity.start}:${entity.end}]`
|
|
10099
10108
|
});
|
|
10100
10109
|
return false;
|
|
10101
10110
|
}
|
|
10102
|
-
logger2
|
|
10111
|
+
logger2.debug("Accepted entity", {
|
|
10103
10112
|
text: entity.exact,
|
|
10104
|
-
offsets: `[${entity.
|
|
10113
|
+
offsets: `[${entity.start}:${entity.end}]`
|
|
10105
10114
|
});
|
|
10106
10115
|
return true;
|
|
10107
10116
|
});
|
|
10108
10117
|
} catch (error) {
|
|
10118
|
+
logger2.error("Failed to parse entity extraction response", {
|
|
10119
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10120
|
+
});
|
|
10109
10121
|
return [];
|
|
10110
10122
|
}
|
|
10111
10123
|
}
|
|
10112
10124
|
function getLanguageName(locale) {
|
|
10113
10125
|
return getLocaleEnglishName(locale) || locale;
|
|
10114
10126
|
}
|
|
10115
|
-
async function generateResourceFromTopic(topic, entityTypes, client, userPrompt, locale, context, temperature, maxTokens,
|
|
10127
|
+
async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage) {
|
|
10128
|
+
logger2.debug("Generating resource from topic", {
|
|
10129
|
+
topicPreview: topic.substring(0, 100),
|
|
10130
|
+
entityTypes,
|
|
10131
|
+
hasUserPrompt: !!userPrompt,
|
|
10132
|
+
locale,
|
|
10133
|
+
sourceLanguage,
|
|
10134
|
+
hasContext: !!context,
|
|
10135
|
+
temperature,
|
|
10136
|
+
maxTokens
|
|
10137
|
+
});
|
|
10116
10138
|
const finalTemperature = temperature ?? 0.7;
|
|
10117
10139
|
const finalMaxTokens = maxTokens ?? 500;
|
|
10118
10140
|
const languageInstruction = locale && locale !== "en" ? `
|
|
@@ -10211,18 +10233,33 @@ Requirements:
|
|
|
10211
10233
|
content
|
|
10212
10234
|
};
|
|
10213
10235
|
};
|
|
10236
|
+
logger2.debug("Sending prompt to inference", {
|
|
10237
|
+
promptLength: prompt.length,
|
|
10238
|
+
temperature: finalTemperature,
|
|
10239
|
+
maxTokens: finalMaxTokens
|
|
10240
|
+
});
|
|
10214
10241
|
const response = await client.generateText(prompt, finalMaxTokens, finalTemperature);
|
|
10242
|
+
logger2.debug("Got response from inference", { responseLength: response.length });
|
|
10215
10243
|
const result = parseResponse(response);
|
|
10244
|
+
logger2.debug("Parsed response", {
|
|
10245
|
+
hasTitle: !!result.title,
|
|
10246
|
+
titleLength: result.title?.length,
|
|
10247
|
+
hasContent: !!result.content,
|
|
10248
|
+
contentLength: result.content?.length
|
|
10249
|
+
});
|
|
10216
10250
|
return result;
|
|
10217
10251
|
}
|
|
10218
10252
|
function buildTextAnnotation(resourceId, userId, generator, motivation, match, body) {
|
|
10253
|
+
const creator = didToAgent(userId);
|
|
10254
|
+
const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
|
|
10219
10255
|
return {
|
|
10220
10256
|
"@context": "http://www.w3.org/ns/anno.jsonld",
|
|
10221
10257
|
"type": "Annotation",
|
|
10222
10258
|
"id": generateAnnotationId(),
|
|
10223
10259
|
motivation,
|
|
10224
|
-
creator
|
|
10260
|
+
creator,
|
|
10225
10261
|
generator,
|
|
10262
|
+
wasAttributedTo,
|
|
10226
10263
|
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10227
10264
|
target: {
|
|
10228
10265
|
type: "SpecificResource",
|
|
@@ -10365,7 +10402,7 @@ async function processReferenceJob(content, inferenceClient, params, userId, gen
|
|
|
10365
10402
|
];
|
|
10366
10403
|
for (const entity of extractedEntities) {
|
|
10367
10404
|
try {
|
|
10368
|
-
const validated = validateAndCorrectOffsets(content, entity.
|
|
10405
|
+
const validated = validateAndCorrectOffsets(content, entity.start, entity.end, entity.exact);
|
|
10369
10406
|
const ann = buildTextAnnotation(
|
|
10370
10407
|
params.resourceId,
|
|
10371
10408
|
userId,
|
|
@@ -10419,7 +10456,7 @@ async function processTagJob(content, inferenceClient, params, userId, generator
|
|
|
10419
10456
|
result: { tagsFound: tags.length, tagsCreated: annotations.length, byCategory }
|
|
10420
10457
|
};
|
|
10421
10458
|
}
|
|
10422
|
-
async function processGenerationJob(inferenceClient, params, onProgress) {
|
|
10459
|
+
async function processGenerationJob(inferenceClient, params, onProgress, logger2) {
|
|
10423
10460
|
onProgress(20, "Fetching context...", "fetching");
|
|
10424
10461
|
const title = params.title ?? "Untitled";
|
|
10425
10462
|
const entityTypes = (params.entityTypes ?? []).map(String);
|
|
@@ -10428,13 +10465,12 @@ async function processGenerationJob(inferenceClient, params, onProgress) {
|
|
|
10428
10465
|
title,
|
|
10429
10466
|
entityTypes,
|
|
10430
10467
|
inferenceClient,
|
|
10468
|
+
logger2,
|
|
10431
10469
|
params.prompt,
|
|
10432
10470
|
params.language,
|
|
10433
10471
|
params.context,
|
|
10434
10472
|
params.temperature,
|
|
10435
10473
|
params.maxTokens,
|
|
10436
|
-
void 0,
|
|
10437
|
-
// logger
|
|
10438
10474
|
params.sourceLanguage
|
|
10439
10475
|
);
|
|
10440
10476
|
onProgress(85, "Creating resource...", "creating");
|
|
@@ -10509,7 +10545,7 @@ async function handleJob(adapter, config, job) {
|
|
|
10509
10545
|
}
|
|
10510
10546
|
}
|
|
10511
10547
|
async function handleJobInner(adapter, config, job) {
|
|
10512
|
-
const { session } = config;
|
|
10548
|
+
const { session, inferenceClient, generator } = config;
|
|
10513
10549
|
const { resourceId, userId, jobId, type: jobType } = job;
|
|
10514
10550
|
const annotationId = job.params.referenceId;
|
|
10515
10551
|
const lifecycleBase = {
|
|
@@ -10520,12 +10556,10 @@ async function handleJobInner(adapter, config, job) {
|
|
|
10520
10556
|
...annotationId ? { annotationId } : {}
|
|
10521
10557
|
};
|
|
10522
10558
|
await emitEvent(session, "job:start", lifecycleBase);
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
adapter.failJob(jobId, `No inference engine configured for job type: ${jobType}`);
|
|
10559
|
+
if (!config.jobTypes.includes(jobType)) {
|
|
10560
|
+
adapter.failJob(jobId, `Worker not configured for job type: ${jobType}`);
|
|
10526
10561
|
return;
|
|
10527
10562
|
}
|
|
10528
|
-
const { inferenceClient, generator } = engine;
|
|
10529
10563
|
const onProgress = (percentage, message, stage, extra) => {
|
|
10530
10564
|
emitEvent(session, "job:report-progress", {
|
|
10531
10565
|
...lifecycleBase,
|
|
@@ -10605,7 +10639,8 @@ async function handleJobInner(adapter, config, job) {
|
|
|
10605
10639
|
job.params,
|
|
10606
10640
|
userId,
|
|
10607
10641
|
generator,
|
|
10608
|
-
onProgress
|
|
10642
|
+
onProgress,
|
|
10643
|
+
config.logger
|
|
10609
10644
|
);
|
|
10610
10645
|
for (const ann of annotations) {
|
|
10611
10646
|
await emitEvent(session, "mark:create", { annotation: ann, userId, resourceId });
|
|
@@ -10637,7 +10672,8 @@ async function handleJobInner(adapter, config, job) {
|
|
|
10637
10672
|
const genResult = await processGenerationJob(
|
|
10638
10673
|
inferenceClient,
|
|
10639
10674
|
job.params,
|
|
10640
|
-
onProgress
|
|
10675
|
+
onProgress,
|
|
10676
|
+
config.logger
|
|
10641
10677
|
);
|
|
10642
10678
|
const genParams = job.params;
|
|
10643
10679
|
const storageUri = deriveStorageUri(genResult.title, genResult.format);
|
|
@@ -10646,7 +10682,6 @@ async function handleJobInner(adapter, config, job) {
|
|
|
10646
10682
|
file: Buffer.from(genResult.content),
|
|
10647
10683
|
format: genResult.format,
|
|
10648
10684
|
storageUri,
|
|
10649
|
-
creationMethod: "generated",
|
|
10650
10685
|
sourceResourceId: resourceId,
|
|
10651
10686
|
...genParams.referenceId ? { sourceAnnotationId: genParams.referenceId } : {},
|
|
10652
10687
|
...genParams.prompt ? { generationPrompt: genParams.prompt } : {},
|
|
@@ -10718,24 +10753,20 @@ function toClientConfig(w) {
|
|
|
10718
10753
|
...w.apiKey && { apiKey: w.apiKey }
|
|
10719
10754
|
};
|
|
10720
10755
|
}
|
|
10721
|
-
var
|
|
10722
|
-
var engines = {};
|
|
10756
|
+
var groups = /* @__PURE__ */ new Map();
|
|
10723
10757
|
for (const jobType of ALL_JOB_TYPES) {
|
|
10724
|
-
const
|
|
10725
|
-
const key = clientKey(
|
|
10726
|
-
let
|
|
10727
|
-
if (!
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
model: w.model
|
|
10737
|
-
};
|
|
10738
|
-
engines[jobType] = { inferenceClient: client, generator };
|
|
10758
|
+
const inference = resolveWorker(jobType);
|
|
10759
|
+
const key = clientKey(inference);
|
|
10760
|
+
let group = groups.get(key);
|
|
10761
|
+
if (!group) {
|
|
10762
|
+
group = {
|
|
10763
|
+
inference,
|
|
10764
|
+
jobTypes: [],
|
|
10765
|
+
client: createInferenceClient(toClientConfig(inference), logger)
|
|
10766
|
+
};
|
|
10767
|
+
groups.set(key, group);
|
|
10768
|
+
}
|
|
10769
|
+
group.jobTypes.push(jobType);
|
|
10739
10770
|
}
|
|
10740
10771
|
function parseBackendUrl(url) {
|
|
10741
10772
|
const parsed = new URL(url);
|
|
@@ -10744,35 +10775,35 @@ function parseBackendUrl(url) {
|
|
|
10744
10775
|
const port = parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80;
|
|
10745
10776
|
return { protocol, host, port };
|
|
10746
10777
|
}
|
|
10747
|
-
async function
|
|
10778
|
+
async function authenticateAgent(provider, model) {
|
|
10748
10779
|
if (!workerSecret) {
|
|
10749
|
-
|
|
10750
|
-
return "";
|
|
10780
|
+
throw new Error("SEMIONT_WORKER_SECRET is required to authenticate worker agents");
|
|
10751
10781
|
}
|
|
10752
|
-
const response = await fetch(`${backendBaseUrl}/api/tokens/
|
|
10782
|
+
const response = await fetch(`${backendBaseUrl}/api/tokens/agent`, {
|
|
10753
10783
|
method: "POST",
|
|
10754
10784
|
headers: { "Content-Type": "application/json" },
|
|
10755
|
-
body: JSON.stringify({ secret: workerSecret })
|
|
10785
|
+
body: JSON.stringify({ secret: workerSecret, provider, model })
|
|
10756
10786
|
});
|
|
10757
10787
|
if (!response.ok) {
|
|
10758
|
-
throw new Error(`
|
|
10788
|
+
throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);
|
|
10759
10789
|
}
|
|
10760
|
-
|
|
10761
|
-
return token;
|
|
10790
|
+
return await response.json();
|
|
10762
10791
|
}
|
|
10763
|
-
async function
|
|
10764
|
-
const {
|
|
10765
|
-
initObservabilityNode({ serviceName: "semiont-worker" });
|
|
10766
|
-
logger.info("Authenticating", { baseUrl: backendBaseUrl });
|
|
10767
|
-
const initialToken = await authenticate();
|
|
10768
|
-
logger.info("Authenticated");
|
|
10792
|
+
async function startAgentWorker(group) {
|
|
10793
|
+
const { inference } = group;
|
|
10769
10794
|
const { protocol, host, port } = parseBackendUrl(backendBaseUrl);
|
|
10770
|
-
const
|
|
10795
|
+
const { token: initialToken, did } = await authenticateAgent(inference.type, inference.model);
|
|
10796
|
+
const generator = softwareToAgent({
|
|
10797
|
+
domain: host,
|
|
10798
|
+
provider: inference.type,
|
|
10799
|
+
model: inference.model
|
|
10800
|
+
});
|
|
10801
|
+
const kbId = `agent-${inference.type}-${inference.model}-${hostname()}`;
|
|
10771
10802
|
const endpoint = { kind: "http", host, port, protocol };
|
|
10772
10803
|
const kb = {
|
|
10773
10804
|
id: kbId,
|
|
10774
|
-
label:
|
|
10775
|
-
email: `
|
|
10805
|
+
label: `${inference.type} / ${inference.model} @ ${host}`,
|
|
10806
|
+
email: `agent@${host}`,
|
|
10776
10807
|
endpoint
|
|
10777
10808
|
};
|
|
10778
10809
|
const storage = new InMemorySessionStorage();
|
|
@@ -10793,37 +10824,58 @@ async function main() {
|
|
|
10793
10824
|
token$,
|
|
10794
10825
|
refresh: async () => {
|
|
10795
10826
|
try {
|
|
10796
|
-
|
|
10827
|
+
const { token } = await authenticateAgent(inference.type, inference.model);
|
|
10828
|
+
return token;
|
|
10797
10829
|
} catch (err) {
|
|
10798
|
-
logger.error("
|
|
10799
|
-
error: err instanceof Error ? err.message : String(err)
|
|
10830
|
+
logger.error("Agent token refresh failed", {
|
|
10831
|
+
error: err instanceof Error ? err.message : String(err),
|
|
10832
|
+
agent: did
|
|
10800
10833
|
});
|
|
10801
10834
|
return null;
|
|
10802
10835
|
}
|
|
10803
10836
|
},
|
|
10804
|
-
// No validate callback — workers are service principals with no
|
|
10805
|
-
// user record to fetch. `session.user$` stays null.
|
|
10806
10837
|
onError: (err) => {
|
|
10807
|
-
logger.error("Session error", { code: err.code, message: err.message });
|
|
10838
|
+
logger.error("Session error", { code: err.code, message: err.message, agent: did });
|
|
10808
10839
|
}
|
|
10809
10840
|
});
|
|
10810
10841
|
await session.ready;
|
|
10811
|
-
const
|
|
10842
|
+
const adapter = startWorkerProcess({
|
|
10812
10843
|
session,
|
|
10813
|
-
jobTypes:
|
|
10814
|
-
|
|
10844
|
+
jobTypes: group.jobTypes,
|
|
10845
|
+
inferenceClient: group.client,
|
|
10846
|
+
generator,
|
|
10815
10847
|
logger
|
|
10816
10848
|
});
|
|
10817
|
-
logger.info("
|
|
10849
|
+
logger.info("Agent ready", {
|
|
10850
|
+
did,
|
|
10851
|
+
provider: inference.type,
|
|
10852
|
+
model: inference.model,
|
|
10853
|
+
jobTypes: group.jobTypes
|
|
10854
|
+
});
|
|
10855
|
+
return {
|
|
10856
|
+
session,
|
|
10857
|
+
dispose: async () => {
|
|
10858
|
+
adapter.dispose();
|
|
10859
|
+
await session.dispose();
|
|
10860
|
+
}
|
|
10861
|
+
};
|
|
10862
|
+
}
|
|
10863
|
+
async function main() {
|
|
10864
|
+
const { initObservabilityNode } = await import('@semiont/observability/node');
|
|
10865
|
+
initObservabilityNode({ serviceName: "semiont-worker" });
|
|
10866
|
+
logger.info("Starting agents", {
|
|
10818
10867
|
baseUrl: backendBaseUrl,
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10868
|
+
agents: Array.from(groups.values()).map((g) => ({
|
|
10869
|
+
provider: g.inference.type,
|
|
10870
|
+
model: g.inference.model,
|
|
10871
|
+
jobTypes: g.jobTypes
|
|
10872
|
+
}))
|
|
10822
10873
|
});
|
|
10874
|
+
const workers = await Promise.all(Array.from(groups.values()).map(startAgentWorker));
|
|
10823
10875
|
const health = createServer((req, res) => {
|
|
10824
10876
|
if (req.url === "/health") {
|
|
10825
10877
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10826
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
10878
|
+
res.end(JSON.stringify({ status: "ok", agents: workers.length }));
|
|
10827
10879
|
} else {
|
|
10828
10880
|
res.writeHead(404);
|
|
10829
10881
|
res.end();
|
|
@@ -10834,8 +10886,7 @@ async function main() {
|
|
|
10834
10886
|
});
|
|
10835
10887
|
const shutdown = async () => {
|
|
10836
10888
|
logger.info("Shutting down");
|
|
10837
|
-
|
|
10838
|
-
await session.dispose();
|
|
10889
|
+
await Promise.all(workers.map((w) => w.dispose()));
|
|
10839
10890
|
health.close();
|
|
10840
10891
|
process.exit(0);
|
|
10841
10892
|
};
|