Tolerate malformed LLM JSON escapes

This commit is contained in:
syntaxbullet
2026-06-17 15:16:43 +02:00
parent e276936af5
commit 6d192c48a1

View File

@@ -372,6 +372,28 @@ function extractJsonObject(text: string): string {
return trimmed; return trimmed;
} }
function escapeInvalidJsonBackslashes(text: string): string {
return text.replace(/\\(?:u(?![0-9a-fA-F]{4})|(?!["\\/bfnrtu]))/g, "\\\\");
}
function parseLlmJson<T>(content: string, context: string): T {
const json = extractJsonObject(content);
try {
return JSON.parse(json) as T;
} catch (error) {
const repaired = escapeInvalidJsonBackslashes(json);
if (repaired !== json) {
try {
return JSON.parse(repaired) as T;
} catch {
// Fall through to the contextual error below.
}
}
const message = error instanceof Error ? error.message : String(error);
throw new SyntaxError(`${context}: ${message}`);
}
}
function summarizeSegmentierungsQualitaet(fragen: FrageMitAntwort[] | undefined): SegmentierungsQualitaet | undefined { function summarizeSegmentierungsQualitaet(fragen: FrageMitAntwort[] | undefined): SegmentierungsQualitaet | undefined {
const items = fragen ?? []; const items = fragen ?? [];
if (!items.length) return undefined; if (!items.length) return undefined;
@@ -961,9 +983,9 @@ ${JSON.stringify(data, null, 2)}`,
); );
} }
const parsed = JSON.parse(extractJsonObject(content)) as Partial<LlmScoringAssessment> & { const parsed = parseLlmJson<Partial<LlmScoringAssessment> & {
begruendung?: string; begruendung?: string;
}; }>(content, "Could not parse traffic light assessment JSON");
const ausschlussgruende = Array.isArray(parsed.ausschlussgruende) const ausschlussgruende = Array.isArray(parsed.ausschlussgruende)
? parsed.ausschlussgruende.map((flag) => String(flag).trim()).filter(Boolean) ? parsed.ausschlussgruende.map((flag) => String(flag).trim()).filter(Boolean)
: []; : [];
@@ -1065,7 +1087,10 @@ Vorgaben:
); );
} }
const parsed = JSON.parse(extractJsonObject(content)) as { fragen?: Array<Partial<FrageMitAntwort>> }; const parsed = parseLlmJson<{ fragen?: Array<Partial<FrageMitAntwort>> }>(
content,
`Could not parse answer segmentation JSON for ${fallbackLabel}`,
);
const byId = new Map((parsed.fragen ?? []).map((frage) => [String(frage.id ?? ""), frage])); const byId = new Map((parsed.fragen ?? []).map((frage) => [String(frage.id ?? ""), frage]));
return fragen.map((frage) => { return fragen.map((frage) => {
@@ -1149,7 +1174,7 @@ ${JSON.stringify(data, null, 2)}`,
); );
} }
const parsed = JSON.parse(extractJsonObject(content)) as Partial<SWOTAnalyse>; const parsed = parseLlmJson<Partial<SWOTAnalyse>>(content, "Could not parse SWOT analysis JSON");
const toStringArray = (value: unknown): string[] => const toStringArray = (value: unknown): string[] =>
Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : []; Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];