Structured outputs

CrewAI · Practical · 12 min

Downstream code needs reliable fields, not paragraphs you have to parse with guesswork.

By default a task returns free text. When code will use the result, ask for structured output instead.

Describe the shape with a Pydantic model: a class whose typed fields define the data, which Pydantic validates. Then set it on the task:

task = Task(..., output_pydantic=Lead)

After the run:

  • task_output.pydantic (or result.pydantic on the crew, for the last task) is a Lead instance with real typed fields.
  • output_json=Lead is an alternative that gives a plain dictionary in json_dict.
  • raw still holds the text.

Set only one of output_pydantic and output_json per task.

Field descriptions (Field(description=...)) are part of what the model sees, so write them as instructions. Use Literal["a", "b"] for fixed categories and keep schemas small.

One agent, no crew. For quick jobs you can call an agent directly with agent.kickoff(messages). Pass response_format=Lead to get structured output. The result's .pydantic holds the object and .raw the text. This skips tasks and crews entirely and is handy inside flows.

On the model itself. LLM(model=..., response_format=Lead) asks the provider for that format on every call. Support differs by provider, so test it.

If validation fails, CrewAI tries to convert the output into the schema. Repeated failures usually mean the schema or the task description is unclear.

Example

from typing import Literal
from pydantic import BaseModel, Field
from crewai import Agent, Crew, Task

class Lead(BaseModel):
    company: str
    size: Literal["small", "medium", "large"]
    interest: int = Field(ge=1, le=5, description="1 = cold, 5 = ready to buy")

qualifier = Agent(role="Sales qualifier", goal="Score inbound leads",
                  backstory="You read emails and judge buying intent.")
task = Task(description="Qualify this email: {email}",
            expected_output="The lead's company, size and interest.",
            agent=qualifier, output_pydantic=Lead)

result = Crew(agents=[qualifier], tasks=[task]).kickoff(
    inputs={"email": "Hi, we're a 40-person agency and need a quote this week."})
lead = result.pydantic
print(lead.size, lead.interest)       # medium 5  (model-dependent)

quick = qualifier.kickoff("Qualify: 'Just browsing, 2 people.'", response_format=Lead)
print(quick.pydantic)                 # company='...' size='small' interest=1

Remember

  • output_pydantic=Model on a task gives .pydantic objects
  • agent.kickoff(..., response_format=Model) for one-off structured calls
  • Field descriptions and Literal types guide the model

Watch out for

  • Setting both output_pydantic and output_json on the same task; choose one.

Check yourself

  1. After a crew whose last task sets output_pydantic=Lead, where is the Lead object?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.crewai.com/en/concepts/tasks