skip to content

What does output_pydantic change about a CrewAI Task's TaskOutput?

level: middleimportance: must knowfreq 66%

answer

  1. one switch for a model, one for a dict
  2. the text form never goes away
  3. conversion runs after the loop finishes
  4. valid shape is not valid content

basics

~20 s

Setting output_pydantic on a Task makes CrewAI convert the agent's finished text into that model after the task ends. The resulting TaskOutput still carries the original text in .raw, and additionally exposes a validated model instance in .pydantic, with .output_format marking it as structured.

solid answer

~40 s

`Task(output_pydantic=MyModel)` adds a post-processing step: the agent produces its final answer as usual, then CrewAI converts that answer into `MyModel` and attaches it to the resulting `TaskOutput`. You read it as `task_output.pydantic` — a real, validated instance — while `task_output.raw` still holds the original string and `task_output.output_format` records that the result is structured. `output_json=MyModel` is the sibling switch that populates `task_output.json_dict` with a plain dict instead; you set one or the other, not both. The important operational points: the conversion happens *after* the agent has finished, so it costs an extra model round-trip when the provider's structured-output path is used, and it can fail on a malformed answer. Keep `expected_output` describing the same fields, so the agent is already producing something close to the schema rather than being reshaped from prose.

code

python · 23 lines
python
from crewai import Task
from pydantic import BaseModel, Field


class Finding(BaseModel):
    claim: str = Field(description="One sentence, no hedging")
    source_type: str = Field(description="docs, blog, paper or vendor page")


class Report(BaseModel):
    title: str
    findings: list[Finding]


research = Task(
    description="Research recent developments in {topic}.",
    expected_output=(
        "A title and exactly five findings; each finding is one sentence "
        "plus the type of source it came from."
    ),
    agent=researcher,
    output_pydantic=Report,
)

go deeper

for a junior

Know that output_pydantic makes the result available as a validated model on the task output, that output_json gives a dict instead, and that the plain text is still there in the raw field.

for a middle

Explain that conversion is a post-loop step with its own cost and failure mode, name the main TaskOutput fields you would read, and say why expected_output must describe the same fields as the schema.

for a senior

Show the operational picture: extra calls per structured task, conversion failures on weak models, and schema-valid filler. Pair the schema with a guardrail so semantic failures retry rather than flowing downstream.

for a principal

Decide where the typed boundary of the system sits — structured output at the crew's edge with plain text between agents, versus schemas everywhere — and weigh the per-task conversion cost against the reliability you gain across a whole pipeline.

## The problem it solves An agent's natural output is text. Anything downstream that is code — a database write, an API call, a template render — wants fields. CrewAI's answer is a per-task declaration of the target type, and a `TaskOutput` object rich enough to carry both the text and the parsed form. ## The two switches - `output_pydantic=MyModel` — the finished answer becomes an instance of the Pydantic model. - `output_json=MyModel` — the finished answer becomes a plain dictionary, still validated against the model. They are mutually exclusive on one task; setting both is a configuration error. Choose `output_pydantic` when Python code consumes the result and you want typed attribute access and validators; choose `output_json` when the result is immediately serialized, stored, or handed to something that wants a dict. ## What TaskOutput carries Every task returns a `TaskOutput` regardless of whether you asked for structure. Its fields include: - `raw` — the agent's final answer as a string. **Always populated.** This is the fallback and the debugging surface. - `pydantic` — the model instance, when `output_pydantic` was set; otherwise `None`. - `json_dict` — the dict, when `output_json` was set; otherwise `None`. - `description` and `expected_output` — the task's own contract strings, so a log line can show what was asked next to what came back. - `name` — the task name, if you gave one. - `summary` — a short truncation of the description, useful in traces. - `agent` — the role of the agent that produced it. - `output_format` — an enum recording whether the result is raw, JSON, or Pydantic. There is also a `to_dict()` helper for getting the structured payload out generically. Printing a `TaskOutput` gives you a readable string, which is why so much example code interpolates it directly — but production code should read the specific field it wants rather than relying on the string form. ## When the conversion happens, and what it costs This is the point most candidates miss. The schema is **not** enforced during the agent's reasoning loop. The agent thinks, calls tools, and produces a final answer in whatever form the prompt steered it toward. Only then does CrewAI convert that answer to the requested type. Depending on the model and provider, that conversion uses the provider's function-calling/structured-output capability or a text-to-schema pass — either way it is work that happens after the loop, and on many providers it is an additional model call. Implications: - **Latency and cost.** Every structured task pays for the extra step. On a ten-task crew that is ten extra calls. - **It can fail.** If the answer is a wall of prose with none of the required content, no converter can invent it. You get a conversion failure or a model instance whose fields were filled with plausible-looking filler. - **Garbage in, valid garbage out.** A schema guarantees *shape*, never *truth*. `Report(findings=["N/A"])` is perfectly valid. ## Keeping the prompt and the schema aligned The fix for both failure modes is the same: make `expected_output` describe the schema in prose. If the model has `title: str`, `findings: list[Finding]` with exactly five entries, then `expected_output` should say so — "a title and exactly five findings, each with a claim and a source type". Then the conversion is a reformat of something already correct, not a rescue operation. Field descriptions on the Pydantic model help too, since they usually travel into the structured-output schema the provider sees. ## Structured output versus guardrails These are complementary, not alternatives. A schema answers "is this the right shape?". A guardrail callable answers "is this acceptable?" — length, banned content, cross-field consistency, a lookup against a real system — and can force a retry with feedback. Real pipelines use `output_pydantic` for the contract and a guardrail for the semantics. ## Chaining structured output When a downstream task consumes this one through context, what it receives is the textual form. So structured output is primarily for *your code* at the boundary of the crew, not a typed channel between agents. If a downstream agent genuinely needs specific fields, be explicit about them in its description rather than assuming a typed object arrives. ## Writing it out `output_file="report.md"` is a separate concern: it writes the task result to disk and works with or without a schema. If you need the structured payload persisted, either point `output_file` at a `.json` path alongside `output_json`, or write the file yourself from `task_output.pydantic` in a callback — the second is easier to test. ## What interviewers are checking That you know `raw` survives; that conversion is a post-loop step with a real cost and a real failure mode; that shape validity is not correctness; and that `expected_output` and the schema have to agree.

  • You set output_pydantic and the fields come back populated but wrong — plausible filler rather than real data. What is happening?
    The conversion step reshapes whatever the agent produced; it cannot add information. If the final answer never contained the facts, the converter fills the required fields with the model's best guess. Fix it upstream: make `expected_output` demand the same fields in prose so the agent gathers them, and add a guardrail that rejects placeholder values so the task retries with feedback instead of returning confident filler.
  • Can you set both output_json and output_pydantic on the same task?
    No — they are mutually exclusive and CrewAI rejects the configuration. Pick based on the consumer: `output_pydantic` for typed attribute access and model validators inside Python, `output_json` when the result is immediately serialized or stored. If you set `output_pydantic` and later need a dict, dump the model instance rather than declaring both.
  • Does a schema on task A give task B typed access to those fields?
    No. Downstream tasks receive upstream results as text in their context, so the schema is a boundary contract for your own code, not a typed channel between agents. If task B depends on particular fields, say so in its description, or read the structured output yourself between runs and pass it in as explicit input.
  • How do you get the structured result persisted to disk?
    `output_file` writes the task's result to the given path and is independent of the schema, so pointing it at a `.json` path alongside `output_json` is the quick route. For anything with naming rules, partitioning, or error handling, write the file yourself from `task_output.pydantic` in a task callback — that path is testable and does not depend on interpolation of the file name.

saying these in an interview costs you the question

  • Believing the schema constrains the agent during its reasoning loop
  • Assuming the raw text is discarded once a model is attached
  • Treating schema validity as evidence the content is correct
  • Setting both output_json and output_pydantic on one task
  • Expecting downstream agents to receive a typed object

context