OpenRouter 모델
OpenRouter 모델
OpenRouter는 여러 모델 제공자(프로바이더)를 하나의 API로 묶어주는 라우팅 서비스예요. pydantic-ai에서 OpenRouterModel을 쓰면 Anthropic, OpenAI, Gemini 등 다양한 모델을 OpenRouter라는 단일 진입점을 통해 호출할 수 있어요. 이 문서는 설치부터 캐싱, 웹 검색까지 OpenRouter 모델 사용법을 하나씩 설명해요.
출처: 문서
본문
설치하기
OpenRouterModel을 쓰려면 pydantic-ai를 설치하거나, pydantic-ai-slim을 openrouter 옵션 그룹과 함께 설치하면 돼요.
pip install "pydantic-ai-slim[openrouter]"
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')
...
아니면 모델과 프로바이더를 직접 초기화해도 돼요:
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에는 앱 어트리뷰션 기능이 있어서, 애플리케이션이 공개 랭킹과 분석에 어떻게 기록되는지 추적할 수 있어요.
프로바이더를 초기화할 때 app_url과 app_title을 전달하면 앱 어트리뷰션을 활성화할 수 있어요. 둘 다 생략하면 OPENROUTER_APP_URL과 OPENROUTER_APP_TITLE 환경 변수로 대체돼요.
참고
환경 변수 폴백은 프로바이더가 스스로 만든 클라이언트에만 적용돼요. 직접 만든 openai_client를 전달하면 그대로 재사용되므로, HTTP-Referer와 X-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 입력 스트리밍
OpenRouter를 통한 Anthropic 모델에서는 eager 입력 스트리밍을 활성화해서 큰 입력이 있는 툴 호출의 지연 시간을 줄일 수 있어요. AnthropicModelSettings에서 anthropic_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)
...
강제 툴 선택(Forced tool choice)
Pydantic AI는 OpenRouter를 통해 라우팅되는 모든 anthropic/ 모델에서 강제된 tool_choice를 thinking과 호환되지 않는 것으로 취급해요. Pydantic AI는 어댑티브 thinking이 강제를 허용하는 직접 Anthropic API보다 더 보수적이에요. OpenRouter 라우트는 검증되지 않았고, 소리 내지 않고 조용히 실패하기 때문이에요. Anthropic이 호환되지 않는 조합을 아예 거부하는 반면, OpenRouter는 요청에서 reasoning 필드를 조용히 버려서 response에 thinking이 전혀 없이 돌아와요. #7283 참고하세요. anthropic/ 모델에서 thinking을 활성화하면:
- 명시적인
tool_choice='required'(또는 툴 이름 리스트)는UserError를 발생시켜요. thinking을 비활성화하거나tool_choice='auto'를 사용하세요. - Pydantic AI가 (예: 출력 툴에서) 대신 해석한
required선택은'auto'로 부드럽게 폴백되어 thinking이 보존돼요. 해석된 선택이 단일 툴을 지명했다면,tool_choice가'auto'인 채로 사용 가능한 툴 목록을 그 툴로 필터링해요. 따라서 모델이 툴을 호출하는 대신 텍스트로 답할 수 있어요. 출력 툴이 필요할 때는 Pydantic AI가 툴을 호출하라는 프롬프트로 재시도해요.
프롬프트 캐싱
OpenRouter는 이를 구현하는 다운스트림 프로바이더를 위한 프롬프트 캐싱을 지원해요. Pydantic AI의 OpenRouter 캐시 설정은 Anthropic과 Gemini 모델의 명시적 cache_control 중단점을 제어해요:
- 시스템 지시문 캐싱:
OpenRouterModelSettings.openrouter_cache_instructions를True로 설정하거나'5m'/'1h'를 직접 지정하세요. - 마지막 메시지 캐싱:
OpenRouterModelSettings.openrouter_cache_messages를True로 설정하면 대화의 마지막 메시지를 자동으로 캐싱해요. - 툴 정의 캐싱:
OpenRouterModelSettings.openrouter_cache_tool_definitions를True로 설정하거나'5m'/'1h'를 직접 지정하세요. CachePoint를 통한 세밀한 제어: 사용자 메시지에CachePoint마커를 삽입하면 그 이전의 모든 것을 캐싱해요.
프로바이더 차이점
- Anthropic 모델은 시스템 지시문과 메시지 콘텐츠 모두에 프리픽스 기반 캐싱을 지원해요. TTL 값(
'5m','1h')은 프로바이더에 그대로 전달돼요. - Gemini 모델은 시스템 지시문과 일반 메시지 콘텐츠의 캐싱을 지원하지만, OpenRouter는 Gemini 캐싱에 일반 메시지 콘텐츠 전체에서 마지막 중단점만 사용해요. 최종 메시지 경계가 의도적일 때는
openrouter_cache_messages나CachePoint를 사용하고, 완전히 정적인 시스템 컨텍스트일 때만openrouter_cache_instructions를 사용하세요. TTL 값은 Gemini에서 무시돼요. 캐싱된 GeminisystemInstruction콘텐츠는 불변이므로, 동적 프롬프트 세그먼트는 캐싱된 시스템 지시문 뒤가 아닌 이후의 사용자 메시지에 넣어야 해요. - OpenAI GPT-5.6 모델은
cache_control이 아니라 OpenAI의prompt_cache_options와prompt_cache_breakpoint프로토콜을 사용해요. 아래 OpenAI GPT-5.6 명시적 캐싱을 참고하세요. - 최소 토큰 임계값이 적용돼요. 현재 프로바이더별 값은 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과 사용량 필드를 사용해요. 다운스트림 프로바이더를 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는 프롬프트 캐싱된 요청 후 프로바이더 스티키 라우팅을 사용해 캐시 지역성을 개선해요. 더 엄격한 프로바이더 제어나 비활성화된 폴백이 필요한 캐시 민감 워크플로우라면 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 서버 툴을 통한 웹 검색을 지원해요. WebSearchTool로 활성화하세요. 모델이 검색 여부를 결정하며 요청에 대해 0회 또는 여러 번 검색할 수 있어요.
Pydantic AI v2.30.0 이전에는 WebSearchTool이 OpenRouter의 web 플러그인을 활성화해서, 질문이 웹을 필요로 하는지와 관계없이 매 요청 검색하고 각각 고정 요금을 청구했어요. 그 항상 켜진 그라운딩을 원한다면 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?')
Pydantic AI는 요청별 웹 검색 횟수를 ModelResponse.provider_details의 'server_tool_use'로 노출해요.
검색 소스
OpenRouter가 다운스트림 프로바이더 자신의 검색에 위임하지 않고 검색을 직접 실행할 때는, 사용한 소스를 메시지에 url_citation 어노테이션으로 첨부해요. Pydantic AI는 이를 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'])
네이티브 검색만 소스를 보고한다
다운스트림 프로바이더가 네이티브로 검색을 실행하는 모델(OpenAI와 Anthropic 포함)은 어노테이션을 전혀 반환하지 않아요. 따라서 그런 모델의 provider_details에는 annotations 항목이 없어요. 일반적인 OpenRouter 프로바이더 상세는 여전히 사용 가능해요. OpenRouter가 어떤 엔진을 고르는지는 현재 Pydantic AI에서 구성할 수 없어요.
엔진별 매개변수
기록된 요청은 OpenRouter가 이 매개변수 이름들을 받아들인다는 것만 검증해요. 아래 엔진별 효과는 이 프로젝트에서 기록된 응답이 아니라 OpenRouter의 Beta 서버 툴 문서에서 온 것이에요. 네이티브 프로바이더 검색은 search_context_size를 무시하고, user_location은 네이티브 검색에서만 동작하며, 도메인 필터 지원은 다양해요(네이티브 OpenAI는 excluded_domains를 무시해요). 서버 툴은 모델에 사용 가능할 때 0회 또는 여러 번 검색할 수 있어요. max_uses는 OpenRouter가 비네이티브 검색 엔진이나 Anthropic의 네이티브 검색을 사용할 때 요청을 제한해요. 이 예제의 OpenAI 모델을 포함한 다른 네이티브 프로바이더는 이를 무시해요. OpenRouter는 WebSearchTool.external_web_access를 지원하지 않아요.