fix: accepter les réponses IA avec contrôles OCR

This commit is contained in:
Manus
2026-10-02 20:05:45 +00:00
parent c5f0c9eed6
commit b09dccb078
2 changed files with 77 additions and 3 deletions

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { cleanJsonResponse } from "./invoiceExtractor";
describe("cleanJsonResponse", () => {
it("supprime les caractères de contrôle bruts dans une chaîne JSON LLM", () => {
const rawResponse = `{
"pageCount": 1,
"invoiceCount": 1,
"invoices": [{
"supplierName": "Manus AI",
"invoiceNumber": "J6LNGDAL-0021",
"extractedText": "Ligne valide\u0000suite du texte"
}]
}`;
const parsed = JSON.parse(cleanJsonResponse(rawResponse));
expect(parsed.invoices[0].invoiceNumber).toBe("J6LNGDAL-0021");
expect(parsed.invoices[0].extractedText).toBe("Ligne validesuite du texte");
});
it("préserve les retours à la ligne échappés dans les chaînes JSON", () => {
const rawResponse = '{"invoices":[{"extractedText":"Ligne 1\nLigne 2"}] }';
const parsed = JSON.parse(cleanJsonResponse(rawResponse));
expect(parsed.invoices[0].extractedText).toBe("Ligne 1\nLigne 2");
});
});

View File

@@ -75,9 +75,54 @@ async function extractTextFromPdf(pdfBuffer: Buffer): Promise<string> {
}
/**
* Clean JSON response from LLM by removing markdown code blocks
* Supprime ou échappe les caractères de contrôle bruts uniquement à
* l'intérieur des chaînes JSON. Certains moteurs retournent ponctuellement
* un caractère NUL provenant du texte OCR : JSON.parse le refuse alors que
* le reste de la réponse est exploitable.
*/
function cleanJsonResponse(content: string): string {
function normalizeControlCharactersInJsonStrings(content: string): string {
let normalized = "";
let inString = false;
let escaped = false;
for (const character of content) {
if (escaped) {
normalized += character;
escaped = false;
continue;
}
if (character === "\\") {
normalized += character;
escaped = true;
continue;
}
if (character === '"') {
inString = !inString;
normalized += character;
continue;
}
if (inString && character.charCodeAt(0) < 0x20) {
if (character === "\n") normalized += "\\n";
else if (character === "\r") normalized += "\\r";
else if (character === "\t") normalized += "\\t";
// Les autres contrôles (notamment NUL) n'ont pas de valeur métier.
continue;
}
normalized += character;
}
return normalized;
}
/**
* Clean JSON response from LLM by removing markdown code blocks.
* Exportée pour couvrir les réponses LLM mal formées en régression.
*/
export function cleanJsonResponse(content: string): string {
let cleaned = content.trim();
// Remove markdown code blocks
@@ -123,7 +168,7 @@ function cleanJsonResponse(content: string): string {
cleaned = cleaned.substring(startIdx, endIdx + 1);
}
return cleaned;
return normalizeControlCharactersInJsonStrings(cleaned);
}
/**