From 6d192c48a18156da36b748a3a6a38b9c86cddf41 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Wed, 17 Jun 2026 15:16:43 +0200 Subject: [PATCH] Tolerate malformed LLM JSON escapes --- packages/summarizer/summarizer.ts | 33 +++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/summarizer/summarizer.ts b/packages/summarizer/summarizer.ts index 4f1072c..eb19798 100644 --- a/packages/summarizer/summarizer.ts +++ b/packages/summarizer/summarizer.ts @@ -372,6 +372,28 @@ function extractJsonObject(text: string): string { return trimmed; } +function escapeInvalidJsonBackslashes(text: string): string { + return text.replace(/\\(?:u(?![0-9a-fA-F]{4})|(?!["\\/bfnrtu]))/g, "\\\\"); +} + +function parseLlmJson(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 { const items = fragen ?? []; if (!items.length) return undefined; @@ -961,9 +983,9 @@ ${JSON.stringify(data, null, 2)}`, ); } - const parsed = JSON.parse(extractJsonObject(content)) as Partial & { + const parsed = parseLlmJson & { begruendung?: string; - }; + }>(content, "Could not parse traffic light assessment JSON"); const ausschlussgruende = Array.isArray(parsed.ausschlussgruende) ? parsed.ausschlussgruende.map((flag) => String(flag).trim()).filter(Boolean) : []; @@ -1065,7 +1087,10 @@ Vorgaben: ); } - const parsed = JSON.parse(extractJsonObject(content)) as { fragen?: Array> }; + const parsed = parseLlmJson<{ fragen?: Array> }>( + content, + `Could not parse answer segmentation JSON for ${fallbackLabel}`, + ); const byId = new Map((parsed.fragen ?? []).map((frage) => [String(frage.id ?? ""), frage])); return fragen.map((frage) => { @@ -1149,7 +1174,7 @@ ${JSON.stringify(data, null, 2)}`, ); } - const parsed = JSON.parse(extractJsonObject(content)) as Partial; + const parsed = parseLlmJson>(content, "Could not parse SWOT analysis JSON"); const toStringArray = (value: unknown): string[] => Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];