Skip to content

Commit 71d043b

Browse files
committed
fix: handle malformed LLM JSON responses in field filtering
1 parent 3263dca commit 71d043b

1 file changed

Lines changed: 13 additions & 5 deletions

File tree

server/src/sdk/workflowEnricher.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,14 @@ export class WorkflowEnricher {
351351
/**
352352
* Analyze page groups using browser-side script
353353
*/
354+
// LLMs sometimes embed literal newlines in string fields like "reasoning". JSON.parse rejects
355+
// unescaped control characters in strings, so we escape them before parsing.
356+
private static sanitizeJsonString(jsonStr: string): string {
357+
return jsonStr.replace(/"(?:[^"\\]|\\.)*"/g, (match) =>
358+
match.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t')
359+
);
360+
}
361+
354362
private static async analyzePageGroups(validator: SelectorValidator): Promise<any[]> {
355363
try {
356364
const page = (validator as any).page;
@@ -586,7 +594,7 @@ Note: selectedGroupIndex must be between 0 and ${elementGroups.length - 1}`;
586594
jsonStr = objectMatch[0];
587595
}
588596

589-
const decision = JSON.parse(jsonStr);
597+
const decision = JSON.parse(this.sanitizeJsonString(jsonStr));
590598

591599
if (!decision.actionType || decision.actionType !== 'captureList') {
592600
throw new Error('LLM response must have actionType: "captureList"');
@@ -901,7 +909,7 @@ Return a JSON object with this exact structure:
901909
jsonStr = objectMatch[0];
902910
}
903911

904-
const parsedResponse = JSON.parse(jsonStr);
912+
const parsedResponse = JSON.parse(this.sanitizeJsonString(jsonStr));
905913

906914
let labelMapping: Record<string, string>;
907915
if (parsedResponse.fieldLabels) {
@@ -1116,7 +1124,7 @@ Rules:
11161124
jsonStr = objectMatch[0];
11171125
}
11181126

1119-
const filterResult = JSON.parse(jsonStr);
1127+
const filterResult = JSON.parse(this.sanitizeJsonString(jsonStr));
11201128

11211129
if (!Array.isArray(filterResult.selectedFields)) {
11221130
throw new Error('Invalid response: selectedFields must be an array');
@@ -1752,7 +1760,7 @@ Extract the search query, extraction goal, and limit. Return JSON only.`;
17521760
jsonStr = objectMatch[0];
17531761
}
17541762

1755-
const intent = JSON.parse(jsonStr);
1763+
const intent = JSON.parse(this.sanitizeJsonString(jsonStr));
17561764

17571765
if (!intent.searchQuery || !intent.extractionGoal) {
17581766
throw new Error('Invalid intent parsing response - missing required fields');
@@ -2023,7 +2031,7 @@ Select the BEST result index (0-${searchResults.length - 1}). Return JSON only.`
20232031
jsonStr = objectMatch[0];
20242032
}
20252033

2026-
const decision = JSON.parse(jsonStr);
2034+
const decision = JSON.parse(this.sanitizeJsonString(jsonStr));
20272035

20282036
if (decision.selectedIndex === undefined || decision.selectedIndex < 0 || decision.selectedIndex >= searchResults.length) {
20292037
throw new Error(`Invalid selectedIndex: ${decision.selectedIndex}`);

0 commit comments

Comments
 (0)