Skip to content

AI Agent structured output parser #21174

Description

@Fazik

Bug Description

Summary

The n8n structured output parser fails when AI agent responses contain markdown code blocks (triple backticks) inside JSON string values, causing workflow execution to fail with "Invalid JSON in model output" error.

Bug Details

Location

  • File: packages/@n8n/nodes-langchain/utils/output_parsers/N8nStructuredOutputParser.ts
  • Line: 36
  • Node: format_final_json_response (Output Parser Structured node)

Error Message

NodeOperationError: Model output doesn't fit required format
Context: { outputParserFailReason: 'Invalid JSON in model output' }

Root Cause

The original parsing logic used a naive string splitting approach:

// BUGGY CODE
const jsonString = text.includes('```') ? text.split(/```(?:json)?/)[1] : text;

Problem: This code triggers on ANY occurrence of triple backticks (\```), including those inside JSON string values. When the AI response contains markdown with code blocks in the message field, it incorrectly splits at the first backtick occurrence, producing invalid JSON.

Example Failure Case

AI Agent Output (Valid JSON):

{
  "output": {
    "message": "## Example\n```bash\n--set globals.enable=false\n```\n",
    "status": "completed"
  }
}

Parser Behavior:

  1. Detects ``` inside the message string
  2. Splits at first ``` and takes second part
  3. Extracts: bash\n--set globals.enable=false\n
  4. Attempts to parse as JSON → SyntaxError: Unexpected token 'b'

The Potential Fix

File: N8nStructuredOutputParser.ts (Lines 35-43)

// Extract JSON from markdown code fence if present
// Using regex to properly match code fences, even if backticks appear in the JSON content
let jsonString = text.trim();

// Look for a code fence with proper opening and closing
// Use GREEDY matching ([\s\S]+) to match to the LAST occurrence of closing ```
// This prevents matching backticks that appear inside JSON string values
// The pattern matches:
//   - Opening: ``` or ```json followed by optional whitespace and optional newline
//   - Content: Any characters (greedy - matches to last ```)
//   - Closing: Optional newline, optional whitespace, then ```
// This handles both standard fences (with newlines) and inline fences (without)
const codeFenceMatch = jsonString.match(/```(?:json)?\s*\n?([\s\S]+)\n?\s*```/);

if (codeFenceMatch) {
  // Extract the content between the fences
  const potentialJson = codeFenceMatch[1].trim();

  // Validate that what we extracted looks like JSON (starts with { or [)
  // This helps avoid false positives from backticks inside JSON content
  if (potentialJson.startsWith('{') || potentialJson.startsWith('[')) {
    jsonString = potentialJson;
  }
}

const json = JSON.parse(jsonString.trim());

How It Works

The regex pattern /```(?:json)?\s*\n?([\s\S]+)\n?\s*```/ ensures:

  1. \``` - Matches the opening three backticks literally
  2. (?:json)? - Non-capturing group that optionally matches the word "json"
  3. \s* - Matches zero or more whitespace characters
  4. \n? - Optionally matches a newline (handles both standard and inline fences)
  5. ([\s\S]+) - CAPTURE GROUP - The actual JSON content
    • [\s\S] = matches ANY character (whitespace OR non-whitespace)
    • + = one or more times, GREEDY (matches to the LAST occurrence of closing ```)
  6. \n? - Optionally matches newline before closing fence
  7. \s* - Optional whitespace before closing backticks
  8. \``` - Matches the closing three backticks

Key Points:

  • Greedy matching ensures we match to the LAST occurrence of ```, not the first one inside JSON content
  • Optional newlines (\n?) handle both standard fences and inline fences (e.g., ```json\n{"foo":"bar"}```)
  • Validation check provides extra safety by verifying extracted content starts with { or [
  • No anchors allows text before/after the fence (e.g., "Here's the output: json\n...\n")

To Reproduce

Provide in AI agent output message tripple backsticks and configure json shcema parser

Expected behavior

Parser passed for correct json.

Debug Info

Debug info

core

  • n8nVersion: 1.116.2
  • platform: docker (self-hosted)
  • nodeJsVersion: 22.13.1
  • nodeEnv: ci
  • database: postgres
  • executionMode: scaling (single-main)
  • concurrency: -1
  • license: enterprise (production)

storage

  • success: all
  • error: all
  • progress: false
  • manual: true
  • binaryMode: memory

pruning

  • enabled: true
  • maxAge: 336 hours
  • maxCount: 10000 executions

client

  • userAgent: mozilla/5.0 (macintosh; intel mac os x 10_15_7) applewebkit/537.36 (khtml, like gecko) chrome/141.0.0.0 safari/537.36
  • isTouchDevice: false

security

  • secureCookie: false

Generated at: 2025-10-24T17:26:39.897Z

Operating System

node:22.13.1-bookworm-slim

n8n Version

1.116.2

Node.js Version

22.13.1

Database

PostgreSQL

Execution mode

queue

Hosting

self hosted

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions