n4nAI

Fixing CrewAI agent output parsing errors

Practical guide to resolving CrewAI output parsing errors in multi-agent pipelines: enforce JSON schemas, build custom parsers, add retries with validation.

n4n Team2 min read532 words

Audio narration

Coming soon — every post will get a voice note here.

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:

  1. crew.kickoff() completes without OutputParsingError on inputs that previously failed.
  2. task.output.pydantic is an instance of ExtractedFact with valid field types.
  3. Your test_tolerant_parser passes 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.

Tagscrewaioutput-parsingdebuggingerrors

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai & autogen multi-agent debugging posts →