모델 프로바이더

모델 프로바이더 (Model Providers)

Pydantic AI는 모델 무관(model-agnostic) 프레임워크예요. LLM과 통신하는 방식이 여러 프로바이더에 대해 내장 지원돼 있고, 같은 에이전트가 어떤 모델이든 문자열 하나만 바꿔서 사용할 수 있어요. 이 문서는 지원 프로바이더, Model·Provider·Profile의 개념, HTTP 클라이언트 수명 주기, 동시성 제한, 오류 처리, 그리고 Fallback 모델까지 다뤄요.

출처: 공식문서

지원 프로바이더

기본 지원 프로바이더는 다음과 같아요.

OpenAI 호환 프로바이더

OpenAI API와 호환되는 많은 프로바이더는 OpenAIChatModel로 쓸 수 있어요.

테스트·개발용으로 TestModelFunctionModel도 함께 제공돼요. 각 프로바이더를 쓰려면 로컬 환경을 설정하고 패키지를 설치해야 해요 — 안 하면 무엇을 설치해야 하는지 안내해줘요.

Model, Provider, Profile

Pydantic AI가 LLM과 상호작용하는 방식을 설명하는 핵심 용어 세 가지가 있어요.

  • Model: 특정 LLM API로 요청을 보내는 Pydantic AI 클래스(일반적으로 openai 같은 벤더 SDK를 감싸요). 벤더 SDK 무관 API를 구현해서, Model만 바꾸면 에이전트 하나가 코드 변경 없이 다른 LLM 벤더로 이식돼요. 클래스 이름은 대략 <VendorSdk>Model 형식이고, OpenAIChatModel, AnthropicModel, GoogleModel 등이 있어요. 실제 LLM 모델 이름(gpt-5, claude-sonnet-4-5, gemini-3-flash-preview 등)은 파라미터로 지정해요.
  • Provider: LLM 벤더에 대한 인증·연결을 처리하는 프로바이더별 클래스. Model에 비기본 Provider를 파라미터로 넘기면 특정 엔드포인트로 요청을 보내거나 특정 인증 방식(예: OpenAIChatModelAzureProvider로 Azure 인증)을 쓰게 해줘요. AI 게이트웨이나 기존 Model의 벤더 SDK와 API 호환이 되는 LLM 벤더를 활용할 때도 이렇게 해요.
  • Profile: 특정 모델·모델군에 대한 요청을 최적 결과로 구성하는 방법의 설명. 모델·프로바이더 클래스와는 독립적이에요. 예를 들어 모델마다 도구에 쓸 수 있는 JSON 스키마 제한이 다른데, GoogleModel(모델명 gemini-3-pro-preview)을 쓰든 OpenAIChatModel+OpenRouterProvider(모델명 google/gemini-3-pro-preview)를 쓰든 같은 스키마 변환기를 써야 해요.

<provider>:<model> 형식, 예를 들어 openai:gpt-5.2openrouter:google/gemini-3-pro-previewAgent를 인스턴스화하면, Pydantic AI가 적절한 model 클래스·provider·profile을 자동 선택해요. 다른 provider나 profile을 쓰려면 model 클래스를 직접 인스턴스화해 provider·profile 인자를 넘기면 돼요.

모델의 프로파일 검사

모델의 ModelProfile은 모델이 무엇을 할 수 있는지도 설명해요. TypedDict라서 model.profile로 일반 dict 접근처럼 능력 플래그를 읽을 수 있어요 — 예를 들어 supports_tools, supports_json_schema_output, supported_native_tools. 요청 시점에 한계를 발견하기보다 능력에 따라 분기하고 싶을 때 유용해요.

from pydantic_ai.models.test import TestModel
from pydantic_ai.native_tools import WebSearchTool

model = TestModel()
profile = model.profile

print(profile['supports_tools'])
#> True
print(profile['supports_json_schema_output'])
#> False
print(WebSearchTool in profile['supported_native_tools'])
#> True

model.profile은 보통 완전히 해석된(resolved) 프로파일이에요. DEFAULT_PROFILE의 키가 provider 기본값과 병합되므로 profile['supports_tools'] 같은 직접 접근이 동작해요. profile=을 callable로 주거나 부분 dict를 주는 경우엔 profile.get('supports_tools', DEFAULT_PROFILE['supports_tools'])처럼 키 누락을 허용하세요.

프로파일은 모델의 context_window도 담아요 — 단일 요청이 처리할 수 있는 최대 토큰 수이고, 알 수 없으면 None이에요. 모든 모델(랜덤 FallbackModel 포함, InstrumentedModel 같은 래퍼도)이 model.context_window로 노출해요. fallback 모델은 후보 중 가장 작은 윈도우를 보고하므로, 그걸 만족하는 히스토리는 어느 후보가 답해도 맞아요. 실행 중에 ctx.context_window_used가 사용 중 비율을 알려줘요.

HTTP 클라이언트 수명 주기

Provider가 자체 HTTP 클라이언트를 만들면(커스텀 http_client를 넘기지 않으면) 그 클라이언트의 수명 주기를 소유해요. Agent를 async 컨텍스트 매니저로 쓰면 종료 시 HTTP 클라이언트를 깨끗하게 닫아줘요.

from pydantic_ai import Agent

agent = Agent('openai:gpt-5.2')

async def main():
    async with agent:
        result = await agent.run('What is the capital of France?')
        print(result.output)
        #> The capital of France is Paris.

Model이나 Provider를 직접 async 컨텍스트 매니저로 써도 같은 효과예요. 직접 http_client를 제공하면 닫는 책임은 직접 지는 거예요.

커스텀 모델

모델 API가 OpenAI API와 호환된다면 커스텀 model 클래스가 필요 없고 커스텀 provider를 제공하면 돼요. 아직 지원되지 않는 모델 API를 구현하려면 Model 추상 베이스 클래스를 서브클래스로 만들고, 스트리밍은 StreamedResponse를 구현하면 돼요. 기존 구현(예: OpenAIChatModel)의 소스코드를 보는 게 시작점이 좋아요.

HTTP 요청 동시성

ConcurrencyLimitedModel 래퍼로 모델에 대한 동시 HTTP 요청 수를 제한할 수 있어요. 많은 에이전트를 병렬로 돌릴 때 레이트 리밋을 지키거나 리소스 사용을 관리하는 데 유용해요.

import asyncio

from pydantic_ai import Agent, ConcurrencyLimitedModel

# Wrap a model with concurrency limiting
model = ConcurrencyLimitedModel('openai:gpt-4o', limiter=5)

# Multiple agents can share this rate-limited model
agent = Agent(model)


async def main():
    # These will be rate-limited to 5 concurrent HTTP requests
    results = await asyncio.gather(
        *[agent.run(f'Question {i}') for i in range(20)]
    )
    print(len(results))
    #> 20

limiter 파라미터는: 정수(단순 제한, limiter=5), ConcurrencyLimit(백프레셔 제어의 고급 설정), ConcurrencyLimiter(여러 모델에 제한 공유)을 받아요. 여러 모델에 같은 제한을 공유하려면 ConcurrencyLimiter를 만들어 여러 ConcurrencyLimitedModel 인스턴스에 넘기면 돼요.

HTTP 오류 처리

프로바이더가 4xx·5xx 응답을 반환하면 Pydantic AI는 ModelHTTPError를 raise해요. 이 예외는 status_code, 응답 body, 그리고 headers 속성(소문자 키의 dict[str, str], 헤더를 노출하지 않는 gRPC 기반 프로바이더는 None)으로 프로바이더의 응답 헤더를 노출해요.

retry_after 속성은 429 응답의 Retry-After 헤더를 파싱해 대기할 초 수를 float로 반환해요(정수 델타초와 HTTP-date 형식을 모두 처리).

Fallback 모델

FallbackModel을 쓰면 여러 모델을 순서대로 시도해서 하나가 성공할 때까지 진행할 수 있어요. 현재 모델이 예외를 던지면 또는 응답 콘텐츠가 의미적 실패(잘린 응답·실패한 네이티브 도구 호출)를 나타내면 다음 모델로 전환해요.

기본적으로 fallback은 ModelAPIError(4xx/5xx API 오류)에서 발동하므로, 가장 흔한 경우엔 별도 설정이 필요 없어요. 이 동작은 fallback_on 파라미터로 제어하며 예외 타입·예외 핸들러·응답 핸들러를 받아요(모두 sync/async 가능).

주의할 점: Model의 기반이 되는 프로바이더 SDK(OpenAI, Anthropic 등)는 종종 내장 재시도 로직이 있어 FallbackModel 활성화를 지연시킬 수 있어요. FallbackModel을 쓸 땐 즉시 fallback되도록 프로바이더 SDK 재시도를 비활성화하는 걸 권장해요(예: 커스텀 OpenAI 클라이언트max_retries=0).

아래 예시는 먼저 OpenAI 모델에 요청(잘못된 API 키로 실패)한 다음 Anthropic 모델로 fallback해요.

from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.fallback import FallbackModel
from pydantic_ai.models.openai import OpenAIChatModel

openai_model = OpenAIChatModel('gpt-5.2')
anthropic_model = AnthropicModel('claude-sonnet-4-5')
fallback_model = FallbackModel(openai_model, anthropic_model)

agent = Agent(fallback_model)
response = agent.run_sync('What is the capital of France?')
print(response.output)
#> The capital of France is Paris.

ModelResponse 메시지의 model_name 필드는 Anthropic 모델(두 번째로 지정된 모델)이 출력을 반환했음을 보여줘요. 각 모델의 옵션(base_url, api_key, 커스텀 클라이언트)은 각각 독립적으로 설정해야 해요 — FallbackModel에 설정하는 게 아니라요.

모든 모델 실패 시 예외

모든 모델이 실패하면 FallbackExceptionGroup이 raise되는데, 여기에는 run 실행 중 마주친 모든 예외가 담겨 있어요. Python 3.11+에선 except* 문법으로 그룹을 처리하고, 더 이른 버전에선 exceptiongroup 백포트 패키지를 써요.

응답 기반 fallback

예외 기반 외에 모델 응답의 콘텐츠로도 fallback을 발동할 수 있어요. 모델이 성공 HTTP 응답(예외 없음)을 반환했지만 응답 콘텐츠가 의미적 실패(예상치 못한 finish reason이나 실패한 네이티브 도구)를 나타낼 때 유용해요. 응답 기반 fallback은 현재 비스트리밍 요청(agent.run()·agent.run_sync())에서만 동작해요. 스트리밍 요청에선 예외 기반 fallback만 지원돼요.

fallback_on은 다음을 받아요: 예외 타입 튜플((ModelAPIError, ModelHTTPError)), 예외 핸들러(sync/async, lambda exc: isinstance(exc, MyError)), 응답 핸들러(def check(r: ModelResponse) -> bool), 또는 전부 섞은 리스트([ModelAPIError, exc_handler, response_handler]). 핸들러 타입은 첫 파라미터의 타입 힌트를 보고 자동 감지해요 — 첫 파라미터가 ModelResponse로 힌트되면 응답 핸들러, 아니면(타입 없는 핸들러·람다 포함) 예외 핸들러예요.

단일 응답 핸들러를 fallback_on으로 넘기면 기본 (ModelAPIError,) 예외 fallback을 완전히 대체해요. 즉 API 오류가 다음 모델로의 fallback 대신 예외로 전파돼요. 예외 기반 fallback을 유지하려면 함께 리스트로 넘기세요.

finish reason 기반 fallback 예시:

from pydantic_ai import Agent
from pydantic_ai.messages import FinishReason, ModelResponse
from pydantic_ai.models.fallback import FallbackModel


def bad_finish_reason(response: ModelResponse) -> bool:
    """Fallback if the model stopped due to length limit, content filter, or error."""
    reason: FinishReason | None = response.finish_reason
    # Trigger fallback for problematic finish reasons
    return reason in ('length', 'content_filter', 'error')


fallback_model = FallbackModel(
    'openai:gpt-5.2',
    'anthropic:claude-sonnet-4-5',
    fallback_on=bad_finish_reason,
)

agent = Agent(fallback_model)
result = agent.run_sync('What is the capital of France?')
print(result.output)
#> The capital of France is Paris.

네이티브 도구 실패 기반 fallback도 가능해요. 예를 들어 Google의 WebFetchTool은 URL 가져오기가 실패했음을 나타내는 상태를 가진 성공 응답을 반환할 수 있어요. 응답 핸들러는 ModelResponse를 받아 다음 모델로 fallback하려면 True, 응답을 받아들이려면 False를 반환해요.

주의: 구조화 출력이나 도구 파라미터검증 오류는 fallback을 발동시키지 않아요. 이런 오류는 재시도 메커니즘을 쓰는데, 같은 모델을 다시 프롬프트해 재시도해요. 의도적인 설계예요 — 검증 오류는 LLM의 비결정적 특성에서 비롯되어 재시도로 성공할 수 있지만, API 오류(4xx/5xx)는 보통 같은 요청을 재시도해도 해결되지 않기 때문이에요.

미들웨어·데코레이터에서의 예외 처리

FallbackExceptionGroup은 Python의 ExceptionGroup을 상속해요. 그래서 특정 예외(예: ModelAPIError)를 잡던 기존 예외 처리 코드는 그룹 안에 감싸진 개별 예외를 자동으로 잡지 못해요. 이 경우 Python 3.11+ except* 문법을 쓰면 exception group과 단일 예외를 모두 잡을 수 있어요. FallbackExceptionGroup을 직접 잡아도 돼요.

더 알아보기 (Learn more)