@semiont/jobs 0.5.9 → 0.5.11
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 +64 -6
- package/dist/index.js +207 -55
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +368 -181
- package/dist/worker-main.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JobId, UserId, ResourceId, EntityType, AnnotationId,
|
|
1
|
+
import { JobId, UserId, ResourceId, EntityType, AnnotationId, GatheredContext, SupportedMediaType, TagSchema, Logger, EventBus, components } from '@semiont/core';
|
|
2
2
|
import { SemiontProject } from '@semiont/core/node';
|
|
3
3
|
import { InferenceClient } from '@semiont/inference';
|
|
4
4
|
|
|
@@ -74,10 +74,13 @@ interface DetectionParams {
|
|
|
74
74
|
* Generation job parameters
|
|
75
75
|
*/
|
|
76
76
|
interface GenerationParams {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The unresolved reference an annotation-focus generation was triggered from.
|
|
79
|
+
* Absent for resource-focus generation (`yield.fromResource`). When present, the
|
|
80
|
+
* worker auto-binds the new resource to it; when absent, provenance is a minted
|
|
81
|
+
* source→derived reference annotation (Fork 2b).
|
|
82
|
+
*/
|
|
83
|
+
referenceId?: AnnotationId;
|
|
81
84
|
prompt?: string;
|
|
82
85
|
title?: string;
|
|
83
86
|
entityTypes?: EntityType[];
|
|
@@ -93,6 +96,36 @@ interface GenerationParams {
|
|
|
93
96
|
temperature?: number;
|
|
94
97
|
maxTokens?: number;
|
|
95
98
|
storageUri?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Requested media type of the generated resource's content. Default `text/markdown`.
|
|
101
|
+
* The generation worker produces `text/markdown` and `text/plain`; any other value
|
|
102
|
+
* fails the job (no silent fallback) — Semiont is multi-modal at the core, but
|
|
103
|
+
* generation coverage is text-only for now and the gap is surfaced, not hidden.
|
|
104
|
+
*/
|
|
105
|
+
outputMediaType?: SupportedMediaType;
|
|
106
|
+
/**
|
|
107
|
+
* What the model is asked to DO — the prompt's framing verb. Canonical values map
|
|
108
|
+
* to tested framings; any other string is used verbatim as the framing instruction
|
|
109
|
+
* (loud degrade: the worker warns, never silently falls back to 'resource').
|
|
110
|
+
* Default 'resource'. See .plans/YIELD-STRUCTURE.md.
|
|
111
|
+
*/
|
|
112
|
+
task?: 'resource' | 'answer' | 'summary' | (string & {});
|
|
113
|
+
/**
|
|
114
|
+
* How the output is internally segmented — text-bearing shape, subordinate to
|
|
115
|
+
* `outputMediaType` (never its peer). Canonical values map to tested guidance; any
|
|
116
|
+
* other string becomes a freeform "Organize the output as: …" instruction (loud
|
|
117
|
+
* degrade). UNSET means NO structure directive is emitted — the task framing and
|
|
118
|
+
* the model determine shape. See .plans/YIELD-STRUCTURE.md D2/D5.
|
|
119
|
+
*/
|
|
120
|
+
structure?: 'prose' | 'sections' | 'chat' | (string & {});
|
|
121
|
+
/**
|
|
122
|
+
* Ask the model to cite: emit `[[<id>]]` transport tokens after each claim, using
|
|
123
|
+
* the ids the context embedding provides (CONTEXT-IDENTIFIERS). The worker
|
|
124
|
+
* validates each id against the embedded context (unknown ids are dropped
|
|
125
|
+
* loudly), strips the tokens from the stored content, and mints W3C linking
|
|
126
|
+
* annotations on the derived resource. See .plans/INLINE-CITATIONS.md.
|
|
127
|
+
*/
|
|
128
|
+
cite?: boolean;
|
|
96
129
|
}
|
|
97
130
|
/**
|
|
98
131
|
* Highlight detection job parameters
|
|
@@ -466,6 +499,30 @@ declare class FsJobQueue implements JobQueue {
|
|
|
466
499
|
}>;
|
|
467
500
|
}
|
|
468
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Citation-token resolver (INLINE-CITATIONS P1).
|
|
504
|
+
*
|
|
505
|
+
* Under `cite`, the generation prompt asks the model to emit `[[<resourceId>]]` /
|
|
506
|
+
* `[[<resourceId>/<annotationId>]]` transport tokens after each claim, citing ids
|
|
507
|
+
* the context embedding put in front of it (CONTEXT-IDENTIFIERS). Tokens are
|
|
508
|
+
* TRANSPORT, not content: this resolver parses them, validates each id against the
|
|
509
|
+
* ids actually present in the embedded context (the hallucination guard — an
|
|
510
|
+
* unknown id is dropped loudly, never silently linked), STRIPS them from the
|
|
511
|
+
* content before upload, and returns claim-span citations the worker mints as W3C
|
|
512
|
+
* linking annotations on the derived resource. See .plans/INLINE-CITATIONS.md.
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
interface GenerationCitation {
|
|
516
|
+
/** The cited source resource (validated present in the embedded context). */
|
|
517
|
+
resourceId: string;
|
|
518
|
+
/** The contributing annotation, when the cited excerpt was annotation-derived. */
|
|
519
|
+
annotationId?: string;
|
|
520
|
+
/** Claim span in the FINAL (token-stripped) content; substring(start, end) === exact. */
|
|
521
|
+
start: number;
|
|
522
|
+
end: number;
|
|
523
|
+
exact: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
469
526
|
/**
|
|
470
527
|
* Job Processors
|
|
471
528
|
*
|
|
@@ -500,6 +557,7 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
|
|
|
500
557
|
content: string;
|
|
501
558
|
title: string;
|
|
502
559
|
format: SupportedMediaType;
|
|
560
|
+
citations: GenerationCitation[];
|
|
503
561
|
result: GenerationResult;
|
|
504
562
|
}>;
|
|
505
563
|
|
|
@@ -627,7 +685,7 @@ declare class AnnotationDetection {
|
|
|
627
685
|
* generate German content from an English source resource. See
|
|
628
686
|
* `types.ts` "Locale conventions" for the full discussion.
|
|
629
687
|
*/
|
|
630
|
-
declare function generateResourceFromTopic(topic: string, entityTypes: string[], client: InferenceClient, logger: Logger, userPrompt?: string, locale?: string, context?: GatheredContext, temperature?: number, maxTokens?: number, sourceLanguage?: string): Promise<{
|
|
688
|
+
declare function generateResourceFromTopic(topic: string, entityTypes: string[], client: InferenceClient, logger: Logger, userPrompt?: string, locale?: string, context?: GatheredContext, temperature?: number, maxTokens?: number, sourceLanguage?: string, outputMediaType?: SupportedMediaType, task?: string, structure?: string, cite?: boolean): Promise<{
|
|
631
689
|
title: string;
|
|
632
690
|
content: string;
|
|
633
691
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { jobId, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
|
|
3
|
+
import { jobId, deriveViews, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
|
|
4
4
|
import { generateAnnotationId } from '@semiont/event-sourcing';
|
|
5
5
|
|
|
6
6
|
// src/fs-job-queue.ts
|
|
@@ -1068,7 +1068,13 @@ Example output:
|
|
|
1068
1068
|
function getLanguageName(locale) {
|
|
1069
1069
|
return getLocaleEnglishName(locale) || locale;
|
|
1070
1070
|
}
|
|
1071
|
-
|
|
1071
|
+
var RESOURCE_CONTENT_CAP = 4e3;
|
|
1072
|
+
var SEMANTIC_MATCH_LIMIT = 3;
|
|
1073
|
+
var SEMANTIC_MATCH_CHARS = 240;
|
|
1074
|
+
function idLabel(resourceId, annotationId) {
|
|
1075
|
+
return `[${resourceId}${annotationId ? `/${annotationId}` : ""}]`;
|
|
1076
|
+
}
|
|
1077
|
+
async function generateResourceFromTopic(topic, entityTypes, client, logger, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown", task = "resource", structure, cite = false) {
|
|
1072
1078
|
logger.debug("Generating resource from topic", {
|
|
1073
1079
|
topicPreview: topic.substring(0, 100),
|
|
1074
1080
|
entityTypes,
|
|
@@ -1077,7 +1083,10 @@ async function generateResourceFromTopic(topic, entityTypes, client, logger, use
|
|
|
1077
1083
|
sourceLanguage,
|
|
1078
1084
|
hasContext: !!context,
|
|
1079
1085
|
temperature,
|
|
1080
|
-
maxTokens
|
|
1086
|
+
maxTokens,
|
|
1087
|
+
outputMediaType,
|
|
1088
|
+
task,
|
|
1089
|
+
structure
|
|
1081
1090
|
});
|
|
1082
1091
|
const finalTemperature = temperature ?? 0.7;
|
|
1083
1092
|
const finalMaxTokens = maxTokens ?? 500;
|
|
@@ -1088,83 +1097,146 @@ IMPORTANT: Write the entire resource in ${getLanguageName(locale)}.` : "";
|
|
|
1088
1097
|
|
|
1089
1098
|
The source resource and embedded context are in ${getLanguageName(sourceLanguage)}.` : "";
|
|
1090
1099
|
let annotationSection = "";
|
|
1100
|
+
let contextSection = "";
|
|
1101
|
+
let resourceSection = "";
|
|
1102
|
+
let graphSection = "";
|
|
1091
1103
|
if (context) {
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1104
|
+
const { focus } = context;
|
|
1105
|
+
const mainResourceId = focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"];
|
|
1106
|
+
const focalAnnotationId = focus.kind === "annotation" ? focus.annotation.id : void 0;
|
|
1107
|
+
if (focus.kind === "annotation") {
|
|
1108
|
+
const parts = [];
|
|
1109
|
+
parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);
|
|
1110
|
+
parts.push(`- Source resource: ${focus.sourceResource.name} ${idLabel(mainResourceId)}`);
|
|
1111
|
+
const { motivation, body } = focus.annotation;
|
|
1112
|
+
if (motivation === "commenting" || motivation === "assessing") {
|
|
1113
|
+
const bodyItem = Array.isArray(body) ? body[0] : body;
|
|
1114
|
+
if (bodyItem && "value" in bodyItem && bodyItem.value) {
|
|
1115
|
+
const label = motivation === "commenting" ? "Comment" : "Assessment";
|
|
1116
|
+
parts.push(`- ${label}: ${bodyItem.value}`);
|
|
1117
|
+
}
|
|
1101
1118
|
}
|
|
1102
|
-
|
|
1103
|
-
annotationSection = `
|
|
1119
|
+
annotationSection = `
|
|
1104
1120
|
|
|
1105
1121
|
Annotation context:
|
|
1106
1122
|
${parts.join("\n")}`;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
const { before, selected, after } = context.sourceContext;
|
|
1111
|
-
contextSection = `
|
|
1123
|
+
if (focus.selected) {
|
|
1124
|
+
const { before, text, after } = focus.selected;
|
|
1125
|
+
contextSection = `
|
|
1112
1126
|
|
|
1113
1127
|
Source document context:
|
|
1114
1128
|
---
|
|
1115
1129
|
${before ? `...${before}` : ""}
|
|
1116
|
-
**[${
|
|
1130
|
+
**[${text}]**
|
|
1117
1131
|
${after ? `${after}...` : ""}
|
|
1118
1132
|
---
|
|
1119
1133
|
`;
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
const parts = [`- Resource: ${focus.resource.name} ${idLabel(mainResourceId)}`];
|
|
1137
|
+
if (focus.summary) parts.push(`- Summary: ${focus.summary}`);
|
|
1138
|
+
if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {
|
|
1139
|
+
parts.push(`- Suggested references: ${focus.suggestedReferences.join(", ")}`);
|
|
1140
|
+
}
|
|
1141
|
+
resourceSection = `
|
|
1142
|
+
|
|
1143
|
+
Resource context:
|
|
1144
|
+
${parts.join("\n")}`;
|
|
1145
|
+
if (focus.content?.main) {
|
|
1146
|
+
resourceSection += `
|
|
1147
|
+
|
|
1148
|
+
Resource content:
|
|
1149
|
+
---
|
|
1150
|
+
${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}
|
|
1151
|
+
---`;
|
|
1152
|
+
}
|
|
1153
|
+
const related = Object.entries(focus.content?.related ?? {});
|
|
1154
|
+
if (related.length > 0) {
|
|
1155
|
+
const blocks = related.map(([id, text]) => `[${id}]
|
|
1156
|
+
${text.slice(0, RESOURCE_CONTENT_CAP)}`).join("\n\n");
|
|
1157
|
+
resourceSection += `
|
|
1158
|
+
|
|
1159
|
+
Related resource content:
|
|
1160
|
+
---
|
|
1161
|
+
${blocks}
|
|
1162
|
+
---`;
|
|
1163
|
+
}
|
|
1140
1164
|
}
|
|
1141
|
-
if (
|
|
1142
|
-
|
|
1165
|
+
if (mainResourceId) {
|
|
1166
|
+
const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);
|
|
1167
|
+
const parts = [];
|
|
1168
|
+
if (views.connections.length > 0) {
|
|
1169
|
+
const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""} ${idLabel(c.resourceId)}`).join(", ");
|
|
1170
|
+
parts.push(`- Connected resources: ${connList}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (views.citedByCount > 0) {
|
|
1173
|
+
const citedNames = views.citedBy.map((c) => `${c.resourceName} ${idLabel(c.resourceId)}`).join(", ");
|
|
1174
|
+
parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (views.siblingEntityTypes.length > 0) {
|
|
1177
|
+
parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(", ")}`);
|
|
1178
|
+
}
|
|
1179
|
+
if (context.inferredRelationshipSummary) {
|
|
1180
|
+
parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);
|
|
1181
|
+
}
|
|
1182
|
+
if (parts.length > 0) {
|
|
1183
|
+
graphSection = `
|
|
1143
1184
|
|
|
1144
1185
|
Knowledge graph context:
|
|
1145
1186
|
${parts.join("\n")}`;
|
|
1187
|
+
}
|
|
1146
1188
|
}
|
|
1147
1189
|
}
|
|
1148
1190
|
let semanticContextSection = "";
|
|
1149
1191
|
const similar = context?.semanticContext?.similar ?? [];
|
|
1150
1192
|
if (similar.length > 0) {
|
|
1151
|
-
const lines = [...similar].sort((a, b) => b.score - a.score).slice(0,
|
|
1193
|
+
const lines = [...similar].sort((a, b) => b.score - a.score).slice(0, SEMANTIC_MATCH_LIMIT).map((m) => `- ${idLabel(m.resourceId, m.annotationId)} (${m.score.toFixed(2)}) ${m.text.slice(0, SEMANTIC_MATCH_CHARS)}`);
|
|
1152
1194
|
semanticContextSection = `
|
|
1153
1195
|
|
|
1154
1196
|
Related passages from the knowledge base:
|
|
1155
1197
|
${lines.join("\n")}`;
|
|
1156
1198
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1199
|
+
let leadLine;
|
|
1200
|
+
if (task === "resource") {
|
|
1201
|
+
leadLine = `Generate a concise, informative resource about "${topic}".`;
|
|
1202
|
+
} else if (task === "answer") {
|
|
1203
|
+
leadLine = `Answer the following question directly and concisely, grounded in the provided context: "${topic}"`;
|
|
1204
|
+
} else if (task === "summary") {
|
|
1205
|
+
leadLine = `Write a concise summary of "${topic}".`;
|
|
1206
|
+
} else {
|
|
1207
|
+
logger.warn("Unknown task \u2014 using it verbatim as the framing instruction", { task });
|
|
1208
|
+
leadLine = `${task}
|
|
1209
|
+
Topic: "${topic}"`;
|
|
1210
|
+
}
|
|
1211
|
+
const isPlainText = outputMediaType === "text/plain";
|
|
1212
|
+
let structureRequirement = "";
|
|
1213
|
+
let titleRequirement = "";
|
|
1214
|
+
if (structure === "sections") {
|
|
1215
|
+
structureRequirement = isPlainText ? "\n- Organize the content into titled sections with well-structured paragraphs" : "\n- Organize the content into titled sections (## Section) with well-structured paragraphs";
|
|
1216
|
+
if (!isPlainText) {
|
|
1217
|
+
titleRequirement = "\n- Start with a clear heading (# Title)";
|
|
1218
|
+
}
|
|
1219
|
+
} else if (structure === "prose") {
|
|
1220
|
+
structureRequirement = "\n- Write flowing, well-structured paragraphs with no section headings";
|
|
1221
|
+
} else if (structure === "chat") {
|
|
1222
|
+
structureRequirement = "\n- Structure the content as a conversational chat transcript \u2014 a sequence of alternating, speaker-labeled turns (no section headings)";
|
|
1223
|
+
} else if (structure) {
|
|
1224
|
+
logger.warn("Unknown structure \u2014 passing it through as freeform organization guidance", { structure });
|
|
1225
|
+
structureRequirement = `
|
|
1226
|
+
- Organize the output as: ${structure}`;
|
|
1227
|
+
}
|
|
1228
|
+
const citeRequirement = cite ? "\n- Ground every claim in the provided context. Immediately after each claim, cite its source by emitting [[<id>]], where <id> is an id shown in square brackets in the context above (for a passage labeled [abc], emit [[abc]]). Cite only ids that appear in the context." : "";
|
|
1229
|
+
const formatRequirements = isPlainText ? `- Write the response as plain text \u2014 no formatting markup (no #, *, backticks, headings, or links)
|
|
1230
|
+
- Begin with the title on its own first line` : `- Use markdown formatting
|
|
1231
|
+
- Write the response as markdown`;
|
|
1232
|
+
const prompt = `${leadLine}
|
|
1233
|
+
${userPrompt ? `Instruction: ${userPrompt}` : ""}
|
|
1234
|
+
${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
|
|
1161
1235
|
|
|
1162
1236
|
Requirements:
|
|
1163
|
-
-
|
|
1164
|
-
-
|
|
1165
|
-
|
|
1166
|
-
- Use markdown formatting
|
|
1167
|
-
- Write the response as markdown`;
|
|
1237
|
+
- Aim for approximately ${finalMaxTokens} tokens of content
|
|
1238
|
+
- Be factual and informative${structureRequirement}${titleRequirement}${citeRequirement}
|
|
1239
|
+
${formatRequirements}`;
|
|
1168
1240
|
const parseResponse = (response2) => {
|
|
1169
1241
|
let content = response2.trim();
|
|
1170
1242
|
if (content.startsWith("```markdown") || content.startsWith("```md")) {
|
|
@@ -1202,6 +1274,67 @@ Requirements:
|
|
|
1202
1274
|
});
|
|
1203
1275
|
return result;
|
|
1204
1276
|
}
|
|
1277
|
+
|
|
1278
|
+
// src/workers/generation/citation-resolver.ts
|
|
1279
|
+
var CITATION_TOKEN = /\[\[([^\s[\]/]+)(?:\/([^\s[\]/]+))?\]\]/g;
|
|
1280
|
+
function collectContextResourceIds(context) {
|
|
1281
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1282
|
+
if (!context) return ids;
|
|
1283
|
+
const { focus } = context;
|
|
1284
|
+
ids.add(focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"]);
|
|
1285
|
+
for (const node of context.graph.nodes) {
|
|
1286
|
+
if (node.type === "resource") ids.add(node.id);
|
|
1287
|
+
}
|
|
1288
|
+
for (const m of context.semanticContext?.similar ?? []) ids.add(m.resourceId);
|
|
1289
|
+
if (focus.kind === "resource") {
|
|
1290
|
+
for (const id of Object.keys(focus.content?.related ?? {})) ids.add(id);
|
|
1291
|
+
}
|
|
1292
|
+
return ids;
|
|
1293
|
+
}
|
|
1294
|
+
function resolveCitationTokens(content, validResourceIds, logger) {
|
|
1295
|
+
const citations = [];
|
|
1296
|
+
let clean = "";
|
|
1297
|
+
let last = 0;
|
|
1298
|
+
for (const match of content.matchAll(CITATION_TOKEN)) {
|
|
1299
|
+
const token = match[0];
|
|
1300
|
+
const citedResourceId = match[1];
|
|
1301
|
+
const annotationId = match[2];
|
|
1302
|
+
clean += content.slice(last, match.index).replace(/[ \t]+$/, "");
|
|
1303
|
+
last = match.index + token.length;
|
|
1304
|
+
if (!validResourceIds.has(citedResourceId)) {
|
|
1305
|
+
logger.warn("Citation token references an id absent from the provided context \u2014 dropped", {
|
|
1306
|
+
resourceId: citedResourceId
|
|
1307
|
+
});
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const end = clean.length;
|
|
1311
|
+
let start = 0;
|
|
1312
|
+
for (let i = end - 2; i >= 0; i--) {
|
|
1313
|
+
const ch = clean[i];
|
|
1314
|
+
if (ch === "." || ch === "!" || ch === "?" || ch === "\n") {
|
|
1315
|
+
start = i + 1;
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
while (start < end && /\s/.test(clean[start])) start++;
|
|
1320
|
+
const exact = clean.slice(start, end);
|
|
1321
|
+
if (exact.length === 0) {
|
|
1322
|
+
logger.warn("Citation token has no preceding claim text \u2014 dropped", {
|
|
1323
|
+
resourceId: citedResourceId
|
|
1324
|
+
});
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
citations.push({
|
|
1328
|
+
resourceId: citedResourceId,
|
|
1329
|
+
...annotationId ? { annotationId } : {},
|
|
1330
|
+
start,
|
|
1331
|
+
end,
|
|
1332
|
+
exact
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
clean += content.slice(last);
|
|
1336
|
+
return { content: clean, citations };
|
|
1337
|
+
}
|
|
1205
1338
|
function toMatch(r) {
|
|
1206
1339
|
return {
|
|
1207
1340
|
exact: r.exact,
|
|
@@ -1483,6 +1616,13 @@ async function processTagJob(content, inferenceClient, params, userId, generator
|
|
|
1483
1616
|
};
|
|
1484
1617
|
}
|
|
1485
1618
|
async function processGenerationJob(inferenceClient, params, onProgress, logger) {
|
|
1619
|
+
const GENERATABLE_MEDIA_TYPES = ["text/markdown", "text/plain"];
|
|
1620
|
+
const outputMediaType = params.outputMediaType ?? "text/markdown";
|
|
1621
|
+
if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {
|
|
1622
|
+
throw new Error(
|
|
1623
|
+
`Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1486
1626
|
onProgress(20, "Fetching context...", "fetching");
|
|
1487
1627
|
const title = params.title ?? "Untitled";
|
|
1488
1628
|
const entityTypes = (params.entityTypes ?? []).map(String);
|
|
@@ -1497,13 +1637,25 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
|
|
|
1497
1637
|
params.context,
|
|
1498
1638
|
params.temperature,
|
|
1499
1639
|
params.maxTokens,
|
|
1500
|
-
params.sourceLanguage
|
|
1640
|
+
params.sourceLanguage,
|
|
1641
|
+
outputMediaType,
|
|
1642
|
+
params.task,
|
|
1643
|
+
params.structure,
|
|
1644
|
+
params.cite
|
|
1501
1645
|
);
|
|
1646
|
+
let content = generated.content;
|
|
1647
|
+
let citations = [];
|
|
1648
|
+
if (params.cite === true) {
|
|
1649
|
+
const resolved = resolveCitationTokens(content, collectContextResourceIds(params.context), logger);
|
|
1650
|
+
content = resolved.content;
|
|
1651
|
+
citations = resolved.citations;
|
|
1652
|
+
}
|
|
1502
1653
|
onProgress(85, "Creating resource...", "creating");
|
|
1503
1654
|
return {
|
|
1504
|
-
content
|
|
1655
|
+
content,
|
|
1505
1656
|
title: generated.title ?? title,
|
|
1506
|
-
format:
|
|
1657
|
+
format: outputMediaType,
|
|
1658
|
+
citations,
|
|
1507
1659
|
result: {
|
|
1508
1660
|
resourceId: "",
|
|
1509
1661
|
resourceName: generated.title ?? title
|