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 ──────────────────────────────────────────────────────
/**
* 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é
* `response_format: { type: "json_object" }`. On retire ces fences avant
* `JSON.parse` pour éviter `SyntaxError: Expected property name`.
* Deux dérives observées malgré `response_format: { type: "json_object" }` :
* 1. Wrap markdown ```json … ``` (rare mais arrive).
* 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 {
return raw
.replace(/^\s*```(?:json)?\s*/i, "")
.replace(/\s*```\s*$/i, "")
let cleaned = raw
.replace(/```json\s*/gi, "")
.replace(/```\s*/g, "")
.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(