Merge remote-tracking branch 'origin/main' into add-health-check-success-modal

This commit is contained in:
Cole McIntosh 2025-06-20 08:05:36 -06:00
commit 0ca70bf92e
113 changed files with 2874 additions and 930 deletions

View file

@ -1,6 +1,8 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
{{- toYaml .Values.deploymentAnnotations | nindent 4 }}
name: {{ include "litellm.fullname" . }}
labels:
{{- include "litellm.labels" . | nindent 4 }}

View file

@ -27,6 +27,9 @@ serviceAccount:
# If not set and create is true, a name is generated using the fullname template
name: ""
# annotations for litellm deployment
deploymentAnnotations: {}
# annotations for litellm pods
podAnnotations: {}
podLabels: {}

View file

@ -265,6 +265,182 @@ if __name__ == "__main__":
</Tabs>
## Using your MCP with client side credentials
Use this if you want to pass a client side authentication token to LiteLLM to then pass to your MCP to auth to your MCP.
You can specify your MCP auth token using the header `x-mcp-auth`. LiteLLM will forward this token to your MCP server for authentication.
<Tabs>
<TabItem value="openai" label="OpenAI API">
#### Connect via OpenAI Responses API with MCP Auth
Use the OpenAI Responses API and include the `x-mcp-auth` header for your MCP server authentication:
```bash title="cURL Example with MCP Auth" showLineNumbers
curl --location 'https://api.openai.com/v1/responses' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $OPENAI_API_KEY" \
--data '{
"model": "gpt-4o",
"tools": [
{
"type": "mcp",
"server_label": "litellm",
"server_url": "<your-litellm-proxy-base-url>/mcp",
"require_approval": "never",
"headers": {
"x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY",
"x-mcp-auth": YOUR_MCP_AUTH_TOKEN
}
}
],
"input": "Run available tools",
"tool_choice": "required"
}'
```
</TabItem>
<TabItem value="litellm" label="LiteLLM Proxy">
#### Connect via LiteLLM Proxy Responses API with MCP Auth
Use this when calling LiteLLM Proxy for LLM API requests to `/v1/responses` endpoint with MCP authentication:
```bash title="cURL Example with MCP Auth" showLineNumbers
curl --location '<your-litellm-proxy-base-url>/v1/responses' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $LITELLM_API_KEY" \
--data '{
"model": "gpt-4o",
"tools": [
{
"type": "mcp",
"server_label": "litellm",
"server_url": "<your-litellm-proxy-base-url>/mcp",
"require_approval": "never",
"headers": {
"x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY",
"x-mcp-auth": "YOUR_MCP_AUTH_TOKEN"
}
}
],
"input": "Run available tools",
"tool_choice": "required"
}'
```
</TabItem>
<TabItem value="cursor" label="Cursor IDE">
#### Connect via Cursor IDE with MCP Auth
Use tools directly from Cursor IDE with LiteLLM MCP and include your MCP authentication token:
**Setup Instructions:**
1. **Open Cursor Settings**: Use `⇧+⌘+J` (Mac) or `Ctrl+Shift+J` (Windows/Linux)
2. **Navigate to MCP Tools**: Go to the "MCP Tools" tab and click "New MCP Server"
3. **Add Configuration**: Copy and paste the JSON configuration below, then save with `Cmd+S` or `Ctrl+S`
```json title="Cursor MCP Configuration with Auth" showLineNumbers
{
"mcpServers": {
"LiteLLM": {
"url": "<your-litellm-proxy-base-url>/mcp",
"headers": {
"x-litellm-api-key": "Bearer $LITELLM_API_KEY",
"x-mcp-auth": "$MCP_AUTH_TOKEN"
}
}
}
}
```
</TabItem>
<TabItem value="http" label="Streamable HTTP">
#### Connect via Streamable HTTP Transport with MCP Auth
Connect to LiteLLM MCP using HTTP transport with MCP authentication:
**Server URL:**
```text showLineNumbers
<your-litellm-proxy-base-url>/mcp
```
**Headers:**
```text showLineNumbers
x-litellm-api-key: Bearer YOUR_LITELLM_API_KEY
x-mcp-auth: Bearer YOUR_MCP_AUTH_TOKEN
```
This URL can be used with any MCP client that supports HTTP transport. The `x-mcp-auth` header will be forwarded to your MCP server for authentication.
</TabItem>
<TabItem value="fastmcp" label="Python FastMCP">
#### Connect via Python FastMCP Client with MCP Auth
Use the Python FastMCP client to connect to your LiteLLM MCP server with MCP authentication:
```python title="Python FastMCP Example with MCP Auth" showLineNumbers
import asyncio
import json
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
# Create the transport with your LiteLLM MCP server URL and auth headers
server_url = "<your-litellm-proxy-base-url>/mcp"
transport = StreamableHttpTransport(
server_url,
headers={
"x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY",
"x-mcp-auth": "Bearer YOUR_MCP_AUTH_TOKEN"
}
)
# Initialize the client with the transport
client = Client(transport=transport)
async def main():
# Connection is established here
print("Connecting to LiteLLM MCP server with authentication...")
async with client:
print(f"Client connected: {client.is_connected()}")
# Make MCP calls within the context
print("Fetching available tools...")
tools = await client.list_tools()
print(f"Available tools: {json.dumps([t.name for t in tools], indent=2)}")
# Example: Call a tool (replace 'tool_name' with an actual tool name)
if tools:
tool_name = tools[0].name
print(f"Calling tool: {tool_name}")
# Call the tool with appropriate arguments
result = await client.call_tool(tool_name, arguments={})
print(f"Tool result: {result}")
# Run the example
if __name__ == "__main__":
asyncio.run(main())
```
</TabItem>
</Tabs>
## ✨ MCP Permission Management
LiteLLM supports managing permissions for MCP Servers by Keys, Teams, Organizations (entities) on LiteLLM. When a MCP client attempts to list tools, LiteLLM will only return the tools the entity has permissions to access.

View file

@ -45,7 +45,7 @@ os.environ["LLAMA_API_KEY"] = "" # your Meta Llama API key
messages = [{"content": "Hello, how are you?", "role": "user"}]
# Meta Llama call
response = completion(model="meta_llama/Llama-3.3-70B-Instruct", messages=messages)
response = completion(model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8", messages=messages)
```
### Streaming
@ -61,7 +61,7 @@ messages = [{"content": "Hello, how are you?", "role": "user"}]
# Meta Llama call with streaming
response = completion(
model="meta_llama/Llama-3.3-70B-Instruct",
model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
messages=messages,
stream=True
)
@ -70,6 +70,104 @@ for chunk in response:
print(chunk)
```
### Function Calling
```python showLineNumbers title="Meta Llama Function Calling"
import os
import litellm
from litellm import completion
os.environ["LLAMA_API_KEY"] = "" # your Meta Llama API key
messages = [{"content": "What's the weather like in San Francisco?", "role": "user"}]
# Define the function
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
}
]
# Meta Llama call with function calling
response = completion(
model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
messages=messages,
tools=tools,
tool_choice="auto"
)
print(response.choices[0].message.tool_calls)
```
### Tool Use
```python showLineNumbers title="Meta Llama Tool Use"
import os
import litellm
from litellm import completion
os.environ["LLAMA_API_KEY"] = "" # your Meta Llama API key
messages = [{"content": "Create a chart showing the population growth of New York City from 2010 to 2020", "role": "user"}]
# Define the tools
tools = [
{
"type": "function",
"function": {
"name": "create_chart",
"description": "Create a chart with the provided data",
"parameters": {
"type": "object",
"properties": {
"chart_type": {
"type": "string",
"enum": ["bar", "line", "pie", "scatter"],
"description": "The type of chart to create"
},
"title": {
"type": "string",
"description": "The title of the chart"
},
"data": {
"type": "object",
"description": "The data to plot in the chart"
}
},
"required": ["chart_type", "title", "data"]
}
}
}
]
# Meta Llama call with tool use
response = completion(
model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
messages=messages,
tools=tools,
tool_choice="auto"
)
print(response.choices[0].message.content)
```
## Usage - LiteLLM Proxy
@ -111,7 +209,7 @@ client = OpenAI(
# Non-streaming response
response = client.chat.completions.create(
model="meta_llama/Llama-3.3-70B-Instruct",
model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
messages=[{"role": "user", "content": "Write a short poem about AI."}]
)
@ -129,7 +227,7 @@ client = OpenAI(
# Streaming response
response = client.chat.completions.create(
model="meta_llama/Llama-3.3-70B-Instruct",
model="meta_llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
messages=[{"role": "user", "content": "Write a short poem about AI."}],
stream=True
)

View file

@ -1,5 +1,5 @@
---
title: "[PRE-RELEASE] v1.72.6-stable - MCP Gateway Permission Management"
title: "v1.72.6-stable - MCP Gateway Permission Management"
slug: "v1-72-6-stable"
date: 2025-06-14T10:00:00
authors:
@ -36,14 +36,14 @@ The production version will be released on Wednesday.
docker run
-e STORE_MODEL_IN_DB=True
-p 4000:4000
ghcr.io/berriai/litellm:main-v1.72.6.post1-nightly
ghcr.io/berriai/litellm:main-v1.72.6-stable
```
</TabItem>
<TabItem value="pip" label="Pip">
``` showLineNumbers title="pip install litellm"
pip install litellm==1.72.6.post1
pip install litellm==1.72.6.post2
```
</TabItem>

View file

@ -2,7 +2,7 @@
import warnings
warnings.filterwarnings("ignore", message=".*conflict with protected namespace.*")
### INIT VARIABLES ###########
### INIT VARIABLES ############
import threading
import os
from typing import Callable, List, Optional, Dict, Union, Any, Literal, get_args

View file

@ -0,0 +1,164 @@
"""
LiteLLM Proxy uses this MCP Client to connnect to other MCP servers.
"""
import base64
from datetime import timedelta
from typing import List, Optional
from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamablehttp_client
from mcp.types import CallToolRequestParams as MCPCallToolRequestParams
from mcp.types import CallToolResult as MCPCallToolResult
from mcp.types import Tool as MCPTool
from litellm.types.mcp import MCPAuth, MCPAuthType, MCPTransport, MCPTransportType
def to_basic_auth(auth_value: str) -> str:
"""Convert auth value to Basic Auth format."""
return base64.b64encode(auth_value.encode("utf-8")).decode()
class MCPClient:
"""
MCP Client supporting:
SSE and HTTP transports
Authentication via Bearer token, Basic Auth, or API Key
Tool calling with error handling and result parsing
"""
def __init__(
self,
server_url: str,
transport_type: MCPTransportType = MCPTransport.http,
auth_type: MCPAuthType = None,
auth_value: Optional[str] = None,
timeout: float = 60.0,
):
self.server_url: str = server_url
self.transport_type: MCPTransport = transport_type
self.auth_type: MCPAuthType = auth_type
self.timeout: float = timeout
self._mcp_auth_value: Optional[str] = None
self._session: Optional[ClientSession] = None
self._context = None
self._transport_ctx = None
self._transport = None
self._session_ctx = None
# handle the basic auth value if provided
if auth_value:
self.update_auth_value(auth_value)
async def __aenter__(self):
"""
Enable async context manager support.
Initializes the transport and session.
"""
await self.connect()
return self
async def connect(self):
"""Initialize the transport and session."""
if self._session:
return # Already connected
headers = self._get_auth_headers()
if self.transport_type == MCPTransport.sse:
self._transport_ctx = sse_client(
url=self.server_url,
timeout=self.timeout,
headers=headers,
)
self._transport = await self._transport_ctx.__aenter__()
self._session_ctx = ClientSession(self._transport[0], self._transport[1])
self._session = await self._session_ctx.__aenter__()
await self._session.initialize()
else:
self._transport_ctx = streamablehttp_client(
url=self.server_url,
timeout=timedelta(seconds=self.timeout),
headers=headers,
)
self._transport = await self._transport_ctx.__aenter__()
self._session_ctx = ClientSession(self._transport[0], self._transport[1])
self._session = await self._session_ctx.__aenter__()
await self._session.initialize()
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Cleanup when exiting context manager."""
if self._session:
await self._session_ctx.__aexit__(exc_type, exc_val, exc_tb) # type: ignore
if self._transport_ctx:
await self._transport_ctx.__aexit__(exc_type, exc_val, exc_tb)
async def disconnect(self):
"""Clean up session and connections."""
if self._session:
try:
# Ensure session is properly closed
await self._session.close() # type: ignore
except Exception:
pass
self._session = None
if self._context:
try:
await self._context.__aexit__(None, None, None) # type: ignore
except Exception:
pass
self._context = None
def update_auth_value(self, mcp_auth_value: str):
"""
Set the authentication header for the MCP client.
"""
if self.auth_type == MCPAuth.basic:
# Assuming mcp_auth_value is in format "username:password", convert it when updating
mcp_auth_value = to_basic_auth(mcp_auth_value)
self._mcp_auth_value = mcp_auth_value
def _get_auth_headers(self) -> dict:
"""Generate authentication headers based on auth type."""
if not self._mcp_auth_value:
return {}
if self.auth_type == MCPAuth.bearer_token:
return {"Authorization": f"Bearer {self._mcp_auth_value}"}
elif self.auth_type == MCPAuth.basic:
return {"Authorization": f"Basic {self._mcp_auth_value}"}
elif self.auth_type == MCPAuth.api_key:
return {"X-API-Key": self._mcp_auth_value}
return {}
async def list_tools(self) -> List[MCPTool]:
"""List available tools from the server."""
if not self._session:
await self.connect()
if self._session is None:
raise ValueError("Session is not initialized")
result = await self._session.list_tools()
return result.tools
async def call_tool(
self, call_tool_request_params: MCPCallToolRequestParams
) -> MCPCallToolResult:
"""
Call an MCP Tool.
"""
if not self._session:
await self.connect()
if self._session is None:
raise ValueError("Session is not initialized")
tool_result = await self._session.call_tool(
name=call_tool_request_params.name,
arguments=call_tool_request_params.arguments,
)
return tool_result

View file

@ -215,40 +215,25 @@ class PrometheusLogger(CustomLogger):
self.litellm_remaining_requests_metric = self._gauge_factory(
"litellm_remaining_requests",
"LLM Deployment Analytics - remaining requests for model, returned from LLM API Provider",
labelnames=[
"model_group",
"api_provider",
"api_base",
"litellm_model_name",
"hashed_api_key",
"api_key_alias",
],
labelnames=self.get_labels_for_metric(
"litellm_remaining_requests_metric"
),
)
self.litellm_remaining_tokens_metric = self._gauge_factory(
"litellm_remaining_tokens",
"remaining tokens for model, returned from LLM API Provider",
labelnames=[
"model_group",
"api_provider",
"api_base",
"litellm_model_name",
"hashed_api_key",
"api_key_alias",
],
labelnames=self.get_labels_for_metric(
"litellm_remaining_tokens_metric"
),
)
self.litellm_overhead_latency_metric = self._histogram_factory(
"litellm_overhead_latency_metric",
"Latency overhead (milliseconds) added by LiteLLM processing",
labelnames=[
"model_group",
"api_provider",
"api_base",
"litellm_model_name",
"hashed_api_key",
"api_key_alias",
],
labelnames=self.get_labels_for_metric(
"litellm_overhead_latency_metric"
),
buckets=LATENCY_BUCKETS,
)
# llm api provider budget metrics
@ -566,6 +551,7 @@ class PrometheusLogger(CustomLogger):
hashed_api_key=user_api_key,
api_key_alias=user_api_key_alias,
requested_model=standard_logging_payload["model_group"],
model_group=standard_logging_payload["model_group"],
team=user_api_team,
team_alias=user_api_team_alias,
user=user_id,
@ -1160,6 +1146,7 @@ class PrometheusLogger(CustomLogger):
enum_values: UserAPIKeyLabelValues,
output_tokens: float = 1.0,
):
try:
verbose_logger.debug("setting remaining tokens requests metric")
standard_logging_payload: Optional[StandardLoggingPayload] = (
@ -1292,7 +1279,7 @@ class PrometheusLogger(CustomLogger):
).observe(latency_per_token)
except Exception as e:
verbose_logger.error(
verbose_logger.exception(
"Prometheus Error: set_llm_deployment_success_metrics. Exception occured - {}".format(
str(e)
)

View file

@ -1053,10 +1053,10 @@ def convert_to_gemini_tool_call_invoke(
if tool_calls is not None:
for tool in tool_calls:
if "function" in tool:
gemini_function_call: Optional[
VertexFunctionCall
] = _gemini_tool_call_invoke_helper(
function_call_params=tool["function"]
gemini_function_call: Optional[VertexFunctionCall] = (
_gemini_tool_call_invoke_helper(
function_call_params=tool["function"]
)
)
if gemini_function_call is not None:
_parts_list.append(
@ -1573,9 +1573,9 @@ def anthropic_messages_pt( # noqa: PLR0915
)
if "cache_control" in _content_element:
_anthropic_content_element[
"cache_control"
] = _content_element["cache_control"]
_anthropic_content_element["cache_control"] = (
_content_element["cache_control"]
)
user_content.append(_anthropic_content_element)
elif m.get("type", "") == "text":
m = cast(ChatCompletionTextObject, m)
@ -1613,9 +1613,9 @@ def anthropic_messages_pt( # noqa: PLR0915
)
if "cache_control" in _content_element:
_anthropic_content_text_element[
"cache_control"
] = _content_element["cache_control"]
_anthropic_content_text_element["cache_control"] = (
_content_element["cache_control"]
)
user_content.append(_anthropic_content_text_element)
@ -2433,8 +2433,10 @@ class BedrockImageProcessor:
# Extract MIME type using regular expression
mime_type_match = re.match(r"data:(.*?);base64", image_metadata)
if mime_type_match:
mime_type = mime_type_match.group(1)
mime_type = mime_type.split(";")[0]
image_format = mime_type.split("/")[1]
else:
mime_type = "image/jpeg"
@ -2458,6 +2460,7 @@ class BedrockImageProcessor:
document_types = ["application", "text"]
is_document = any(mime_type.startswith(doc_type) for doc_type in document_types)
supported_image_and_video_formats: List[str] = (
supported_video_formats + supported_image_formats
)

View file

@ -77,9 +77,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
to pass metadata to anthropic, it's {"user_id": "any-relevant-information"}
"""
max_tokens: Optional[
int
] = DEFAULT_ANTHROPIC_CHAT_MAX_TOKENS # anthropic requires a default value (Opus, Sonnet, and Haiku have the same default)
max_tokens: Optional[int] = (
DEFAULT_ANTHROPIC_CHAT_MAX_TOKENS # anthropic requires a default value (Opus, Sonnet, and Haiku have the same default)
)
stop_sequences: Optional[list] = None
temperature: Optional[int] = None
top_p: Optional[int] = None
@ -104,11 +104,16 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
if key != "self" and value is not None:
setattr(self.__class__, key, value)
@property
def custom_llm_provider(self) -> Optional[str]:
return "anthropic"
@classmethod
def get_config(cls):
return super().get_config()
def get_supported_openai_params(self, model: str):
params = [
"stream",
"stop",
@ -447,11 +452,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
if mcp_servers:
optional_params["mcp_servers"] = mcp_servers
if param == "tool_choice" or param == "parallel_tool_calls":
_tool_choice: Optional[
AnthropicMessagesToolChoice
] = self._map_tool_choice(
tool_choice=non_default_params.get("tool_choice"),
parallel_tool_use=non_default_params.get("parallel_tool_calls"),
_tool_choice: Optional[AnthropicMessagesToolChoice] = (
self._map_tool_choice(
tool_choice=non_default_params.get("tool_choice"),
parallel_tool_use=non_default_params.get("parallel_tool_calls"),
)
)
if _tool_choice is not None:
@ -557,9 +562,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
text=system_message_block["content"],
)
if "cache_control" in system_message_block:
anthropic_system_message_content[
"cache_control"
] = system_message_block["cache_control"]
anthropic_system_message_content["cache_control"] = (
system_message_block["cache_control"]
)
anthropic_system_message_list.append(
anthropic_system_message_content
)
@ -573,9 +578,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
)
)
if "cache_control" in _content:
anthropic_system_message_content[
"cache_control"
] = _content["cache_control"]
anthropic_system_message_content["cache_control"] = (
_content["cache_control"]
)
anthropic_system_message_list.append(
anthropic_system_message_content
@ -735,9 +740,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
)
return _message
def extract_response_content(
self, completion_response: dict
) -> Tuple[
def extract_response_content(self, completion_response: dict) -> Tuple[
str,
Optional[List[Any]],
Optional[

View file

@ -97,6 +97,7 @@ class BaseConfig(ABC):
types.BuiltinFunctionType,
classmethod,
staticmethod,
property,
),
)
and v is not None

View file

@ -28,6 +28,10 @@ class AmazonAnthropicClaude3Config(AmazonInvokeConfig, AnthropicConfig):
anthropic_version: str = "bedrock-2023-05-31"
@property
def custom_llm_provider(self) -> Optional[str]:
return "bedrock"
def get_supported_openai_params(self, model: str) -> List[str]:
return AnthropicConfig.get_supported_openai_params(self, model)

View file

@ -533,6 +533,39 @@ class AsyncHTTPHandler:
verbose_logger.debug("Using AiohttpTransport...")
return True
@staticmethod
def _get_ssl_connector_kwargs(
ssl_verify: Optional[bool] = None,
ssl_context: Optional[ssl.SSLContext] = None,
) -> Dict[str, Any]:
"""
Helper method to get SSL connector initialization arguments for aiohttp TCPConnector.
SSL Configuration Priority:
1. If ssl_context is provided -> use the custom SSL context
2. If ssl_verify is False -> disable SSL verification (ssl=False)
3. If ssl_verify is True/None -> use default SSL context with certifi CA bundle
Returns:
Dict with appropriate SSL configuration for TCPConnector
"""
connector_kwargs: Dict[str, Any] = {
"local_addr": ("0.0.0.0", 0) if litellm.force_ipv4 else None,
}
if ssl_context is not None:
# Priority 1: Use the provided custom SSL context
connector_kwargs["ssl"] = ssl_context
elif ssl_verify is False:
# Priority 2: Explicitly disable SSL verification
connector_kwargs["verify_ssl"] = False
else:
# Priority 3: Use our default SSL context with certifi CA bundle
# This covers ssl_verify=True and ssl_verify=None cases
connector_kwargs["ssl"] = AsyncHTTPHandler._get_ssl_context()
return connector_kwargs
@staticmethod
def _create_aiohttp_transport(
ssl_verify: Optional[bool] = None,
@ -541,29 +574,34 @@ class AsyncHTTPHandler:
"""
Creates an AiohttpTransport with RequestNotRead error handling
- If force_ipv4 is True, it will create an AiohttpTransport with local_addr set to "0.0.0.0"
- [Default] If force_ipv4 is False, it will create an AiohttpTransport with default settings
Note: aiohttp TCPConnector ssl parameter accepts:
- SSLContext: custom SSL context
- False: disable SSL verification
- True: use default SSL verification (equivalent to ssl.create_default_context())
"""
from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport
#########################################################
# If ssl_verify is None, set it to True
# TCP Connector does not allow ssl_verify to be None
# by default aiohttp sets ssl_verify to True
#########################################################
if ssl_verify is None:
ssl_verify = True
connector_kwargs = AsyncHTTPHandler._get_ssl_connector_kwargs(
ssl_verify=ssl_verify, ssl_context=ssl_context
)
verbose_logger.debug("Creating AiohttpTransport...")
return LiteLLMAiohttpTransport(
client=lambda: ClientSession(
connector=TCPConnector(
verify_ssl=ssl_verify,
ssl_context=ssl_context,
local_addr=("0.0.0.0", 0) if litellm.force_ipv4 else None,
)
connector=TCPConnector(**connector_kwargs)
),
)
@staticmethod
def _get_ssl_context() -> ssl.SSLContext:
"""
Get the SSL context for the AiohttpTransport
"""
import certifi
return ssl.create_default_context(
cafile=certifi.where()
)
@staticmethod
def _create_httpx_transport() -> Optional[AsyncHTTPTransport]:

View file

@ -2,13 +2,16 @@
Translate from OpenAI's `/v1/chat/completions` to VLLM's `/v1/chat/completions`
"""
from typing import List, Optional, Tuple
from typing import TYPE_CHECKING, List, Optional, Tuple
from litellm.secret_managers.main import get_secret_bool, get_secret_str
from litellm.types.router import LiteLLM_Params
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
if TYPE_CHECKING:
from litellm.types.llms.openai import AllMessageValues
class LiteLLMProxyChatConfig(OpenAIGPTConfig):
def get_supported_openai_params(self, model: str) -> List:
@ -113,3 +116,33 @@ class LiteLLMProxyChatConfig(OpenAIGPTConfig):
)
return model, custom_llm_provider, api_key, api_base
def transform_request(
self,
model: str,
messages: List["AllMessageValues"],
optional_params: dict,
litellm_params: dict,
headers: dict,
) -> dict:
# don't transform the request
return {
"model": model,
"messages": messages,
**optional_params,
}
async def async_transform_request(
self,
model: str,
messages: List["AllMessageValues"],
optional_params: dict,
litellm_params: dict,
headers: dict,
) -> dict:
# don't transform the request
return {
"model": model,
"messages": messages,
**optional_params,
}

View file

@ -6,9 +6,11 @@ Calls done in OpenAI/openai.py as Llama API is openai-compatible.
Docs: https://llama.developer.meta.com/docs/features/compatibility/
"""
from typing import Optional
import warnings
# Suppress Pydantic serialization warnings for Meta Llama responses
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
from litellm import get_model_info, verbose_logger
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
@ -17,27 +19,11 @@ class LlamaAPIConfig(OpenAIGPTConfig):
"""
Llama API has limited support for OpenAI parameters
Tool calling, Functional Calling, tool choice are not working right now
function_call, tools, and tool_choice are working
response_format: only json_schema is working
"""
supports_function_calling: Optional[bool] = None
supports_tool_choice: Optional[bool] = None
try:
model_info = get_model_info(model, custom_llm_provider="meta_llama")
supports_function_calling = model_info.get(
"supports_function_calling", False
)
supports_tool_choice = model_info.get("supports_tool_choice", False)
except Exception as e:
verbose_logger.debug(f"Error getting supported openai params: {e}")
pass
# Function calling and tool choice are now supported on Llama API
optional_params = super().get_supported_openai_params(model)
if not supports_function_calling:
optional_params.remove("function_call")
if not supports_tool_choice:
optional_params.remove("tools")
optional_params.remove("tool_choice")
return optional_params
def map_openai_params(

View file

@ -86,8 +86,9 @@ class MistralConfig(OpenAIGPTConfig):
"seed",
"stop",
"response_format",
"parallel_tool_calls",
]
# Add reasoning support for magistral models
if "magistral" in model.lower():
supported_params.extend(["thinking", "reasoning_effort"])
@ -154,6 +155,8 @@ Then provide a clear, concise answer based on your reasoning."""
if param == "thinking" and "magistral" in model.lower():
# Flag that we need to add reasoning system prompt
optional_params["_add_reasoning_prompt"] = True
if param == "parallel_tool_calls":
optional_params["parallel_tool_calls"] = value
return optional_params
def _get_openai_compatible_provider_info(
@ -287,12 +290,18 @@ Then provide a clear, concise answer based on your reasoning."""
"""
Mistral API only supports `name` in tool messages
If role == tool, then we keep `name`
If role == tool, then we keep `name` if it's not an empty string
Otherwise, we drop `name`
"""
_name = message.get("name") # type: ignore
if _name is not None and message["role"] != "tool":
message.pop("name", None) # type: ignore
if _name is not None:
# Remove name if not a tool message
if message["role"] != "tool":
message.pop("name", None) # type: ignore
# For tool messages, remove name if it's an empty string
elif isinstance(_name, str) and len(_name.strip()) == 0:
message.pop("name", None) # type: ignore
return message

View file

@ -1025,9 +1025,9 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig):
response_tokens_details = CompletionTokensDetailsWrapper()
for detail in usage_metadata["responseTokensDetails"]:
if detail["modality"] == "TEXT":
response_tokens_details.text_tokens = detail["tokenCount"]
response_tokens_details.text_tokens = detail.get("tokenCount", 0)
elif detail["modality"] == "AUDIO":
response_tokens_details.audio_tokens = detail["tokenCount"]
response_tokens_details.audio_tokens = detail.get("tokenCount", 0)
#########################################################
if "promptTokensDetails" in usage_metadata:

View file

@ -46,6 +46,7 @@ class VertexAIAnthropicConfig(AnthropicConfig):
Note: Please make sure to modify the default parameters as required for your use case.
"""
@property
def custom_llm_provider(self) -> Optional[str]:
return "vertex_ai"

View file

@ -79,7 +79,15 @@ class VertexBase:
# Check if the JSON object contains Workload Identity Federation configuration
if "type" in json_obj and json_obj["type"] == "external_account":
creds = self._credentials_from_identity_pool(json_obj)
# If environment_id key contains "aws" value it corresponds to an AWS config file
if (
"credential_source" in json_obj
and "environment_id" in json_obj["credential_source"]
and "aws" in json_obj["credential_source"]["environment_id"]
):
creds = self._credentials_from_identity_pool_with_aws(json_obj)
else:
creds = self._credentials_from_identity_pool(json_obj)
# Check if the JSON object contains Authorized User configuration (via gcloud auth application-default login)
elif "type" in json_obj and json_obj["type"] == "authorized_user":
creds = self._credentials_from_authorized_user(
@ -122,6 +130,11 @@ class VertexBase:
from google.auth import identity_pool
return identity_pool.Credentials.from_info(json_obj)
def _credentials_from_identity_pool_with_aws(self, json_obj):
from google.auth import aws
return aws.Credentials.from_info(json_obj)
def _credentials_from_authorized_user(self, json_obj, scopes):
import google.oauth2.credentials

View file

@ -451,9 +451,9 @@
"max_input_tokens": 128000,
"max_output_tokens": 16384,
"input_cost_per_token": 2.5e-06,
"input_cost_per_audio_token": 4.0e-5,
"input_cost_per_audio_token": 4e-05,
"output_cost_per_token": 1e-05,
"output_cost_per_audio_token": 8.0e-5,
"output_cost_per_audio_token": 8e-05,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -594,7 +594,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 1.5e-06,
"output_cost_per_token": 6e-06,
"cache_read_input_token_cost": 0.375e-06,
"cache_read_input_token_cost": 3.75e-07,
"litellm_provider": "openai",
"mode": "responses",
"supports_pdf_input": true,
@ -744,10 +744,10 @@
"max_tokens": 100000,
"max_input_tokens": 200000,
"max_output_tokens": 100000,
"input_cost_per_token": 20e-06,
"input_cost_per_token_batches": 10e-06,
"output_cost_per_token_batches": 40e-06,
"output_cost_per_token": 80e-06,
"input_cost_per_token": 2e-05,
"input_cost_per_token_batches": 1e-05,
"output_cost_per_token_batches": 4e-05,
"output_cost_per_token": 8e-05,
"litellm_provider": "openai",
"mode": "responses",
"supports_function_calling": true,
@ -774,10 +774,10 @@
"max_tokens": 100000,
"max_input_tokens": 200000,
"max_output_tokens": 100000,
"input_cost_per_token": 20e-06,
"input_cost_per_token_batches": 10e-06,
"output_cost_per_token_batches": 40e-06,
"output_cost_per_token": 80e-06,
"input_cost_per_token": 2e-05,
"input_cost_per_token_batches": 1e-05,
"output_cost_per_token_batches": 4e-05,
"output_cost_per_token": 8e-05,
"litellm_provider": "openai",
"mode": "responses",
"supports_function_calling": true,
@ -806,7 +806,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 2e-06,
"output_cost_per_token": 8e-06,
"cache_read_input_token_cost": 0.5e-06,
"cache_read_input_token_cost": 5e-07,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -837,7 +837,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 2e-06,
"output_cost_per_token": 8e-06,
"cache_read_input_token_cost": 0.5e-06,
"cache_read_input_token_cost": 5e-07,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -2685,7 +2685,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 1.5e-06,
"output_cost_per_token": 6e-06,
"cache_read_input_token_cost": 0.375e-06,
"cache_read_input_token_cost": 3.75e-07,
"litellm_provider": "azure",
"mode": "responses",
"supports_pdf_input": true,
@ -4295,8 +4295,8 @@
"max_tokens": 40000,
"max_input_tokens": 40000,
"max_output_tokens": 40000,
"input_cost_per_token": 0.5e-6,
"output_cost_per_token": 1.5e-6,
"input_cost_per_token": 5e-07,
"output_cost_per_token": 1.5e-06,
"litellm_provider": "mistral",
"mode": "chat",
"source": "https://mistral.ai/pricing#api-pricing",
@ -4309,7 +4309,7 @@
"max_tokens": 40000,
"max_input_tokens": 40000,
"max_output_tokens": 40000,
"input_cost_per_token": 0.5e-06,
"input_cost_per_token": 5e-07,
"output_cost_per_token": 1.5e-06,
"litellm_provider": "mistral",
"mode": "chat",
@ -4579,9 +4579,9 @@
"output_cost_per_token": 4e-06,
"litellm_provider": "xai",
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_reasoning": true,
"supports_response_schema": false,
"source": "https://x.ai/api#pricing",
"supports_web_search": true
@ -4616,21 +4616,6 @@
"source": "https://x.ai/api#pricing",
"supports_web_search": true
},
"xai/grok-3-mini-fast-latest": {
"max_tokens": 131072,
"max_input_tokens": 131072,
"max_output_tokens": 131072,
"input_cost_per_token": 6e-07,
"output_cost_per_token": 4e-06,
"litellm_provider": "xai",
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_response_schema": false,
"source": "https://x.ai/api#pricing",
"supports_web_search": true
},
"xai/grok-vision-beta": {
"max_tokens": 8192,
"max_input_tokens": 8192,
@ -5812,9 +5797,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text",
"image"
@ -5829,9 +5814,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text",
"image"
@ -5846,9 +5831,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text"
],
@ -5862,9 +5847,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text"
],
@ -6735,8 +6720,8 @@
"output_cost_per_token_above_200k_tokens": 1.5e-05,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 2e-3,
"tpm": 8e-6,
"rpm": 2000,
"tpm": 800000,
"supports_system_messages": true,
"supports_function_calling": true,
"supports_vision": true,
@ -6802,8 +6787,9 @@
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true,
"tpm": 8e-6,
"rpm": 1e-5
"tpm": 8000000,
"rpm": 100000,
"supports_pdf_input": true
},
"gemini-2.5-flash": {
"max_tokens": 65535,
@ -6845,7 +6831,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-preview-tts": {
"max_tokens": 65535,
@ -6896,9 +6883,9 @@
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 1.5e-07,
"output_cost_per_token": 6e-07,
"output_cost_per_reasoning_token": 3.5e-06,
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.5e-06,
"output_cost_per_reasoning_token": 2.5e-06,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 10,
@ -6925,7 +6912,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-preview-04-17": {
"max_tokens": 65535,
@ -6966,7 +6954,53 @@
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-lite-preview-06-17": {
"max_tokens": 65535,
"max_input_tokens": 1048576,
"max_output_tokens": 65535,
"max_images_per_prompt": 3000,
"max_videos_per_prompt": 10,
"max_video_length": 1,
"max_audio_length_hours": 8.4,
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 1e-07,
"output_cost_per_token": 4e-07,
"output_cost_per_reasoning_token": 4e-07,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 15,
"tpm": 250000,
"supports_reasoning": true,
"supports_system_messages": true,
"supports_function_calling": true,
"supports_vision": true,
"supports_response_schema": true,
"supports_audio_output": false,
"supports_tool_choice": true,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions",
"/v1/batch"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-preview-05-20": {
"max_tokens": 65535,
@ -6979,9 +7013,9 @@
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 1.5e-07,
"output_cost_per_token": 6e-07,
"output_cost_per_reasoning_token": 3.5e-06,
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.5e-06,
"output_cost_per_reasoning_token": 2.5e-06,
"litellm_provider": "vertex_ai-language-models",
"mode": "chat",
"supports_reasoning": true,
@ -7008,7 +7042,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-preview-04-17": {
"max_tokens": 65535,
@ -7049,7 +7084,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-lite-preview-06-17": {
"max_tokens": 65535,
@ -7061,7 +7097,7 @@
"max_audio_length_hours": 8.4,
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 1e-07,
"output_cost_per_token": 4e-07,
"output_cost_per_reasoning_token": 4e-07,
@ -7091,7 +7127,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.0-flash": {
"max_tokens": 8192,
@ -7237,7 +7274,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-pro-preview-05-06": {
"max_tokens": 65535,
@ -7282,7 +7320,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-pro-preview-03-25": {
"max_tokens": 65535,
@ -7324,7 +7363,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.0-flash-preview-image-generation": {
"max_tokens": 8192,
@ -7649,7 +7689,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-pro-preview-05-06": {
"max_tokens": 65535,
@ -7687,7 +7728,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-pro-preview-03-25": {
"max_tokens": 65535,
@ -7724,7 +7766,8 @@
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini/gemini-2.0-flash-exp": {
"max_tokens": 8192,
@ -8562,13 +8605,13 @@
"litellm_provider": "vertex_ai-image-models",
"mode": "image_generation",
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
},
},
"vertex_ai/imagen-4.0-fast-generate-preview-06-06": {
"output_cost_per_image": 0.02,
"litellm_provider": "vertex_ai-image-models",
"mode": "image_generation",
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
},
},
"vertex_ai/imagen-3.0-generate-002": {
"output_cost_per_image": 0.04,
"litellm_provider": "vertex_ai-image-models",
@ -10830,6 +10873,46 @@
"supports_response_schema": true,
"source": "https://aws.amazon.com/bedrock/pricing/"
},
"apac.amazon.nova-micro-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 300000,
"max_output_tokens": 10000,
"input_cost_per_token": 3.7e-08,
"output_cost_per_token": 1.48e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"apac.amazon.nova-lite-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 128000,
"max_output_tokens": 10000,
"input_cost_per_token": 6.3e-08,
"output_cost_per_token": 2.52e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"apac.amazon.nova-pro-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 300000,
"max_output_tokens": 10000,
"input_cost_per_token": 8.4e-07,
"output_cost_per_token": 3.36e-06,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"us.amazon.nova-premier-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 1000000,
@ -11315,6 +11398,93 @@
"supports_reasoning": true,
"supports_computer_use": true
},
"apac.anthropic.claude-3-haiku-20240307-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 2.5e-07,
"output_cost_per_token": 1.25e-06,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-sonnet-20240229-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-5-sonnet-20240620-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0": {
"max_tokens": 8192,
"max_input_tokens": 200000,
"max_output_tokens": 8192,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"cache_creation_input_token_cost": 3.75e-06,
"cache_read_input_token_cost": 3e-07,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-sonnet-4-20250514-v1:0": {
"max_tokens": 64000,
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"search_context_cost_per_query": {
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01,
"search_context_size_high": 0.01
},
"cache_creation_input_token_cost": 3.75e-06,
"cache_read_input_token_cost": 3e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"tool_use_system_prompt_tokens": 159,
"supports_assistant_prefill": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_reasoning": true,
"supports_computer_use": true
},
"eu.anthropic.claude-3-5-haiku-20241022-v1:0": {
"max_tokens": 8192,
"max_input_tokens": 200000,
@ -14770,7 +14940,7 @@
},
"deepgram/nova-3": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14784,7 +14954,7 @@
},
"deepgram/nova-3-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14798,7 +14968,7 @@
},
"deepgram/nova-3-medical": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00008667,
"input_cost_per_second": 8.667e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14812,7 +14982,7 @@
},
"deepgram/nova-2": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14826,7 +14996,7 @@
},
"deepgram/nova-2-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14840,7 +15010,7 @@
},
"deepgram/nova-2-meeting": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14854,7 +15024,7 @@
},
"deepgram/nova-2-phonecall": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14868,7 +15038,7 @@
},
"deepgram/nova-2-voicemail": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14882,7 +15052,7 @@
},
"deepgram/nova-2-finance": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14896,7 +15066,7 @@
},
"deepgram/nova-2-conversationalai": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14910,7 +15080,7 @@
},
"deepgram/nova-2-video": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14924,7 +15094,7 @@
},
"deepgram/nova-2-drivethru": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14938,7 +15108,7 @@
},
"deepgram/nova-2-automotive": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14952,7 +15122,7 @@
},
"deepgram/nova-2-atc": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14966,7 +15136,7 @@
},
"deepgram/nova": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14980,7 +15150,7 @@
},
"deepgram/nova-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14994,7 +15164,7 @@
},
"deepgram/nova-phonecall": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -15266,4 +15436,4 @@
"notes": "Deepgram's hosted OpenAI Whisper models - pricing may differ from native Deepgram models"
}
}
}
}

View file

@ -1,3 +1,5 @@
from typing import Optional
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
from litellm.proxy._types import UserAPIKeyAuth
@ -8,5 +10,6 @@ class LiteLLMAuthenticatedUser(AuthenticatedUser):
Wrapper class to make UserAPIKeyAuth compatible with MCP's AuthenticatedUser
"""
def __init__(self, user_api_key_auth: UserAPIKeyAuth):
def __init__(self, user_api_key_auth: UserAPIKeyAuth, mcp_auth_header: Optional[str] = None):
self.user_api_key_auth = user_api_key_auth
self.mcp_auth_header = mcp_auth_header

View file

@ -1,11 +1,11 @@
from typing import List, Optional
from typing import List, Optional, Tuple
from starlette.datastructures import Headers
from starlette.requests import Request
from starlette.types import Scope
from litellm._logging import verbose_logger
from litellm.proxy._types import LiteLLM_TeamTable, UserAPIKeyAuth
from litellm.proxy._types import LiteLLM_TeamTable, SpecialHeaders, UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
@ -16,11 +16,14 @@ class UserAPIKeyAuthMCP:
Utilizes the main `user_api_key_auth` function to validate the request
"""
LITELLM_API_KEY_HEADER_NAME_PRIMARY = "x-litellm-api-key"
LITELLM_API_KEY_HEADER_NAME_SECONDARY = "Authorization"
LITELLM_API_KEY_HEADER_NAME_PRIMARY = SpecialHeaders.custom_litellm_api_key.value
LITELLM_API_KEY_HEADER_NAME_SECONDARY = SpecialHeaders.openai_authorization.value
# This is the header to use if you want LiteLLM to use this header for authenticating to the MCP server
LITELLM_MCP_AUTH_HEADER_NAME = SpecialHeaders.mcp_auth.value
@staticmethod
async def user_api_key_auth_mcp(scope: Scope) -> UserAPIKeyAuth:
async def user_api_key_auth_mcp(scope: Scope) -> Tuple[UserAPIKeyAuth, Optional[str]]:
"""
Validate and extract headers from the ASGI scope for MCP requests.
@ -29,6 +32,7 @@ class UserAPIKeyAuthMCP:
Returns:
UserAPIKeyAuth containing validated authentication information
mcp_auth_header: Optional[str] MCP auth header to be passed to the MCP server
Raises:
HTTPException: If headers are invalid or missing required headers
@ -37,6 +41,7 @@ class UserAPIKeyAuthMCP:
litellm_api_key = (
UserAPIKeyAuthMCP.get_litellm_api_key_from_headers(headers) or ""
)
mcp_auth_header = headers.get(UserAPIKeyAuthMCP.LITELLM_MCP_AUTH_HEADER_NAME)
# Create a proper Request object with mock body method to avoid ASGI receive channel issues
request = Request(scope=scope)
@ -52,7 +57,7 @@ class UserAPIKeyAuthMCP:
api_key=litellm_api_key, request=request
)
return validated_user_api_key_auth
return validated_user_api_key_auth, mcp_auth_header
@staticmethod
def get_litellm_api_key_from_headers(headers: Headers) -> Optional[str]:

View file

@ -11,12 +11,12 @@ import hashlib
import json
from typing import Any, Dict, List, Optional, cast
from mcp import ClientSession
from mcp.client.sse import sse_client
from mcp.types import CallToolRequestParams as MCPCallToolRequestParams
from mcp.types import CallToolResult
from mcp.types import Tool as MCPTool
from litellm._logging import verbose_logger
from litellm.experimental_mcp_client.client import MCPClient
from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import (
UserAPIKeyAuthMCP,
)
@ -29,12 +29,6 @@ from litellm.proxy._types import (
MCPTransportType,
UserAPIKeyAuth,
)
try:
from mcp.client.streamable_http import streamablehttp_client
except ImportError:
streamablehttp_client = None # type: ignore
from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPServer
@ -164,7 +158,9 @@ class MCPServerManager:
return list(self.get_registry().keys())
async def list_tools(
self, user_api_key_auth: Optional[UserAPIKeyAuth] = None
self,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
) -> List[MCPTool]:
"""
List all tools available across all MCP Servers.
@ -183,7 +179,10 @@ class MCPServerManager:
verbose_logger.warning(f"MCP Server {server_id} not found")
continue
try:
tools = await self._get_tools_from_server(server)
tools = await self._get_tools_from_server(
server=server,
mcp_auth_header=mcp_auth_header,
)
list_tools_result.extend(tools)
except Exception as e:
verbose_logger.exception(
@ -192,7 +191,30 @@ class MCPServerManager:
return list_tools_result
async def _get_tools_from_server(self, server: MCPServer) -> List[MCPTool]:
#########################################################
# Methods that call the upstream MCP servers
#########################################################
def _create_mcp_client(self, server: MCPServer, mcp_auth_header: Optional[str] = None) -> MCPClient:
"""
Create an MCPClient instance for the given server.
Args:
server (MCPServer): The server configuration
mcp_auth_header: MCP auth header to be passed to the MCP server. This is optional and will be used if provided.
Returns:
MCPClient: Configured MCP client instance
"""
transport = server.transport or MCPTransport.sse
return MCPClient(
server_url=server.url,
transport_type=transport,
auth_type=server.auth_type,
auth_value=mcp_auth_header or server.authentication_token,
timeout=60.0,
)
async def _get_tools_from_server(self, server: MCPServer, mcp_auth_header: Optional[str] = None) -> List[MCPTool]:
"""
Helper method to get tools from a single MCP server.
@ -203,57 +225,51 @@ class MCPServerManager:
List[MCPTool]: List of tools available on the server
"""
verbose_logger.debug(f"Connecting to url: {server.url}")
verbose_logger.info("_get_tools_from_server...")
# send transport to connect to the server
if server.transport is None or server.transport == MCPTransport.sse:
async with sse_client(url=server.url) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools_result = await session.list_tools()
verbose_logger.debug(f"Tools from {server.name}: {tools_result}")
client = self._create_mcp_client(
server=server,
mcp_auth_header=mcp_auth_header,
)
async with client:
tools = await client.list_tools()
verbose_logger.debug(f"Tools from {server.name}: {tools}")
# Update tool to server mapping
for tool in tools_result.tools:
self.tool_name_to_mcp_server_name_mapping[tool.name] = (
server.name
)
# Update tool to server mapping
for tool in tools:
self.tool_name_to_mcp_server_name_mapping[tool.name] = server.name
return tools_result.tools
elif server.transport == MCPTransport.http:
if streamablehttp_client is None:
verbose_logger.error(
"streamablehttp_client not available - install mcp with HTTP support"
)
raise ValueError(
"streamablehttp_client not available - please run `pip install mcp -U`"
)
verbose_logger.debug(f"Using HTTP streamable transport for {server.url}")
async with streamablehttp_client(
url=server.url,
) as (read_stream, write_stream, get_session_id):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
return tools
async def call_tool(
self,
name: str,
arguments: Dict[str, Any],
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
) -> CallToolResult:
"""
Call a tool with the given name and arguments
"""
mcp_server = self._get_mcp_server_from_tool_name(name)
if mcp_server is None:
raise ValueError(f"Tool {name} not found")
if get_session_id is not None:
session_id = get_session_id()
if session_id:
verbose_logger.debug(f"HTTP session ID: {session_id}")
client = self._create_mcp_client(
server=mcp_server,
mcp_auth_header=mcp_auth_header,
)
async with client:
call_tool_params = MCPCallToolRequestParams(
name=name,
arguments=arguments,
)
return await client.call_tool(call_tool_params)
#########################################################
# End of Methods that call the upstream MCP servers
#########################################################
tools_result = await session.list_tools()
verbose_logger.debug(f"Tools from {server.name}: {tools_result}")
# Update tool to server mapping
for tool in tools_result.tools:
self.tool_name_to_mcp_server_name_mapping[tool.name] = (
server.name
)
return tools_result.tools
else:
verbose_logger.warning(f"Unsupported transport type: {server.transport}")
return []
def initialize_tool_name_to_mcp_server_name_mapping(self):
"""
@ -278,46 +294,6 @@ class MCPServerManager:
for tool in tools:
self.tool_name_to_mcp_server_name_mapping[tool.name] = server.name
async def call_tool(self, name: str, arguments: Dict[str, Any]):
"""
Call a tool with the given name and arguments
"""
mcp_server = self._get_mcp_server_from_tool_name(name)
if mcp_server is None:
raise ValueError(f"Tool {name} not found")
elif mcp_server.transport is None or mcp_server.transport == MCPTransport.sse:
async with sse_client(url=mcp_server.url) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
return await session.call_tool(name, arguments)
elif mcp_server.transport == MCPTransport.http:
if streamablehttp_client is None:
verbose_logger.error(
"streamablehttp_client not available - install mcp with HTTP support"
)
raise ValueError(
"streamablehttp_client not available - please run `pip install mcp -U`"
)
verbose_logger.debug(
f"Using HTTP streamable transport for tool call: {name}"
)
async with streamablehttp_client(
url=mcp_server.url,
) as (read_stream, write_stream, get_session_id):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
if get_session_id is not None:
session_id = get_session_id()
if session_id:
verbose_logger.debug(
f"HTTP session ID for tool call: {session_id}"
)
return await session.call_tool(name, arguments)
else:
return CallToolResult(content=[], isError=True)
def _get_mcp_server_from_tool_name(self, tool_name: str) -> Optional[MCPServer]:
"""
Get the MCP Server from the tool name

View file

@ -70,7 +70,9 @@ if MCP_AVAILABLE:
if server_id and server.server_id != server_id:
continue
try:
tools = await global_mcp_server_manager._get_tools_from_server(server)
tools = await global_mcp_server_manager._get_tools_from_server(
server=server,
)
for tool in tools:
list_tools_result.append(
ListMCPToolsRestAPIResponseObject(

View file

@ -4,7 +4,7 @@ LiteLLM MCP Server Routes
import asyncio
import contextlib
from typing import Any, AsyncIterator, Dict, List, Optional, Union
from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Union
from fastapi import FastAPI, HTTPException
from pydantic import ConfigDict
@ -166,11 +166,14 @@ if MCP_AVAILABLE:
List all available tools
"""
# Get user authentication from context variable
user_api_key_auth = get_auth_context()
user_api_key_auth, mcp_auth_header = get_auth_context()
verbose_logger.debug(
f"MCP list_tools - User API Key Auth from context: {user_api_key_auth}"
)
return await _list_mcp_tools(user_api_key_auth)
return await _list_mcp_tools(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
@server.call_tool()
async def mcp_server_tool_call(
@ -190,9 +193,15 @@ if MCP_AVAILABLE:
HTTPException: If tool not found or arguments missing
"""
# Validate arguments
user_api_key_auth, mcp_auth_header = get_auth_context()
verbose_logger.debug(
f"MCP mcp_server_tool_call - User API Key Auth from context: {user_api_key_auth}"
)
response = await call_mcp_tool(
name=name,
arguments=arguments,
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
return response
@ -206,6 +215,7 @@ if MCP_AVAILABLE:
async def _list_mcp_tools(
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
) -> List[MCPTool]:
"""
List all available tools
@ -229,6 +239,7 @@ if MCP_AVAILABLE:
tools_from_mcp_servers: List[MCPTool] = (
await global_mcp_server_manager.list_tools(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
)
verbose_logger.debug("TOOLS FROM MCP SERVERS: %s", tools_from_mcp_servers)
@ -238,7 +249,11 @@ if MCP_AVAILABLE:
@client
async def call_mcp_tool(
name: str, arguments: Optional[Dict[str, Any]] = None, **kwargs: Any
name: str,
arguments: Optional[Dict[str, Any]] = None,
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
**kwargs: Any
) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]:
"""
Call a specific tool with the provided arguments
@ -270,7 +285,12 @@ if MCP_AVAILABLE:
# Try managed server tool first
if name in global_mcp_server_manager.tool_name_to_mcp_server_name_mapping:
return await _handle_managed_mcp_tool(name, arguments)
return await _handle_managed_mcp_tool(
name=name,
arguments=arguments,
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
# Fall back to local tool registry
return await _handle_local_mcp_tool(name, arguments)
@ -295,12 +315,17 @@ if MCP_AVAILABLE:
)
async def _handle_managed_mcp_tool(
name: str, arguments: Dict[str, Any]
name: str,
arguments: Dict[str, Any],
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
mcp_auth_header: Optional[str] = None,
) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]:
"""Handle tool execution for managed server tools"""
call_tool_result = await global_mcp_server_manager.call_tool(
name=name,
arguments=arguments,
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
verbose_logger.debug("CALL TOOL RESULT: %s", call_tool_result)
return call_tool_result.content
@ -325,11 +350,14 @@ if MCP_AVAILABLE:
"""Handle MCP requests through StreamableHTTP."""
try:
# Validate headers and log request info
user_api_key_auth: UserAPIKeyAuth = (
user_api_key_auth, mcp_auth_header = (
await UserAPIKeyAuthMCP.user_api_key_auth_mcp(scope)
)
# Set the auth context variable for easy access in MCP functions
set_auth_context(user_api_key_auth)
set_auth_context(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
# Ensure session managers are initialized
if not _SESSION_MANAGERS_INITIALIZED:
@ -346,11 +374,14 @@ if MCP_AVAILABLE:
"""Handle MCP requests through SSE."""
try:
# Validate headers and log request info
user_api_key_auth: UserAPIKeyAuth = (
user_api_key_auth, mcp_auth_header = (
await UserAPIKeyAuthMCP.user_api_key_auth_mcp(scope)
)
# Set the auth context variable for easy access in MCP functions
set_auth_context(user_api_key_auth)
set_auth_context(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
# Ensure session managers are initialized
if not _SESSION_MANAGERS_INITIALIZED:
@ -390,17 +421,31 @@ if MCP_AVAILABLE:
############ Auth Context Functions ####################
########################################################
def set_auth_context(user_api_key_auth: UserAPIKeyAuth) -> None:
"""Set the UserAPIKeyAuth in the auth context variable."""
auth_user = LiteLLMAuthenticatedUser(user_api_key_auth)
def set_auth_context(user_api_key_auth: UserAPIKeyAuth, mcp_auth_header: Optional[str] = None) -> None:
"""
Set the UserAPIKeyAuth in the auth context variable.
Args:
user_api_key_auth: UserAPIKeyAuth object
mcp_auth_header: MCP auth header to be passed to the MCP server
"""
auth_user = LiteLLMAuthenticatedUser(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,
)
auth_context_var.set(auth_user)
def get_auth_context() -> Optional[UserAPIKeyAuth]:
"""Get the UserAPIKeyAuth from the auth context variable."""
def get_auth_context() -> Tuple[Optional[UserAPIKeyAuth], Optional[str]]:
"""
Get the UserAPIKeyAuth from the auth context variable.
Returns:
Tuple[Optional[UserAPIKeyAuth], Optional[str]]: UserAPIKeyAuth object and MCP auth header
"""
auth_user = auth_context_var.get()
if auth_user and isinstance(auth_user, LiteLLMAuthenticatedUser):
return auth_user.user_api_key_auth
return None
return auth_user.user_api_key_auth, auth_user.mcp_auth_header
return None, None
########################################################
############ End of Auth Context Functions #############

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/litellm-asset-prefix/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}

View file

@ -1,34 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
</style>
<g id="_x31__stroke">
<g id="Amazon_1_">
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
<g id="Amazon">
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
</style>
<g id="_x31__stroke">
<g id="Amazon_1_">
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
<g id="Amazon">
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,89 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
</style>
<g id="Contact">
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
<g id="map" transform="translate(-6.000000, 1027.000000)">
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
<g id="Group-26" transform="translate(50.000000, 51.000000)">
<g id="Group-3">
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
C195.4,359,207.7,363,221,363V344.6z"/>
</g>
<g id="Group" transform="translate(22.000000, 13.000000)">
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
v11h-17.6V207.6z"/>
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
</style>
<g id="Contact">
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
<g id="map" transform="translate(-6.000000, 1027.000000)">
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
<g id="Group-26" transform="translate(50.000000, 51.000000)">
<g id="Group-3">
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
C195.4,359,207.7,363,221,363V344.6z"/>
</g>
<g id="Group" transform="translate(22.000000, 13.000000)">
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
v11h-17.6V207.6z"/>
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#566AB2;}
</style>
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#566AB2;}
</style>
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
<stop offset="0.002" style="stop-color:#9C55D4"/>
<stop offset="0.003" style="stop-color:#20808D"/>
<stop offset="0.3731" style="stop-color:#218F9B"/>
<stop offset="1" style="stop-color:#22B1BC"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
<stop offset="0.002" style="stop-color:#9C55D4"/>
<stop offset="0.003" style="stop-color:#20808D"/>
<stop offset="0.3731" style="stop-color:#218F9B"/>
<stop offset="1" style="stop-color:#22B1BC"/>
</linearGradient>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
2:I[19107,[],"ClientPageRoot"]
3:I[86917,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","402","static/chunks/402-ba983153c902cb17.js","313","static/chunks/313-cf4a28394ee560d6.js","899","static/chunks/899-ebef5c8ffc176e86.js","276","static/chunks/276-859a00a1fe124231.js","250","static/chunks/250-562275aca86e6d43.js","699","static/chunks/699-7660d0629950118b.js","931","static/chunks/app/page-00691d283cb03cc8.js"],"default",1]
3:I[34125,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","402","static/chunks/402-239a4ed70dc393da.js","313","static/chunks/313-cf4a28394ee560d6.js","899","static/chunks/899-0459bccb48a6666d.js","966","static/chunks/966-d23842bbd48c2bc2.js","250","static/chunks/250-8d759a787d622b48.js","699","static/chunks/699-96a60f79614507d5.js","931","static/chunks/app/page-86bbb3edba7ec5f0.js"],"default",1]
4:I[4707,[],""]
5:I[36423,[],""]
0:["aV7wX1vsNVbDvWm3r-ZF3",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/20729c6c90dc7f33.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
0:["zyqLTLglamGh14G70gBJG",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
1:null

View file

@ -1,7 +1,7 @@
2:I[19107,[],"ClientPageRoot"]
3:I[52829,["402","static/chunks/402-ba983153c902cb17.js","313","static/chunks/313-cf4a28394ee560d6.js","250","static/chunks/250-562275aca86e6d43.js","699","static/chunks/699-7660d0629950118b.js","418","static/chunks/app/model_hub/page-ce40c5a05f3174ca.js"],"default",1]
3:I[52829,["402","static/chunks/402-239a4ed70dc393da.js","313","static/chunks/313-cf4a28394ee560d6.js","250","static/chunks/250-8d759a787d622b48.js","699","static/chunks/699-96a60f79614507d5.js","418","static/chunks/app/model_hub/page-ce40c5a05f3174ca.js"],"default",1]
4:I[4707,[],""]
5:I[36423,[],""]
0:["aV7wX1vsNVbDvWm3r-ZF3",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/20729c6c90dc7f33.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
0:["zyqLTLglamGh14G70gBJG",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
1:null

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
2:I[19107,[],"ClientPageRoot"]
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","402","static/chunks/402-ba983153c902cb17.js","899","static/chunks/899-ebef5c8ffc176e86.js","250","static/chunks/250-562275aca86e6d43.js","461","static/chunks/app/onboarding/page-74706b15590d94d7.js"],"default",1]
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","402","static/chunks/402-239a4ed70dc393da.js","899","static/chunks/899-0459bccb48a6666d.js","250","static/chunks/250-8d759a787d622b48.js","461","static/chunks/app/onboarding/page-be52e5770ada890a.js"],"default",1]
4:I[4707,[],""]
5:I[36423,[],""]
0:["aV7wX1vsNVbDvWm3r-ZF3",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/20729c6c90dc7f33.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
0:["zyqLTLglamGh14G70gBJG",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/27aa365198bdc7c8.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
1:null

View file

@ -3,6 +3,12 @@ model_list:
litellm_params:
model: codex-mini-latest
api_key: os.environ/OPENAI_API_KEY
- model_name: bedrock/*
litellm_params:
model: bedrock/*
- model_name: eu.anthropic.claude-3-5-sonnet-20240620-v1:0
litellm_params:
model: eu.anthropic.claude-3-5-sonnet-20240620-v1:0
- model_name: "gpt-4o-mini-openai"
litellm_params:
model: gpt-4o-mini

View file

@ -17,6 +17,13 @@ from typing_extensions import Required, TypedDict
from litellm.types.integrations.slack_alerting import AlertType
from litellm.types.llms.openai import AllMessageValues, OpenAIFileObject
from litellm.types.mcp import (
MCPAuthType,
MCPSpecVersion,
MCPSpecVersionType,
MCPTransport,
MCPTransportType,
)
from litellm.types.router import RouterErrors, UpdateRouterConfig
from litellm.types.utils import (
CallTypes,
@ -830,32 +837,6 @@ class SpecialMCPServerName(str, enum.Enum):
all_team_servers = "all-team-mcpservers"
all_proxy_servers = "all-proxy-mcpservers"
class MCPTransport(str, enum.Enum):
sse = "sse"
http = "http"
class MCPSpecVersion(str, enum.Enum):
nov_2024 = "2024-11-05"
mar_2025 = "2025-03-26"
class MCPAuth(str, enum.Enum):
none = "none"
api_key = "api_key"
bearer_token = "bearer_token"
basic = "basic"
# MCP Literals
MCPTransportType = Literal[MCPTransport.sse, MCPTransport.http]
MCPSpecVersionType = Literal[MCPSpecVersion.nov_2024, MCPSpecVersion.mar_2025]
MCPAuthType = Optional[
Literal[MCPAuth.none, MCPAuth.api_key, MCPAuth.bearer_token, MCPAuth.basic]
]
# MCP Proxy Request Types
class NewMCPServerRequest(LiteLLMPydanticObjectBase):
server_id: Optional[str] = None
@ -2703,6 +2684,7 @@ class SpecialHeaders(enum.Enum):
google_ai_studio_authorization = "x-goog-api-key"
azure_apim_authorization = "Ocp-Apim-Subscription-Key"
custom_litellm_api_key = "x-litellm-api-key"
mcp_auth = "x-mcp-auth"
class LitellmDataForBackendLLMCall(TypedDict, total=False):

View file

@ -199,14 +199,17 @@ async def _add_user_to_team(
str(e)
)
)
except ProxyException as e:
except Exception as e:
if "already exists" in str(e) or "doesn't exist" in str(e):
verbose_proxy_logger.debug(
"litellm.proxy.management_endpoints.internal_user_endpoints.new_user(): User already exists in team - {}".format(
str(e)
)
)
elif ProxyErrorTypes.team_member_already_in_team in e.type:
elif (
isinstance(e, ProxyException)
and ProxyErrorTypes.team_member_already_in_team in e.type
):
verbose_proxy_logger.debug(
"litellm.proxy.management_endpoints.internal_user_endpoints.new_user(): User already exists in team - {}".format(
str(e)

View file

@ -1,18 +1,19 @@
"""
TAG MANAGEMENT
All /tag management endpoints
All /tag management endpoints
/tag/new
/tag/new
/tag/info
/tag/update
/tag/delete
/tag/list
"""
import asyncio
import datetime
import json
from typing import Dict, List, Optional
from typing import TYPE_CHECKING, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException
@ -33,6 +34,10 @@ from litellm.types.tag_management import (
TagUpdateRequest,
)
if TYPE_CHECKING:
from litellm import Router
from litellm.types.router import Deployment
router = APIRouter()
@ -111,6 +116,33 @@ async def _save_tags_config(prisma_client, tags_config: Dict[str, TagConfig]):
)
async def get_deployments_by_model(
model: str, llm_router: "Router"
) -> List["Deployment"]:
"""
Get all deployments by model
"""
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
# Check if model id
deployment = llm_router.get_deployment(model_id=model)
if deployment is not None:
return [deployment]
# Check if model name
deployments = llm_router.get_model_list(model_name=model)
if deployments is None:
return []
return [
Deployment(
model_name=deployment["model_name"],
litellm_params=LiteLLM_Params(**deployment["litellm_params"]), # type: ignore
model_info=ModelInfo(**deployment.get("model_info") or {}),
)
for deployment in deployments
]
@router.post(
"/tag/new",
tags=["tag management"],
@ -126,12 +158,19 @@ async def new_tag(
Parameters:
- name: str - The name of the tag
- description: Optional[str] - Description of what this tag represents
- models: List[str] - List of LLM models allowed for this tag
- models: List[str] - List of either 'model_id' or 'model_name' allowed for this tag
"""
from litellm.proxy.proxy_server import prisma_client
from litellm.proxy._types import CommonProxyErrors
from litellm.proxy.proxy_server import llm_router, prisma_client
if prisma_client is None:
raise HTTPException(status_code=500, detail="Database not connected")
raise HTTPException(
status_code=500, detail=CommonProxyErrors.db_not_connected_error.value
)
if llm_router is None:
raise HTTPException(
status_code=500, detail=CommonProxyErrors.no_llm_router.value
)
try:
# Get existing tags config
tags_config = await _get_tags_config(prisma_client)
@ -160,11 +199,19 @@ async def new_tag(
# Update models with new tag
if tag.models:
for model_id in tag.models:
await _add_tag_to_deployment(
model_id=model_id,
tag=tag.name,
tasks = []
for model in tag.models:
deployments = await get_deployments_by_model(model, llm_router)
tasks.extend(
[
_add_tag_to_deployment(
deployment=deployment,
tag=tag.name,
)
for deployment in deployments
]
)
await asyncio.gather(*tasks)
# Get model names for response
model_info = await _get_model_names(prisma_client, tag.models or [])
@ -179,27 +226,26 @@ async def new_tag(
raise HTTPException(status_code=500, detail=str(e))
async def _add_tag_to_deployment(model_id: str, tag: str):
async def _add_tag_to_deployment(deployment: "Deployment", tag: str):
"""Helper function to add tag to deployment"""
from litellm.proxy.proxy_server import prisma_client
if prisma_client is None:
raise HTTPException(status_code=500, detail="Database not connected")
deployment = await prisma_client.db.litellm_proxymodeltable.find_unique(
where={"model_id": model_id}
)
if deployment is None:
raise HTTPException(status_code=404, detail=f"Deployment {model_id} not found")
litellm_params = deployment.litellm_params
if "tags" not in litellm_params:
litellm_params["tags"] = []
litellm_params["tags"].append(tag)
await prisma_client.db.litellm_proxymodeltable.update(
where={"model_id": model_id},
data={"litellm_params": safe_dumps(litellm_params)},
)
try:
await prisma_client.db.litellm_proxymodeltable.update(
where={"model_id": deployment.model_info.id},
data={"litellm_params": safe_dumps(litellm_params)},
)
except Exception as e:
verbose_proxy_logger.exception(f"Error adding tag to deployment: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.post(

View file

@ -752,21 +752,6 @@ try:
current_dir = os.path.dirname(os.path.abspath(__file__))
ui_path = os.path.join(current_dir, "_experimental", "out")
litellm_asset_prefix = "/litellm-asset-prefix"
# # Mount the _next directory at the root level
app.mount(
"/_next",
StaticFiles(directory=os.path.join(ui_path, "_next")),
name="next_static",
)
app.mount(
f"{litellm_asset_prefix}/_next",
StaticFiles(directory=os.path.join(ui_path, "_next")),
name="next_static",
)
# print(f"mounted _next at {server_root_path}/ui/_next")
app.mount("/ui", StaticFiles(directory=ui_path, html=True), name="ui")
# Iterate through files in the UI directory
for root, dirs, files in os.walk(ui_path):
for filename in files:
@ -792,19 +777,37 @@ try:
# Replace the asset prefix with the server root path
modified_content = content.replace(
f"{litellm_asset_prefix}", server_root_path
f"{litellm_asset_prefix}",
f"{server_root_path}",
)
# Replace the /.well-known/litellm-ui-config with the server root path
modified_content = modified_content.replace(
"/litellm/.well-known/litellm-ui-config",
f"{server_root_path}/.well-known/litellm-ui-config",
)
with open(file_path, "w", encoding="utf-8") as f:
f.write(modified_content)
except UnicodeDecodeError:
# Skip binary files that can't be decoded
continue
# # Mount the _next directory at the root level
app.mount(
"/_next",
StaticFiles(directory=os.path.join(ui_path, "_next")),
name="next_static",
)
app.mount(
f"{litellm_asset_prefix}/_next",
StaticFiles(directory=os.path.join(ui_path, "_next")),
name="next_static",
)
# print(f"mounted _next at {server_root_path}/ui/_next")
app.mount("/ui", StaticFiles(directory=ui_path, html=True), name="ui")
# Handle HTML file restructuring
for filename in os.listdir(ui_path):
if filename.endswith(".html") and filename != "index.html":

View file

@ -2,7 +2,7 @@
Handles transforming from Responses API -> LiteLLM completion (Chat Completion API)
"""
from typing import Any, Dict, List, Optional, Union, cast
from typing import Any, Dict, List, Literal, Optional, Tuple, Union, cast
from openai.types.responses.tool_param import FunctionToolParam
from typing_extensions import TypedDict
@ -32,6 +32,8 @@ from litellm.types.llms.openai import (
ChatCompletionUserMessage,
GenericChatCompletionMessage,
OpenAIMcpServerTool,
OpenAIWebSearchOptions,
OpenAIWebSearchUserLocation,
Reasoning,
ResponseAPIUsage,
ResponseInputParam,
@ -109,6 +111,9 @@ class LiteLLMCompletionResponsesConfig:
"""
Transform a Responses API request into a Chat Completion request
"""
tools, web_search_options = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
responses_api_request.get("tools") or [] # type: ignore
)
litellm_completion_request: dict = {
"messages": LiteLLMCompletionResponsesConfig.transform_responses_api_input_to_messages(
input=input,
@ -116,9 +121,7 @@ class LiteLLMCompletionResponsesConfig:
),
"model": model,
"tool_choice": responses_api_request.get("tool_choice"),
"tools": LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
responses_api_request.get("tools") or [] # type: ignore
),
"tools": tools,
"top_p": responses_api_request.get("top_p"),
"user": responses_api_request.get("user"),
"temperature": responses_api_request.get("temperature"),
@ -127,6 +130,7 @@ class LiteLLMCompletionResponsesConfig:
"stream": stream,
"metadata": kwargs.get("metadata"),
"service_tier": kwargs.get("service_tier"),
"web_search_options": web_search_options,
# litellm specific params
"custom_llm_provider": custom_llm_provider,
}
@ -468,32 +472,40 @@ class LiteLLMCompletionResponsesConfig:
@staticmethod
def transform_responses_api_tools_to_chat_completion_tools(
tools: Optional[List[Union[FunctionToolParam, OpenAIMcpServerTool]]],
) -> List[Union[ChatCompletionToolParam, OpenAIMcpServerTool]]:
) -> Tuple[List[Union[ChatCompletionToolParam, OpenAIMcpServerTool]], Optional[OpenAIWebSearchOptions]]:
"""
Transform a Responses API tools into a Chat Completion tools
"""
if tools is None:
return []
return [], None
chat_completion_tools: List[
Union[ChatCompletionToolParam, OpenAIMcpServerTool]
] = []
web_search_options: Optional[OpenAIWebSearchOptions] = None
for tool in tools:
if tool.get("type") == "mcp":
chat_completion_tools.append(cast(OpenAIMcpServerTool, tool))
elif tool.get("type") == "web_search_preview" or tool.get("type") == "web_search":
_search_context_size: Literal["low", "medium", "high"] = cast(Literal["low", "medium", "high"], tool.get("search_context_size"))
_user_location: Optional[OpenAIWebSearchUserLocation] = cast(Optional[OpenAIWebSearchUserLocation], tool.get("user_location") or None)
web_search_options = OpenAIWebSearchOptions(
search_context_size=_search_context_size,
user_location=_user_location,
)
else:
typed_tool = cast(FunctionToolParam, tool)
chat_completion_tools.append(
ChatCompletionToolParam(
type="function",
function=ChatCompletionToolParamFunctionChunk(
name=typed_tool["name"],
name=typed_tool.get("name") or "",
description=typed_tool.get("description") or "",
parameters=dict(typed_tool.get("parameters", {}) or {}),
strict=typed_tool.get("strict", False) or False,
),
)
)
return chat_completion_tools
return chat_completion_tools, web_search_options
@staticmethod
def transform_chat_completion_tools_to_responses_tools(

View file

@ -71,6 +71,7 @@ class UserAPIKeyLabelNames(Enum):
STATUS_CODE = "status_code"
FALLBACK_MODEL = "fallback_model"
ROUTE = "route"
MODEL_GROUP = "model_group"
DEFINED_PROMETHEUS_METRICS = Literal[
@ -161,7 +162,7 @@ class PrometheusMetricLabels:
]
litellm_overhead_latency_metric = [
UserAPIKeyLabelNames.REQUESTED_MODEL.value,
UserAPIKeyLabelNames.MODEL_GROUP.value,
UserAPIKeyLabelNames.API_PROVIDER.value,
UserAPIKeyLabelNames.API_BASE.value,
UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value,
@ -170,7 +171,7 @@ class PrometheusMetricLabels:
]
litellm_remaining_requests_metric = [
UserAPIKeyLabelNames.REQUESTED_MODEL.value,
UserAPIKeyLabelNames.MODEL_GROUP.value,
UserAPIKeyLabelNames.API_PROVIDER.value,
UserAPIKeyLabelNames.API_BASE.value,
UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value,
@ -179,7 +180,7 @@ class PrometheusMetricLabels:
]
litellm_remaining_tokens_metric = [
UserAPIKeyLabelNames.REQUESTED_MODEL.value,
UserAPIKeyLabelNames.MODEL_GROUP.value,
UserAPIKeyLabelNames.API_PROVIDER.value,
UserAPIKeyLabelNames.API_BASE.value,
UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value,
@ -335,6 +336,9 @@ class UserAPIKeyLabelValues(BaseModel):
team_alias: Annotated[
Optional[str], Field(..., alias=UserAPIKeyLabelNames.TEAM_ALIAS.value)
] = None
model_group: Annotated[
Optional[str], Field(..., alias=UserAPIKeyLabelNames.MODEL_GROUP.value)
] = None
requested_model: Annotated[
Optional[str], Field(..., alias=UserAPIKeyLabelNames.REQUESTED_MODEL.value)
] = None

29
litellm/types/mcp.py Normal file
View file

@ -0,0 +1,29 @@
import enum
from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict
from typing_extensions import TypedDict
class MCPTransport(str, enum.Enum):
sse = "sse"
http = "http"
class MCPSpecVersion(str, enum.Enum):
nov_2024 = "2024-11-05"
mar_2025 = "2025-03-26"
class MCPAuth(str, enum.Enum):
none = "none"
api_key = "api_key"
bearer_token = "bearer_token"
basic = "basic"
# MCP Literals
MCPTransportType = Literal[MCPTransport.sse, MCPTransport.http]
MCPSpecVersionType = Literal[MCPSpecVersion.nov_2024, MCPSpecVersion.mar_2025]
MCPAuthType = Optional[
Literal[MCPAuth.none, MCPAuth.api_key, MCPAuth.bearer_token, MCPAuth.basic]
]

View file

@ -16,9 +16,9 @@ class MCPServer(BaseModel):
server_id: str
name: str
url: str
# TODO: alter the types to be the Literal explicit
transport: MCPTransportType
spec_version: MCPSpecVersionType
auth_type: Optional[MCPAuthType] = None
authentication_token: Optional[str] = None
mcp_info: Optional[MCPInfo] = None
model_config = ConfigDict(arbitrary_types_allowed=True)

View file

@ -451,9 +451,9 @@
"max_input_tokens": 128000,
"max_output_tokens": 16384,
"input_cost_per_token": 2.5e-06,
"input_cost_per_audio_token": 4.0e-5,
"input_cost_per_audio_token": 4e-05,
"output_cost_per_token": 1e-05,
"output_cost_per_audio_token": 8.0e-5,
"output_cost_per_audio_token": 8e-05,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -594,7 +594,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 1.5e-06,
"output_cost_per_token": 6e-06,
"cache_read_input_token_cost": 0.375e-06,
"cache_read_input_token_cost": 3.75e-07,
"litellm_provider": "openai",
"mode": "responses",
"supports_pdf_input": true,
@ -744,10 +744,10 @@
"max_tokens": 100000,
"max_input_tokens": 200000,
"max_output_tokens": 100000,
"input_cost_per_token": 20e-06,
"input_cost_per_token_batches": 10e-06,
"output_cost_per_token_batches": 40e-06,
"output_cost_per_token": 80e-06,
"input_cost_per_token": 2e-05,
"input_cost_per_token_batches": 1e-05,
"output_cost_per_token_batches": 4e-05,
"output_cost_per_token": 8e-05,
"litellm_provider": "openai",
"mode": "responses",
"supports_function_calling": true,
@ -774,10 +774,10 @@
"max_tokens": 100000,
"max_input_tokens": 200000,
"max_output_tokens": 100000,
"input_cost_per_token": 20e-06,
"input_cost_per_token_batches": 10e-06,
"output_cost_per_token_batches": 40e-06,
"output_cost_per_token": 80e-06,
"input_cost_per_token": 2e-05,
"input_cost_per_token_batches": 1e-05,
"output_cost_per_token_batches": 4e-05,
"output_cost_per_token": 8e-05,
"litellm_provider": "openai",
"mode": "responses",
"supports_function_calling": true,
@ -806,7 +806,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 2e-06,
"output_cost_per_token": 8e-06,
"cache_read_input_token_cost": 0.5e-06,
"cache_read_input_token_cost": 5e-07,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -837,7 +837,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 2e-06,
"output_cost_per_token": 8e-06,
"cache_read_input_token_cost": 0.5e-06,
"cache_read_input_token_cost": 5e-07,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": true,
@ -2685,7 +2685,7 @@
"max_output_tokens": 100000,
"input_cost_per_token": 1.5e-06,
"output_cost_per_token": 6e-06,
"cache_read_input_token_cost": 0.375e-06,
"cache_read_input_token_cost": 3.75e-07,
"litellm_provider": "azure",
"mode": "responses",
"supports_pdf_input": true,
@ -4295,8 +4295,8 @@
"max_tokens": 40000,
"max_input_tokens": 40000,
"max_output_tokens": 40000,
"input_cost_per_token": 0.5e-6,
"output_cost_per_token": 1.5e-6,
"input_cost_per_token": 5e-07,
"output_cost_per_token": 1.5e-06,
"litellm_provider": "mistral",
"mode": "chat",
"source": "https://mistral.ai/pricing#api-pricing",
@ -4309,7 +4309,7 @@
"max_tokens": 40000,
"max_input_tokens": 40000,
"max_output_tokens": 40000,
"input_cost_per_token": 0.5e-06,
"input_cost_per_token": 5e-07,
"output_cost_per_token": 1.5e-06,
"litellm_provider": "mistral",
"mode": "chat",
@ -4579,9 +4579,9 @@
"output_cost_per_token": 4e-06,
"litellm_provider": "xai",
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_reasoning": true,
"supports_response_schema": false,
"source": "https://x.ai/api#pricing",
"supports_web_search": true
@ -4616,21 +4616,6 @@
"source": "https://x.ai/api#pricing",
"supports_web_search": true
},
"xai/grok-3-mini-fast-latest": {
"max_tokens": 131072,
"max_input_tokens": 131072,
"max_output_tokens": 131072,
"input_cost_per_token": 6e-07,
"output_cost_per_token": 4e-06,
"litellm_provider": "xai",
"mode": "chat",
"supports_reasoning": true,
"supports_function_calling": true,
"supports_tool_choice": true,
"supports_response_schema": false,
"source": "https://x.ai/api#pricing",
"supports_web_search": true
},
"xai/grok-vision-beta": {
"max_tokens": 8192,
"max_input_tokens": 8192,
@ -5812,9 +5797,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text",
"image"
@ -5829,9 +5814,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text",
"image"
@ -5846,9 +5831,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text"
],
@ -5862,9 +5847,9 @@
"max_output_tokens": 4028,
"litellm_provider": "meta_llama",
"mode": "chat",
"supports_function_calling": false,
"supports_function_calling": true,
"source": "https://llama.developer.meta.com/docs/models",
"supports_tool_choice": false,
"supports_tool_choice": true,
"supported_modalities": [
"text"
],
@ -6735,8 +6720,8 @@
"output_cost_per_token_above_200k_tokens": 1.5e-05,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 2e-3,
"tpm": 8e-6,
"rpm": 2000,
"tpm": 800000,
"supports_system_messages": true,
"supports_function_calling": true,
"supports_vision": true,
@ -6802,8 +6787,9 @@
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true,
"tpm": 8e-6,
"rpm": 1e-5
"tpm": 8000000,
"rpm": 100000,
"supports_pdf_input": true
},
"gemini-2.5-flash": {
"max_tokens": 65535,
@ -6845,7 +6831,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-preview-tts": {
"max_tokens": 65535,
@ -6896,9 +6883,9 @@
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 1.5e-07,
"output_cost_per_token": 6e-07,
"output_cost_per_reasoning_token": 3.5e-06,
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.5e-06,
"output_cost_per_reasoning_token": 2.5e-06,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 10,
@ -6925,7 +6912,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-preview-04-17": {
"max_tokens": 65535,
@ -6966,7 +6954,53 @@
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-flash-lite-preview-06-17": {
"max_tokens": 65535,
"max_input_tokens": 1048576,
"max_output_tokens": 65535,
"max_images_per_prompt": 3000,
"max_videos_per_prompt": 10,
"max_video_length": 1,
"max_audio_length_hours": 8.4,
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 1e-07,
"output_cost_per_token": 4e-07,
"output_cost_per_reasoning_token": 4e-07,
"litellm_provider": "gemini",
"mode": "chat",
"rpm": 15,
"tpm": 250000,
"supports_reasoning": true,
"supports_system_messages": true,
"supports_function_calling": true,
"supports_vision": true,
"supports_response_schema": true,
"supports_audio_output": false,
"supports_tool_choice": true,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/completions",
"/v1/batch"
],
"supported_modalities": [
"text",
"image",
"audio",
"video"
],
"supported_output_modalities": [
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-preview-05-20": {
"max_tokens": 65535,
@ -6979,9 +7013,9 @@
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 1.5e-07,
"output_cost_per_token": 6e-07,
"output_cost_per_reasoning_token": 3.5e-06,
"input_cost_per_token": 3e-07,
"output_cost_per_token": 2.5e-06,
"output_cost_per_reasoning_token": 2.5e-06,
"litellm_provider": "vertex_ai-language-models",
"mode": "chat",
"supports_reasoning": true,
@ -7008,7 +7042,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-preview-04-17": {
"max_tokens": 65535,
@ -7049,7 +7084,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-flash-lite-preview-06-17": {
"max_tokens": 65535,
@ -7061,7 +7097,7 @@
"max_audio_length_hours": 8.4,
"max_audio_per_prompt": 1,
"max_pdf_size_mb": 30,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_audio_token": 5e-07,
"input_cost_per_token": 1e-07,
"output_cost_per_token": 4e-07,
"output_cost_per_reasoning_token": 4e-07,
@ -7091,7 +7127,8 @@
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini-2.0-flash": {
"max_tokens": 8192,
@ -7237,7 +7274,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-pro-preview-05-06": {
"max_tokens": 65535,
@ -7282,7 +7320,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.5-pro-preview-03-25": {
"max_tokens": 65535,
@ -7324,7 +7363,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
"supports_parallel_function_calling": true,
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini-2.0-flash-preview-image-generation": {
"max_tokens": 8192,
@ -7649,7 +7689,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-pro-preview-05-06": {
"max_tokens": 65535,
@ -7687,7 +7728,8 @@
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true,
"supports_url_context": true
"supports_url_context": true,
"supports_pdf_input": true
},
"gemini/gemini-2.5-pro-preview-03-25": {
"max_tokens": 65535,
@ -7724,7 +7766,8 @@
"text"
],
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview",
"supports_web_search": true
"supports_web_search": true,
"supports_pdf_input": true
},
"gemini/gemini-2.0-flash-exp": {
"max_tokens": 8192,
@ -8562,13 +8605,13 @@
"litellm_provider": "vertex_ai-image-models",
"mode": "image_generation",
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
},
},
"vertex_ai/imagen-4.0-fast-generate-preview-06-06": {
"output_cost_per_image": 0.02,
"litellm_provider": "vertex_ai-image-models",
"mode": "image_generation",
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing"
},
},
"vertex_ai/imagen-3.0-generate-002": {
"output_cost_per_image": 0.04,
"litellm_provider": "vertex_ai-image-models",
@ -10830,6 +10873,46 @@
"supports_response_schema": true,
"source": "https://aws.amazon.com/bedrock/pricing/"
},
"apac.amazon.nova-micro-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 300000,
"max_output_tokens": 10000,
"input_cost_per_token": 3.7e-08,
"output_cost_per_token": 1.48e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"apac.amazon.nova-lite-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 128000,
"max_output_tokens": 10000,
"input_cost_per_token": 6.3e-08,
"output_cost_per_token": 2.52e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"apac.amazon.nova-pro-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 300000,
"max_output_tokens": 10000,
"input_cost_per_token": 8.4e-07,
"output_cost_per_token": 3.36e-06,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true
},
"us.amazon.nova-premier-v1:0": {
"max_tokens": 10000,
"max_input_tokens": 1000000,
@ -11315,6 +11398,93 @@
"supports_reasoning": true,
"supports_computer_use": true
},
"apac.anthropic.claude-3-haiku-20240307-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 2.5e-07,
"output_cost_per_token": 1.25e-06,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-sonnet-20240229-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-5-sonnet-20240620-v1:0": {
"max_tokens": 4096,
"max_input_tokens": 200000,
"max_output_tokens": 4096,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_response_schema": true,
"supports_vision": true,
"supports_pdf_input": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0": {
"max_tokens": 8192,
"max_input_tokens": 200000,
"max_output_tokens": 8192,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"cache_creation_input_token_cost": 3.75e-06,
"cache_read_input_token_cost": 3e-07,
"litellm_provider": "bedrock",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_tool_choice": true
},
"apac.anthropic.claude-sonnet-4-20250514-v1:0": {
"max_tokens": 64000,
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"search_context_cost_per_query": {
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01,
"search_context_size_high": 0.01
},
"cache_creation_input_token_cost": 3.75e-06,
"cache_read_input_token_cost": 3e-07,
"litellm_provider": "bedrock_converse",
"mode": "chat",
"supports_function_calling": true,
"supports_vision": true,
"tool_use_system_prompt_tokens": 159,
"supports_assistant_prefill": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_reasoning": true,
"supports_computer_use": true
},
"eu.anthropic.claude-3-5-haiku-20241022-v1:0": {
"max_tokens": 8192,
"max_input_tokens": 200000,
@ -14770,7 +14940,7 @@
},
"deepgram/nova-3": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14784,7 +14954,7 @@
},
"deepgram/nova-3-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14798,7 +14968,7 @@
},
"deepgram/nova-3-medical": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00008667,
"input_cost_per_second": 8.667e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14812,7 +14982,7 @@
},
"deepgram/nova-2": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14826,7 +14996,7 @@
},
"deepgram/nova-2-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14840,7 +15010,7 @@
},
"deepgram/nova-2-meeting": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14854,7 +15024,7 @@
},
"deepgram/nova-2-phonecall": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14868,7 +15038,7 @@
},
"deepgram/nova-2-voicemail": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14882,7 +15052,7 @@
},
"deepgram/nova-2-finance": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14896,7 +15066,7 @@
},
"deepgram/nova-2-conversationalai": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14910,7 +15080,7 @@
},
"deepgram/nova-2-video": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14924,7 +15094,7 @@
},
"deepgram/nova-2-drivethru": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14938,7 +15108,7 @@
},
"deepgram/nova-2-automotive": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14952,7 +15122,7 @@
},
"deepgram/nova-2-atc": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14966,7 +15136,7 @@
},
"deepgram/nova": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14980,7 +15150,7 @@
},
"deepgram/nova-general": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -14994,7 +15164,7 @@
},
"deepgram/nova-phonecall": {
"mode": "audio_transcription",
"input_cost_per_second": 0.00007167,
"input_cost_per_second": 7.167e-05,
"output_cost_per_second": 0.0,
"litellm_provider": "deepgram",
"supported_endpoints": [
@ -15266,4 +15436,4 @@
"notes": "Deepgram's hosted OpenAI Whisper models - pricing may differ from native Deepgram models"
}
}
}
}

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "litellm"
version = "1.72.7"
version = "1.72.9"
description = "Library to easily interface with LLM API providers"
authors = ["BerriAI"]
license = "MIT"
@ -141,7 +141,7 @@ requires = ["poetry-core", "wheel"]
build-backend = "poetry.core.masonry.api"
[tool.commitizen]
version = "1.72.7"
version = "1.72.9"
version_files = [
"pyproject.toml:^version"
]

View file

@ -1,7 +1,7 @@
# LITELLM PROXY DEPENDENCIES #
anyio==4.5.0 # openai + http req.
httpx==0.27.0 # Pin Httpx dependency
openai==1.81.0 # openai req.
openai==1.81.0 # openai req.
fastapi==0.115.5 # server dep
backoff==2.2.1 # server dep
pyyaml==6.0.2 # server dep
@ -14,7 +14,7 @@ prisma==0.11.0 # for db
mangum==0.17.0 # for aws lambda functions
pynacl==1.5.0 # for encrypting keys
google-cloud-aiplatform==1.47.0 # for vertex ai calls
anthropic[vertex]==0.21.3
anthropic[vertex]==0.54.0
mcp==1.9.3 # for MCP server
google-generativeai==0.5.0 # for vertex ai calls
async_generator==1.10.0 # for async ollama calls
@ -22,7 +22,7 @@ langfuse==2.45.0 # for langfuse self-hosted logging
prometheus_client==0.20.0 # for /metrics endpoint on proxy
ddtrace==2.19.0 # for advanced DD tracing / profiling
orjson==3.10.12 # fast /embedding responses
apscheduler==3.10.4 # for resetting budget in background
apscheduler==3.10.4 # for resetting budget in background
fastapi-sso==0.16.0 # admin UI, SSO
pyjwt[crypto]==2.9.0
python-multipart==0.0.18 # admin UI
@ -39,12 +39,12 @@ cryptography==43.0.1
tzdata==2025.1 # IANA time zone database
litellm-proxy-extras==0.2.5 # for proxy extras - e.g. prisma migrations
### LITELLM PACKAGE DEPENDENCIES
python-dotenv==1.0.0 # for env
python-dotenv==1.0.0 # for env
tiktoken==0.8.0 # for calculating usage
importlib-metadata==6.8.0 # for random utils
tokenizers==0.20.2 # for calculating usage
click==8.1.7 # for proxy cli
rich==13.7.1 # for litellm proxy cli
click==8.1.7 # for proxy cli
rich==13.7.1 # for litellm proxy cli
jinja2==3.1.6 # for prompt templates
aiohttp==3.10.2 # for network calls
aioboto3==12.3.0 # for async sagemaker calls

View file

@ -4,10 +4,21 @@ import json
gemini_model_cost_map = json.load(open("model_prices_and_context_window.json"))
for model, model_info in gemini_model_cost_map.items():
if model_info.get("litellm_provider") == "gemini" and model_info.get("mode") == "chat" and "gemini-2" in model:
if (
(
model_info.get("litellm_provider") == "gemini"
or model_info.get("litellm_provider") == "vertex_ai-language-models"
)
and model_info.get("mode") == "chat"
and ("gemini-2.5" in model and "tts" not in model)
and model_info.get("supports_pdf_input") is None
):
"""
Update all gemini chat models to support web search
Update all gemini chat models to support pdf input
"""
model_info["supports_web_search"] = True
model_info["supports_pdf_input"] = True
print(f"Updated {model} to support pdf input")
json.dump(gemini_model_cost_map, open("model_prices_and_context_window.json", "w"), indent=4)
json.dump(
gemini_model_cost_map, open("model_prices_and_context_window.json", "w"), indent=4
)

View file

@ -0,0 +1,94 @@
import os
import sys
import pytest
from unittest.mock import patch, AsyncMock
sys.path.insert(0, os.path.abspath("../.."))
import litellm
import json
@pytest.mark.asyncio
async def test_basic_google_ai_studio_responses_api_with_tools():
litellm._turn_on_debug()
litellm.set_verbose = True
request_model = "gemini/gemini-2.5-flash"
response = await litellm.aresponses(
model=request_model,
input="what is the latest version of supabase python package and when was it released?",
tools=[
{
"type": "web_search_preview",
"search_context_size": "low"
}
]
)
print("litellm response=", json.dumps(response, indent=4, default=str))
@pytest.mark.asyncio
async def test_mock_basic_google_ai_studio_responses_api_with_tools():
"""
- Ensure that this is the request that litellm.completion gets when we pass web search options
litellm.acompletion(messages=[{'role': 'user', 'content': 'what is the latest version of supabase python package and when was it released?'}], model='gemini-2.5-flash', tools=[], web_search_options={'search_context_size': 'low', 'user_location': None})
"""
# Mock the acompletion function
litellm._turn_on_debug()
mock_response = litellm.ModelResponse(
id="test-id",
created=1234567890,
model="gemini/gemini-2.5-flash",
object="chat.completion",
choices=[
litellm.utils.Choices(
index=0,
message=litellm.utils.Message(
role="assistant",
content="Test response"
),
finish_reason="stop"
)
]
)
with patch('litellm.acompletion', new_callable=AsyncMock) as mock_acompletion:
mock_acompletion.return_value = mock_response
request_model = "gemini/gemini-2.5-flash"
await litellm.aresponses(
model=request_model,
input="what is the latest version of supabase python package and when was it released?",
tools=[
{
"type": "web_search_preview",
"search_context_size": "low"
}
]
)
# Verify that acompletion was called
assert mock_acompletion.called
# Get the call arguments
call_args, call_kwargs = mock_acompletion.call_args
# Verify the expected parameters were passed
print("call kwargs to litellm.completion=", json.dumps(call_kwargs, indent=4, default=str))
assert "web_search_options" in call_kwargs
assert call_kwargs["web_search_options"] is not None
assert call_kwargs["web_search_options"]["search_context_size"] == "low"
assert call_kwargs["web_search_options"]["user_location"] is None
# Verify other expected parameters
assert call_kwargs["model"] == "gemini-2.5-flash"
assert len(call_kwargs["messages"]) == 1
assert call_kwargs["messages"][0]["role"] == "user"
assert call_kwargs["messages"][0]["content"] == "what is the latest version of supabase python package and when was it released?"
assert call_kwargs["tools"] == [] # web search tools are converted to web_search_options, not kept as tools

View file

@ -35,4 +35,4 @@ class TestMistralCompletion(BaseLLMChatTest):
def test_tool_call_no_arguments(self, tool_call_no_arguments):
"""Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833"""
pass
pass

View file

@ -219,7 +219,14 @@ def test_increment_token_metrics(prometheus_logger):
)
prometheus_logger.litellm_tokens_metric.labels.assert_called_once_with(
end_user=None, user=None, hashed_api_key='test_hash', api_key_alias='test_alias', team='test_team', team_alias='test_team_alias', requested_model=None, model='gpt-3.5-turbo'
end_user=None,
user=None,
hashed_api_key="test_hash",
api_key_alias="test_alias",
team="test_team",
team_alias="test_team_alias",
requested_model=None,
model="gpt-3.5-turbo",
)
prometheus_logger.litellm_tokens_metric.labels().inc.assert_called_once_with(100)
@ -836,12 +843,12 @@ def test_set_llm_deployment_success_metrics(prometheus_logger):
# Verify remaining requests metric
prometheus_logger.litellm_remaining_requests_metric.labels.assert_called_once_with(
api_base="https://api.openai.com",
api_key_alias=standard_logging_payload["metadata"]["user_api_key_alias"],
api_provider="openai",
model_group="my_custom_model_group", # model_group / requested model from create_standard_logging_payload()
api_provider="openai", # llm provider
api_base="https://api.openai.com", # api base
litellm_model_name="gpt-3.5-turbo", # actual model used - litellm model name
hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"],
litellm_model_name="gpt-3.5-turbo",
requested_model="my_custom_model_group",
api_key_alias=standard_logging_payload["metadata"]["user_api_key_alias"],
)
prometheus_logger.litellm_remaining_requests_metric.labels().set.assert_called_once_with(
@ -855,7 +862,7 @@ def test_set_llm_deployment_success_metrics(prometheus_logger):
api_provider="openai",
hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"],
litellm_model_name="gpt-3.5-turbo",
requested_model="my_custom_model_group",
model_group="my_custom_model_group",
)
prometheus_logger.litellm_remaining_tokens_metric.labels().set.assert_called_once_with(
@ -915,7 +922,7 @@ def test_set_llm_deployment_success_metrics(prometheus_logger):
api_provider="openai",
hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"],
litellm_model_name="gpt-3.5-turbo",
requested_model="my_custom_model_group",
model_group="my_custom_model_group",
)
# Calculate expected latency per token (1 second / 10 tokens = 0.1 seconds per token)
@ -1499,60 +1506,66 @@ def test_get_exception_class_name(prometheus_logger):
"""
# Test case 1: Exception with llm_provider
rate_limit_error = litellm.RateLimitError(
message="Rate limit exceeded",
llm_provider="openai",
model="gpt-3.5-turbo"
message="Rate limit exceeded", llm_provider="openai", model="gpt-3.5-turbo"
)
assert (
prometheus_logger._get_exception_class_name(rate_limit_error)
== "Openai.RateLimitError"
)
assert prometheus_logger._get_exception_class_name(rate_limit_error) == "Openai.RateLimitError"
# Test case 2: Exception with empty llm_provider
auth_error = litellm.AuthenticationError(
message="Invalid API key",
llm_provider="",
model="gpt-4"
message="Invalid API key", llm_provider="", model="gpt-4"
)
assert (
prometheus_logger._get_exception_class_name(auth_error) == "AuthenticationError"
)
assert prometheus_logger._get_exception_class_name(auth_error) == "AuthenticationError"
# Test case 3: Exception with None llm_provider
context_window_error = litellm.ContextWindowExceededError(
message="Context length exceeded",
llm_provider=None,
model="gpt-4"
message="Context length exceeded", llm_provider=None, model="gpt-4"
)
assert (
prometheus_logger._get_exception_class_name(context_window_error)
== "ContextWindowExceededError"
)
assert prometheus_logger._get_exception_class_name(context_window_error) == "ContextWindowExceededError"
def test_set_llm_deployment_success_metrics_with_label_filtering():
"""
Test that set_llm_deployment_success_metrics correctly uses prometheus_label_factory
Test that set_llm_deployment_success_metrics correctly uses prometheus_label_factory
and respects label filtering configuration to prevent "Incorrect label names" errors.
"""
from litellm.types.integrations.prometheus import PrometheusMetricsConfig
# Create a prometheus logger with label filtering configuration
config = [
PrometheusMetricsConfig(
group="test_group",
metrics=[
"litellm_overhead_latency_metric",
"litellm_remaining_requests_metric",
"litellm_remaining_requests_metric",
"litellm_remaining_tokens_metric",
"litellm_deployment_success_responses",
"litellm_deployment_total_requests"
"litellm_deployment_total_requests",
],
include_labels=["requested_model", "api_provider", "hashed_api_key"] # Limited labels
include_labels=[
"requested_model",
"api_provider",
"hashed_api_key",
], # Limited labels
)
]
# Mock litellm.prometheus_metrics_config
with patch('litellm.prometheus_metrics_config', config):
with patch("litellm.prometheus_metrics_config", config):
# Clear registry before creating new logger
collectors = list(REGISTRY._collector_to_names.keys())
for collector in collectors:
REGISTRY.unregister(collector)
prometheus_logger = PrometheusLogger()
# Mock all the metrics used in the method
prometheus_logger.litellm_overhead_latency_metric = MagicMock()
prometheus_logger.litellm_remaining_requests_metric = MagicMock()
@ -1607,46 +1620,59 @@ def test_set_llm_deployment_success_metrics_with_label_filtering():
# Verify that metrics were called with filtered labels (only the configured ones)
# The exact labels depend on what get_labels_for_metric returns for each metric
# Verify overhead latency metric was called with filtered labels
prometheus_logger.litellm_overhead_latency_metric.labels.assert_called_once()
overhead_labels = prometheus_logger.litellm_overhead_latency_metric.labels.call_args[1]
overhead_labels = (
prometheus_logger.litellm_overhead_latency_metric.labels.call_args[1]
)
# Should only contain the filtered labels that are supported for this metric
expected_filtered_labels = {"requested_model", "api_provider", "hashed_api_key"}
actual_labels = set(k for k in overhead_labels.keys() if k is not None)
# Verify that only expected labels are present (subset of configured labels)
assert actual_labels <= expected_filtered_labels
# Verify remaining requests metric was called with filtered labels
# Verify remaining requests metric was called with filtered labels
prometheus_logger.litellm_remaining_requests_metric.labels.assert_called_once()
requests_labels = prometheus_logger.litellm_remaining_requests_metric.labels.call_args[1]
requests_labels = (
prometheus_logger.litellm_remaining_requests_metric.labels.call_args[1]
)
actual_labels = set(k for k in requests_labels.keys() if k is not None)
assert actual_labels <= expected_filtered_labels
# Verify remaining tokens metric was called with filtered labels
prometheus_logger.litellm_remaining_tokens_metric.labels.assert_called_once()
tokens_labels = prometheus_logger.litellm_remaining_tokens_metric.labels.call_args[1]
tokens_labels = (
prometheus_logger.litellm_remaining_tokens_metric.labels.call_args[1]
)
actual_labels = set(k for k in tokens_labels.keys() if k is not None)
assert actual_labels <= expected_filtered_labels
# Verify deployment success responses metric was called with filtered labels
prometheus_logger.litellm_deployment_success_responses.labels.assert_called_once()
success_labels = prometheus_logger.litellm_deployment_success_responses.labels.call_args[1]
success_labels = (
prometheus_logger.litellm_deployment_success_responses.labels.call_args[1]
)
actual_labels = set(k for k in success_labels.keys() if k is not None)
assert actual_labels <= expected_filtered_labels
# Verify deployment total requests metric was called with filtered labels
prometheus_logger.litellm_deployment_total_requests.labels.assert_called_once()
total_labels = prometheus_logger.litellm_deployment_total_requests.labels.call_args[1]
total_labels = (
prometheus_logger.litellm_deployment_total_requests.labels.call_args[1]
)
actual_labels = set(total_labels.keys())
assert actual_labels.issubset(expected_filtered_labels.union({None}))
# Verify all metrics were actually called (no exceptions were raised)
prometheus_logger.litellm_overhead_latency_metric.labels().observe.assert_called_once()
prometheus_logger.litellm_remaining_requests_metric.labels().set.assert_called_once_with(123)
prometheus_logger.litellm_remaining_tokens_metric.labels().set.assert_called_once_with(4321)
prometheus_logger.litellm_remaining_requests_metric.labels().set.assert_called_once_with(
123
)
prometheus_logger.litellm_remaining_tokens_metric.labels().set.assert_called_once_with(
4321
)
prometheus_logger.litellm_deployment_success_responses.labels().inc.assert_called_once()
prometheus_logger.litellm_deployment_total_requests.labels().inc.assert_called_once()

View file

@ -0,0 +1,169 @@
"""
Unit tests for the MCPClient class - critical functionality only.
"""
import base64
import os
import sys
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
# Add the project root to the path
sys.path.insert(0, os.path.abspath("../../.."))
from litellm.experimental_mcp_client.client import MCPClient
from litellm.types.mcp import MCPAuth, MCPTransport
from mcp.types import Tool as MCPTool, CallToolResult as MCPCallToolResult
class TestMCPClientUnitTests:
"""Unit tests for MCPClient functionality."""
def test_init_with_auth(self):
"""Test initialization with authentication."""
client = MCPClient(
server_url="http://example.com",
transport_type=MCPTransport.sse,
auth_type=MCPAuth.bearer_token,
auth_value="test_token",
timeout=30.0
)
assert client.server_url == "http://example.com"
assert client.transport_type == MCPTransport.sse
assert client.auth_type == MCPAuth.bearer_token
assert client.timeout == 30.0
assert client._mcp_auth_value == "test_token"
def test_get_auth_headers(self):
"""Test authentication header generation for different auth types."""
# Bearer token
client = MCPClient(
"http://example.com",
auth_type=MCPAuth.bearer_token,
auth_value="test_token"
)
headers = client._get_auth_headers()
assert headers == {"Authorization": "Bearer test_token"}
# Basic auth
client = MCPClient(
"http://example.com",
auth_type=MCPAuth.basic,
auth_value="user:pass"
)
expected_encoded = base64.b64encode("user:pass".encode("utf-8")).decode()
headers = client._get_auth_headers()
assert headers == {"Authorization": f"Basic {expected_encoded}"}
# API key
client = MCPClient(
"http://example.com",
auth_type=MCPAuth.api_key,
auth_value="api_key_123"
)
headers = client._get_auth_headers()
assert headers == {"X-API-Key": "api_key_123"}
@pytest.mark.asyncio
@patch('litellm.experimental_mcp_client.client.streamablehttp_client')
@patch('litellm.experimental_mcp_client.client.ClientSession')
async def test_connect(self, mock_session_class, mock_transport):
"""Test connecting to MCP server with authentication."""
# Setup mocks
mock_transport_ctx = AsyncMock()
mock_transport.return_value = mock_transport_ctx
mock_transport_instance = MagicMock()
mock_transport_ctx.__aenter__ = AsyncMock(return_value=mock_transport_instance)
mock_session_ctx = AsyncMock()
mock_session_class.return_value = mock_session_ctx
mock_session_instance = AsyncMock()
mock_session_ctx.__aenter__ = AsyncMock(return_value=mock_session_instance)
client = MCPClient(
"http://example.com",
auth_type=MCPAuth.bearer_token,
auth_value="test_token"
)
await client.connect()
# Verify transport was created with auth headers
call_args = mock_transport.call_args
assert call_args[1]['headers'] == {"Authorization": "Bearer test_token"}
# Verify session was initialized
mock_session_instance.initialize.assert_called_once()
assert client._session == mock_session_instance
@pytest.mark.asyncio
@patch('litellm.experimental_mcp_client.client.streamablehttp_client')
@patch('litellm.experimental_mcp_client.client.ClientSession')
async def test_list_tools(self, mock_session_class, mock_transport):
"""Test listing tools from the server."""
# Setup mocks
mock_transport_ctx = AsyncMock()
mock_transport.return_value = mock_transport_ctx
mock_transport_instance = MagicMock()
mock_transport_ctx.__aenter__ = AsyncMock(return_value=mock_transport_instance)
mock_session_ctx = AsyncMock()
mock_session_class.return_value = mock_session_ctx
mock_session_instance = AsyncMock()
mock_session_ctx.__aenter__ = AsyncMock(return_value=mock_session_instance)
mock_tools = [
MCPTool(
name="test_tool",
description="Test tool",
inputSchema={
"type": "object",
"properties": {"arg1": {"type": "string"}},
"required": ["arg1"]
}
)
]
mock_result = MagicMock()
mock_result.tools = mock_tools
mock_session_instance.list_tools.return_value = mock_result
client = MCPClient("http://example.com")
result = await client.list_tools()
assert result == mock_tools
mock_session_instance.initialize.assert_called_once()
mock_session_instance.list_tools.assert_called_once()
@pytest.mark.asyncio
@patch('litellm.experimental_mcp_client.client.streamablehttp_client')
@patch('litellm.experimental_mcp_client.client.ClientSession')
async def test_call_tool(self, mock_session_class, mock_transport):
"""Test calling a tool."""
from mcp.types import CallToolRequestParams
# Setup mocks
mock_transport_ctx = AsyncMock()
mock_transport.return_value = mock_transport_ctx
mock_transport_instance = MagicMock()
mock_transport_ctx.__aenter__ = AsyncMock(return_value=mock_transport_instance)
mock_session_ctx = AsyncMock()
mock_session_class.return_value = mock_session_ctx
mock_session_instance = AsyncMock()
mock_session_ctx.__aenter__ = AsyncMock(return_value=mock_session_instance)
mock_result = MCPCallToolResult(content=[])
mock_session_instance.call_tool.return_value = mock_result
client = MCPClient("http://example.com")
params = CallToolRequestParams(name="test_tool", arguments={"arg1": "value1"})
result = await client.call_tool(params)
assert result == mock_result
mock_session_instance.initialize.assert_called_once()
mock_session_instance.call_tool.assert_called_once_with(
name="test_tool",
arguments={"arg1": "value1"}
)
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View file

@ -101,26 +101,17 @@ async def test_mcp_http_transport_list_tools_mock():
)
]
# Mock the session and its methods
mock_session = AsyncMock()
mock_session.initialize = AsyncMock()
mock_session.list_tools = AsyncMock(return_value=ListToolsResult(tools=mock_tools))
# Create a mock MCPClient that returns our test tools
mock_client = AsyncMock()
mock_client.list_tools = AsyncMock(return_value=mock_tools)
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
# Create an async context manager mock for streamablehttp_client
@asynccontextmanager
async def mock_streamablehttp_client(url):
read_stream = AsyncMock()
write_stream = AsyncMock()
get_session_id = MagicMock(return_value="test-session-123")
yield (read_stream, write_stream, get_session_id)
# Mock the MCPClient constructor to return our mock
def mock_client_constructor(*args, **kwargs):
return mock_client
# Create an async context manager mock for ClientSession
@asynccontextmanager
async def mock_client_session(read_stream, write_stream):
yield mock_session
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.streamablehttp_client', mock_streamablehttp_client), \
patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.ClientSession', mock_client_session):
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.MCPClient', mock_client_constructor):
# Load server config with HTTP transport
test_manager.load_servers_from_config({
@ -139,9 +130,9 @@ async def test_mcp_http_transport_list_tools_mock():
assert tools[0].name == "gmail_send_email"
assert tools[1].name == "calendar_create_event"
# Verify session methods were called
mock_session.initialize.assert_called_once()
mock_session.list_tools.assert_called_once()
# Verify client methods were called
mock_client.__aenter__.assert_called()
mock_client.list_tools.assert_called_once()
# Verify tool mapping was updated
assert test_manager.tool_name_to_mcp_server_name_mapping["gmail_send_email"] == "test_http_server"
@ -166,26 +157,17 @@ async def test_mcp_http_transport_call_tool_mock():
isError=False
)
# Mock the session and its methods
mock_session = AsyncMock()
mock_session.initialize = AsyncMock()
mock_session.call_tool = AsyncMock(return_value=mock_result)
# Create a mock MCPClient that returns our test result
mock_client = AsyncMock()
mock_client.call_tool = AsyncMock(return_value=mock_result)
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
# Create an async context manager mock for streamablehttp_client
@asynccontextmanager
async def mock_streamablehttp_client(url):
read_stream = AsyncMock()
write_stream = AsyncMock()
get_session_id = MagicMock(return_value="test-session-456")
yield (read_stream, write_stream, get_session_id)
# Mock the MCPClient constructor to return our mock
def mock_client_constructor(*args, **kwargs):
return mock_client
# Create an async context manager mock for ClientSession
@asynccontextmanager
async def mock_client_session(read_stream, write_stream):
yield mock_session
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.streamablehttp_client', mock_streamablehttp_client), \
patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.ClientSession', mock_client_session):
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.MCPClient', mock_client_constructor):
# Load server config with HTTP transport
test_manager.load_servers_from_config({
@ -216,16 +198,9 @@ async def test_mcp_http_transport_call_tool_mock():
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "Email sent successfully to test@example.com"
# Verify session methods were called
mock_session.initialize.assert_called_once()
mock_session.call_tool.assert_called_once_with(
"gmail_send_email",
{
"to": "test@example.com",
"subject": "Test Subject",
"body": "Test email body"
}
)
# Verify client methods were called
mock_client.__aenter__.assert_called()
mock_client.call_tool.assert_called_once()
@pytest.mark.asyncio
@ -246,26 +221,17 @@ async def test_mcp_http_transport_call_tool_error_mock():
isError=True
)
# Mock the session and its methods
mock_session = AsyncMock()
mock_session.initialize = AsyncMock()
mock_session.call_tool = AsyncMock(return_value=mock_error_result)
# Create a mock MCPClient that returns our test error result
mock_client = AsyncMock()
mock_client.call_tool = AsyncMock(return_value=mock_error_result)
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=None)
# Create an async context manager mock for streamablehttp_client
@asynccontextmanager
async def mock_streamablehttp_client(url):
read_stream = AsyncMock()
write_stream = AsyncMock()
get_session_id = MagicMock(return_value="test-session-789")
yield (read_stream, write_stream, get_session_id)
# Mock the MCPClient constructor to return our mock
def mock_client_constructor(*args, **kwargs):
return mock_client
# Create an async context manager mock for ClientSession
@asynccontextmanager
async def mock_client_session(read_stream, write_stream):
yield mock_session
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.streamablehttp_client', mock_streamablehttp_client), \
patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.ClientSession', mock_client_session):
with patch('litellm.proxy._experimental.mcp_server.mcp_server_manager.MCPClient', mock_client_constructor):
# Load server config with HTTP transport
test_manager.load_servers_from_config({
@ -292,9 +258,9 @@ async def test_mcp_http_transport_call_tool_error_mock():
assert isinstance(result.content[0], TextContent)
assert "Error: Invalid email address" in result.content[0].text
# Verify session methods were called
mock_session.initialize.assert_called_once()
mock_session.call_tool.assert_called_once()
# Verify client methods were called
mock_client.__aenter__.assert_called()
mock_client.call_tool.assert_called_once()
@pytest.mark.asyncio

View file

@ -1,6 +1,8 @@
import os
from unittest.mock import MagicMock, patch
import pytest
from unittest.mock import patch, MagicMock
from litellm.integrations.langfuse.langfuse_otel import LangfuseOtelLogger
from litellm.types.integrations.langfuse_otel import LangfuseOtelConfig

View file

@ -308,3 +308,20 @@ def test_vertex_ai_transform_empty_function_call_arguments():
assert result["args"] == {
"type": "object",
}
@pytest.mark.asyncio
async def test_bedrock_process_image_async_factory():
"""
Test that the _process_image_async_factory method handles image input correctly
"""
from litellm.litellm_core_utils.prompt_templates.factory import (
BedrockImageProcessor,
)
image_url = "data:application/pdf; qs=0.001;base64,JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4"
content_block = await BedrockImageProcessor.process_image_async(
image_url=image_url, format=None
)
print(f"content_block: {content_block}")

View file

@ -218,6 +218,7 @@ def test_map_tool_choice():
assert result["type"] == "none"
print(result)
def test_transform_response_with_prefix_prompt():
import httpx
@ -262,3 +263,8 @@ def test_transform_response_with_prefix_prompt():
== "You are a helpful assistant. The grass is green."
)
def test_get_supported_params_thinking():
config = AnthropicConfig()
params = config.get_supported_openai_params(model="claude-sonnet-4-20250514")
assert "thinking" in params

View file

@ -0,0 +1,22 @@
import asyncio
import json
import os
import sys
import pytest
# Ensure the project root is on the import path so `litellm` can be imported when
# tests are executed from any working directory.
sys.path.insert(0, os.path.abspath("../../../../../.."))
from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import (
AmazonAnthropicClaude3Config,
)
def test_get_supported_params_thinking():
config = AmazonAnthropicClaude3Config()
params = config.get_supported_openai_params(
model="anthropic.claude-sonnet-4-20250514-v1:0"
)
assert "thinking" in params

View file

@ -5,6 +5,7 @@ import ssl
import sys
from unittest.mock import MagicMock, patch
import certifi
import httpx
import pytest
from aiohttp import ClientSession, TCPConnector
@ -120,3 +121,34 @@ async def test_ssl_verification_with_aiohttp_transport():
# assert both litellm transport and aiohttp session have ssl_verify=False
assert transport_connector._ssl == aiohttp_session.connector._ssl
def test_get_ssl_context():
"""Test that _get_ssl_context() returns a proper SSL context with certifi CA bundle"""
with patch('ssl.create_default_context') as mock_create_context:
# Mock the return value
mock_ssl_context = MagicMock(spec=ssl.SSLContext)
mock_create_context.return_value = mock_ssl_context
# Call the static method
result = AsyncHTTPHandler._get_ssl_context()
# Verify ssl.create_default_context was called with certifi's CA file
expected_ca_file = certifi.where()
mock_create_context.assert_called_once_with(cafile=expected_ca_file)
# Verify it returns the mocked SSL context
assert result == mock_ssl_context
def test_get_ssl_context_integration():
"""Integration test that _get_ssl_context() returns a working SSL context"""
# Call the static method without mocking
ssl_context = AsyncHTTPHandler._get_ssl_context()
# Verify it returns an SSLContext instance
assert isinstance(ssl_context, ssl.SSLContext)
# Verify it has basic SSL context properties
assert ssl_context.protocol is not None
assert ssl_context.verify_mode is not None

View file

@ -0,0 +1,32 @@
from typing import Optional
from unittest.mock import patch
import pytest
import litellm
from litellm.llms.litellm_proxy.chat.transformation import LiteLLMProxyChatConfig
def test_litellm_proxy_chat_transformation():
"""
Assert messages are not transformed when calling litellm proxy
"""
config = LiteLLMProxyChatConfig()
file_content = [
{"type": "text", "text": "What is this document about?"},
{
"type": "file",
"file": {
"file_id": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"format": "application/pdf",
},
},
]
messages = [{"role": "user", "content": file_content}]
assert config.transform_request(
model="model",
messages=messages,
optional_params={},
litellm_params={},
headers={},
) == {"model": "model", "messages": messages}

View file

@ -11,18 +11,6 @@ sys.path.insert(
from litellm.llms.meta_llama.chat.transformation import LlamaAPIConfig
def test_get_supported_openai_params():
"""Test that LlamaAPIConfig correctly filters unsupported parameters"""
config = LlamaAPIConfig()
# Test error handling
with patch("litellm.get_model_info", side_effect=Exception("Test error")):
params = config.get_supported_openai_params("llama-3.3-8B-instruct")
assert "function_call" not in params
assert "tools" not in params
assert "tool_choice" not in params
def test_map_openai_params():
"""Test that LlamaAPIConfig correctly maps OpenAI parameters"""
config = LlamaAPIConfig()

View file

@ -349,3 +349,67 @@ class TestMistralReasoningSupport:
assert result["messages"][1]["content"] == "Solve for x: 2x + 5 = 13"
assert result.get("temperature") == 0.7
assert "_add_reasoning_prompt" not in result
class TestMistralNameHandling:
"""Test suite for Mistral name handling in messages."""
def test_handle_name_in_message_tool_role_empty_name_removes_name(self):
"""Test that empty name is removed for tool messages."""
# Test with empty string
tool_message = {"role": "tool", "content": "Function result", "name": ""}
result = MistralConfig._handle_name_in_message(tool_message)
assert "name" not in result
assert result["role"] == "tool"
assert result["content"] == "Function result"
def test_handle_name_in_message_tool_role_valid_name_keeps_name(self):
"""Test that valid name is kept for tool messages."""
# Test with normal function name
tool_message = {"role": "tool", "content": "Function result", "name": "get_weather"}
result = MistralConfig._handle_name_in_message(tool_message)
assert "name" in result
assert result["name"] == "get_weather"
assert result["role"] == "tool"
assert result["content"] == "Function result"
def test_handle_name_in_message_no_name_field(self):
"""Test that messages without name field are unchanged."""
# Test with user role
user_message = {"role": "user", "content": "Hello"}
result = MistralConfig._handle_name_in_message(user_message)
assert "name" not in result
assert result["role"] == "user"
assert result["content"] == "Hello"
class TestMistralParallelToolCalls:
"""Test suite for Mistral parallel tool calls functionality."""
def test_get_supported_openai_params_includes_parallel_tool_calls(self):
"""Test that parallel_tool_calls is in supported parameters."""
mistral_config = MistralConfig()
supported_params = mistral_config.get_supported_openai_params("mistral/mistral-large-latest")
assert "parallel_tool_calls" in supported_params
def test_transform_request_preserves_parallel_tool_calls(self):
"""Test that transform_request preserves parallel_tool_calls parameter."""
mistral_config = MistralConfig()
messages = [
{"role": "user", "content": "What's the weather like?"}
]
optional_params = {"parallel_tool_calls": True}
result = mistral_config.transform_request(
model="mistral/mistral-large-latest",
messages=messages,
optional_params=optional_params,
litellm_params={},
headers={}
)
assert result.get("parallel_tool_calls") is True
assert len(result["messages"]) == 1
assert result["messages"][0]["role"] == "user"

View file

@ -713,3 +713,31 @@ def test_vertex_ai_transform_parts():
assert function["name"] == "simple_function"
assert function["arguments"] == "{}"
assert tools is None
def test_vertex_ai_usage_metadata_missing_token_count():
"""Test that missing tokenCount in responseTokensDetails defaults to 0"""
from litellm.types.utils import PromptTokensDetailsWrapper
v = VertexGeminiConfig()
usage_metadata = {
"promptTokenCount": 57,
"responseTokenCount": 74,
"totalTokenCount": 131,
"promptTokensDetails": [{"modality": "TEXT", "tokenCount": 57}],
"responseTokensDetails": [
{"modality": "TEXT"}, # Missing tokenCount
{"modality": "AUDIO"}, # Missing tokenCount
],
}
usage_metadata = UsageMetadata(**usage_metadata)
result = v._calculate_usage(completion_response={"usageMetadata": usage_metadata})
# Should not crash and should default missing tokenCount to 0
assert result.prompt_tokens == 57
assert result.completion_tokens == 74
assert result.total_tokens == 131
assert result.completion_tokens_details.text_tokens == 0 # Default value for missing tokenCount
assert result.completion_tokens_details.audio_tokens == 0 # Default value for missing tokenCount

View file

@ -243,6 +243,98 @@ class TestVertexBase:
assert token == "refreshed-token"
assert project == not_quota_project_id
@pytest.mark.parametrize("is_async", [True, False], ids=["async", "sync"])
@pytest.mark.asyncio
async def test_identity_pool_credentials(self, is_async):
vertex_base = VertexBase()
# Test case: Using Workload Identity Federation for Microsoft Azure and
# OIDC identity providers (default behavior)
credentials = {
"project_id": "test-project",
"refresh_token": "fake-refresh-token",
"type": "external_account",
}
mock_creds = MagicMock()
mock_creds.token = "token-1"
mock_creds.expired = False
mock_creds.project_id = "test-project"
with patch.object(
vertex_base, "_credentials_from_identity_pool", return_value=mock_creds
) as mock_credentials_from_identity_pool, patch.object(
vertex_base, "refresh_auth"
) as mock_refresh:
def mock_refresh_impl(creds):
creds.token = "refreshed-token"
mock_refresh.side_effect = mock_refresh_impl
if is_async:
token, _ = await vertex_base._ensure_access_token_async(
credentials=credentials,
project_id=None,
custom_llm_provider="vertex_ai",
)
else:
token, _ = vertex_base._ensure_access_token(
credentials=credentials,
project_id=None,
custom_llm_provider="vertex_ai",
)
assert mock_credentials_from_identity_pool.called
assert token == "refreshed-token"
@pytest.mark.parametrize("is_async", [True, False], ids=["async", "sync"])
@pytest.mark.asyncio
async def test_identity_pool_credentials_with_aws(self, is_async):
vertex_base = VertexBase()
# Test case: Using Workload Identity Federation for Microsoft Azure and
# OIDC identity providers (default behavior)
credentials = {
"project_id": "test-project",
"refresh_token": "fake-refresh-token",
"type": "external_account",
"credential_source": {
"environment_id": "aws1"
}
}
mock_creds = MagicMock()
mock_creds.token = "token-1"
mock_creds.expired = False
mock_creds.project_id = "test-project"
with patch.object(
vertex_base, "_credentials_from_identity_pool_with_aws", return_value=mock_creds
) as mock_credentials_from_identity_pool_with_aws, patch.object(
vertex_base, "refresh_auth"
) as mock_refresh:
def mock_refresh_impl(creds):
creds.token = "refreshed-token"
mock_refresh.side_effect = mock_refresh_impl
if is_async:
token, _ = await vertex_base._ensure_access_token_async(
credentials=credentials,
project_id=None,
custom_llm_provider="vertex_ai",
)
else:
token, _ = vertex_base._ensure_access_token(
credentials=credentials,
project_id=None,
custom_llm_provider="vertex_ai",
)
assert mock_credentials_from_identity_pool_with_aws.called
assert token == "refreshed-token"
@pytest.mark.parametrize(
"api_base, vertex_location, expected",
[
@ -270,6 +362,7 @@ class TestVertexBase:
),
],
)
def test_get_api_base(self, api_base, vertex_location, expected):
vertex_base = VertexBase()
assert (

View file

@ -1,5 +1,5 @@
import sys
import os
import sys
import pytest
@ -14,15 +14,21 @@ from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation im
@pytest.mark.parametrize(
"model, expected_thinking",
[
("claude-sonnet-4@20250514", True),
("claude-sonnet-4@20250514", True),
],
)
def test_vertex_ai_anthropic_thinking_param(model, expected_thinking):
supported_openai_params = VertexAIAnthropicConfig().get_supported_openai_params(
model=model
)
model=model
)
if expected_thinking:
assert "thinking" in supported_openai_params
else:
assert "thinking" not in supported_openai_params
def test_get_supported_params_thinking():
config = VertexAIAnthropicConfig()
params = config.get_supported_openai_params(model="claude-sonnet-4")
assert "thinking" in params

View file

@ -116,17 +116,19 @@ class TestUserAPIKeyAuthMCP:
mock_find_unique.assert_not_called()
@pytest.mark.parametrize(
"headers,expected_api_key",
"headers,expected_api_key,expected_mcp_auth_header",
[
# Test case 1: x-litellm-api-key header present
(
[(b"x-litellm-api-key", b"test-api-key-123")],
"test-api-key-123",
None,
),
# Test case 2: Authorization header present (fallback)
(
[(b"authorization", b"Bearer test-auth-token")],
"Bearer test-auth-token",
None,
),
# Test case 3: Both headers present (primary should win)
(
@ -135,22 +137,40 @@ class TestUserAPIKeyAuthMCP:
(b"authorization", b"Bearer fallback-token"),
],
"primary-key",
None,
),
# Test case 4: Case insensitive headers
(
[(b"X-LITELLM-API-KEY", b"case-insensitive-key")],
"case-insensitive-key",
None,
),
# Test case 5: No relevant headers
(
[(b"content-type", b"application/json")],
"",
None,
),
# Test case 6: Empty headers
([], ""),
([], "", None),
# Test case 7: MCP auth header present
(
[
(b"x-litellm-api-key", b"test-api-key-123"),
(b"x-mcp-auth", b"mcp-auth-token"),
],
"test-api-key-123",
"mcp-auth-token",
),
# Test case 8: Only MCP auth header present (no API key)
(
[(b"x-mcp-auth", b"mcp-auth-token")],
"",
"mcp-auth-token",
),
],
)
async def test_user_api_key_auth_mcp(self, headers, expected_api_key):
async def test_user_api_key_auth_mcp(self, headers, expected_api_key, expected_mcp_auth_header):
"""Test user_api_key_auth_mcp method with various header scenarios"""
# Create ASGI scope with headers
@ -174,10 +194,11 @@ class TestUserAPIKeyAuthMCP:
mock_user_api_key_auth.return_value = mock_auth_result
# Call the method
result = await UserAPIKeyAuthMCP.user_api_key_auth_mcp(scope)
auth_result, mcp_auth_header = await UserAPIKeyAuthMCP.user_api_key_auth_mcp(scope)
# Assert the result
assert result == mock_auth_result
# Assert the results
assert auth_result == mock_auth_result
assert mcp_auth_header == expected_mcp_auth_header
# Verify user_api_key_auth was called with correct parameters
mock_user_api_key_auth.assert_called_once()

View file

@ -18,11 +18,11 @@ from typing import Optional
from litellm.proxy._types import (
LiteLLM_MCPServerTable,
LitellmUserRoles,
MCPAuth,
MCPSpecVersion,
MCPTransport,
UserAPIKeyAuth,
)
from litellm.types.mcp import MCPAuth
from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPServer

View file

@ -26,6 +26,8 @@ async def test_create_and_get_tag():
"""
# Mock the prisma client and _get_tags_config and _save_tags_config
with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch(
"litellm.proxy.proxy_server.llm_router"
) as mock_router, patch(
"litellm.proxy.management_endpoints.tag_management_endpoints._get_tags_config"
) as mock_get_tags, patch(
"litellm.proxy.management_endpoints.tag_management_endpoints._save_tags_config"
@ -50,6 +52,7 @@ async def test_create_and_get_tag():
# Test tag creation
response = client.post("/tag/new", json=tag_data, headers=headers)
print(f"response: {response.text}")
assert response.status_code == 200
result = response.json()
assert result["message"] == "Tag test-tag created successfully"
@ -158,3 +161,93 @@ async def test_delete_tag():
# Verify _save_tags_config was called without the deleted tag
mock_save_tags.assert_called_once()
@pytest.mark.asyncio
async def test_get_deployments_by_model_id():
"""
Test get_deployments_by_model when model is found by model_id
"""
from unittest.mock import Mock
from litellm.proxy.management_endpoints.tag_management_endpoints import (
get_deployments_by_model,
)
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
# Create a mock router
mock_router = Mock()
# Setup mock to return deployment by model_id
mock_deployment = Deployment(
model_name="gpt-3.5-turbo",
litellm_params=LiteLLM_Params(model="gpt-3.5-turbo"),
model_info=ModelInfo(),
)
mock_router.get_deployment.return_value = mock_deployment
result = await get_deployments_by_model("model-123", mock_router)
assert len(result) == 1
assert result[0] == mock_deployment
mock_router.get_deployment.assert_called_once_with(model_id="model-123")
@pytest.mark.asyncio
async def test_get_deployments_by_model_name():
"""
Test get_deployments_by_model when model is found by model_name
"""
from unittest.mock import Mock
from litellm.proxy.management_endpoints.tag_management_endpoints import (
get_deployments_by_model,
)
from litellm.types.router import Deployment
# Create a mock router
mock_router = Mock()
# Setup mock to not find by model_id but find by model_name
mock_router.get_deployment.return_value = None
mock_router.get_model_list.return_value = [
{
"model_name": "gpt-3.5-turbo",
"litellm_params": {"model": "gpt-3.5-turbo", "api_key": "test-key"},
"model_info": {"id": "model-1", "description": "Test model"},
}
]
result = await get_deployments_by_model("gpt-3.5-turbo", mock_router)
assert len(result) == 1
assert result[0].model_name == "gpt-3.5-turbo"
assert isinstance(result[0], Deployment)
mock_router.get_deployment.assert_called_once_with(model_id="gpt-3.5-turbo")
mock_router.get_model_list.assert_called_once_with(model_name="gpt-3.5-turbo")
@pytest.mark.asyncio
async def test_get_deployments_by_model_not_found():
"""
Test get_deployments_by_model when model is not found
"""
from unittest.mock import Mock
from litellm.proxy.management_endpoints.tag_management_endpoints import (
get_deployments_by_model,
)
# Create a mock router
mock_router = Mock()
# Setup mock to not find model by either method
mock_router.get_deployment.return_value = None
mock_router.get_model_list.return_value = None
result = await get_deployments_by_model("nonexistent-model", mock_router)
assert len(result) == 0
assert result == []
mock_router.get_deployment.assert_called_once_with(model_id="nonexistent-model")
mock_router.get_model_list.assert_called_once_with(model_name="nonexistent-model")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more