OpenRouter
OpenRouter
OpenRouter는 여러 제공자의 모델을 하나의 API로 라우팅해주는 게이트웨이예요. PydanticAI에서는 OpenRouterModel을 쓰면 하나의 API 키로 다양한 모델을 오가며 쓸 수 있어요. 앱 기여도 추적, 모델 설정, 프롬프트 캐싱, 웹 검색 같은 기능도 지원하니 하나씩 살펴볼게요.
출처: 공식문서
설치
OpenRouterModel을 쓰려면 pydantic-ai를 설치하거나 pydantic-ai-slim을 openrouter 옵션 그룹과 함께 설치해야 해요.
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_url과 app_title을 넘기면 앱 기여도를 켤 수 있어요. 둘 다 생략하면 OPENROUTER_APP_URL과 OPENROUTER_APP_TITLE 환경 변수로 폴백해요.
참고: 환경 변수 폴백은 provider가 스스로 만든 클라이언트에만 적용돼요. 직접 만든 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 Input Streaming
OpenRouter를 통한 Anthropic 모델에서는 대용량 입력으로 도구 호출의 지연 시간을 줄이기 위해 eager input streaming을 켤 수 있어요. 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)
...
강제 도구 선택
PydanticAI는 OpenRouter를 통해 라우팅되는 모든 anthropic/ 모델에서 강제된 tool_choice를 thinking과 호환되지 않는 것으로 취급해요. 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 중단점을 제어해요.
- 시스템 지시 캐시:
OpenRouterModelSettings.openrouter_cache_instructions를True로 설정하거나'5m'/'1h'를 직접 지정 - 마지막 메시지 캐시:
OpenRouterModelSettings.openrouter_cache_messages를True로 설정해서 대화의 마지막 메시지를 자동 캐시 - 도구 정의 캐시:
OpenRouterModelSettings.openrouter_cache_tool_definitions를True로 설정하거나'5m'/'1h'를 직접 지정 CachePoint로 세밀하게 제어:CachePoint를 사용자 메시지에 넣어 그 앞의 모든 것을 캐시
Provider 차이:
- Anthropic 모델은 시스템 지시와 메시지 콘텐츠 모두에 접두사 기반 캐싱을 지원해요. TTL 값(
'5m','1h')은 provider에 그대로 전달돼요. - 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 명시적 캐싱을 보세요. - 최소 토큰 임계값이 적용돼요. 현재 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)
- OpenRouter 프롬프트 캐싱베스트 프랙티스 — 중단점·최소 토큰·sticky routing
- OpenRouter 웹 검색 서버 도구 — 검색 엔진·파라미터
- App Attribution — 앱 추적