CrewAI output parsing errors show up when an agent returns text that doesn’t match the structure CrewAI expects for a task or tool call. The framework wraps LangChain parsers, so a stray markdown fence or a leading “Sure!” turns a valid JSON blob into a thrown OutputParsingError. This guide gives you concrete steps to reproduce, isolate, and fix those failures with runnable code.
Step 1: Reproduce and capture the raw model output
You can’t fix what you can’t see. CrewAI swallows the raw LLM response inside TaskOutput, but the text is there if you catch the exception and inspect the task object.
from crewai import Agent, Task, Crew
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = Agent(
role="data extractor",
goal="Extract structured facts from text",
backstory="You are a precise extraction engine.",
llm=llm,
verbose=True,
)
task = Task(
description="Read the paragraph and return JSON with key 'summary'.",
expected_output="A JSON object with a single 'summary' string field.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
try:
crew.kickoff()
except Exception as e:
print("PARSE ERROR:", e)
# CrewAI attaches the last task output to the crew
raw = crew.tasks[0].output.raw if crew.tasks[0].output else "no output"
print("RAW MODEL TEXT:\n", raw)
Run this against a prompt that provokes commentary. You’ll typically see output like:
Sure! Here is the extracted data:
```json
{"summary": "Model declined to answer"}
That leading sentence is what breaks the default `JsonOutputParser`.
## Step 2: Lock the contract with Pydantic models
CrewAI supports `output_pydantic` on a `Task`. This forces a validation step and gives you a typed object instead of a dict. Define the schema narrowly.
```python
from pydantic import BaseModel, Field
class ExtractedFact(BaseModel):
summary: str = Field(..., description="One sentence summary")
confidence: float = Field(..., ge=0.0, le=1.0)
task = Task(
description="Read the paragraph and extract a summary.",
expected_output="JSON matching ExtractedFact",
output_pydantic=ExtractedFact,
agent=agent,
)
When the model emits extra keys or wraps the JSON in prose, Pydantic validation fails. That’s expected—now you have a precise error instead of a silent None.
Step 3: Build a tolerant JSON extractor
The robust fix is to strip fences and slice the first balanced brace pair before parsing. LangChain’s BaseOutputParser is reusable here; you call it manually when CrewAI’s built-in parse throws.
import json
import re
from langchain_core.output_parsers import BaseOutputParser
class TolerantJsonParser(BaseOutputParser):
def parse(self, text: str):
# remove ```json ... ``` or ``` ... ```
fenced = re.search(r"```(?:json)?\s*([\s\S]*?)```", text, re.IGNORECASE)
if fenced:
text = fenced.group(1)
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1:
raise ValueError("No JSON object found in model output")
blob = text[start:end + 1]
return json.loads(blob)
def get_format_instructions(self) -> str:
return "Return only a JSON object."
tolerant = TolerantJsonParser()
Wrap your kickoff in a helper that falls back to the tolerant parser and then validates with the Pydantic model:
from pydantic import ValidationError
def run_task_with_fallback(crew, task, model_cls):
try:
crew.kickoff()
return task.output.pydantic
except Exception:
raw = task.output.raw if task.output else ""
try:
data = tolerant.parse(raw)
return model_cls(**data)
except (json.JSONDecodeError, ValidationError, ValueError) as e:
print("Hard failure:", e)
raise
result = run_task_with_fallback(crew, task, ExtractedFact)
print("Parsed:", result)
This converts 90% of CrewAI output parsing errors into clean objects.
Step 4: Wire retries and validation into the crew
CrewAI’s Agent accepts max_retries. Set it explicitly; the default of 2 is often too low when a provider is flaky.
agent = Agent(
role="data extractor",
goal="Extract structured facts from text",
backstory="You are a precise extraction engine.",
llm=llm,
max_retries=4,
verbose=True,
)
Combine that with the fallback parser from Step 3. If the first attempt returns prose, the retry re-prompts with CrewAI’s internal correction flow, and your fallback catches the case where even that drifts.
Add a lightweight unit test that injects a malformed string through TolerantJsonParser to confirm it extracts correctly:
def test_tolerant_parser():
bad = "Here you go:\n```json\n{\"summary\": \"ok\", \"confidence\": 0.9}\n```\nThanks!"
parsed = tolerant.parse(bad)
assert parsed["summary"] == "ok"
ExtractedFact(**parsed)
Step 5: Route around provider formatting drift
Some providers return non-standard chat templates that confuse the JSON parser more often than OpenAI’s reference format. If you front your agents with an OpenAI-compatible gateway such as n4n.ai, which aggregates 240+ models and automatically falls back when a provider is rate-limited or degraded, you avoid silent malformation from a single misbehaving endpoint. The change is just a base_url and api_key on the LangChain client:
llm = ChatOpenAI(
model="openai/gpt-4o-mini",
base_url="https://your-gateway.example/v1",
api_key="sk-...",
temperature=0,
)
Keep the same output_pydantic and tolerant parser. The gateway forwards cache-control hints and honors routing directives, so you can pin a known-good model family per task without rewriting agent code.
Verify the fix
Success means three things:
crew.kickoff()completes withoutOutputParsingErroron inputs that previously failed.task.output.pydanticis an instance ofExtractedFactwith valid field types.- Your
test_tolerant_parserpasses in CI, proving the extractor handles fenced and prosy output.
Run the crew against a corpus of 20 real inputs that triggered errors before. If zero throw and all validate, the CrewAI output parsing errors are resolved. If any still fail, log the raw text and extend the regex in TolerantJsonParser—don’t loosen the Pydantic schema to mask dirty data.