OpenRouter

OpenRouter

OpenRouter는 여러 제공자의 모델을 하나의 API로 라우팅해주는 게이트웨이예요. PydanticAI에서는 OpenRouterModel을 쓰면 하나의 API 키로 다양한 모델을 오가며 쓸 수 있어요. 앱 기여도 추적, 모델 설정, 프롬프트 캐싱, 웹 검색 같은 기능도 지원하니 하나씩 살펴볼게요.

출처: 공식문서

설치

OpenRouterModel을 쓰려면 pydantic-ai를 설치하거나 pydantic-ai-slimopenrouter 옵션 그룹과 함께 설치해야 해요.

Terminal

pip install "pydantic-ai-slim[openrouter]"

Terminal

uv add "pydantic-ai-slim[openrouter]"

설정

OpenRouter를 쓰려면 먼저 openrouter.ai/keys에서 API 키를 만들어요.

OPENROUTER_API_KEY 환경 변수를 설정하고 OpenRouterProvider를 이름으로 쓸 수 있어요.

from pydantic_ai import Agent

agent = Agent('openrouter:anthropic/claude-sonnet-4.6')
...

또는 모델과 provider를 직접 초기화할 수도 있어요.

from pydantic_ai import Agent
from pydantic_ai.models.openrouter import OpenRouterModel
from pydantic_ai.providers.openrouter import OpenRouterProvider

model = OpenRouterModel(
    'anthropic/claude-sonnet-4.6',
    provider=OpenRouterProvider(api_key='your-openrouter-api-key'),
)
agent = Agent(model)
...

앱 기여도 (App Attribution)

OpenRouter는 앱 기여도 기능으로 공개 순위와 분석에서 당신의 애플리케이션을 추적하게 해줘요.

provider를 초기화할 때 app_urlapp_title을 넘기면 앱 기여도를 켤 수 있어요. 둘 다 생략하면 OPENROUTER_APP_URLOPENROUTER_APP_TITLE 환경 변수로 폴백해요.

참고: 환경 변수 폴백은 provider가 스스로 만든 클라이언트에만 적용돼요. 직접 만든 openai_client를 넘기면 그대로 재사용되므로, 그 클라이언트에 HTTP-RefererX-Title 헤더를 직접 설정하세요.

from pydantic_ai.providers.openrouter import OpenRouterProvider

provider=OpenRouterProvider(
    api_key='your-openrouter-api-key',
    app_url='https://your-app.com',
    app_title='Your App',
),
...

모델 설정

OpenRouterModelSettings로 모델 동작을 커스터마이즈할 수 있어요.

from pydantic_ai import Agent
from pydantic_ai.models.openrouter import OpenRouterModel, OpenRouterModelSettings

settings = OpenRouterModelSettings(
    openrouter_reasoning={
        'effort': 'high',
    },
    openrouter_usage={
        'include': True,
    }
)
model = OpenRouterModel('openai/gpt-5.2')
agent = Agent(model, model_settings=settings)
...

Eager Input Streaming

OpenRouter를 통한 Anthropic 모델에서는 대용량 입력으로 도구 호출의 지연 시간을 줄이기 위해 eager input streaming을 켤 수 있어요. AnthropicModelSettingsanthropic_eager_input_streaming을 설정하세요.

from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModelSettings
from pydantic_ai.models.openrouter import OpenRouterModel

model = OpenRouterModel('anthropic/claude-sonnet-4-5')
settings = AnthropicModelSettings(anthropic_eager_input_streaming=True)
agent = Agent(model, model_settings=settings)
...

강제 도구 선택

PydanticAI는 OpenRouter를 통해 라우팅되는 모든 anthropic/ 모델에서 강제된 tool_choicethinking과 호환되지 않는 것으로 취급해요. PydanticAI는 adaptive thinking이 강제를 받아들이는 직접 Anthropic API보다 더 보수적이에요. OpenRouter 경로는 검증되지 않았고 조용히 실패하거든요. Anthropic은 호환되지 않는 조합을 바로 거부하는 반면, OpenRouter는 요청에서 reasoning 필드를 조용히 버려서 응답이 thinking 없이 돌아오죠. #7283을 보세요. anthropic/ 모델에서 thinking이 켜진 상태에선:

  • 명시적 tool_choice='required'(또는 도구 이름 목록)는 UserError를 일으켜요. thinking을 끄거나 tool_choice='auto'를 쓰세요.
  • PydanticAI가 당신을 대신해 결정한 required 선택(예: output tool에서)은 'auto'로 부드럽게 폴백돼서 thinking이 보존돼요. 결정된 선택이 단일 도구를 지명했다면 tool_choice'auto'로 남은 채 사용 가능한 도구 목록이 그 도구로 필터링돼요. 그래서 모델이 그것을 호출하는 대신 텍스트로 답할 수 있어요. output tool이 필요할 때 PydanticAI는 도구를 호출하라는 프롬프트로 재시도해요.

프롬프트 캐싱

OpenRouter는 그것을 구현하는 다운스트림 provider를 위해 프롬프트 캐싱을 지원해요. PydanticAI의 OpenRouter 캐시 설정은 Anthropic·Gemini 모델의 명시적 cache_control 중단점을 제어해요.

  1. 시스템 지시 캐시: OpenRouterModelSettings.openrouter_cache_instructionsTrue로 설정하거나 '5m'/'1h'를 직접 지정
  2. 마지막 메시지 캐시: OpenRouterModelSettings.openrouter_cache_messagesTrue로 설정해서 대화의 마지막 메시지를 자동 캐시
  3. 도구 정의 캐시: OpenRouterModelSettings.openrouter_cache_tool_definitionsTrue로 설정하거나 '5m'/'1h'를 직접 지정
  4. CachePoint로 세밀하게 제어: CachePoint를 사용자 메시지에 넣어 그 앞의 모든 것을 캐시

Provider 차이:

  • Anthropic 모델은 시스템 지시와 메시지 콘텐츠 모두에 접두사 기반 캐싱을 지원해요. TTL 값('5m', '1h')은 provider에 그대로 전달돼요.
  • Gemini 모델은 시스템 지시와 일반 메시지 콘텐츠에 캐싱을 지원하지만, OpenRouter는 Gemini 캐싱에 일반 메시지 콘텐츠에 걸쳐 마지막 중단점만 씁니다. 그 마지막 메시지 경계를 의도한 것이라면 openrouter_cache_messagesCachePoint를, 완전히 정적인 시스템 컨텍스트에만 openrouter_cache_instructions를 쓰세요. TTL 값은 Gemini에서 무시돼요. 캐시된 Gemini systemInstruction 콘텐츠는 변경할 수 없으므로, 동적 프롬프트 세그먼트는 캐시된 시스템 지시 뒤가 아니라 이후 사용자 메시지에 넣어야 해요.
  • OpenAI GPT-5.6 모델은 cache_control이 아니라 OpenAI의 prompt_cache_optionsprompt_cache_breakpoint 프로토콜을 써요. 아래 OpenAI GPT-5.6 명시적 캐싱을 보세요.
  • 최소 토큰 임계값이 적용돼요. 현재 provider별 값은 OpenRouter의 최소 토큰 요구사항을 보세요.

OpenAI GPT-5.6 명시적 캐싱

OpenRouterModel은 현재 CachePoint를 OpenAI의 중단점 프로토콜로 번역하지 않아요(OpenRouter의 OpenAI 모델은 여전히 자동 캐싱을 받아요). GPT-5.6 명시적 중단점을 위해선 OpenAIResponsesModel(또는 OpenAIChatModel)을 OpenRouterProvider와 결합하세요.

from pydantic_ai import Agent, CachePoint
from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings
from pydantic_ai.providers.openrouter import OpenRouterProvider

model = OpenAIResponsesModel(
    'openai/gpt-5.6-sol',
    provider=OpenRouterProvider(api_key='your-openrouter-api-key'),
)
settings = OpenAIResponsesModelSettings(
    openai_prompt_cache_key='product-docs-v1',
    openai_prompt_cache_options={'mode': 'explicit', 'ttl': '30m'},
    # OpenRouter also offers Azure routes for GPT-5.6, where explicit caching is not documented.
    extra_body={'provider': {'only': ['openai']}},
)
agent = Agent(model, model_settings=settings)

result = agent.run_sync([
    'Long-lived reference material...',
    CachePoint(),
    'Answer using the reference material.',
])

OpenRouter Responses API는 OpenAI와 같은 요청 전체 TTL과 usage 필드를 써요. 다운스트림 provider를 openai로 제한하면 명시적 캐시 요청을 그 필드가 문서화되지 않은 엔드포인트로 라우팅하는 걸 피할 수 있어요. OpenRouter는 현재 텍스트 블록에서만 명시적 중단점을 문서화하므로 텍스트 콘텐츠 뒤에 CachePoint 마커를 놓으세요.

모델 설정을 통한 캐싱

OpenRouterModelSettings를 써서 시스템 지시, 대화의 마지막 메시지, 도구 정의에 명시적 캐싱을 켤 수 있어요.

from pydantic_ai import Agent, RunContext
from pydantic_ai.models.openrouter import OpenRouterModel, OpenRouterModelSettings

model = OpenRouterModel('anthropic/claude-sonnet-4.6')
agent = Agent(
    model,
    instructions='You are a specialized assistant with deep domain knowledge...',
    model_settings=OpenRouterModelSettings(
        openrouter_cache_instructions=True,  # Cache system instructions (broadly supported)
        openrouter_cache_messages=True,  # Cache the last message (best with Anthropic)
        openrouter_cache_tool_definitions=True,  # Cache tool definitions (Anthropic only)
    ),
)


@agent.tool
def search_docs(ctx: RunContext, query: str) -> str:
    """Search documentation."""
    return f'Results for {query}'
...

각 설정은 True나 명시적 '5m'/'1h' TTL 값을 받아요. True는 Anthropic 모델에 Anthropic의 기본 '5m' TTL을 보내고, Gemini는 TTL 값을 무시하고 캐시 수명을 스스로 관리해요. 초기 쓰기에서 result.usage.cache_write_tokens, 재사용에서 result.usage.cache_read_tokens를 확인하세요. message_history=result.all_messages()로 후속 호출을 할 때도요.

OpenRouter는 프롬프트 캐시 요청 후 provider sticky routing을 써서 캐시 지역성을 개선해요. 더 엄격한 provider 제어나 폴백 비활성화가 필요한 캐시 민감 워크플로우에서는 openrouter_provider도 설정하세요. 예: {'order': ['anthropic'], 'allow_fallbacks': False}.

CachePoint로 세밀하게 제어하기

CachePoint 마커를 써서 캐시 경계가 정확히 어디에 놓일지 제어할 수 있어요.

from pydantic_ai import Agent, CachePoint
from pydantic_ai.models.openrouter import OpenRouterModel

model = OpenRouterModel('anthropic/claude-sonnet-4.6')
agent = Agent(model)

prompt = [
    'Long reference document or context to cache...',
    CachePoint(),  # Cache everything before this point
    'Now answer my question about the context above',
]
...

프롬프트 리스트를 agent.run_sync(prompt)로 넘기세요. CachePoint() 마커 앞의 모든 것이 캐시돼요. 여러 마커를 놓아 캐시 경계를 세밀하게 제어할 수 있어요.

Anthropic 캐시 중단점 순서: Anthropic은 캐시 중단점을 고정된 순서로 처리해요. 도구 정의, 그다음 시스템 지시, 그다음 메시지예요. 그리고 그 순서에서 '5m' 중단점 뒤에 나타나는 '1h' 중단점은 거부해요. CachePoint 마커나 Anthropic 모델의 캐시 설정에서 TTL을 섞을 때는 더 긴('1h') 중단점을 더 짧은('5m') 것보다 앞에 놓으세요. Anthropic은 요청당 명시적 중단점을 최대 4개까지 허용하고, 초과 중단점은 요청이 보내지기 전에(가장 오래된 것부터) 버려져요.

웹 검색

OpenRouter는 Beta server tool을 통해 웹 검색을 지원해요. WebSearchTool로 켜요. 모델이 검색할지 결정하고, 요청당 0회 또는 여러 번 검색할 수 있어요.

Pydantic AI v2.30.0 이전에는 WebSearchTool이 OpenRouter의 web 플러그인을 켜서 질문이 웹을 필요로 하든 말든 매 요청 검색하고 각각에 고정 요금을 청구했어요. 그 항상 켜진 grounding을 원한다면, OpenRouter의 플러그인은 deprecated지만 직접 넘겨서 여전히 쓸 수 있어요.

from pydantic_ai import Agent
from pydantic_ai.models.openrouter import OpenRouterModel, OpenRouterModelSettings

model = OpenRouterModel('openai/gpt-5.2')
settings = OpenRouterModelSettings(extra_body={'plugins': [{'id': 'web'}]})
agent = Agent(model, model_settings=settings)
result = agent.run_sync('What is the latest news in AI?')

웹 검색 파라미터

WebSearchTool로 검색 컨텍스트, 대략적 사용자 위치, 도메인 필터, 검색 횟수 제한을 구성할 수 있어요.

from pydantic_ai import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.models.openrouter import OpenRouterModel
from pydantic_ai.native_tools import WebSearchTool

tool = WebSearchTool(
    search_context_size='high',
    user_location={'city': 'London', 'country': 'GB'},
    allowed_domains=['pydantic.dev'],
    max_uses=1,
)
model = OpenRouterModel('openai/gpt-4.1')
agent = Agent(
    model,
    capabilities=[NativeTool(tool)],
)
result = agent.run_sync('What is the latest news in AI?')

PydanticAI는 요청별 웹 검색 횟수를 ModelResponse.provider_details'server_tool_use' 아래 표면화해요.

검색 소스

OpenRouter가 다운스트림 provider의 자체 검색에 위임하지 않고 스스로 검색을 실행하면, 사용한 소스를 url_citation 주석으로 메시지에 붙여요. PydanticAI는 그것들을 ModelResponse.provider_details['annotations'] 아래 그대로 표면화해요. 각각은 결과의 url, title, 그리고 모델에 주어진 발췌를 담아요.

from pydantic_ai import Agent
from pydantic_ai.capabilities import WebSearch
from pydantic_ai.models.openrouter import OpenRouterModel

agent = Agent(OpenRouterModel('deepseek/deepseek-chat'), capabilities=[WebSearch()])
result = agent.run_sync('What is the latest news in AI?')

annotations = (result.response.provider_details or {}).get('annotations', [])
for annotation in annotations:
    if annotation['type'] == 'url_citation':
        print(annotation['url_citation']['url'])

네이티브가 아닌 검색만 소스를 보고해요. 다운스트림 provider가 검색을 네이티브로 실행하는 모델(OpenAI와 Anthropic 포함)은 주석을 전혀 반환하지 않으므로 그 모델의 provider_details에는 annotations 항목이 없어요. 일반 OpenRouter provider 상세는 여전히 남아요. OpenRouter가 어떤 엔진을 고르는지는 현재 Pydantic AI에서 구성할 수 없어요.

엔진별 파라미터: 기록된 요청은 OpenRouter가 이 파라미터 이름을 받아들인다는 것만 검증해요. 아래의 엔진별 효과는 이 프로젝트에 기록된 응답이 아니라 OpenRouter의 Beta server-tool 문서에서 온 거예요. 네이티브 provider 검색은 search_context_size를 무시하고, user_location은 네이티브 검색에서만 동작하며, 도메인 필터 지원은 다양해요(네이티브 OpenAI는 excluded_domains를 무시해요). server tool은 모델에 사용 가능할 때 0회 또는 여러 번 검색할 수 있어요. max_uses는 OpenRouter가 네이티브가 아닌 검색 엔진이나 Anthropic의 네이티브 검색을 쓸 때 요청을 제한해요. 이 예시의 OpenAI 모델을 포함한 다른 네이티브 provider는 그것을 무시해요. OpenRouter는 WebSearchTool.external_web_access를 지원하지 않아요.

더 알아보기 (Learn more)