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