OpenRouter 모델

OpenRouter 모델

OpenRouter는 여러 모델 제공자(프로바이더)를 하나의 API로 묶어주는 라우팅 서비스예요. pydantic-ai에서 OpenRouterModel을 쓰면 Anthropic, OpenAI, Gemini 등 다양한 모델을 OpenRouter라는 단일 진입점을 통해 호출할 수 있어요. 이 문서는 설치부터 캐싱, 웹 검색까지 OpenRouter 모델 사용법을 하나씩 설명해요.

출처: 문서

본문

설치하기

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

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_urlapp_title을 전달하면 앱 어트리뷰션을 활성화할 수 있어요. 둘 다 생략하면 OPENROUTER_APP_URLOPENROUTER_APP_TITLE 환경 변수로 대체돼요.

참고

환경 변수 폴백은 프로바이더가 스스로 만든 클라이언트에만 적용돼요. 직접 만든 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 입력 스트리밍

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_choicethinking과 호환되지 않는 것으로 취급해요. 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 중단점을 제어해요:

  1. 시스템 지시문 캐싱: OpenRouterModelSettings.openrouter_cache_instructionsTrue로 설정하거나 '5m' / '1h'를 직접 지정하세요.
  2. 마지막 메시지 캐싱: OpenRouterModelSettings.openrouter_cache_messagesTrue로 설정하면 대화의 마지막 메시지를 자동으로 캐싱해요.
  3. 툴 정의 캐싱: OpenRouterModelSettings.openrouter_cache_tool_definitionsTrue로 설정하거나 '5m' / '1h'를 직접 지정하세요.
  4. CachePoint를 통한 세밀한 제어: 사용자 메시지에 CachePoint 마커를 삽입하면 그 이전의 모든 것을 캐싱해요.

프로바이더 차이점

  • Anthropic 모델은 시스템 지시문과 메시지 콘텐츠 모두에 프리픽스 기반 캐싱을 지원해요. TTL 값('5m', '1h')은 프로바이더에 그대로 전달돼요.
  • 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 명시적 캐싱을 참고하세요.
  • 최소 토큰 임계값이 적용돼요. 현재 프로바이더별 값은 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를 지원하지 않아요.

더 알아보기 (Learn more)