@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.
- package/dist/index.d.ts +10 -0
- package/dist/index.js +156 -185
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +168 -193
- package/dist/worker-main.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -503,6 +503,16 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
|
|
|
503
503
|
result: GenerationResult;
|
|
504
504
|
}>;
|
|
505
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Response parsers for annotation detection motivations
|
|
508
|
+
*
|
|
509
|
+
* Provides static methods to parse and validate AI responses for each motivation type.
|
|
510
|
+
* Includes offset validation and correction logic.
|
|
511
|
+
* Extracted from worker implementations to centralize parsing logic.
|
|
512
|
+
*
|
|
513
|
+
* NOTE: These are static utility methods without logger access.
|
|
514
|
+
* Console statements kept for debugging - consider adding logger parameter in future.
|
|
515
|
+
*/
|
|
506
516
|
/**
|
|
507
517
|
* Represents a detected comment with validated position
|
|
508
518
|
*/
|
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, getLocaleEnglishName, didToAgent } from '@semiont/core';
|
|
3
|
+
import { jobId, 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
|
|
@@ -710,197 +710,144 @@ Example format:
|
|
|
710
710
|
return prompt;
|
|
711
711
|
}
|
|
712
712
|
};
|
|
713
|
-
function
|
|
714
|
-
let
|
|
715
|
-
if (cleaned.startsWith("```")) {
|
|
716
|
-
cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
717
|
-
}
|
|
713
|
+
function parseJsonArray(response, motivation) {
|
|
714
|
+
let parsed;
|
|
718
715
|
try {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
716
|
+
parsed = JSON.parse(response.trim());
|
|
717
|
+
} catch (error) {
|
|
718
|
+
console.error(`[MotivationParsers] Failed to parse AI ${motivation} response:`, error);
|
|
719
|
+
console.error("Raw response:", response);
|
|
720
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
722
721
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const end = endBracket > start ? endBracket : cleaned.length;
|
|
727
|
-
const inner = cleaned.slice(start + 1, end);
|
|
728
|
-
const objects = [];
|
|
729
|
-
let depth = 0;
|
|
730
|
-
let objStart = -1;
|
|
731
|
-
let inString = false;
|
|
732
|
-
let escape = false;
|
|
733
|
-
for (let i = 0; i < inner.length; i++) {
|
|
734
|
-
const ch = inner[i];
|
|
735
|
-
if (escape) {
|
|
736
|
-
escape = false;
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
if (ch === "\\") {
|
|
740
|
-
escape = true;
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
if (ch === '"') {
|
|
744
|
-
inString = !inString;
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
if (inString) continue;
|
|
748
|
-
if (ch === "{") {
|
|
749
|
-
if (depth === 0) objStart = i;
|
|
750
|
-
depth++;
|
|
751
|
-
} else if (ch === "}") {
|
|
752
|
-
depth--;
|
|
753
|
-
if (depth === 0 && objStart !== -1) {
|
|
754
|
-
try {
|
|
755
|
-
objects.push(JSON.parse(inner.slice(objStart, i + 1)));
|
|
756
|
-
} catch {
|
|
757
|
-
}
|
|
758
|
-
objStart = -1;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
722
|
+
if (!Array.isArray(parsed)) {
|
|
723
|
+
console.error(`[MotivationParsers] Expected a JSON array for ${motivation} detection, got ${typeof parsed}:`, response);
|
|
724
|
+
throw new Error(`Expected a JSON array for ${motivation} detection, got ${typeof parsed}`);
|
|
761
725
|
}
|
|
762
|
-
return
|
|
726
|
+
return parsed;
|
|
763
727
|
}
|
|
764
728
|
var MotivationParsers = class {
|
|
765
729
|
/**
|
|
766
730
|
* Parse and validate AI response for comment detection
|
|
767
731
|
*
|
|
768
|
-
* @param response - Raw AI response
|
|
732
|
+
* @param response - Raw AI response text (a JSON array)
|
|
769
733
|
* @param content - Original content to validate offsets against
|
|
770
734
|
* @returns Array of validated comment matches
|
|
735
|
+
* @throws if the response is not a parseable JSON array
|
|
771
736
|
*/
|
|
772
737
|
static parseComments(response, content) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
|
|
791
|
-
validatedComments.push({
|
|
792
|
-
comment: comment.comment,
|
|
793
|
-
exact: reconciled.exact,
|
|
794
|
-
start: reconciled.start,
|
|
795
|
-
end: reconciled.end,
|
|
796
|
-
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
797
|
-
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
798
|
-
});
|
|
738
|
+
const parsed = parseJsonArray(response, "comment");
|
|
739
|
+
const valid = parsed.filter(
|
|
740
|
+
(c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
|
|
741
|
+
);
|
|
742
|
+
console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
|
|
743
|
+
const validatedComments = [];
|
|
744
|
+
for (const comment of valid) {
|
|
745
|
+
const reconciled = reconcileSelector(content, {
|
|
746
|
+
exact: comment.exact,
|
|
747
|
+
...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
|
|
748
|
+
...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
|
|
749
|
+
});
|
|
750
|
+
if (!reconciled) {
|
|
751
|
+
console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
|
|
752
|
+
continue;
|
|
799
753
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
754
|
+
logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
|
|
755
|
+
validatedComments.push({
|
|
756
|
+
comment: comment.comment,
|
|
757
|
+
exact: reconciled.exact,
|
|
758
|
+
start: reconciled.start,
|
|
759
|
+
end: reconciled.end,
|
|
760
|
+
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
761
|
+
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
762
|
+
});
|
|
804
763
|
}
|
|
764
|
+
return validatedComments;
|
|
805
765
|
}
|
|
806
766
|
/**
|
|
807
767
|
* Parse and validate AI response for highlight detection
|
|
808
768
|
*
|
|
809
|
-
* @param response - Raw AI response
|
|
769
|
+
* @param response - Raw AI response text (a JSON array)
|
|
810
770
|
* @param content - Original content to validate offsets against
|
|
811
771
|
* @returns Array of validated highlight matches
|
|
772
|
+
* @throws if the response is not a parseable JSON array
|
|
812
773
|
*/
|
|
813
774
|
static parseHighlights(response, content) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
|
|
831
|
-
validatedHighlights.push({
|
|
832
|
-
exact: reconciled.exact,
|
|
833
|
-
start: reconciled.start,
|
|
834
|
-
end: reconciled.end,
|
|
835
|
-
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
836
|
-
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
837
|
-
});
|
|
775
|
+
const parsed = parseJsonArray(response, "highlight");
|
|
776
|
+
const highlights = parsed.filter(
|
|
777
|
+
(h) => isObject(h) && isString(h.exact)
|
|
778
|
+
);
|
|
779
|
+
const validatedHighlights = [];
|
|
780
|
+
for (const highlight of highlights) {
|
|
781
|
+
const reconciled = reconcileSelector(content, {
|
|
782
|
+
exact: highlight.exact,
|
|
783
|
+
...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
|
|
784
|
+
...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
|
|
785
|
+
});
|
|
786
|
+
if (!reconciled) {
|
|
787
|
+
console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
|
|
788
|
+
continue;
|
|
838
789
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
790
|
+
logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
|
|
791
|
+
validatedHighlights.push({
|
|
792
|
+
exact: reconciled.exact,
|
|
793
|
+
start: reconciled.start,
|
|
794
|
+
end: reconciled.end,
|
|
795
|
+
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
796
|
+
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
797
|
+
});
|
|
844
798
|
}
|
|
799
|
+
return validatedHighlights;
|
|
845
800
|
}
|
|
846
801
|
/**
|
|
847
802
|
* Parse and validate AI response for assessment detection
|
|
848
803
|
*
|
|
849
|
-
* @param response - Raw AI response
|
|
804
|
+
* @param response - Raw AI response text (a JSON array)
|
|
850
805
|
* @param content - Original content to validate offsets against
|
|
851
806
|
* @returns Array of validated assessment matches
|
|
807
|
+
* @throws if the response is not a parseable JSON array
|
|
852
808
|
*/
|
|
853
809
|
static parseAssessments(response, content) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
|
|
871
|
-
validatedAssessments.push({
|
|
872
|
-
assessment: assessment.assessment,
|
|
873
|
-
exact: reconciled.exact,
|
|
874
|
-
start: reconciled.start,
|
|
875
|
-
end: reconciled.end,
|
|
876
|
-
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
877
|
-
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
878
|
-
});
|
|
810
|
+
const parsed = parseJsonArray(response, "assessment");
|
|
811
|
+
const assessments = parsed.filter(
|
|
812
|
+
(a) => isObject(a) && isString(a.exact) && isString(a.assessment)
|
|
813
|
+
);
|
|
814
|
+
const validatedAssessments = [];
|
|
815
|
+
for (const assessment of assessments) {
|
|
816
|
+
const reconciled = reconcileSelector(content, {
|
|
817
|
+
exact: assessment.exact,
|
|
818
|
+
...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
|
|
819
|
+
...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
|
|
820
|
+
});
|
|
821
|
+
if (!reconciled) {
|
|
822
|
+
console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
|
|
823
|
+
continue;
|
|
879
824
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
825
|
+
logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
|
|
826
|
+
validatedAssessments.push({
|
|
827
|
+
assessment: assessment.assessment,
|
|
828
|
+
exact: reconciled.exact,
|
|
829
|
+
start: reconciled.start,
|
|
830
|
+
end: reconciled.end,
|
|
831
|
+
...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
|
|
832
|
+
...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
|
|
833
|
+
});
|
|
885
834
|
}
|
|
835
|
+
return validatedAssessments;
|
|
886
836
|
}
|
|
887
837
|
/**
|
|
888
838
|
* Parse the LLM's tag response into raw, pre-reconciliation tag inputs.
|
|
889
839
|
* Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
|
|
890
840
|
* by anchoring `exact` against the source content.
|
|
841
|
+
*
|
|
842
|
+
* @throws if the response is not a parseable JSON array
|
|
891
843
|
*/
|
|
892
844
|
static parseTags(response) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
return valid;
|
|
900
|
-
} catch (error) {
|
|
901
|
-
console.error("[MotivationParsers] Failed to parse AI tag response:", error);
|
|
902
|
-
return [];
|
|
903
|
-
}
|
|
845
|
+
const parsed = parseJsonArray(response, "tag");
|
|
846
|
+
const valid = parsed.filter(
|
|
847
|
+
(t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
|
|
848
|
+
);
|
|
849
|
+
console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
|
|
850
|
+
return valid;
|
|
904
851
|
}
|
|
905
852
|
/**
|
|
906
853
|
* Anchor raw tag inputs against source content and add category.
|
|
@@ -937,6 +884,11 @@ function logAnchorMethod(motivation, exact, anchorMethod) {
|
|
|
937
884
|
}
|
|
938
885
|
|
|
939
886
|
// src/workers/annotation-detection.ts
|
|
887
|
+
function assertNotTruncated(response, motivation) {
|
|
888
|
+
if (response.stopReason === "max_tokens") {
|
|
889
|
+
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.`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
940
892
|
var AnnotationDetection = class {
|
|
941
893
|
/**
|
|
942
894
|
* Detect comments in content.
|
|
@@ -948,8 +900,9 @@ var AnnotationDetection = class {
|
|
|
948
900
|
*/
|
|
949
901
|
static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
|
|
950
902
|
const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density, language, sourceLanguage);
|
|
951
|
-
const response = await client.
|
|
952
|
-
|
|
903
|
+
const response = await client.generateTextWithMetadata(prompt, 3e3, 0.4, { format: "json" });
|
|
904
|
+
assertNotTruncated(response, "comment");
|
|
905
|
+
return MotivationParsers.parseComments(response.text, content);
|
|
953
906
|
}
|
|
954
907
|
/**
|
|
955
908
|
* Detect highlights in content.
|
|
@@ -960,8 +913,9 @@ var AnnotationDetection = class {
|
|
|
960
913
|
*/
|
|
961
914
|
static async detectHighlights(content, client, instructions, density, sourceLanguage) {
|
|
962
915
|
const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density, sourceLanguage);
|
|
963
|
-
const response = await client.
|
|
964
|
-
|
|
916
|
+
const response = await client.generateTextWithMetadata(prompt, 2e3, 0.3, { format: "json" });
|
|
917
|
+
assertNotTruncated(response, "highlight");
|
|
918
|
+
return MotivationParsers.parseHighlights(response.text, content);
|
|
965
919
|
}
|
|
966
920
|
/**
|
|
967
921
|
* Detect assessments in content.
|
|
@@ -972,8 +926,9 @@ var AnnotationDetection = class {
|
|
|
972
926
|
*/
|
|
973
927
|
static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
|
|
974
928
|
const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density, language, sourceLanguage);
|
|
975
|
-
const response = await client.
|
|
976
|
-
|
|
929
|
+
const response = await client.generateTextWithMetadata(prompt, 3e3, 0.3, { format: "json" });
|
|
930
|
+
assertNotTruncated(response, "assessment");
|
|
931
|
+
return MotivationParsers.parseAssessments(response.text, content);
|
|
977
932
|
}
|
|
978
933
|
/**
|
|
979
934
|
* Detect tags in content for a specific category.
|
|
@@ -1002,8 +957,9 @@ var AnnotationDetection = class {
|
|
|
1002
957
|
categoryInfo.examples,
|
|
1003
958
|
sourceLanguage
|
|
1004
959
|
);
|
|
1005
|
-
const response = await client.
|
|
1006
|
-
|
|
960
|
+
const response = await client.generateTextWithMetadata(prompt, 4e3, 0.2, { format: "json" });
|
|
961
|
+
assertNotTruncated(response, "tag");
|
|
962
|
+
const parsedTags = MotivationParsers.parseTags(response.text);
|
|
1007
963
|
return MotivationParsers.validateTagOffsets(parsedTags, content, category);
|
|
1008
964
|
}
|
|
1009
965
|
};
|
|
@@ -1072,36 +1028,42 @@ Example output:
|
|
|
1072
1028
|
{ format: "json" }
|
|
1073
1029
|
);
|
|
1074
1030
|
logger.debug("Got entity extraction response", { responseLength: response.text.length });
|
|
1031
|
+
if (response.stopReason === "max_tokens") {
|
|
1032
|
+
const errorMsg = "Entity extraction response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than dropping annotations.";
|
|
1033
|
+
logger.error(errorMsg, { responseLength: response.text.length });
|
|
1034
|
+
throw new Error(errorMsg);
|
|
1035
|
+
}
|
|
1036
|
+
let entities;
|
|
1075
1037
|
try {
|
|
1076
|
-
|
|
1077
|
-
if (jsonStr.startsWith("```")) {
|
|
1078
|
-
jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
1079
|
-
}
|
|
1080
|
-
const entities = JSON.parse(jsonStr);
|
|
1081
|
-
logger.debug("Parsed entities from AI response", { count: entities.length });
|
|
1082
|
-
if (response.stopReason === "max_tokens") {
|
|
1083
|
-
const errorMsg = `AI response truncated: Found ${entities.length} entities but response hit max_tokens limit. Increase max_tokens or reduce resource size.`;
|
|
1084
|
-
logger.error(errorMsg);
|
|
1085
|
-
throw new Error(errorMsg);
|
|
1086
|
-
}
|
|
1087
|
-
return entities.filter((e) => {
|
|
1088
|
-
const ok = e && typeof e === "object" && typeof e.exact === "string" && typeof e.entityType === "string";
|
|
1089
|
-
if (!ok) {
|
|
1090
|
-
logger.debug("Dropped malformed LLM entity", { entity: e });
|
|
1091
|
-
}
|
|
1092
|
-
return ok;
|
|
1093
|
-
}).map((entity) => ({
|
|
1094
|
-
exact: entity.exact,
|
|
1095
|
-
entityType: entity.entityType,
|
|
1096
|
-
...typeof entity.prefix === "string" ? { prefix: entity.prefix } : {},
|
|
1097
|
-
...typeof entity.suffix === "string" ? { suffix: entity.suffix } : {}
|
|
1098
|
-
}));
|
|
1038
|
+
entities = JSON.parse(response.text.trim());
|
|
1099
1039
|
} catch (error) {
|
|
1100
1040
|
logger.error("Failed to parse entity extraction response", {
|
|
1101
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1041
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1042
|
+
response: response.text.slice(0, 500)
|
|
1043
|
+
});
|
|
1044
|
+
throw new Error("Failed to parse entity extraction response", {
|
|
1045
|
+
cause: error instanceof Error ? error : new Error(String(error))
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
if (!isArray(entities)) {
|
|
1049
|
+
logger.error("Failed to parse entity extraction response: expected a JSON array", {
|
|
1050
|
+
response: response.text.slice(0, 500)
|
|
1102
1051
|
});
|
|
1103
|
-
|
|
1052
|
+
throw new Error("Failed to parse entity extraction response: expected a JSON array");
|
|
1104
1053
|
}
|
|
1054
|
+
logger.debug("Parsed entities from AI response", { count: entities.length });
|
|
1055
|
+
return entities.filter((e) => {
|
|
1056
|
+
const ok = isObject(e) && isString(e.exact) && isString(e.entityType);
|
|
1057
|
+
if (!ok) {
|
|
1058
|
+
logger.debug("Dropped malformed LLM entity", { entity: e });
|
|
1059
|
+
}
|
|
1060
|
+
return ok;
|
|
1061
|
+
}).map((entity) => ({
|
|
1062
|
+
exact: entity.exact,
|
|
1063
|
+
entityType: entity.entityType,
|
|
1064
|
+
...isString(entity.prefix) ? { prefix: entity.prefix } : {},
|
|
1065
|
+
...isString(entity.suffix) ? { suffix: entity.suffix } : {}
|
|
1066
|
+
}));
|
|
1105
1067
|
}
|
|
1106
1068
|
function getLanguageName(locale) {
|
|
1107
1069
|
return getLocaleEnglishName(locale) || locale;
|
|
@@ -1183,10 +1145,19 @@ Knowledge graph context:
|
|
|
1183
1145
|
${parts.join("\n")}`;
|
|
1184
1146
|
}
|
|
1185
1147
|
}
|
|
1148
|
+
let semanticContextSection = "";
|
|
1149
|
+
const similar = context?.semanticContext?.similar ?? [];
|
|
1150
|
+
if (similar.length > 0) {
|
|
1151
|
+
const lines = [...similar].sort((a, b) => b.score - a.score).slice(0, 3).map((m) => `- (${m.score.toFixed(2)}) ${m.text.slice(0, 240)}`);
|
|
1152
|
+
semanticContextSection = `
|
|
1153
|
+
|
|
1154
|
+
Related passages from the knowledge base:
|
|
1155
|
+
${lines.join("\n")}`;
|
|
1156
|
+
}
|
|
1186
1157
|
const structureGuidance = finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
|
|
1187
1158
|
const prompt = `Generate a concise, informative resource about "${topic}".
|
|
1188
1159
|
${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
|
|
1189
|
-
${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${sourceLanguageInstruction}${languageInstruction}
|
|
1160
|
+
${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
|
|
1190
1161
|
|
|
1191
1162
|
Requirements:
|
|
1192
1163
|
- Start with a clear heading (# Title)
|