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.
The error you're seeing
You set up a JsonOutputParser or PydanticOutputParser, the LLM returns what looks like valid JSON, and you get this:
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.
<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:
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
Fix 2: Manual preprocessing
Strip the fences yourself before the parser sees the output. Zero extra LLM calls, zero dependencies:
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()
<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:
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()
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:
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