fix(deepseek): handle single-quote JSON from DeepSeek responses

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann_Kitio 2026-04-25 07:41:58 +03:00
parent 9420612abf
commit c473e54ae8

View file

@ -403,17 +403,37 @@ export function truncateToMaxWords(
// ── Appels DeepSeek ────────────────────────────────────────────────────── // ── Appels DeepSeek ──────────────────────────────────────────────────────
/** /**
* Strip d'éventuels fences markdown autour d'une réponse JSON. * Nettoie une réponse DeepSeek avant `JSON.parse`.
* *
* DeepSeek wrappe parfois sa sortie dans des blocs ```json ... ``` malgré * Deux dérives observées malgré `response_format: { type: "json_object" }` :
* `response_format: { type: "json_object" }`. On retire ces fences avant * 1. Wrap markdown ```json … ``` (rare mais arrive).
* `JSON.parse` pour éviter `SyntaxError: Expected property name`. * 2. Guillemets simples au lieu de doubles (`'key': 'value'`) voire
* des chevrons « ... ». Diagnostiqué Sprint 4b (correction EO).
*
* Stratégie défensive :
* - Strip systématique des fences markdown.
* - Tentative JSON.parse en l'état si OK, on retourne tel quel.
* - Sinon : remplacement des single quotes JSON par des doubles, en
* préservant les apostrophes légitimes à l'intérieur des valeurs
* (heuristique : on bascule les `\'` échappés en `'` après le swap).
*/ */
function sanitizeJsonContent(raw: string): string { function sanitizeJsonContent(raw: string): string {
return raw let cleaned = raw
.replace(/^\s*```(?:json)?\s*/i, "") .replace(/```json\s*/gi, "")
.replace(/\s*```\s*$/i, "") .replace(/```\s*/g, "")
.trim(); .trim();
try {
JSON.parse(cleaned);
return cleaned;
} catch {
// Fallback : DeepSeek a renvoyé du « pseudo-JSON » avec single quotes.
// 1. Remplace toutes les `'` par `"` (suffisant dans la plupart des cas).
// 2. Restaure les apostrophes échappées : `\"` (résultat du swap sur un
// `\'` original) redevient `'`.
cleaned = cleaned.replace(/'/g, '"').replace(/\\"/g, "'");
return cleaned;
}
} }
async function callDeepSeek( async function callDeepSeek(