Skip to content

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

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

from openai import OpenAI
import os
import instructor
from pydantic import BaseModel


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


client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)

resp = client.chat.completions.create(
    model="google/gemini-2.0-flash-lite-001",
    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 )

from openai import AsyncOpenAI
import os
import instructor
from pydantic import BaseModel
import asyncio


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


client = AsyncOpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)


client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)


async def extract_user():
    user = await client.chat.completions.create(
        model="google/gemini-2.0-flash-lite-001",
        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 )

from pydantic import BaseModel
import os
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
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

# Enable instructor patches for OpenAI client
client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)

# Create structured output with nested objects
user = client.chat.completions.create(
    model="anthropic/claude-3.7-sonnet",
    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. 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. 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.

from pydantic import BaseModel, Field
import os
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
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

# Enable instructor patches for OpenAI client
client = instructor.from_openai(
    client, mode=instructor.Mode.OPENROUTER_STRUCTURED_OUTPUTS
)

# Create structured output with nested objects
user = client.chat.completions.create(
    model="google/gemini-2.0-flash-001",
    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.

from pydantic import BaseModel, Field
import os
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
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

# Enable instructor patches for OpenAI client
client = instructor.from_openai(client, mode=instructor.Mode.JSON)

# Create structured output with nested objects
user = client.chat.completions.create(
    model="openai/chatgpt-4o-latest",
    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.

from pydantic import BaseModel, Field
import os
from openai import OpenAI
import instructor


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


# Initialize with API key
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

# Enable instructor patches for OpenAI client
client = instructor.from_openai(client, mode=instructor.Mode.JSON)

# Create structured output with nested objects
user = client.chat.completions.create_partial(
    model="openai/chatgpt-4o-latest",
    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