97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
271 lines
7.0 KiB
Markdown
271 lines
7.0 KiB
Markdown
---
|
|
title: "Structured outputs with OpenRouter, a complete guide with instructor"
|
|
description: "Learn how to use Instructor with OpenRouter to access multiple LLM providers through a unified API. Get type-safe, structured outputs from various models including Qwen, Gemini, Mistral, and Cohere."
|
|
---
|
|
|
|
# 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
|
|
```
|