跳到内容

使用 OpenRouter 的结构化输出,附带 Instructor 的完整指南

OpenRouter 提供了一个统一的 API,用于访问多个 LLM 提供商,让您可以在不同模型之间轻松切换。本指南将向您展示如何将 Instructor 与 OpenRouter 结合使用,以实现跨各种 LLM 提供商的类型安全、已验证响应。

要在 openai 客户端上设置特定于提供商的配置,请务必使用 extra_body kwarg。

快速入门

⚠️ 重要:请确保您使用的模型在 OpenRouter 模型列表 中支持 Tool Calling 和/或 Structured Outputs

Instructor 通过 OpenAI 客户端与 OpenRouter 协作,因此您无需安装基础软件包之外的任何额外内容。

简单用户示例 (同步)

我们支持使用此进行简单的工具调用

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

简单用户示例 (异步)

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)

嵌套对象示例 (同步)

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')]

结构化输出 (同步)

⚠️ 重要:请检查您选择的模型是否在 OpenRouter 模型列表 中支持 Structured Outputs。结构化输出是工具调用的一个子集,它限制模型的输出以匹配您的 schema,从而生成有效的 JSON Schema。

Instructor 也支持 OpenRouter 的结构化输出,如其 API 文档 此处 所述。请注意,如果我们使用 OpenAI 的 GPT-4o 模型(例如 openai/gpt-4o-2024-11-20),以下 User 模型将抛出错误,因为 OpenAI 不支持在其结构化输出 schema 中使用 regex 模式。

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.TOOLS 时,您将看到以下错误:

instructor.exceptions.InstructorRetryException: Error code: 404 - {'error': {'message': '未找到支持工具调用的端点。要了解更多关于提供商路由的信息,请访问:https://openrouter.ai/docs/provider-routing', 'code': 404}}

在这种情况下,我们建议改为使用 JSON 模式,如下所示。

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)

流式传输

您还可以使用流式传输,如下所示,使用 create_partial 方法。虽然我们这里使用的是 JSON 模式,但这应该也适用于工具调用和结构化输出。

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