# Structured outputs with OpenRouter, a complete guide with instructor

OpenRouter provides a unified API to access multiple LLM providers, allowing you to easily switch between different models. This guide shows you how to use Instructor with OpenRouter for type-safe, validated responses across various LLM providers.

To set Provider specific configuration on the `openai` client, make sure to use the `extra_body` kwarg.

## Quick Start

⚠️ **Important**: Make sure that the model you're using has support for `Tool Calling` and/or `Structured Outputs` in the [OpenRouter models listing](https://openrouter.ai/models)

Instructor works with OpenRouter through the OpenAI client, so you don't need to install anything extra beyond the base package.

## Simple User Example (Sync)

We support simple tool calling with this

```python
from openai import OpenAI
import instructor
from pydantic import BaseModel


class User(BaseModel):
    name: str
    age: int


client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    base_url="https://openrouter.ai/api/v1",
    async_client=False
)

resp = client.create(
    messages=[
        {
            "role": "user",
            "content": "Ivan is 28 years old",
        },
    ],
    response_model=User,
    extra_body={"provider": {"require_parameters": True}},
)

print(resp)
#> name='Ivan' age=20
```

## Simple User Example ( Async )

```python
import instructor
from pydantic import BaseModel
import asyncio


class User(BaseModel):
    name: str
    age: int


client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    async_client=True,
)


async def extract_user():
    user = await client.create(
        messages=[
            {"role": "user", "content": "Extract: Jason is 25 years old"},
        ],
        response_model=User,
        extra_body={"provider": {"require_parameters": True}},
    )
    return user


# Run async function
user = asyncio.run(extract_user())
print(user)
```

## Nested Object Example ( Sync )

```python
from pydantic import BaseModel
from openai import OpenAI
import instructor
from pydantic import BaseModel


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


class User(BaseModel):
    name: str
    age: int
    addresses: list[Address]


# Initialize with API key
# Initialize client with base URL
client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    base_url="https://openrouter.ai/api/v1",
    async_client=False
)

# Create structured output with nested objects
user = client.create(
    messages=[
        {
            "role": "user",
            "content": """
            Extract: Jason is 25 years old.
            He lives at 123 Main St, New York, USA
            and has a summer house at 456 Beach Rd, Miami, USA
        """,
        },
    ],
    extra_body={"provider": {"require_parameters": True}},
    response_model=User,
)

print(user)
#> name='Jason' age=25 addresses=[Address(street='123 Main St', city='New York', country='USA'), Address(street='456 Beach Rd', city='Miami', country='USA')]
```

## Structured Outputs (Sync)

⚠️ **Important**: Check that your chosen model supports `Structured Outputs` in the [OpenRouter models listing](https://openrouter.ai/models). Structured Outputs is a subset of Tool Calling that constrains the model's output to match your schema in order to produce valid JSON Schema.

Instructor also supports Structured Outputs with OpenRouter as documented in their API [here](https://openrouter.ai/docs/features/structured-outputs). Note that the following User model will throw an error if we use the OpenAI GPT-4o model like `openai/gpt-4o-2024-11-20` because OpenAI does not support using a regex pattern as part of their structured output schema.

```python
from pydantic import BaseModel, Field
from openai import OpenAI
import instructor


class User(BaseModel):
    name: str
    age: int
    phone_number: str = Field(
        pattern=r"^\+?1?\s*\(?(\d{3})\)?[-.\s]*(\d{3})[-.\s]*(\d{4})$"
    )


# Initialize with API key
# Initialize client with base URL
client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    base_url="https://openrouter.ai/api/v1",
    async_client=False
)

# Create structured output with nested objects
user = client.create(
    messages=[
        {
            "role": "user",
            "content": """
            Extract: Jason is 25 years old and his number is 1-212-456-7890
        """,
        },
    ],
    response_model=User,
    extra_body={"provider": {"require_parameters": True}},
)

print(user)
# > name='Jason' age=25 phone_number='+1 (212) 456-7890'
```

## JSON Mode

In the event that your model doesn't support tool calling, you will see the following error when you try to use `mode.TOOLS`

> instructor.exceptions.InstructorRetryException: Error code: 404 - {'error': {'message': 'No endpoints found that support tool use. To learn more about provider routing, visit: <https://openrouter.ai/docs/provider-routing>', 'code': 404}}

In this case, we recommend using the `JSON` mode instead as seen below.

```python
from pydantic import BaseModel, Field
from openai import OpenAI
import instructor


class User(BaseModel):
    name: str
    age: int
    phone_number: str = Field(
        pattern=r"^\+?1?\s*\(?(\d{3})\)?[-.\s]*(\d{3})[-.\s]*(\d{4})$"
    )


# Initialize with API key
# Initialize client with base URL
client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    base_url="https://openrouter.ai/api/v1",
    async_client=False
)

# Create structured output with nested objects
user = client.create(
    messages=[
        {
            "role": "user",
            "content": """
            Extract: Jason is 25 years old and his number is 1-212-456-7890
        """,
        },
    ],
    response_model=User,
)

print(user)
```

## Streaming

You can also use streaming with as seen below using the `create_partial` method. While we're using JSON mode here, this should work with tool calling and structured outputs too.

```python
from pydantic import BaseModel, Field
from openai import OpenAI
import instructor


class User(BaseModel):
    name: str
    age: int


# Initialize with API key
# Initialize client with base URL
client = instructor.from_provider(
    "openrouter/google/gemini-2.0-flash-lite-001",
    base_url="https://openrouter.ai/api/v1",
)

# Create structured output with nested objects
user = client.create_partial(
    messages=[
        {
            "role": "user",
            "content": """
            Extract: Jason is 25 years old and his number is 1-212-456-7890
        """,
        },
    ],
    response_model=User,
)

for chunk in user:
    print(chunk)
    # > name=None age=None
    # > name='Jason' age=None
    # > name='Jason' age=25
```
