Home / Guides / LangChain OutputParserException Fix

Fix LangChain OutputParserException

LangChain's JsonOutputParser and PydanticOutputParser throw OutputParserException when the LLM returns JSON wrapped in markdown fences, prefixed with reasoning tags, or containing other malformed output. Here are 4 fixes, from quick patch to bulletproof.

Applies to: langchain 0.2+, langchain-core, langchain-openai — any model via OpenAI, OpenRouter, Ollama, or direct API

The error you're seeing

You set up a JsonOutputParser or PydanticOutputParser, the LLM returns what looks like valid JSON, and you get this:

❌ Full traceback
Traceback (most recent call last):
  File "chain.py", line 42, in <module>
    result = chain.invoke({"query": "Tell me about Alice"})
  ...
  File "langchain_core/output_parsers/json.py", line 69, in parse
    raise OutputParserException(
langchain_core.exceptions.OutputParserException: Invalid json output: ```json
{"name": "Alice", "age": 30}
```

The JSON itself is perfectly valid. The problem is the model wrapped it in markdown code fences (```json ... ```), and LangChain's parser calls json.loads() on the raw string — which includes the fences. This is the #1 cause of OutputParserException.

Other triggers: Models like DeepSeek R1 prepend <think>...</think> reasoning blocks. Some models add trailing commas, use Python-style True/None instead of JSON true/null, or include explanatory text before/after the JSON. All of these cause the same exception.

Fix 1: OutputFixingParser (built-in retry)

LangChain ships with OutputFixingParser that catches the parse error and sends the malformed output back to the LLM with instructions to fix it. The quickest fix if you're already in a LangChain chain:

✅ Fix — OutputFixingParser
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from langchain.output_parsers import OutputFixingParser

llm = ChatOpenAI(model="gpt-4o-mini")
json_parser = JsonOutputParser()

# Wraps your parser — on failure, asks the LLM to fix its own output
fixing_parser = OutputFixingParser.from_llm(
    parser=json_parser,
    llm=llm,
)

chain = prompt | llm | fixing_parser
result = chain.invoke({"query": "Tell me about Alice"})  # ✅ works
Caveat: This makes a second LLM call every time parsing fails. That means double the latency and double the cost on those requests. If fences are the failure mode (and they usually are), you're paying for a full round-trip to strip three backticks.

Fix 2: Manual preprocessing

Strip the fences yourself before the parser sees the output. Zero extra LLM calls, zero dependencies:

✅ Fix — strip fences before parsing
import re
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.runnables import RunnableLambda

def clean_llm_json(text: str) -> str:
    """Strip markdown fences from LLM output."""
    text = re.sub(r'^```(?:json)?\n?', '', text.strip())
    text = re.sub(r'\n?```$', '', text.strip())
    return text

# Insert the cleaner into your chain
chain = prompt | llm | RunnableLambda(lambda msg: clean_llm_json(msg.content)) | JsonOutputParser()
Caveat: This only handles markdown fences. It won't fix <think> tags, trailing commas, Python-style booleans (True/False/None), or other malformed JSON. You'll need to keep extending the function as you encounter new failure modes.

Fix 3: Custom parser with repair logic

Build a subclass that handles the most common LLM JSON issues in one place. This covers fences, think tags, Python literals, and trailing commas:

✅ Fix — repairing parser subclass
import re, json
from langchain_core.output_parsers import JsonOutputParser

class RepairingJsonParser(JsonOutputParser):
    """JsonOutputParser that repairs common LLM JSON issues."""

    def _repair(self, text: str) -> str:
        # 1. Strip <think> blocks (DeepSeek R1, QwQ, etc.)
        text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
        # 2. Extract from markdown fences
        fence = re.search(r'```(?:json)?\s*([\s\S]*?)```', text)
        if fence:
            text = fence.group(1)
        # 3. Fix Python-style literals
        text = text.replace("True", "true").replace("False", "false").replace("None", "null")
        # 4. Remove trailing commas before } or ]
        text = re.sub(r',\s*([}\]])', r'\1', text)
        return text.strip()

    def parse(self, text: str) -> dict:
        return super().parse(self._repair(text))

# Use it as a drop-in replacement
chain = prompt | llm | RepairingJsonParser()
Caveat: You're now maintaining JSON repair code. Every new model quirk (nested fences, partial JSON in streaming, Unicode escapes) means another regex. The True/False replacement is also naive — it will break if those words appear inside string values.

Fix 4: Proxy-level repair (StreamFix)

Instead of fixing the output after LangChain receives it, repair it before it arrives. StreamFix sits between LangChain and the LLM provider and strips fences, think tags, and other malformed output at the proxy level — including during streaming. Your parser never sees broken JSON:

✅ Fix — proxy approach (no parser changes)
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser

llm = ChatOpenAI(
    model="openai/gpt-4o-mini",        # or deepseek/deepseek-r1, etc.
    base_url="https://streamfix.dev/v1",  # routes through StreamFix
    api_key="sk_YOUR_STREAMFIX_KEY",
)

# Standard JsonOutputParser — no wrapper, no subclass, no cleanup
chain = prompt | llm | JsonOutputParser()
result = chain.invoke({"query": "Tell me about Alice"})  # ✅ always clean JSON

Because the repair happens at the HTTP response level, it works with JsonOutputParser, PydanticOutputParser, StructuredOutputParser, and any custom parser. Streaming is handled token-by-token — fences and think tags are stripped from the SSE stream in real time.

Comparison of all 4 fixes

OutputFixingParser Manual strip Custom parser StreamFix proxy
Extra LLM calls 1 per failure None None None
Handles fences Yes Yes Yes Yes
Handles think tags Yes No Yes Yes
Handles trailing commas Usually No Yes Yes
Handles type coercion Usually No Fragile Yes
Streaming support No No No Yes

Streaming support means the fix works on partial JSON chunks during SSE streaming, not just on complete responses.

Stop fighting OutputParserException

StreamFix repairs LLM JSON output at the proxy level — fences, think tags, trailing commas, and Python literals are all handled before LangChain ever sees the response. One base_url change, zero parser code.

from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser

llm = ChatOpenAI(
    model="openai/gpt-4o-mini",
    base_url="https://streamfix.dev/v1",
    api_key="sk_YOUR_STREAMFIX_KEY",
)

# No OutputFixingParser, no custom subclass, no regex
chain = prompt | llm | JsonOutputParser()
result = chain.invoke({"query": "Tell me about Alice"})  # ✅ always works
Get Free API Key →

Related guides