> ## Documentation Index
> Fetch the complete documentation index at: https://polos.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured outputs

Structured outputs let agents return data in a predefined format instead of natural language. This is essential for building reliable data extraction workflows, APIs, and integrations.

## Defining structured outputs

Use Pydantic models to define the output schema:

<CodeGroup>
  ```python Python theme={null}
  from polos import Agent
  from pydantic import BaseModel, Field
  from typing import Optional
  from enum import Enum

  class PersonName(BaseModel):
      first: str = Field(description="First name")
      last: str = Field(description="Last name")

  class Gender(str, Enum):
      MALE = "male"
      FEMALE = "female"
      UNKNOWN = "unknown"

  class Person(BaseModel):
      """Structured person information extracted from text."""
      name: PersonName = Field(description="Person's name with first and last name")
      age: int = Field(description="Age in years", ge=0, le=130)
      email: Optional[str] = Field(description="Email address", default=None)
      location: str = Field(description="City or location", default="")
      gender: Gender = Field(description="Gender of the person", default=Gender.UNKNOWN)

  person_extractor = Agent(
      id="person-extractor",
      provider="openai",
      model="gpt-4o",
      system_prompt="Extract information about the person mentioned in the text"
  )
  ```

  ```typescript TypeScript theme={null}
  import { defineAgent } from "@polos/sdk";
  import { openai } from "@ai-sdk/openai";
  import { z } from "zod";

  const personNameSchema = z.object({
    first: z.string().describe("First name"),
    last: z.string().describe("Last name"),
  });

  const genderEnum = z.enum(["male", "female", "unknown"]);

  const personSchema = z
    .object({
      name: personNameSchema.describe("Person's name with first and last name"),
      age: z.number().int().min(0).max(130).describe("Age in years"),
      email: z.string().nullable().default(null).describe("Email address"),
      location: z.string().default("").describe("City or location"),
      gender: genderEnum.default("unknown").describe("Gender of the person"),
    })
    .describe("Structured person information extracted from text.");

  const personExtractor = defineAgent({
    id: "person-extractor",
    model: openai("gpt-4o"),
    systemPrompt:
      "Extract information about the person mentioned in the text",
    outputSchema: personSchema,
  });
  ```
</CodeGroup>

## Using with agent.run()

The `result` field contains a fully validated Pydantic model instance:

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from polos import PolosClient

  async def main():
      client = PolosClient()
      response = await person_extractor.run(
          client,
          "Hi, I'm Alice Johnson, 28 years old, living in San Francisco. Email: alice@example.com"
      )

      person = response.result  # Person (Pydantic model instance)

      print("Structured Output:")
      print(f"  Name: {person.name.first} {person.name.last}")
      print(f"  Age: {person.age}")
      print(f"  Email: {person.email or '(not provided)'}")
      print(f"  Location: {person.location or '(not provided)'}")
      print(f"  Gender: {person.gender.value}")

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```typescript TypeScript theme={null}
  import { PolosClient } from "@polos/sdk";

  async function main() {
    const client = PolosClient.fromEnv();
    const response = await personExtractor.run(
      client,
      "Hi, I'm Alice Johnson, 28 years old, living in San Francisco. Email: alice@example.com"
    );

    const person = response.result; // Typed via Zod inference

    console.log("Structured Output:");
    console.log(`  Name: ${person.name.first} ${person.name.last}`);
    console.log(`  Age: ${person.age}`);
    console.log(`  Email: ${person.email ?? "(not provided)"}`);
    console.log(`  Location: ${person.location || "(not provided)"}`);
    console.log(`  Gender: ${person.gender}`);
  }

  main();
  ```
</CodeGroup>

**Output:**

```
Structured Output:
  Name: Alice Johnson
  Age: 28
  Email: alice@example.com
  Location: San Francisco
  Gender: unknown
```

The result is type-safe: you get autocomplete, validation, and runtime type checking.

## Using with agent.stream()

When streaming, the output is JSON that you can parse into your Pydantic model:

<CodeGroup>
  ```python Python theme={null}
  async def main():
      client = PolosClient()
      stream = await person_extractor.stream(
          client,
          "My name is Bob Smith, I'm 35, and I live in Austin, Texas."
      )

      print("Streaming structured output:")
      output = ""
      async for chunk in stream.text_chunks:
          output += chunk
          print(chunk, end="", flush=True)

      print("\n\nParsed structure:")
      person = Person.model_validate_json(output)
      print(f"  Name: {person.name.first} {person.name.last}")
      print(f"  Age: {person.age}")
      print(f"  Location: {person.location}")

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```typescript TypeScript theme={null}
  async function main() {
    const client = PolosClient.fromEnv();
    const stream = await personExtractor.stream(
      client,
      "My name is Bob Smith, I'm 35, and I live in Austin, Texas."
    );

    console.log("Streaming structured output:");
    let output = "";
    for await (const chunk of stream.textChunks) {
      output += chunk;
      process.stdout.write(chunk);
    }

    console.log("\n\nParsed structure:");
    const person = personSchema.parse(JSON.parse(output));
    console.log(`  Name: ${person.name.first} ${person.name.last}`);
    console.log(`  Age: ${person.age}`);
    console.log(`  Location: ${person.location}`);
  }

  main();
  ```
</CodeGroup>

**Output:**

```
Streaming structured output:
{"name": {"first": "Bob", "last": "Smith"}, "age": 35, "email": null, "location": "Austin, Texas", "gender": "unknown"}

Parsed structure:
  Name: Bob Smith
  Age: 35
  Location: Austin, Texas
```

## Complex nested structures

Structured outputs support nested models, lists, and enums:

<CodeGroup>
  ```python Python theme={null}
  from typing import List
  from pydantic import BaseModel, Field
  from polos import Agent, PolosClient

  class Address(BaseModel):
      street: str = Field(description="Street address", default="")
      city: str = Field(description="City", default="")
      country: str = Field(description="Country", default="")
      postal_code: Optional[str] = Field(description="Postal code", default=None)

  class PhoneNumber(BaseModel):
      type: str = Field(description="Type of phone number (can be mobile, work, home)", default="")
      number: str = Field(description="Phone number", default="")

  class Contact(BaseModel):
      name: str
      email: str
      phones: List[PhoneNumber] = []
      address: Optional[Address] = None
      notes: str = ""

  contact_extractor = Agent(
      id="contact-extractor",
      provider="anthropic",
      model="claude-sonnet-4-5",
      system_prompt="Extract contact information from text.",
      output_schema=Contact
  )

  client = PolosClient()
  response = await contact_extractor.run(
      client,
      """
      John Doe
      Email: john@example.com
      Mobile: +1-555-0123
      Work: +1-555-0199
      Address: 123 Main St, New York, NY 10001
      """
  )

  contact = response.result
  print(f"Name: {contact.name}")
  for phone in contact.phones:
      print(f"  {phone.type}: {phone.number}")
  print(f"Address: {contact.address}")
  ```

  ```typescript TypeScript theme={null}
  import { defineAgent, PolosClient } from "@polos/sdk";
  import { anthropic } from "@ai-sdk/anthropic";
  import { z } from "zod";

  const addressSchema = z.object({
    street: z.string().default("").describe("Street address"),
    city: z.string().default("").describe("City"),
    country: z.string().default("").describe("Country"),
    postalCode: z.string().nullable().default(null).describe("Postal code"),
  });

  const phoneNumberSchema = z.object({
    type: z
      .string()
      .default("")
      .describe("Type of phone number (can be mobile, work, home)"),
    number: z.string().default("").describe("Phone number"),
  });

  const contactSchema = z.object({
    name: z.string(),
    email: z.string(),
    phones: z.array(phoneNumberSchema).default([]),
    address: addressSchema.nullable().default(null),
    notes: z.string().default(""),
  });

  const contactExtractor = defineAgent({
    id: "contact-extractor",
    model: anthropic("claude-sonnet-4-5"),
    systemPrompt: "Extract contact information from text.",
    outputSchema: contactSchema,
  });

  const client = PolosClient.fromEnv();
  const response = await contactExtractor.run(
    client,
    `
      John Doe
      Email: john@example.com
      Mobile: +1-555-0123
      Work: +1-555-0199
      Address: 123 Main St, New York, NY 10001
    `
  );

  const contact = response.result;
  console.log(`Name: ${contact.name}`);
  for (const phone of contact.phones) {
    console.log(`  ${phone.type}: ${phone.number}`);
  }
  console.log(`Address: ${JSON.stringify(contact.address)}`);
  ```
</CodeGroup>

## Provider compatibility

Not all model providers support structured output.

* Some models don't support it at all
* Some models support structured outputs only when no tools are present

### Fallback behavior

When a model doesn't natively support structured outputs, Polos automatically:

1. Generates the natural language response
2. Makes an additional LLM call to structure the output according to your schema
3. Returns the validated Pydantic model

This ensures your code works consistently across all providers, though it may consume extra tokens for models without native support.

<CodeGroup>
  ```python Python theme={null}
  # This works even if the model doesn't natively support structured outputs
  # Polos will use an extra LLM call if needed
  response = await person_extractor.run("Extract info from: John, 30, NYC")
  person = response.result  # Always a validated Person instance
  ```

  ```typescript TypeScript theme={null}
  // This works even if the model doesn't natively support structured outputs
  // Polos will use an extra LLM call if needed
  const response = await personExtractor.run("Extract info from: John, 30, NYC");
  const person = response.result; // Always a validated person object
  ```
</CodeGroup>

## Use cases

### Data extraction

<CodeGroup>
  ```python Python theme={null}
  # Extract invoice details from text/images
  invoice_extractor = Agent(
      output_schema=Invoice,
      system_prompt="Extract invoice data including line items, totals, dates"
  )
  ```

  ```typescript TypeScript theme={null}
  // Extract invoice details from text/images
  const invoiceExtractor = defineAgent({
    outputSchema: invoiceSchema,
    systemPrompt: "Extract invoice data including line items, totals, dates",
  });
  ```
</CodeGroup>

### Form processing

<CodeGroup>
  ```python Python theme={null}
  # Convert natural language to structured form data
  form_filler = Agent(
      output_schema=ApplicationForm,
      system_prompt="Fill out application forms from user descriptions"
  )
  ```

  ```typescript TypeScript theme={null}
  // Convert natural language to structured form data
  const formFiller = defineAgent({
    outputSchema: applicationFormSchema,
    systemPrompt: "Fill out application forms from user descriptions",
  });
  ```
</CodeGroup>

### API responses

<CodeGroup>
  ```python Python theme={null}
  # Build type-safe APIs with guaranteed response structure
  api_handler = Agent(
      output_schema=APIResponse,
      system_prompt="Process requests and return structured API responses"
  )
  ```

  ```typescript TypeScript theme={null}
  // Build type-safe APIs with guaranteed response structure
  const apiHandler = defineAgent({
    outputSchema: apiResponseSchema,
    systemPrompt: "Process requests and return structured API responses",
  });
  ```
</CodeGroup>

### Content categorization

<CodeGroup>
  ```python Python theme={null}
  # Classify and tag content
  classifier = Agent(
      output_schema=ContentClassification,
      system_prompt="Categorize content with tags, sentiment, topics"
  )
  ```

  ```typescript TypeScript theme={null}
  // Classify and tag content
  const classifier = defineAgent({
    outputSchema: contentClassificationSchema,
    systemPrompt: "Categorize content with tags, sentiment, topics",
  });
  ```
</CodeGroup>
