Erreur JSON.parse dans n8n après un appel LLM : causes et corrections

Publié le 10 octobre 2026 Dernière mise à jour le 10 octobre 2026 6 min de lecture

En bref
Lorsqu'un workflow n8n demande à un LLM de répondre en JSON, le modèle renvoie parfois sa réponse entourée de balises markdown. Ces balises restent invisibles dans une interface de chat, mais elles rendent la chaîne illisible pour JSON.parse(), qui s'interrompt avec une erreur de type Unexpected token. L'approche la plus prudente combine une contrainte de format côté API (sortie structurée) et un nettoyage par code placé en filet de sécurité.[1][2]

Un workflow n8n appelle un modèle de langage, récupère sa réponse et la transmet au nœud suivant. Pendant des jours, tout se déroule normalement. Puis une exécution échoue sur une erreur de syntaxe, sans qu'aucun paramètre n'ait changé.

Ce scénario est fréquent dès qu'un LLM alimente un traitement automatisé. Il a été identifié lors des tests du node Miravig avec Gemini, puis corrigé. Cet article ouvre une série consacrée à la fiabilité et à la sécurité des workflows n8n qui appellent un LLM. Il explique le bug, la manière de le reconnaître et les corrections disponibles, de la plus simple à la plus robuste.

Pourquoi demander du JSON à un LLM

n8n est une plateforme d'automatisation : un workflow est une chaîne de nœuds, chacun recevant les données du précédent. Un LLM répond par défaut en texte libre, ce qui convient à un lecteur humain mais pas à un nœud qui doit lire des champs précis.

Prenons un exemple concret. Un workflow reçoit des e-mails de clients et doit en extraire le nom et le montant de la facture pour alimenter un CRM. Un texte libre comme « Le client Dupont réclame 1 200 euros » ne s'insère dans aucune colonne. Le même contenu en JSON, un format de données lisible par les machines, se range immédiatement :

{"nom": "Dupont", "montant": 1200}

Le nœud Code de n8n transforme la chaîne reçue en objet grâce à la fonction JSON.parse(). Cette fonction est stricte : elle accepte un JSON pur et refuse tout caractère supplémentaire.

Le symptôme : une erreur qui désigne le mauvais nœud

L'erreur affichée varie selon la version de n8n et de Node.js. On rencontre le plus souvent SyntaxError: Unexpected token ou une variante mentionnant un caractère inattendu à la position 0 de la chaîne.

Ce message pointe le nœud Code, pas le nœud LLM. Le réflexe consiste alors à vérifier les identifiants API, le prompt ou les limites de requêtes, alors que la cause se trouve dans le format de la réponse reçue.

La cause : une réponse livrée dans une enveloppe markdown

Certains modèles entourent leur JSON d'une « clôture » markdown, c'est-à-dire de trois accents graves suivis du mot json :

```json
{"nom": "Dupont", "montant": 1200}
```

Une comparaison aide à saisir la différence. JSON.parse() attend la lettre seule. Le modèle envoie la lettre dans une enveloppe. Un humain ouvre l'enveloppe sans y penser. Une fonction de lecture stricte, elle, rejette le courrier dès le premier caractère.

Les fournisseurs ne documentent pas la raison exacte de ce comportement. L'explication la plus plausible tient à l'usage principal de ces modèles : produire des réponses lisibles dans une interface de chat, où le markdown est converti en bloc de code propre. Le réflexe de mise en forme se retrouve alors dans la réponse d'API.

Pour les fournisseurs, demander du JSON dans le prompt ne constitue pas une garantie. La documentation de Gemini indique que le modèle n'est pas assuré de renvoyer uniquement du JSON lorsque la demande passe par le texte de la consigne.[1]

Pourquoi le bug traverse les phases de test

Trois caractéristiques le rendent difficile à repérer.

Il n'est pas systématique. Selon le modèle, sa version et la formulation de la consigne, la même requête peut produire du JSON pur une fois et une réponse enveloppée la fois suivante. Quelques exécutions de test réussies ne prouvent donc rien.

Personne ne relit la réponse brute. Dans une interface de chat, l'utilisateur voit immédiatement un défaut de format. Dans un workflow, la réponse passe directement au nœud suivant.

L'erreur survient sans témoin. Elle apparaît souvent sur une exécution déclenchée automatiquement, par exemple à la réception d'un e-mail, sans personne devant l'écran.

Diagnostiquer en trois étapes

  1. Ouvrir l'exécution en échec dans n8n et afficher la sortie du nœud LLM, pas celle du nœud Code.
  2. Lire les premiers caractères du champ de réponse : la présence de ```json confirme le diagnostic.
  3. Vérifier l'autre cause possible de la même erreur : une réponse tronquée par la limite de longueur du modèle produit également un JSON invalide, mais sans balises.

Quatre niveaux de correction

Les corrections se distinguent par leur fiabilité et par l'endroit où elles interviennent : dans la consigne, dans le code, chez le fournisseur ou dans n8n.

NiveauPrincipeFiabilitéLimite
Consigne dans le promptDemander un JSON brut, sans markdownMoyenneAucune garantie, le modèle peut ignorer la consigne
Nettoyage par codeRetirer les balises avant JSON.parse()BonneNe règle pas un JSON tronqué ou mal formé
Sortie structurée de l'APIImposer un schéma côté fournisseurÉlevéeDépend du modèle et de l'option disponible
Structured Output Parser n8nValider la réponse dans n8nÉlevée sur une chaîne simplePeu fiable avec les agents

1. La consigne dans le prompt

Une phrase explicite réduit la fréquence du problème :

Réponds uniquement avec un objet JSON brut. N'utilise aucune balise markdown et n'ajoute aucun texte avant ou après.

Cette consigne diminue les occurrences sans les supprimer. Elle suffit rarement seule.

2. Le nettoyage par code

Un nœud Code placé juste après le LLM retire les balises, puis lit le JSON. La version suivante gère aussi un éventuel texte avant ou après l'objet :

// Le nom du champ ("output" ici) dépend du nœud LLM utilisé
const raw = $input.first().json.output;

// Retire les balises markdown éventuelles
let text = raw.replace(/```(?:json)?/gi, '').trim();

// Conserve ce qui se trouve entre la première et la dernière accolade
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start === -1 || end === -1) {
  throw new Error('Aucun objet JSON trouvé dans la réponse du modèle');
}
text = text.slice(start, end + 1);

return [{ json: JSON.parse(text) }];

Cette version possède des limites connues. Elle suppose que la réponse est un objet et non un tableau placé à la racine. Elle ne répare pas une réponse tronquée. Et si le modèle renvoie plusieurs blocs JSON, elle les fusionne dans une seule chaîne invalide. Le message d'erreur explicite permet au moins de repérer rapidement ces cas dans les journaux d'exécution.

Lorsque plusieurs workflows appellent des LLM, il est préférable d'isoler ce nettoyage dans un sous-workflow réutilisable. Une correction ultérieure ne se fait alors qu'à un seul endroit.

3. La sortie structurée de l'API

Les principaux fournisseurs proposent désormais de contraindre la réponse à un schéma. Chez Gemini, il s'agit de définir responseMimeType sur application/json et de fournir un responseSchema.[1] Chez OpenAI, le paramètre response_format accepte un json_schema. La documentation précise que la sortie structurée garantit le respect du schéma, là où l'ancien mode JSON garantit seulement la validité de la syntaxe.[2]

C'est la solution la plus solide lorsqu'elle est disponible, car le format est imposé en amont plutôt que corrigé en aval.

4. Le Structured Output Parser de n8n

n8n propose un sous-nœud dédié, le Structured Output Parser, qui valide la réponse selon un schéma JSON. Il s'active en cochant l'option « Require Specific Output Format » sur le nœud d'IA concerné.[3] La documentation de n8n signale qu'il est souvent peu fiable avec les agents, et recommande dans ce cas de confier l'analyse à une chaîne LLM distincte.[3]

La combinaison la plus prudente

Aucun niveau ne couvre tous les cas. La contrainte de format côté API réduit fortement le problème, le nettoyage par code rattrape les écarts restants et l'erreur explicite signale ce qui échappe encore aux deux premiers.

Ce que ce bug révèle des workflows automatisés

Cette panne a une particularité : elle se voit. Le workflow s'arrête, une erreur s'affiche, quelqu'un intervient. Elle illustre pourtant un principe plus large : dans un workflow automatisé, personne ne relit ce qui circule entre les nœuds.

Le même constat s'applique au contenu des requêtes. Une réponse mal formatée interrompt le traitement. Une donnée personnelle envoyée par erreur au modèle ne déclenche aucune alerte. Le coût d'un tel envoi commence au moment où la donnée quitte le poste de travail, ce que détaille l'article sur le copier-coller dans une IA. La question de la responsabilité juridique et financière se pose ensuite, comme l'explique l'article sur les fuites de données via l'IA. Les articles suivants de la série abordent cette seconde catégorie de pannes, celle qui ne produit aucun message d'erreur.

Pour comprendre pourquoi un appel d'API n'offre pas les mêmes garanties de confidentialité qu'une interface de chat, l'article sur les politiques de ChatGPT, Claude et Gemini détaille les différences de conservation et d'entraînement.

Questions fréquentes

Pourquoi JSON.parse() échoue-t-il après un appel à OpenAI ou Gemini alors que mon prompt n'a pas changé ?

Le modèle a probablement entouré sa réponse de balises markdown. Elles sont invisibles dans une interface de chat, mais incompatibles avec un lecteur JSON strict. Le comportement peut varier d'une exécution à l'autre.

Que signifie l'erreur « Unexpected token » dans un nœud Code n8n ?

Le nœud a rencontré un caractère qu'il ne s'attendait pas à lire. Dans ce contexte, il s'agit le plus souvent du premier accent grave d'une balise markdown, parfois d'un texte d'introduction ajouté par le modèle.

Comment forcer un LLM à répondre en JSON pur ?

La consigne dans le prompt aide mais ne garantit rien. Les modes de sortie structurée proposés par les API (schéma JSON imposé) offrent une garantie bien supérieure. Un nettoyage par code reste utile en complément.

Le bug touche-t-il tous les modèles de la même façon ?

Non. Il a été observé avec Gemini lors des tests du node Miravig. Il peut apparaître avec d'autres modèles selon la consigne donnée. Les fournisseurs ne publient pas de liste précise des cas concernés.

Une réponse tronquée produit-elle la même erreur ?

Oui. Lorsque la limite de longueur est atteinte, le JSON s'arrête avant l'accolade fermante et devient invalide. Dans ce cas, aucune balise n'est visible, et seule l'augmentation de la limite ou la réduction de la réponse attendue corrige le problème.

Ce qu'il faut retenir

Les LLM peuvent entourer une réponse JSON de balises markdown, ce qui fait échouer JSON.parse() dans le nœud suivant. Le comportement n'est pas systématique, d'où un bug qui passe les tests et apparaît en production. L'erreur désigne le nœud Code alors que la cause se trouve dans la réponse du LLM. Combiner une sortie structurée côté API et un nettoyage par code offre la meilleure couverture, avec une limite à garder en tête : aucune de ces protections ne vérifie le contenu des données envoyées au modèle.

Pour aller plus loin

Sources

[1] Gemini API, sorties structurées et mode JSON

[2] OpenAI, guide des sorties structurées

[3] n8n, Structured Output Parser

Masquer les données sensibles avant l'appel au modèle : le node Miravig pour n8n détecte et masque les données identifiantes dans un workflow, avant l'envoi au LLM. Voir le mode d'emploi →