Skip to main content
Schemas define the structure of data flowing into and out of your agents. They provide type safety, validation, and help the LLM understand what format to use.

Why Use Schemas?

Without schemas, agents return unstructured strings. With schemas:
  • Type safety: Get typed objects instead of strings
  • Validation: Catch malformed outputs early
  • LLM guidance: The model knows exactly what format to produce
  • IDE support: Autocomplete and type checking

Output Schemas

Define what your agent should return:
from pydantic import BaseModel, Field
from opper_agents import Agent

class AnalysisResult(BaseModel):
    summary: str = Field(description="Brief summary of the analysis")
    sentiment: str = Field(description="Overall sentiment: positive, negative, or neutral")
    confidence: float = Field(description="Confidence score from 0 to 1")
    key_points: list[str] = Field(description="Main points identified")

agent = Agent(
    name="AnalysisAgent",
    output_schema=AnalysisResult
)

result = await agent.process("Analyze this review: Great product, fast shipping!")
print(result.sentiment)     # "positive"
print(result.confidence)    # 0.95
print(result.key_points)    # ["Product quality praised", "Shipping speed noted"]

Input Schemas

Define the expected input structure:
from pydantic import BaseModel, Field

class SearchQuery(BaseModel):
    query: str = Field(description="The search term")
    max_results: int = Field(default=10, description="Maximum results to return")
    filters: dict = Field(default={}, description="Optional filters")

agent = Agent(
    name="SearchAgent",
    input_schema=SearchQuery,
    tools=[search_tool]
)

# Input is validated against the schema
result = await agent.process({
    "query": "python tutorials",
    "max_results": 5
})

Schema Field Types

Basic Types

from pydantic import BaseModel

class BasicTypes(BaseModel):
    text: str
    number: int
    decimal: float
    flag: bool
    items: list[str]
    mapping: dict[str, int]

Optional Fields

from pydantic import BaseModel
from typing import Optional

class WithOptional(BaseModel):
    required_field: str
    optional_field: Optional[str] = None
    with_default: str = "default value"

Enums and Literals

from pydantic import BaseModel
from typing import Literal
from enum import Enum

class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class Task(BaseModel):
    title: str
    priority: Priority
    status: Literal["pending", "in_progress", "done"]

Nested Objects

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str

class Person(BaseModel):
    name: str
    email: str
    address: Address

Field Descriptions

Always add descriptions to help the LLM understand the expected values:
from pydantic import BaseModel, Field

class ArticleSummary(BaseModel):
    title: str = Field(
        description="A concise title for the article, max 100 characters"
    )
    summary: str = Field(
        description="2-3 sentence summary of the main points"
    )
    category: str = Field(
        description="One of: technology, business, science, health, other"
    )
    reading_time: int = Field(
        description="Estimated reading time in minutes"
    )

Validation

Schemas validate outputs automatically. Invalid outputs raise errors:
from pydantic import BaseModel, Field, field_validator

class Score(BaseModel):
    value: float = Field(description="Score from 0 to 100")

    @field_validator('value')
    @classmethod
    def validate_range(cls, v):
        if not 0 <= v <= 100:
            raise ValueError("Score must be between 0 and 100")
        return v

Complex Example

A complete example with nested schemas:
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime

class Author(BaseModel):
    name: str
    email: Optional[str] = None

class Reference(BaseModel):
    title: str
    url: str
    accessed_date: str

class ResearchReport(BaseModel):
    title: str = Field(description="Report title")
    authors: list[Author] = Field(description="List of authors")
    abstract: str = Field(description="Brief abstract, 100-200 words")
    sections: list[str] = Field(description="Main section titles")
    conclusions: list[str] = Field(description="Key conclusions")
    references: list[Reference] = Field(description="Cited sources")
    confidence_score: float = Field(description="Confidence in findings, 0-1")

agent = Agent(
    name="ResearchAgent",
    output_schema=ResearchReport,
    tools=[search, analyze]
)

Best Practices

  1. Always add descriptions: Help the LLM understand what each field should contain
  2. Use appropriate types: Be specific (int vs float, list[str] vs list)
  3. Set sensible defaults: For optional fields that have common values
  4. Validate ranges: Add constraints for numeric fields
  5. Keep schemas focused: Don’t try to capture everything in one schema

Next Steps