Structured AI Agents

Return type-safe, validated outputs from agents using Pydantic models

Structured outputs give agents predictable, validated data. When a Task sets output_pydantic or output_json, PraisonAI fails the task if the model can’t produce a value matching the schema — no silent None.

python
from praisonaiagents import Agent
from pydantic import BaseModel

class TopicList(BaseModel):
    topics: list[str]

agent = Agent(name="TopicAgent", llm="gpt-4o-mini")
result = agent.chat("List 3 AI topics", output_pydantic=TopicList)
print(result.topics)

The user asks for structured data; the agent returns validated Pydantic output for downstream code.

Diagram

Structured Outputs

🤖 Agent

📐 Pydantic Schema

✅ Validation

📄 JSON Output

🏗️ Pydantic Model

📱 Your Application

Quick Start

  1. Simple Usage

    Define a Pydantic model and pass it to output_pydantic:

    python
    from praisonaiagents import Agent
    from pydantic import BaseModel
    
    class AnalysisReport(BaseModel):
        title: str
        findings: str
        summary: str
    
    agent = Agent(
        name="Analyst",
        instructions="Return concise structured analysis.",
    )
    
    result = agent.chat(
        "Summarise recent AI agent trends",
        output_pydantic=AnalysisReport,
    )
    print(result.title, result.summary)
  2. Multi-Agent Task

    Use Task with output_pydantic inside an AgentTeam:

    python
    from praisonaiagents import Agent, Task, AgentTeam
    from pydantic import BaseModel
    
    class ResearchReport(BaseModel):
        topic: str
        findings: str
        sources: list[str]
    
    researcher = Agent(
        name="Researcher",
        instructions="Research and return structured findings.",
    )
    
    task = Task(
        description="Research quantum computing developments",
        expected_output="Structured research report",
        agent=researcher,
        output_pydantic=ResearchReport,
    )
    
    team = AgentTeam(agents=[researcher], tasks=[task], process="sequential")
    result = team.start()
    print(result.pydantic.topic)

How It Works

Diagram
TaskPydantic SchemaAgentUserTaskPydantic SchemaAgentUserAfter retries exhausted, task.status = “failed”alt[Valid][Invalid]Request + output_pydantic=ModelGenerate responsejson.loads + model_validateTaskResult(success=True, pydantic=instance)model.field valuesTaskResult(success=False, error=”… (schema + validation err)”)Retry (up to max_retries)
PhaseWhat happens
1. RequestYou pass a Pydantic model to output_pydantic
2. GenerateThe agent produces a response
3. ValidateThe response is parsed and checked against your schema
4. ReturnOn success a typed object flows back; on failure the task retries then fails

Failure Handling

Structured tasks fail-closed — success is False and error carries the schema name plus the parse error when the model can’t match your schema.

python
from praisonaiagents import Agent, Task, AgentTeam
from pydantic import BaseModel

class Fact(BaseModel):
    title: str
    detail: str

agent = Agent(name="F", llm="gpt-4o-mini")

task = Task(
    description="Give one fact about the ocean",
    agent=agent,
    output_pydantic=Fact,
    max_retries=2,     # optional — see Task Retry Policy
)

team = AgentTeam(agents=[agent], tasks=[task])
team.start()

# Success — pydantic is guaranteed populated
if task.result.success:
    print(task.result.pydantic.title)
else:
    print("Failed:", task.result.error)   # includes schema name + validation error
    print("Raw text was:", task.result.raw)   # kept for debugging
Casesuccesspydantic / json_dicterrorraw
Model returns valid JSON matching schemaTruepopulated instanceNoneJSON text
Model returns invalid / freeform proseFalseNoneSchema name + parse errorPreserved for debugging
Model returns valid falsey JSON ({}, [], false, 0) with output_jsonTruethe falsey valueNoneJSON text
No structured output requested + non-empty proseTruen/aNoneprose

Pick the option that matches what your downstream code needs:

Diagram

Yes — parse errors matter

Only need dict-shaped JSON

Free prose is fine

Does downstream code need typed fields?

Set output_pydantic=Model
fail-closed on parse error

Set output_json=True
same fail-closed policy

Leave both unset
non-empty text = success


Native Structured Output

PraisonAI auto-detects models that support native response_format with JSON schema (GPT-4o, Claude 3.5, Gemini 2.0). Unsupported models fall back to prompt injection.

python
from praisonaiagents import Agent
from pydantic import BaseModel

class TopicList(BaseModel):
    topics: list[str]

# Auto-detect (default)
agent = Agent(name="TopicAgent", llm="gpt-4o-mini")
result = agent.chat("List 3 AI topics", output_pydantic=TopicList)

# Force native mode
agent = Agent(name="TopicAgent", llm="custom-model", native_structured_output=True)

# Disable native — use text injection fallback
agent = Agent(name="TopicAgent", llm="gpt-4o-mini", native_structured_output=False)
OptionTypeDefaultDescription
output_pydanticBaseModelNoneReturn a validated Pydantic object
output_jsonbool / BaseModelNoneReturn structured JSON; task fails if not parseable
native_structured_outputboolautoForce native response_format on/off

YAML Configuration

yaml
framework: praisonai
process: sequential
agents:
  analyst:
    instructions: Expert in data analysis.
    goal: Provide structured insights
    role: Data Analyst
    tasks:
      analysis_task:
        description: Analyse recent AI developments.
        expected_output: Structured analysis report.
        output_structure:
          type: pydantic
          model:
            title: str
            findings: str
            summary: str

Run with praisonai agents.yaml.


Best Practices

Keep models small and explicit

Define only the fields you need. Smaller schemas validate faster and reduce model confusion.

Prefer output_pydantic for Python apps

Use output_pydantic when you want a typed object; use output_json when you only need serialisable dicts.

Let native mode auto-detect

Leave native_structured_output unset unless you know your model needs forcing — the SDK picks the cleanest path.

Check task.result.success for structured tasks

pydantic is guaranteed populated on success. On failure, read task.result.error for the schema name plus validation error, and task.result.raw for the model text that failed.

Use max_retries for flaky models

Soft models may need a couple of retries to hit the schema. Combine output_pydantic with Task(max_retries=2) from Task Retry Policy.


Related