Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/api/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@
::: pydantic_ai.providers.moonshotai.MoonshotAIProvider

::: pydantic_ai.providers.ollama.OllamaProvider

::: pydantic_ai.providers.litellm.LiteLLMProvider
26 changes: 26 additions & 0 deletions docs/models/openai.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,29 @@ result = agent.run_sync('What is the capital of France?')
print(result.output)
#> The capital of France is Paris.
```

### LiteLLM
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this to docs/models/index.md under OpenAI-compatible Providers as well


To use [LiteLLM](https://www.litellm.ai/), set the configs as outlined in the [doc](https://docs.litellm.ai/docs/set_keys). In `LiteLLMProvider`, you can pass `api_base` and `api_key`. The value of these configs will depend on your setup. For example, if you are using OpenAI models, then you need to pass `https://api.openai.com/v1` as the `api_base` and your OpenAI API key as the `api_key`. If you are using a LiteLLM proxy server running on your local machine, then you need to pass `http://localhost:<port>` as the `api_base` and your LiteLLM API key as the `api_key`.

Once you have the configs, use the [`LiteLLMProvider`][pydantic_ai.providers.litellm.LiteLLMProvider] as follows:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.litellm import LiteLLMProvider

model = OpenAIChatModel(
'openai/gpt-3.5-turbo',
provider=LiteLLMProvider(
api_base='<api-base-url>',
api_key='<api-key>'
)
)
agent = Agent(model)

result = agent.run_sync('What is the capital of France?')
print(result.output)
#> The capital of France is Paris.
...
```
1 change: 1 addition & 0 deletions pydantic_ai_slim/pydantic_ai/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
'openrouter',
'together',
'vercel',
'litellm',
):
from .openai import OpenAIChatModel

Expand Down
3 changes: 3 additions & 0 deletions pydantic_ai_slim/pydantic_ai/models/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def __init__(
'openrouter',
'together',
'vercel',
'litellm',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand Down Expand Up @@ -252,6 +253,7 @@ def __init__(
'openrouter',
'together',
'vercel',
'litellm',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand All @@ -278,6 +280,7 @@ def __init__(
'openrouter',
'together',
'vercel',
'litellm',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand Down
4 changes: 4 additions & 0 deletions pydantic_ai_slim/pydantic_ai/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ def infer_provider_class(provider: str) -> type[Provider[Any]]: # noqa: C901
from .github import GitHubProvider

return GitHubProvider
elif provider == 'litellm':
from .litellm import LiteLLMProvider

return LiteLLMProvider
else: # pragma: no cover
raise ValueError(f'Unknown provider: {provider}')

Expand Down
138 changes: 138 additions & 0 deletions pydantic_ai_slim/pydantic_ai/providers/litellm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from __future__ import annotations as _annotations

from typing import overload

from httpx import AsyncClient as AsyncHTTPClient
from openai import AsyncOpenAI

from pydantic_ai.models import cached_async_http_client
from pydantic_ai.profiles import ModelProfile
from pydantic_ai.profiles.amazon import amazon_model_profile
from pydantic_ai.profiles.anthropic import anthropic_model_profile
from pydantic_ai.profiles.cohere import cohere_model_profile
from pydantic_ai.profiles.deepseek import deepseek_model_profile
from pydantic_ai.profiles.google import google_model_profile
from pydantic_ai.profiles.grok import grok_model_profile
from pydantic_ai.profiles.groq import groq_model_profile
from pydantic_ai.profiles.meta import meta_model_profile
from pydantic_ai.profiles.mistral import mistral_model_profile
from pydantic_ai.profiles.moonshotai import moonshotai_model_profile
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer, OpenAIModelProfile, openai_model_profile
from pydantic_ai.profiles.qwen import qwen_model_profile
from pydantic_ai.providers import Provider

try:
from openai import AsyncOpenAI
except ImportError as _import_error: # pragma: no cover
raise ImportError(
'Please install the `openai` package to use the LiteLLM provider, '
'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
) from _import_error


class LiteLLMProvider(Provider[AsyncOpenAI]):
"""Provider for LiteLLM API."""

@property
def name(self) -> str:
return 'litellm'

@property
def base_url(self) -> str:
return str(self.client.base_url)

@property
def client(self) -> AsyncOpenAI:
return self._client

def model_profile(self, model_name: str) -> ModelProfile | None:
# Map provider prefixes to their profile functions
provider_to_profile = {
'anthropic': anthropic_model_profile,
'openai': openai_model_profile,
'google': google_model_profile,
'mistralai': mistral_model_profile,
'mistral': mistral_model_profile,
'cohere': cohere_model_profile,
'amazon': amazon_model_profile,
'bedrock': amazon_model_profile,
'meta-llama': meta_model_profile,
'meta': meta_model_profile,
'groq': groq_model_profile,
'deepseek': deepseek_model_profile,
'moonshotai': moonshotai_model_profile,
'x-ai': grok_model_profile,
'qwen': qwen_model_profile,
}

profile = None

# Check if model name contains a provider prefix (e.g., "anthropic/claude-3")
if '/' in model_name:
provider_prefix, model_suffix = model_name.split('/', 1)
if provider_prefix in provider_to_profile:
profile = provider_to_profile[provider_prefix](model_suffix)

# If no profile found, default to OpenAI profile
if profile is None:
profile = openai_model_profile(model_name)

# As LiteLLMProvider is used with OpenAIModel, which uses OpenAIJsonSchemaTransformer,
# we maintain that behavior
return OpenAIModelProfile(json_schema_transformer=OpenAIJsonSchemaTransformer).update(profile)

@overload
def __init__(
self,
*,
api_key: str | None = None,
api_base: str | None = None,
custom_llm_provider: str | None = None,
) -> None: ...

@overload
def __init__(
self,
*,
api_key: str | None = None,
api_base: str | None = None,
custom_llm_provider: str | None = None,
http_client: AsyncHTTPClient,
) -> None: ...

@overload
def __init__(self, *, openai_client: AsyncOpenAI) -> None: ...

def __init__(
self,
*,
api_key: str | None = None,
api_base: str | None = None,
custom_llm_provider: str | None = None,
openai_client: AsyncOpenAI | None = None,
http_client: AsyncHTTPClient | None = None,
) -> None:
"""Initialize a LiteLLM provider.

Args:
api_key: API key for the model provider. If None, LiteLLM will try to get it from environment variables.
api_base: Base URL for the model provider. Use this for custom endpoints or self-hosted models.
custom_llm_provider: Custom LLM provider name for LiteLLM. Use this if LiteLLM can't auto-detect the provider.
openai_client: Pre-configured OpenAI client. If provided, other parameters are ignored.
http_client: Custom HTTP client to use.
"""
if openai_client is not None:
self._client = openai_client
return

# Create OpenAI client that will be used with LiteLLM's completion function
# The actual API calls will be intercepted and routed through LiteLLM
if http_client is not None:
self._client = AsyncOpenAI(
base_url=api_base, api_key=api_key or 'litellm-placeholder', http_client=http_client
)
else:
http_client = cached_async_http_client(provider='litellm')
self._client = AsyncOpenAI(
base_url=api_base, api_key=api_key or 'litellm-placeholder', http_client=http_client
)
Original file line number Diff line number Diff line change
Expand Up @@ -99,53 +99,19 @@ interactions:
alt-svc:
- h3=":443"; ma=86400
content-length:
- '762'
- '99'
content-type:
- application/json
referrer-policy:
- strict-origin-when-cross-origin
strict-transport-security:
- max-age=3600; includeSubDomains
parsed_body:
data:
- created: 0
id: llama-4-maverick-17b-128e-instruct
object: model
owned_by: Cerebras
- created: 0
id: qwen-3-32b
object: model
owned_by: Cerebras
- created: 0
id: qwen-3-235b-a22b-instruct-2507
object: model
owned_by: Cerebras
- created: 0
id: llama-4-scout-17b-16e-instruct
object: model
owned_by: Cerebras
- created: 0
id: gpt-oss-120b
object: model
owned_by: Cerebras
- created: 0
id: qwen-3-coder-480b
object: model
owned_by: Cerebras
- created: 0
id: llama-3.3-70b
object: model
owned_by: Cerebras
- created: 0
id: llama3.1-8b
object: model
owned_by: Cerebras
- created: 0
id: qwen-3-235b-a22b-thinking-2507
object: model
owned_by: Cerebras
object: list
code: wrong_api_key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you restore this file please? That may also fix the coverage issue

message: Wrong API Key
param: api_key
type: invalid_request_error
status:
code: 200
message: OK
code: 401
message: Unauthorized
version: 1
Loading
Loading