@semiont/jobs 0.5.7 → 0.5.9

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.
@@ -1,4 +1,4 @@
1
- import { createTomlConfigLoader, softwareToAgent, baseUrl, getPrimaryMediaType, textExtractionOf, reconcileSelector, didToAgent, getLocaleEnglishName } from '@semiont/core';
1
+ import { createTomlConfigLoader, softwareToAgent, baseUrl, getPrimaryMediaType, textExtractionOf, reconcileSelector, didToAgent, getLocaleEnglishName, isArray, isObject, isString } 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';
@@ -18,7 +18,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
18
18
  var __getProtoOf = Object.getPrototypeOf;
19
19
  var __hasOwnProp = Object.prototype.hasOwnProperty;
20
20
  var __commonJS = (cb, mod) => function __require() {
21
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
21
+ try {
22
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
23
+ } catch (e) {
24
+ throw mod = 0, e;
25
+ }
22
26
  };
23
27
  var __copyProps = (to, from, except, desc) => {
24
28
  if (from && typeof from === "object" || typeof from === "function") {
@@ -3761,9 +3765,9 @@ var require_mapOneOrManyArgs = __commonJS({
3761
3765
  Object.defineProperty(exports, "__esModule", { value: true });
3762
3766
  exports.mapOneOrManyArgs = void 0;
3763
3767
  var map_1 = require_map();
3764
- var isArray = Array.isArray;
3768
+ var isArray2 = Array.isArray;
3765
3769
  function callOrApply(fn, args) {
3766
- return isArray(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3770
+ return isArray2(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3767
3771
  }
3768
3772
  function mapOneOrManyArgs(fn) {
3769
3773
  return map_1.map(function(args) {
@@ -3908,14 +3912,14 @@ var require_argsArgArrayOrObject = __commonJS({
3908
3912
  "../../node_modules/rxjs/dist/cjs/internal/util/argsArgArrayOrObject.js"(exports) {
3909
3913
  Object.defineProperty(exports, "__esModule", { value: true });
3910
3914
  exports.argsArgArrayOrObject = void 0;
3911
- var isArray = Array.isArray;
3915
+ var isArray2 = Array.isArray;
3912
3916
  var getPrototypeOf = Object.getPrototypeOf;
3913
3917
  var objectProto = Object.prototype;
3914
3918
  var getKeys = Object.keys;
3915
3919
  function argsArgArrayOrObject(args) {
3916
3920
  if (args.length === 1) {
3917
3921
  var first_1 = args[0];
3918
- if (isArray(first_1)) {
3922
+ if (isArray2(first_1)) {
3919
3923
  return { args: first_1, keys: null };
3920
3924
  }
3921
3925
  if (isPOJO(first_1)) {
@@ -4663,9 +4667,9 @@ var require_argsOrArgArray = __commonJS({
4663
4667
  "../../node_modules/rxjs/dist/cjs/internal/util/argsOrArgArray.js"(exports) {
4664
4668
  Object.defineProperty(exports, "__esModule", { value: true });
4665
4669
  exports.argsOrArgArray = void 0;
4666
- var isArray = Array.isArray;
4670
+ var isArray2 = Array.isArray;
4667
4671
  function argsOrArgArray(args) {
4668
- return args.length === 1 && isArray(args[0]) ? args[0] : args;
4672
+ return args.length === 1 && isArray2(args[0]) ? args[0] : args;
4669
4673
  }
4670
4674
  exports.argsOrArgArray = argsOrArgArray;
4671
4675
  }
@@ -9583,197 +9587,144 @@ Example format:
9583
9587
  return prompt;
9584
9588
  }
9585
9589
  };
9586
- function extractObjectsFromArray(response) {
9587
- let cleaned = response.trim();
9588
- if (cleaned.startsWith("```")) {
9589
- cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
9590
- }
9590
+ function parseJsonArray(response, motivation) {
9591
+ let parsed;
9591
9592
  try {
9592
- const parsed = JSON.parse(cleaned);
9593
- return Array.isArray(parsed) ? parsed : [];
9594
- } catch {
9595
- }
9596
- const start = cleaned.indexOf("[");
9597
- if (start === -1) return [];
9598
- const endBracket = cleaned.lastIndexOf("]");
9599
- const end = endBracket > start ? endBracket : cleaned.length;
9600
- const inner = cleaned.slice(start + 1, end);
9601
- const objects = [];
9602
- let depth = 0;
9603
- let objStart = -1;
9604
- let inString = false;
9605
- let escape = false;
9606
- for (let i = 0; i < inner.length; i++) {
9607
- const ch = inner[i];
9608
- if (escape) {
9609
- escape = false;
9610
- continue;
9611
- }
9612
- if (ch === "\\") {
9613
- escape = true;
9614
- continue;
9615
- }
9616
- if (ch === '"') {
9617
- inString = !inString;
9618
- continue;
9619
- }
9620
- if (inString) continue;
9621
- if (ch === "{") {
9622
- if (depth === 0) objStart = i;
9623
- depth++;
9624
- } else if (ch === "}") {
9625
- depth--;
9626
- if (depth === 0 && objStart !== -1) {
9627
- try {
9628
- objects.push(JSON.parse(inner.slice(objStart, i + 1)));
9629
- } catch {
9630
- }
9631
- objStart = -1;
9632
- }
9633
- }
9593
+ parsed = JSON.parse(response.trim());
9594
+ } catch (error) {
9595
+ console.error(`[MotivationParsers] Failed to parse AI ${motivation} response:`, error);
9596
+ console.error("Raw response:", response);
9597
+ throw error instanceof Error ? error : new Error(String(error));
9598
+ }
9599
+ if (!Array.isArray(parsed)) {
9600
+ console.error(`[MotivationParsers] Expected a JSON array for ${motivation} detection, got ${typeof parsed}:`, response);
9601
+ throw new Error(`Expected a JSON array for ${motivation} detection, got ${typeof parsed}`);
9634
9602
  }
9635
- return objects;
9603
+ return parsed;
9636
9604
  }
9637
9605
  var MotivationParsers = class {
9638
9606
  /**
9639
9607
  * Parse and validate AI response for comment detection
9640
9608
  *
9641
- * @param response - Raw AI response string (may include markdown code fences)
9609
+ * @param response - Raw AI response text (a JSON array)
9642
9610
  * @param content - Original content to validate offsets against
9643
9611
  * @returns Array of validated comment matches
9612
+ * @throws if the response is not a parseable JSON array
9644
9613
  */
9645
9614
  static parseComments(response, content) {
9646
- try {
9647
- const parsed = extractObjectsFromArray(response);
9648
- const valid = parsed.filter(
9649
- (c) => !!c && typeof c === "object" && typeof c.exact === "string" && typeof c.comment === "string" && c.comment.trim().length > 0
9650
- );
9651
- console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
9652
- const validatedComments = [];
9653
- for (const comment of valid) {
9654
- const reconciled = reconcileSelector(content, {
9655
- exact: comment.exact,
9656
- ...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
9657
- ...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
9658
- });
9659
- if (!reconciled) {
9660
- console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
9661
- continue;
9662
- }
9663
- logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
9664
- validatedComments.push({
9665
- comment: comment.comment,
9666
- exact: reconciled.exact,
9667
- start: reconciled.start,
9668
- end: reconciled.end,
9669
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9670
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9671
- });
9615
+ const parsed = parseJsonArray(response, "comment");
9616
+ const valid = parsed.filter(
9617
+ (c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
9618
+ );
9619
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
9620
+ const validatedComments = [];
9621
+ for (const comment of valid) {
9622
+ const reconciled = reconcileSelector(content, {
9623
+ exact: comment.exact,
9624
+ ...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
9625
+ ...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
9626
+ });
9627
+ if (!reconciled) {
9628
+ console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
9629
+ continue;
9672
9630
  }
9673
- return validatedComments;
9674
- } catch (error) {
9675
- console.error("[MotivationParsers] Failed to parse AI comment response:", error);
9676
- return [];
9631
+ logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
9632
+ validatedComments.push({
9633
+ comment: comment.comment,
9634
+ exact: reconciled.exact,
9635
+ start: reconciled.start,
9636
+ end: reconciled.end,
9637
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9638
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9639
+ });
9677
9640
  }
9641
+ return validatedComments;
9678
9642
  }
9679
9643
  /**
9680
9644
  * Parse and validate AI response for highlight detection
9681
9645
  *
9682
- * @param response - Raw AI response string (may include markdown code fences)
9646
+ * @param response - Raw AI response text (a JSON array)
9683
9647
  * @param content - Original content to validate offsets against
9684
9648
  * @returns Array of validated highlight matches
9649
+ * @throws if the response is not a parseable JSON array
9685
9650
  */
9686
9651
  static parseHighlights(response, content) {
9687
- try {
9688
- const parsed = extractObjectsFromArray(response);
9689
- const highlights = parsed.filter(
9690
- (h) => !!h && typeof h === "object" && typeof h.exact === "string"
9691
- );
9692
- const validatedHighlights = [];
9693
- for (const highlight of highlights) {
9694
- const reconciled = reconcileSelector(content, {
9695
- exact: highlight.exact,
9696
- ...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
9697
- ...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
9698
- });
9699
- if (!reconciled) {
9700
- console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
9701
- continue;
9702
- }
9703
- logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
9704
- validatedHighlights.push({
9705
- exact: reconciled.exact,
9706
- start: reconciled.start,
9707
- end: reconciled.end,
9708
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9709
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9710
- });
9652
+ const parsed = parseJsonArray(response, "highlight");
9653
+ const highlights = parsed.filter(
9654
+ (h) => isObject(h) && isString(h.exact)
9655
+ );
9656
+ const validatedHighlights = [];
9657
+ for (const highlight of highlights) {
9658
+ const reconciled = reconcileSelector(content, {
9659
+ exact: highlight.exact,
9660
+ ...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
9661
+ ...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
9662
+ });
9663
+ if (!reconciled) {
9664
+ console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
9665
+ continue;
9711
9666
  }
9712
- return validatedHighlights;
9713
- } catch (error) {
9714
- console.error("[MotivationParsers] Failed to parse AI highlight response:", error);
9715
- console.error("Raw response:", response);
9716
- return [];
9667
+ logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
9668
+ validatedHighlights.push({
9669
+ exact: reconciled.exact,
9670
+ start: reconciled.start,
9671
+ end: reconciled.end,
9672
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9673
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9674
+ });
9717
9675
  }
9676
+ return validatedHighlights;
9718
9677
  }
9719
9678
  /**
9720
9679
  * Parse and validate AI response for assessment detection
9721
9680
  *
9722
- * @param response - Raw AI response string (may include markdown code fences)
9681
+ * @param response - Raw AI response text (a JSON array)
9723
9682
  * @param content - Original content to validate offsets against
9724
9683
  * @returns Array of validated assessment matches
9684
+ * @throws if the response is not a parseable JSON array
9725
9685
  */
9726
9686
  static parseAssessments(response, content) {
9727
- try {
9728
- const parsed = extractObjectsFromArray(response);
9729
- const assessments = parsed.filter(
9730
- (a) => !!a && typeof a === "object" && typeof a.exact === "string" && typeof a.assessment === "string"
9731
- );
9732
- const validatedAssessments = [];
9733
- for (const assessment of assessments) {
9734
- const reconciled = reconcileSelector(content, {
9735
- exact: assessment.exact,
9736
- ...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
9737
- ...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
9738
- });
9739
- if (!reconciled) {
9740
- console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
9741
- continue;
9742
- }
9743
- logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
9744
- validatedAssessments.push({
9745
- assessment: assessment.assessment,
9746
- exact: reconciled.exact,
9747
- start: reconciled.start,
9748
- end: reconciled.end,
9749
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9750
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9751
- });
9687
+ const parsed = parseJsonArray(response, "assessment");
9688
+ const assessments = parsed.filter(
9689
+ (a) => isObject(a) && isString(a.exact) && isString(a.assessment)
9690
+ );
9691
+ const validatedAssessments = [];
9692
+ for (const assessment of assessments) {
9693
+ const reconciled = reconcileSelector(content, {
9694
+ exact: assessment.exact,
9695
+ ...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
9696
+ ...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
9697
+ });
9698
+ if (!reconciled) {
9699
+ console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
9700
+ continue;
9752
9701
  }
9753
- return validatedAssessments;
9754
- } catch (error) {
9755
- console.error("[MotivationParsers] Failed to parse AI assessment response:", error);
9756
- console.error("Raw response:", response);
9757
- return [];
9702
+ logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
9703
+ validatedAssessments.push({
9704
+ assessment: assessment.assessment,
9705
+ exact: reconciled.exact,
9706
+ start: reconciled.start,
9707
+ end: reconciled.end,
9708
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9709
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9710
+ });
9758
9711
  }
9712
+ return validatedAssessments;
9759
9713
  }
9760
9714
  /**
9761
9715
  * Parse the LLM's tag response into raw, pre-reconciliation tag inputs.
9762
9716
  * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
9763
9717
  * by anchoring `exact` against the source content.
9718
+ *
9719
+ * @throws if the response is not a parseable JSON array
9764
9720
  */
9765
9721
  static parseTags(response) {
9766
- try {
9767
- const parsed = extractObjectsFromArray(response);
9768
- const valid = parsed.filter(
9769
- (t) => !!t && typeof t === "object" && typeof t.exact === "string" && t.exact.trim().length > 0
9770
- );
9771
- console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
9772
- return valid;
9773
- } catch (error) {
9774
- console.error("[MotivationParsers] Failed to parse AI tag response:", error);
9775
- return [];
9776
- }
9722
+ const parsed = parseJsonArray(response, "tag");
9723
+ const valid = parsed.filter(
9724
+ (t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
9725
+ );
9726
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
9727
+ return valid;
9777
9728
  }
9778
9729
  /**
9779
9730
  * Anchor raw tag inputs against source content and add category.
@@ -9810,6 +9761,11 @@ function logAnchorMethod(motivation, exact, anchorMethod) {
9810
9761
  }
9811
9762
 
9812
9763
  // src/workers/annotation-detection.ts
9764
+ function assertNotTruncated(response, motivation) {
9765
+ if (response.stopReason === "max_tokens") {
9766
+ throw new Error(`${motivation} detection response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than under-reporting annotations.`);
9767
+ }
9768
+ }
9813
9769
  var AnnotationDetection = class {
9814
9770
  /**
9815
9771
  * Detect comments in content.
@@ -9821,8 +9777,9 @@ var AnnotationDetection = class {
9821
9777
  */
9822
9778
  static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
9823
9779
  const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density, language, sourceLanguage);
9824
- const response = await client.generateText(prompt, 3e3, 0.4, { format: "json" });
9825
- return MotivationParsers.parseComments(response, content);
9780
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.4, { format: "json" });
9781
+ assertNotTruncated(response, "comment");
9782
+ return MotivationParsers.parseComments(response.text, content);
9826
9783
  }
9827
9784
  /**
9828
9785
  * Detect highlights in content.
@@ -9833,8 +9790,9 @@ var AnnotationDetection = class {
9833
9790
  */
9834
9791
  static async detectHighlights(content, client, instructions, density, sourceLanguage) {
9835
9792
  const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density, sourceLanguage);
9836
- const response = await client.generateText(prompt, 2e3, 0.3, { format: "json" });
9837
- return MotivationParsers.parseHighlights(response, content);
9793
+ const response = await client.generateTextWithMetadata(prompt, 2e3, 0.3, { format: "json" });
9794
+ assertNotTruncated(response, "highlight");
9795
+ return MotivationParsers.parseHighlights(response.text, content);
9838
9796
  }
9839
9797
  /**
9840
9798
  * Detect assessments in content.
@@ -9845,8 +9803,9 @@ var AnnotationDetection = class {
9845
9803
  */
9846
9804
  static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
9847
9805
  const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density, language, sourceLanguage);
9848
- const response = await client.generateText(prompt, 3e3, 0.3, { format: "json" });
9849
- return MotivationParsers.parseAssessments(response, content);
9806
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.3, { format: "json" });
9807
+ assertNotTruncated(response, "assessment");
9808
+ return MotivationParsers.parseAssessments(response.text, content);
9850
9809
  }
9851
9810
  /**
9852
9811
  * Detect tags in content for a specific category.
@@ -9875,8 +9834,9 @@ var AnnotationDetection = class {
9875
9834
  categoryInfo.examples,
9876
9835
  sourceLanguage
9877
9836
  );
9878
- const response = await client.generateText(prompt, 4e3, 0.2, { format: "json" });
9879
- const parsedTags = MotivationParsers.parseTags(response);
9837
+ const response = await client.generateTextWithMetadata(prompt, 4e3, 0.2, { format: "json" });
9838
+ assertNotTruncated(response, "tag");
9839
+ const parsedTags = MotivationParsers.parseTags(response.text);
9880
9840
  return MotivationParsers.validateTagOffsets(parsedTags, content, category);
9881
9841
  }
9882
9842
  };
@@ -9945,36 +9905,42 @@ Example output:
9945
9905
  { format: "json" }
9946
9906
  );
9947
9907
  logger2.debug("Got entity extraction response", { responseLength: response.text.length });
9908
+ if (response.stopReason === "max_tokens") {
9909
+ const errorMsg = "Entity extraction response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than dropping annotations.";
9910
+ logger2.error(errorMsg, { responseLength: response.text.length });
9911
+ throw new Error(errorMsg);
9912
+ }
9913
+ let entities;
9948
9914
  try {
9949
- let jsonStr = response.text.trim();
9950
- if (jsonStr.startsWith("```")) {
9951
- jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
9952
- }
9953
- const entities = JSON.parse(jsonStr);
9954
- logger2.debug("Parsed entities from AI response", { count: entities.length });
9955
- if (response.stopReason === "max_tokens") {
9956
- const errorMsg = `AI response truncated: Found ${entities.length} entities but response hit max_tokens limit. Increase max_tokens or reduce resource size.`;
9957
- logger2.error(errorMsg);
9958
- throw new Error(errorMsg);
9959
- }
9960
- return entities.filter((e) => {
9961
- const ok = e && typeof e === "object" && typeof e.exact === "string" && typeof e.entityType === "string";
9962
- if (!ok) {
9963
- logger2.debug("Dropped malformed LLM entity", { entity: e });
9964
- }
9965
- return ok;
9966
- }).map((entity) => ({
9967
- exact: entity.exact,
9968
- entityType: entity.entityType,
9969
- ...typeof entity.prefix === "string" ? { prefix: entity.prefix } : {},
9970
- ...typeof entity.suffix === "string" ? { suffix: entity.suffix } : {}
9971
- }));
9915
+ entities = JSON.parse(response.text.trim());
9972
9916
  } catch (error) {
9973
9917
  logger2.error("Failed to parse entity extraction response", {
9974
- error: error instanceof Error ? error.message : String(error)
9918
+ error: error instanceof Error ? error.message : String(error),
9919
+ response: response.text.slice(0, 500)
9920
+ });
9921
+ throw new Error("Failed to parse entity extraction response", {
9922
+ cause: error instanceof Error ? error : new Error(String(error))
9975
9923
  });
9976
- return [];
9977
9924
  }
9925
+ if (!isArray(entities)) {
9926
+ logger2.error("Failed to parse entity extraction response: expected a JSON array", {
9927
+ response: response.text.slice(0, 500)
9928
+ });
9929
+ throw new Error("Failed to parse entity extraction response: expected a JSON array");
9930
+ }
9931
+ logger2.debug("Parsed entities from AI response", { count: entities.length });
9932
+ return entities.filter((e) => {
9933
+ const ok = isObject(e) && isString(e.exact) && isString(e.entityType);
9934
+ if (!ok) {
9935
+ logger2.debug("Dropped malformed LLM entity", { entity: e });
9936
+ }
9937
+ return ok;
9938
+ }).map((entity) => ({
9939
+ exact: entity.exact,
9940
+ entityType: entity.entityType,
9941
+ ...isString(entity.prefix) ? { prefix: entity.prefix } : {},
9942
+ ...isString(entity.suffix) ? { suffix: entity.suffix } : {}
9943
+ }));
9978
9944
  }
9979
9945
  function getLanguageName(locale) {
9980
9946
  return getLocaleEnglishName(locale) || locale;
@@ -10056,10 +10022,19 @@ Knowledge graph context:
10056
10022
  ${parts.join("\n")}`;
10057
10023
  }
10058
10024
  }
10025
+ let semanticContextSection = "";
10026
+ const similar = context?.semanticContext?.similar ?? [];
10027
+ if (similar.length > 0) {
10028
+ const lines = [...similar].sort((a, b) => b.score - a.score).slice(0, 3).map((m) => `- (${m.score.toFixed(2)}) ${m.text.slice(0, 240)}`);
10029
+ semanticContextSection = `
10030
+
10031
+ Related passages from the knowledge base:
10032
+ ${lines.join("\n")}`;
10033
+ }
10059
10034
  const structureGuidance = finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
10060
10035
  const prompt = `Generate a concise, informative resource about "${topic}".
10061
10036
  ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
10062
- ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${sourceLanguageInstruction}${languageInstruction}
10037
+ ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
10063
10038
 
10064
10039
  Requirements:
10065
10040
  - Start with a clear heading (# Title)