diff --git a/.env.example b/.env.example index 82b09ca25e..54986a97cd 100644 --- a/.env.example +++ b/.env.example @@ -20,6 +20,8 @@ REPLICATE_API_TOKEN = "" ANTHROPIC_API_KEY = "" # Infisical INFISICAL_TOKEN = "" +# INFINITY +INFINITY_API_KEY = "" # Development Configs LITELLM_MASTER_KEY = "sk-1234" diff --git a/.gitignore b/.gitignore index 81fff2d342..e8c18bed4c 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,5 @@ litellm/proxy/db/migrations/0_init/migration.sql litellm/proxy/db/migrations/* litellm/proxy/migrations/*config.yaml litellm/proxy/migrations/* +config.yaml +tests/litellm/litellm_core_utils/llm_cost_calc/log.txt diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/deploy/charts/litellm-helm/templates/migrations-job.yaml index 1c4b6817fa..ba69f0fef8 100644 --- a/deploy/charts/litellm-helm/templates/migrations-job.yaml +++ b/deploy/charts/litellm-helm/templates/migrations-job.yaml @@ -16,6 +16,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} spec: + serviceAccountName: {{ include "litellm.serviceAccountName" . }} containers: - name: prisma-migrations image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "main-%s" .Chart.AppVersion) }}" diff --git a/docs/my-website/docs/completion/audio.md b/docs/my-website/docs/completion/audio.md index 97153a5867..96b5e4f41c 100644 --- a/docs/my-website/docs/completion/audio.md +++ b/docs/my-website/docs/completion/audio.md @@ -3,7 +3,7 @@ import TabItem from '@theme/TabItem'; # Using Audio Models -How to send / receieve audio to a `/chat/completions` endpoint +How to send / receive audio to a `/chat/completions` endpoint ## Audio Output from a model diff --git a/docs/my-website/docs/completion/document_understanding.md b/docs/my-website/docs/completion/document_understanding.md index f58b836c63..acebb2e160 100644 --- a/docs/my-website/docs/completion/document_understanding.md +++ b/docs/my-website/docs/completion/document_understanding.md @@ -3,7 +3,7 @@ import TabItem from '@theme/TabItem'; # Using PDF Input -How to send / receieve pdf's (other document types) to a `/chat/completions` endpoint +How to send / receive pdf's (other document types) to a `/chat/completions` endpoint Works for: - Vertex AI models (Gemini + Anthropic) diff --git a/docs/my-website/docs/completion/vision.md b/docs/my-website/docs/completion/vision.md index 1e18109b3b..7670008486 100644 --- a/docs/my-website/docs/completion/vision.md +++ b/docs/my-website/docs/completion/vision.md @@ -194,7 +194,7 @@ Expected Response ## Explicitly specify image type -If you have images without a mime-type, or if litellm is incorrectly inferring the mime type of your image (e.g. calling `gs://` url's with vertex ai), you can set this explicity via the `format` param. +If you have images without a mime-type, or if litellm is incorrectly inferring the mime type of your image (e.g. calling `gs://` url's with vertex ai), you can set this explicitly via the `format` param. ```python "image_url": { diff --git a/docs/my-website/docs/image_generation.md b/docs/my-website/docs/image_generation.md index 958ff4c020..5fe0bfb7d4 100644 --- a/docs/my-website/docs/image_generation.md +++ b/docs/my-website/docs/image_generation.md @@ -20,9 +20,9 @@ print(f"response: {response}") ```yaml model_list: - - model_name: dall-e-2 ### RECEIVED MODEL NAME ### + - model_name: gpt-image-1 ### RECEIVED MODEL NAME ### litellm_params: # all params accepted by litellm.image_generation() - model: azure/dall-e-2 ### MODEL NAME sent to `litellm.image_generation()` ### + model: azure/gpt-image-1 ### MODEL NAME sent to `litellm.image_generation()` ### api_base: https://my-endpoint-europe-berri-992.openai.azure.com/ api_key: "os.environ/AZURE_API_KEY_EU" # does os.getenv("AZURE_API_KEY_EU") rpm: 6 # [OPTIONAL] Rate limit for this deployment: in requests per minute (rpm) @@ -47,7 +47,7 @@ curl -X POST 'http://0.0.0.0:4000/v1/images/generations' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer sk-1234' \ -D '{ - "model": "dall-e-2", + "model": "gpt-image-1", "prompt": "A cute baby sea otter", "n": 1, "size": "1024x1024" @@ -104,7 +104,7 @@ Any non-openai params, will be treated as provider-specific params, and sent in litellm_logging_obj=None, custom_llm_provider=None, -- `model`: *string (optional)* The model to use for image generation. Defaults to openai/dall-e-2 +- `model`: *string (optional)* The model to use for image generation. Defaults to openai/gpt-image-1 - `n`: *int (optional)* The number of images to generate. Must be between 1 and 10. For dall-e-3, only n=1 is supported. @@ -112,7 +112,7 @@ Any non-openai params, will be treated as provider-specific params, and sent in - `response_format`: *string (optional)* The format in which the generated images are returned. Must be one of url or b64_json. -- `size`: *string (optional)* The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. +- `size`: *string (optional)* The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 for gpt-image-1. Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. - `timeout`: *integer* - The maximum time, in seconds, to wait for the API to respond. Defaults to 600 seconds (10 minutes). @@ -148,13 +148,14 @@ Any non-openai params, will be treated as provider-specific params, and sent in from litellm import image_generation import os os.environ['OPENAI_API_KEY'] = "" -response = image_generation(model='dall-e-2', prompt="cute baby otter") +response = image_generation(model='gpt-image-1', prompt="cute baby otter") ``` | Model Name | Function Call | Required OS Variables | |----------------------|---------------------------------------------|--------------------------------------| -| dall-e-2 | `image_generation(model='dall-e-2', prompt="cute baby otter")` | `os.environ['OPENAI_API_KEY']` | +| gpt-image-1 | `image_generation(model='gpt-image-1', prompt="cute baby otter")` | `os.environ['OPENAI_API_KEY']` | | dall-e-3 | `image_generation(model='dall-e-3', prompt="cute baby otter")` | `os.environ['OPENAI_API_KEY']` | +| dall-e-2 | `image_generation(model='dall-e-2', prompt="cute baby otter")` | `os.environ['OPENAI_API_KEY']` | ## Azure OpenAI Image Generation Models @@ -182,8 +183,9 @@ print(response) | Model Name | Function Call | |----------------------|---------------------------------------------| -| dall-e-2 | `image_generation(model="azure/", prompt="cute baby otter")` | +| gpt-image-1 | `image_generation(model="azure/", prompt="cute baby otter")` | | dall-e-3 | `image_generation(model="azure/", prompt="cute baby otter")` | +| dall-e-2 | `image_generation(model="azure/", prompt="cute baby otter")` | ## OpenAI Compatible Image Generation Models diff --git a/docs/my-website/docs/observability/agentops_integration.md b/docs/my-website/docs/observability/agentops_integration.md new file mode 100644 index 0000000000..e0599fab70 --- /dev/null +++ b/docs/my-website/docs/observability/agentops_integration.md @@ -0,0 +1,83 @@ +# 🖇️ AgentOps - LLM Observability Platform + +:::tip + +This is community maintained. Please make an issue if you run into a bug: +https://github.com/BerriAI/litellm + +::: + +[AgentOps](https://docs.agentops.ai) is an observability platform that enables tracing and monitoring of LLM calls, providing detailed insights into your AI operations. + +## Using AgentOps with LiteLLM + +LiteLLM provides `success_callbacks` and `failure_callbacks`, allowing you to easily integrate AgentOps for comprehensive tracing and monitoring of your LLM operations. + +### Integration + +Use just a few lines of code to instantly trace your responses **across all providers** with AgentOps: +Get your AgentOps API Keys from https://app.agentops.ai/ +```python +import litellm + +# Configure LiteLLM to use AgentOps +litellm.success_callback = ["agentops"] + +# Make your LLM calls as usual +response = litellm.completion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello, how are you?"}], +) +``` + +Complete Code: + +```python +import os +from litellm import completion + +# Set env variables +os.environ["OPENAI_API_KEY"] = "your-openai-key" +os.environ["AGENTOPS_API_KEY"] = "your-agentops-api-key" + +# Configure LiteLLM to use AgentOps +litellm.success_callback = ["agentops"] + +# OpenAI call +response = completion( + model="gpt-4", + messages=[{"role": "user", "content": "Hi 👋 - I'm OpenAI"}], +) + +print(response) +``` + +### Configuration Options + +The AgentOps integration can be configured through environment variables: + +- `AGENTOPS_API_KEY` (str, optional): Your AgentOps API key +- `AGENTOPS_ENVIRONMENT` (str, optional): Deployment environment (defaults to "production") +- `AGENTOPS_SERVICE_NAME` (str, optional): Service name for tracing (defaults to "agentops") + +### Advanced Usage + +You can configure additional settings through environment variables: + +```python +import os + +# Configure AgentOps settings +os.environ["AGENTOPS_API_KEY"] = "your-agentops-api-key" +os.environ["AGENTOPS_ENVIRONMENT"] = "staging" +os.environ["AGENTOPS_SERVICE_NAME"] = "my-service" + +# Enable AgentOps tracing +litellm.success_callback = ["agentops"] +``` + +### Support + +For issues or questions, please refer to: +- [AgentOps Documentation](https://docs.agentops.ai) +- [LiteLLM Documentation](https://docs.litellm.ai) \ No newline at end of file diff --git a/docs/my-website/docs/observability/greenscale_integration.md b/docs/my-website/docs/observability/greenscale_integration.md index 49eadc6453..c9b00cd0e8 100644 --- a/docs/my-website/docs/observability/greenscale_integration.md +++ b/docs/my-website/docs/observability/greenscale_integration.md @@ -53,7 +53,7 @@ response = completion( ## Additional information in metadata -You can send any additional information to Greenscale by using the `metadata` field in completion and `greenscale_` prefix. This can be useful for sending metadata about the request, such as the project and application name, customer_id, enviornment, or any other information you want to track usage. `greenscale_project` and `greenscale_application` are required fields. +You can send any additional information to Greenscale by using the `metadata` field in completion and `greenscale_` prefix. This can be useful for sending metadata about the request, such as the project and application name, customer_id, environment, or any other information you want to track usage. `greenscale_project` and `greenscale_application` are required fields. ```python #openai call with additional metadata diff --git a/docs/my-website/docs/observability/langfuse_integration.md b/docs/my-website/docs/observability/langfuse_integration.md index 9727730363..576135ba67 100644 --- a/docs/my-website/docs/observability/langfuse_integration.md +++ b/docs/my-website/docs/observability/langfuse_integration.md @@ -185,7 +185,7 @@ curl --location --request POST 'http://0.0.0.0:4000/chat/completions' \ * `trace_release` - Release for the trace, defaults to `None` * `trace_metadata` - Metadata for the trace, defaults to `None` * `trace_user_id` - User identifier for the trace, defaults to completion argument `user` -* `tags` - Tags for the trace, defeaults to `None` +* `tags` - Tags for the trace, defaults to `None` ##### Updatable Parameters on Continuation diff --git a/docs/my-website/docs/pass_through/cohere.md b/docs/my-website/docs/pass_through/cohere.md index a74ef695ef..227ff5777a 100644 --- a/docs/my-website/docs/pass_through/cohere.md +++ b/docs/my-website/docs/pass_through/cohere.md @@ -4,7 +4,7 @@ Pass-through endpoints for Cohere - call provider-specific endpoint, in native f | Feature | Supported | Notes | |-------|-------|-------| -| Cost Tracking | ✅ | works across all integrations | +| Cost Tracking | ✅ | Supported for `/v1/chat`, and `/v2/chat` | | Logging | ✅ | works across all integrations | | End-user Tracking | ❌ | [Tell us if you need this](https://github.com/BerriAI/litellm/issues/new) | | Streaming | ✅ | | diff --git a/docs/my-website/docs/pass_through/mistral.md b/docs/my-website/docs/pass_through/mistral.md new file mode 100644 index 0000000000..ee7ca800c4 --- /dev/null +++ b/docs/my-website/docs/pass_through/mistral.md @@ -0,0 +1,217 @@ +# Mistral + +Pass-through endpoints for Mistral - call provider-specific endpoint, in native format (no translation). + +| Feature | Supported | Notes | +|-------|-------|-------| +| Cost Tracking | ❌ | Not supported | +| Logging | ✅ | works across all integrations | +| End-user Tracking | ❌ | [Tell us if you need this](https://github.com/BerriAI/litellm/issues/new) | +| Streaming | ✅ | | + +Just replace `https://api.mistral.ai/v1` with `LITELLM_PROXY_BASE_URL/mistral` 🚀 + +#### **Example Usage** + +```bash +curl -L -X POST 'http://0.0.0.0:4000/mistral/v1/ocr' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ + "model": "mistral-ocr-latest", + "document": { + "type": "image_url", + "image_url": "https://raw.githubusercontent.com/mistralai/cookbook/refs/heads/main/mistral/ocr/receipt.png" + } + +}' +``` + +Supports **ALL** Mistral Endpoints (including streaming). + +## Quick Start + +Let's call the Mistral [`/chat/completions` endpoint](https://docs.mistral.ai/api/#tag/chat/operation/chat_completion_v1_chat_completions_post) + +1. Add MISTRAL_API_KEY to your environment + +```bash +export MISTRAL_API_KEY="sk-1234" +``` + +2. Start LiteLLM Proxy + +```bash +litellm + +# RUNNING on http://0.0.0.0:4000 +``` + +3. Test it! + +Let's call the Mistral `/ocr` endpoint + +```bash +curl -L -X POST 'http://0.0.0.0:4000/mistral/v1/ocr' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +-d '{ + "model": "mistral-ocr-latest", + "document": { + "type": "image_url", + "image_url": "https://raw.githubusercontent.com/mistralai/cookbook/refs/heads/main/mistral/ocr/receipt.png" + } + +}' +``` + + +## Examples + +Anything after `http://0.0.0.0:4000/mistral` is treated as a provider-specific route, and handled accordingly. + +Key Changes: + +| **Original Endpoint** | **Replace With** | +|------------------------------------------------------|-----------------------------------| +| `https://api.mistral.ai/v1` | `http://0.0.0.0:4000/mistral` (LITELLM_PROXY_BASE_URL="http://0.0.0.0:4000") | +| `bearer $MISTRAL_API_KEY` | `bearer anything` (use `bearer LITELLM_VIRTUAL_KEY` if Virtual Keys are setup on proxy) | + + +### **Example 1: OCR endpoint** + +#### LiteLLM Proxy Call + +```bash +curl -L -X POST 'http://0.0.0.0:4000/mistral/v1/ocr' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_API_KEY' \ +-d '{ + "model": "mistral-ocr-latest", + "document": { + "type": "image_url", + "image_url": "https://raw.githubusercontent.com/mistralai/cookbook/refs/heads/main/mistral/ocr/receipt.png" + } +}' +``` + + +#### Direct Mistral API Call + +```bash +curl https://api.mistral.ai/v1/ocr \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${MISTRAL_API_KEY}" \ + -d '{ + "model": "mistral-ocr-latest", + "document": { + "type": "document_url", + "document_url": "https://arxiv.org/pdf/2201.04234" + }, + "include_image_base64": true + }' +``` + +### **Example 2: Chat API** + +#### LiteLLM Proxy Call + +```bash +curl -L -X POST 'http://0.0.0.0:4000/mistral/v1/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_VIRTUAL_KEY' \ +-d '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "mistral-large-latest", +}' +``` + +#### Direct Mistral API Call + +```bash +curl -L -X POST 'https://api.mistral.ai/v1/chat/completions' \ +-H 'Content-Type: application/json' \ +-d '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "mistral-large-latest", +}' +``` + + +## Advanced - Use with Virtual Keys + +Pre-requisites +- [Setup proxy with DB](../proxy/virtual_keys.md#setup) + +Use this, to avoid giving developers the raw Mistral API key, but still letting them use Mistral endpoints. + +### Usage + +1. Setup environment + +```bash +export DATABASE_URL="" +export LITELLM_MASTER_KEY="" +export MISTRAL_API_BASE="" +``` + +```bash +litellm + +# RUNNING on http://0.0.0.0:4000 +``` + +2. Generate virtual key + +```bash +curl -X POST 'http://0.0.0.0:4000/key/generate' \ +-H 'Authorization: Bearer sk-1234' \ +-H 'Content-Type: application/json' \ +-d '{}' +``` + +Expected Response + +```bash +{ + ... + "key": "sk-1234ewknldferwedojwojw" +} +``` + +3. Test it! + + +```bash +curl -L -X POST 'http://0.0.0.0:4000/mistral/v1/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234ewknldferwedojwojw' \ + --data '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "qwen2.5-7b-instruct", +}' +``` \ No newline at end of file diff --git a/docs/my-website/docs/pass_through/vertex_ai.md b/docs/my-website/docs/pass_through/vertex_ai.md index 4918d889ed..b99f0fcf98 100644 --- a/docs/my-website/docs/pass_through/vertex_ai.md +++ b/docs/my-website/docs/pass_through/vertex_ai.md @@ -13,6 +13,15 @@ Pass-through endpoints for Vertex AI - call provider-specific endpoint, in nativ | End-user Tracking | ❌ | [Tell us if you need this](https://github.com/BerriAI/litellm/issues/new) | | Streaming | ✅ | | +## Supported Endpoints + +LiteLLM supports 2 vertex ai passthrough routes: + +1. `/vertex_ai` → routes to `https://{vertex_location}-aiplatform.googleapis.com/` +2. `/vertex_ai/discovery` → routes to [`https://discoveryengine.googleapis.com`](https://discoveryengine.googleapis.com/) + +## How to use + Just replace `https://REGION-aiplatform.googleapis.com` with `LITELLM_PROXY_BASE_URL/vertex_ai` LiteLLM supports 3 flows for calling Vertex AI endpoints via pass-through: @@ -213,7 +222,7 @@ curl http://localhost:4000/vertex-ai/v1/projects/${PROJECT_ID}/locations/us-cent LiteLLM Proxy Server supports two methods of authentication to Vertex AI: -1. Pass Vertex Credetials client side to proxy server +1. Pass Vertex Credentials client side to proxy server 2. Set Vertex AI credentials on proxy server diff --git a/docs/my-website/docs/pass_through/vllm.md b/docs/my-website/docs/pass_through/vllm.md new file mode 100644 index 0000000000..b267622948 --- /dev/null +++ b/docs/my-website/docs/pass_through/vllm.md @@ -0,0 +1,185 @@ +# VLLM + +Pass-through endpoints for VLLM - call provider-specific endpoint, in native format (no translation). + +| Feature | Supported | Notes | +|-------|-------|-------| +| Cost Tracking | ❌ | Not supported | +| Logging | ✅ | works across all integrations | +| End-user Tracking | ❌ | [Tell us if you need this](https://github.com/BerriAI/litellm/issues/new) | +| Streaming | ✅ | | + +Just replace `https://my-vllm-server.com` with `LITELLM_PROXY_BASE_URL/vllm` 🚀 + +#### **Example Usage** + +```bash +curl -L -X GET 'http://0.0.0.0:4000/vllm/metrics' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +``` + +Supports **ALL** VLLM Endpoints (including streaming). + +## Quick Start + +Let's call the VLLM [`/metrics` endpoint](https://vllm.readthedocs.io/en/latest/api_reference/api_reference.html) + +1. Add HOSTED VLLM API BASE to your environment + +```bash +export HOSTED_VLLM_API_BASE="https://my-vllm-server.com" +``` + +2. Start LiteLLM Proxy + +```bash +litellm + +# RUNNING on http://0.0.0.0:4000 +``` + +3. Test it! + +Let's call the VLLM `/metrics` endpoint + +```bash +curl -L -X GET 'http://0.0.0.0:4000/vllm/metrics' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234' \ +``` + + +## Examples + +Anything after `http://0.0.0.0:4000/vllm` is treated as a provider-specific route, and handled accordingly. + +Key Changes: + +| **Original Endpoint** | **Replace With** | +|------------------------------------------------------|-----------------------------------| +| `https://my-vllm-server.com` | `http://0.0.0.0:4000/vllm` (LITELLM_PROXY_BASE_URL="http://0.0.0.0:4000") | +| `bearer $VLLM_API_KEY` | `bearer anything` (use `bearer LITELLM_VIRTUAL_KEY` if Virtual Keys are setup on proxy) | + + +### **Example 1: Metrics endpoint** + +#### LiteLLM Proxy Call + +```bash +curl -L -X GET 'http://0.0.0.0:4000/vllm/metrics' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_VIRTUAL_KEY' \ +``` + + +#### Direct VLLM API Call + +```bash +curl -L -X GET 'https://my-vllm-server.com/metrics' \ +-H 'Content-Type: application/json' \ +``` + +### **Example 2: Chat API** + +#### LiteLLM Proxy Call + +```bash +curl -L -X POST 'http://0.0.0.0:4000/vllm/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer $LITELLM_VIRTUAL_KEY' \ +-d '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "qwen2.5-7b-instruct", +}' +``` + +#### Direct VLLM API Call + +```bash +curl -L -X POST 'https://my-vllm-server.com/chat/completions' \ +-H 'Content-Type: application/json' \ +-d '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "qwen2.5-7b-instruct", +}' +``` + + +## Advanced - Use with Virtual Keys + +Pre-requisites +- [Setup proxy with DB](../proxy/virtual_keys.md#setup) + +Use this, to avoid giving developers the raw Cohere API key, but still letting them use Cohere endpoints. + +### Usage + +1. Setup environment + +```bash +export DATABASE_URL="" +export LITELLM_MASTER_KEY="" +export HOSTED_VLLM_API_BASE="" +``` + +```bash +litellm + +# RUNNING on http://0.0.0.0:4000 +``` + +2. Generate virtual key + +```bash +curl -X POST 'http://0.0.0.0:4000/key/generate' \ +-H 'Authorization: Bearer sk-1234' \ +-H 'Content-Type: application/json' \ +-d '{}' +``` + +Expected Response + +```bash +{ + ... + "key": "sk-1234ewknldferwedojwojw" +} +``` + +3. Test it! + + +```bash +curl -L -X POST 'http://0.0.0.0:4000/vllm/chat/completions' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer sk-1234ewknldferwedojwojw' \ + --data '{ + "messages": [ + { + "role": "user", + "content": "I am going to Paris, what should I see?" + } + ], + "max_tokens": 2048, + "temperature": 0.8, + "top_p": 0.1, + "model": "qwen2.5-7b-instruct", +}' +``` \ No newline at end of file diff --git a/docs/my-website/docs/providers/anthropic.md b/docs/my-website/docs/providers/anthropic.md index 9e4f6908a4..95323719f0 100644 --- a/docs/my-website/docs/providers/anthropic.md +++ b/docs/my-website/docs/providers/anthropic.md @@ -1095,7 +1095,7 @@ response = completion( print(response.choices[0]) ``` - + 1. Add model to config diff --git a/docs/my-website/docs/providers/azure.md b/docs/my-website/docs/providers/azure.md index 30bf3be1e4..2ea444b029 100644 --- a/docs/my-website/docs/providers/azure.md +++ b/docs/my-website/docs/providers/azure.md @@ -483,7 +483,7 @@ response.stream_to_file(speech_file_path) This is a walkthrough on how to use Azure Active Directory Tokens - Microsoft Entra ID to make `litellm.completion()` calls Step 1 - Download Azure CLI -Installation instructons: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli +Installation instructions: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli ```shell brew update && brew install azure-cli ``` @@ -1002,8 +1002,125 @@ Expected Response: ``` +## **Azure Responses API** +| Property | Details | +|-------|-------| +| Description | Azure OpenAI Responses API | +| `custom_llm_provider` on LiteLLM | `azure/` | +| Supported Operations | `/v1/responses`| +| Azure OpenAI Responses API | [Azure OpenAI Responses API ↗](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure) | +| Cost Tracking, Logging Support | ✅ LiteLLM will log, track cost for Responses API Requests | +| Supported OpenAI Params | ✅ All OpenAI params are supported, [See here](https://github.com/BerriAI/litellm/blob/0717369ae6969882d149933da48eeb8ab0e691bd/litellm/llms/openai/responses/transformation.py#L23) | +## Usage + +## Create a model response + + + + +#### Non-streaming + +```python showLineNumbers title="Azure Responses API" +import litellm + +# Non-streaming response +response = litellm.responses( + model="azure/o1-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + max_output_tokens=100, + api_key=os.getenv("AZURE_RESPONSES_OPENAI_API_KEY"), + api_base="https://litellm8397336933.openai.azure.com/", + api_version="2023-03-15-preview", +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Azure Responses API" +import litellm + +# Streaming response +response = litellm.responses( + model="azure/o1-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True, + api_key=os.getenv("AZURE_RESPONSES_OPENAI_API_KEY"), + api_base="https://litellm8397336933.openai.azure.com/", + api_version="2023-03-15-preview", +) + +for event in response: + print(event) +``` + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="Azure Responses API" +model_list: + - model_name: o1-pro + litellm_params: + model: azure/o1-pro + api_key: os.environ/AZURE_RESPONSES_OPENAI_API_KEY + api_base: https://litellm8397336933.openai.azure.com/ + api_version: 2023-03-15-preview +``` + +Start your LiteLLM proxy: +```bash +litellm --config /path/to/config.yaml + +# RUNNING on http://0.0.0.0:4000 +``` + +Then use the OpenAI SDK pointed to your proxy: + +#### Non-streaming +```python showLineNumbers +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="o1-pro", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="o1-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + diff --git a/docs/my-website/docs/providers/gemini.md b/docs/my-website/docs/providers/gemini.md index db63d33d8d..957c5c13c2 100644 --- a/docs/my-website/docs/providers/gemini.md +++ b/docs/my-website/docs/providers/gemini.md @@ -39,14 +39,164 @@ response = completion( - temperature - top_p - max_tokens +- max_completion_tokens - stream - tools - tool_choice +- functions - response_format - n - stop +- logprobs +- frequency_penalty +- modalities +- reasoning_content + +**Anthropic Params** +- thinking (used to set max budget tokens across anthropic/gemini models) + +[**See Updated List**](https://github.com/BerriAI/litellm/blob/main/litellm/llms/gemini/chat/transformation.py#L70) + + + +## Usage - Thinking / `reasoning_content` + +LiteLLM translates OpenAI's `reasoning_effort` to Gemini's `thinking` parameter. [Code](https://github.com/BerriAI/litellm/blob/620664921902d7a9bfb29897a7b27c1a7ef4ddfb/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py#L362) + +**Mapping** + +| reasoning_effort | thinking | +| ---------------- | -------- | +| "low" | "budget_tokens": 1024 | +| "medium" | "budget_tokens": 2048 | +| "high" | "budget_tokens": 4096 | + + + + +```python +from litellm import completion + +resp = completion( + model="gemini/gemini-2.5-flash-preview-04-17", + messages=[{"role": "user", "content": "What is the capital of France?"}], + reasoning_effort="low", +) + +``` + + + + + +1. Setup config.yaml + +```yaml +- model_name: gemini-2.5-flash + litellm_params: + model: gemini/gemini-2.5-flash-preview-04-17 + api_key: os.environ/GEMINI_API_KEY +``` + +2. Start proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [{"role": "user", "content": "What is the capital of France?"}], + "reasoning_effort": "low" + }' +``` + + + + + +**Expected Response** + +```python +ModelResponse( + id='chatcmpl-c542d76d-f675-4e87-8e5f-05855f5d0f5e', + created=1740470510, + model='claude-3-7-sonnet-20250219', + object='chat.completion', + system_fingerprint=None, + choices=[ + Choices( + finish_reason='stop', + index=0, + message=Message( + content="The capital of France is Paris.", + role='assistant', + tool_calls=None, + function_call=None, + reasoning_content='The capital of France is Paris. This is a very straightforward factual question.' + ), + ) + ], + usage=Usage( + completion_tokens=68, + prompt_tokens=42, + total_tokens=110, + completion_tokens_details=None, + prompt_tokens_details=PromptTokensDetailsWrapper( + audio_tokens=None, + cached_tokens=0, + text_tokens=None, + image_tokens=None + ), + cache_creation_input_tokens=0, + cache_read_input_tokens=0 + ) +) +``` + +### Pass `thinking` to Gemini models + +You can also pass the `thinking` parameter to Gemini models. + +This is translated to Gemini's [`thinkingConfig` parameter](https://ai.google.dev/gemini-api/docs/thinking#set-budget). + + + + +```python +response = litellm.completion( + model="gemini/gemini-2.5-flash-preview-04-17", + messages=[{"role": "user", "content": "What is the capital of France?"}], + thinking={"type": "enabled", "budget_tokens": 1024}, +) +``` + + + + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $LITELLM_KEY" \ + -d '{ + "model": "gemini/gemini-2.5-flash-preview-04-17", + "messages": [{"role": "user", "content": "What is the capital of France?"}], + "thinking": {"type": "enabled", "budget_tokens": 1024} + }' +``` + + + + + + -[**See Updated List**](https://github.com/BerriAI/litellm/blob/1c747f3ad372399c5b95cc5696b06a5fbe53186b/litellm/llms/vertex_httpx.py#L122) ## Passing Gemini Specific Params ### Response schema @@ -505,7 +655,7 @@ import os os.environ["GEMINI_API_KEY"] = ".." -tools = [{"googleSearchRetrieval": {}}] # 👈 ADD GOOGLE SEARCH +tools = [{"googleSearch": {}}] # 👈 ADD GOOGLE SEARCH response = completion( model="gemini/gemini-2.0-flash", @@ -541,7 +691,7 @@ curl -X POST 'http://0.0.0.0:4000/chat/completions' \ -d '{ "model": "gemini-2.0-flash", "messages": [{"role": "user", "content": "What is the weather in San Francisco?"}], - "tools": [{"googleSearchRetrieval": {}}] + "tools": [{"googleSearch": {}}] } ' ``` diff --git a/docs/my-website/docs/providers/infinity.md b/docs/my-website/docs/providers/infinity.md index 091503bf18..7900d5adb4 100644 --- a/docs/my-website/docs/providers/infinity.md +++ b/docs/my-website/docs/providers/infinity.md @@ -3,18 +3,17 @@ import TabItem from '@theme/TabItem'; # Infinity -| Property | Details | -|-------|-------| -| Description | Infinity is a high-throughput, low-latency REST API for serving text-embeddings, reranking models and clip| -| Provider Route on LiteLLM | `infinity/` | -| Supported Operations | `/rerank` | -| Link to Provider Doc | [Infinity ↗](https://github.com/michaelfeil/infinity) | - +| Property | Details | +| ------------------------- | ---------------------------------------------------------------------------------------------------------- | +| Description | Infinity is a high-throughput, low-latency REST API for serving text-embeddings, reranking models and clip | +| Provider Route on LiteLLM | `infinity/` | +| Supported Operations | `/rerank`, `/embeddings` | +| Link to Provider Doc | [Infinity ↗](https://github.com/michaelfeil/infinity) | ## **Usage - LiteLLM Python SDK** ```python -from litellm import rerank +from litellm import rerank, embedding import os os.environ["INFINITY_API_BASE"] = "http://localhost:8080" @@ -39,8 +38,8 @@ model_list: - model_name: custom-infinity-rerank litellm_params: model: infinity/rerank - api_key: os.environ/INFINITY_API_KEY api_base: https://localhost:8080 + api_key: os.environ/INFINITY_API_KEY ``` Start litellm @@ -51,7 +50,9 @@ litellm --config /path/to/config.yaml # RUNNING on http://0.0.0.0:4000 ``` -Test request +## Test request: + +### Rerank ```bash curl http://0.0.0.0:4000/rerank \ @@ -70,15 +71,14 @@ curl http://0.0.0.0:4000/rerank \ }' ``` +#### Supported Cohere Rerank API Params -## Supported Cohere Rerank API Params - -| Param | Type | Description | -|-------|-------|-------| -| `query` | `str` | The query to rerank the documents against | -| `documents` | `list[str]` | The documents to rerank | -| `top_n` | `int` | The number of documents to return | -| `return_documents` | `bool` | Whether to return the documents in the response | +| Param | Type | Description | +| ------------------ | ----------- | ----------------------------------------------- | +| `query` | `str` | The query to rerank the documents against | +| `documents` | `list[str]` | The documents to rerank | +| `top_n` | `int` | The number of documents to return | +| `return_documents` | `bool` | Whether to return the documents in the response | ### Usage - Return Documents @@ -138,6 +138,7 @@ response = rerank( raw_scores=True, # 👈 PROVIDER-SPECIFIC PARAM ) ``` + @@ -161,7 +162,7 @@ litellm --config /path/to/config.yaml # RUNNING on http://0.0.0.0:4000 ``` -3. Test it! +3. Test it! ```bash curl http://0.0.0.0:4000/rerank \ @@ -179,6 +180,121 @@ curl http://0.0.0.0:4000/rerank \ "raw_scores": True # 👈 PROVIDER-SPECIFIC PARAM }' ``` + + +## Embeddings + +LiteLLM provides an OpenAI api compatible `/embeddings` endpoint for embedding calls. + +**Setup** + +Add this to your litellm proxy config.yaml + +```yaml +model_list: + - model_name: custom-infinity-embedding + litellm_params: + model: infinity/provider/custom-embedding-v1 + api_base: http://localhost:8080 + api_key: os.environ/INFINITY_API_KEY +``` + +### Test request: + +```bash +curl http://0.0.0.0:4000/embeddings \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "custom-infinity-embedding", + "input": ["hello"] + }' +``` + +#### Supported Embedding API Params + +| Param | Type | Description | +| ----------------- | ----------- | ----------------------------------------------------------- | +| `model` | `str` | The embedding model to use | +| `input` | `list[str]` | The text inputs to generate embeddings for | +| `encoding_format` | `str` | The format to return embeddings in (e.g. "float", "base64") | +| `modality` | `str` | The type of input (e.g. "text", "image", "audio") | + +### Usage - Basic Examples + + + + +```python +from litellm import embedding +import os + +os.environ["INFINITY_API_BASE"] = "http://localhost:8080" + +response = embedding( + model="infinity/bge-small", + input=["good morning from litellm"] +) + +print(response.data[0]['embedding']) +``` + + + + + +```bash +curl http://0.0.0.0:4000/embeddings \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "custom-infinity-embedding", + "input": ["hello"] + }' +``` + + + + +### Usage - OpenAI Client + + + + +```python +from openai import OpenAI + +client = OpenAI( + api_key="", + base_url="" +) + +response = client.embeddings.create( + model="bge-small", + input=["The food was delicious and the waiter..."], + encoding_format="float" +) + +print(response.data[0].embedding) +``` + + + + + +```bash +curl http://0.0.0.0:4000/embeddings \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "bge-small", + "input": ["The food was delicious and the waiter..."], + "encoding_format": "float" + }' +``` + + + diff --git a/docs/my-website/docs/providers/openai.md b/docs/my-website/docs/providers/openai.md index 9ab9061aaa..a4aee5dbf7 100644 --- a/docs/my-website/docs/providers/openai.md +++ b/docs/my-website/docs/providers/openai.md @@ -163,6 +163,12 @@ os.environ["OPENAI_API_BASE"] = "openaiai-api-base" # OPTIONAL | Model Name | Function Call | |-----------------------|-----------------------------------------------------------------| +| gpt-4.1 | `response = completion(model="gpt-4.1", messages=messages)` | +| gpt-4.1-mini | `response = completion(model="gpt-4.1-mini", messages=messages)` | +| gpt-4.1-nano | `response = completion(model="gpt-4.1-nano", messages=messages)` | +| o4-mini | `response = completion(model="o4-mini", messages=messages)` | +| o3-mini | `response = completion(model="o3-mini", messages=messages)` | +| o3 | `response = completion(model="o3", messages=messages)` | | o1-mini | `response = completion(model="o1-mini", messages=messages)` | | o1-preview | `response = completion(model="o1-preview", messages=messages)` | | gpt-4o-mini | `response = completion(model="gpt-4o-mini", messages=messages)` | diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 476cc8a453..b328c80577 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -364,7 +364,7 @@ from litellm import completion ## SETUP ENVIRONMENT # !gcloud auth application-default login - run this to add vertex credentials to your env -tools = [{"googleSearchRetrieval": {}}] # 👈 ADD GOOGLE SEARCH +tools = [{"googleSearch": {}}] # 👈 ADD GOOGLE SEARCH resp = litellm.completion( model="vertex_ai/gemini-1.0-pro-001", @@ -391,7 +391,7 @@ client = OpenAI( response = client.chat.completions.create( model="gemini-pro", messages=[{"role": "user", "content": "Who won the world cup?"}], - tools=[{"googleSearchRetrieval": {}}], + tools=[{"googleSearch": {}}], ) print(response) @@ -410,7 +410,7 @@ curl http://localhost:4000/v1/chat/completions \ ], "tools": [ { - "googleSearchRetrieval": {} + "googleSearch": {} } ] }' @@ -529,7 +529,7 @@ from litellm import completion # !gcloud auth application-default login - run this to add vertex credentials to your env -tools = [{"googleSearchRetrieval": {"disable_attributon": False}}] # 👈 ADD GOOGLE SEARCH +tools = [{"googleSearch": {"disable_attributon": False}}] # 👈 ADD GOOGLE SEARCH resp = litellm.completion( model="vertex_ai/gemini-1.0-pro-001", @@ -542,9 +542,157 @@ print(resp) ``` +### **Thinking / `reasoning_content`** + +LiteLLM translates OpenAI's `reasoning_effort` to Gemini's `thinking` parameter. [Code](https://github.com/BerriAI/litellm/blob/620664921902d7a9bfb29897a7b27c1a7ef4ddfb/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py#L362) + +**Mapping** + +| reasoning_effort | thinking | +| ---------------- | -------- | +| "low" | "budget_tokens": 1024 | +| "medium" | "budget_tokens": 2048 | +| "high" | "budget_tokens": 4096 | + + + + +```python +from litellm import completion + +# !gcloud auth application-default login - run this to add vertex credentials to your env + +resp = completion( + model="vertex_ai/gemini-2.5-flash-preview-04-17", + messages=[{"role": "user", "content": "What is the capital of France?"}], + reasoning_effort="low", + vertex_project="project-id", + vertex_location="us-central1" +) + +``` + + + + + +1. Setup config.yaml + +```yaml +- model_name: gemini-2.5-flash + litellm_params: + model: vertex_ai/gemini-2.5-flash-preview-04-17 + vertex_credentials: {"project_id": "project-id", "location": "us-central1", "project_key": "project-key"} + vertex_project: "project-id" + vertex_location: "us-central1" +``` + +2. Start proxy + +```bash +litellm --config /path/to/config.yaml +``` + +3. Test it! + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "model": "gemini-2.5-flash", + "messages": [{"role": "user", "content": "What is the capital of France?"}], + "reasoning_effort": "low" + }' +``` + + + + + +**Expected Response** + +```python +ModelResponse( + id='chatcmpl-c542d76d-f675-4e87-8e5f-05855f5d0f5e', + created=1740470510, + model='claude-3-7-sonnet-20250219', + object='chat.completion', + system_fingerprint=None, + choices=[ + Choices( + finish_reason='stop', + index=0, + message=Message( + content="The capital of France is Paris.", + role='assistant', + tool_calls=None, + function_call=None, + reasoning_content='The capital of France is Paris. This is a very straightforward factual question.' + ), + ) + ], + usage=Usage( + completion_tokens=68, + prompt_tokens=42, + total_tokens=110, + completion_tokens_details=None, + prompt_tokens_details=PromptTokensDetailsWrapper( + audio_tokens=None, + cached_tokens=0, + text_tokens=None, + image_tokens=None + ), + cache_creation_input_tokens=0, + cache_read_input_tokens=0 + ) +) +``` + +#### Pass `thinking` to Gemini models + +You can also pass the `thinking` parameter to Gemini models. + +This is translated to Gemini's [`thinkingConfig` parameter](https://ai.google.dev/gemini-api/docs/thinking#set-budget). + + + + +```python +from litellm import completion + +# !gcloud auth application-default login - run this to add vertex credentials to your env + +response = litellm.completion( + model="vertex_ai/gemini-2.5-flash-preview-04-17", + messages=[{"role": "user", "content": "What is the capital of France?"}], + thinking={"type": "enabled", "budget_tokens": 1024}, + vertex_project="project-id", + vertex_location="us-central1" +) +``` + + + + +```bash +curl http://0.0.0.0:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $LITELLM_KEY" \ + -d '{ + "model": "vertex_ai/gemini-2.5-flash-preview-04-17", + "messages": [{"role": "user", "content": "What is the capital of France?"}], + "thinking": {"type": "enabled", "budget_tokens": 1024} + }' +``` + + + + + ### **Context Caching** -Use Vertex AI context caching is supported by calling provider api directly. (Unified Endpoint support comin soon.). +Use Vertex AI context caching is supported by calling provider api directly. (Unified Endpoint support coming soon.). [**Go straight to provider**](../pass_through/vertex_ai.md#context-caching) @@ -762,7 +910,7 @@ export VERTEXAI_PROJECT="my-test-project" # ONLY use if model project is differe ## Specifying Safety Settings -In certain use-cases you may need to make calls to the models and pass [safety settigns](https://ai.google.dev/docs/safety_setting_gemini) different from the defaults. To do so, simple pass the `safety_settings` argument to `completion` or `acompletion`. For example: +In certain use-cases you may need to make calls to the models and pass [safety settings](https://ai.google.dev/docs/safety_setting_gemini) different from the defaults. To do so, simple pass the `safety_settings` argument to `completion` or `acompletion`. For example: ### Set per model/request @@ -1902,7 +2050,7 @@ response = completion( print(response.choices[0]) ``` - + 1. Add model to config diff --git a/docs/my-website/docs/providers/vllm.md b/docs/my-website/docs/providers/vllm.md index b5987167ec..5c8233b056 100644 --- a/docs/my-website/docs/providers/vllm.md +++ b/docs/my-website/docs/providers/vllm.md @@ -161,6 +161,120 @@ curl -L -X POST 'http://0.0.0.0:4000/embeddings' \ Example Implementation from VLLM [here](https://github.com/vllm-project/vllm/pull/10020) + + + +Use this to send a video url to VLLM + Gemini in the same format, using OpenAI's `files` message type. + +There are two ways to send a video url to VLLM: + +1. Pass the video url directly + +``` +{"type": "file", "file": {"file_id": video_url}}, +``` + +2. Pass the video data as base64 + +``` +{"type": "file", "file": {"file_data": f"data:video/mp4;base64,{video_data_base64}"}} +``` + + + + +```python +from litellm import completion + +messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Summarize the following video" + }, + { + "type": "file", + "file": { + "file_id": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + } + } + ] + } +] + +# call vllm +os.environ["HOSTED_VLLM_API_BASE"] = "https://hosted-vllm-api.co" +os.environ["HOSTED_VLLM_API_KEY"] = "" # [optional], if your VLLM server requires an API key +response = completion( + model="hosted_vllm/qwen", # pass the vllm model name + messages=messages, +) + +# call gemini +os.environ["GEMINI_API_KEY"] = "your-gemini-api-key" +response = completion( + model="gemini/gemini-1.5-flash", # pass the gemini model name + messages=messages, +) + +print(response) +``` + + + + +1. Setup config.yaml + +```yaml +model_list: + - model_name: my-model + litellm_params: + model: hosted_vllm/qwen # add hosted_vllm/ prefix to route as OpenAI provider + api_base: https://hosted-vllm-api.co # add api base for OpenAI compatible provider + - model_name: my-gemini-model + litellm_params: + model: gemini/gemini-1.5-flash # add gemini/ prefix to route as Google AI Studio provider + api_key: os.environ/GEMINI_API_KEY +``` + +2. Start the proxy + +```bash +$ litellm --config /path/to/config.yaml + +# RUNNING on http://0.0.0.0:4000 +``` + +3. Test it! + +```bash +curl -X POST http://0.0.0.0:4000/chat/completions \ +-H "Authorization: Bearer sk-1234" \ +-H "Content-Type: application/json" \ +-d '{ + "model": "my-model", + "messages": [ + {"role": "user", "content": + [ + {"type": "text", "text": "Summarize the following video"}, + {"type": "file", "file": {"file_id": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}} + ] + } + ] +}' +``` + + + + + + + + +Use this to send a video url to VLLM in it's native message format (`video_url`). + There are two ways to send a video url to VLLM: 1. Pass the video url directly @@ -249,6 +363,10 @@ curl -X POST http://0.0.0.0:4000/chat/completions \ + + + + ## (Deprecated) for `vllm pip package` ### Using - `litellm.completion` diff --git a/docs/my-website/docs/proxy/admin_ui_sso.md b/docs/my-website/docs/proxy/admin_ui_sso.md index 0bbba57fd9..a0dde80e9c 100644 --- a/docs/my-website/docs/proxy/admin_ui_sso.md +++ b/docs/my-website/docs/proxy/admin_ui_sso.md @@ -243,12 +243,12 @@ We allow you to pass a local image or a an http/https url of your image Set `UI_LOGO_PATH` on your env. We recommend using a hosted image, it's a lot easier to set up and configure / debug -Exaple setting Hosted image +Example setting Hosted image ```shell UI_LOGO_PATH="https://litellm-logo-aws-marketplace.s3.us-west-2.amazonaws.com/berriai-logo-github.png" ``` -Exaple setting a local image (on your container) +Example setting a local image (on your container) ```shell UI_LOGO_PATH="ui_images/logo.jpg" ``` diff --git a/docs/my-website/docs/proxy/alerting.md b/docs/my-website/docs/proxy/alerting.md index c2fc510d96..e2f6223c8f 100644 --- a/docs/my-website/docs/proxy/alerting.md +++ b/docs/my-website/docs/proxy/alerting.md @@ -213,7 +213,7 @@ model_list: general_settings: master_key: sk-1234 alerting: ["slack"] - alerting_threshold: 0.0001 # (Seconds) set an artifically low threshold for testing alerting + alerting_threshold: 0.0001 # (Seconds) set an artificially low threshold for testing alerting alert_to_webhook_url: { "llm_exceptions": "https://hooks.slack.com/services/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", "llm_too_slow": "https://hooks.slack.com/services/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", @@ -247,7 +247,7 @@ model_list: general_settings: master_key: sk-1234 alerting: ["slack"] - alerting_threshold: 0.0001 # (Seconds) set an artifically low threshold for testing alerting + alerting_threshold: 0.0001 # (Seconds) set an artificially low threshold for testing alerting alert_to_webhook_url: { "llm_exceptions": ["os.environ/SLACK_WEBHOOK_URL", "os.environ/SLACK_WEBHOOK_URL_2"], "llm_too_slow": ["https://webhook.site/7843a980-a494-4967-80fb-d502dbc16886", "https://webhook.site/28cfb179-f4fb-4408-8129-729ff55cf213"], @@ -425,7 +425,7 @@ curl -X GET --location 'http://0.0.0.0:4000/health/services?service=webhook' \ - `projected_exceeded_date` *str or null*: The date when the budget is projected to be exceeded, returned when 'soft_budget' is set for key (optional). - `projected_spend` *float or null*: The projected spend amount, returned when 'soft_budget' is set for key (optional). - `event` *Literal["budget_crossed", "threshold_crossed", "projected_limit_exceeded"]*: The type of event that triggered the webhook. Possible values are: - * "spend_tracked": Emitted whenver spend is tracked for a customer id. + * "spend_tracked": Emitted whenever spend is tracked for a customer id. * "budget_crossed": Indicates that the spend has exceeded the max budget. * "threshold_crossed": Indicates that spend has crossed a threshold (currently sent when 85% and 95% of budget is reached). * "projected_limit_exceeded": For "key" only - Indicates that the projected spend is expected to exceed the soft budget threshold. @@ -480,7 +480,7 @@ LLM-related Alerts | `cooldown_deployment` | Alerts when a deployment is put into cooldown | ✅ | | `new_model_added` | Notifications when a new model is added to litellm proxy through /model/new| ✅ | | `outage_alerts` | Alerts when a specific LLM deployment is facing an outage | ✅ | -| `region_outage_alerts` | Alerts when a specfic LLM region is facing an outage. Example us-east-1 | ✅ | +| `region_outage_alerts` | Alerts when a specific LLM region is facing an outage. Example us-east-1 | ✅ | Budget and Spend Alerts diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index bd8e2116c2..1e3c800b03 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -299,6 +299,9 @@ router_settings: |------|-------------| | ACTIONS_ID_TOKEN_REQUEST_TOKEN | Token for requesting ID in GitHub Actions | ACTIONS_ID_TOKEN_REQUEST_URL | URL for requesting ID token in GitHub Actions +| AGENTOPS_ENVIRONMENT | Environment for AgentOps logging integration +| AGENTOPS_API_KEY | API Key for AgentOps logging integration +| AGENTOPS_SERVICE_NAME | Service Name for AgentOps logging integration | AISPEND_ACCOUNT_ID | Account ID for AI Spend | AISPEND_API_KEY | API Key for AI Spend | ALLOWED_EMAIL_DOMAINS | List of email domains allowed for access diff --git a/docs/my-website/docs/proxy/custom_pricing.md b/docs/my-website/docs/proxy/custom_pricing.md index 792d5c26dd..e2df7721bf 100644 --- a/docs/my-website/docs/proxy/custom_pricing.md +++ b/docs/my-website/docs/proxy/custom_pricing.md @@ -56,7 +56,7 @@ model_list: model: azure/ api_key: os.environ/AZURE_API_KEY api_base: os.environ/AZURE_API_BASE - api_version: os.envrion/AZURE_API_VERSION + api_version: os.environ/AZURE_API_VERSION model_info: input_cost_per_token: 0.000421 # 👈 ONLY to track cost per token output_cost_per_token: 0.000520 # 👈 ONLY to track cost per token @@ -133,4 +133,4 @@ acompletion( If these keys are not present, LiteLLM will not use your custom pricing. -If the problem persists, please file an issue on [GitHub](https://github.com/BerriAI/litellm/issues). \ No newline at end of file +If the problem persists, please file an issue on [GitHub](https://github.com/BerriAI/litellm/issues). diff --git a/docs/my-website/docs/proxy/db_deadlocks.md b/docs/my-website/docs/proxy/db_deadlocks.md index 332374995d..0eee928fa6 100644 --- a/docs/my-website/docs/proxy/db_deadlocks.md +++ b/docs/my-website/docs/proxy/db_deadlocks.md @@ -19,7 +19,7 @@ LiteLLM writes `UPDATE` and `UPSERT` queries to the DB. When using 10+ instances ### Stage 1. Each instance writes updates to redis -Each instance will accumlate the spend updates for a key, user, team, etc and write the updates to a redis queue. +Each instance will accumulate the spend updates for a key, user, team, etc and write the updates to a redis queue.

diff --git a/docs/my-website/docs/proxy/deploy.md b/docs/my-website/docs/proxy/deploy.md index 011778f584..d57686dc78 100644 --- a/docs/my-website/docs/proxy/deploy.md +++ b/docs/my-website/docs/proxy/deploy.md @@ -22,7 +22,7 @@ echo 'LITELLM_MASTER_KEY="sk-1234"' > .env # Add the litellm salt key - you cannot change this after adding a model # It is used to encrypt / decrypt your LLM API Key credentials -# We recommned - https://1password.com/password-generator/ +# We recommend - https://1password.com/password-generator/ # password generator to get a random hash for litellm salt key echo 'LITELLM_SALT_KEY="sk-1234"' >> .env @@ -125,7 +125,7 @@ CMD ["--port", "4000", "--config", "config.yaml", "--detailed_debug"] ### Build from litellm `pip` package -Follow these instructons to build a docker container from the litellm pip package. If your company has a strict requirement around security / building images you can follow these steps. +Follow these instructions to build a docker container from the litellm pip package. If your company has a strict requirement around security / building images you can follow these steps. Dockerfile @@ -999,7 +999,7 @@ services: - "4000:4000" # Map the container port to the host, change the host port if necessary volumes: - ./litellm-config.yaml:/app/config.yaml # Mount the local configuration file - # You can change the port or number of workers as per your requirements or pass any new supported CLI augument. Make sure the port passed here matches with the container port defined above in `ports` value + # You can change the port or number of workers as per your requirements or pass any new supported CLI argument. Make sure the port passed here matches with the container port defined above in `ports` value command: [ "--config", "/app/config.yaml", "--port", "4000", "--num_workers", "8" ] # ...rest of your docker-compose config if any diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index fb0945d488..26dc9d4950 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -691,7 +691,7 @@ curl --request POST \ -**Successfull Request** +**Successful Request** ```shell curl --location 'http://0.0.0.0:4000/key/generate' \ @@ -729,7 +729,7 @@ curl --location 'http://0.0.0.0:4000/key/generate' \ -**Successfull Request** +**Successful Request** ```shell curl http://localhost:4000/chat/completions \ diff --git a/docs/my-website/docs/proxy/guardrails/quick_start.md b/docs/my-website/docs/proxy/guardrails/quick_start.md index aeac507e0a..55cfa98d48 100644 --- a/docs/my-website/docs/proxy/guardrails/quick_start.md +++ b/docs/my-website/docs/proxy/guardrails/quick_start.md @@ -164,7 +164,7 @@ curl -i http://localhost:4000/v1/chat/completions \ **Expected response** -Your response headers will incude `x-litellm-applied-guardrails` with the guardrail applied +Your response headers will include `x-litellm-applied-guardrails` with the guardrail applied ``` x-litellm-applied-guardrails: aporia-pre-guard diff --git a/docs/my-website/docs/proxy/logging.md b/docs/my-website/docs/proxy/logging.md index c8731dd270..ad4cababc0 100644 --- a/docs/my-website/docs/proxy/logging.md +++ b/docs/my-website/docs/proxy/logging.md @@ -277,7 +277,7 @@ Found under `kwargs["standard_logging_object"]`. This is a standard payload, log ## Langfuse -We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successfull LLM calls to langfuse. Make sure to set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` in your environment +We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successful LLM calls to langfuse. Make sure to set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` in your environment **Step 1** Install langfuse @@ -534,15 +534,15 @@ print(response) Use this if you want to control which LiteLLM-specific fields are logged as tags by the LiteLLM proxy. By default LiteLLM Proxy logs no LiteLLM-specific fields -| LiteLLM specific field | Description | Example Value | -|------------------------|-------------------------------------------------------|------------------------------------------------| -| `cache_hit` | Indicates whether a cache hit occured (True) or not (False) | `true`, `false` | -| `cache_key` | The Cache key used for this request | `d2b758c****`| -| `proxy_base_url` | The base URL for the proxy server, the value of env var `PROXY_BASE_URL` on your server | `https://proxy.example.com`| -| `user_api_key_alias` | An alias for the LiteLLM Virtual Key.| `prod-app1` | -| `user_api_key_user_id` | The unique ID associated with a user's API key. | `user_123`, `user_456` | -| `user_api_key_user_email` | The email associated with a user's API key. | `user@example.com`, `admin@example.com` | -| `user_api_key_team_alias` | An alias for a team associated with an API key. | `team_alpha`, `dev_team` | +| LiteLLM specific field | Description | Example Value | +|---------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------| +| `cache_hit` | Indicates whether a cache hit occurred (True) or not (False) | `true`, `false` | +| `cache_key` | The Cache key used for this request | `d2b758c****` | +| `proxy_base_url` | The base URL for the proxy server, the value of env var `PROXY_BASE_URL` on your server | `https://proxy.example.com` | +| `user_api_key_alias` | An alias for the LiteLLM Virtual Key. | `prod-app1` | +| `user_api_key_user_id` | The unique ID associated with a user's API key. | `user_123`, `user_456` | +| `user_api_key_user_email` | The email associated with a user's API key. | `user@example.com`, `admin@example.com` | +| `user_api_key_team_alias` | An alias for a team associated with an API key. | `team_alpha`, `dev_team` | **Usage** @@ -1190,7 +1190,7 @@ We will use the `--config` to set - `litellm.success_callback = ["s3"]` -This will log all successfull LLM calls to s3 Bucket +This will log all successful LLM calls to s3 Bucket **Step 1** Set AWS Credentials in .env @@ -1279,7 +1279,7 @@ Log LLM Logs to [Azure Data Lake Storage](https://learn.microsoft.com/en-us/azur | Property | Details | |----------|---------| -| Description | Log LLM Input/Output to Azure Blob Storag (Bucket) | +| Description | Log LLM Input/Output to Azure Blob Storage (Bucket) | | Azure Docs on Data Lake Storage | [Azure Data Lake Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-introduction) | @@ -1360,7 +1360,7 @@ LiteLLM Supports logging to the following Datdog Integrations: -We will use the `--config` to set `litellm.callbacks = ["datadog"]` this will log all successfull LLM calls to DataDog +We will use the `--config` to set `litellm.callbacks = ["datadog"]` this will log all successful LLM calls to DataDog **Step 1**: Create a `config.yaml` file and set `litellm_settings`: `success_callback` @@ -1636,7 +1636,7 @@ class MyCustomHandler(CustomLogger): litellm_params = kwargs.get("litellm_params", {}) metadata = litellm_params.get("metadata", {}) # headers passed to LiteLLM proxy, can be found here - # Acess Exceptions & Traceback + # Access Exceptions & Traceback exception_event = kwargs.get("exception", None) traceback_event = kwargs.get("traceback_exception", None) @@ -2205,7 +2205,7 @@ We will use the `--config` to set - `litellm.success_callback = ["dynamodb"]` - `litellm.dynamodb_table_name = "your-table-name"` -This will log all successfull LLM calls to DynamoDB +This will log all successful LLM calls to DynamoDB **Step 1** Set AWS Credentials in .env @@ -2370,7 +2370,7 @@ litellm --test [Athina](https://athina.ai/) allows you to log LLM Input/Output for monitoring, analytics, and observability. -We will use the `--config` to set `litellm.success_callback = ["athina"]` this will log all successfull LLM calls to athina +We will use the `--config` to set `litellm.success_callback = ["athina"]` this will log all successful LLM calls to athina **Step 1** Set Athina API key diff --git a/docs/my-website/docs/proxy/model_discovery.md b/docs/my-website/docs/proxy/model_discovery.md new file mode 100644 index 0000000000..5790dfc520 --- /dev/null +++ b/docs/my-website/docs/proxy/model_discovery.md @@ -0,0 +1,108 @@ +# Model Discovery + +Use this to give users an accurate list of models available behind provider endpoint, when calling `/v1/models` for wildcard models. + +## Supported Models + +- Fireworks AI +- OpenAI +- Gemini +- LiteLLM Proxy +- Topaz +- Anthropic +- XAI +- VLLM +- Vertex AI + +### Usage + +**1. Setup config.yaml** + +```yaml +model_list: + - model_name: xai/* + litellm_params: + model: xai/* + api_key: os.environ/XAI_API_KEY + +litellm_settings: + check_provider_endpoint: true # 👈 Enable checking provider endpoint for wildcard models +``` + +**2. Start proxy** + +```bash +litellm --config /path/to/config.yaml + +# RUNNING on http://0.0.0.0:4000 +``` + +**3. Call `/v1/models`** + +```bash +curl -X GET "http://localhost:4000/v1/models" -H "Authorization: Bearer $LITELLM_KEY" +``` + +Expected response + +```json +{ + "data": [ + { + "id": "xai/grok-2-1212", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-2-vision-1212", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-3-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-3-fast-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-3-mini-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-3-mini-fast-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-vision-beta", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + }, + { + "id": "xai/grok-2-image-1212", + "object": "model", + "created": 1677610602, + "owned_by": "openai" + } + ], + "object": "list" +} +``` \ No newline at end of file diff --git a/docs/my-website/docs/proxy/prod.md b/docs/my-website/docs/proxy/prod.md index 2d09502d52..e1b8336401 100644 --- a/docs/my-website/docs/proxy/prod.md +++ b/docs/my-website/docs/proxy/prod.md @@ -61,7 +61,7 @@ CMD ["--port", "4000", "--config", "./proxy_server_config.yaml"] ## 3. Use Redis 'port','host', 'password'. NOT 'redis_url' -If you decide to use Redis, DO NOT use 'redis_url'. We recommend usig redis port, host, and password params. +If you decide to use Redis, DO NOT use 'redis_url'. We recommend using redis port, host, and password params. `redis_url`is 80 RPS slower @@ -169,7 +169,7 @@ If you plan on using the DB, set a salt key for encrypting/decrypting variables Do not change this after adding a model. It is used to encrypt / decrypt your LLM API Key credentials -We recommned - https://1password.com/password-generator/ password generator to get a random hash for litellm salt key. +We recommend - https://1password.com/password-generator/ password generator to get a random hash for litellm salt key. ```bash export LITELLM_SALT_KEY="sk-1234" diff --git a/docs/my-website/docs/proxy/temporary_budget_increase.md b/docs/my-website/docs/proxy/temporary_budget_increase.md index 917ff0d6b5..de985eb9bd 100644 --- a/docs/my-website/docs/proxy/temporary_budget_increase.md +++ b/docs/my-website/docs/proxy/temporary_budget_increase.md @@ -3,7 +3,7 @@ Set temporary budget increase for a LiteLLM Virtual Key. Use this if you get asked to increase the budget for a key temporarily. -| Heirarchy | Supported | +| Hierarchy | Supported | |-----------|-----------| | LiteLLM Virtual Key | ✅ | | User | ❌ | diff --git a/docs/my-website/docs/proxy/ui_credentials.md b/docs/my-website/docs/proxy/ui_credentials.md index ba9d1c4c66..40db536859 100644 --- a/docs/my-website/docs/proxy/ui_credentials.md +++ b/docs/my-website/docs/proxy/ui_credentials.md @@ -4,7 +4,7 @@ import TabItem from '@theme/TabItem'; # Adding LLM Credentials -You can add LLM provider credentials on the UI. Once you add credentials you can re-use them when adding new models +You can add LLM provider credentials on the UI. Once you add credentials you can reuse them when adding new models ## Add a credential + model diff --git a/docs/my-website/docs/proxy/virtual_keys.md b/docs/my-website/docs/proxy/virtual_keys.md index 04be4ade48..26ec69b30d 100644 --- a/docs/my-website/docs/proxy/virtual_keys.md +++ b/docs/my-website/docs/proxy/virtual_keys.md @@ -23,7 +23,7 @@ Requirements: - ** Set on config.yaml** set your master key under `general_settings:master_key`, example below - ** Set env variable** set `LITELLM_MASTER_KEY` -(the proxy Dockerfile checks if the `DATABASE_URL` is set and then intializes the DB connection) +(the proxy Dockerfile checks if the `DATABASE_URL` is set and then initializes the DB connection) ```shell export DATABASE_URL=postgresql://:@:/ @@ -333,7 +333,7 @@ curl http://localhost:4000/v1/chat/completions \ **Expected Response** -Expect to see a successfull response from the litellm proxy since the key passed in `X-Litellm-Key` is valid +Expect to see a successful response from the litellm proxy since the key passed in `X-Litellm-Key` is valid ```shell {"id":"chatcmpl-f9b2b79a7c30477ab93cd0e717d1773e","choices":[{"finish_reason":"stop","index":0,"message":{"content":"\n\nHello there, how may I assist you today?","role":"assistant","tool_calls":null,"function_call":null}}],"created":1677652288,"model":"gpt-3.5-turbo-0125","object":"chat.completion","system_fingerprint":"fp_44709d6fcb","usage":{"completion_tokens":12,"prompt_tokens":9,"total_tokens":21} ``` diff --git a/docs/my-website/docs/reasoning_content.md b/docs/my-website/docs/reasoning_content.md index 86ef58bd68..12a0f17ba0 100644 --- a/docs/my-website/docs/reasoning_content.md +++ b/docs/my-website/docs/reasoning_content.md @@ -16,6 +16,8 @@ Supported Providers: - Vertex AI (Anthropic) (`vertexai/`) - OpenRouter (`openrouter/`) - XAI (`xai/`) +- Google AI Studio (`google/`) +- Vertex AI (`vertex_ai/`) LiteLLM will standardize the `reasoning_content` in the response and `thinking_blocks` in the assistant message. @@ -23,7 +25,7 @@ LiteLLM will standardize the `reasoning_content` in the response and `thinking_b "message": { ... "reasoning_content": "The capital of France is Paris.", - "thinking_blocks": [ + "thinking_blocks": [ # only returned for Anthropic models { "type": "thinking", "thinking": "The capital of France is Paris.", diff --git a/docs/my-website/docs/response_api.md b/docs/my-website/docs/response_api.md index 1ab9865504..532f20bc05 100644 --- a/docs/my-website/docs/response_api.md +++ b/docs/my-website/docs/response_api.md @@ -14,22 +14,22 @@ LiteLLM provides a BETA endpoint in the spec of [OpenAI's `/responses` API](http | Fallbacks | ✅ | Works between supported models | | Loadbalancing | ✅ | Works between supported models | | Supported LiteLLM Versions | 1.63.8+ | | -| Supported LLM providers | `openai` | | +| Supported LLM providers | **All LiteLLM supported providers** | `openai`, `anthropic`, `bedrock`, `vertex_ai`, `gemini`, `azure`, `azure_ai` etc. | ## Usage -## Create a model response +### LiteLLM Python SDK - + #### Non-streaming -```python +```python showLineNumbers title="OpenAI Non-streaming Response" import litellm # Non-streaming response response = litellm.responses( - model="o1-pro", + model="openai/o1-pro", input="Tell me a three sentence bedtime story about a unicorn.", max_output_tokens=100 ) @@ -38,12 +38,12 @@ print(response) ``` #### Streaming -```python +```python showLineNumbers title="OpenAI Streaming Response" import litellm # Streaming response response = litellm.responses( - model="o1-pro", + model="openai/o1-pro", input="Tell me a three sentence bedtime story about a unicorn.", stream=True ) @@ -53,58 +53,169 @@ for event in response: ``` - -First, add this to your litellm proxy config.yaml: -```yaml -model_list: - - model_name: o1-pro - litellm_params: - model: openai/o1-pro - api_key: os.environ/OPENAI_API_KEY -``` - -Start your LiteLLM proxy: -```bash -litellm --config /path/to/config.yaml - -# RUNNING on http://0.0.0.0:4000 -``` - -Then use the OpenAI SDK pointed to your proxy: + #### Non-streaming -```python -from openai import OpenAI +```python showLineNumbers title="Anthropic Non-streaming Response" +import litellm +import os -# Initialize client with your proxy URL -client = OpenAI( - base_url="http://localhost:4000", # Your proxy URL - api_key="your-api-key" # Your proxy API key -) +# Set API key +os.environ["ANTHROPIC_API_KEY"] = "your-anthropic-api-key" # Non-streaming response -response = client.responses.create( - model="o1-pro", - input="Tell me a three sentence bedtime story about a unicorn." +response = litellm.responses( + model="anthropic/claude-3-5-sonnet-20240620", + input="Tell me a three sentence bedtime story about a unicorn.", + max_output_tokens=100 ) print(response) ``` #### Streaming -```python -from openai import OpenAI +```python showLineNumbers title="Anthropic Streaming Response" +import litellm +import os -# Initialize client with your proxy URL -client = OpenAI( - base_url="http://localhost:4000", # Your proxy URL - api_key="your-api-key" # Your proxy API key -) +# Set API key +os.environ["ANTHROPIC_API_KEY"] = "your-anthropic-api-key" # Streaming response -response = client.responses.create( - model="o1-pro", +response = litellm.responses( + model="anthropic/claude-3-5-sonnet-20240620", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +#### Non-streaming +```python showLineNumbers title="Vertex AI Non-streaming Response" +import litellm +import os + +# Set credentials - Vertex AI uses application default credentials +# Run 'gcloud auth application-default login' to authenticate +os.environ["VERTEXAI_PROJECT"] = "your-gcp-project-id" +os.environ["VERTEXAI_LOCATION"] = "us-central1" + +# Non-streaming response +response = litellm.responses( + model="vertex_ai/gemini-1.5-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + max_output_tokens=100 +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Vertex AI Streaming Response" +import litellm +import os + +# Set credentials - Vertex AI uses application default credentials +# Run 'gcloud auth application-default login' to authenticate +os.environ["VERTEXAI_PROJECT"] = "your-gcp-project-id" +os.environ["VERTEXAI_LOCATION"] = "us-central1" + +# Streaming response +response = litellm.responses( + model="vertex_ai/gemini-1.5-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +#### Non-streaming +```python showLineNumbers title="AWS Bedrock Non-streaming Response" +import litellm +import os + +# Set AWS credentials +os.environ["AWS_ACCESS_KEY_ID"] = "your-access-key-id" +os.environ["AWS_SECRET_ACCESS_KEY"] = "your-secret-access-key" +os.environ["AWS_REGION_NAME"] = "us-west-2" # or your AWS region + +# Non-streaming response +response = litellm.responses( + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + input="Tell me a three sentence bedtime story about a unicorn.", + max_output_tokens=100 +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="AWS Bedrock Streaming Response" +import litellm +import os + +# Set AWS credentials +os.environ["AWS_ACCESS_KEY_ID"] = "your-access-key-id" +os.environ["AWS_SECRET_ACCESS_KEY"] = "your-secret-access-key" +os.environ["AWS_REGION_NAME"] = "us-west-2" # or your AWS region + +# Streaming response +response = litellm.responses( + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +#### Non-streaming +```python showLineNumbers title="Google AI Studio Non-streaming Response" +import litellm +import os + +# Set API key for Google AI Studio +os.environ["GEMINI_API_KEY"] = "your-gemini-api-key" + +# Non-streaming response +response = litellm.responses( + model="gemini/gemini-1.5-flash", + input="Tell me a three sentence bedtime story about a unicorn.", + max_output_tokens=100 +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Google AI Studio Streaming Response" +import litellm +import os + +# Set API key for Google AI Studio +os.environ["GEMINI_API_KEY"] = "your-gemini-api-key" + +# Streaming response +response = litellm.responses( + model="gemini/gemini-1.5-flash", input="Tell me a three sentence bedtime story about a unicorn.", stream=True ) @@ -115,3 +226,408 @@ for event in response: + +### LiteLLM Proxy with OpenAI SDK + +First, set up and start your LiteLLM proxy server. + +```bash title="Start LiteLLM Proxy Server" +litellm --config /path/to/config.yaml + +# RUNNING on http://0.0.0.0:4000 +``` + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="OpenAI Proxy Configuration" +model_list: + - model_name: openai/o1-pro + litellm_params: + model: openai/o1-pro + api_key: os.environ/OPENAI_API_KEY +``` + +#### Non-streaming +```python showLineNumbers title="OpenAI Proxy Non-streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="openai/o1-pro", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="OpenAI Proxy Streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="openai/o1-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="Anthropic Proxy Configuration" +model_list: + - model_name: anthropic/claude-3-5-sonnet-20240620 + litellm_params: + model: anthropic/claude-3-5-sonnet-20240620 + api_key: os.environ/ANTHROPIC_API_KEY +``` + +#### Non-streaming +```python showLineNumbers title="Anthropic Proxy Non-streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="anthropic/claude-3-5-sonnet-20240620", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Anthropic Proxy Streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="anthropic/claude-3-5-sonnet-20240620", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="Vertex AI Proxy Configuration" +model_list: + - model_name: vertex_ai/gemini-1.5-pro + litellm_params: + model: vertex_ai/gemini-1.5-pro + vertex_project: your-gcp-project-id + vertex_location: us-central1 +``` + +#### Non-streaming +```python showLineNumbers title="Vertex AI Proxy Non-streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="vertex_ai/gemini-1.5-pro", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Vertex AI Proxy Streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="vertex_ai/gemini-1.5-pro", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="AWS Bedrock Proxy Configuration" +model_list: + - model_name: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + litellm_params: + model: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID + aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY + aws_region_name: us-west-2 +``` + +#### Non-streaming +```python showLineNumbers title="AWS Bedrock Proxy Non-streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="AWS Bedrock Proxy Streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + + +First, add this to your litellm proxy config.yaml: +```yaml showLineNumbers title="Google AI Studio Proxy Configuration" +model_list: + - model_name: gemini/gemini-1.5-flash + litellm_params: + model: gemini/gemini-1.5-flash + api_key: os.environ/GEMINI_API_KEY +``` + +#### Non-streaming +```python showLineNumbers title="Google AI Studio Proxy Non-streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Non-streaming response +response = client.responses.create( + model="gemini/gemini-1.5-flash", + input="Tell me a three sentence bedtime story about a unicorn." +) + +print(response) +``` + +#### Streaming +```python showLineNumbers title="Google AI Studio Proxy Streaming Response" +from openai import OpenAI + +# Initialize client with your proxy URL +client = OpenAI( + base_url="http://localhost:4000", # Your proxy URL + api_key="your-api-key" # Your proxy API key +) + +# Streaming response +response = client.responses.create( + model="gemini/gemini-1.5-flash", + input="Tell me a three sentence bedtime story about a unicorn.", + stream=True +) + +for event in response: + print(event) +``` + + + + +## Supported Responses API Parameters + +| Provider | Supported Parameters | +|----------|---------------------| +| `openai` | [All Responses API parameters are supported](https://github.com/BerriAI/litellm/blob/7c3df984da8e4dff9201e4c5353fdc7a2b441831/litellm/llms/openai/responses/transformation.py#L23) | +| `azure` | [All Responses API parameters are supported](https://github.com/BerriAI/litellm/blob/7c3df984da8e4dff9201e4c5353fdc7a2b441831/litellm/llms/openai/responses/transformation.py#L23) | +| `anthropic` | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | +| `bedrock` | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | +| `gemini` | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | +| `vertex_ai` | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | +| `azure_ai` | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | +| All other llm api providers | [See supported parameters here](https://github.com/BerriAI/litellm/blob/f39d9178868662746f159d5ef642c7f34f9bfe5f/litellm/responses/litellm_completion_transformation/transformation.py#L57) | + +## Load Balancing with Routing Affinity + +When using the Responses API with multiple deployments of the same model (e.g., multiple Azure OpenAI endpoints), LiteLLM provides routing affinity for conversations. This ensures that follow-up requests using a `previous_response_id` are routed to the same deployment that generated the original response. + + +#### Example Usage + + + + +```python showLineNumbers title="Python SDK with Routing Affinity" +import litellm + +# Set up router with multiple deployments of the same model +router = litellm.Router( + model_list=[ + { + "model_name": "azure-gpt4-turbo", + "litellm_params": { + "model": "azure/gpt-4-turbo", + "api_key": "your-api-key-1", + "api_version": "2024-06-01", + "api_base": "https://endpoint1.openai.azure.com", + }, + }, + { + "model_name": "azure-gpt4-turbo", + "litellm_params": { + "model": "azure/gpt-4-turbo", + "api_key": "your-api-key-2", + "api_version": "2024-06-01", + "api_base": "https://endpoint2.openai.azure.com", + }, + }, + ], + optional_pre_call_checks=["responses_api_deployment_check"], +) + +# Initial request +response = await router.aresponses( + model="azure-gpt4-turbo", + input="Hello, who are you?", + truncation="auto", +) + +# Store the response ID +response_id = response.id + +# Follow-up request - will be automatically routed to the same deployment +follow_up = await router.aresponses( + model="azure-gpt4-turbo", + input="Tell me more about yourself", + truncation="auto", + previous_response_id=response_id # This ensures routing to the same deployment +) +``` + + + + +#### 1. Setup routing affinity on proxy config.yaml + +To enable routing affinity for Responses API in your LiteLLM proxy, set `optional_pre_call_checks: ["responses_api_deployment_check"]` in your proxy config.yaml. + +```yaml showLineNumbers title="config.yaml with Responses API Routing Affinity" +model_list: + - model_name: azure-gpt4-turbo + litellm_params: + model: azure/gpt-4-turbo + api_key: your-api-key-1 + api_version: 2024-06-01 + api_base: https://endpoint1.openai.azure.com + - model_name: azure-gpt4-turbo + litellm_params: + model: azure/gpt-4-turbo + api_key: your-api-key-2 + api_version: 2024-06-01 + api_base: https://endpoint2.openai.azure.com + +router_settings: + optional_pre_call_checks: ["responses_api_deployment_check"] +``` + +#### 2. Use the OpenAI Python SDK to make requests to LiteLLM Proxy + +```python showLineNumbers title="OpenAI Client with Proxy Server" +from openai import OpenAI + +client = OpenAI( + base_url="http://localhost:4000", + api_key="your-api-key" +) + +# Initial request +response = client.responses.create( + model="azure-gpt4-turbo", + input="Hello, who are you?" +) + +response_id = response.id + +# Follow-up request - will be automatically routed to the same deployment +follow_up = client.responses.create( + model="azure-gpt4-turbo", + input="Tell me more about yourself", + previous_response_id=response_id # This ensures routing to the same deployment +) +``` + + + diff --git a/docs/my-website/docs/simple_proxy_old_doc.md b/docs/my-website/docs/simple_proxy_old_doc.md index 64491b1ea8..730fd0aab4 100644 --- a/docs/my-website/docs/simple_proxy_old_doc.md +++ b/docs/my-website/docs/simple_proxy_old_doc.md @@ -994,16 +994,16 @@ litellm --health ## Logging Proxy Input/Output - OpenTelemetry -### Step 1 Start OpenTelemetry Collecter Docker Container +### Step 1 Start OpenTelemetry Collector Docker Container This container sends logs to your selected destination -#### Install OpenTelemetry Collecter Docker Image +#### Install OpenTelemetry Collector Docker Image ```shell docker pull otel/opentelemetry-collector:0.90.0 docker run -p 127.0.0.1:4317:4317 -p 127.0.0.1:55679:55679 otel/opentelemetry-collector:0.90.0 ``` -#### Set Destination paths on OpenTelemetry Collecter +#### Set Destination paths on OpenTelemetry Collector Here's the OpenTelemetry yaml config to use with Elastic Search ```yaml @@ -1077,7 +1077,7 @@ general_settings: LiteLLM will read the `OTEL_ENDPOINT` environment variable to send data to your OTEL collector ```python -os.environ['OTEL_ENDPOINT'] # defauls to 127.0.0.1:4317 if not provided +os.environ['OTEL_ENDPOINT'] # defaults to 127.0.0.1:4317 if not provided ``` #### Start LiteLLM Proxy @@ -1101,8 +1101,8 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ ``` -#### Test & View Logs on OpenTelemetry Collecter -On successfull logging you should be able to see this log on your `OpenTelemetry Collecter` Docker Container +#### Test & View Logs on OpenTelemetry Collector +On successful logging you should be able to see this log on your `OpenTelemetry Collector` Docker Container ```shell Events: SpanEvent #0 @@ -1149,7 +1149,7 @@ Here's the log view on Elastic Search. You can see the request `input`, `output` ## Logging Proxy Input/Output - Langfuse -We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successfull LLM calls to langfuse +We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successful LLM calls to langfuse **Step 1** Install langfuse diff --git a/docs/my-website/docs/tutorials/compare_llms.md b/docs/my-website/docs/tutorials/compare_llms.md index a7eda2c85f..d7fdf8d7d9 100644 --- a/docs/my-website/docs/tutorials/compare_llms.md +++ b/docs/my-website/docs/tutorials/compare_llms.md @@ -117,7 +117,7 @@ response = completion("command-nightly", messages) """ -# qustions/logs you want to run the LLM on +# questions/logs you want to run the LLM on questions = [ "what is litellm?", "why should I use LiteLLM", diff --git a/docs/my-website/docs/tutorials/gradio_integration.md b/docs/my-website/docs/tutorials/gradio_integration.md index 4854fc2ac9..021815d937 100644 --- a/docs/my-website/docs/tutorials/gradio_integration.md +++ b/docs/my-website/docs/tutorials/gradio_integration.md @@ -30,7 +30,7 @@ def inference(message, history): yield partial_message except Exception as e: print("Exception encountered:", str(e)) - yield f"An Error occured please 'Clear' the error and try your question again" + yield f"An Error occurred please 'Clear' the error and try your question again" ``` ### Define Chat Interface diff --git a/docs/my-website/docs/tutorials/openai_codex.md b/docs/my-website/docs/tutorials/openai_codex.md new file mode 100644 index 0000000000..bb5af956b0 --- /dev/null +++ b/docs/my-website/docs/tutorials/openai_codex.md @@ -0,0 +1,146 @@ +import Image from '@theme/IdealImage'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Using LiteLLM with OpenAI Codex + +This guide walks you through connecting OpenAI Codex to LiteLLM. Using LiteLLM with Codex allows teams to: +- Access 100+ LLMs through the Codex interface +- Use powerful models like Gemini through a familiar interface +- Track spend and usage with LiteLLM's built-in analytics +- Control model access with virtual keys + + + +## Quickstart + +:::info + +Requires LiteLLM v1.66.3.dev5 and higher + +::: + + +Make sure to set up LiteLLM with the [LiteLLM Getting Started Guide](../proxy/docker_quick_start.md). + +## 1. Install OpenAI Codex + +Install the OpenAI Codex CLI tool globally using npm: + + + + +```bash showLineNumbers +npm i -g @openai/codex +``` + + + + +```bash showLineNumbers +yarn global add @openai/codex +``` + + + + +## 2. Start LiteLLM Proxy + + + + +```bash showLineNumbers +docker run \ + -v $(pwd)/litellm_config.yaml:/app/config.yaml \ + -p 4000:4000 \ + ghcr.io/berriai/litellm:main-latest \ + --config /app/config.yaml +``` + + + + +```bash showLineNumbers +litellm --config /path/to/config.yaml +``` + + + + +LiteLLM should now be running on [http://localhost:4000](http://localhost:4000) + +## 3. Configure LiteLLM for Model Routing + +Ensure your LiteLLM Proxy is properly configured to route to your desired models. Create a `litellm_config.yaml` file with the following content: + +```yaml showLineNumbers +model_list: + - model_name: o3-mini + litellm_params: + model: openai/o3-mini + api_key: os.environ/OPENAI_API_KEY + - model_name: claude-3-7-sonnet-latest + litellm_params: + model: anthropic/claude-3-7-sonnet-latest + api_key: os.environ/ANTHROPIC_API_KEY + - model_name: gemini-2.0-flash + litellm_params: + model: gemini/gemini-2.0-flash + api_key: os.environ/GEMINI_API_KEY + +litellm_settings: + drop_params: true +``` + +This configuration enables routing to specific OpenAI, Anthropic, and Gemini models with explicit names. + +## 4. Configure Codex to Use LiteLLM Proxy + +Set the required environment variables to point Codex to your LiteLLM Proxy: + +```bash +# Point to your LiteLLM Proxy server +export OPENAI_BASE_URL=http://0.0.0.0:4000 + +# Use your LiteLLM API key (if you've set up authentication) +export OPENAI_API_KEY="sk-1234" +``` + +## 5. Run Codex with Gemini + +With everything configured, you can now run Codex with Gemini: + +```bash showLineNumbers +codex --model gemini-2.0-flash --full-auto +``` + + + +The `--full-auto` flag allows Codex to automatically generate code without additional prompting. + +## 6. Advanced Options + +### Using Different Models + +You can use any model configured in your LiteLLM proxy: + +```bash +# Use Claude models +codex --model claude-3-7-sonnet-latest + +# Use Google AI Studio Gemini models +codex --model gemini/gemini-2.0-flash +``` + +## Troubleshooting + +- If you encounter connection issues, ensure your LiteLLM Proxy is running and accessible at the specified URL +- Verify your LiteLLM API key is valid if you're using authentication +- Check that your model routing configuration is correct +- For model-specific errors, ensure the model is properly configured in your LiteLLM setup + +## Additional Resources + +- [LiteLLM Docker Quick Start Guide](../proxy/docker_quick_start.md) +- [OpenAI Codex GitHub Repository](https://github.com/openai/codex) +- [LiteLLM Virtual Keys and Authentication](../proxy/virtual_keys.md) diff --git a/docs/my-website/docs/tutorials/scim_litellm.md b/docs/my-website/docs/tutorials/scim_litellm.md new file mode 100644 index 0000000000..c744abe4b4 --- /dev/null +++ b/docs/my-website/docs/tutorials/scim_litellm.md @@ -0,0 +1,74 @@ + +import Image from '@theme/IdealImage'; + +# SCIM with LiteLLM + +Enables identity providers (Okta, Azure AD, OneLogin, etc.) to automate user and team (group) provisioning, updates, and deprovisioning on LiteLLM. + + +This tutorial will walk you through the steps to connect your IDP to LiteLLM SCIM Endpoints. + +### Supported SSO Providers for SCIM +Below is a list of supported SSO providers for connecting to LiteLLM SCIM Endpoints. +- Microsoft Entra ID (Azure AD) +- Okta +- Google Workspace +- OneLogin +- Keycloak +- Auth0 + + +## 1. Get your SCIM Tenant URL and Bearer Token + +On LiteLLM, navigate to the Settings > Admin Settings > SCIM. On this page you will create a SCIM Token, this allows your IDP to authenticate to litellm `/scim` endpoints. + + + +## 2. Connect your IDP to LiteLLM SCIM Endpoints + +On your IDP provider, navigate to your SSO application and select `Provisioning` > `New provisioning configuration`. + +On this page, paste in your litellm scim tenant url and bearer token. + +Once this is pasted in, click on `Test Connection` to ensure your IDP can authenticate to the LiteLLM SCIM endpoints. + + + + +## 3. Test SCIM Connection + +### 3.1 Assign the group to your LiteLLM Enterprise App + +On your IDP Portal, navigate to `Enterprise Applications` > Select your litellm app + + + +
+
+ +Once you've selected your litellm app, click on `Users and Groups` > `Add user/group` + + + +
+ +Now select the group you created in step 1.1. And add it to the LiteLLM Enterprise App. At this point we have added `Production LLM Evals Group` to the LiteLLM Enterprise App. The next step is having LiteLLM automatically create the `Production LLM Evals Group` on the LiteLLM DB when a new user signs in. + + + + +### 3.2 Sign in to LiteLLM UI via SSO + +Sign into the LiteLLM UI via SSO. You should be redirected to the Entra ID SSO page. This SSO sign in flow will trigger LiteLLM to fetch the latest Groups and Members from Azure Entra ID. + + + +### 3.3 Check the new team on LiteLLM UI + +On the LiteLLM UI, Navigate to `Teams`, You should see the new team `Production LLM Evals Group` auto-created on LiteLLM. + + + + + + diff --git a/docs/my-website/img/litellm_codex.gif b/docs/my-website/img/litellm_codex.gif new file mode 100644 index 0000000000..04332b5053 Binary files /dev/null and b/docs/my-website/img/litellm_codex.gif differ diff --git a/docs/my-website/img/release_notes/new_tag_usage.png b/docs/my-website/img/release_notes/new_tag_usage.png new file mode 100644 index 0000000000..4188cbc245 Binary files /dev/null and b/docs/my-website/img/release_notes/new_tag_usage.png differ diff --git a/docs/my-website/img/release_notes/new_team_usage.png b/docs/my-website/img/release_notes/new_team_usage.png new file mode 100644 index 0000000000..5fea2506d9 Binary files /dev/null and b/docs/my-website/img/release_notes/new_team_usage.png differ diff --git a/docs/my-website/img/release_notes/new_team_usage_highlight.jpg b/docs/my-website/img/release_notes/new_team_usage_highlight.jpg new file mode 100644 index 0000000000..05dbf4b918 Binary files /dev/null and b/docs/my-website/img/release_notes/new_team_usage_highlight.jpg differ diff --git a/docs/my-website/img/release_notes/unified_responses_api_rn.png b/docs/my-website/img/release_notes/unified_responses_api_rn.png new file mode 100644 index 0000000000..60ede0e211 Binary files /dev/null and b/docs/my-website/img/release_notes/unified_responses_api_rn.png differ diff --git a/docs/my-website/img/scim_0.png b/docs/my-website/img/scim_0.png new file mode 100644 index 0000000000..265271b78c Binary files /dev/null and b/docs/my-website/img/scim_0.png differ diff --git a/docs/my-website/img/scim_1.png b/docs/my-website/img/scim_1.png new file mode 100644 index 0000000000..c6d64b5d11 Binary files /dev/null and b/docs/my-website/img/scim_1.png differ diff --git a/docs/my-website/img/scim_2.png b/docs/my-website/img/scim_2.png new file mode 100644 index 0000000000..c96cf9f0b5 Binary files /dev/null and b/docs/my-website/img/scim_2.png differ diff --git a/docs/my-website/img/scim_3.png b/docs/my-website/img/scim_3.png new file mode 100644 index 0000000000..5ecd3906bd Binary files /dev/null and b/docs/my-website/img/scim_3.png differ diff --git a/docs/my-website/img/scim_4.png b/docs/my-website/img/scim_4.png new file mode 100644 index 0000000000..b4b484418c Binary files /dev/null and b/docs/my-website/img/scim_4.png differ diff --git a/docs/my-website/img/scim_integration.png b/docs/my-website/img/scim_integration.png new file mode 100644 index 0000000000..2cfeb872bf Binary files /dev/null and b/docs/my-website/img/scim_integration.png differ diff --git a/docs/my-website/release_notes/v1.67.0-stable/index.md b/docs/my-website/release_notes/v1.67.0-stable/index.md new file mode 100644 index 0000000000..cb7938fce5 --- /dev/null +++ b/docs/my-website/release_notes/v1.67.0-stable/index.md @@ -0,0 +1,153 @@ +--- +title: v1.67.0-stable - SCIM Integration +slug: v1.67.0-stable +date: 2025-04-19T10:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://media.licdn.com/dms/image/v2/D4D03AQGrlsJ3aqpHmQ/profile-displayphoto-shrink_400_400/B4DZSAzgP7HYAg-/0/1737327772964?e=1749686400&v=beta&t=Hkl3U8Ps0VtvNxX0BNNq24b4dtX5wQaPFp6oiKCIHD8 + - name: Ishaan Jaffer + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg + +tags: ["sso", "unified_file_id", "cost_tracking", "security"] +hide_table_of_contents: false +--- +import Image from '@theme/IdealImage'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Key Highlights + +- **SCIM Integration**: Enables identity providers (Okta, Azure AD, OneLogin, etc.) to automate user and team (group) provisioning, updates, and deprovisioning +- **Team and Tag based usage tracking**: You can now see usage and spend by team and tag at 1M+ spend logs. +- **Unified Responses API**: Support for calling Anthropic, Gemini, Groq, etc. via OpenAI's new Responses API. + +Let's dive in. + +## SCIM Integration + + + +This release adds SCIM support to LiteLLM. This allows your SSO provider (Okta, Azure AD, etc) to automatically create/delete users, teams, and memberships on LiteLLM. This means that when you remove a team on your SSO provider, your SSO provider will automatically delete the corresponding team on LiteLLM. + +[Read more](../../docs/tutorials/scim_litellm) +## Team and Tag based usage tracking + + + + +This release improves team and tag based usage tracking at 1m+ spend logs, making it easy to monitor your LLM API Spend in production. This covers: + +- View **daily spend** by teams + tags +- View **usage / spend by key**, within teams +- View **spend by multiple tags** +- Allow **internal users** to view spend of teams they're a member of + +[Read more](#management-endpoints--ui) + +## Unified Responses API + +This release allows you to call Azure OpenAI, Anthropic, AWS Bedrock, and Google Vertex AI models via the POST /v1/responses endpoint on LiteLLM. This means you can now use popular tools like [OpenAI Codex](https://docs.litellm.ai/docs/tutorials/openai_codex) with your own models. + + + + +[Read more](https://docs.litellm.ai/docs/response_api) + + +## New Models / Updated Models + +- **OpenAI** + 1. gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, o3, o3-mini, o4-mini pricing - [Get Started](../../docs/providers/openai#usage), [PR](https://github.com/BerriAI/litellm/pull/9990) + 2. o4 - correctly map o4 to openai o_series model +- **Azure AI** + 1. Phi-4 output cost per token fix - [PR](https://github.com/BerriAI/litellm/pull/9880) + 2. Responses API support [Get Started](../../docs/providers/azure#azure-responses-api),[PR](https://github.com/BerriAI/litellm/pull/10116) +- **Anthropic** + 1. redacted message thinking support - [Get Started](../../docs/providers/anthropic#usage---thinking--reasoning_content),[PR](https://github.com/BerriAI/litellm/pull/10129) +- **Cohere** + 1. `/v2/chat` Passthrough endpoint support w/ cost tracking - [Get Started](../../docs/pass_through/cohere), [PR](https://github.com/BerriAI/litellm/pull/9997) +- **Azure** + 1. Support azure tenant_id/client_id env vars - [Get Started](../../docs/providers/azure#entra-id---use-tenant_id-client_id-client_secret), [PR](https://github.com/BerriAI/litellm/pull/9993) + 2. Fix response_format check for 2025+ api versions - [PR](https://github.com/BerriAI/litellm/pull/9993) + 3. Add gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, o3, o3-mini, o4-mini pricing +- **VLLM** + 1. Files - Support 'file' message type for VLLM video url's - [Get Started](../../docs/providers/vllm#send-video-url-to-vllm), [PR](https://github.com/BerriAI/litellm/pull/10129) + 2. Passthrough - new `/vllm/` passthrough endpoint support [Get Started](../../docs/pass_through/vllm), [PR](https://github.com/BerriAI/litellm/pull/10002) +- **Mistral** + 1. new `/mistral` passthrough endpoint support [Get Started](../../docs/pass_through/mistral), [PR](https://github.com/BerriAI/litellm/pull/10002) +- **AWS** + 1. New mapped bedrock regions - [PR](https://github.com/BerriAI/litellm/pull/9430) +- **VertexAI / Google AI Studio** + 1. Gemini - Response format - Retain schema field ordering for google gemini and vertex by specifying propertyOrdering - [Get Started](../../docs/providers/vertex#json-schema), [PR](https://github.com/BerriAI/litellm/pull/9828) + 2. Gemini-2.5-flash - return reasoning content [Google AI Studio](../../docs/providers/gemini#usage---thinking--reasoning_content), [Vertex AI](../../docs/providers/vertex#thinking--reasoning_content) + 3. Gemini-2.5-flash - pricing + model information [PR](https://github.com/BerriAI/litellm/pull/10125) + 4. Passthrough - new `/vertex_ai/discovery` route - enables calling AgentBuilder API routes [Get Started](../../docs/pass_through/vertex_ai#supported-api-endpoints), [PR](https://github.com/BerriAI/litellm/pull/10084) +- **Fireworks AI** + 1. return tool calling responses in `tool_calls` field (fireworks incorrectly returns this as a json str in content) [PR](https://github.com/BerriAI/litellm/pull/10130) +- **Triton** + 1. Remove fixed remove bad_words / stop words from `/generate` call - [Get Started](../../docs/providers/triton-inference-server#triton-generate---chat-completion), [PR](https://github.com/BerriAI/litellm/pull/10163) +- **Other** + 1. Support for all litellm providers on Responses API (works with Codex) - [Get Started](../../docs/tutorials/openai_codex), [PR](https://github.com/BerriAI/litellm/pull/10132) + 2. Fix combining multiple tool calls in streaming response - [Get Started](../../docs/completion/stream#helper-function), [PR](https://github.com/BerriAI/litellm/pull/10040) + + +## Spend Tracking Improvements + +- **Cost Control** - inject cache control points in prompt for cost reduction [Get Started](../../docs/tutorials/prompt_caching), [PR](https://github.com/BerriAI/litellm/pull/10000) +- **Spend Tags** - spend tags in headers - support x-litellm-tags even if tag based routing not enabled [Get Started](../../docs/proxy/request_headers#litellm-headers), [PR](https://github.com/BerriAI/litellm/pull/10000) +- **Gemini-2.5-flash** - support cost calculation for reasoning tokens [PR](https://github.com/BerriAI/litellm/pull/10141) + +## Management Endpoints / UI +- **Users** + 1. Show created_at and updated_at on users page - [PR](https://github.com/BerriAI/litellm/pull/10033) +- **Virtual Keys** + 1. Filter by key alias - https://github.com/BerriAI/litellm/pull/10085 +- **Usage Tab** + + 1. Team based usage + + - New `LiteLLM_DailyTeamSpend` Table for aggregate team based usage logging - [PR](https://github.com/BerriAI/litellm/pull/10039) + + - New Team based usage dashboard + new `/team/daily/activity` API - [PR](https://github.com/BerriAI/litellm/pull/10081) + - Return team alias on /team/daily/activity API - [PR](https://github.com/BerriAI/litellm/pull/10157) + - allow internal user view spend for teams they belong to - [PR](https://github.com/BerriAI/litellm/pull/10157) + - allow viewing top keys by team - [PR](https://github.com/BerriAI/litellm/pull/10157) + + + + 2. Tag Based Usage + - New `LiteLLM_DailyTagSpend` Table for aggregate tag based usage logging - [PR](https://github.com/BerriAI/litellm/pull/10071) + - Restrict to only Proxy Admins - [PR](https://github.com/BerriAI/litellm/pull/10157) + - allow viewing top keys by tag + - Return tags passed in request (i.e. dynamic tags) on `/tag/list` API - [PR](https://github.com/BerriAI/litellm/pull/10157) + + 3. Track prompt caching metrics in daily user, team, tag tables - [PR](https://github.com/BerriAI/litellm/pull/10029) + 4. Show usage by key (on all up, team, and tag usage dashboards) - [PR](https://github.com/BerriAI/litellm/pull/10157) + 5. swap old usage with new usage tab +- **Models** + 1. Make columns resizable/hideable - [PR](https://github.com/BerriAI/litellm/pull/10119) +- **API Playground** + 1. Allow internal user to call api playground - [PR](https://github.com/BerriAI/litellm/pull/10157) +- **SCIM** + 1. Add LiteLLM SCIM Integration for Team and User management - [Get Started](../../docs/tutorials/scim_litellm), [PR](https://github.com/BerriAI/litellm/pull/10072) + + +## Logging / Guardrail Integrations +- **GCS** + 1. Fix gcs pub sub logging with env var GCS_PROJECT_ID - [Get Started](../../docs/observability/gcs_bucket_integration#usage), [PR](https://github.com/BerriAI/litellm/pull/10042) +- **AIM** + 1. Add litellm call id passing to Aim guardrails on pre and post-hooks calls - [Get Started](../../docs/proxy/guardrails/aim_security), [PR](https://github.com/BerriAI/litellm/pull/10021) +- **Azure blob storage** + 1. Ensure logging works in high throughput scenarios - [Get Started](../../docs/proxy/logging#azure-blob-storage), [PR](https://github.com/BerriAI/litellm/pull/9962) + +## General Proxy Improvements + +- **Support setting `litellm.modify_params` via env var** [PR](https://github.com/BerriAI/litellm/pull/9964) +- **Model Discovery** - Check provider’s `/models` endpoints when calling proxy’s `/v1/models` endpoint - [Get Started](../../docs/proxy/model_discovery), [PR](https://github.com/BerriAI/litellm/pull/9958) +- **`/utils/token_counter`** - fix retrieving custom tokenizer for db models - [Get Started](../../docs/proxy/configs#set-custom-tokenizer), [PR](https://github.com/BerriAI/litellm/pull/10047) +- **Prisma migrate** - handle existing columns in db table - [PR](https://github.com/BerriAI/litellm/pull/10138) + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index fdf2019cc2..60030a59bb 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -69,6 +69,7 @@ const sidebars = { "proxy/clientside_auth", "proxy/request_headers", "proxy/response_headers", + "proxy/model_discovery", ], }, { @@ -101,6 +102,7 @@ const sidebars = { "proxy/admin_ui_sso", "proxy/self_serve", "proxy/public_teams", + "tutorials/scim_litellm", "proxy/custom_sso", "proxy/ui_credentials", "proxy/ui_logs" @@ -330,6 +332,8 @@ const sidebars = { "pass_through/vertex_ai", "pass_through/google_ai_studio", "pass_through/cohere", + "pass_through/vllm", + "pass_through/mistral", "pass_through/openai_passthrough", "pass_through/anthropic_completion", "pass_through/bedrock", @@ -407,6 +411,7 @@ const sidebars = { type: "category", label: "Logging & Observability", items: [ + "observability/agentops_integration", "observability/langfuse_integration", "observability/lunary_integration", "observability/mlflow", @@ -443,6 +448,7 @@ const sidebars = { label: "Tutorials", items: [ "tutorials/openweb_ui", + "tutorials/openai_codex", "tutorials/msft_sso", "tutorials/prompt_caching", "tutorials/tag_management", diff --git a/litellm-proxy-extras/litellm_proxy_extras/utils.py b/litellm-proxy-extras/litellm_proxy_extras/utils.py index 2dab9421cb..e771e48e45 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/utils.py +++ b/litellm-proxy-extras/litellm_proxy_extras/utils.py @@ -1,6 +1,7 @@ import glob import os import random +import re import subprocess import time from pathlib import Path @@ -82,6 +83,26 @@ class ProxyExtrasDBManager: logger.info(f"Found {len(migration_paths)} migrations at {migrations_dir}") return [Path(p).parent.name for p in migration_paths] + @staticmethod + def _roll_back_migration(migration_name: str): + """Mark a specific migration as rolled back""" + subprocess.run( + ["prisma", "migrate", "resolve", "--rolled-back", migration_name], + timeout=60, + check=True, + capture_output=True, + ) + + @staticmethod + def _resolve_specific_migration(migration_name: str): + """Mark a specific migration as applied""" + subprocess.run( + ["prisma", "migrate", "resolve", "--applied", migration_name], + timeout=60, + check=True, + capture_output=True, + ) + @staticmethod def _resolve_all_migrations(migrations_dir: str): """Mark all existing migrations as applied""" @@ -141,7 +162,34 @@ class ProxyExtrasDBManager: return True except subprocess.CalledProcessError as e: logger.info(f"prisma db error: {e.stderr}, e: {e.stdout}") - if ( + if "P3009" in e.stderr: + # Extract the failed migration name from the error message + migration_match = re.search( + r"`(\d+_.*)` migration", e.stderr + ) + if migration_match: + failed_migration = migration_match.group(1) + logger.info( + f"Found failed migration: {failed_migration}, marking as rolled back" + ) + # Mark the failed migration as rolled back + subprocess.run( + [ + "prisma", + "migrate", + "resolve", + "--rolled-back", + failed_migration, + ], + timeout=60, + check=True, + capture_output=True, + text=True, + ) + logger.info( + f"✅ Migration {failed_migration} marked as rolled back... retrying" + ) + elif ( "P3005" in e.stderr and "database schema is not empty" in e.stderr ): @@ -155,6 +203,29 @@ class ProxyExtrasDBManager: ProxyExtrasDBManager._resolve_all_migrations(migrations_dir) logger.info("✅ All migrations resolved.") return True + elif ( + "P3018" in e.stderr + ): # PostgreSQL error code for duplicate column + logger.info( + "Migration already exists, resolving specific migration" + ) + # Extract the migration name from the error message + migration_match = re.search( + r"Migration name: (\d+_.*)", e.stderr + ) + if migration_match: + migration_name = migration_match.group(1) + logger.info(f"Rolling back migration {migration_name}") + ProxyExtrasDBManager._roll_back_migration( + migration_name + ) + logger.info( + f"Resolving migration {migration_name} that failed due to existing columns" + ) + ProxyExtrasDBManager._resolve_specific_migration( + migration_name + ) + logger.info("✅ Migration resolved.") else: # Use prisma db push with increased timeout subprocess.run( diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index 634dbb2800..75e2ef9a5c 100644 --- a/litellm-proxy-extras/pyproject.toml +++ b/litellm-proxy-extras/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-proxy-extras" -version = "0.1.10" +version = "0.1.11" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.1.10" +version = "0.1.11" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/litellm/__init__.py b/litellm/__init__.py index 7327c0e0ac..59c8c78eb9 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -113,6 +113,7 @@ _custom_logger_compatible_callbacks_literal = Literal[ "pagerduty", "humanloop", "gcs_pubsub", + "agentops", "anthropic_cache_control_hook", ] logged_real_time_event_types: Optional[Union[List[str], Literal["*"]]] = None @@ -128,19 +129,19 @@ prometheus_initialize_budget_metrics: Optional[bool] = False require_auth_for_metrics_endpoint: Optional[bool] = False argilla_batch_size: Optional[int] = None datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload -gcs_pub_sub_use_v1: Optional[ - bool -] = False # if you want to use v1 gcs pubsub logged payload +gcs_pub_sub_use_v1: Optional[bool] = ( + False # if you want to use v1 gcs pubsub logged payload +) argilla_transformation_object: Optional[Dict[str, Any]] = None -_async_input_callback: List[ - Union[str, Callable, CustomLogger] -] = [] # internal variable - async custom callbacks are routed here. -_async_success_callback: List[ - Union[str, Callable, CustomLogger] -] = [] # internal variable - async custom callbacks are routed here. -_async_failure_callback: List[ - Union[str, Callable, CustomLogger] -] = [] # internal variable - async custom callbacks are routed here. +_async_input_callback: List[Union[str, Callable, CustomLogger]] = ( + [] +) # internal variable - async custom callbacks are routed here. +_async_success_callback: List[Union[str, Callable, CustomLogger]] = ( + [] +) # internal variable - async custom callbacks are routed here. +_async_failure_callback: List[Union[str, Callable, CustomLogger]] = ( + [] +) # internal variable - async custom callbacks are routed here. pre_call_rules: List[Callable] = [] post_call_rules: List[Callable] = [] turn_off_message_logging: Optional[bool] = False @@ -148,18 +149,18 @@ log_raw_request_response: bool = False redact_messages_in_exceptions: Optional[bool] = False redact_user_api_key_info: Optional[bool] = False filter_invalid_headers: Optional[bool] = False -add_user_information_to_llm_headers: Optional[ - bool -] = None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers +add_user_information_to_llm_headers: Optional[bool] = ( + None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers +) store_audit_logs = False # Enterprise feature, allow users to see audit logs ### end of callbacks ############# -email: Optional[ - str -] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -token: Optional[ - str -] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +email: Optional[str] = ( + None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +) +token: Optional[str] = ( + None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +) telemetry = True max_tokens: int = DEFAULT_MAX_TOKENS # OpenAI Defaults drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False)) @@ -235,20 +236,24 @@ enable_loadbalancing_on_batch_endpoints: Optional[bool] = None enable_caching_on_provider_specific_optional_params: bool = ( False # feature-flag for caching on optional params - e.g. 'top_k' ) -caching: bool = False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -caching_with_models: bool = False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -cache: Optional[ - Cache -] = None # cache object <- use this - https://docs.litellm.ai/docs/caching +caching: bool = ( + False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +) +caching_with_models: bool = ( + False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +) +cache: Optional[Cache] = ( + None # cache object <- use this - https://docs.litellm.ai/docs/caching +) default_in_memory_ttl: Optional[float] = None default_redis_ttl: Optional[float] = None default_redis_batch_cache_expiry: Optional[float] = None model_alias_map: Dict[str, str] = {} model_group_alias_map: Dict[str, str] = {} max_budget: float = 0.0 # set the max budget across all providers -budget_duration: Optional[ - str -] = None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). +budget_duration: Optional[str] = ( + None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). +) default_soft_budget: float = ( DEFAULT_SOFT_BUDGET # by default all litellm proxy keys have a soft budget of 50.0 ) @@ -257,11 +262,15 @@ forward_traceparent_to_llm_provider: bool = False _current_cost = 0.0 # private variable, used if max budget is set error_logs: Dict = {} -add_function_to_prompt: bool = False # if function calling not supported by api, append function call details to system prompt +add_function_to_prompt: bool = ( + False # if function calling not supported by api, append function call details to system prompt +) client_session: Optional[httpx.Client] = None aclient_session: Optional[httpx.AsyncClient] = None model_fallbacks: Optional[List] = None # Deprecated for 'litellm.fallbacks' -model_cost_map_url: str = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" +model_cost_map_url: str = ( + "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" +) suppress_debug_info = False dynamodb_table_name: Optional[str] = None s3_callback_params: Optional[Dict] = None @@ -284,7 +293,9 @@ disable_end_user_cost_tracking_prometheus_only: Optional[bool] = None custom_prometheus_metadata_labels: List[str] = [] #### REQUEST PRIORITIZATION #### priority_reservation: Optional[Dict[str, float]] = None -force_ipv4: bool = False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. +force_ipv4: bool = ( + False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. +) module_level_aclient = AsyncHTTPHandler( timeout=request_timeout, client_alias="module level aclient" ) @@ -298,13 +309,13 @@ fallbacks: Optional[List] = None context_window_fallbacks: Optional[List] = None content_policy_fallbacks: Optional[List] = None allowed_fails: int = 3 -num_retries_per_request: Optional[ - int -] = None # for the request overall (incl. fallbacks + model retries) +num_retries_per_request: Optional[int] = ( + None # for the request overall (incl. fallbacks + model retries) +) ####### SECRET MANAGERS ##################### -secret_manager_client: Optional[ - Any -] = None # list of instantiated key management clients - e.g. azure kv, infisical, etc. +secret_manager_client: Optional[Any] = ( + None # list of instantiated key management clients - e.g. azure kv, infisical, etc. +) _google_kms_resource_name: Optional[str] = None _key_management_system: Optional[KeyManagementSystem] = None _key_management_settings: KeyManagementSettings = KeyManagementSettings() @@ -405,6 +416,7 @@ deepseek_models: List = [] azure_ai_models: List = [] jina_ai_models: List = [] voyage_models: List = [] +infinity_models: List = [] databricks_models: List = [] cloudflare_models: List = [] codestral_models: List = [] @@ -546,6 +558,8 @@ def add_known_models(): azure_ai_models.append(key) elif value.get("litellm_provider") == "voyage": voyage_models.append(key) + elif value.get("litellm_provider") == "infinity": + infinity_models.append(key) elif value.get("litellm_provider") == "databricks": databricks_models.append(key) elif value.get("litellm_provider") == "cloudflare": @@ -634,6 +648,7 @@ model_list = ( + deepseek_models + azure_ai_models + voyage_models + + infinity_models + databricks_models + cloudflare_models + codestral_models @@ -689,6 +704,7 @@ models_by_provider: dict = { "mistral": mistral_chat_models, "azure_ai": azure_ai_models, "voyage": voyage_models, + "infinity": infinity_models, "databricks": databricks_models, "cloudflare": cloudflare_models, "codestral": codestral_models, @@ -936,9 +952,11 @@ from .llms.topaz.image_variations.transformation import TopazImageVariationConfi from litellm.llms.openai.completion.transformation import OpenAITextCompletionConfig from .llms.groq.chat.transformation import GroqChatConfig from .llms.voyage.embedding.transformation import VoyageEmbeddingConfig +from .llms.infinity.embedding.transformation import InfinityEmbeddingConfig from .llms.azure_ai.chat.transformation import AzureAIStudioConfig from .llms.mistral.mistral_chat_transformation import MistralConfig from .llms.openai.responses.transformation import OpenAIResponsesAPIConfig +from .llms.azure.responses.transformation import AzureOpenAIResponsesAPIConfig from .llms.openai.chat.o_series_transformation import ( OpenAIOSeriesConfig as OpenAIO1Config, # maintain backwards compatibility OpenAIOSeriesConfig, @@ -1055,10 +1073,10 @@ from .types.llms.custom_llm import CustomLLMItem from .types.utils import GenericStreamingChunk custom_provider_map: List[CustomLLMItem] = [] -_custom_providers: List[ - str -] = [] # internal helper util, used to track names of custom providers -disable_hf_tokenizer_download: Optional[ - bool -] = None # disable huggingface tokenizer download. Defaults to openai clk100 +_custom_providers: List[str] = ( + [] +) # internal helper util, used to track names of custom providers +disable_hf_tokenizer_download: Optional[bool] = ( + None # disable huggingface tokenizer download. Defaults to openai clk100 +) global_disable_no_log_param: bool = False diff --git a/litellm/assistants/main.py b/litellm/assistants/main.py index 28f4518f15..928b6e8ac2 100644 --- a/litellm/assistants/main.py +++ b/litellm/assistants/main.py @@ -304,6 +304,11 @@ def create_assistants( "response_format": response_format, } + # only send params that are not None + create_assistant_data = { + k: v for k, v in create_assistant_data.items() if v is not None + } + response: Optional[Union[Coroutine[Any, Any, Assistant], Assistant]] = None if custom_llm_provider == "openai": api_base = ( diff --git a/litellm/constants.py b/litellm/constants.py index 9c30dc06a2..f48ce97afe 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -21,6 +21,10 @@ DEFAULT_MAX_TOKENS = 256 # used when providers need a default MAX_SIZE_PER_ITEM_IN_MEMORY_CACHE_IN_KB = 1024 # 1MB = 1024KB SINGLE_DEPLOYMENT_TRAFFIC_FAILURE_THRESHOLD = 1000 # Minimum number of requests to consider "reasonable traffic". Used for single-deployment cooldown logic. +DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET = 1024 +DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET = 2048 +DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET = 4096 + ########## Networking constants ############################################################## _DEFAULT_TTL_FOR_HTTPX_CLIENTS = 3600 # 1 hour, re-use the same httpx client for 1 hour diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 7f3d4fcc9f..eafd924bc6 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -57,6 +57,7 @@ from litellm.llms.vertex_ai.image_generation.cost_calculator import ( from litellm.responses.utils import ResponseAPILoggingUtils from litellm.types.llms.openai import ( HttpxBinaryResponseContent, + ImageGenerationRequestQuality, OpenAIRealtimeStreamList, OpenAIRealtimeStreamResponseBaseObject, OpenAIRealtimeStreamSessionEvents, @@ -642,9 +643,9 @@ def completion_cost( # noqa: PLR0915 or isinstance(completion_response, dict) ): # tts returns a custom class if isinstance(completion_response, dict): - usage_obj: Optional[ - Union[dict, Usage] - ] = completion_response.get("usage", {}) + usage_obj: Optional[Union[dict, Usage]] = ( + completion_response.get("usage", {}) + ) else: usage_obj = getattr(completion_response, "usage", {}) if isinstance(usage_obj, BaseModel) and not _is_known_usage_objects( @@ -913,7 +914,7 @@ def completion_cost( # noqa: PLR0915 def get_response_cost_from_hidden_params( - hidden_params: Union[dict, BaseModel] + hidden_params: Union[dict, BaseModel], ) -> Optional[float]: if isinstance(hidden_params, BaseModel): _hidden_params_dict = hidden_params.model_dump() @@ -1101,30 +1102,37 @@ def default_image_cost_calculator( f"{quality}/{base_model_name}" if quality else base_model_name ) + # gpt-image-1 models use low, medium, high quality. If user did not specify quality, use medium fot gpt-image-1 model family + model_name_with_v2_quality = ( + f"{ImageGenerationRequestQuality.MEDIUM.value}/{base_model_name}" + ) + verbose_logger.debug( f"Looking up cost for models: {model_name_with_quality}, {base_model_name}" ) - # Try model with quality first, fall back to base model name - if model_name_with_quality in litellm.model_cost: - cost_info = litellm.model_cost[model_name_with_quality] - elif base_model_name in litellm.model_cost: - cost_info = litellm.model_cost[base_model_name] - else: - # Try without provider prefix - model_without_provider = f"{size_str}/{model.split('/')[-1]}" - model_with_quality_without_provider = ( - f"{quality}/{model_without_provider}" if quality else model_without_provider - ) + model_without_provider = f"{size_str}/{model.split('/')[-1]}" + model_with_quality_without_provider = ( + f"{quality}/{model_without_provider}" if quality else model_without_provider + ) - if model_with_quality_without_provider in litellm.model_cost: - cost_info = litellm.model_cost[model_with_quality_without_provider] - elif model_without_provider in litellm.model_cost: - cost_info = litellm.model_cost[model_without_provider] - else: - raise Exception( - f"Model not found in cost map. Tried {model_name_with_quality}, {base_model_name}, {model_with_quality_without_provider}, and {model_without_provider}" - ) + # Try model with quality first, fall back to base model name + cost_info: Optional[dict] = None + models_to_check = [ + model_name_with_quality, + base_model_name, + model_name_with_v2_quality, + model_with_quality_without_provider, + model_without_provider, + ] + for model in models_to_check: + if model in litellm.model_cost: + cost_info = litellm.model_cost[model] + break + if cost_info is None: + raise Exception( + f"Model not found in cost map. Tried checking {models_to_check}" + ) return cost_info["input_cost_per_pixel"] * height * width * n diff --git a/litellm/integrations/_types/open_inference.py b/litellm/integrations/_types/open_inference.py index bcfabe9b7b..65ecadcf37 100644 --- a/litellm/integrations/_types/open_inference.py +++ b/litellm/integrations/_types/open_inference.py @@ -45,6 +45,14 @@ class SpanAttributes: """ The name of the model being used. """ + LLM_PROVIDER = "llm.provider" + """ + The provider of the model, such as OpenAI, Azure, Google, etc. + """ + LLM_SYSTEM = "llm.system" + """ + The AI product as identified by the client or server + """ LLM_PROMPTS = "llm.prompts" """ Prompts provided to a completions API. @@ -65,15 +73,40 @@ class SpanAttributes: """ Number of tokens in the prompt. """ + LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE = "llm.token_count.prompt_details.cache_write" + """ + Number of tokens in the prompt that were written to cache. + """ + LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ = "llm.token_count.prompt_details.cache_read" + """ + Number of tokens in the prompt that were read from cache. + """ + LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO = "llm.token_count.prompt_details.audio" + """ + The number of audio input tokens presented in the prompt + """ LLM_TOKEN_COUNT_COMPLETION = "llm.token_count.completion" """ Number of tokens in the completion. """ + LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING = "llm.token_count.completion_details.reasoning" + """ + Number of tokens used for reasoning steps in the completion. + """ + LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO = "llm.token_count.completion_details.audio" + """ + The number of audio input tokens generated by the model + """ LLM_TOKEN_COUNT_TOTAL = "llm.token_count.total" """ Total number of tokens, including both prompt and completion. """ + LLM_TOOLS = "llm.tools" + """ + List of tools that are advertised to the LLM to be able to call + """ + TOOL_NAME = "tool.name" """ Name of the tool being used. @@ -112,6 +145,19 @@ class SpanAttributes: The id of the user """ + PROMPT_VENDOR = "prompt.vendor" + """ + The vendor or origin of the prompt, e.g. a prompt library, a specialized service, etc. + """ + PROMPT_ID = "prompt.id" + """ + A vendor-specific id used to locate the prompt. + """ + PROMPT_URL = "prompt.url" + """ + A vendor-specific url used to locate the prompt. + """ + class MessageAttributes: """ @@ -151,6 +197,10 @@ class MessageAttributes: The JSON string representing the arguments passed to the function during a function call. """ + MESSAGE_TOOL_CALL_ID = "message.tool_call_id" + """ + The id of the tool call. + """ class MessageContentAttributes: @@ -186,6 +236,25 @@ class ImageAttributes: """ +class AudioAttributes: + """ + Attributes for audio + """ + + AUDIO_URL = "audio.url" + """ + The url to an audio file + """ + AUDIO_MIME_TYPE = "audio.mime_type" + """ + The mime type of the audio file + """ + AUDIO_TRANSCRIPT = "audio.transcript" + """ + The transcript of the audio file + """ + + class DocumentAttributes: """ Attributes for a document. @@ -257,6 +326,10 @@ class ToolCallAttributes: Attributes for a tool call """ + TOOL_CALL_ID = "tool_call.id" + """ + The id of the tool call. + """ TOOL_CALL_FUNCTION_NAME = "tool_call.function.name" """ The name of function that is being called during a tool call. @@ -268,6 +341,18 @@ class ToolCallAttributes: """ +class ToolAttributes: + """ + Attributes for a tools + """ + + TOOL_JSON_SCHEMA = "tool.json_schema" + """ + The json schema of a tool input, It is RECOMMENDED that this be in the + OpenAI tool calling format: https://platform.openai.com/docs/assistants/tools + """ + + class OpenInferenceSpanKindValues(Enum): TOOL = "TOOL" CHAIN = "CHAIN" @@ -284,3 +369,21 @@ class OpenInferenceSpanKindValues(Enum): class OpenInferenceMimeTypeValues(Enum): TEXT = "text/plain" JSON = "application/json" + + +class OpenInferenceLLMSystemValues(Enum): + OPENAI = "openai" + ANTHROPIC = "anthropic" + COHERE = "cohere" + MISTRALAI = "mistralai" + VERTEXAI = "vertexai" + + +class OpenInferenceLLMProviderValues(Enum): + OPENAI = "openai" + ANTHROPIC = "anthropic" + COHERE = "cohere" + MISTRALAI = "mistralai" + GOOGLE = "google" + AZURE = "azure" + AWS = "aws" diff --git a/litellm/integrations/agentops/__init__.py b/litellm/integrations/agentops/__init__.py new file mode 100644 index 0000000000..6ad02ce0ba --- /dev/null +++ b/litellm/integrations/agentops/__init__.py @@ -0,0 +1,3 @@ +from .agentops import AgentOps + +__all__ = ["AgentOps"] \ No newline at end of file diff --git a/litellm/integrations/agentops/agentops.py b/litellm/integrations/agentops/agentops.py new file mode 100644 index 0000000000..11e76841e9 --- /dev/null +++ b/litellm/integrations/agentops/agentops.py @@ -0,0 +1,118 @@ +""" +AgentOps integration for LiteLLM - Provides OpenTelemetry tracing for LLM calls +""" +import os +from dataclasses import dataclass +from typing import Optional, Dict, Any +from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig +from litellm.llms.custom_httpx.http_handler import _get_httpx_client + +@dataclass +class AgentOpsConfig: + endpoint: str = "https://otlp.agentops.cloud/v1/traces" + api_key: Optional[str] = None + service_name: Optional[str] = None + deployment_environment: Optional[str] = None + auth_endpoint: str = "https://api.agentops.ai/v3/auth/token" + + @classmethod + def from_env(cls): + return cls( + endpoint="https://otlp.agentops.cloud/v1/traces", + api_key=os.getenv("AGENTOPS_API_KEY"), + service_name=os.getenv("AGENTOPS_SERVICE_NAME", "agentops"), + deployment_environment=os.getenv("AGENTOPS_ENVIRONMENT", "production"), + auth_endpoint="https://api.agentops.ai/v3/auth/token" + ) + +class AgentOps(OpenTelemetry): + """ + AgentOps integration - built on top of OpenTelemetry + + Example usage: + ```python + import litellm + + litellm.success_callback = ["agentops"] + + response = litellm.completion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello, how are you?"}], + ) + ``` + """ + def __init__( + self, + config: Optional[AgentOpsConfig] = None, + ): + if config is None: + config = AgentOpsConfig.from_env() + + # Prefetch JWT token for authentication + jwt_token = None + project_id = None + if config.api_key: + try: + response = self._fetch_auth_token(config.api_key, config.auth_endpoint) + jwt_token = response.get("token") + project_id = response.get("project_id") + except Exception: + pass + + headers = f"Authorization=Bearer {jwt_token}" if jwt_token else None + + otel_config = OpenTelemetryConfig( + exporter="otlp_http", + endpoint=config.endpoint, + headers=headers + ) + + # Initialize OpenTelemetry with our config + super().__init__( + config=otel_config, + callback_name="agentops" + ) + + # Set AgentOps-specific resource attributes + resource_attrs = { + "service.name": config.service_name or "litellm", + "deployment.environment": config.deployment_environment or "production", + "telemetry.sdk.name": "agentops", + } + + if project_id: + resource_attrs["project.id"] = project_id + + self.resource_attributes = resource_attrs + + def _fetch_auth_token(self, api_key: str, auth_endpoint: str) -> Dict[str, Any]: + """ + Fetch JWT authentication token from AgentOps API + + Args: + api_key: AgentOps API key + auth_endpoint: Authentication endpoint + + Returns: + Dict containing JWT token and project ID + """ + headers = { + "Content-Type": "application/json", + "Connection": "keep-alive", + } + + client = _get_httpx_client() + try: + response = client.post( + url=auth_endpoint, + headers=headers, + json={"api_key": api_key}, + timeout=10 + ) + + if response.status_code != 200: + raise Exception(f"Failed to fetch auth token: {response.text}") + + return response.json() + finally: + client.close() \ No newline at end of file diff --git a/litellm/integrations/arize/_utils.py b/litellm/integrations/arize/_utils.py index 5a090968b4..e93ef128b4 100644 --- a/litellm/integrations/arize/_utils.py +++ b/litellm/integrations/arize/_utils.py @@ -1,3 +1,4 @@ +import json from typing import TYPE_CHECKING, Any, Optional, Union from litellm._logging import verbose_logger @@ -12,36 +13,141 @@ else: Span = Any -def set_attributes(span: Span, kwargs, response_obj): +def cast_as_primitive_value_type(value) -> Union[str, bool, int, float]: + """ + Converts a value to an OTEL-supported primitive for Arize/Phoenix observability. + """ + if value is None: + return "" + if isinstance(value, (str, bool, int, float)): + return value + try: + return str(value) + except Exception: + return "" + + +def safe_set_attribute(span: Span, key: str, value: Any): + """ + Sets a span attribute safely with OTEL-compliant primitive typing for Arize/Phoenix. + """ + primitive_value = cast_as_primitive_value_type(value) + span.set_attribute(key, primitive_value) + + +def set_attributes(span: Span, kwargs, response_obj): # noqa: PLR0915 + """ + Populates span with OpenInference-compliant LLM attributes for Arize and Phoenix tracing. + """ from litellm.integrations._types.open_inference import ( MessageAttributes, OpenInferenceSpanKindValues, SpanAttributes, + ToolCallAttributes, ) try: + optional_params = kwargs.get("optional_params", {}) + litellm_params = kwargs.get("litellm_params", {}) standard_logging_payload: Optional[StandardLoggingPayload] = kwargs.get( "standard_logging_object" ) + if standard_logging_payload is None: + raise ValueError("standard_logging_object not found in kwargs") ############################################# ############ LLM CALL METADATA ############## ############################################# - if standard_logging_payload and ( - metadata := standard_logging_payload["metadata"] - ): - span.set_attribute(SpanAttributes.METADATA, safe_dumps(metadata)) + # Set custom metadata for observability and trace enrichment. + metadata = ( + standard_logging_payload.get("metadata") + if standard_logging_payload + else None + ) + if metadata is not None: + safe_set_attribute(span, SpanAttributes.METADATA, safe_dumps(metadata)) ############################################# ########## LLM Request Attributes ########### ############################################# - # The name of the LLM a request is being made to + # The name of the LLM a request is being made to. if kwargs.get("model"): - span.set_attribute(SpanAttributes.LLM_MODEL_NAME, kwargs.get("model")) + safe_set_attribute( + span, + SpanAttributes.LLM_MODEL_NAME, + kwargs.get("model"), + ) - span.set_attribute( + # The LLM request type. + safe_set_attribute( + span, + "llm.request.type", + standard_logging_payload["call_type"], + ) + + # The Generative AI Provider: Azure, OpenAI, etc. + safe_set_attribute( + span, + SpanAttributes.LLM_PROVIDER, + litellm_params.get("custom_llm_provider", "Unknown"), + ) + + # The maximum number of tokens the LLM generates for a request. + if optional_params.get("max_tokens"): + safe_set_attribute( + span, + "llm.request.max_tokens", + optional_params.get("max_tokens"), + ) + + # The temperature setting for the LLM request. + if optional_params.get("temperature"): + safe_set_attribute( + span, + "llm.request.temperature", + optional_params.get("temperature"), + ) + + # The top_p sampling setting for the LLM request. + if optional_params.get("top_p"): + safe_set_attribute( + span, + "llm.request.top_p", + optional_params.get("top_p"), + ) + + # Indicates whether response is streamed. + safe_set_attribute( + span, + "llm.is_streaming", + str(optional_params.get("stream", False)), + ) + + # Logs the user ID if present. + if optional_params.get("user"): + safe_set_attribute( + span, + "llm.user", + optional_params.get("user"), + ) + + # The unique identifier for the completion. + if response_obj and response_obj.get("id"): + safe_set_attribute(span, "llm.response.id", response_obj.get("id")) + + # The model used to generate the response. + if response_obj and response_obj.get("model"): + safe_set_attribute( + span, + "llm.response.model", + response_obj.get("model"), + ) + + # Required by OpenInference to mark span as LLM kind. + safe_set_attribute( + span, SpanAttributes.OPENINFERENCE_SPAN_KIND, OpenInferenceSpanKindValues.LLM.value, ) @@ -50,77 +156,132 @@ def set_attributes(span: Span, kwargs, response_obj): # for /chat/completions # https://docs.arize.com/arize/large-language-models/tracing/semantic-conventions if messages: - span.set_attribute( + last_message = messages[-1] + safe_set_attribute( + span, SpanAttributes.INPUT_VALUE, - messages[-1].get("content", ""), # get the last message for input + last_message.get("content", ""), ) - # LLM_INPUT_MESSAGES shows up under `input_messages` tab on the span page + # LLM_INPUT_MESSAGES shows up under `input_messages` tab on the span page. for idx, msg in enumerate(messages): - # Set the role per message - span.set_attribute( - f"{SpanAttributes.LLM_INPUT_MESSAGES}.{idx}.{MessageAttributes.MESSAGE_ROLE}", - msg["role"], + prefix = f"{SpanAttributes.LLM_INPUT_MESSAGES}.{idx}" + # Set the role per message. + safe_set_attribute( + span, f"{prefix}.{MessageAttributes.MESSAGE_ROLE}", msg.get("role") ) - # Set the content per message - span.set_attribute( - f"{SpanAttributes.LLM_INPUT_MESSAGES}.{idx}.{MessageAttributes.MESSAGE_CONTENT}", + # Set the content per message. + safe_set_attribute( + span, + f"{prefix}.{MessageAttributes.MESSAGE_CONTENT}", msg.get("content", ""), ) - if standard_logging_payload and ( - model_params := standard_logging_payload["model_parameters"] - ): + # Capture tools (function definitions) used in the LLM call. + tools = optional_params.get("tools") + if tools: + for idx, tool in enumerate(tools): + function = tool.get("function") + if not function: + continue + prefix = f"{SpanAttributes.LLM_TOOLS}.{idx}" + safe_set_attribute( + span, f"{prefix}.{SpanAttributes.TOOL_NAME}", function.get("name") + ) + safe_set_attribute( + span, + f"{prefix}.{SpanAttributes.TOOL_DESCRIPTION}", + function.get("description"), + ) + safe_set_attribute( + span, + f"{prefix}.{SpanAttributes.TOOL_PARAMETERS}", + json.dumps(function.get("parameters")), + ) + + # Capture tool calls made during function-calling LLM flows. + functions = optional_params.get("functions") + if functions: + for idx, function in enumerate(functions): + prefix = f"{MessageAttributes.MESSAGE_TOOL_CALLS}.{idx}" + safe_set_attribute( + span, + f"{prefix}.{ToolCallAttributes.TOOL_CALL_FUNCTION_NAME}", + function.get("name"), + ) + + # Capture invocation parameters and user ID if available. + model_params = ( + standard_logging_payload.get("model_parameters") + if standard_logging_payload + else None + ) + if model_params: # The Generative AI Provider: Azure, OpenAI, etc. - span.set_attribute( - SpanAttributes.LLM_INVOCATION_PARAMETERS, safe_dumps(model_params) + safe_set_attribute( + span, + SpanAttributes.LLM_INVOCATION_PARAMETERS, + safe_dumps(model_params), ) if model_params.get("user"): user_id = model_params.get("user") if user_id is not None: - span.set_attribute(SpanAttributes.USER_ID, user_id) + safe_set_attribute(span, SpanAttributes.USER_ID, user_id) ############################################# ########## LLM Response Attributes ########## - # https://docs.arize.com/arize/large-language-models/tracing/semantic-conventions ############################################# - if hasattr(response_obj, "get"): - for choice in response_obj.get("choices", []): - response_message = choice.get("message", {}) - span.set_attribute( - SpanAttributes.OUTPUT_VALUE, response_message.get("content", "") - ) - # This shows up under `output_messages` tab on the span page - # This code assumes a single response - span.set_attribute( - f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}", - response_message.get("role"), - ) - span.set_attribute( - f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}", + # Captures response tokens, message, and content. + if hasattr(response_obj, "get"): + for idx, choice in enumerate(response_obj.get("choices", [])): + response_message = choice.get("message", {}) + safe_set_attribute( + span, + SpanAttributes.OUTPUT_VALUE, response_message.get("content", ""), ) - usage = response_obj.get("usage") + # This shows up under `output_messages` tab on the span page. + prefix = f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.{idx}" + safe_set_attribute( + span, + f"{prefix}.{MessageAttributes.MESSAGE_ROLE}", + response_message.get("role"), + ) + safe_set_attribute( + span, + f"{prefix}.{MessageAttributes.MESSAGE_CONTENT}", + response_message.get("content", ""), + ) + + # Token usage info. + usage = response_obj and response_obj.get("usage") if usage: - span.set_attribute( + safe_set_attribute( + span, SpanAttributes.LLM_TOKEN_COUNT_TOTAL, usage.get("total_tokens"), ) # The number of tokens used in the LLM response (completion). - span.set_attribute( + safe_set_attribute( + span, SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, usage.get("completion_tokens"), ) # The number of tokens used in the LLM prompt. - span.set_attribute( + safe_set_attribute( + span, SpanAttributes.LLM_TOKEN_COUNT_PROMPT, usage.get("prompt_tokens"), ) - pass + except Exception as e: - verbose_logger.error(f"Error setting arize attributes: {e}") + verbose_logger.error( + f"[Arize/Phoenix] Failed to set OpenInference span attributes: {e}" + ) + if hasattr(span, "record_exception"): + span.record_exception(e) diff --git a/litellm/integrations/datadog/datadog_llm_obs.py b/litellm/integrations/datadog/datadog_llm_obs.py index e4e074bab7..bbb042c57b 100644 --- a/litellm/integrations/datadog/datadog_llm_obs.py +++ b/litellm/integrations/datadog/datadog_llm_obs.py @@ -13,10 +13,15 @@ import uuid from datetime import datetime from typing import Any, Dict, List, Optional, Union +import httpx + import litellm from litellm._logging import verbose_logger from litellm.integrations.custom_batch_logger import CustomBatchLogger from litellm.integrations.datadog.datadog import DataDogLogger +from litellm.litellm_core_utils.prompt_templates.common_utils import ( + handle_any_messages_to_chat_completion_str_messages_conversion, +) from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, @@ -106,7 +111,6 @@ class DataDogLLMObsLogger(DataDogLogger, CustomBatchLogger): }, ) - response.raise_for_status() if response.status_code != 202: raise Exception( f"DataDogLLMObs: Unexpected response - status_code: {response.status_code}, text: {response.text}" @@ -116,6 +120,10 @@ class DataDogLLMObsLogger(DataDogLogger, CustomBatchLogger): f"DataDogLLMObs: Successfully sent batch - status_code: {response.status_code}" ) self.log_queue.clear() + except httpx.HTTPStatusError as e: + verbose_logger.exception( + f"DataDogLLMObs: Error sending batch - {e.response.text}" + ) except Exception as e: verbose_logger.exception(f"DataDogLLMObs: Error sending batch - {str(e)}") @@ -133,7 +141,11 @@ class DataDogLLMObsLogger(DataDogLogger, CustomBatchLogger): metadata = kwargs.get("litellm_params", {}).get("metadata", {}) - input_meta = InputMeta(messages=messages) # type: ignore + input_meta = InputMeta( + messages=handle_any_messages_to_chat_completion_str_messages_conversion( + messages + ) + ) output_meta = OutputMeta(messages=self._get_response_messages(response_obj)) meta = Meta( diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 54d87cc42e..7578019dff 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -311,6 +311,9 @@ def exception_type( # type: ignore # noqa: PLR0915 elif ( "invalid_request_error" in error_str and "content_policy_violation" in error_str + ) or ( + "Invalid prompt" in error_str + and "violating our usage policy" in error_str ): exception_mapping_worked = True raise ContentPolicyViolationError( diff --git a/litellm/litellm_core_utils/get_supported_openai_params.py b/litellm/litellm_core_utils/get_supported_openai_params.py index bcf9fdb961..c0f638ddc2 100644 --- a/litellm/litellm_core_utils/get_supported_openai_params.py +++ b/litellm/litellm_core_utils/get_supported_openai_params.py @@ -221,6 +221,8 @@ def get_supported_openai_params( # noqa: PLR0915 return litellm.PredibaseConfig().get_supported_openai_params(model=model) elif custom_llm_provider == "voyage": return litellm.VoyageEmbeddingConfig().get_supported_openai_params(model=model) + elif custom_llm_provider == "infinity": + return litellm.InfinityEmbeddingConfig().get_supported_openai_params(model=model) elif custom_llm_provider == "triton": if request_type == "embeddings": return litellm.TritonEmbeddingConfig().get_supported_openai_params( diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 4d0b93f390..77d4fd7d5d 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -28,6 +28,7 @@ from litellm._logging import _is_debugging_on, verbose_logger from litellm.batches.batch_utils import _handle_completed_batch from litellm.caching.caching import DualCache, InMemoryCache from litellm.caching.caching_handler import LLMCachingHandler + from litellm.constants import ( DEFAULT_MOCK_RESPONSE_COMPLETION_TOKEN_COUNT, DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT, @@ -36,6 +37,7 @@ from litellm.cost_calculator import ( RealtimeAPITokenUsageProcessor, _select_model_name_for_cost_calc, ) +from litellm.integrations.agentops import AgentOps from litellm.integrations.anthropic_cache_control_hook import AnthropicCacheControlHook from litellm.integrations.arize.arize import ArizeLogger from litellm.integrations.custom_guardrail import CustomGuardrail @@ -2685,7 +2687,15 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 """ try: custom_logger_init_args = custom_logger_init_args or {} - if logging_integration == "lago": + if logging_integration == "agentops": # Add AgentOps initialization + for callback in _in_memory_loggers: + if isinstance(callback, AgentOps): + return callback # type: ignore + + agentops_logger = AgentOps() + _in_memory_loggers.append(agentops_logger) + return agentops_logger # type: ignore + elif logging_integration == "lago": for callback in _in_memory_loggers: if isinstance(callback, LagoLogger): return callback # type: ignore diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index ae5eb286e4..48809fe856 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -265,8 +265,10 @@ def generic_cost_per_token( ) ## CALCULATE OUTPUT COST - text_tokens = usage.completion_tokens + text_tokens = 0 audio_tokens = 0 + reasoning_tokens = 0 + is_text_tokens_total = False if usage.completion_tokens_details is not None: audio_tokens = ( cast( @@ -280,9 +282,20 @@ def generic_cost_per_token( Optional[int], getattr(usage.completion_tokens_details, "text_tokens", None), ) - or usage.completion_tokens # default to completion tokens, if this field is not set + or 0 # default to completion tokens, if this field is not set + ) + reasoning_tokens = ( + cast( + Optional[int], + getattr(usage.completion_tokens_details, "reasoning_tokens", 0), + ) + or 0 ) + if text_tokens == 0: + text_tokens = usage.completion_tokens + if text_tokens == usage.completion_tokens: + is_text_tokens_total = True ## TEXT COST completion_cost = float(text_tokens) * completion_base_cost @@ -290,12 +303,26 @@ def generic_cost_per_token( "output_cost_per_audio_token" ) + _output_cost_per_reasoning_token: Optional[float] = model_info.get( + "output_cost_per_reasoning_token" + ) + ## AUDIO COST - if ( - _output_cost_per_audio_token is not None - and audio_tokens is not None - and audio_tokens > 0 - ): + if not is_text_tokens_total and audio_tokens is not None and audio_tokens > 0: + _output_cost_per_audio_token = ( + _output_cost_per_audio_token + if _output_cost_per_audio_token is not None + else completion_base_cost + ) completion_cost += float(audio_tokens) * _output_cost_per_audio_token + ## REASONING COST + if not is_text_tokens_total and reasoning_tokens and reasoning_tokens > 0: + _output_cost_per_reasoning_token = ( + _output_cost_per_reasoning_token + if _output_cost_per_reasoning_token is not None + else completion_base_cost + ) + completion_cost += float(reasoning_tokens) * _output_cost_per_reasoning_token + return prompt_cost, completion_cost diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index a0a99f580b..5a8319a747 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -14,6 +14,7 @@ from litellm.types.llms.openai import ChatCompletionThinkingBlock from litellm.types.utils import ( ChatCompletionDeltaToolCall, ChatCompletionMessageToolCall, + ChatCompletionRedactedThinkingBlock, Choices, Delta, EmbeddingResponse, @@ -486,7 +487,14 @@ def convert_to_model_response_object( # noqa: PLR0915 ) # Handle thinking models that display `thinking_blocks` within `content` - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ + ChatCompletionThinkingBlock, + ChatCompletionRedactedThinkingBlock, + ] + ] + ] = None if "thinking_blocks" in choice["message"]: thinking_blocks = choice["message"]["thinking_blocks"] provider_specific_fields["thinking_blocks"] = thinking_blocks diff --git a/litellm/litellm_core_utils/model_param_helper.py b/litellm/litellm_core_utils/model_param_helper.py index c96b4a3f5b..91f2f1341c 100644 --- a/litellm/litellm_core_utils/model_param_helper.py +++ b/litellm/litellm_core_utils/model_param_helper.py @@ -75,6 +75,10 @@ class ModelParamHelper: combined_kwargs = combined_kwargs.difference(exclude_kwargs) return combined_kwargs + @staticmethod + def get_litellm_provider_specific_params_for_chat_params() -> Set[str]: + return set(["thinking"]) + @staticmethod def _get_litellm_supported_chat_completion_kwargs() -> Set[str]: """ @@ -82,11 +86,18 @@ class ModelParamHelper: This follows the OpenAI API Spec """ - all_chat_completion_kwargs = set( + non_streaming_params: Set[str] = set( getattr(CompletionCreateParamsNonStreaming, "__annotations__", {}).keys() - ).union( - set(getattr(CompletionCreateParamsStreaming, "__annotations__", {}).keys()) ) + streaming_params: Set[str] = set( + getattr(CompletionCreateParamsStreaming, "__annotations__", {}).keys() + ) + litellm_provider_specific_params: Set[str] = ( + ModelParamHelper.get_litellm_provider_specific_params_for_chat_params() + ) + all_chat_completion_kwargs: Set[str] = non_streaming_params.union( + streaming_params + ).union(litellm_provider_specific_params) return all_chat_completion_kwargs @staticmethod diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index 2fe99fb27b..963ab33f52 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -6,7 +6,7 @@ import io import mimetypes import re from os import PathLike -from typing import Dict, List, Literal, Mapping, Optional, Union, cast +from typing import Any, Dict, List, Literal, Mapping, Optional, Union, cast from litellm.types.llms.openai import ( AllMessageValues, @@ -32,6 +32,35 @@ DEFAULT_ASSISTANT_CONTINUE_MESSAGE = ChatCompletionAssistantMessage( ) +def handle_any_messages_to_chat_completion_str_messages_conversion( + messages: Any, +) -> List[Dict[str, str]]: + """ + Handles any messages to chat completion str messages conversion + + Relevant Issue: https://github.com/BerriAI/litellm/issues/9494 + """ + import json + + if isinstance(messages, list): + try: + return cast( + List[Dict[str, str]], + handle_messages_with_content_list_to_str_conversion(messages), + ) + except Exception: + return [{"input": json.dumps(message, default=str)} for message in messages] + elif isinstance(messages, dict): + try: + return [{"input": json.dumps(messages, default=str)}] + except Exception: + return [{"input": str(messages)}] + elif isinstance(messages, str): + return [{"input": messages}] + else: + return [{"input": str(messages)}] + + def handle_messages_with_content_list_to_str_conversion( messages: List[AllMessageValues], ) -> List[AllMessageValues]: @@ -471,3 +500,59 @@ def unpack_defs(schema, defs): unpack_defs(ref, defs) value["items"] = ref continue + + +def _get_image_mime_type_from_url(url: str) -> Optional[str]: + """ + Get mime type for common image URLs + See gemini mime types: https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/image-understanding#image-requirements + + Supported by Gemini: + application/pdf + audio/mpeg + audio/mp3 + audio/wav + image/png + image/jpeg + image/webp + text/plain + video/mov + video/mpeg + video/mp4 + video/mpg + video/avi + video/wmv + video/mpegps + video/flv + """ + url = url.lower() + + # Map file extensions to mime types + mime_types = { + # Images + (".jpg", ".jpeg"): "image/jpeg", + (".png",): "image/png", + (".webp",): "image/webp", + # Videos + (".mp4",): "video/mp4", + (".mov",): "video/mov", + (".mpeg", ".mpg"): "video/mpeg", + (".avi",): "video/avi", + (".wmv",): "video/wmv", + (".mpegps",): "video/mpegps", + (".flv",): "video/flv", + # Audio + (".mp3",): "audio/mp3", + (".wav",): "audio/wav", + (".mpeg",): "audio/mpeg", + # Documents + (".pdf",): "application/pdf", + (".txt",): "text/plain", + } + + # Check each extension group against the URL + for extensions, mime_type in mime_types.items(): + if any(url.endswith(ext) for ext in extensions): + return mime_type + + return None diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index aa5dc0d49a..5b11b224bb 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -2258,6 +2258,14 @@ def _parse_content_type(content_type: str) -> str: return m.get_content_type() +def _parse_mime_type(base64_data: str) -> Optional[str]: + mime_type_match = re.match(r"data:(.*?);base64", base64_data) + if mime_type_match: + return mime_type_match.group(1) + else: + return None + + class BedrockImageProcessor: """Handles both sync and async image processing for Bedrock conversations.""" diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index ebb8650044..397aa1e047 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -29,6 +29,7 @@ from litellm.types.llms.anthropic import ( UsageDelta, ) from litellm.types.llms.openai import ( + ChatCompletionRedactedThinkingBlock, ChatCompletionThinkingBlock, ChatCompletionToolCallChunk, ) @@ -501,18 +502,19 @@ class ModelResponseIterator: ) -> Tuple[ str, Optional[ChatCompletionToolCallChunk], - List[ChatCompletionThinkingBlock], + List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]], Dict[str, Any], ]: """ Helper function to handle the content block delta """ - text = "" tool_use: Optional[ChatCompletionToolCallChunk] = None provider_specific_fields = {} content_block = ContentBlockDelta(**chunk) # type: ignore - thinking_blocks: List[ChatCompletionThinkingBlock] = [] + thinking_blocks: List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] = [] self.content_blocks.append(content_block) if "text" in content_block["delta"]: @@ -541,20 +543,25 @@ class ModelResponseIterator: ) ] provider_specific_fields["thinking_blocks"] = thinking_blocks + return text, tool_use, thinking_blocks, provider_specific_fields def _handle_reasoning_content( - self, thinking_blocks: List[ChatCompletionThinkingBlock] + self, + thinking_blocks: List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ], ) -> Optional[str]: """ Handle the reasoning content """ reasoning_content = None for block in thinking_blocks: + thinking_content = cast(Optional[str], block.get("thinking")) if reasoning_content is None: reasoning_content = "" - if "thinking" in block: - reasoning_content += block["thinking"] + if thinking_content is not None: + reasoning_content += thinking_content return reasoning_content def chunk_parser(self, chunk: dict) -> ModelResponseStream: @@ -567,7 +574,13 @@ class ModelResponseIterator: usage: Optional[Usage] = None provider_specific_fields: Dict[str, Any] = {} reasoning_content: Optional[str] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ + ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock + ] + ] + ] = None index = int(chunk.get("index", 0)) if type_chunk == "content_block_delta": @@ -605,6 +618,15 @@ class ModelResponseIterator: }, "index": self.tool_index, } + elif ( + content_block_start["content_block"]["type"] == "redacted_thinking" + ): + thinking_blocks = [ + ChatCompletionRedactedThinkingBlock( + type="redacted_thinking", + data=content_block_start["content_block"]["data"], + ) + ] elif type_chunk == "content_block_stop": ContentBlockStop(**chunk) # type: ignore # check if tool call content block diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 590931321d..06e0553f8d 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -7,6 +7,9 @@ import httpx import litellm from litellm.constants import ( DEFAULT_ANTHROPIC_CHAT_MAX_TOKENS, + DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, RESPONSE_FORMAT_TOOL_NAME, ) from litellm.litellm_core_utils.core_helpers import map_finish_reason @@ -27,6 +30,7 @@ from litellm.types.llms.openai import ( REASONING_EFFORT, AllMessageValues, ChatCompletionCachedContent, + ChatCompletionRedactedThinkingBlock, ChatCompletionSystemMessage, ChatCompletionThinkingBlock, ChatCompletionToolCallChunk, @@ -276,11 +280,20 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if reasoning_effort is None: return None elif reasoning_effort == "low": - return AnthropicThinkingParam(type="enabled", budget_tokens=1024) + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + ) elif reasoning_effort == "medium": - return AnthropicThinkingParam(type="enabled", budget_tokens=2048) + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, + ) elif reasoning_effort == "high": - return AnthropicThinkingParam(type="enabled", budget_tokens=4096) + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + ) else: raise ValueError(f"Unmapped reasoning effort: {reasoning_effort}") @@ -563,13 +576,21 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) -> Tuple[ str, Optional[List[Any]], - Optional[List[ChatCompletionThinkingBlock]], + Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ], Optional[str], List[ChatCompletionToolCallChunk], ]: text_content = "" citations: Optional[List[Any]] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ] = None reasoning_content: Optional[str] = None tool_calls: List[ChatCompletionToolCallChunk] = [] for idx, content in enumerate(completion_response["content"]): @@ -588,20 +609,30 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): index=idx, ) ) - ## CITATIONS - if content.get("citations", None) is not None: - if citations is None: - citations = [] - citations.append(content["citations"]) - if content.get("thinking", None) is not None: + + elif content.get("thinking", None) is not None: if thinking_blocks is None: thinking_blocks = [] thinking_blocks.append(cast(ChatCompletionThinkingBlock, content)) + elif content["type"] == "redacted_thinking": + if thinking_blocks is None: + thinking_blocks = [] + thinking_blocks.append( + cast(ChatCompletionRedactedThinkingBlock, content) + ) + + ## CITATIONS + if content.get("citations") is not None: + if citations is None: + citations = [] + citations.append(content["citations"]) if thinking_blocks is not None: reasoning_content = "" for block in thinking_blocks: - if "thinking" in block: - reasoning_content += block["thinking"] + thinking_content = cast(Optional[str], block.get("thinking")) + if thinking_content is not None: + reasoning_content += thinking_content + return text_content, citations, thinking_blocks, reasoning_content, tool_calls def calculate_usage( @@ -691,7 +722,13 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): else: text_content = "" citations: Optional[List[Any]] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ + ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock + ] + ] + ] = None reasoning_content: Optional[str] = None tool_calls: List[ChatCompletionToolCallChunk] = [] diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py index a37d816770..ab335ca7c1 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py @@ -1,6 +1,6 @@ """ - call /messages on Anthropic API -- Make streaming + non-streaming request - just pass it through direct to Anthropic. No need to do anything special here +- Make streaming + non-streaming request - just pass it through direct to Anthropic. No need to do anything special here - Ensure requests are logged in the DB - stream + non-stream """ @@ -43,7 +43,9 @@ class AnthropicMessagesHandler: from litellm.proxy.pass_through_endpoints.success_handler import ( PassThroughEndpointLogging, ) - from litellm.proxy.pass_through_endpoints.types import EndpointType + from litellm.types.passthrough_endpoints.pass_through_endpoints import ( + EndpointType, + ) # Create success handler object passthrough_success_handler_obj = PassThroughEndpointLogging() @@ -98,11 +100,11 @@ async def anthropic_messages( api_base=optional_params.api_base, api_key=optional_params.api_key, ) - anthropic_messages_provider_config: Optional[ - BaseAnthropicMessagesConfig - ] = ProviderConfigManager.get_provider_anthropic_messages_config( - model=model, - provider=litellm.LlmProviders(_custom_llm_provider), + anthropic_messages_provider_config: Optional[BaseAnthropicMessagesConfig] = ( + ProviderConfigManager.get_provider_anthropic_messages_config( + model=model, + provider=litellm.LlmProviders(_custom_llm_provider), + ) ) if anthropic_messages_provider_config is None: raise ValueError( diff --git a/litellm/llms/azure/assistants.py b/litellm/llms/azure/assistants.py index 2e8c78b259..271cd698e7 100644 --- a/litellm/llms/azure/assistants.py +++ b/litellm/llms/azure/assistants.py @@ -288,6 +288,7 @@ class AzureAssistantsAPI(BaseAzureLLM): timeout=timeout, max_retries=max_retries, client=client, + litellm_params=litellm_params, ) thread_message: OpenAIMessage = openai_client.beta.threads.messages.create( # type: ignore diff --git a/litellm/llms/azure/chat/o_series_transformation.py b/litellm/llms/azure/chat/o_series_transformation.py index 21aafce7fb..69fb941ca5 100644 --- a/litellm/llms/azure/chat/o_series_transformation.py +++ b/litellm/llms/azure/chat/o_series_transformation.py @@ -79,7 +79,7 @@ class AzureOpenAIO1Config(OpenAIOSeriesConfig): return True def is_o_series_model(self, model: str) -> bool: - return "o1" in model or "o3" in model or "o_series/" in model + return "o1" in model or "o3" in model or "o4" in model or "o_series/" in model def transform_request( self, diff --git a/litellm/llms/azure/responses/transformation.py b/litellm/llms/azure/responses/transformation.py new file mode 100644 index 0000000000..7d9244e31b --- /dev/null +++ b/litellm/llms/azure/responses/transformation.py @@ -0,0 +1,172 @@ +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, cast + +import httpx + +import litellm +from litellm._logging import verbose_logger +from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import * +from litellm.types.responses.main import * +from litellm.types.router import GenericLiteLLMParams +from litellm.utils import _add_path_to_api_base + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): + def validate_environment( + self, + headers: dict, + model: str, + api_key: Optional[str] = None, + ) -> dict: + api_key = ( + api_key + or litellm.api_key + or litellm.azure_key + or get_secret_str("AZURE_OPENAI_API_KEY") + or get_secret_str("AZURE_API_KEY") + ) + + headers.update( + { + "Authorization": f"Bearer {api_key}", + } + ) + return headers + + def get_complete_url( + self, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + """ + Constructs a complete URL for the API request. + + Args: + - api_base: Base URL, e.g., + "https://litellm8397336933.openai.azure.com" + OR + "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview" + - model: Model name. + - optional_params: Additional query parameters, including "api_version". + - stream: If streaming is required (optional). + + Returns: + - A complete URL string, e.g., + "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview" + """ + api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") + if api_base is None: + raise ValueError( + f"api_base is required for Azure AI Studio. Please set the api_base parameter. Passed `api_base={api_base}`" + ) + original_url = httpx.URL(api_base) + + # Extract api_version or use default + api_version = cast(Optional[str], litellm_params.get("api_version")) + + # Create a new dictionary with existing params + query_params = dict(original_url.params) + + # Add api_version if needed + if "api-version" not in query_params and api_version: + query_params["api-version"] = api_version + + # Add the path to the base URL + if "/openai/responses" not in api_base: + new_url = _add_path_to_api_base( + api_base=api_base, ending_path="/openai/responses" + ) + else: + new_url = api_base + + # Use the new query_params dictionary + final_url = httpx.URL(new_url).copy_with(params=query_params) + + return str(final_url) + + ######################################################### + ########## DELETE RESPONSE API TRANSFORMATION ############## + ######################################################### + def _construct_url_for_response_id_in_path( + self, api_base: str, response_id: str + ) -> str: + """ + Constructs a URL for the API request with the response_id in the path. + """ + from urllib.parse import urlparse, urlunparse + + # Parse the URL to separate its components + parsed_url = urlparse(api_base) + + # Insert the response_id at the end of the path component + # Remove trailing slash if present to avoid double slashes + path = parsed_url.path.rstrip("/") + new_path = f"{path}/{response_id}" + + # Reconstruct the URL with all original components but with the modified path + constructed_url = urlunparse( + ( + parsed_url.scheme, # http, https + parsed_url.netloc, # domain name, port + new_path, # path with response_id added + parsed_url.params, # parameters + parsed_url.query, # query string + parsed_url.fragment, # fragment + ) + ) + return constructed_url + + def transform_delete_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + """ + Transform the delete response API request into a URL and data + + Azure OpenAI API expects the following request: + - DELETE /openai/responses/{response_id}?api-version=xxx + + This function handles URLs with query parameters by inserting the response_id + at the correct location (before any query parameters). + """ + delete_url = self._construct_url_for_response_id_in_path( + api_base=api_base, response_id=response_id + ) + + data: Dict = {} + verbose_logger.debug(f"delete response url={delete_url}") + return delete_url, data + + ######################################################### + ########## GET RESPONSE API TRANSFORMATION ############### + ######################################################### + def transform_get_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + """ + Transform the get response API request into a URL and data + + OpenAI API expects the following request + - GET /v1/responses/{response_id} + """ + get_url = self._construct_url_for_response_id_in_path( + api_base=api_base, response_id=response_id + ) + data: Dict = {} + verbose_logger.debug(f"get response url={get_url}") + return get_url, data diff --git a/litellm/llms/azure_ai/chat/transformation.py b/litellm/llms/azure_ai/chat/transformation.py index 839f875f75..1adc56804f 100644 --- a/litellm/llms/azure_ai/chat/transformation.py +++ b/litellm/llms/azure_ai/chat/transformation.py @@ -1,3 +1,4 @@ +import enum from typing import Any, List, Optional, Tuple, cast from urllib.parse import urlparse @@ -19,6 +20,10 @@ from litellm.types.utils import ModelResponse, ProviderField from litellm.utils import _add_path_to_api_base, supports_tool_choice +class AzureFoundryErrorStrings(str, enum.Enum): + SET_EXTRA_PARAMETERS_TO_PASS_THROUGH = "Set extra-parameters to 'pass-through'" + + class AzureAIStudioConfig(OpenAIConfig): def get_supported_openai_params(self, model: str) -> List: model_supports_tool_choice = True # azure ai supports this by default @@ -240,12 +245,18 @@ class AzureAIStudioConfig(OpenAIConfig): ) -> bool: should_drop_params = litellm_params.get("drop_params") or litellm.drop_params error_text = e.response.text + if should_drop_params and "Extra inputs are not permitted" in error_text: return True elif ( "unknown field: parameter index is not a valid field" in error_text ): # remove index from tool calls return True + elif ( + AzureFoundryErrorStrings.SET_EXTRA_PARAMETERS_TO_PASS_THROUGH.value + in error_text + ): # remove extra-parameters from tool calls + return True return super().should_retry_llm_api_inside_llm_translation_on_http_error( e=e, litellm_params=litellm_params ) @@ -265,5 +276,46 @@ class AzureAIStudioConfig(OpenAIConfig): litellm.remove_index_from_tool_calls( messages=_messages, ) + elif ( + AzureFoundryErrorStrings.SET_EXTRA_PARAMETERS_TO_PASS_THROUGH.value + in e.response.text + ): + request_data = self._drop_extra_params_from_request_data( + request_data, e.response.text + ) data = drop_params_from_unprocessable_entity_error(e=e, data=request_data) return data + + def _drop_extra_params_from_request_data( + self, request_data: dict, error_text: str + ) -> dict: + params_to_drop = self._extract_params_to_drop_from_error_text(error_text) + if params_to_drop: + for param in params_to_drop: + if param in request_data: + request_data.pop(param, None) + return request_data + + def _extract_params_to_drop_from_error_text( + self, error_text: str + ) -> Optional[List[str]]: + """ + Error text looks like this" + "Extra parameters ['stream_options', 'extra-parameters'] are not allowed when extra-parameters is not set or set to be 'error'. + """ + import re + + # Extract parameters within square brackets + match = re.search(r"\[(.*?)\]", error_text) + if not match: + return [] + + # Parse the extracted string into a list of parameter names + params_str = match.group(1) + params = [] + for param in params_str.split(","): + # Clean up the parameter name (remove quotes, spaces) + clean_param = param.strip().strip("'").strip('"') + if clean_param: + params.append(clean_param) + return params diff --git a/litellm/llms/base_llm/responses/transformation.py b/litellm/llms/base_llm/responses/transformation.py index e98a579845..751d29dd56 100644 --- a/litellm/llms/base_llm/responses/transformation.py +++ b/litellm/llms/base_llm/responses/transformation.py @@ -1,6 +1,6 @@ import types from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union import httpx @@ -10,6 +10,7 @@ from litellm.types.llms.openai import ( ResponsesAPIResponse, ResponsesAPIStreamingResponse, ) +from litellm.types.responses.main import * from litellm.types.router import GenericLiteLLMParams if TYPE_CHECKING: @@ -73,8 +74,7 @@ class BaseResponsesAPIConfig(ABC): def get_complete_url( self, api_base: Optional[str], - model: str, - stream: Optional[bool] = None, + litellm_params: dict, ) -> str: """ OPTIONAL @@ -119,6 +119,56 @@ class BaseResponsesAPIConfig(ABC): """ pass + ######################################################### + ########## DELETE RESPONSE API TRANSFORMATION ############## + ######################################################### + @abstractmethod + def transform_delete_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + pass + + @abstractmethod + def transform_delete_response_api_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> DeleteResponseResult: + pass + + ######################################################### + ########## END DELETE RESPONSE API TRANSFORMATION ####### + ######################################################### + + ######################################################### + ########## GET RESPONSE API TRANSFORMATION ############### + ######################################################### + @abstractmethod + def transform_get_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + pass + + @abstractmethod + def transform_get_response_api_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> ResponsesAPIResponse: + pass + + ######################################################### + ########## END GET RESPONSE API TRANSFORMATION ########## + ######################################################### + def get_error_class( self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] ) -> BaseLLMException: diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 76ea51f435..8332463c5c 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -22,6 +22,7 @@ from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMExcepti from litellm.types.llms.bedrock import * from litellm.types.llms.openai import ( AllMessageValues, + ChatCompletionRedactedThinkingBlock, ChatCompletionResponseMessage, ChatCompletionSystemMessage, ChatCompletionThinkingBlock, @@ -106,6 +107,15 @@ class AmazonConverseConfig(BaseConfig): "response_format", ] + if ( + "arn" in model + ): # we can't infer the model from the arn, so just add all params + supported_params.append("tools") + supported_params.append("tool_choice") + supported_params.append("thinking") + supported_params.append("reasoning_effort") + return supported_params + ## Filter out 'cross-region' from model name base_model = BedrockModelInfo.get_base_model(model) @@ -375,25 +385,27 @@ class AmazonConverseConfig(BaseConfig): system_content_blocks: List[SystemContentBlock] = [] for idx, message in enumerate(messages): if message["role"] == "system": - _system_content_block: Optional[SystemContentBlock] = None - _cache_point_block: Optional[SystemContentBlock] = None - if isinstance(message["content"], str) and len(message["content"]) > 0: - _system_content_block = SystemContentBlock(text=message["content"]) - _cache_point_block = self._get_cache_point_block( + system_prompt_indices.append(idx) + if isinstance(message["content"], str) and message["content"]: + system_content_blocks.append( + SystemContentBlock(text=message["content"]) + ) + cache_block = self._get_cache_point_block( message, block_type="system" ) + if cache_block: + system_content_blocks.append(cache_block) elif isinstance(message["content"], list): for m in message["content"]: - if m.get("type", "") == "text" and len(m["text"]) > 0: - _system_content_block = SystemContentBlock(text=m["text"]) - _cache_point_block = self._get_cache_point_block( + if m.get("type") == "text" and m.get("text"): + system_content_blocks.append( + SystemContentBlock(text=m["text"]) + ) + cache_block = self._get_cache_point_block( m, block_type="system" ) - if _system_content_block is not None: - system_content_blocks.append(_system_content_block) - if _cache_point_block is not None: - system_content_blocks.append(_cache_point_block) - system_prompt_indices.append(idx) + if cache_block: + system_content_blocks.append(cache_block) if len(system_prompt_indices) > 0: for idx in reversed(system_prompt_indices): messages.pop(idx) @@ -627,9 +639,11 @@ class AmazonConverseConfig(BaseConfig): def _transform_thinking_blocks( self, thinking_blocks: List[BedrockConverseReasoningContentBlock] - ) -> List[ChatCompletionThinkingBlock]: + ) -> List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]]: """Return a consistent format for thinking blocks between Anthropic and Bedrock.""" - thinking_blocks_list: List[ChatCompletionThinkingBlock] = [] + thinking_blocks_list: List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] = [] for block in thinking_blocks: if "reasoningText" in block: _thinking_block = ChatCompletionThinkingBlock(type="thinking") @@ -640,6 +654,11 @@ class AmazonConverseConfig(BaseConfig): if _signature is not None: _thinking_block["signature"] = _signature thinking_blocks_list.append(_thinking_block) + elif "redactedContent" in block: + _redacted_block = ChatCompletionRedactedThinkingBlock( + type="redacted_thinking", data=block["redactedContent"] + ) + thinking_blocks_list.append(_redacted_block) return thinking_blocks_list def _transform_usage(self, usage: ConverseTokenUsageBlock) -> Usage: diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 09bdd63572..dfd1658543 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -50,6 +50,7 @@ from litellm.llms.custom_httpx.http_handler import ( ) from litellm.types.llms.bedrock import * from litellm.types.llms.openai import ( + ChatCompletionRedactedThinkingBlock, ChatCompletionThinkingBlock, ChatCompletionToolCallChunk, ChatCompletionToolCallFunctionChunk, @@ -1255,19 +1256,33 @@ class AWSEventStreamDecoder: def translate_thinking_blocks( self, thinking_block: BedrockConverseReasoningContentBlockDelta - ) -> Optional[List[ChatCompletionThinkingBlock]]: + ) -> Optional[ + List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]] + ]: """ Translate the thinking blocks to a string """ - thinking_blocks_list: List[ChatCompletionThinkingBlock] = [] - _thinking_block = ChatCompletionThinkingBlock(type="thinking") + thinking_blocks_list: List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] = [] + _thinking_block: Optional[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] = None + if "text" in thinking_block: + _thinking_block = ChatCompletionThinkingBlock(type="thinking") _thinking_block["thinking"] = thinking_block["text"] elif "signature" in thinking_block: + _thinking_block = ChatCompletionThinkingBlock(type="thinking") _thinking_block["signature"] = thinking_block["signature"] _thinking_block["thinking"] = "" # consistent with anthropic response - thinking_blocks_list.append(_thinking_block) + elif "redactedContent" in thinking_block: + _thinking_block = ChatCompletionRedactedThinkingBlock( + type="redacted_thinking", data=thinking_block["redactedContent"] + ) + if _thinking_block is not None: + thinking_blocks_list.append(_thinking_block) return thinking_blocks_list def converse_chunk_parser(self, chunk_data: dict) -> ModelResponseStream: @@ -1279,31 +1294,44 @@ class AWSEventStreamDecoder: usage: Optional[Usage] = None provider_specific_fields: dict = {} reasoning_content: Optional[str] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ + ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock + ] + ] + ] = None index = int(chunk_data.get("contentBlockIndex", 0)) if "start" in chunk_data: start_obj = ContentBlockStartEvent(**chunk_data["start"]) self.content_blocks = [] # reset - if ( - start_obj is not None - and "toolUse" in start_obj - and start_obj["toolUse"] is not None - ): - ## check tool name was formatted by litellm - _response_tool_name = start_obj["toolUse"]["name"] - response_tool_name = get_bedrock_tool_name( - response_tool_name=_response_tool_name - ) - tool_use = { - "id": start_obj["toolUse"]["toolUseId"], - "type": "function", - "function": { - "name": response_tool_name, - "arguments": "", - }, - "index": index, - } + if start_obj is not None: + if "toolUse" in start_obj and start_obj["toolUse"] is not None: + ## check tool name was formatted by litellm + _response_tool_name = start_obj["toolUse"]["name"] + response_tool_name = get_bedrock_tool_name( + response_tool_name=_response_tool_name + ) + tool_use = { + "id": start_obj["toolUse"]["toolUseId"], + "type": "function", + "function": { + "name": response_tool_name, + "arguments": "", + }, + "index": index, + } + elif ( + "reasoningContent" in start_obj + and start_obj["reasoningContent"] is not None + ): # redacted thinking can be in start object + thinking_blocks = self.translate_thinking_blocks( + start_obj["reasoningContent"] + ) + provider_specific_fields = { + "reasoningContent": start_obj["reasoningContent"], + } elif "delta" in chunk_data: delta_obj = ContentBlockDeltaEvent(**chunk_data["delta"]) self.content_blocks.append(delta_obj) diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 627dd8c9f9..f99e04ab9d 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -650,6 +650,49 @@ class HTTPHandler: except Exception as e: raise e + def delete( + self, + url: str, + data: Optional[Union[dict, str]] = None, # type: ignore + json: Optional[dict] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + stream: bool = False, + ): + try: + if timeout is not None: + req = self.client.build_request( + "DELETE", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore + ) + else: + req = self.client.build_request( + "DELETE", url, data=data, json=json, params=params, headers=headers # type: ignore + ) + response = self.client.send(req, stream=stream) + response.raise_for_status() + return response + except httpx.TimeoutException: + raise litellm.Timeout( + message=f"Connection timed out after {timeout} seconds.", + model="default-model-name", + llm_provider="litellm-httpx-handler", + ) + except httpx.HTTPStatusError as e: + if stream is True: + setattr(e, "message", mask_sensitive_info(e.response.read())) + setattr(e, "text", mask_sensitive_info(e.response.read())) + else: + error_text = mask_sensitive_info(e.response.text) + setattr(e, "message", error_text) + setattr(e, "text", error_text) + + setattr(e, "status_code", e.response.status_code) + + raise e + except Exception as e: + raise e + def __del__(self) -> None: try: self.close() diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 1ab8a94adf..abbbc2e595 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -36,6 +36,7 @@ from litellm.types.llms.openai import ( ResponsesAPIResponse, ) from litellm.types.rerank import OptionalRerankParams, RerankResponse +from litellm.types.responses.main import DeleteResponseResult from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import EmbeddingResponse, FileTypes, TranscriptionResponse from litellm.utils import CustomStreamWrapper, ModelResponse, ProviderConfigManager @@ -229,13 +230,17 @@ class BaseLLMHTTPHandler: api_key: Optional[str] = None, headers: Optional[dict] = {}, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + provider_config: Optional[BaseConfig] = None, ): json_mode: bool = optional_params.pop("json_mode", False) extra_body: Optional[dict] = optional_params.pop("extra_body", None) fake_stream = fake_stream or optional_params.pop("fake_stream", False) - provider_config = ProviderConfigManager.get_provider_chat_config( - model=model, provider=litellm.LlmProviders(custom_llm_provider) + provider_config = ( + provider_config + or ProviderConfigManager.get_provider_chat_config( + model=model, provider=litellm.LlmProviders(custom_llm_provider) + ) ) if provider_config is None: raise ValueError( @@ -462,7 +467,7 @@ class BaseLLMHTTPHandler: ) if fake_stream is True: - model_response: (ModelResponse) = provider_config.transform_response( + model_response: ModelResponse = provider_config.transform_response( model=model, raw_response=response, model_response=litellm.ModelResponse(), @@ -595,7 +600,7 @@ class BaseLLMHTTPHandler: ) if fake_stream is True: - model_response: (ModelResponse) = provider_config.transform_response( + model_response: ModelResponse = provider_config.transform_response( model=model, raw_response=response, model_response=litellm.ModelResponse(), @@ -1011,6 +1016,7 @@ class BaseLLMHTTPHandler: client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, _is_async: bool = False, fake_stream: bool = False, + litellm_metadata: Optional[Dict[str, Any]] = None, ) -> Union[ ResponsesAPIResponse, BaseResponsesAPIStreamingIterator, @@ -1037,6 +1043,7 @@ class BaseLLMHTTPHandler: timeout=timeout, client=client if isinstance(client, AsyncHTTPHandler) else None, fake_stream=fake_stream, + litellm_metadata=litellm_metadata, ) if client is None or not isinstance(client, HTTPHandler): @@ -1055,9 +1062,12 @@ class BaseLLMHTTPHandler: if extra_headers: headers.update(extra_headers) + # Check if streaming is requested + stream = response_api_optional_request_params.get("stream", False) + api_base = responses_api_provider_config.get_complete_url( api_base=litellm_params.api_base, - model=model, + litellm_params=dict(litellm_params), ) data = responses_api_provider_config.transform_responses_api_request( @@ -1079,9 +1089,6 @@ class BaseLLMHTTPHandler: }, ) - # Check if streaming is requested - stream = response_api_optional_request_params.get("stream", False) - try: if stream: # For streaming, use stream=True in the request @@ -1105,6 +1112,8 @@ class BaseLLMHTTPHandler: model=model, logging_obj=logging_obj, responses_api_provider_config=responses_api_provider_config, + litellm_metadata=litellm_metadata, + custom_llm_provider=custom_llm_provider, ) return SyncResponsesAPIStreamingIterator( @@ -1112,6 +1121,8 @@ class BaseLLMHTTPHandler: model=model, logging_obj=logging_obj, responses_api_provider_config=responses_api_provider_config, + litellm_metadata=litellm_metadata, + custom_llm_provider=custom_llm_provider, ) else: # For non-streaming requests @@ -1148,6 +1159,7 @@ class BaseLLMHTTPHandler: timeout: Optional[Union[float, httpx.Timeout]] = None, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, fake_stream: bool = False, + litellm_metadata: Optional[Dict[str, Any]] = None, ) -> Union[ResponsesAPIResponse, BaseResponsesAPIStreamingIterator]: """ Async version of the responses API handler. @@ -1170,9 +1182,12 @@ class BaseLLMHTTPHandler: if extra_headers: headers.update(extra_headers) + # Check if streaming is requested + stream = response_api_optional_request_params.get("stream", False) + api_base = responses_api_provider_config.get_complete_url( api_base=litellm_params.api_base, - model=model, + litellm_params=dict(litellm_params), ) data = responses_api_provider_config.transform_responses_api_request( @@ -1193,8 +1208,6 @@ class BaseLLMHTTPHandler: "headers": headers, }, ) - # Check if streaming is requested - stream = response_api_optional_request_params.get("stream", False) try: if stream: @@ -1221,6 +1234,8 @@ class BaseLLMHTTPHandler: model=model, logging_obj=logging_obj, responses_api_provider_config=responses_api_provider_config, + litellm_metadata=litellm_metadata, + custom_llm_provider=custom_llm_provider, ) # Return the streaming iterator @@ -1229,6 +1244,8 @@ class BaseLLMHTTPHandler: model=model, logging_obj=logging_obj, responses_api_provider_config=responses_api_provider_config, + litellm_metadata=litellm_metadata, + custom_llm_provider=custom_llm_provider, ) else: # For non-streaming, proceed as before @@ -1252,6 +1269,319 @@ class BaseLLMHTTPHandler: logging_obj=logging_obj, ) + async def async_delete_response_api_handler( + self, + response_id: str, + responses_api_provider_config: BaseResponsesAPIConfig, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + custom_llm_provider: Optional[str], + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + _is_async: bool = False, + ) -> DeleteResponseResult: + """ + Async version of the delete response API handler. + Uses async HTTP client to make requests. + """ + if client is None or not isinstance(client, AsyncHTTPHandler): + async_httpx_client = get_async_httpx_client( + llm_provider=litellm.LlmProviders(custom_llm_provider), + params={"ssl_verify": litellm_params.get("ssl_verify", None)}, + ) + else: + async_httpx_client = client + + headers = responses_api_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=extra_headers or {}, + model="None", + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = responses_api_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + url, data = responses_api_provider_config.transform_delete_response_api_request( + response_id=response_id, + api_base=api_base, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input=input, + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = await async_httpx_client.delete( + url=url, headers=headers, data=json.dumps(data), timeout=timeout + ) + + except Exception as e: + raise self._handle_error( + e=e, + provider_config=responses_api_provider_config, + ) + + return responses_api_provider_config.transform_delete_response_api_response( + raw_response=response, + logging_obj=logging_obj, + ) + + def delete_response_api_handler( + self, + response_id: str, + responses_api_provider_config: BaseResponsesAPIConfig, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + custom_llm_provider: Optional[str], + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + _is_async: bool = False, + ) -> Union[DeleteResponseResult, Coroutine[Any, Any, DeleteResponseResult]]: + """ + Async version of the responses API handler. + Uses async HTTP client to make requests. + """ + if _is_async: + return self.async_delete_response_api_handler( + response_id=response_id, + responses_api_provider_config=responses_api_provider_config, + litellm_params=litellm_params, + logging_obj=logging_obj, + custom_llm_provider=custom_llm_provider, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout, + client=client, + ) + if client is None or not isinstance(client, HTTPHandler): + sync_httpx_client = _get_httpx_client( + params={"ssl_verify": litellm_params.get("ssl_verify", None)} + ) + else: + sync_httpx_client = client + + headers = responses_api_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=extra_headers or {}, + model="None", + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = responses_api_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + url, data = responses_api_provider_config.transform_delete_response_api_request( + response_id=response_id, + api_base=api_base, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input=input, + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = sync_httpx_client.delete( + url=url, headers=headers, data=json.dumps(data), timeout=timeout + ) + + except Exception as e: + raise self._handle_error( + e=e, + provider_config=responses_api_provider_config, + ) + + return responses_api_provider_config.transform_delete_response_api_response( + raw_response=response, + logging_obj=logging_obj, + ) + + def get_responses( + self, + response_id: str, + responses_api_provider_config: BaseResponsesAPIConfig, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + custom_llm_provider: Optional[str] = None, + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + _is_async: bool = False, + ) -> Union[ResponsesAPIResponse, Coroutine[Any, Any, ResponsesAPIResponse]]: + """ + Get a response by ID + Uses GET /v1/responses/{response_id} endpoint in the responses API + """ + if _is_async: + return self.async_get_responses( + response_id=response_id, + responses_api_provider_config=responses_api_provider_config, + litellm_params=litellm_params, + logging_obj=logging_obj, + custom_llm_provider=custom_llm_provider, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout, + client=client, + ) + + if client is None or not isinstance(client, HTTPHandler): + sync_httpx_client = _get_httpx_client( + params={"ssl_verify": litellm_params.get("ssl_verify", None)} + ) + else: + sync_httpx_client = client + + headers = responses_api_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=extra_headers or {}, + model="None", + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = responses_api_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + url, data = responses_api_provider_config.transform_get_response_api_request( + response_id=response_id, + api_base=api_base, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input="", + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = sync_httpx_client.get( + url=url, headers=headers, params=data + ) + except Exception as e: + raise self._handle_error( + e=e, + provider_config=responses_api_provider_config, + ) + + return responses_api_provider_config.transform_get_response_api_response( + raw_response=response, + logging_obj=logging_obj, + ) + + async def async_get_responses( + self, + response_id: str, + responses_api_provider_config: BaseResponsesAPIConfig, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + custom_llm_provider: Optional[str] = None, + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + ) -> ResponsesAPIResponse: + """ + Async version of get_responses + """ + if client is None or not isinstance(client, AsyncHTTPHandler): + async_httpx_client = get_async_httpx_client( + llm_provider=litellm.LlmProviders(custom_llm_provider), + params={"ssl_verify": litellm_params.get("ssl_verify", None)}, + ) + else: + async_httpx_client = client + + headers = responses_api_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=extra_headers or {}, + model="None", + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = responses_api_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + url, data = responses_api_provider_config.transform_get_response_api_request( + response_id=response_id, + api_base=api_base, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input="", + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = await async_httpx_client.get( + url=url, headers=headers, params=data + ) + + except Exception as e: + verbose_logger.exception(f"Error retrieving response: {e}") + raise self._handle_error( + e=e, + provider_config=responses_api_provider_config, + ) + + return responses_api_provider_config.transform_get_response_api_response( + raw_response=response, + logging_obj=logging_obj, + ) + def create_file( self, create_file_data: CreateFileRequest, diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 6f5738fb4b..7eb3d82963 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -37,6 +37,7 @@ from litellm.types.llms.databricks import ( ) from litellm.types.llms.openai import ( AllMessageValues, + ChatCompletionRedactedThinkingBlock, ChatCompletionThinkingBlock, ChatCompletionToolChoiceFunctionParam, ChatCompletionToolChoiceObjectParam, @@ -314,13 +315,24 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): @staticmethod def extract_reasoning_content( content: Optional[AllDatabricksContentValues], - ) -> Tuple[Optional[str], Optional[List[ChatCompletionThinkingBlock]]]: + ) -> Tuple[ + Optional[str], + Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ], + ]: """ Extract and return the reasoning content and thinking blocks """ if content is None: return None, None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ] = None reasoning_content: Optional[str] = None if isinstance(content, list): for item in content: diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index dc78c5bc5d..2a795bdf2f 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -1,15 +1,33 @@ -from typing import List, Literal, Optional, Tuple, Union, cast +import json +import uuid +from typing import Any, List, Literal, Optional, Tuple, Union, cast + +import httpx import litellm +from litellm.constants import RESPONSE_FORMAT_TOOL_NAME +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.litellm_core_utils.llm_response_utils.get_headers import ( + get_response_headers, +) from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import ( AllMessageValues, ChatCompletionImageObject, + ChatCompletionToolParam, OpenAIChatCompletionToolParam, ) -from litellm.types.utils import ProviderSpecificModelInfo +from litellm.types.utils import ( + ChatCompletionMessageToolCall, + Choices, + Function, + Message, + ModelResponse, + ProviderSpecificModelInfo, +) from ...openai.chat.gpt_transformation import OpenAIGPTConfig +from ..common_utils import FireworksAIException class FireworksAIConfig(OpenAIGPTConfig): @@ -219,6 +237,94 @@ class FireworksAIConfig(OpenAIGPTConfig): headers=headers, ) + def _handle_message_content_with_tool_calls( + self, + message: Message, + tool_calls: Optional[List[ChatCompletionToolParam]], + ) -> Message: + """ + Fireworks AI sends tool calls in the content field instead of tool_calls + + Relevant Issue: https://github.com/BerriAI/litellm/issues/7209#issuecomment-2813208780 + """ + if ( + tool_calls is not None + and message.content is not None + and message.tool_calls is None + ): + try: + function = Function(**json.loads(message.content)) + if function.name != RESPONSE_FORMAT_TOOL_NAME and function.name in [ + tool["function"]["name"] for tool in tool_calls + ]: + tool_call = ChatCompletionMessageToolCall( + function=function, id=str(uuid.uuid4()), type="function" + ) + message.tool_calls = [tool_call] + + message.content = None + except Exception: + pass + + return message + + def transform_response( + self, + model: str, + raw_response: httpx.Response, + model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ModelResponse: + ## LOGGING + logging_obj.post_call( + input=messages, + api_key=api_key, + original_response=raw_response.text, + additional_args={"complete_input_dict": request_data}, + ) + + ## RESPONSE OBJECT + try: + completion_response = raw_response.json() + except Exception as e: + response_headers = getattr(raw_response, "headers", None) + raise FireworksAIException( + message="Unable to get json response - {}, Original Response: {}".format( + str(e), raw_response.text + ), + status_code=raw_response.status_code, + headers=response_headers, + ) + + raw_response_headers = dict(raw_response.headers) + + additional_headers = get_response_headers(raw_response_headers) + + response = ModelResponse(**completion_response) + + if response.model is not None: + response.model = "fireworks_ai/" + response.model + + ## FIREWORKS AI sends tool calls in the content field instead of tool_calls + for choice in response.choices: + cast( + Choices, choice + ).message = self._handle_message_content_with_tool_calls( + message=cast(Choices, choice).message, + tool_calls=optional_params.get("tools", None), + ) + + response._hidden_params = {"additional_headers": additional_headers} + + return response + def _get_openai_compatible_provider_info( self, api_base: Optional[str], api_key: Optional[str] ) -> Tuple[Optional[str], Optional[str]]: diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index 795333d598..dc65c46455 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -7,6 +7,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import ( ) from litellm.types.llms.openai import AllMessageValues from litellm.types.llms.vertex_ai import ContentType, PartType +from litellm.utils import supports_reasoning from ...vertex_ai.gemini.transformation import _gemini_convert_messages_with_history from ...vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexGeminiConfig @@ -67,7 +68,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): return super().get_config() def get_supported_openai_params(self, model: str) -> List[str]: - return [ + supported_params = [ "temperature", "top_p", "max_tokens", @@ -83,6 +84,10 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): "frequency_penalty", "modalities", ] + if supports_reasoning(model): + supported_params.append("reasoning_effort") + supported_params.append("thinking") + return supported_params def map_openai_params( self, diff --git a/litellm/llms/hosted_vllm/chat/transformation.py b/litellm/llms/hosted_vllm/chat/transformation.py index 9332e98789..e328bf2881 100644 --- a/litellm/llms/hosted_vllm/chat/transformation.py +++ b/litellm/llms/hosted_vllm/chat/transformation.py @@ -2,9 +2,19 @@ Translate from OpenAI's `/v1/chat/completions` to VLLM's `/v1/chat/completions` """ -from typing import Optional, Tuple +from typing import List, Optional, Tuple, cast +from litellm.litellm_core_utils.prompt_templates.common_utils import ( + _get_image_mime_type_from_url, +) +from litellm.litellm_core_utils.prompt_templates.factory import _parse_mime_type from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import ( + AllMessageValues, + ChatCompletionFileObject, + ChatCompletionVideoObject, + ChatCompletionVideoUrlObject, +) from ....utils import _remove_additional_properties, _remove_strict_from_schema from ...openai.chat.gpt_transformation import OpenAIGPTConfig @@ -38,3 +48,71 @@ class HostedVLLMChatConfig(OpenAIGPTConfig): api_key or get_secret_str("HOSTED_VLLM_API_KEY") or "fake-api-key" ) # vllm does not require an api key return api_base, dynamic_api_key + + def _is_video_file(self, content_item: ChatCompletionFileObject) -> bool: + """ + Check if the file is a video + + - format: video/ + - file_data: base64 encoded video data + - file_id: infer mp4 from extension + """ + file = content_item.get("file", {}) + format = file.get("format") + file_data = file.get("file_data") + file_id = file.get("file_id") + if content_item.get("type") != "file": + return False + if format and format.startswith("video/"): + return True + elif file_data: + mime_type = _parse_mime_type(file_data) + if mime_type and mime_type.startswith("video/"): + return True + elif file_id: + mime_type = _get_image_mime_type_from_url(file_id) + if mime_type and mime_type.startswith("video/"): + return True + return False + + def _convert_file_to_video_url( + self, content_item: ChatCompletionFileObject + ) -> ChatCompletionVideoObject: + file = content_item.get("file", {}) + file_id = file.get("file_id") + file_data = file.get("file_data") + + if file_id: + return ChatCompletionVideoObject( + type="video_url", video_url=ChatCompletionVideoUrlObject(url=file_id) + ) + elif file_data: + return ChatCompletionVideoObject( + type="video_url", video_url=ChatCompletionVideoUrlObject(url=file_data) + ) + raise ValueError("file_id or file_data is required") + + def _transform_messages( + self, messages: List[AllMessageValues], model: str + ) -> List[AllMessageValues]: + """ + Support translating video files from file_id or file_data to video_url + """ + for message in messages: + if message["role"] == "user": + message_content = message.get("content") + if message_content and isinstance(message_content, list): + replaced_content_items: List[ + Tuple[int, ChatCompletionFileObject] + ] = [] + for idx, content_item in enumerate(message_content): + if content_item.get("type") == "file": + content_item = cast(ChatCompletionFileObject, content_item) + if self._is_video_file(content_item): + replaced_content_items.append((idx, content_item)) + for idx, content_item in replaced_content_items: + message_content[idx] = self._convert_file_to_video_url( + content_item + ) + transformed_messages = super()._transform_messages(messages, model) + return transformed_messages diff --git a/litellm/llms/infinity/rerank/common_utils.py b/litellm/llms/infinity/common_utils.py similarity index 76% rename from litellm/llms/infinity/rerank/common_utils.py rename to litellm/llms/infinity/common_utils.py index 99477d1a33..089818c829 100644 --- a/litellm/llms/infinity/rerank/common_utils.py +++ b/litellm/llms/infinity/common_utils.py @@ -1,10 +1,16 @@ +from typing import Union import httpx from litellm.llms.base_llm.chat.transformation import BaseLLMException class InfinityError(BaseLLMException): - def __init__(self, status_code, message): + def __init__( + self, + status_code: int, + message: str, + headers: Union[dict, httpx.Headers] = {} + ): self.status_code = status_code self.message = message self.request = httpx.Request( @@ -16,4 +22,5 @@ class InfinityError(BaseLLMException): message=message, request=self.request, response=self.response, + headers=headers, ) # Call the base class constructor with the parameters it needs diff --git a/litellm/llms/infinity/embedding/handler.py b/litellm/llms/infinity/embedding/handler.py new file mode 100644 index 0000000000..cdcb99c433 --- /dev/null +++ b/litellm/llms/infinity/embedding/handler.py @@ -0,0 +1,5 @@ +""" +Infinity Embedding - uses `llm_http_handler.py` to make httpx requests + +Request/Response transformation is handled in `transformation.py` +""" diff --git a/litellm/llms/infinity/embedding/transformation.py b/litellm/llms/infinity/embedding/transformation.py new file mode 100644 index 0000000000..824dcd38da --- /dev/null +++ b/litellm/llms/infinity/embedding/transformation.py @@ -0,0 +1,141 @@ +from typing import List, Optional, Union + +import httpx + +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues +from litellm.types.utils import EmbeddingResponse, Usage + +from ..common_utils import InfinityError + + +class InfinityEmbeddingConfig(BaseEmbeddingConfig): + """ + Reference: https://infinity.modal.michaelfeil.eu/docs + """ + + def __init__(self) -> None: + pass + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + if api_base is None: + raise ValueError("api_base is required for Infinity embeddings") + # Remove trailing slashes and ensure clean base URL + api_base = api_base.rstrip("/") + if not api_base.endswith("/embeddings"): + api_base = f"{api_base}/embeddings" + return api_base + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + if api_key is None: + api_key = get_secret_str("INFINITY_API_KEY") + + default_headers = { + "Authorization": f"Bearer {api_key}", + "accept": "application/json", + "Content-Type": "application/json", + } + + # If 'Authorization' is provided in headers, it overrides the default. + if "Authorization" in headers: + default_headers["Authorization"] = headers["Authorization"] + + # Merge other headers, overriding any default ones except Authorization + return {**default_headers, **headers} + + def get_supported_openai_params(self, model: str) -> list: + return [ + "encoding_format", + "modality", + "dimensions", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + """ + Map OpenAI params to Infinity params + + Reference: https://infinity.modal.michaelfeil.eu/docs + """ + if "encoding_format" in non_default_params: + optional_params["encoding_format"] = non_default_params["encoding_format"] + if "modality" in non_default_params: + optional_params["modality"] = non_default_params["modality"] + if "dimensions" in non_default_params: + optional_params["output_dimension"] = non_default_params["dimensions"] + return optional_params + + def transform_embedding_request( + self, + model: str, + input: AllEmbeddingInputValues, + optional_params: dict, + headers: dict, + ) -> dict: + return { + "input": input, + "model": model, + **optional_params, + } + + def transform_embedding_response( + self, + model: str, + raw_response: httpx.Response, + model_response: EmbeddingResponse, + logging_obj: LiteLLMLoggingObj, + api_key: Optional[str] = None, + request_data: dict = {}, + optional_params: dict = {}, + litellm_params: dict = {}, + ) -> EmbeddingResponse: + try: + raw_response_json = raw_response.json() + except Exception: + raise InfinityError( + message=raw_response.text, status_code=raw_response.status_code + ) + + # model_response.usage + model_response.model = raw_response_json.get("model") + model_response.data = raw_response_json.get("data") + model_response.object = raw_response_json.get("object") + + usage = Usage( + prompt_tokens=raw_response_json.get("usage", {}).get("prompt_tokens", 0), + total_tokens=raw_response_json.get("usage", {}).get("total_tokens", 0), + ) + model_response.usage = usage + return model_response + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] + ) -> BaseLLMException: + return InfinityError( + message=error_message, status_code=status_code, headers=headers + ) diff --git a/litellm/llms/infinity/rerank/transformation.py b/litellm/llms/infinity/rerank/transformation.py index 1e7234ab17..4b75fa121b 100644 --- a/litellm/llms/infinity/rerank/transformation.py +++ b/litellm/llms/infinity/rerank/transformation.py @@ -22,7 +22,7 @@ from litellm.types.rerank import ( RerankTokens, ) -from .common_utils import InfinityError +from ..common_utils import InfinityError class InfinityRerankConfig(CohereRerankConfig): diff --git a/litellm/llms/litellm_proxy/chat/transformation.py b/litellm/llms/litellm_proxy/chat/transformation.py index 6699ef70d4..22013198ba 100644 --- a/litellm/llms/litellm_proxy/chat/transformation.py +++ b/litellm/llms/litellm_proxy/chat/transformation.py @@ -13,6 +13,7 @@ class LiteLLMProxyChatConfig(OpenAIGPTConfig): def get_supported_openai_params(self, model: str) -> List: list = super().get_supported_openai_params(model) list.append("thinking") + list.append("reasoning_effort") return list def _map_openai_params( diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index e062c0c9fa..8cbdf6bdcc 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -7,6 +7,7 @@ from litellm._logging import verbose_logger from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import * +from litellm.types.responses.main import * from litellm.types.router import GenericLiteLLMParams from ..common_utils import OpenAIError @@ -110,8 +111,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): def get_complete_url( self, api_base: Optional[str], - model: str, - stream: Optional[bool] = None, + litellm_params: dict, ) -> str: """ Get the endpoint for OpenAI responses API @@ -187,7 +187,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): model_class = event_models.get(cast(ResponsesAPIStreamEvents, event_type)) if not model_class: - raise ValueError(f"Unknown event type: {event_type}") + return GenericEvent return model_class @@ -214,3 +214,75 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): f"Error getting model info in OpenAIResponsesAPIConfig: {e}" ) return False + + ######################################################### + ########## DELETE RESPONSE API TRANSFORMATION ############## + ######################################################### + def transform_delete_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + """ + Transform the delete response API request into a URL and data + + OpenAI API expects the following request + - DELETE /v1/responses/{response_id} + """ + url = f"{api_base}/{response_id}" + data: Dict = {} + return url, data + + def transform_delete_response_api_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> DeleteResponseResult: + """ + Transform the delete response API response into a DeleteResponseResult + """ + try: + raw_response_json = raw_response.json() + except Exception: + raise OpenAIError( + message=raw_response.text, status_code=raw_response.status_code + ) + return DeleteResponseResult(**raw_response_json) + + ######################################################### + ########## GET RESPONSE API TRANSFORMATION ############### + ######################################################### + def transform_get_response_api_request( + self, + response_id: str, + api_base: str, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[str, Dict]: + """ + Transform the get response API request into a URL and data + + OpenAI API expects the following request + - GET /v1/responses/{response_id} + """ + url = f"{api_base}/{response_id}" + data: Dict = {} + return url, data + + def transform_get_response_api_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> ResponsesAPIResponse: + """ + Transform the get response API response into a ResponsesAPIResponse + """ + try: + raw_response_json = raw_response.json() + except Exception: + raise OpenAIError( + message=raw_response.text, status_code=raw_response.status_code + ) + return ResponsesAPIResponse(**raw_response_json) diff --git a/litellm/llms/triton/completion/transformation.py b/litellm/llms/triton/completion/transformation.py index 21fcf2eefb..0db83b2d3d 100644 --- a/litellm/llms/triton/completion/transformation.py +++ b/litellm/llms/triton/completion/transformation.py @@ -201,8 +201,6 @@ class TritonGenerateConfig(TritonConfig): "max_tokens": int( optional_params.get("max_tokens", DEFAULT_MAX_TOKENS_FOR_TRITON) ), - "bad_words": [""], - "stop_words": [""], }, "stream": bool(stream), } diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 0afad13feb..e50954b8f9 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -12,6 +12,9 @@ from pydantic import BaseModel import litellm from litellm._logging import verbose_logger +from litellm.litellm_core_utils.prompt_templates.common_utils import ( + _get_image_mime_type_from_url, +) from litellm.litellm_core_utils.prompt_templates.factory import ( convert_to_anthropic_image_obj, convert_to_gemini_tool_call_invoke, @@ -99,62 +102,6 @@ def _process_gemini_image(image_url: str, format: Optional[str] = None) -> PartT raise e -def _get_image_mime_type_from_url(url: str) -> Optional[str]: - """ - Get mime type for common image URLs - See gemini mime types: https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/image-understanding#image-requirements - - Supported by Gemini: - application/pdf - audio/mpeg - audio/mp3 - audio/wav - image/png - image/jpeg - image/webp - text/plain - video/mov - video/mpeg - video/mp4 - video/mpg - video/avi - video/wmv - video/mpegps - video/flv - """ - url = url.lower() - - # Map file extensions to mime types - mime_types = { - # Images - (".jpg", ".jpeg"): "image/jpeg", - (".png",): "image/png", - (".webp",): "image/webp", - # Videos - (".mp4",): "video/mp4", - (".mov",): "video/mov", - (".mpeg", ".mpg"): "video/mpeg", - (".avi",): "video/avi", - (".wmv",): "video/wmv", - (".mpegps",): "video/mpegps", - (".flv",): "video/flv", - # Audio - (".mp3",): "audio/mp3", - (".wav",): "audio/wav", - (".mpeg",): "audio/mpeg", - # Documents - (".pdf",): "application/pdf", - (".txt",): "text/plain", - } - - # Check each extension group against the URL - for extensions, mime_type in mime_types.items(): - if any(url.endswith(ext) for ext in extensions): - return mime_type - - return None - - def _gemini_convert_messages_with_history( # noqa: PLR0915 messages: List[AllMessageValues], ) -> List[ContentType]: @@ -269,6 +216,11 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915 msg_dict = messages[msg_i] # type: ignore assistant_msg = ChatCompletionAssistantMessage(**msg_dict) # type: ignore _message_content = assistant_msg.get("content", None) + reasoning_content = assistant_msg.get("reasoning_content", None) + if reasoning_content is not None: + assistant_content.append( + PartType(thought=True, text=reasoning_content) + ) if _message_content is not None and isinstance(_message_content, list): _parts = [] for element in _message_content: @@ -276,6 +228,7 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915 if element["type"] == "text": _part = PartType(text=element["text"]) _parts.append(_part) + assistant_content.extend(_parts) elif ( _message_content is not None diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 3a58bb2c6d..9ea1c2ee12 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -24,6 +24,11 @@ import litellm import litellm.litellm_core_utils import litellm.litellm_core_utils.litellm_logging from litellm import verbose_logger +from litellm.constants import ( + DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, +) from litellm.litellm_core_utils.core_helpers import map_finish_reason from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException from litellm.llms.custom_httpx.http_handler import ( @@ -31,6 +36,7 @@ from litellm.llms.custom_httpx.http_handler import ( HTTPHandler, get_async_httpx_client, ) +from litellm.types.llms.anthropic import AnthropicThinkingParam from litellm.types.llms.openai import ( AllMessageValues, ChatCompletionResponseMessage, @@ -45,11 +51,13 @@ from litellm.types.llms.vertex_ai import ( ContentType, FunctionCallingConfig, FunctionDeclaration, + GeminiThinkingConfig, GenerateContentResponseBody, HttpxPartType, LogprobsResult, ToolConfig, Tools, + UsageMetadata, ) from litellm.types.utils import ( ChatCompletionTokenLogprob, @@ -59,7 +67,7 @@ from litellm.types.utils import ( TopLogprob, Usage, ) -from litellm.utils import CustomStreamWrapper, ModelResponse +from litellm.utils import CustomStreamWrapper, ModelResponse, supports_reasoning from ....utils import _remove_additional_properties, _remove_strict_from_schema from ..common_utils import VertexAIError, _build_vertex_schema @@ -190,7 +198,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): return super().get_config() def get_supported_openai_params(self, model: str) -> List[str]: - return [ + supported_params = [ "temperature", "top_p", "max_tokens", @@ -210,6 +218,10 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "top_logprobs", "modalities", ] + if supports_reasoning(model): + supported_params.append("reasoning_effort") + supported_params.append("thinking") + return supported_params def map_tool_choice_values( self, model: str, tool_choice: Union[str, dict] @@ -313,10 +325,14 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): if isinstance(old_schema, list): for item in old_schema: if isinstance(item, dict): - item = _build_vertex_schema(parameters=item, add_property_ordering=True) + item = _build_vertex_schema( + parameters=item, add_property_ordering=True + ) elif isinstance(old_schema, dict): - old_schema = _build_vertex_schema(parameters=old_schema, add_property_ordering=True) + old_schema = _build_vertex_schema( + parameters=old_schema, add_property_ordering=True + ) return old_schema def apply_response_schema_transformation(self, value: dict, optional_params: dict): @@ -343,6 +359,43 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): value=optional_params["response_schema"] ) + @staticmethod + def _map_reasoning_effort_to_thinking_budget( + reasoning_effort: str, + ) -> GeminiThinkingConfig: + if reasoning_effort == "low": + return { + "thinkingBudget": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + "includeThoughts": True, + } + elif reasoning_effort == "medium": + return { + "thinkingBudget": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, + "includeThoughts": True, + } + elif reasoning_effort == "high": + return { + "thinkingBudget": DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + "includeThoughts": True, + } + else: + raise ValueError(f"Invalid reasoning effort: {reasoning_effort}") + + @staticmethod + def _map_thinking_param( + thinking_param: AnthropicThinkingParam, + ) -> GeminiThinkingConfig: + thinking_enabled = thinking_param.get("type") == "enabled" + thinking_budget = thinking_param.get("budget_tokens") + + params: GeminiThinkingConfig = {} + if thinking_enabled: + params["includeThoughts"] = True + if thinking_budget is not None and isinstance(thinking_budget, int): + params["thinkingBudget"] = thinking_budget + + return params + def map_openai_params( self, non_default_params: Dict, @@ -399,6 +452,16 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): optional_params["tool_choice"] = _tool_choice_value elif param == "seed": optional_params["seed"] = value + elif param == "reasoning_effort" and isinstance(value, str): + optional_params[ + "thinkingConfig" + ] = VertexGeminiConfig._map_reasoning_effort_to_thinking_budget(value) + elif param == "thinking": + optional_params[ + "thinkingConfig" + ] = VertexGeminiConfig._map_thinking_param( + cast(AnthropicThinkingParam, value) + ) elif param == "modalities" and isinstance(value, list): response_modalities = [] for modality in value: @@ -514,19 +577,28 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): def get_assistant_content_message( self, parts: List[HttpxPartType] - ) -> Optional[str]: - _content_str = "" + ) -> Tuple[Optional[str], Optional[str]]: + content_str: Optional[str] = None + reasoning_content_str: Optional[str] = None for part in parts: + _content_str = "" if "text" in part: _content_str += part["text"] elif "inlineData" in part: # base64 encoded image _content_str += "data:{};base64,{}".format( part["inlineData"]["mimeType"], part["inlineData"]["data"] ) + if len(_content_str) > 0: + if part.get("thought") is True: + if reasoning_content_str is None: + reasoning_content_str = "" + reasoning_content_str += _content_str + else: + if content_str is None: + content_str = "" + content_str += _content_str - if _content_str: - return _content_str - return None + return content_str, reasoning_content_str def _transform_parts( self, @@ -669,6 +741,23 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): return model_response + def is_candidate_token_count_inclusive(self, usage_metadata: UsageMetadata) -> bool: + """ + Check if the candidate token count is inclusive of the thinking token count + + if prompttokencount + candidatesTokenCount == totalTokenCount, then the candidate token count is inclusive of the thinking token count + + else the candidate token count is exclusive of the thinking token count + + Addresses - https://github.com/BerriAI/litellm/pull/10141#discussion_r2052272035 + """ + if usage_metadata.get("promptTokenCount", 0) + usage_metadata.get( + "candidatesTokenCount", 0 + ) == usage_metadata.get("totalTokenCount", 0): + return True + else: + return False + def _calculate_usage( self, completion_response: GenerateContentResponseBody, @@ -677,6 +766,7 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): audio_tokens: Optional[int] = None text_tokens: Optional[int] = None prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None + reasoning_tokens: Optional[int] = None if "cachedContentTokenCount" in completion_response["usageMetadata"]: cached_tokens = completion_response["usageMetadata"][ "cachedContentTokenCount" @@ -687,22 +777,35 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): audio_tokens = detail["tokenCount"] elif detail["modality"] == "TEXT": text_tokens = detail["tokenCount"] - + if "thoughtsTokenCount" in completion_response["usageMetadata"]: + reasoning_tokens = completion_response["usageMetadata"][ + "thoughtsTokenCount" + ] prompt_tokens_details = PromptTokensDetailsWrapper( cached_tokens=cached_tokens, audio_tokens=audio_tokens, text_tokens=text_tokens, ) + + completion_tokens = completion_response["usageMetadata"].get( + "candidatesTokenCount", 0 + ) + if ( + not self.is_candidate_token_count_inclusive( + completion_response["usageMetadata"] + ) + and reasoning_tokens + ): + completion_tokens = reasoning_tokens + completion_tokens ## GET USAGE ## usage = Usage( prompt_tokens=completion_response["usageMetadata"].get( "promptTokenCount", 0 ), - completion_tokens=completion_response["usageMetadata"].get( - "candidatesTokenCount", 0 - ), + completion_tokens=completion_tokens, total_tokens=completion_response["usageMetadata"].get("totalTokenCount", 0), prompt_tokens_details=prompt_tokens_details, + reasoning_tokens=reasoning_tokens, ) return usage @@ -731,11 +834,16 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): citation_metadata.append(candidate["citationMetadata"]) if "parts" in candidate["content"]: - chat_completion_message[ - "content" - ] = VertexGeminiConfig().get_assistant_content_message( + ( + content, + reasoning_content, + ) = VertexGeminiConfig().get_assistant_content_message( parts=candidate["content"]["parts"] ) + if content is not None: + chat_completion_message["content"] = content + if reasoning_content is not None: + chat_completion_message["reasoning_content"] = reasoning_content functions, tools = self._transform_parts( parts=candidate["content"]["parts"], diff --git a/litellm/llms/watsonx/common_utils.py b/litellm/llms/watsonx/common_utils.py index e13e015add..d6f296c608 100644 --- a/litellm/llms/watsonx/common_utils.py +++ b/litellm/llms/watsonx/common_utils.py @@ -38,7 +38,7 @@ def generate_iam_token(api_key=None, **params) -> str: headers = {} headers["Content-Type"] = "application/x-www-form-urlencoded" if api_key is None: - api_key = get_secret_str("WX_API_KEY") or get_secret_str("WATSONX_API_KEY") + api_key = get_secret_str("WX_API_KEY") or get_secret_str("WATSONX_API_KEY") or get_secret_str("WATSONX_APIKEY") if api_key is None: raise ValueError("API key is required") headers["Accept"] = "application/json" diff --git a/litellm/main.py b/litellm/main.py index 1dffb87f43..de0716fd96 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -182,6 +182,7 @@ from .types.llms.openai import ( ChatCompletionPredictionContentParam, ChatCompletionUserMessage, HttpxBinaryResponseContent, + ImageGenerationRequestQuality, ) from .types.utils import ( LITELLM_IMAGE_VARIATION_PROVIDERS, @@ -1435,6 +1436,7 @@ def completion( # type: ignore # noqa: PLR0915 custom_llm_provider=custom_llm_provider, encoding=encoding, stream=stream, + provider_config=provider_config, ) except Exception as e: ## LOGGING - log the original exception returned @@ -1596,6 +1598,37 @@ def completion( # type: ignore # noqa: PLR0915 additional_args={"headers": headers}, ) response = _response + elif custom_llm_provider == "fireworks_ai": + ## COMPLETION CALL + try: + response = base_llm_http_handler.completion( + model=model, + messages=messages, + headers=headers, + model_response=model_response, + api_key=api_key, + api_base=api_base, + acompletion=acompletion, + logging_obj=logging, + optional_params=optional_params, + litellm_params=litellm_params, + timeout=timeout, # type: ignore + client=client, + custom_llm_provider=custom_llm_provider, + encoding=encoding, + stream=stream, + provider_config=provider_config, + ) + except Exception as e: + ## LOGGING - log the original exception returned + logging.post_call( + input=messages, + api_key=api_key, + original_response=str(e), + additional_args={"headers": headers}, + ) + raise e + elif custom_llm_provider == "groq": api_base = ( api_base # for deepinfra/perplexity/anyscale/groq/friendliai we check in get_llm_provider and pass in the api base from there @@ -2656,9 +2689,9 @@ def completion( # type: ignore # noqa: PLR0915 "aws_region_name" not in optional_params or optional_params["aws_region_name"] is None ): - optional_params[ - "aws_region_name" - ] = aws_bedrock_client.meta.region_name + optional_params["aws_region_name"] = ( + aws_bedrock_client.meta.region_name + ) bedrock_route = BedrockModelInfo.get_bedrock_route(model) if bedrock_route == "converse": @@ -3852,6 +3885,21 @@ def embedding( # noqa: PLR0915 aembedding=aembedding, litellm_params={}, ) + elif custom_llm_provider == "infinity": + response = base_llm_http_handler.embedding( + model=model, + input=input, + custom_llm_provider=custom_llm_provider, + api_base=api_base, + api_key=api_key, + logging_obj=logging, + timeout=timeout, + model_response=EmbeddingResponse(), + optional_params=optional_params, + client=client, + aembedding=aembedding, + litellm_params={}, + ) elif custom_llm_provider == "watsonx": credentials = IBMWatsonXMixin.get_watsonx_credentials( optional_params=optional_params, api_key=api_key, api_base=api_base @@ -4365,9 +4413,9 @@ def adapter_completion( new_kwargs = translation_obj.translate_completion_input_params(kwargs=kwargs) response: Union[ModelResponse, CustomStreamWrapper] = completion(**new_kwargs) # type: ignore - translated_response: Optional[ - Union[BaseModel, AdapterCompletionStreamWrapper] - ] = None + translated_response: Optional[Union[BaseModel, AdapterCompletionStreamWrapper]] = ( + None + ) if isinstance(response, ModelResponse): translated_response = translation_obj.translate_completion_output_params( response=response @@ -4520,7 +4568,7 @@ def image_generation( # noqa: PLR0915 prompt: str, model: Optional[str] = None, n: Optional[int] = None, - quality: Optional[str] = None, + quality: Optional[Union[str, ImageGenerationRequestQuality]] = None, response_format: Optional[str] = None, size: Optional[str] = None, style: Optional[str] = None, @@ -5787,9 +5835,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(content_chunks) > 0: - response["choices"][0]["message"][ - "content" - ] = processor.get_combined_content(content_chunks) + response["choices"][0]["message"]["content"] = ( + processor.get_combined_content(content_chunks) + ) reasoning_chunks = [ chunk @@ -5800,9 +5848,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(reasoning_chunks) > 0: - response["choices"][0]["message"][ - "reasoning_content" - ] = processor.get_combined_reasoning_content(reasoning_chunks) + response["choices"][0]["message"]["reasoning_content"] = ( + processor.get_combined_reasoning_content(reasoning_chunks) + ) audio_chunks = [ chunk diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 9918743bc9..55052761c7 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -5,6 +5,7 @@ "max_output_tokens": "max output tokens, if the provider specifies it. if not default to max_tokens", "input_cost_per_token": 0.0000, "output_cost_per_token": 0.000, + "output_cost_per_reasoning_token": 0.000, "litellm_provider": "one of https://docs.litellm.ai/docs/providers", "mode": "one of: chat, embedding, completion, image_generation, audio_transcription, audio_speech, image_generation, moderation, rerank", "supports_function_calling": true, @@ -1436,6 +1437,76 @@ "output_cost_per_pixel": 0.0, "litellm_provider": "openai" }, + "gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0490417e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.59263611e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0172526e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.58945719e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0172526e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.58945719e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, "gpt-4o-transcribe": { "mode": "audio_transcription", "input_cost_per_token": 0.0000025, @@ -1471,6 +1542,294 @@ "litellm_provider": "openai", "supported_endpoints": ["/v1/audio/speech"] }, + "azure/computer-use-preview": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000012, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_reasoning": true + }, + "azure/gpt-4o-audio-preview-2024-12-17": { + "max_tokens": 16384, + "max_input_tokens": 128000, + "max_output_tokens": 16384, + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.00004, + "output_cost_per_token": 0.00001, + "output_cost_per_audio_token": 0.00008, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions"], + "supported_modalities": ["text", "audio"], + "supported_output_modalities": ["text", "audio"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": false, + "supports_vision": false, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_reasoning": false + }, + "azure/gpt-4o-mini-audio-preview-2024-12-17": { + "max_tokens": 16384, + "max_input_tokens": 128000, + "max_output_tokens": 16384, + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.00004, + "output_cost_per_token": 0.00001, + "output_cost_per_audio_token": 0.00008, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions"], + "supported_modalities": ["text", "audio"], + "supported_output_modalities": ["text", "audio"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": false, + "supports_vision": false, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_reasoning": false + }, + "azure/gpt-4.1": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 2e-6, + "output_cost_per_token": 8e-6, + "input_cost_per_token_batches": 1e-6, + "output_cost_per_token_batches": 4e-6, + "cache_read_input_token_cost": 0.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 30e-3, + "search_context_size_medium": 35e-3, + "search_context_size_high": 50e-3 + } + }, + "azure/gpt-4.1-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 2e-6, + "output_cost_per_token": 8e-6, + "input_cost_per_token_batches": 1e-6, + "output_cost_per_token_batches": 4e-6, + "cache_read_input_token_cost": 0.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 30e-3, + "search_context_size_medium": 35e-3, + "search_context_size_high": 50e-3 + } + }, + "azure/gpt-4.1-mini": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.4e-6, + "output_cost_per_token": 1.6e-6, + "input_cost_per_token_batches": 0.2e-6, + "output_cost_per_token_batches": 0.8e-6, + "cache_read_input_token_cost": 0.1e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 25e-3, + "search_context_size_medium": 27.5e-3, + "search_context_size_high": 30e-3 + } + }, + "azure/gpt-4.1-mini-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.4e-6, + "output_cost_per_token": 1.6e-6, + "input_cost_per_token_batches": 0.2e-6, + "output_cost_per_token_batches": 0.8e-6, + "cache_read_input_token_cost": 0.1e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 25e-3, + "search_context_size_medium": 27.5e-3, + "search_context_size_high": 30e-3 + } + }, + "azure/gpt-4.1-nano": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.1e-6, + "output_cost_per_token": 0.4e-6, + "input_cost_per_token_batches": 0.05e-6, + "output_cost_per_token_batches": 0.2e-6, + "cache_read_input_token_cost": 0.025e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true + }, + "azure/gpt-4.1-nano-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.1e-6, + "output_cost_per_token": 0.4e-6, + "input_cost_per_token_batches": 0.05e-6, + "output_cost_per_token_batches": 0.2e-6, + "cache_read_input_token_cost": 0.025e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true + }, + "azure/o3": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1e-5, + "output_cost_per_token": 4e-5, + "cache_read_input_token_cost": 2.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "azure/o3-2025-04-16": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1e-5, + "output_cost_per_token": 4e-5, + "cache_read_input_token_cost": 2.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "azure/o4-mini": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1.1e-6, + "output_cost_per_token": 4.4e-6, + "cache_read_input_token_cost": 2.75e-7, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "azure/gpt-4o-mini-realtime-preview-2024-12-17": { "max_tokens": 4096, "max_input_tokens": 128000, @@ -1647,6 +2006,23 @@ "supports_system_messages": true, "supports_tool_choice": true }, + "azure/o4-mini-2025-04-16": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1.1e-6, + "output_cost_per_token": 4.4e-6, + "cache_read_input_token_cost": 2.75e-7, + "litellm_provider": "azure", + "mode": "chat", + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "azure/o3-mini-2025-01-31": { "max_tokens": 100000, "max_input_tokens": 200000, @@ -5093,6 +5469,64 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini/gemini-2.5-flash-preview-04-17": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 1e-6, + "input_cost_per_token": 0.15e-6, + "output_cost_per_token": 0.6e-6, + "output_cost_per_reasoning_token": 3.5e-6, + "litellm_provider": "gemini", + "mode": "chat", + "rpm": 10, + "tpm": 250000, + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_audio_output": false, + "supports_tool_choice": true, + "supported_endpoints": ["/v1/chat/completions", "/v1/completions"], + "supported_modalities": ["text", "image", "audio", "video"], + "supported_output_modalities": ["text"], + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview" + }, + "gemini-2.5-flash-preview-04-17": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 1e-6, + "input_cost_per_token": 0.15e-6, + "output_cost_per_token": 0.6e-6, + "output_cost_per_reasoning_token": 3.5e-6, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "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-preview" + }, "gemini-2.0-flash": { "max_tokens": 8192, "max_input_tokens": 1048576, @@ -5167,6 +5601,35 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini-2.5-pro-preview-03-25": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 0.00000125, + "input_cost_per_token": 0.00000125, + "input_cost_per_token_above_200k_tokens": 0.0000025, + "output_cost_per_token": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.000015, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "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-preview" + }, "gemini/gemini-2.0-pro-exp-02-05": { "max_tokens": 8192, "max_input_tokens": 2097152, diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/117-87ec698bfca6820e.js b/litellm/proxy/_experimental/out/_next/static/chunks/117-1c5bfc45bfc4237d.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/chunks/117-87ec698bfca6820e.js rename to litellm/proxy/_experimental/out/_next/static/chunks/117-1c5bfc45bfc4237d.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/250-7b7f46d48724f856.js b/litellm/proxy/_experimental/out/_next/static/chunks/250-7b7f46d48724f856.js deleted file mode 100644 index 31bb67f8c6..0000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/250-7b7f46d48724f856.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$I:function(){return K},AZ:function(){return D},Au:function(){return em},BL:function(){return eU},Br:function(){return b},E9:function(){return eZ},EB:function(){return te},EG:function(){return eX},EY:function(){return eK},Eb:function(){return C},FC:function(){return ei},Gh:function(){return eO},H1:function(){return v},H2:function(){return n},Hx:function(){return eg},I1:function(){return j},It:function(){return x},J$:function(){return ea},K8:function(){return d},K_:function(){return eY},LY:function(){return eI},Lp:function(){return eG},N3:function(){return eN},N8:function(){return ee},NL:function(){return e1},NV:function(){return f},Nc:function(){return ex},O3:function(){return eV},OD:function(){return e_},OU:function(){return eh},Of:function(){return S},Og:function(){return y},Ov:function(){return E},PT:function(){return X},Qg:function(){return eS},RQ:function(){return _},Rg:function(){return Q},Sb:function(){return eJ},So:function(){return et},TF:function(){return ta},Tj:function(){return e$},UM:function(){return e7},VA:function(){return G},Vt:function(){return eD},W_:function(){return V},X:function(){return er},XO:function(){return k},Xd:function(){return eT},Xm:function(){return F},YU:function(){return eL},Yo:function(){return J},Z9:function(){return R},Zr:function(){return m},a6:function(){return O},aC:function(){return to},ao:function(){return eq},b1:function(){return ed},cq:function(){return A},cu:function(){return eP},eH:function(){return Y},eZ:function(){return eF},fE:function(){return e8},fP:function(){return W},g:function(){return eQ},gX:function(){return eb},h3:function(){return es},hT:function(){return ej},hy:function(){return u},ix:function(){return H},j2:function(){return en},jA:function(){return eH},jE:function(){return ez},kK:function(){return p},kn:function(){return q},lP:function(){return h},lU:function(){return e2},lg:function(){return eE},mC:function(){return e6},mR:function(){return eo},mY:function(){return e5},m_:function(){return U},mp:function(){return eM},n$:function(){return ey},n9:function(){return e9},nd:function(){return e3},o6:function(){return $},oC:function(){return eC},ol:function(){return z},pf:function(){return eR},qI:function(){return g},qk:function(){return e0},qm:function(){return w},r1:function(){return tt},r6:function(){return B},rs:function(){return N},s0:function(){return L},sN:function(){return ev},t$:function(){return P},t0:function(){return ek},t3:function(){return eW},tB:function(){return e4},tN:function(){return el},u5:function(){return ec},um:function(){return eB},v9:function(){return ef},vh:function(){return eA},wX:function(){return T},wd:function(){return ew},xA:function(){return eu},xX:function(){return I},zg:function(){return ep}});var a=o(20347),r=o(41021);let n=null;console.log=function(){};let c=0,s=e=>new Promise(t=>setTimeout(t,e)),i=async e=>{let t=Date.now();t-c>6e4?(e.includes("Authentication Error - Expired Key")&&(r.ZP.info("UI Session Expired. Logging out."),c=t,await s(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),c=t):console.log("Error suppressed to prevent spam:",e)},l="Authorization";function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),l=e}let h=async()=>{let e=n?"".concat(n,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},w=async e=>{try{let t=n?"".concat(n,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}}),a=await o.json();return console.log("received litellm model cost data: ".concat(a)),a}catch(e){throw console.error("Failed to get model cost map:",e),e}},p=async(e,t)=>{try{let o=n?"".concat(n,"/model/new"):"/model/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text()||"Network response was not ok";throw r.ZP.error(e),Error(e)}let c=await a.json();return console.log("API Response:",c),r.ZP.destroy(),r.ZP.success("Model ".concat(t.model_name," created successfully"),2),c}catch(e){throw console.error("Failed to create key:",e),e}},u=async e=>{try{let t=n?"".concat(n,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){console.error("Failed to get model settings:",e)}},y=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=n?"".concat(n,"/model/delete"):"/model/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=n?"".concat(n,"/budget/delete"):"/budget/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/new"):"/budget/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/update"):"/budget/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},k=async(e,t)=>{try{let o=n?"".concat(n,"/invitation/new"):"/invitation/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},_=async e=>{try{let t=n?"".concat(n,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/key/generate"):"/key/generate",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.auto_create_key=!1,o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/user/new"):"/user/new",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=n?"".concat(n,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},C=async(e,t)=>{try{let o=n?"".concat(n,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete user(s):",e),e}},N=async(e,t)=>{try{let o=n?"".concat(n,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},S=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;try{let r=n?"".concat(n,"/user/list"):"/user/list";console.log("in userListCall");let c=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");c.append("user_ids",e)}o&&c.append("page",o.toString()),a&&c.append("page_size",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log("/user/list API Response:",h),h}catch(e){throw console.error("Failed to create key:",e),e}},b=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0;try{let s;if(a){s=n?"".concat(n,"/user/list"):"/user/list";let e=new URLSearchParams;null!=r&&e.append("page",r.toString()),null!=c&&e.append("page_size",c.toString()),s+="?".concat(e.toString())}else s=n?"".concat(n,"/user/info"):"/user/info","Admin"===o||"Admin Viewer"===o||t&&(s+="?user_id=".concat(t));console.log("Requesting user data from:",s);let d=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log("API Response:",h),h}catch(e){throw console.error("Failed to fetch user data:",e),e}},F=async(e,t)=>{try{let o=n?"".concat(n,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},x=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let a=n?"".concat(n,"/team/list"):"/team/list";console.log("in teamInfoCall");let r=new URLSearchParams;o&&r.append("user_id",o.toString()),t&&r.append("organization_id",t.toString());let c=r.toString();c&&(a+="?".concat(c));let s=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},O=async e=>{try{let t=n?"".concat(n,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/team/available_teams API Response:",a),a}catch(e){throw e}},B=async e=>{try{let t=n?"".concat(n,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{let o=n?"".concat(n,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/organization/new"):"/organization/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=n?"".concat(n,"/organization/update"):"/organization/update",a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t)=>{try{let o=n?"".concat(n,"/organization/delete"):"/organization/delete",a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Error deleting organization: ".concat(e))}return await a.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async(e,t)=>{try{let o=n?"".concat(n,"/utils/transform_request"):"/utils/transform_request",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},I=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;try{let r=n?"".concat(n,"/user/daily/activity"):"/user/daily/activity",c=new URLSearchParams;c.append("start_date",t.toISOString()),c.append("end_date",o.toISOString()),c.append("page_size","1000"),c.append("page",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}return await d.json()}catch(e){throw console.error("Failed to create key:",e),e}},R=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/tag/daily/activity"):"/tag/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("tags",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},z=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/team/daily/activity"):"/team/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("team_ids",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},V=async e=>{try{let t=n?"".concat(n,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},U=async(e,t,o,a)=>{let r=n?"".concat(n,"/onboarding/claim_token"):"/onboarding/claim_token";try{let n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:a})});if(!n.ok){let e=await n.text();throw i(e),Error("Network response was not ok")}let c=await n.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},L=async(e,t,o)=>{try{let a=n?"".concat(n,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},M=!1,Z=null,D=async(e,t,o)=>{try{console.log("modelInfoCall:",e,t,o);let c=n?"".concat(n,"/v2/model/info"):"/v2/model/info";a.ZL.includes(o)||(c+="?user_models_only=true");let s=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw e+="error shown=".concat(M),M||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),r.ZP.info(e,10),M=!0,Z&&clearTimeout(Z),Z=setTimeout(()=>{M=!1},1e4)),Error("Network response was not ok")}let i=await s.json();return console.log("modelInfoCall:",i),i}catch(e){throw console.error("Failed to create key:",e),e}},H=async(e,t)=>{try{let o=n?"".concat(n,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");let r=await a.json();return console.log("modelInfoV1Call:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},q=async e=>{try{let t=n?"".concat(n,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("modelHubCall:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},X=async e=>{try{let t=n?"".concat(n,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let a=await o.json();return console.log("getAllowedIPs:",a),a.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},Y=async(e,t)=>{try{let o=n?"".concat(n,"/add/allowed_ip"):"/add/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("addAllowedIP:",r),r}catch(e){throw console.error("Failed to add allowed IP:",e),e}},K=async(e,t)=>{try{let o=n?"".concat(n,"/delete/allowed_ip"):"/delete/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("deleteAllowedIP:",r),r}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},$=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics"):"/model/metrics";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},Q=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(r="".concat(r,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(a));let c=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},W=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},ee=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/exceptions"):"/model/metrics/exceptions";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},et=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",l);try{let t=n?"".concat(n,"/models"):"/models",o=new URLSearchParams;!0===a&&o.append("return_wildcard_routes","True"),r&&o.append("team_id",r.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},eo=async e=>{try{let t=n?"".concat(n,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ea=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/tags"):"/global/spend/tags";t&&o&&(r="".concat(r,"?start_date=").concat(t,"&end_date=").concat(o)),a&&(r+="".concat(r,"&tags=").concat(a.join(","))),console.log("in tagsSpendLogsCall:",r);let c=await fetch("".concat(r),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},er=async e=>{try{let t=n?"".concat(n,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},en=async e=>{try{let t=n?"".concat(n,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t)=>{try{let o=n?"".concat(n,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},es=async(e,t,o,a,r,c,s,d,h)=>{try{let w=n?"".concat(n,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),a&&p.append("request_id",a),r&&p.append("start_date",r),c&&p.append("end_date",c),s&&p.append("page",s.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw i(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},ei=async e=>{try{let t=n?"".concat(n,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},el=async e=>{try{let t=n?"".concat(n,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ed=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:a}):JSON.stringify({startTime:o,endTime:a});let s={method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(r,s);if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},eh=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/provider"):"/global/spend/provider";o&&a&&(r+="?start_date=".concat(o,"&end_date=").concat(a)),t&&(r+="&api_key=".concat(t));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity"):"/global/activity";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ep=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eu=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/model"):"/global/activity/model";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ey=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ef=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},em=async e=>{try{let t=n?"".concat(n,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},eg=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let r=n?"".concat(n,"/health/test_connection"):"/health/test_connection",c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",[l]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let i=await c.json();if(!c.ok||"error"===i.status){if("error"===i.status);else{var a;return{status:"error",message:(null===(a=i.error)||void 0===a?void 0:a.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return i}catch(e){throw console.error("Model connection test error:",e),e}},ek=async(e,t)=>{try{console.log("entering keyInfoV1Call");let o=n?"".concat(n,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(console.log("response",a),!a.ok){let e=await a.text();i(e),r.ZP.error("Failed to fetch key info - "+e)}let c=await a.json();return console.log("data",c),c}catch(e){throw console.error("Failed to fetch key info:",e),e}},e_=async(e,t,o,a,r,c)=>{try{let s=n?"".concat(n,"/key/list"):"/key/list";console.log("in keyListCall");let d=new URLSearchParams;o&&d.append("team_id",o.toString()),t&&d.append("organization_id",t.toString()),a&&d.append("key_alias",a),r&&d.append("page",r.toString()),c&&d.append("size",c.toString()),d.append("return_full_object","true"),d.append("include_team_keys","true");let h=d.toString();h&&(s+="?".concat(h));let w=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!w.ok){let e=await w.text();throw i(e),Error("Network response was not ok")}let p=await w.json();return console.log("/team/list API Response:",p),p}catch(e){throw console.error("Failed to create key:",e),e}},eT=async(e,t)=>{try{let o=n?"".concat(n,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to get requested models:",e),e}},eE=async e=>{try{let t=n?"".concat(n,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("response from user/available_role",a),a}catch(e){throw e}},ej=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/team/new"):"/team/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eC=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/credentials"):"/credentials",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eN=async e=>{try{let t=n?"".concat(n,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/credentials API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eS=async(e,t,o)=>{try{let a=n?"".concat(n,"/credentials"):"/credentials";t?a+="/by_name/".concat(t):o&&(a+="/by_model/".concat(o)),console.log("in credentialListCall");let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t)=>{try{let o=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},eF=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let a=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ex=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=n?"".concat(n,"/key/update"):"/key/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update key Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eO=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=n?"".concat(n,"/team/update"):"/team/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t)=>{try{console.log("Form Values in modelUpateCall:",t);let o=n?"".concat(n,"/model/update"):"/model/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update model Response:",r),r}catch(e){throw console.error("Failed to update model:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_add"):"/team/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_update"):"/team/member_update",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eG=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_delete"):"/team/member_delete",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eA=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/organization/member_add"):"/organization/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eJ=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let a=n?"".concat(n,"/organization/member_delete"):"/organization/member_delete",r=await fetch(a,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},eI=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let a=n?"".concat(n,"/organization/member_update"):"/organization/member_update",r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},eR=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let a=n?"".concat(n,"/user/update"):"/user/update",r={...t};null!==o&&(r.user_role=o),r=JSON.stringify(r);let c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:r});if(!c.ok){let e=await c.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let s=await c.json();return console.log("API Response:",s),s}catch(e){throw console.error("Failed to create key:",e),e}},ez=async(e,t)=>{try{let o=n?"".concat(n,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error(e)}let c=await a.json();return r.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eV=async e=>{try{let t=n?"".concat(n,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eU=async(e,t,o)=>{try{let t=n?"".concat(n,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eL=async e=>{try{let t=n?"".concat(n,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eM=async e=>{try{let t=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eZ=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eD=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eH=async(e,t,o)=>{try{let a=n?"".concat(n,"/config/field/update"):"/config/field/update",c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}let s=await c.json();return r.ZP.success("Successfully updated value!"),s}catch(e){throw console.error("Failed to set callbacks:",e),e}},eq=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/delete"):"/config/field/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return r.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eX=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eY=async(e,t)=>{try{let o=n?"".concat(n,"/config/update"):"/config/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eK=async e=>{try{let t=n?"".concat(n,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},e$=async e=>{try{let t=n?"".concat(n,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eQ=async e=>{try{let t=n?"".concat(n,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eW=async e=>{try{let t=n?"".concat(n,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Guardrails list response:",a),a}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},e0=async(e,t,o)=>{try{let a=n?"".concat(n,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",a);let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},e1=async e=>{try{let t=n?"".concat(n,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched SSO settings:",a),a}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},e3=async(e,t)=>{try{let o=n?"".concat(n,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated internal user settings:",c),r.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}},e2=async e=>{try{let t=n?"".concat(n,"/mcp/tools/list"):"/mcp/tools/list";console.log("Fetching MCP tools from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched MCP tools:",a),a}catch(e){throw console.error("Failed to fetch MCP tools:",e),e}},e4=async(e,t,o)=>{try{let a=n?"".concat(n,"/mcp/tools/call"):"/mcp/tools/call";console.log("Calling MCP tool:",t,"with arguments:",o);let r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({name:t,arguments:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("MCP tool call response:",c),c}catch(e){throw console.error("Failed to call MCP tool:",e),e}},e5=async(e,t)=>{try{let o=n?"".concat(n,"/tag/new"):"/tag/new",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error creating tag:",e),e}},e9=async(e,t)=>{try{let o=n?"".concat(n,"/tag/update"):"/tag/update",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error updating tag:",e),e}},e6=async(e,t)=>{try{let o=n?"".concat(n,"/tag/info"):"/tag/info",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({names:t})});if(!a.ok){let e=await a.text();return await i(e),{}}return await a.json()}catch(e){throw console.error("Error getting tag info:",e),e}},e7=async e=>{try{let t=n?"".concat(n,"/tag/list"):"/tag/list",o=await fetch(t,{method:"GET",headers:{Authorization:"Bearer ".concat(e)}});if(!o.ok){let e=await o.text();return await i(e),{}}return await o.json()}catch(e){throw console.error("Error listing tags:",e),e}},e8=async(e,t)=>{try{let o=n?"".concat(n,"/tag/delete"):"/tag/delete",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({name:t})});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error deleting tag:",e),e}},te=async e=>{try{let t=n?"".concat(n,"/get/default_team_settings"):"/get/default_team_settings";console.log("Fetching default team settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched default team settings:",a),a}catch(e){throw console.error("Failed to fetch default team settings:",e),e}},tt=async(e,t)=>{try{let o=n?"".concat(n,"/update/default_team_settings"):"/update/default_team_settings";console.log("Updating default team settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated default team settings:",c),r.ZP.success("Default team settings updated successfully"),c}catch(e){throw console.error("Failed to update default team settings:",e),e}},to=async(e,t)=>{try{let o=n?"".concat(n,"/team/permissions_list?team_id=").concat(t):"/team/permissions_list?team_id=".concat(t),a=await fetch(o,{method:"GET",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("Team permissions response:",r),r}catch(e){throw console.error("Failed to get team permissions:",e),e}},ta=async(e,t,o)=>{try{let a=n?"".concat(n,"/team/permissions_update"):"/team/permissions_update",r=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({team_id:t,team_member_permissions:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Team permissions response:",c),c}catch(e){throw console.error("Failed to update team permissions:",e),e}}},20347:function(e,t,o){o.d(t,{LQ:function(){return n},ZL:function(){return a},lo:function(){return r},tY:function(){return c}});let a=["Admin","Admin Viewer","proxy_admin","proxy_admin_viewer","org_admin"],r=["Internal User","Internal Viewer"],n=["Internal User","Admin"],c=e=>a.includes(e)}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js b/litellm/proxy/_experimental/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js new file mode 100644 index 0000000000..40e2873306 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$D:function(){return eP},$I:function(){return $},AZ:function(){return D},Au:function(){return em},BL:function(){return eL},Br:function(){return b},E9:function(){return eD},EB:function(){return tt},EG:function(){return eY},EY:function(){return eK},Eb:function(){return C},FC:function(){return ei},Gh:function(){return eB},H1:function(){return v},H2:function(){return n},Hx:function(){return ek},I1:function(){return j},It:function(){return x},J$:function(){return ea},K8:function(){return d},K_:function(){return e$},LY:function(){return eR},Lp:function(){return eA},N3:function(){return eS},N8:function(){return ee},NL:function(){return e3},NV:function(){return f},Nc:function(){return eO},O3:function(){return eU},OD:function(){return eT},OU:function(){return eh},Of:function(){return S},Og:function(){return y},Ov:function(){return E},PT:function(){return X},Qg:function(){return eb},RQ:function(){return _},Rg:function(){return Q},Sb:function(){return eI},So:function(){return et},TF:function(){return tr},Tj:function(){return eQ},UM:function(){return e8},VA:function(){return G},Vt:function(){return eH},W_:function(){return V},X:function(){return er},XO:function(){return k},Xd:function(){return eE},Xm:function(){return F},YU:function(){return eM},Yo:function(){return J},Z9:function(){return R},Zr:function(){return m},a6:function(){return O},aC:function(){return ta},ao:function(){return eX},b1:function(){return ed},cq:function(){return A},cu:function(){return ev},e2:function(){return eg},eH:function(){return Y},eZ:function(){return ex},fE:function(){return te},fP:function(){return W},g:function(){return eW},gX:function(){return eF},h3:function(){return es},hT:function(){return eC},hy:function(){return u},ix:function(){return H},j2:function(){return en},jA:function(){return eq},jE:function(){return eV},kK:function(){return p},kn:function(){return q},lP:function(){return h},lU:function(){return e4},lg:function(){return ej},mC:function(){return e7},mR:function(){return eo},mY:function(){return e6},m_:function(){return U},mp:function(){return eZ},n$:function(){return ey},n9:function(){return e9},nd:function(){return e2},o6:function(){return K},oC:function(){return eN},ol:function(){return z},pf:function(){return ez},qI:function(){return g},qk:function(){return e1},qm:function(){return w},r1:function(){return to},r6:function(){return B},rs:function(){return N},s0:function(){return L},sN:function(){return eG},t$:function(){return P},t0:function(){return e_},t3:function(){return e0},tB:function(){return e5},tN:function(){return el},u5:function(){return ec},v9:function(){return ef},vh:function(){return eJ},wX:function(){return T},wd:function(){return ew},xA:function(){return eu},xX:function(){return I},zg:function(){return ep}});var a=o(20347),r=o(41021);let n=null;console.log=function(){};let c=0,s=e=>new Promise(t=>setTimeout(t,e)),i=async e=>{let t=Date.now();t-c>6e4?(e.includes("Authentication Error - Expired Key")&&(r.ZP.info("UI Session Expired. Logging out."),c=t,await s(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),c=t):console.log("Error suppressed to prevent spam:",e)},l="Authorization";function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),l=e}let h=async()=>{let e=n?"".concat(n,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},w=async e=>{try{let t=n?"".concat(n,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}}),a=await o.json();return console.log("received litellm model cost data: ".concat(a)),a}catch(e){throw console.error("Failed to get model cost map:",e),e}},p=async(e,t)=>{try{let o=n?"".concat(n,"/model/new"):"/model/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text()||"Network response was not ok";throw r.ZP.error(e),Error(e)}let c=await a.json();return console.log("API Response:",c),r.ZP.destroy(),r.ZP.success("Model ".concat(t.model_name," created successfully"),2),c}catch(e){throw console.error("Failed to create key:",e),e}},u=async e=>{try{let t=n?"".concat(n,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){console.error("Failed to get model settings:",e)}},y=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=n?"".concat(n,"/model/delete"):"/model/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=n?"".concat(n,"/budget/delete"):"/budget/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/new"):"/budget/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/update"):"/budget/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},k=async(e,t)=>{try{let o=n?"".concat(n,"/invitation/new"):"/invitation/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},_=async e=>{try{let t=n?"".concat(n,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/key/generate"):"/key/generate",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.auto_create_key=!1,o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/user/new"):"/user/new",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=n?"".concat(n,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},C=async(e,t)=>{try{let o=n?"".concat(n,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete user(s):",e),e}},N=async(e,t)=>{try{let o=n?"".concat(n,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},S=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,c=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,s=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,d=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null,h=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null,w=arguments.length>9&&void 0!==arguments[9]?arguments[9]:null;try{let p=n?"".concat(n,"/user/list"):"/user/list";console.log("in userListCall");let u=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");u.append("user_ids",e)}o&&u.append("page",o.toString()),a&&u.append("page_size",a.toString()),r&&u.append("user_email",r),c&&u.append("role",c),s&&u.append("team",s),d&&u.append("sso_user_ids",d),h&&u.append("sort_by",h),w&&u.append("sort_order",w);let y=u.toString();y&&(p+="?".concat(y));let f=await fetch(p,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!f.ok){let e=await f.text();throw i(e),Error("Network response was not ok")}let m=await f.json();return console.log("/user/list API Response:",m),m}catch(e){throw console.error("Failed to create key:",e),e}},b=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0,s=arguments.length>6&&void 0!==arguments[6]&&arguments[6];console.log("userInfoCall: ".concat(t,", ").concat(o,", ").concat(a,", ").concat(r,", ").concat(c,", ").concat(s));try{let d;if(a){d=n?"".concat(n,"/user/list"):"/user/list";let e=new URLSearchParams;null!=r&&e.append("page",r.toString()),null!=c&&e.append("page_size",c.toString()),d+="?".concat(e.toString())}else d=n?"".concat(n,"/user/info"):"/user/info",("Admin"!==o&&"Admin Viewer"!==o||s)&&t&&(d+="?user_id=".concat(t));console.log("Requesting user data from:",d);let h=await fetch(d,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}let w=await h.json();return console.log("API Response:",w),w}catch(e){throw console.error("Failed to fetch user data:",e),e}},F=async(e,t)=>{try{let o=n?"".concat(n,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},x=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let a=n?"".concat(n,"/team/list"):"/team/list";console.log("in teamInfoCall");let r=new URLSearchParams;o&&r.append("user_id",o.toString()),t&&r.append("organization_id",t.toString());let c=r.toString();c&&(a+="?".concat(c));let s=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},O=async e=>{try{let t=n?"".concat(n,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/team/available_teams API Response:",a),a}catch(e){throw e}},B=async e=>{try{let t=n?"".concat(n,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{let o=n?"".concat(n,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/organization/new"):"/organization/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=n?"".concat(n,"/organization/update"):"/organization/update",a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t)=>{try{let o=n?"".concat(n,"/organization/delete"):"/organization/delete",a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Error deleting organization: ".concat(e))}return await a.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async(e,t)=>{try{let o=n?"".concat(n,"/utils/transform_request"):"/utils/transform_request",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},I=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;try{let r=n?"".concat(n,"/user/daily/activity"):"/user/daily/activity",c=new URLSearchParams;c.append("start_date",t.toISOString()),c.append("end_date",o.toISOString()),c.append("page_size","1000"),c.append("page",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}return await d.json()}catch(e){throw console.error("Failed to create key:",e),e}},R=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/tag/daily/activity"):"/tag/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("tags",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},z=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/team/daily/activity"):"/team/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("team_ids",r.join(",")),s.append("exclude_team_ids","litellm-dashboard");let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},V=async e=>{try{let t=n?"".concat(n,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},U=async(e,t,o,a)=>{let r=n?"".concat(n,"/onboarding/claim_token"):"/onboarding/claim_token";try{let n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:a})});if(!n.ok){let e=await n.text();throw i(e),Error("Network response was not ok")}let c=await n.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},L=async(e,t,o)=>{try{let a=n?"".concat(n,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},M=!1,Z=null,D=async(e,t,o)=>{try{console.log("modelInfoCall:",e,t,o);let c=n?"".concat(n,"/v2/model/info"):"/v2/model/info";a.ZL.includes(o)||(c+="?user_models_only=true");let s=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw e+="error shown=".concat(M),M||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),r.ZP.info(e,10),M=!0,Z&&clearTimeout(Z),Z=setTimeout(()=>{M=!1},1e4)),Error("Network response was not ok")}let i=await s.json();return console.log("modelInfoCall:",i),i}catch(e){throw console.error("Failed to create key:",e),e}},H=async(e,t)=>{try{let o=n?"".concat(n,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");let r=await a.json();return console.log("modelInfoV1Call:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},q=async e=>{try{let t=n?"".concat(n,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("modelHubCall:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},X=async e=>{try{let t=n?"".concat(n,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let a=await o.json();return console.log("getAllowedIPs:",a),a.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},Y=async(e,t)=>{try{let o=n?"".concat(n,"/add/allowed_ip"):"/add/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("addAllowedIP:",r),r}catch(e){throw console.error("Failed to add allowed IP:",e),e}},$=async(e,t)=>{try{let o=n?"".concat(n,"/delete/allowed_ip"):"/delete/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("deleteAllowedIP:",r),r}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},K=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics"):"/model/metrics";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},Q=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(r="".concat(r,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(a));let c=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},W=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},ee=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/exceptions"):"/model/metrics/exceptions";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},et=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",l);try{let t=n?"".concat(n,"/models"):"/models",o=new URLSearchParams;!0===a&&o.append("return_wildcard_routes","True"),r&&o.append("team_id",r.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},eo=async e=>{try{let t=n?"".concat(n,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ea=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/tags"):"/global/spend/tags";t&&o&&(r="".concat(r,"?start_date=").concat(t,"&end_date=").concat(o)),a&&(r+="".concat(r,"&tags=").concat(a.join(","))),console.log("in tagsSpendLogsCall:",r);let c=await fetch("".concat(r),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},er=async e=>{try{let t=n?"".concat(n,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},en=async e=>{try{let t=n?"".concat(n,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t)=>{try{let o=n?"".concat(n,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},es=async(e,t,o,a,r,c,s,d,h)=>{try{let w=n?"".concat(n,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),a&&p.append("request_id",a),r&&p.append("start_date",r),c&&p.append("end_date",c),s&&p.append("page",s.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw i(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},ei=async e=>{try{let t=n?"".concat(n,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},el=async e=>{try{let t=n?"".concat(n,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ed=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:a}):JSON.stringify({startTime:o,endTime:a});let s={method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(r,s);if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},eh=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/provider"):"/global/spend/provider";o&&a&&(r+="?start_date=".concat(o,"&end_date=").concat(a)),t&&(r+="&api_key=".concat(t));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity"):"/global/activity";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ep=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eu=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/model"):"/global/activity/model";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ey=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ef=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},em=async e=>{try{let t=n?"".concat(n,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},eg=async(e,t)=>{try{let o=n?"".concat(n,"/v2/key/info"):"/v2/key/info",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:t})});if(!a.ok){let e=await a.text();if(e.includes("Invalid proxy server token passed"))throw Error("Invalid proxy server token passed");throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ek=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let r=n?"".concat(n,"/health/test_connection"):"/health/test_connection",c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",[l]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let i=await c.json();if(!c.ok||"error"===i.status){if("error"===i.status);else{var a;return{status:"error",message:(null===(a=i.error)||void 0===a?void 0:a.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return i}catch(e){throw console.error("Model connection test error:",e),e}},e_=async(e,t)=>{try{console.log("entering keyInfoV1Call");let o=n?"".concat(n,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(console.log("response",a),!a.ok){let e=await a.text();i(e),r.ZP.error("Failed to fetch key info - "+e)}let c=await a.json();return console.log("data",c),c}catch(e){throw console.error("Failed to fetch key info:",e),e}},eT=async(e,t,o,a,r,c)=>{try{let s=n?"".concat(n,"/key/list"):"/key/list";console.log("in keyListCall");let d=new URLSearchParams;o&&d.append("team_id",o.toString()),t&&d.append("organization_id",t.toString()),a&&d.append("key_alias",a),r&&d.append("page",r.toString()),c&&d.append("size",c.toString()),d.append("return_full_object","true"),d.append("include_team_keys","true");let h=d.toString();h&&(s+="?".concat(h));let w=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!w.ok){let e=await w.text();throw i(e),Error("Network response was not ok")}let p=await w.json();return console.log("/team/list API Response:",p),p}catch(e){throw console.error("Failed to create key:",e),e}},eE=async(e,t)=>{try{let o=n?"".concat(n,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to get requested models:",e),e}},ej=async e=>{try{let t=n?"".concat(n,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("response from user/available_role",a),a}catch(e){throw e}},eC=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/team/new"):"/team/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eN=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/credentials"):"/credentials",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eS=async e=>{try{let t=n?"".concat(n,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/credentials API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t,o)=>{try{let a=n?"".concat(n,"/credentials"):"/credentials";t?a+="/by_name/".concat(t):o&&(a+="/by_model/".concat(o)),console.log("in credentialListCall");let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eF=async(e,t)=>{try{let o=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},ex=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let a=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eO=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=n?"".concat(n,"/key/update"):"/key/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update key Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=n?"".concat(n,"/team/update"):"/team/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in modelUpateCall:",t);let a=n?"".concat(n,"/model/").concat(o,"/update"):"/model/".concat(o,"/update"),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("Update model Response:",c),c}catch(e){throw console.error("Failed to update model:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_add"):"/team/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eG=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_update"):"/team/member_update",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eA=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_delete"):"/team/member_delete",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eJ=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/organization/member_add"):"/organization/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eI=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let a=n?"".concat(n,"/organization/member_delete"):"/organization/member_delete",r=await fetch(a,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},eR=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let a=n?"".concat(n,"/organization/member_update"):"/organization/member_update",r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},ez=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let a=n?"".concat(n,"/user/update"):"/user/update",r={...t};null!==o&&(r.user_role=o),r=JSON.stringify(r);let c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:r});if(!c.ok){let e=await c.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let s=await c.json();return console.log("API Response:",s),s}catch(e){throw console.error("Failed to create key:",e),e}},eV=async(e,t)=>{try{let o=n?"".concat(n,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error(e)}let c=await a.json();return r.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eU=async e=>{try{let t=n?"".concat(n,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eL=async(e,t,o)=>{try{let t=n?"".concat(n,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eM=async e=>{try{let t=n?"".concat(n,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eZ=async e=>{try{let t=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eD=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eH=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eq=async(e,t,o)=>{try{let a=n?"".concat(n,"/config/field/update"):"/config/field/update",c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}let s=await c.json();return r.ZP.success("Successfully updated value!"),s}catch(e){throw console.error("Failed to set callbacks:",e),e}},eX=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/delete"):"/config/field/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return r.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eY=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},e$=async(e,t)=>{try{let o=n?"".concat(n,"/config/update"):"/config/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eK=async e=>{try{let t=n?"".concat(n,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},eQ=async e=>{try{let t=n?"".concat(n,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eW=async e=>{try{let t=n?"".concat(n,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},e0=async e=>{try{let t=n?"".concat(n,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Guardrails list response:",a),a}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},e1=async(e,t,o)=>{try{let a=n?"".concat(n,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",a);let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},e3=async e=>{try{let t=n?"".concat(n,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched SSO settings:",a),a}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},e2=async(e,t)=>{try{let o=n?"".concat(n,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated internal user settings:",c),r.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}},e4=async e=>{try{let t=n?"".concat(n,"/mcp/tools/list"):"/mcp/tools/list";console.log("Fetching MCP tools from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched MCP tools:",a),a}catch(e){throw console.error("Failed to fetch MCP tools:",e),e}},e5=async(e,t,o)=>{try{let a=n?"".concat(n,"/mcp/tools/call"):"/mcp/tools/call";console.log("Calling MCP tool:",t,"with arguments:",o);let r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({name:t,arguments:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("MCP tool call response:",c),c}catch(e){throw console.error("Failed to call MCP tool:",e),e}},e6=async(e,t)=>{try{let o=n?"".concat(n,"/tag/new"):"/tag/new",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error creating tag:",e),e}},e9=async(e,t)=>{try{let o=n?"".concat(n,"/tag/update"):"/tag/update",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error updating tag:",e),e}},e7=async(e,t)=>{try{let o=n?"".concat(n,"/tag/info"):"/tag/info",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({names:t})});if(!a.ok){let e=await a.text();return await i(e),{}}return await a.json()}catch(e){throw console.error("Error getting tag info:",e),e}},e8=async e=>{try{let t=n?"".concat(n,"/tag/list"):"/tag/list",o=await fetch(t,{method:"GET",headers:{Authorization:"Bearer ".concat(e)}});if(!o.ok){let e=await o.text();return await i(e),{}}return await o.json()}catch(e){throw console.error("Error listing tags:",e),e}},te=async(e,t)=>{try{let o=n?"".concat(n,"/tag/delete"):"/tag/delete",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({name:t})});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error deleting tag:",e),e}},tt=async e=>{try{let t=n?"".concat(n,"/get/default_team_settings"):"/get/default_team_settings";console.log("Fetching default team settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched default team settings:",a),a}catch(e){throw console.error("Failed to fetch default team settings:",e),e}},to=async(e,t)=>{try{let o=n?"".concat(n,"/update/default_team_settings"):"/update/default_team_settings";console.log("Updating default team settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated default team settings:",c),r.ZP.success("Default team settings updated successfully"),c}catch(e){throw console.error("Failed to update default team settings:",e),e}},ta=async(e,t)=>{try{let o=n?"".concat(n,"/team/permissions_list?team_id=").concat(t):"/team/permissions_list?team_id=".concat(t),a=await fetch(o,{method:"GET",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("Team permissions response:",r),r}catch(e){throw console.error("Failed to get team permissions:",e),e}},tr=async(e,t,o)=>{try{let a=n?"".concat(n,"/team/permissions_update"):"/team/permissions_update",r=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({team_id:t,team_member_permissions:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Team permissions response:",c),c}catch(e){throw console.error("Failed to update team permissions:",e),e}}},20347:function(e,t,o){o.d(t,{LQ:function(){return n},ZL:function(){return a},lo:function(){return r},tY:function(){return c}});let a=["Admin","Admin Viewer","proxy_admin","proxy_admin_viewer","org_admin"],r=["Internal User","Internal Viewer"],n=["Internal User","Admin"],c=e=>a.includes(e)}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/261-57d48f76eec1e568.js b/litellm/proxy/_experimental/out/_next/static/chunks/261-92d8946249b3296e.js similarity index 99% rename from litellm/proxy/_experimental/out/_next/static/chunks/261-57d48f76eec1e568.js rename to litellm/proxy/_experimental/out/_next/static/chunks/261-92d8946249b3296e.js index 44e5f1be73..e87c443917 100644 --- a/litellm/proxy/_experimental/out/_next/static/chunks/261-57d48f76eec1e568.js +++ b/litellm/proxy/_experimental/out/_next/static/chunks/261-92d8946249b3296e.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[261],{23639:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"}}]},name:"copy",theme:"outlined"},o=n(55015),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},77565:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"},o=n(55015),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},12485:function(e,t,n){"use strict";n.d(t,{Z:function(){return p}});var a=n(5853),r=n(31492),i=n(26898),o=n(65954),s=n(1153),l=n(2265),c=n(35242),u=n(42698);n(64016),n(8710),n(33232);let d=(0,s.fn)("Tab"),p=l.forwardRef((e,t)=>{let{icon:n,className:p,children:g}=e,m=(0,a._T)(e,["icon","className","children"]),b=(0,l.useContext)(c.O),f=(0,l.useContext)(u.Z);return l.createElement(r.O,Object.assign({ref:t,className:(0,o.q)(d("root"),"flex whitespace-nowrap truncate max-w-xs outline-none focus:ring-0 text-tremor-default transition duration-100",f?(0,s.bM)(f,i.K.text).selectTextColor:"solid"===b?"ui-selected:text-tremor-content-emphasis dark:ui-selected:text-dark-tremor-content-emphasis":"ui-selected:text-tremor-brand dark:ui-selected:text-dark-tremor-brand",function(e,t){switch(e){case"line":return(0,o.q)("ui-selected:border-b-2 hover:border-b-2 border-transparent transition duration-100 -mb-px px-2 py-2","hover:border-tremor-content hover:text-tremor-content-emphasis text-tremor-content","dark:hover:border-dark-tremor-content-emphasis dark:hover:text-dark-tremor-content-emphasis dark:text-dark-tremor-content",t?(0,s.bM)(t,i.K.border).selectBorderColor:"ui-selected:border-tremor-brand dark:ui-selected:border-dark-tremor-brand");case"solid":return(0,o.q)("border-transparent border rounded-tremor-small px-2.5 py-1","ui-selected:border-tremor-border ui-selected:bg-tremor-background ui-selected:shadow-tremor-input hover:text-tremor-content-emphasis ui-selected:text-tremor-brand","dark:ui-selected:border-dark-tremor-border dark:ui-selected:bg-dark-tremor-background dark:ui-selected:shadow-dark-tremor-input dark:hover:text-dark-tremor-content-emphasis dark:ui-selected:text-dark-tremor-brand",t?(0,s.bM)(t,i.K.text).selectTextColor:"text-tremor-content dark:text-dark-tremor-content")}}(b,f),p)},m),n?l.createElement(n,{className:(0,o.q)(d("icon"),"flex-none h-5 w-5",g?"mr-2":"")}):null,g?l.createElement("span",null,g):null)});p.displayName="Tab"},18135:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var a=n(5853),r=n(31492),i=n(65954),o=n(1153),s=n(2265);let l=(0,o.fn)("TabGroup"),c=s.forwardRef((e,t)=>{let{defaultIndex:n,index:o,onIndexChange:c,children:u,className:d}=e,p=(0,a._T)(e,["defaultIndex","index","onIndexChange","children","className"]);return s.createElement(r.O.Group,Object.assign({as:"div",ref:t,defaultIndex:n,selectedIndex:o,onChange:c,className:(0,i.q)(l("root"),"w-full",d)},p),u)});c.displayName="TabGroup"},35242:function(e,t,n){"use strict";n.d(t,{O:function(){return c},Z:function(){return d}});var a=n(5853),r=n(2265),i=n(42698);n(64016),n(8710),n(33232);var o=n(31492),s=n(65954);let l=(0,n(1153).fn)("TabList"),c=(0,r.createContext)("line"),u={line:(0,s.q)("flex border-b space-x-4","border-tremor-border","dark:border-dark-tremor-border"),solid:(0,s.q)("inline-flex p-0.5 rounded-tremor-default space-x-1.5","bg-tremor-background-subtle","dark:bg-dark-tremor-background-subtle")},d=r.forwardRef((e,t)=>{let{color:n,variant:d="line",children:p,className:g}=e,m=(0,a._T)(e,["color","variant","children","className"]);return r.createElement(o.O.List,Object.assign({ref:t,className:(0,s.q)(l("root"),"justify-start overflow-x-clip",u[d],g)},m),r.createElement(c.Provider,{value:d},r.createElement(i.Z.Provider,{value:n},p)))});d.displayName="TabList"},29706:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var a=n(5853);n(42698);var r=n(64016);n(8710);var i=n(33232),o=n(65954),s=n(1153),l=n(2265);let c=(0,s.fn)("TabPanel"),u=l.forwardRef((e,t)=>{let{children:n,className:s}=e,u=(0,a._T)(e,["children","className"]),{selectedValue:d}=(0,l.useContext)(i.Z),p=d===(0,l.useContext)(r.Z);return l.createElement("div",Object.assign({ref:t,className:(0,o.q)(c("root"),"w-full mt-2",p?"":"hidden",s),"aria-selected":p?"true":"false"},u),n)});u.displayName="TabPanel"},77991:function(e,t,n){"use strict";n.d(t,{Z:function(){return d}});var a=n(5853),r=n(31492);n(42698);var i=n(64016);n(8710);var o=n(33232),s=n(65954),l=n(1153),c=n(2265);let u=(0,l.fn)("TabPanels"),d=c.forwardRef((e,t)=>{let{children:n,className:l}=e,d=(0,a._T)(e,["children","className"]);return c.createElement(r.O.Panels,Object.assign({as:"div",ref:t,className:(0,s.q)(u("root"),"w-full",l)},d),e=>{let{selectedIndex:t}=e;return c.createElement(o.Z.Provider,{value:{selectedValue:t}},c.Children.map(n,(e,t)=>c.createElement(i.Z.Provider,{value:t},e)))})});d.displayName="TabPanels"},42698:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var a=n(2265),r=n(7084);n(65954);let i=(0,a.createContext)(r.fr.Blue)},64016:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(0)},8710:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(void 0)},33232:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},93942:function(e,t,n){"use strict";n.d(t,{i:function(){return s}});var a=n(2265),r=n(50506),i=n(13959),o=n(71744);function s(e){return t=>a.createElement(i.ZP,{theme:{token:{motion:!1,zIndexPopupBase:0}}},a.createElement(e,Object.assign({},t)))}t.Z=(e,t,n,i)=>s(s=>{let{prefixCls:l,style:c}=s,u=a.useRef(null),[d,p]=a.useState(0),[g,m]=a.useState(0),[b,f]=(0,r.Z)(!1,{value:s.open}),{getPrefixCls:E}=a.useContext(o.E_),h=E(t||"select",l);a.useEffect(()=>{if(f(!0),"undefined"!=typeof ResizeObserver){let e=new ResizeObserver(e=>{let t=e[0].target;p(t.offsetHeight+8),m(t.offsetWidth)}),t=setInterval(()=>{var a;let r=n?".".concat(n(h)):".".concat(h,"-dropdown"),i=null===(a=u.current)||void 0===a?void 0:a.querySelector(r);i&&(clearInterval(t),e.observe(i))},10);return()=>{clearInterval(t),e.disconnect()}}},[]);let S=Object.assign(Object.assign({},s),{style:Object.assign(Object.assign({},c),{margin:0}),open:b,visible:b,getPopupContainer:()=>u.current});return i&&(S=i(S)),a.createElement("div",{ref:u,style:{paddingBottom:d,position:"relative",minWidth:g}},a.createElement(e,Object.assign({},S)))})},51369:function(e,t,n){"use strict";let a;n.d(t,{Z:function(){return eY}});var r=n(83145),i=n(2265),o=n(18404),s=n(71744),l=n(13959),c=n(8900),u=n(39725),d=n(54537),p=n(55726),g=n(36760),m=n.n(g),b=n(62236),f=n(68710),E=n(55274),h=n(29961),S=n(69819),y=n(73002),T=n(51248),A=e=>{let{type:t,children:n,prefixCls:a,buttonProps:r,close:o,autoFocus:s,emitEvent:l,isSilent:c,quitOnNullishReturnValue:u,actionFn:d}=e,p=i.useRef(!1),g=i.useRef(null),[m,b]=(0,S.Z)(!1),f=function(){null==o||o.apply(void 0,arguments)};i.useEffect(()=>{let e=null;return s&&(e=setTimeout(()=>{var e;null===(e=g.current)||void 0===e||e.focus()})),()=>{e&&clearTimeout(e)}},[]);let E=e=>{e&&e.then&&(b(!0),e.then(function(){b(!1,!0),f.apply(void 0,arguments),p.current=!1},e=>{if(b(!1,!0),p.current=!1,null==c||!c())return Promise.reject(e)}))};return i.createElement(y.ZP,Object.assign({},(0,T.nx)(t),{onClick:e=>{let t;if(!p.current){if(p.current=!0,!d){f();return}if(l){var n;if(t=d(e),u&&!((n=t)&&n.then)){p.current=!1,f(e);return}}else if(d.length)t=d(o),p.current=!1;else if(!(t=d())){f();return}E(t)}},loading:m,prefixCls:a},r,{ref:g}),n)};let R=i.createContext({}),{Provider:I}=R;var N=()=>{let{autoFocusButton:e,cancelButtonProps:t,cancelTextLocale:n,isSilent:a,mergedOkCancel:r,rootPrefixCls:o,close:s,onCancel:l,onConfirm:c}=(0,i.useContext)(R);return r?i.createElement(A,{isSilent:a,actionFn:l,close:function(){null==s||s.apply(void 0,arguments),null==c||c(!1)},autoFocus:"cancel"===e,buttonProps:t,prefixCls:"".concat(o,"-btn")},n):null},_=()=>{let{autoFocusButton:e,close:t,isSilent:n,okButtonProps:a,rootPrefixCls:r,okTextLocale:o,okType:s,onConfirm:l,onOk:c}=(0,i.useContext)(R);return i.createElement(A,{isSilent:n,type:s||"primary",actionFn:c,close:function(){null==t||t.apply(void 0,arguments),null==l||l(!0)},autoFocus:"ok"===e,buttonProps:a,prefixCls:"".concat(r,"-btn")},o)},v=n(49638),w=n(1119),k=n(26365),C=n(28036),O=i.createContext({}),x=n(31686),L=n(2161),D=n(92491),P=n(95814),M=n(18242);function F(e,t,n){var a=t;return!a&&n&&(a="".concat(e,"-").concat(n)),a}function U(e,t){var n=e["page".concat(t?"Y":"X","Offset")],a="scroll".concat(t?"Top":"Left");if("number"!=typeof n){var r=e.document;"number"!=typeof(n=r.documentElement[a])&&(n=r.body[a])}return n}var B=n(47970),G=n(28791),$=i.memo(function(e){return e.children},function(e,t){return!t.shouldUpdate}),H={width:0,height:0,overflow:"hidden",outline:"none"},z=i.forwardRef(function(e,t){var n,a,r,o=e.prefixCls,s=e.className,l=e.style,c=e.title,u=e.ariaId,d=e.footer,p=e.closable,g=e.closeIcon,b=e.onClose,f=e.children,E=e.bodyStyle,h=e.bodyProps,S=e.modalRender,y=e.onMouseDown,T=e.onMouseUp,A=e.holderRef,R=e.visible,I=e.forceRender,N=e.width,_=e.height,v=e.classNames,k=e.styles,C=i.useContext(O).panel,L=(0,G.x1)(A,C),D=(0,i.useRef)(),P=(0,i.useRef)();i.useImperativeHandle(t,function(){return{focus:function(){var e;null===(e=D.current)||void 0===e||e.focus()},changeActive:function(e){var t=document.activeElement;e&&t===P.current?D.current.focus():e||t!==D.current||P.current.focus()}}});var M={};void 0!==N&&(M.width=N),void 0!==_&&(M.height=_),d&&(n=i.createElement("div",{className:m()("".concat(o,"-footer"),null==v?void 0:v.footer),style:(0,x.Z)({},null==k?void 0:k.footer)},d)),c&&(a=i.createElement("div",{className:m()("".concat(o,"-header"),null==v?void 0:v.header),style:(0,x.Z)({},null==k?void 0:k.header)},i.createElement("div",{className:"".concat(o,"-title"),id:u},c))),p&&(r=i.createElement("button",{type:"button",onClick:b,"aria-label":"Close",className:"".concat(o,"-close")},g||i.createElement("span",{className:"".concat(o,"-close-x")})));var F=i.createElement("div",{className:m()("".concat(o,"-content"),null==v?void 0:v.content),style:null==k?void 0:k.content},r,a,i.createElement("div",(0,w.Z)({className:m()("".concat(o,"-body"),null==v?void 0:v.body),style:(0,x.Z)((0,x.Z)({},E),null==k?void 0:k.body)},h),f),n);return i.createElement("div",{key:"dialog-element",role:"dialog","aria-labelledby":c?u:null,"aria-modal":"true",ref:L,style:(0,x.Z)((0,x.Z)({},l),M),className:m()(o,s),onMouseDown:y,onMouseUp:T},i.createElement("div",{tabIndex:0,ref:D,style:H,"aria-hidden":"true"}),i.createElement($,{shouldUpdate:R||I},S?S(F):F),i.createElement("div",{tabIndex:0,ref:P,style:H,"aria-hidden":"true"}))}),j=i.forwardRef(function(e,t){var n=e.prefixCls,a=e.title,r=e.style,o=e.className,s=e.visible,l=e.forceRender,c=e.destroyOnClose,u=e.motionName,d=e.ariaId,p=e.onVisibleChanged,g=e.mousePosition,b=(0,i.useRef)(),f=i.useState(),E=(0,k.Z)(f,2),h=E[0],S=E[1],y={};function T(){var e,t,n,a,r,i=(n={left:(t=(e=b.current).getBoundingClientRect()).left,top:t.top},r=(a=e.ownerDocument).defaultView||a.parentWindow,n.left+=U(r),n.top+=U(r,!0),n);S(g?"".concat(g.x-i.left,"px ").concat(g.y-i.top,"px"):"")}return h&&(y.transformOrigin=h),i.createElement(B.ZP,{visible:s,onVisibleChanged:p,onAppearPrepare:T,onEnterPrepare:T,forceRender:l,motionName:u,removeOnLeave:c,ref:b},function(s,l){var c=s.className,u=s.style;return i.createElement(z,(0,w.Z)({},e,{ref:t,title:a,ariaId:d,prefixCls:n,holderRef:l,style:(0,x.Z)((0,x.Z)((0,x.Z)({},u),r),y),className:m()(o,c)}))})});function V(e){var t=e.prefixCls,n=e.style,a=e.visible,r=e.maskProps,o=e.motionName,s=e.className;return i.createElement(B.ZP,{key:"mask",visible:a,motionName:o,leavedClassName:"".concat(t,"-mask-hidden")},function(e,a){var o=e.className,l=e.style;return i.createElement("div",(0,w.Z)({ref:a,style:(0,x.Z)((0,x.Z)({},l),n),className:m()("".concat(t,"-mask"),o,s)},r))})}function W(e){var t=e.prefixCls,n=void 0===t?"rc-dialog":t,a=e.zIndex,r=e.visible,o=void 0!==r&&r,s=e.keyboard,l=void 0===s||s,c=e.focusTriggerAfterClose,u=void 0===c||c,d=e.wrapStyle,p=e.wrapClassName,g=e.wrapProps,b=e.onClose,f=e.afterOpenChange,E=e.afterClose,h=e.transitionName,S=e.animation,y=e.closable,T=e.mask,A=void 0===T||T,R=e.maskTransitionName,I=e.maskAnimation,N=e.maskClosable,_=e.maskStyle,v=e.maskProps,C=e.rootClassName,O=e.classNames,U=e.styles,B=(0,i.useRef)(),G=(0,i.useRef)(),$=(0,i.useRef)(),H=i.useState(o),z=(0,k.Z)(H,2),W=z[0],q=z[1],Y=(0,D.Z)();function K(e){null==b||b(e)}var Z=(0,i.useRef)(!1),X=(0,i.useRef)(),Q=null;return(void 0===N||N)&&(Q=function(e){Z.current?Z.current=!1:G.current===e.target&&K(e)}),(0,i.useEffect)(function(){o&&(q(!0),(0,L.Z)(G.current,document.activeElement)||(B.current=document.activeElement))},[o]),(0,i.useEffect)(function(){return function(){clearTimeout(X.current)}},[]),i.createElement("div",(0,w.Z)({className:m()("".concat(n,"-root"),C)},(0,M.Z)(e,{data:!0})),i.createElement(V,{prefixCls:n,visible:A&&o,motionName:F(n,R,I),style:(0,x.Z)((0,x.Z)({zIndex:a},_),null==U?void 0:U.mask),maskProps:v,className:null==O?void 0:O.mask}),i.createElement("div",(0,w.Z)({tabIndex:-1,onKeyDown:function(e){if(l&&e.keyCode===P.Z.ESC){e.stopPropagation(),K(e);return}o&&e.keyCode===P.Z.TAB&&$.current.changeActive(!e.shiftKey)},className:m()("".concat(n,"-wrap"),p,null==O?void 0:O.wrapper),ref:G,onClick:Q,style:(0,x.Z)((0,x.Z)((0,x.Z)({zIndex:a},d),null==U?void 0:U.wrapper),{},{display:W?null:"none"})},g),i.createElement(j,(0,w.Z)({},e,{onMouseDown:function(){clearTimeout(X.current),Z.current=!0},onMouseUp:function(){X.current=setTimeout(function(){Z.current=!1})},ref:$,closable:void 0===y||y,ariaId:Y,prefixCls:n,visible:o&&W,onClose:K,onVisibleChanged:function(e){if(e)!function(){if(!(0,L.Z)(G.current,document.activeElement)){var e;null===(e=$.current)||void 0===e||e.focus()}}();else{if(q(!1),A&&B.current&&u){try{B.current.focus({preventScroll:!0})}catch(e){}B.current=null}W&&(null==E||E())}null==f||f(e)},motionName:F(n,h,S)}))))}j.displayName="Content",n(32559);var q=function(e){var t=e.visible,n=e.getContainer,a=e.forceRender,r=e.destroyOnClose,o=void 0!==r&&r,s=e.afterClose,l=e.panelRef,c=i.useState(t),u=(0,k.Z)(c,2),d=u[0],p=u[1],g=i.useMemo(function(){return{panel:l}},[l]);return(i.useEffect(function(){t&&p(!0)},[t]),a||!o||d)?i.createElement(O.Provider,{value:g},i.createElement(C.Z,{open:t||a||d,autoDestroy:!1,getContainer:n,autoLock:t||d},i.createElement(W,(0,w.Z)({},e,{destroyOnClose:o,afterClose:function(){null==s||s(),p(!1)}})))):null};q.displayName="Dialog";var Y=function(e,t,n){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i.createElement(v.Z,null),r=arguments.length>4&&void 0!==arguments[4]&&arguments[4];if("boolean"==typeof e?!e:void 0===t?!r:!1===t||null===t)return[!1,null];let o="boolean"==typeof t||null==t?a:t;return[!0,n?n(o):o]},K=n(94981),Z=n(95140),X=n(39109),Q=n(65658),J=n(74126);function ee(){}let et=i.createContext({add:ee,remove:ee});var en=n(86586),ea=()=>{let{cancelButtonProps:e,cancelTextLocale:t,onCancel:n}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({onClick:n},e),t)},er=()=>{let{confirmLoading:e,okButtonProps:t,okType:n,okTextLocale:a,onOk:r}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({},(0,T.nx)(n),{loading:e,onClick:r},t),a)},ei=n(92246);function eo(e,t){return i.createElement("span",{className:"".concat(e,"-close-x")},t||i.createElement(v.Z,{className:"".concat(e,"-close-icon")}))}let es=e=>{let t;let{okText:n,okType:a="primary",cancelText:o,confirmLoading:s,onOk:l,onCancel:c,okButtonProps:u,cancelButtonProps:d,footer:p}=e,[g]=(0,E.Z)("Modal",(0,ei.A)()),m={confirmLoading:s,okButtonProps:u,cancelButtonProps:d,okTextLocale:n||(null==g?void 0:g.okText),cancelTextLocale:o||(null==g?void 0:g.cancelText),okType:a,onOk:l,onCancel:c},b=i.useMemo(()=>m,(0,r.Z)(Object.values(m)));return"function"==typeof p||void 0===p?(t=i.createElement(i.Fragment,null,i.createElement(ea,null),i.createElement(er,null)),"function"==typeof p&&(t=p(t,{OkBtn:er,CancelBtn:ea})),t=i.createElement(I,{value:b},t)):t=p,i.createElement(en.n,{disabled:!1},t)};var el=n(12918),ec=n(11699),eu=n(691),ed=n(3104),ep=n(80669),eg=n(352);function em(e){return{position:e,inset:0}}let eb=e=>{let{componentCls:t,antCls:n}=e;return[{["".concat(t,"-root")]:{["".concat(t).concat(n,"-zoom-enter, ").concat(t).concat(n,"-zoom-appear")]:{transform:"none",opacity:0,animationDuration:e.motionDurationSlow,userSelect:"none"},["".concat(t).concat(n,"-zoom-leave ").concat(t,"-content")]:{pointerEvents:"none"},["".concat(t,"-mask")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,height:"100%",backgroundColor:e.colorBgMask,pointerEvents:"none",["".concat(t,"-hidden")]:{display:"none"}}),["".concat(t,"-wrap")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,overflow:"auto",outline:0,WebkitOverflowScrolling:"touch",["&:has(".concat(t).concat(n,"-zoom-enter), &:has(").concat(t).concat(n,"-zoom-appear)")]:{pointerEvents:"none"}})}},{["".concat(t,"-root")]:(0,ec.J$)(e)}]},ef=e=>{let{componentCls:t}=e;return[{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl"},["".concat(t,"-centered")]:{textAlign:"center","&::before":{display:"inline-block",width:0,height:"100%",verticalAlign:"middle",content:'""'},[t]:{top:0,display:"inline-block",paddingBottom:0,textAlign:"start",verticalAlign:"middle"}},["@media (max-width: ".concat(e.screenSMMax,"px)")]:{[t]:{maxWidth:"calc(100vw - 16px)",margin:"".concat((0,eg.bf)(e.marginXS)," auto")},["".concat(t,"-centered")]:{[t]:{flex:1}}}}},{[t]:Object.assign(Object.assign({},(0,el.Wf)(e)),{pointerEvents:"none",position:"relative",top:100,width:"auto",maxWidth:"calc(100vw - ".concat((0,eg.bf)(e.calc(e.margin).mul(2).equal()),")"),margin:"0 auto",paddingBottom:e.paddingLG,["".concat(t,"-title")]:{margin:0,color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.titleFontSize,lineHeight:e.titleLineHeight,wordWrap:"break-word"},["".concat(t,"-content")]:{position:"relative",backgroundColor:e.contentBg,backgroundClip:"padding-box",border:0,borderRadius:e.borderRadiusLG,boxShadow:e.boxShadow,pointerEvents:"auto",padding:e.contentPadding},["".concat(t,"-close")]:Object.assign({position:"absolute",top:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),insetInlineEnd:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),zIndex:e.calc(e.zIndexPopupBase).add(10).equal(),padding:0,color:e.modalCloseIconColor,fontWeight:e.fontWeightStrong,lineHeight:1,textDecoration:"none",background:"transparent",borderRadius:e.borderRadiusSM,width:e.modalCloseBtnSize,height:e.modalCloseBtnSize,border:0,outline:0,cursor:"pointer",transition:"color ".concat(e.motionDurationMid,", background-color ").concat(e.motionDurationMid),"&-x":{display:"flex",fontSize:e.fontSizeLG,fontStyle:"normal",lineHeight:"".concat((0,eg.bf)(e.modalCloseBtnSize)),justifyContent:"center",textTransform:"none",textRendering:"auto"},"&:hover":{color:e.modalIconHoverColor,backgroundColor:e.closeBtnHoverBg,textDecoration:"none"},"&:active":{backgroundColor:e.closeBtnActiveBg}},(0,el.Qy)(e)),["".concat(t,"-header")]:{color:e.colorText,background:e.headerBg,borderRadius:"".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)," 0 0"),marginBottom:e.headerMarginBottom,padding:e.headerPadding,borderBottom:e.headerBorderBottom},["".concat(t,"-body")]:{fontSize:e.fontSize,lineHeight:e.lineHeight,wordWrap:"break-word",padding:e.bodyPadding},["".concat(t,"-footer")]:{textAlign:"end",background:e.footerBg,marginTop:e.footerMarginTop,padding:e.footerPadding,borderTop:e.footerBorderTop,borderRadius:e.footerBorderRadius,["> ".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginInlineStart:e.marginXS}},["".concat(t,"-open")]:{overflow:"hidden"}})},{["".concat(t,"-pure-panel")]:{top:"auto",padding:0,display:"flex",flexDirection:"column",["".concat(t,"-content,\n ").concat(t,"-body,\n ").concat(t,"-confirm-body-wrapper")]:{display:"flex",flexDirection:"column",flex:"auto"},["".concat(t,"-confirm-body")]:{marginBottom:"auto"}}}]},eE=e=>{let{componentCls:t}=e;return{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl",["".concat(t,"-confirm-body")]:{direction:"rtl"}}}}},eh=e=>{let t=e.padding,n=e.fontSizeHeading5,a=e.lineHeightHeading5;return(0,ed.TS)(e,{modalHeaderHeight:e.calc(e.calc(a).mul(n).equal()).add(e.calc(t).mul(2).equal()).equal(),modalFooterBorderColorSplit:e.colorSplit,modalFooterBorderStyle:e.lineType,modalFooterBorderWidth:e.lineWidth,modalIconHoverColor:e.colorIconHover,modalCloseIconColor:e.colorIcon,modalCloseBtnSize:e.fontHeight,modalConfirmIconSize:e.fontHeight,modalTitleHeight:e.calc(e.titleFontSize).mul(e.titleLineHeight).equal()})},eS=e=>({footerBg:"transparent",headerBg:e.colorBgElevated,titleLineHeight:e.lineHeightHeading5,titleFontSize:e.fontSizeHeading5,contentBg:e.colorBgElevated,titleColor:e.colorTextHeading,closeBtnHoverBg:e.wireframe?"transparent":e.colorFillContent,closeBtnActiveBg:e.wireframe?"transparent":e.colorFillContentHover,contentPadding:e.wireframe?0:"".concat((0,eg.bf)(e.paddingMD)," ").concat((0,eg.bf)(e.paddingContentHorizontalLG)),headerPadding:e.wireframe?"".concat((0,eg.bf)(e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,headerBorderBottom:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",headerMarginBottom:e.wireframe?0:e.marginXS,bodyPadding:e.wireframe?e.paddingLG:0,footerPadding:e.wireframe?"".concat((0,eg.bf)(e.paddingXS)," ").concat((0,eg.bf)(e.padding)):0,footerBorderTop:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",footerBorderRadius:e.wireframe?"0 0 ".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)):0,footerMarginTop:e.wireframe?0:e.marginSM,confirmBodyPadding:e.wireframe?"".concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,confirmIconMarginInlineEnd:e.wireframe?e.margin:e.marginSM,confirmBtnsMarginTop:e.wireframe?e.marginLG:e.marginSM});var ey=(0,ep.I$)("Modal",e=>{let t=eh(e);return[ef(t),eE(t),eb(t),(0,eu._y)(t,"zoom")]},eS,{unitless:{titleLineHeight:!0}}),eT=n(64024),eA=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};(0,K.Z)()&&window.document.documentElement&&document.documentElement.addEventListener("click",e=>{a={x:e.pageX,y:e.pageY},setTimeout(()=>{a=null},100)},!0);var eR=e=>{var t;let{getPopupContainer:n,getPrefixCls:r,direction:o,modal:l}=i.useContext(s.E_),c=t=>{let{onCancel:n}=e;null==n||n(t)},{prefixCls:u,className:d,rootClassName:p,open:g,wrapClassName:E,centered:h,getContainer:S,closeIcon:y,closable:T,focusTriggerAfterClose:A=!0,style:R,visible:I,width:N=520,footer:_,classNames:w,styles:k}=e,C=eA(e,["prefixCls","className","rootClassName","open","wrapClassName","centered","getContainer","closeIcon","closable","focusTriggerAfterClose","style","visible","width","footer","classNames","styles"]),O=r("modal",u),x=r(),L=(0,eT.Z)(O),[D,P,M]=ey(O,L),F=m()(E,{["".concat(O,"-centered")]:!!h,["".concat(O,"-wrap-rtl")]:"rtl"===o}),U=null!==_&&i.createElement(es,Object.assign({},e,{onOk:t=>{let{onOk:n}=e;null==n||n(t)},onCancel:c})),[B,G]=Y(T,y,e=>eo(O,e),i.createElement(v.Z,{className:"".concat(O,"-close-icon")}),!0),$=function(e){let t=i.useContext(et),n=i.useRef();return(0,J.zX)(a=>{if(a){let r=e?a.querySelector(e):a;t.add(r),n.current=r}else t.remove(n.current)})}(".".concat(O,"-content")),[H,z]=(0,b.Cn)("Modal",C.zIndex);return D(i.createElement(Q.BR,null,i.createElement(X.Ux,{status:!0,override:!0},i.createElement(Z.Z.Provider,{value:z},i.createElement(q,Object.assign({width:N},C,{zIndex:H,getContainer:void 0===S?n:S,prefixCls:O,rootClassName:m()(P,p,M,L),footer:U,visible:null!=g?g:I,mousePosition:null!==(t=C.mousePosition)&&void 0!==t?t:a,onClose:c,closable:B,closeIcon:G,focusTriggerAfterClose:A,transitionName:(0,f.m)(x,"zoom",e.transitionName),maskTransitionName:(0,f.m)(x,"fade",e.maskTransitionName),className:m()(P,d,null==l?void 0:l.className),style:Object.assign(Object.assign({},null==l?void 0:l.style),R),classNames:Object.assign(Object.assign({wrapper:F},null==l?void 0:l.classNames),w),styles:Object.assign(Object.assign({},null==l?void 0:l.styles),k),panelRef:$}))))))};let eI=e=>{let{componentCls:t,titleFontSize:n,titleLineHeight:a,modalConfirmIconSize:r,fontSize:i,lineHeight:o,modalTitleHeight:s,fontHeight:l,confirmBodyPadding:c}=e,u="".concat(t,"-confirm");return{[u]:{"&-rtl":{direction:"rtl"},["".concat(e.antCls,"-modal-header")]:{display:"none"},["".concat(u,"-body-wrapper")]:Object.assign({},(0,el.dF)()),["&".concat(t," ").concat(t,"-body")]:{padding:c},["".concat(u,"-body")]:{display:"flex",flexWrap:"nowrap",alignItems:"start",["> ".concat(e.iconCls)]:{flex:"none",fontSize:r,marginInlineEnd:e.confirmIconMarginInlineEnd,marginTop:e.calc(e.calc(l).sub(r).equal()).div(2).equal()},["&-has-title > ".concat(e.iconCls)]:{marginTop:e.calc(e.calc(s).sub(r).equal()).div(2).equal()}},["".concat(u,"-paragraph")]:{display:"flex",flexDirection:"column",flex:"auto",rowGap:e.marginXS,maxWidth:"calc(100% - ".concat((0,eg.bf)(e.calc(e.modalConfirmIconSize).add(e.marginSM).equal()),")")},["".concat(u,"-title")]:{color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:n,lineHeight:a},["".concat(u,"-content")]:{color:e.colorText,fontSize:i,lineHeight:o},["".concat(u,"-btns")]:{textAlign:"end",marginTop:e.confirmBtnsMarginTop,["".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginBottom:0,marginInlineStart:e.marginXS}}},["".concat(u,"-error ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorError},["".concat(u,"-warning ").concat(u,"-body > ").concat(e.iconCls,",\n ").concat(u,"-confirm ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorWarning},["".concat(u,"-info ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorInfo},["".concat(u,"-success ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorSuccess}}};var eN=(0,ep.bk)(["Modal","confirm"],e=>[eI(eh(e))],eS,{order:-1e3}),e_=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};function ev(e){let{prefixCls:t,icon:n,okText:a,cancelText:o,confirmPrefixCls:s,type:l,okCancel:g,footer:b,locale:f}=e,h=e_(e,["prefixCls","icon","okText","cancelText","confirmPrefixCls","type","okCancel","footer","locale"]),S=n;if(!n&&null!==n)switch(l){case"info":S=i.createElement(p.Z,null);break;case"success":S=i.createElement(c.Z,null);break;case"error":S=i.createElement(u.Z,null);break;default:S=i.createElement(d.Z,null)}let y=null!=g?g:"confirm"===l,T=null!==e.autoFocusButton&&(e.autoFocusButton||"ok"),[A]=(0,E.Z)("Modal"),R=f||A,v=a||(y?null==R?void 0:R.okText:null==R?void 0:R.justOkText),w=Object.assign({autoFocusButton:T,cancelTextLocale:o||(null==R?void 0:R.cancelText),okTextLocale:v,mergedOkCancel:y},h),k=i.useMemo(()=>w,(0,r.Z)(Object.values(w))),C=i.createElement(i.Fragment,null,i.createElement(N,null),i.createElement(_,null)),O=void 0!==e.title&&null!==e.title,x="".concat(s,"-body");return i.createElement("div",{className:"".concat(s,"-body-wrapper")},i.createElement("div",{className:m()(x,{["".concat(x,"-has-title")]:O})},S,i.createElement("div",{className:"".concat(s,"-paragraph")},O&&i.createElement("span",{className:"".concat(s,"-title")},e.title),i.createElement("div",{className:"".concat(s,"-content")},e.content))),void 0===b||"function"==typeof b?i.createElement(I,{value:k},i.createElement("div",{className:"".concat(s,"-btns")},"function"==typeof b?b(C,{OkBtn:_,CancelBtn:N}):C)):b,i.createElement(eN,{prefixCls:t}))}let ew=e=>{let{close:t,zIndex:n,afterClose:a,open:r,keyboard:o,centered:s,getContainer:l,maskStyle:c,direction:u,prefixCls:d,wrapClassName:p,rootPrefixCls:g,bodyStyle:E,closable:S=!1,closeIcon:y,modalRender:T,focusTriggerAfterClose:A,onConfirm:R,styles:I}=e,N="".concat(d,"-confirm"),_=e.width||416,v=e.style||{},w=void 0===e.mask||e.mask,k=void 0!==e.maskClosable&&e.maskClosable,C=m()(N,"".concat(N,"-").concat(e.type),{["".concat(N,"-rtl")]:"rtl"===u},e.className),[,O]=(0,h.ZP)(),x=i.useMemo(()=>void 0!==n?n:O.zIndexPopupBase+b.u6,[n,O]);return i.createElement(eR,{prefixCls:d,className:C,wrapClassName:m()({["".concat(N,"-centered")]:!!e.centered},p),onCancel:()=>{null==t||t({triggerCancel:!0}),null==R||R(!1)},open:r,title:"",footer:null,transitionName:(0,f.m)(g||"","zoom",e.transitionName),maskTransitionName:(0,f.m)(g||"","fade",e.maskTransitionName),mask:w,maskClosable:k,style:v,styles:Object.assign({body:E,mask:c},I),width:_,zIndex:x,afterClose:a,keyboard:o,centered:s,getContainer:l,closable:S,closeIcon:y,modalRender:T,focusTriggerAfterClose:A},i.createElement(ev,Object.assign({},e,{confirmPrefixCls:N})))};var ek=e=>{let{rootPrefixCls:t,iconPrefixCls:n,direction:a,theme:r}=e;return i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:n,direction:a,theme:r},i.createElement(ew,Object.assign({},e)))},eC=[];let eO="",ex=e=>{var t,n;let{prefixCls:a,getContainer:r,direction:o}=e,l=(0,ei.A)(),c=(0,i.useContext)(s.E_),u=eO||c.getPrefixCls(),d=a||"".concat(u,"-modal"),p=r;return!1===p&&(p=void 0),i.createElement(ek,Object.assign({},e,{rootPrefixCls:u,prefixCls:d,iconPrefixCls:c.iconPrefixCls,theme:c.theme,direction:null!=o?o:c.direction,locale:null!==(n=null===(t=c.locale)||void 0===t?void 0:t.Modal)&&void 0!==n?n:l,getContainer:p}))};function eL(e){let t;let n=(0,l.w6)(),a=document.createDocumentFragment(),s=Object.assign(Object.assign({},e),{close:d,open:!0});function c(){for(var t=arguments.length,n=Array(t),i=0;ie&&e.triggerCancel);e.onCancel&&s&&e.onCancel.apply(e,[()=>{}].concat((0,r.Z)(n.slice(1))));for(let e=0;e{let t=n.getPrefixCls(void 0,eO),r=n.getIconPrefixCls(),s=n.getTheme(),c=i.createElement(ex,Object.assign({},e));(0,o.s)(i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:r,theme:s},n.holderRender?n.holderRender(c):c),a)})}function d(){for(var t=arguments.length,n=Array(t),a=0;a{"function"==typeof e.afterClose&&e.afterClose(),c.apply(this,n)}})).visible&&delete s.visible,u(s)}return u(s),eC.push(d),{destroy:d,update:function(e){u(s="function"==typeof e?e(s):Object.assign(Object.assign({},s),e))}}}function eD(e){return Object.assign(Object.assign({},e),{type:"warning"})}function eP(e){return Object.assign(Object.assign({},e),{type:"info"})}function eM(e){return Object.assign(Object.assign({},e),{type:"success"})}function eF(e){return Object.assign(Object.assign({},e),{type:"error"})}function eU(e){return Object.assign(Object.assign({},e),{type:"confirm"})}var eB=n(93942),eG=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},e$=(0,eB.i)(e=>{let{prefixCls:t,className:n,closeIcon:a,closable:r,type:o,title:l,children:c,footer:u}=e,d=eG(e,["prefixCls","className","closeIcon","closable","type","title","children","footer"]),{getPrefixCls:p}=i.useContext(s.E_),g=p(),b=t||p("modal"),f=(0,eT.Z)(g),[E,h,S]=ey(b,f),y="".concat(b,"-confirm"),T={};return T=o?{closable:null!=r&&r,title:"",footer:"",children:i.createElement(ev,Object.assign({},e,{prefixCls:b,confirmPrefixCls:y,rootPrefixCls:g,content:c}))}:{closable:null==r||r,title:l,footer:null!==u&&i.createElement(es,Object.assign({},e)),children:c},E(i.createElement(z,Object.assign({prefixCls:b,className:m()(h,"".concat(b,"-pure-panel"),o&&y,o&&"".concat(y,"-").concat(o),n,S,f)},d,{closeIcon:eo(b,a),closable:r},T)))}),eH=n(13823),ez=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},ej=i.forwardRef((e,t)=>{var n,{afterClose:a,config:o}=e,l=ez(e,["afterClose","config"]);let[c,u]=i.useState(!0),[d,p]=i.useState(o),{direction:g,getPrefixCls:m}=i.useContext(s.E_),b=m("modal"),f=m(),h=function(){u(!1);for(var e=arguments.length,t=Array(e),n=0;ne&&e.triggerCancel);d.onCancel&&a&&d.onCancel.apply(d,[()=>{}].concat((0,r.Z)(t.slice(1))))};i.useImperativeHandle(t,()=>({destroy:h,update:e=>{p(t=>Object.assign(Object.assign({},t),e))}}));let S=null!==(n=d.okCancel)&&void 0!==n?n:"confirm"===d.type,[y]=(0,E.Z)("Modal",eH.Z.Modal);return i.createElement(ek,Object.assign({prefixCls:b,rootPrefixCls:f},d,{close:h,open:c,afterClose:()=>{var e;a(),null===(e=d.afterClose)||void 0===e||e.call(d)},okText:d.okText||(S?null==y?void 0:y.okText:null==y?void 0:y.justOkText),direction:d.direction||g,cancelText:d.cancelText||(null==y?void 0:y.cancelText)},l))});let eV=0,eW=i.memo(i.forwardRef((e,t)=>{let[n,a]=function(){let[e,t]=i.useState([]);return[e,i.useCallback(e=>(t(t=>[].concat((0,r.Z)(t),[e])),()=>{t(t=>t.filter(t=>t!==e))}),[])]}();return i.useImperativeHandle(t,()=>({patchElement:a}),[]),i.createElement(i.Fragment,null,n)}));function eq(e){return eL(eD(e))}eR.useModal=function(){let e=i.useRef(null),[t,n]=i.useState([]);i.useEffect(()=>{t.length&&((0,r.Z)(t).forEach(e=>{e()}),n([]))},[t]);let a=i.useCallback(t=>function(a){var o;let s,l;eV+=1;let c=i.createRef(),u=new Promise(e=>{s=e}),d=!1,p=i.createElement(ej,{key:"modal-".concat(eV),config:t(a),ref:c,afterClose:()=>{null==l||l()},isSilent:()=>d,onConfirm:e=>{s(e)}});return(l=null===(o=e.current)||void 0===o?void 0:o.patchElement(p))&&eC.push(l),{destroy:()=>{function e(){var e;null===(e=c.current)||void 0===e||e.destroy()}c.current?e():n(t=>[].concat((0,r.Z)(t),[e]))},update:e=>{function t(){var t;null===(t=c.current)||void 0===t||t.update(e)}c.current?t():n(e=>[].concat((0,r.Z)(e),[t]))},then:e=>(d=!0,u.then(e))}},[]);return[i.useMemo(()=>({info:a(eP),success:a(eM),error:a(eF),warning:a(eD),confirm:a(eU)}),[]),i.createElement(eW,{key:"modal-holder",ref:e})]},eR.info=function(e){return eL(eP(e))},eR.success=function(e){return eL(eM(e))},eR.error=function(e){return eL(eF(e))},eR.warning=eq,eR.warn=eq,eR.confirm=function(e){return eL(eU(e))},eR.destroyAll=function(){for(;eC.length;){let e=eC.pop();e&&e()}},eR.config=function(e){let{rootPrefixCls:t}=e;eO=t},eR._InternalPanelDoNotUseOrYouWillBeFired=e$;var eY=eR},11699:function(e,t,n){"use strict";n.d(t,{J$:function(){return s}});var a=n(352),r=n(37133);let i=new a.E4("antFadeIn",{"0%":{opacity:0},"100%":{opacity:1}}),o=new a.E4("antFadeOut",{"0%":{opacity:1},"100%":{opacity:0}}),s=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],{antCls:n}=e,a="".concat(n,"-fade"),s=t?"&":"";return[(0,r.R)(a,i,o,e.motionDurationMid,t),{["\n ".concat(s).concat(a,"-enter,\n ").concat(s).concat(a,"-appear\n ")]:{opacity:0,animationTimingFunction:"linear"},["".concat(s).concat(a,"-leave")]:{animationTimingFunction:"linear"}}]}},26035:function(e){"use strict";e.exports=function(e,n){for(var a,r,i,o=e||"",s=n||"div",l={},c=0;c4&&m.slice(0,4)===o&&s.test(t)&&("-"===t.charAt(4)?b=o+(n=t.slice(5).replace(l,d)).charAt(0).toUpperCase()+n.slice(1):(g=(p=t).slice(4),t=l.test(g)?p:("-"!==(g=g.replace(c,u)).charAt(0)&&(g="-"+g),o+g)),f=r),new f(b,t))};var s=/^data[-\w.:]+$/i,l=/-[a-z]/g,c=/[A-Z]/g;function u(e){return"-"+e.toLowerCase()}function d(e){return e.charAt(1).toUpperCase()}},30466:function(e,t,n){"use strict";var a=n(82855),r=n(64541),i=n(80808),o=n(44987),s=n(72731),l=n(98946);e.exports=a([i,r,o,s,l])},72731:function(e,t,n){"use strict";var a=n(20321),r=n(41757),i=a.booleanish,o=a.number,s=a.spaceSeparated;e.exports=r({transform:function(e,t){return"role"===t?t:"aria-"+t.slice(4).toLowerCase()},properties:{ariaActiveDescendant:null,ariaAtomic:i,ariaAutoComplete:null,ariaBusy:i,ariaChecked:i,ariaColCount:o,ariaColIndex:o,ariaColSpan:o,ariaControls:s,ariaCurrent:null,ariaDescribedBy:s,ariaDetails:null,ariaDisabled:i,ariaDropEffect:s,ariaErrorMessage:null,ariaExpanded:i,ariaFlowTo:s,ariaGrabbed:i,ariaHasPopup:null,ariaHidden:i,ariaInvalid:null,ariaKeyShortcuts:null,ariaLabel:null,ariaLabelledBy:s,ariaLevel:o,ariaLive:null,ariaModal:i,ariaMultiLine:i,ariaMultiSelectable:i,ariaOrientation:null,ariaOwns:s,ariaPlaceholder:null,ariaPosInSet:o,ariaPressed:i,ariaReadOnly:i,ariaRelevant:null,ariaRequired:i,ariaRoleDescription:s,ariaRowCount:o,ariaRowIndex:o,ariaRowSpan:o,ariaSelected:i,ariaSetSize:o,ariaSort:null,ariaValueMax:o,ariaValueMin:o,ariaValueNow:o,ariaValueText:null,role:null}})},98946:function(e,t,n){"use strict";var a=n(20321),r=n(41757),i=n(53296),o=a.boolean,s=a.overloadedBoolean,l=a.booleanish,c=a.number,u=a.spaceSeparated,d=a.commaSeparated;e.exports=r({space:"html",attributes:{acceptcharset:"accept-charset",classname:"class",htmlfor:"for",httpequiv:"http-equiv"},transform:i,mustUseProperty:["checked","multiple","muted","selected"],properties:{abbr:null,accept:d,acceptCharset:u,accessKey:u,action:null,allow:null,allowFullScreen:o,allowPaymentRequest:o,allowUserMedia:o,alt:null,as:null,async:o,autoCapitalize:null,autoComplete:u,autoFocus:o,autoPlay:o,capture:o,charSet:null,checked:o,cite:null,className:u,cols:c,colSpan:null,content:null,contentEditable:l,controls:o,controlsList:u,coords:c|d,crossOrigin:null,data:null,dateTime:null,decoding:null,default:o,defer:o,dir:null,dirName:null,disabled:o,download:s,draggable:l,encType:null,enterKeyHint:null,form:null,formAction:null,formEncType:null,formMethod:null,formNoValidate:o,formTarget:null,headers:u,height:c,hidden:o,high:c,href:null,hrefLang:null,htmlFor:u,httpEquiv:u,id:null,imageSizes:null,imageSrcSet:d,inputMode:null,integrity:null,is:null,isMap:o,itemId:null,itemProp:u,itemRef:u,itemScope:o,itemType:u,kind:null,label:null,lang:null,language:null,list:null,loading:null,loop:o,low:c,manifest:null,max:null,maxLength:c,media:null,method:null,min:null,minLength:c,multiple:o,muted:o,name:null,nonce:null,noModule:o,noValidate:o,onAbort:null,onAfterPrint:null,onAuxClick:null,onBeforePrint:null,onBeforeUnload:null,onBlur:null,onCancel:null,onCanPlay:null,onCanPlayThrough:null,onChange:null,onClick:null,onClose:null,onContextMenu:null,onCopy:null,onCueChange:null,onCut:null,onDblClick:null,onDrag:null,onDragEnd:null,onDragEnter:null,onDragExit:null,onDragLeave:null,onDragOver:null,onDragStart:null,onDrop:null,onDurationChange:null,onEmptied:null,onEnded:null,onError:null,onFocus:null,onFormData:null,onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null,onKeyUp:null,onLanguageChange:null,onLoad:null,onLoadedData:null,onLoadedMetadata:null,onLoadEnd:null,onLoadStart:null,onMessage:null,onMessageError:null,onMouseDown:null,onMouseEnter:null,onMouseLeave:null,onMouseMove:null,onMouseOut:null,onMouseOver:null,onMouseUp:null,onOffline:null,onOnline:null,onPageHide:null,onPageShow:null,onPaste:null,onPause:null,onPlay:null,onPlaying:null,onPopState:null,onProgress:null,onRateChange:null,onRejectionHandled:null,onReset:null,onResize:null,onScroll:null,onSecurityPolicyViolation:null,onSeeked:null,onSeeking:null,onSelect:null,onSlotChange:null,onStalled:null,onStorage:null,onSubmit:null,onSuspend:null,onTimeUpdate:null,onToggle:null,onUnhandledRejection:null,onUnload:null,onVolumeChange:null,onWaiting:null,onWheel:null,open:o,optimum:c,pattern:null,ping:u,placeholder:null,playsInline:o,poster:null,preload:null,readOnly:o,referrerPolicy:null,rel:u,required:o,reversed:o,rows:c,rowSpan:c,sandbox:u,scope:null,scoped:o,seamless:o,selected:o,shape:null,size:c,sizes:null,slot:null,span:c,spellCheck:l,src:null,srcDoc:null,srcLang:null,srcSet:d,start:c,step:null,style:null,tabIndex:c,target:null,title:null,translate:null,type:null,typeMustMatch:o,useMap:null,value:l,width:c,wrap:null,align:null,aLink:null,archive:u,axis:null,background:null,bgColor:null,border:c,borderColor:null,bottomMargin:c,cellPadding:null,cellSpacing:null,char:null,charOff:null,classId:null,clear:null,code:null,codeBase:null,codeType:null,color:null,compact:o,declare:o,event:null,face:null,frame:null,frameBorder:null,hSpace:c,leftMargin:c,link:null,longDesc:null,lowSrc:null,marginHeight:c,marginWidth:c,noResize:o,noHref:o,noShade:o,noWrap:o,object:null,profile:null,prompt:null,rev:null,rightMargin:c,rules:null,scheme:null,scrolling:l,standby:null,summary:null,text:null,topMargin:c,valueType:null,version:null,vAlign:null,vLink:null,vSpace:c,allowTransparency:null,autoCorrect:null,autoSave:null,disablePictureInPicture:o,disableRemotePlayback:o,prefix:null,property:null,results:c,security:null,unselectable:null}})},53296:function(e,t,n){"use strict";var a=n(38781);e.exports=function(e,t){return a(e,t.toLowerCase())}},38781:function(e){"use strict";e.exports=function(e,t){return t in e?e[t]:t}},41757:function(e,t,n){"use strict";var a=n(96532),r=n(61723),i=n(51351);e.exports=function(e){var t,n,o=e.space,s=e.mustUseProperty||[],l=e.attributes||{},c=e.properties,u=e.transform,d={},p={};for(t in c)n=new i(t,u(l,t),c[t],o),-1!==s.indexOf(t)&&(n.mustUseProperty=!0),d[t]=n,p[a(t)]=t,p[a(n.attribute)]=t;return new r(d,p,o)}},51351:function(e,t,n){"use strict";var a=n(24192),r=n(20321);e.exports=s,s.prototype=new a,s.prototype.defined=!0;var i=["boolean","booleanish","overloadedBoolean","number","commaSeparated","spaceSeparated","commaOrSpaceSeparated"],o=i.length;function s(e,t,n,s){var l,c,u,d=-1;for(s&&(this.space=s),a.call(this,e,t);++d

Hi

\\n\\n",">","index.html"]}', + name='shell', + 'id': 'toolu_018KFWsEySHjdKZPdUzXpymJ', + 'type': 'function' + } + - Output: + { + "id": "toolu_018KFWsEySHjdKZPdUzXpymJ", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}" + } + } + + """ + function: dict = _tool_use_definition.get("function") or {} + tool_call_chunk = ChatCompletionToolCallChunk( + id=_tool_use_definition.get("id") or "", + type=_tool_use_definition.get("type") or "function", + function=ChatCompletionToolCallFunctionChunk( + name=function.get("name") or "", + arguments=function.get("arguments") or "", + ), + index=0, + ) + chat_completion_response_message = ChatCompletionResponseMessage( + tool_calls=[tool_call_chunk], + role="assistant", + ) + return [chat_completion_response_message, tool_output_message] + + return [tool_output_message] + + @staticmethod + def _transform_responses_api_content_to_chat_completion_content( + content: Any, + ) -> Union[str, List[Union[str, Dict[str, Any]]]]: + """ + Transform a Responses API content into a Chat Completion content + """ + + if isinstance(content, str): + return content + elif isinstance(content, list): + content_list: List[Union[str, Dict[str, Any]]] = [] + for item in content: + if isinstance(item, str): + content_list.append(item) + elif isinstance(item, dict): + content_list.append( + { + "type": LiteLLMCompletionResponsesConfig._get_chat_completion_request_content_type( + item.get("type") or "text" + ), + "text": item.get("text"), + } + ) + return content_list + else: + raise ValueError(f"Invalid content type: {type(content)}") + + @staticmethod + def _get_chat_completion_request_content_type(content_type: str) -> str: + """ + Get the Chat Completion request content type + """ + # Responses API content has `input_` prefix, if it exists, remove it + if content_type.startswith("input_"): + return content_type[len("input_") :] + else: + return content_type + + @staticmethod + def transform_instructions_to_system_message( + instructions: Optional[str], + ) -> ChatCompletionSystemMessage: + """ + Transform a Instructions into a system message + """ + return ChatCompletionSystemMessage(role="system", content=instructions or "") + + @staticmethod + def transform_responses_api_tools_to_chat_completion_tools( + tools: Optional[List[FunctionToolParam]], + ) -> List[ChatCompletionToolParam]: + """ + Transform a Responses API tools into a Chat Completion tools + """ + if tools is None: + return [] + chat_completion_tools: List[ChatCompletionToolParam] = [] + for tool in tools: + chat_completion_tools.append( + ChatCompletionToolParam( + type="function", + function=ChatCompletionToolParamFunctionChunk( + name=tool["name"], + description=tool.get("description") or "", + parameters=tool.get("parameters", {}), + strict=tool.get("strict", False), + ), + ) + ) + return chat_completion_tools + + @staticmethod + def transform_chat_completion_tools_to_responses_tools( + chat_completion_response: ModelResponse, + ) -> List[OutputFunctionToolCall]: + """ + Transform a Chat Completion tools into a Responses API tools + """ + all_chat_completion_tools: List[ChatCompletionMessageToolCall] = [] + for choice in chat_completion_response.choices: + if isinstance(choice, Choices): + if choice.message.tool_calls: + all_chat_completion_tools.extend(choice.message.tool_calls) + for tool_call in choice.message.tool_calls: + TOOL_CALLS_CACHE.set_cache( + key=tool_call.id, + value=tool_call, + ) + + responses_tools: List[OutputFunctionToolCall] = [] + for tool in all_chat_completion_tools: + if tool.type == "function": + function_definition = tool.function + responses_tools.append( + OutputFunctionToolCall( + name=function_definition.name or "", + arguments=function_definition.get("arguments") or "", + call_id=tool.id or "", + id=tool.id or "", + type="function_call", # critical this is "function_call" to work with tools like openai codex + status=function_definition.get("status") or "completed", + ) + ) + return responses_tools + + @staticmethod + def transform_chat_completion_response_to_responses_api_response( + request_input: Union[str, ResponseInputParam], + responses_api_request: ResponsesAPIOptionalRequestParams, + chat_completion_response: ModelResponse, + ) -> ResponsesAPIResponse: + """ + Transform a Chat Completion response into a Responses API response + """ + responses_api_response: ResponsesAPIResponse = ResponsesAPIResponse( + id=chat_completion_response.id, + created_at=chat_completion_response.created, + model=chat_completion_response.model, + object=chat_completion_response.object, + error=getattr(chat_completion_response, "error", None), + incomplete_details=getattr( + chat_completion_response, "incomplete_details", None + ), + instructions=getattr(chat_completion_response, "instructions", None), + metadata=getattr(chat_completion_response, "metadata", {}), + output=LiteLLMCompletionResponsesConfig._transform_chat_completion_choices_to_responses_output( + chat_completion_response=chat_completion_response, + choices=getattr(chat_completion_response, "choices", []), + ), + parallel_tool_calls=getattr( + chat_completion_response, "parallel_tool_calls", False + ), + temperature=getattr(chat_completion_response, "temperature", 0), + tool_choice=getattr(chat_completion_response, "tool_choice", "auto"), + tools=getattr(chat_completion_response, "tools", []), + top_p=getattr(chat_completion_response, "top_p", None), + max_output_tokens=getattr( + chat_completion_response, "max_output_tokens", None + ), + previous_response_id=getattr( + chat_completion_response, "previous_response_id", None + ), + reasoning=Reasoning(), + status=getattr(chat_completion_response, "status", "completed"), + text=ResponseTextConfig(), + truncation=getattr(chat_completion_response, "truncation", None), + usage=LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + chat_completion_response=chat_completion_response + ), + user=getattr(chat_completion_response, "user", None), + ) + + RESPONSES_API_SESSION_HANDLER.add_completed_response_to_cache( + response_id=responses_api_response.id, + session_element=ResponsesAPISessionElement( + input=request_input, + output=responses_api_response, + response_id=responses_api_response.id, + previous_response_id=responses_api_request.get("previous_response_id"), + ), + ) + return responses_api_response + + @staticmethod + def _transform_chat_completion_choices_to_responses_output( + chat_completion_response: ModelResponse, + choices: List[Choices], + ) -> List[Union[GenericResponseOutputItem, OutputFunctionToolCall]]: + responses_output: List[ + Union[GenericResponseOutputItem, OutputFunctionToolCall] + ] = [] + for choice in choices: + responses_output.append( + GenericResponseOutputItem( + type="message", + id=chat_completion_response.id, + status=choice.finish_reason, + role=choice.message.role, + content=[ + LiteLLMCompletionResponsesConfig._transform_chat_message_to_response_output_text( + choice.message + ) + ], + ) + ) + + tool_calls = LiteLLMCompletionResponsesConfig.transform_chat_completion_tools_to_responses_tools( + chat_completion_response=chat_completion_response + ) + responses_output.extend(tool_calls) + return responses_output + + @staticmethod + def _transform_responses_api_outputs_to_chat_completion_messages( + responses_api_output: ResponsesAPIResponse, + ) -> List[ + Union[ + AllMessageValues, + GenericChatCompletionMessage, + ChatCompletionMessageToolCall, + ] + ]: + messages: List[ + Union[ + AllMessageValues, + GenericChatCompletionMessage, + ChatCompletionMessageToolCall, + ] + ] = [] + output_items = responses_api_output.output + for _output_item in output_items: + output_item: dict = dict(_output_item) + if output_item.get("type") == "function_call": + # handle function call output + messages.append( + LiteLLMCompletionResponsesConfig._transform_responses_output_tool_call_to_chat_completion_output_tool_call( + tool_call=output_item + ) + ) + else: + # transform as generic ResponseOutputItem + messages.append( + GenericChatCompletionMessage( + role=str(output_item.get("role")) or "user", + content=LiteLLMCompletionResponsesConfig._transform_responses_api_content_to_chat_completion_content( + output_item.get("content") + ), + ) + ) + return messages + + @staticmethod + def _transform_responses_output_tool_call_to_chat_completion_output_tool_call( + tool_call: dict, + ) -> ChatCompletionMessageToolCall: + return ChatCompletionMessageToolCall( + id=tool_call.get("id") or "", + type="function", + function=Function( + name=tool_call.get("name") or "", + arguments=tool_call.get("arguments") or "", + ), + ) + + @staticmethod + def _transform_chat_message_to_response_output_text( + message: Message, + ) -> OutputText: + return OutputText( + type="output_text", + text=message.content, + annotations=LiteLLMCompletionResponsesConfig._transform_chat_completion_annotations_to_response_output_annotations( + annotations=getattr(message, "annotations", None) + ), + ) + + @staticmethod + def _transform_chat_completion_annotations_to_response_output_annotations( + annotations: Optional[List[ChatCompletionAnnotation]], + ) -> List[GenericResponseOutputItemContentAnnotation]: + response_output_annotations: List[ + GenericResponseOutputItemContentAnnotation + ] = [] + + if annotations is None: + return response_output_annotations + + for annotation in annotations: + annotation_type = annotation.get("type") + if annotation_type == "url_citation" and "url_citation" in annotation: + url_citation = annotation["url_citation"] + response_output_annotations.append( + GenericResponseOutputItemContentAnnotation( + type=annotation_type, + start_index=url_citation.get("start_index"), + end_index=url_citation.get("end_index"), + url=url_citation.get("url"), + title=url_citation.get("title"), + ) + ) + # Handle other annotation types here + + return response_output_annotations + + @staticmethod + def _transform_chat_completion_usage_to_responses_usage( + chat_completion_response: ModelResponse, + ) -> ResponseAPIUsage: + usage: Optional[Usage] = getattr(chat_completion_response, "usage", None) + if usage is None: + return ResponseAPIUsage( + input_tokens=0, + output_tokens=0, + total_tokens=0, + ) + return ResponseAPIUsage( + input_tokens=usage.prompt_tokens, + output_tokens=usage.completion_tokens, + total_tokens=usage.total_tokens, + ) diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 70b651f376..bd0c7246c0 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -1,7 +1,7 @@ import asyncio import contextvars from functools import partial -from typing import Any, Dict, Iterable, List, Literal, Optional, Union +from typing import Any, Coroutine, Dict, Iterable, List, Literal, Optional, Union import httpx @@ -10,6 +10,9 @@ from litellm.constants import request_timeout from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler +from litellm.responses.litellm_completion_transformation.handler import ( + LiteLLMCompletionTransformationHandler, +) from litellm.responses.utils import ResponsesAPIRequestUtils from litellm.types.llms.openai import ( Reasoning, @@ -21,6 +24,7 @@ from litellm.types.llms.openai import ( ToolChoice, ToolParam, ) +from litellm.types.responses.main import * from litellm.types.router import GenericLiteLLMParams from litellm.utils import ProviderConfigManager, client @@ -29,6 +33,7 @@ from .streaming_iterator import BaseResponsesAPIStreamingIterator ####### ENVIRONMENT VARIABLES ################### # Initialize any necessary instances or variables here base_llm_http_handler = BaseLLMHTTPHandler() +litellm_completion_transformation_handler = LiteLLMCompletionTransformationHandler() ################################################# @@ -112,6 +117,14 @@ async def aresponses( response = await init_response else: response = init_response + + # Update the responses_api_response_id with the model_id + if isinstance(response, ResponsesAPIResponse): + response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id( + responses_api_response=response, + litellm_metadata=kwargs.get("litellm_metadata", {}), + custom_llm_provider=custom_llm_provider, + ) return response except Exception as e: raise litellm.exception_type( @@ -178,19 +191,12 @@ def responses( ) # get provider config - responses_api_provider_config: Optional[ - BaseResponsesAPIConfig - ] = ProviderConfigManager.get_provider_responses_api_config( - model=model, - provider=litellm.LlmProviders(custom_llm_provider), - ) - - if responses_api_provider_config is None: - raise litellm.BadRequestError( + responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( + ProviderConfigManager.get_provider_responses_api_config( model=model, - llm_provider=custom_llm_provider, - message=f"Responses API not available for custom_llm_provider={custom_llm_provider}, model: {model}", + provider=litellm.LlmProviders(custom_llm_provider), ) + ) local_vars.update(kwargs) # Get ResponsesAPIOptionalRequestParams with only valid parameters @@ -200,6 +206,17 @@ def responses( ) ) + if responses_api_provider_config is None: + return litellm_completion_transformation_handler.response_api_handler( + model=model, + input=input, + responses_api_request=response_api_optional_params, + custom_llm_provider=custom_llm_provider, + _is_async=_is_async, + stream=stream, + **kwargs, + ) + # Get optional parameters for the responses API responses_api_request_params: Dict = ( ResponsesAPIRequestUtils.get_optional_params_responses_api( @@ -238,8 +255,17 @@ def responses( fake_stream=responses_api_provider_config.should_fake_stream( model=model, stream=stream, custom_llm_provider=custom_llm_provider ), + litellm_metadata=kwargs.get("litellm_metadata", {}), ) + # Update the responses_api_response_id with the model_id + if isinstance(response, ResponsesAPIResponse): + response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id( + responses_api_response=response, + litellm_metadata=kwargs.get("litellm_metadata", {}), + custom_llm_provider=custom_llm_provider, + ) + return response except Exception as e: raise litellm.exception_type( @@ -249,3 +275,347 @@ def responses( completion_kwargs=local_vars, extra_kwargs=kwargs, ) + + +@client +async def adelete_responses( + response_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> DeleteResponseResult: + """ + Async version of the DELETE Responses API + + DELETE /v1/responses/{response_id} endpoint in the responses API + + """ + local_vars = locals() + try: + loop = asyncio.get_event_loop() + kwargs["adelete_responses"] = True + + # get custom llm provider from response_id + decoded_response_id: DecodedResponseId = ( + ResponsesAPIRequestUtils._decode_responses_api_response_id( + response_id=response_id, + ) + ) + response_id = decoded_response_id.get("response_id") or response_id + custom_llm_provider = ( + decoded_response_id.get("custom_llm_provider") or custom_llm_provider + ) + + func = partial( + delete_responses, + response_id=response_id, + custom_llm_provider=custom_llm_provider, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + **kwargs, + ) + + ctx = contextvars.copy_context() + func_with_context = partial(ctx.run, func) + init_response = await loop.run_in_executor(None, func_with_context) + + if asyncio.iscoroutine(init_response): + response = await init_response + else: + response = init_response + return response + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) + + +@client +def delete_responses( + response_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> Union[DeleteResponseResult, Coroutine[Any, Any, DeleteResponseResult]]: + """ + Synchronous version of the DELETE Responses API + + DELETE /v1/responses/{response_id} endpoint in the responses API + + """ + local_vars = locals() + try: + litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) + _is_async = kwargs.pop("adelete_responses", False) is True + + # get llm provider logic + litellm_params = GenericLiteLLMParams(**kwargs) + + # get custom llm provider from response_id + decoded_response_id: DecodedResponseId = ( + ResponsesAPIRequestUtils._decode_responses_api_response_id( + response_id=response_id, + ) + ) + response_id = decoded_response_id.get("response_id") or response_id + custom_llm_provider = ( + decoded_response_id.get("custom_llm_provider") or custom_llm_provider + ) + + if custom_llm_provider is None: + raise ValueError("custom_llm_provider is required but passed as None") + + # get provider config + responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( + ProviderConfigManager.get_provider_responses_api_config( + model=None, + provider=litellm.LlmProviders(custom_llm_provider), + ) + ) + + if responses_api_provider_config is None: + raise ValueError( + f"DELETE responses is not supported for {custom_llm_provider}" + ) + + local_vars.update(kwargs) + + # Pre Call logging + litellm_logging_obj.update_environment_variables( + model=None, + optional_params={ + "response_id": response_id, + }, + litellm_params={ + "litellm_call_id": litellm_call_id, + }, + custom_llm_provider=custom_llm_provider, + ) + + # Call the handler with _is_async flag instead of directly calling the async handler + response = base_llm_http_handler.delete_response_api_handler( + response_id=response_id, + custom_llm_provider=custom_llm_provider, + responses_api_provider_config=responses_api_provider_config, + litellm_params=litellm_params, + logging_obj=litellm_logging_obj, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout or request_timeout, + _is_async=_is_async, + client=kwargs.get("client"), + ) + + return response + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) + +@client +async def aget_responses( + response_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> ResponsesAPIResponse: + """ + Async: Fetch a response by its ID. + + GET /v1/responses/{response_id} endpoint in the responses API + + Args: + response_id: The ID of the response to fetch. + custom_llm_provider: Optional provider name. If not specified, will be decoded from response_id. + + Returns: + The response object with complete information about the stored response. + """ + local_vars = locals() + try: + loop = asyncio.get_event_loop() + kwargs["aget_responses"] = True + + # get custom llm provider from response_id + decoded_response_id: DecodedResponseId = ( + ResponsesAPIRequestUtils._decode_responses_api_response_id( + response_id=response_id, + ) + ) + response_id = decoded_response_id.get("response_id") or response_id + custom_llm_provider = ( + decoded_response_id.get("custom_llm_provider") or custom_llm_provider + ) + + func = partial( + get_responses, + response_id=response_id, + custom_llm_provider=custom_llm_provider, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + **kwargs, + ) + + ctx = contextvars.copy_context() + func_with_context = partial(ctx.run, func) + init_response = await loop.run_in_executor(None, func_with_context) + + if asyncio.iscoroutine(init_response): + response = await init_response + else: + response = init_response + + # Update the responses_api_response_id with the model_id + if isinstance(response, ResponsesAPIResponse): + response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id( + responses_api_response=response, + litellm_metadata=kwargs.get("litellm_metadata", {}), + custom_llm_provider=custom_llm_provider, + ) + return response + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) + +@client +def get_responses( + response_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> Union[ResponsesAPIResponse, Coroutine[Any, Any, ResponsesAPIResponse]]: + """ + Fetch a response by its ID. + + GET /v1/responses/{response_id} endpoint in the responses API + + Args: + response_id: The ID of the response to fetch. + custom_llm_provider: Optional provider name. If not specified, will be decoded from response_id. + + Returns: + The response object with complete information about the stored response. + """ + local_vars = locals() + try: + litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) + _is_async = kwargs.pop("aget_responses", False) is True + + # get llm provider logic + litellm_params = GenericLiteLLMParams(**kwargs) + + # get custom llm provider from response_id + decoded_response_id: DecodedResponseId = ( + ResponsesAPIRequestUtils._decode_responses_api_response_id( + response_id=response_id, + ) + ) + response_id = decoded_response_id.get("response_id") or response_id + custom_llm_provider = ( + decoded_response_id.get("custom_llm_provider") or custom_llm_provider + ) + + if custom_llm_provider is None: + raise ValueError("custom_llm_provider is required but passed as None") + + # get provider config + responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( + ProviderConfigManager.get_provider_responses_api_config( + model=None, + provider=litellm.LlmProviders(custom_llm_provider), + ) + ) + + if responses_api_provider_config is None: + raise ValueError( + f"GET responses is not supported for {custom_llm_provider}" + ) + + local_vars.update(kwargs) + + # Pre Call logging + litellm_logging_obj.update_environment_variables( + model=None, + optional_params={ + "response_id": response_id, + }, + litellm_params={ + "litellm_call_id": litellm_call_id, + }, + custom_llm_provider=custom_llm_provider, + ) + + # Call the handler with _is_async flag instead of directly calling the async handler + response = base_llm_http_handler.get_responses( + response_id=response_id, + custom_llm_provider=custom_llm_provider, + responses_api_provider_config=responses_api_provider_config, + litellm_params=litellm_params, + logging_obj=litellm_logging_obj, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout or request_timeout, + _is_async=_is_async, + client=kwargs.get("client"), + ) + + # Update the responses_api_response_id with the model_id + if isinstance(response, ResponsesAPIResponse): + response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id( + responses_api_response=response, + litellm_metadata=kwargs.get("litellm_metadata", {}), + custom_llm_provider=custom_llm_provider, + ) + + return response + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) \ No newline at end of file diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index 3039efb9f7..a111fbec09 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -1,7 +1,7 @@ import asyncio import json from datetime import datetime -from typing import Optional +from typing import Any, Dict, Optional import httpx @@ -10,8 +10,11 @@ from litellm.litellm_core_utils.asyncify import run_async_function from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.thread_pool_executor import executor from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig +from litellm.responses.utils import ResponsesAPIRequestUtils from litellm.types.llms.openai import ( + OutputTextDeltaEvent, ResponseCompletedEvent, + ResponsesAPIResponse, ResponsesAPIStreamEvents, ResponsesAPIStreamingResponse, ) @@ -31,6 +34,8 @@ class BaseResponsesAPIStreamingIterator: model: str, responses_api_provider_config: BaseResponsesAPIConfig, logging_obj: LiteLLMLoggingObj, + litellm_metadata: Optional[Dict[str, Any]] = None, + custom_llm_provider: Optional[str] = None, ): self.response = response self.model = model @@ -39,8 +44,12 @@ class BaseResponsesAPIStreamingIterator: self.responses_api_provider_config = responses_api_provider_config self.completed_response: Optional[ResponsesAPIStreamingResponse] = None self.start_time = datetime.now() + + # set request kwargs + self.litellm_metadata = litellm_metadata + self.custom_llm_provider = custom_llm_provider - def _process_chunk(self, chunk): + def _process_chunk(self, chunk) -> Optional[ResponsesAPIStreamingResponse]: """Process a single chunk of data from the stream""" if not chunk: return None @@ -68,6 +77,17 @@ class BaseResponsesAPIStreamingIterator: logging_obj=self.logging_obj, ) ) + + # if "response" in parsed_chunk, then encode litellm specific information like custom_llm_provider + response_object = getattr(openai_responses_api_chunk, "response", None) + if response_object: + response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id( + responses_api_response=response_object, + litellm_metadata=self.litellm_metadata, + custom_llm_provider=self.custom_llm_provider, + ) + setattr(openai_responses_api_chunk, "response", response) + # Store the completed response if ( openai_responses_api_chunk @@ -100,8 +120,17 @@ class ResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator): model: str, responses_api_provider_config: BaseResponsesAPIConfig, logging_obj: LiteLLMLoggingObj, + litellm_metadata: Optional[Dict[str, Any]] = None, + custom_llm_provider: Optional[str] = None, ): - super().__init__(response, model, responses_api_provider_config, logging_obj) + super().__init__( + response, + model, + responses_api_provider_config, + logging_obj, + litellm_metadata, + custom_llm_provider, + ) self.stream_iterator = response.aiter_lines() def __aiter__(self): @@ -161,8 +190,17 @@ class SyncResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator): model: str, responses_api_provider_config: BaseResponsesAPIConfig, logging_obj: LiteLLMLoggingObj, + litellm_metadata: Optional[Dict[str, Any]] = None, + custom_llm_provider: Optional[str] = None, ): - super().__init__(response, model, responses_api_provider_config, logging_obj) + super().__init__( + response, + model, + responses_api_provider_config, + logging_obj, + litellm_metadata, + custom_llm_provider, + ) self.stream_iterator = response.iter_lines() def __iter__(self): @@ -212,59 +250,87 @@ class SyncResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator): class MockResponsesAPIStreamingIterator(BaseResponsesAPIStreamingIterator): """ - mock iterator - some models like o1-pro do not support streaming, we need to fake a stream + Mock iterator—fake a stream by slicing the full response text into + 5 char deltas, then emit a completed event. + + Models like o1-pro don't support streaming, so we fake it. """ + CHUNK_SIZE = 5 + def __init__( self, response: httpx.Response, model: str, responses_api_provider_config: BaseResponsesAPIConfig, logging_obj: LiteLLMLoggingObj, + litellm_metadata: Optional[Dict[str, Any]] = None, + custom_llm_provider: Optional[str] = None, ): - self.raw_http_response = response super().__init__( response=response, model=model, responses_api_provider_config=responses_api_provider_config, logging_obj=logging_obj, + litellm_metadata=litellm_metadata, + custom_llm_provider=custom_llm_provider, ) - self.is_done = False + + # one-time transform + transformed = ( + self.responses_api_provider_config.transform_response_api_response( + model=self.model, + raw_response=response, + logging_obj=logging_obj, + ) + ) + full_text = self._collect_text(transformed) + + # build a list of 5‑char delta events + deltas = [ + OutputTextDeltaEvent( + type=ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA, + delta=full_text[i : i + self.CHUNK_SIZE], + item_id=transformed.id, + output_index=0, + content_index=0, + ) + for i in range(0, len(full_text), self.CHUNK_SIZE) + ] + + # append the completed event + self._events = deltas + [ + ResponseCompletedEvent( + type=ResponsesAPIStreamEvents.RESPONSE_COMPLETED, + response=transformed, + ) + ] + self._idx = 0 def __aiter__(self): return self async def __anext__(self) -> ResponsesAPIStreamingResponse: - if self.is_done: + if self._idx >= len(self._events): raise StopAsyncIteration - self.is_done = True - transformed_response = ( - self.responses_api_provider_config.transform_response_api_response( - model=self.model, - raw_response=self.raw_http_response, - logging_obj=self.logging_obj, - ) - ) - return ResponseCompletedEvent( - type=ResponsesAPIStreamEvents.RESPONSE_COMPLETED, - response=transformed_response, - ) + evt = self._events[self._idx] + self._idx += 1 + return evt def __iter__(self): return self def __next__(self) -> ResponsesAPIStreamingResponse: - if self.is_done: + if self._idx >= len(self._events): raise StopIteration - self.is_done = True - transformed_response = ( - self.responses_api_provider_config.transform_response_api_response( - model=self.model, - raw_response=self.raw_http_response, - logging_obj=self.logging_obj, - ) - ) - return ResponseCompletedEvent( - type=ResponsesAPIStreamEvents.RESPONSE_COMPLETED, - response=transformed_response, - ) + evt = self._events[self._idx] + self._idx += 1 + return evt + + def _collect_text(self, resp: ResponsesAPIResponse) -> str: + out = "" + for out_item in resp.output: + if out_item.type == "message": + for c in getattr(out_item, "content", []): + out += c.text + return out diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 679b9e16c6..9fa455de71 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -1,12 +1,16 @@ -from typing import Any, Dict, Union, cast, get_type_hints +import base64 +from typing import Any, Dict, Optional, Union, cast, get_type_hints import litellm +from litellm._logging import verbose_logger from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig from litellm.types.llms.openai import ( ResponseAPIUsage, ResponsesAPIOptionalRequestParams, + ResponsesAPIResponse, ) -from litellm.types.utils import Usage +from litellm.types.responses.main import DecodedResponseId +from litellm.types.utils import SpecialEnums, Usage class ResponsesAPIRequestUtils: @@ -60,7 +64,7 @@ class ResponsesAPIRequestUtils: @staticmethod def get_requested_response_api_optional_param( - params: Dict[str, Any] + params: Dict[str, Any], ) -> ResponsesAPIOptionalRequestParams: """ Filter parameters to only include those defined in ResponsesAPIOptionalRequestParams. @@ -72,9 +76,106 @@ class ResponsesAPIRequestUtils: ResponsesAPIOptionalRequestParams instance with only the valid parameters """ valid_keys = get_type_hints(ResponsesAPIOptionalRequestParams).keys() - filtered_params = {k: v for k, v in params.items() if k in valid_keys} + filtered_params = { + k: v for k, v in params.items() if k in valid_keys and v is not None + } return cast(ResponsesAPIOptionalRequestParams, filtered_params) + @staticmethod + def _update_responses_api_response_id_with_model_id( + responses_api_response: ResponsesAPIResponse, + custom_llm_provider: Optional[str], + litellm_metadata: Optional[Dict[str, Any]] = None, + ) -> ResponsesAPIResponse: + """ + Update the responses_api_response_id with model_id and custom_llm_provider + + This builds a composite ID containing the custom LLM provider, model ID, and original response ID + """ + litellm_metadata = litellm_metadata or {} + model_info: Dict[str, Any] = litellm_metadata.get("model_info", {}) or {} + model_id = model_info.get("id") + updated_id = ResponsesAPIRequestUtils._build_responses_api_response_id( + model_id=model_id, + custom_llm_provider=custom_llm_provider, + response_id=responses_api_response.id, + ) + + responses_api_response.id = updated_id + return responses_api_response + + @staticmethod + def _build_responses_api_response_id( + custom_llm_provider: Optional[str], + model_id: Optional[str], + response_id: str, + ) -> str: + """Build the responses_api_response_id""" + assembled_id: str = str( + SpecialEnums.LITELLM_MANAGED_RESPONSE_COMPLETE_STR.value + ).format(custom_llm_provider, model_id, response_id) + base64_encoded_id: str = base64.b64encode(assembled_id.encode("utf-8")).decode( + "utf-8" + ) + return f"resp_{base64_encoded_id}" + + @staticmethod + def _decode_responses_api_response_id( + response_id: str, + ) -> DecodedResponseId: + """ + Decode the responses_api_response_id + + Returns: + DecodedResponseId: Structured tuple with custom_llm_provider, model_id, and response_id + """ + try: + # Remove prefix and decode + cleaned_id = response_id.replace("resp_", "") + decoded_id = base64.b64decode(cleaned_id.encode("utf-8")).decode("utf-8") + + # Parse components using known prefixes + if ";" not in decoded_id: + return DecodedResponseId( + custom_llm_provider=None, + model_id=None, + response_id=response_id, + ) + + parts = decoded_id.split(";") + + # Format: litellm:custom_llm_provider:{};model_id:{};response_id:{} + custom_llm_provider = None + model_id = None + + if ( + len(parts) >= 3 + ): # Full format with custom_llm_provider, model_id, and response_id + custom_llm_provider_part = parts[0] + model_id_part = parts[1] + response_part = parts[2] + + custom_llm_provider = custom_llm_provider_part.replace( + "litellm:custom_llm_provider:", "" + ) + model_id = model_id_part.replace("model_id:", "") + decoded_response_id = response_part.replace("response_id:", "") + else: + decoded_response_id = response_id + + return DecodedResponseId( + custom_llm_provider=custom_llm_provider, + model_id=model_id, + response_id=decoded_response_id, + ) + except Exception as e: + verbose_logger.debug(f"Error decoding response_id '{response_id}': {e}") + return DecodedResponseId( + custom_llm_provider=None, + model_id=None, + response_id=response_id, + ) + class ResponseAPILoggingUtils: @staticmethod @@ -88,7 +189,7 @@ class ResponseAPILoggingUtils: @staticmethod def _transform_response_api_usage_to_chat_usage( - usage: Union[dict, ResponseAPIUsage] + usage: Union[dict, ResponseAPIUsage], ) -> Usage: """Tranforms the ResponseAPIUsage object to a Usage object""" response_api_usage: ResponseAPIUsage = ( diff --git a/litellm/router.py b/litellm/router.py index c934b1e9a8..dba886b856 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -98,6 +98,9 @@ from litellm.router_utils.handle_error import ( from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import ( PromptCachingDeploymentCheck, ) +from litellm.router_utils.pre_call_checks.responses_api_deployment_check import ( + ResponsesApiDeploymentCheck, +) from litellm.router_utils.router_callbacks.track_deployment_metrics import ( increment_deployment_failures_for_current_minute, increment_deployment_successes_for_current_minute, @@ -339,9 +342,9 @@ class Router: ) # names of models under litellm_params. ex. azure/chatgpt-v-2 self.deployment_latency_map = {} ### CACHING ### - cache_type: Literal[ - "local", "redis", "redis-semantic", "s3", "disk" - ] = "local" # default to an in-memory cache + cache_type: Literal["local", "redis", "redis-semantic", "s3", "disk"] = ( + "local" # default to an in-memory cache + ) redis_cache = None cache_config: Dict[str, Any] = {} @@ -562,9 +565,9 @@ class Router: ) ) - self.model_group_retry_policy: Optional[ - Dict[str, RetryPolicy] - ] = model_group_retry_policy + self.model_group_retry_policy: Optional[Dict[str, RetryPolicy]] = ( + model_group_retry_policy + ) self.allowed_fails_policy: Optional[AllowedFailsPolicy] = None if allowed_fails_policy is not None: @@ -765,6 +768,8 @@ class Router: provider_budget_config=self.provider_budget_config, model_list=self.model_list, ) + elif pre_call_check == "responses_api_deployment_check": + _callback = ResponsesApiDeploymentCheck() if _callback is not None: litellm.logging_callback_manager.add_litellm_callback(_callback) @@ -1104,17 +1109,21 @@ class Router: ) -> None: """ Adds default litellm params to kwargs, if set. + + Handles inserting this as either "metadata" or "litellm_metadata" depending on the metadata_variable_name """ - self.default_litellm_params[ - metadata_variable_name - ] = self.default_litellm_params.pop("metadata", {}) - for k, v in self.default_litellm_params.items(): - if ( - k not in kwargs and v is not None - ): # prioritize model-specific params > default router params - kwargs[k] = v - elif k == metadata_variable_name: - kwargs[metadata_variable_name].update(v) + # 1) copy your defaults and pull out metadata + defaults = self.default_litellm_params.copy() + metadata_defaults = defaults.pop("metadata", {}) or {} + + # 2) add any non-metadata defaults that aren't already in kwargs + for key, value in defaults.items(): + if value is None: + continue + kwargs.setdefault(key, value) + + # 3) merge in metadata, this handles inserting this as either "metadata" or "litellm_metadata" + kwargs.setdefault(metadata_variable_name, {}).update(metadata_defaults) def _handle_clientside_credential( self, deployment: dict, kwargs: dict @@ -3243,11 +3252,11 @@ class Router: if isinstance(e, litellm.ContextWindowExceededError): if context_window_fallbacks is not None: - fallback_model_group: Optional[ - List[str] - ] = self._get_fallback_model_group_from_fallbacks( - fallbacks=context_window_fallbacks, - model_group=model_group, + fallback_model_group: Optional[List[str]] = ( + self._get_fallback_model_group_from_fallbacks( + fallbacks=context_window_fallbacks, + model_group=model_group, + ) ) if fallback_model_group is None: raise original_exception @@ -3279,11 +3288,11 @@ class Router: e.message += "\n{}".format(error_message) elif isinstance(e, litellm.ContentPolicyViolationError): if content_policy_fallbacks is not None: - fallback_model_group: Optional[ - List[str] - ] = self._get_fallback_model_group_from_fallbacks( - fallbacks=content_policy_fallbacks, - model_group=model_group, + fallback_model_group: Optional[List[str]] = ( + self._get_fallback_model_group_from_fallbacks( + fallbacks=content_policy_fallbacks, + model_group=model_group, + ) ) if fallback_model_group is None: raise original_exception @@ -4979,8 +4988,12 @@ class Router: ) if model_group_info is None: - model_group_info = ModelGroupInfo( - model_group=user_facing_model_group_name, providers=[llm_provider], **model_info # type: ignore + model_group_info = ModelGroupInfo( # type: ignore + **{ + "model_group": user_facing_model_group_name, + "providers": [llm_provider], + **model_info, + } ) else: # if max_input_tokens > curr diff --git a/litellm/router_utils/pre_call_checks/responses_api_deployment_check.py b/litellm/router_utils/pre_call_checks/responses_api_deployment_check.py new file mode 100644 index 0000000000..b030fc28c8 --- /dev/null +++ b/litellm/router_utils/pre_call_checks/responses_api_deployment_check.py @@ -0,0 +1,45 @@ +""" +For Responses API, we need routing affinity when a user sends a previous_response_id. + +eg. If proxy admins are load balancing between N gpt-4.1-turbo deployments, and a user sends a previous_response_id, +we want to route to the same gpt-4.1-turbo deployment. + +This is different from the normal behavior of the router, which does not have routing affinity for previous_response_id. + + +If previous_response_id is provided, route to the deployment that returned the previous response +""" + +from typing import List, Optional + +from litellm.integrations.custom_logger import CustomLogger, Span +from litellm.responses.utils import ResponsesAPIRequestUtils +from litellm.types.llms.openai import AllMessageValues + + +class ResponsesApiDeploymentCheck(CustomLogger): + async def async_filter_deployments( + self, + model: str, + healthy_deployments: List, + messages: Optional[List[AllMessageValues]], + request_kwargs: Optional[dict] = None, + parent_otel_span: Optional[Span] = None, + ) -> List[dict]: + request_kwargs = request_kwargs or {} + previous_response_id = request_kwargs.get("previous_response_id", None) + if previous_response_id is None: + return healthy_deployments + + decoded_response = ResponsesAPIRequestUtils._decode_responses_api_response_id( + response_id=previous_response_id, + ) + model_id = decoded_response.get("model_id") + if model_id is None: + return healthy_deployments + + for deployment in healthy_deployments: + if deployment["model_info"]["id"] == model_id: + return [deployment] + + return healthy_deployments diff --git a/litellm/types/integrations/datadog_llm_obs.py b/litellm/types/integrations/datadog_llm_obs.py index 9298b157d2..1a0f7df501 100644 --- a/litellm/types/integrations/datadog_llm_obs.py +++ b/litellm/types/integrations/datadog_llm_obs.py @@ -8,7 +8,9 @@ from typing import Any, Dict, List, Literal, Optional, TypedDict class InputMeta(TypedDict): - messages: List[Any] + messages: List[ + Dict[str, str] + ] # Relevant Issue: https://github.com/BerriAI/litellm/issues/9494 class OutputMeta(TypedDict): diff --git a/litellm/types/llms/base.py b/litellm/types/llms/base.py new file mode 100644 index 0000000000..aec1438c48 --- /dev/null +++ b/litellm/types/llms/base.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel + + +class BaseLiteLLMOpenAIResponseObject(BaseModel): + def __getitem__(self, key): + return self.__dict__[key] + + def get(self, key, default=None): + return self.__dict__.get(key, default) + + def __contains__(self, key): + return key in self.__dict__ + + def items(self): + return self.__dict__.items() diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index fe3f2e1b5f..6e02ff2ab7 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -179,6 +179,7 @@ class ToolUseBlockStartEvent(TypedDict): class ContentBlockStartEvent(TypedDict, total=False): toolUse: Optional[ToolUseBlockStartEvent] + reasoningContent: BedrockConverseReasoningContentBlockDelta class ContentBlockDeltaEvent(TypedDict, total=False): diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 0cb05a710f..be5f7585bf 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -49,9 +49,16 @@ from openai.types.responses.response_create_params import ( ToolChoice, ToolParam, ) -from pydantic import BaseModel, Discriminator, Field, PrivateAttr +from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall +from pydantic import BaseModel, ConfigDict, Discriminator, Field, PrivateAttr from typing_extensions import Annotated, Dict, Required, TypedDict, override +from litellm.types.llms.base import BaseLiteLLMOpenAIResponseObject +from litellm.types.responses.main import ( + GenericResponseOutputItem, + OutputFunctionToolCall, +) + FileContent = Union[IO[bytes], bytes, PathLike] FileTypes = Union[ @@ -461,6 +468,12 @@ class ChatCompletionThinkingBlock(TypedDict, total=False): cache_control: Optional[Union[dict, ChatCompletionCachedContent]] +class ChatCompletionRedactedThinkingBlock(TypedDict, total=False): + type: Required[Literal["redacted_thinking"]] + data: str + cache_control: Optional[Union[dict, ChatCompletionCachedContent]] + + class WebSearchOptionsUserLocationApproximate(TypedDict, total=False): city: str """Free text input for the city of the user, e.g. `San Francisco`.""" @@ -638,6 +651,7 @@ class OpenAIChatCompletionAssistantMessage(TypedDict, total=False): name: Optional[str] tool_calls: Optional[List[ChatCompletionAssistantToolCall]] function_call: Optional[ChatCompletionToolCallFunctionChunk] + reasoning_content: Optional[str] class ChatCompletionAssistantMessage(OpenAIChatCompletionAssistantMessage, total=False): @@ -678,6 +692,11 @@ class ChatCompletionDeveloperMessage(OpenAIChatCompletionDeveloperMessage, total cache_control: ChatCompletionCachedContent +class GenericChatCompletionMessage(TypedDict, total=False): + role: Required[str] + content: Required[Union[str, List]] + + ValidUserMessageContentTypes = [ "text", "image_url", @@ -785,7 +804,9 @@ class ChatCompletionResponseMessage(TypedDict, total=False): function_call: Optional[ChatCompletionToolCallFunctionChunk] provider_specific_fields: Optional[dict] reasoning_content: Optional[str] - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] + thinking_blocks: Optional[ + List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]] + ] class ChatCompletionUsageBlock(TypedDict): @@ -803,12 +824,12 @@ class OpenAIChatCompletionChunk(ChatCompletionChunk): class Hyperparameters(BaseModel): batch_size: Optional[Union[str, int]] = None # "Number of examples in each batch." - learning_rate_multiplier: Optional[ - Union[str, float] - ] = None # Scaling factor for the learning rate - n_epochs: Optional[ - Union[str, int] - ] = None # "The number of epochs to train the model for" + learning_rate_multiplier: Optional[Union[str, float]] = ( + None # Scaling factor for the learning rate + ) + n_epochs: Optional[Union[str, int]] = ( + None # "The number of epochs to train the model for" + ) class FineTuningJobCreate(BaseModel): @@ -835,18 +856,18 @@ class FineTuningJobCreate(BaseModel): model: str # "The name of the model to fine-tune." training_file: str # "The ID of an uploaded file that contains training data." - hyperparameters: Optional[ - Hyperparameters - ] = None # "The hyperparameters used for the fine-tuning job." - suffix: Optional[ - str - ] = None # "A string of up to 18 characters that will be added to your fine-tuned model name." - validation_file: Optional[ - str - ] = None # "The ID of an uploaded file that contains validation data." - integrations: Optional[ - List[str] - ] = None # "A list of integrations to enable for your fine-tuning job." + hyperparameters: Optional[Hyperparameters] = ( + None # "The hyperparameters used for the fine-tuning job." + ) + suffix: Optional[str] = ( + None # "A string of up to 18 characters that will be added to your fine-tuned model name." + ) + validation_file: Optional[str] = ( + None # "The ID of an uploaded file that contains validation data." + ) + integrations: Optional[List[str]] = ( + None # "A list of integrations to enable for your fine-tuning job." + ) seed: Optional[int] = None # "The seed controls the reproducibility of the job." @@ -872,6 +893,19 @@ OpenAIAudioTranscriptionOptionalParams = Literal[ OpenAIImageVariationOptionalParams = Literal["n", "size", "response_format", "user"] +class ComputerToolParam(TypedDict, total=False): + display_height: Required[float] + """The height of the computer display.""" + + display_width: Required[float] + """The width of the computer display.""" + + environment: Required[Union[Literal["mac", "windows", "ubuntu", "browser"], str]] + """The type of computer environment to control.""" + + type: Required[Union[Literal["computer_use_preview"], str]] + + class ResponsesAPIOptionalRequestParams(TypedDict, total=False): """TypedDict for Optional parameters supported by the responses API.""" @@ -887,7 +921,7 @@ class ResponsesAPIOptionalRequestParams(TypedDict, total=False): temperature: Optional[float] text: Optional[ResponseTextConfigParam] tool_choice: Optional[ToolChoice] - tools: Optional[Iterable[ToolParam]] + tools: Optional[List[Union[ToolParam, ComputerToolParam]]] top_p: Optional[float] truncation: Optional[Literal["auto", "disabled"]] user: Optional[str] @@ -900,20 +934,6 @@ class ResponsesAPIRequestParams(ResponsesAPIOptionalRequestParams, total=False): model: str -class BaseLiteLLMOpenAIResponseObject(BaseModel): - def __getitem__(self, key): - return self.__dict__[key] - - def get(self, key, default=None): - return self.__dict__.get(key, default) - - def __contains__(self, key): - return key in self.__dict__ - - def items(self): - return self.__dict__.items() - - class OutputTokensDetails(BaseLiteLLMOpenAIResponseObject): reasoning_tokens: Optional[int] = None @@ -958,11 +978,14 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject): metadata: Optional[Dict] model: Optional[str] object: Optional[str] - output: List[ResponseOutputItem] + output: Union[ + List[ResponseOutputItem], + List[Union[GenericResponseOutputItem, OutputFunctionToolCall]], + ] parallel_tool_calls: bool temperature: Optional[float] tool_choice: ToolChoice - tools: List[Tool] + tools: Union[List[Tool], List[ResponseFunctionToolCall]] top_p: Optional[float] max_output_tokens: Optional[int] previous_response_id: Optional[str] @@ -990,6 +1013,9 @@ class ResponsesAPIStreamEvents(str, Enum): RESPONSE_FAILED = "response.failed" RESPONSE_INCOMPLETE = "response.incomplete" + # Part added + RESPONSE_PART_ADDED = "response.reasoning_summary_part.added" + # Output item events OUTPUT_ITEM_ADDED = "response.output_item.added" OUTPUT_ITEM_DONE = "response.output_item.done" @@ -1177,6 +1203,12 @@ class ErrorEvent(BaseLiteLLMOpenAIResponseObject): param: Optional[str] +class GenericEvent(BaseLiteLLMOpenAIResponseObject): + type: str + + model_config = ConfigDict(extra="allow", protected_namespaces=()) + + # Union type for all possible streaming responses ResponsesAPIStreamingResponse = Annotated[ Union[ @@ -1203,6 +1235,7 @@ ResponsesAPIStreamingResponse = Annotated[ WebSearchCallSearchingEvent, WebSearchCallCompletedEvent, ErrorEvent, + GenericEvent, ], Discriminator("type"), ] @@ -1226,3 +1259,12 @@ class OpenAIRealtimeStreamResponseBaseObject(TypedDict): OpenAIRealtimeStreamList = List[ Union[OpenAIRealtimeStreamResponseBaseObject, OpenAIRealtimeStreamSessionEvents] ] + + +class ImageGenerationRequestQuality(str, Enum): + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + AUTO = "auto" + STANDARD = "standard" + HD = "hd" diff --git a/litellm/types/llms/vertex_ai.py b/litellm/types/llms/vertex_ai.py index 55273371fc..6ac7ae6275 100644 --- a/litellm/types/llms/vertex_ai.py +++ b/litellm/types/llms/vertex_ai.py @@ -39,6 +39,7 @@ class PartType(TypedDict, total=False): file_data: FileDataType function_call: FunctionCall function_response: FunctionResponse + thought: bool class HttpxFunctionCall(TypedDict): @@ -69,6 +70,7 @@ class HttpxPartType(TypedDict, total=False): functionResponse: FunctionResponse executableCode: HttpxExecutableCode codeExecutionResult: HttpxCodeExecutionResult + thought: bool class HttpxContentType(TypedDict, total=False): @@ -166,6 +168,11 @@ class SafetSettingsConfig(TypedDict, total=False): method: HarmBlockMethod +class GeminiThinkingConfig(TypedDict, total=False): + includeThoughts: bool + thinkingBudget: int + + class GenerationConfig(TypedDict, total=False): temperature: float top_p: float @@ -181,6 +188,7 @@ class GenerationConfig(TypedDict, total=False): responseLogprobs: bool logprobs: int responseModalities: List[Literal["TEXT", "IMAGE", "AUDIO", "VIDEO"]] + thinkingConfig: GeminiThinkingConfig class Tools(TypedDict, total=False): @@ -212,6 +220,7 @@ class UsageMetadata(TypedDict, total=False): candidatesTokenCount: int cachedContentTokenCount: int promptTokensDetails: List[PromptTokensDetails] + thoughtsTokenCount: int class CachedContent(TypedDict, total=False): diff --git a/litellm/proxy/pass_through_endpoints/types.py b/litellm/types/passthrough_endpoints/pass_through_endpoints.py similarity index 62% rename from litellm/proxy/pass_through_endpoints/types.py rename to litellm/types/passthrough_endpoints/pass_through_endpoints.py index 59047a630d..ef4e2a0a25 100644 --- a/litellm/proxy/pass_through_endpoints/types.py +++ b/litellm/types/passthrough_endpoints/pass_through_endpoints.py @@ -14,5 +14,21 @@ class PassthroughStandardLoggingPayload(TypedDict, total=False): """ url: str + """ + The full url of the request + """ + + request_method: Optional[str] + """ + The method of the request + "GET", "POST", "PUT", "DELETE", etc. + """ + request_body: Optional[dict] + """ + The body of the request + """ response_body: Optional[dict] # only tracked for non-streaming responses + """ + The body of the response + """ diff --git a/litellm/types/proxy/management_endpoints/common_daily_activity.py b/litellm/types/proxy/management_endpoints/common_daily_activity.py index 9408035746..6213087f64 100644 --- a/litellm/types/proxy/management_endpoints/common_daily_activity.py +++ b/litellm/types/proxy/management_endpoints/common_daily_activity.py @@ -39,6 +39,7 @@ class KeyMetadata(BaseModel): """Metadata for a key""" key_alias: Optional[str] = None + team_id: Optional[str] = None class KeyMetricWithMetadata(MetricBase): diff --git a/litellm/types/proxy/management_endpoints/internal_user_endpoints.py b/litellm/types/proxy/management_endpoints/internal_user_endpoints.py new file mode 100644 index 0000000000..5c2c5bf371 --- /dev/null +++ b/litellm/types/proxy/management_endpoints/internal_user_endpoints.py @@ -0,0 +1,18 @@ +from typing import Any, Dict, List, Literal, Optional, Union + +from fastapi import HTTPException +from pydantic import BaseModel, EmailStr + +from litellm.proxy._types import LiteLLM_UserTableWithKeyCount + + +class UserListResponse(BaseModel): + """ + Response model for the user list endpoint + """ + + users: List[LiteLLM_UserTableWithKeyCount] + total: int + page: int + page_size: int + total_pages: int diff --git a/litellm/types/responses/main.py b/litellm/types/responses/main.py new file mode 100644 index 0000000000..b85df206bc --- /dev/null +++ b/litellm/types/responses/main.py @@ -0,0 +1,76 @@ +from typing import Literal + +from pydantic import PrivateAttr +from typing_extensions import Any, List, Optional, TypedDict + +from litellm.types.llms.base import BaseLiteLLMOpenAIResponseObject + + +class GenericResponseOutputItemContentAnnotation(BaseLiteLLMOpenAIResponseObject): + """Annotation for content in a message""" + + type: Optional[str] + start_index: Optional[int] + end_index: Optional[int] + url: Optional[str] + title: Optional[str] + pass + + +class OutputText(BaseLiteLLMOpenAIResponseObject): + """Text output content from an assistant message""" + + type: Optional[str] # "output_text" + text: Optional[str] + annotations: Optional[List[GenericResponseOutputItemContentAnnotation]] + + +class OutputFunctionToolCall(BaseLiteLLMOpenAIResponseObject): + """A tool call to run a function""" + + arguments: Optional[str] + call_id: Optional[str] + name: Optional[str] + type: Optional[str] # "function_call" + id: Optional[str] + status: Literal["in_progress", "completed", "incomplete"] + + +class GenericResponseOutputItem(BaseLiteLLMOpenAIResponseObject): + """ + Generic response API output item + + """ + + type: str # "message" + id: str + status: str # "completed", "in_progress", etc. + role: str # "assistant", "user", etc. + content: List[OutputText] + + +class DeleteResponseResult(BaseLiteLLMOpenAIResponseObject): + """ + Result of a delete response request + + { + "id": "resp_6786a1bec27481909a17d673315b29f6", + "object": "response", + "deleted": true + } + """ + + id: Optional[str] + object: Optional[str] + deleted: Optional[bool] + + # Define private attributes using PrivateAttr + _hidden_params: dict = PrivateAttr(default_factory=dict) + + +class DecodedResponseId(TypedDict, total=False): + """Structure representing a decoded response ID""" + + custom_llm_provider: Optional[str] + model_id: Optional[str] + response_id: str diff --git a/litellm/types/router.py b/litellm/types/router.py index fa8273e7b2..22467fa3b0 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -709,7 +709,11 @@ class GenericBudgetWindowDetails(BaseModel): ttl_seconds: int -OptionalPreCallChecks = List[Literal["prompt_caching", "router_budget_limiting"]] +OptionalPreCallChecks = List[ + Literal[ + "prompt_caching", "router_budget_limiting", "responses_api_deployment_check" + ] +] class LiteLLM_RouterFileObject(TypedDict, total=False): diff --git a/litellm/types/tag_management.py b/litellm/types/tag_management.py index e530b37cab..a3615b658c 100644 --- a/litellm/types/tag_management.py +++ b/litellm/types/tag_management.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Dict, List, Optional from pydantic import BaseModel @@ -30,3 +31,23 @@ class TagDeleteRequest(BaseModel): class TagInfoRequest(BaseModel): names: List[str] + + +class LiteLLM_DailyTagSpendTable(BaseModel): + id: str + tag: str + date: str + api_key: str + model: str + model_group: Optional[str] + custom_llm_provider: Optional[str] + prompt_tokens: int + completion_tokens: int + cache_read_input_tokens: int + cache_creation_input_tokens: int + spend: float + api_requests: int + successful_requests: int + failed_requests: int + created_at: datetime + updated_at: datetime diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 35a584b6cf..532162e60f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -29,6 +29,7 @@ from .guardrails import GuardrailEventHooks from .llms.openai import ( Batch, ChatCompletionAnnotation, + ChatCompletionRedactedThinkingBlock, ChatCompletionThinkingBlock, ChatCompletionToolCallChunk, ChatCompletionUsageBlock, @@ -150,6 +151,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): ] # only for vertex ai models output_cost_per_image: Optional[float] output_vector_size: Optional[int] + output_cost_per_reasoning_token: Optional[float] output_cost_per_video_per_second: Optional[float] # only for vertex ai models output_cost_per_audio_per_second: Optional[float] # only for vertex ai models output_cost_per_second: Optional[float] # for OpenAI Speech models @@ -377,12 +379,18 @@ class Function(OpenAIObject): def __init__( self, - arguments: Optional[Union[Dict, str]], + arguments: Optional[Union[Dict, str]] = None, name: Optional[str] = None, **params, ): if arguments is None: - arguments = "" + if params.get("parameters", None) is not None and isinstance( + params["parameters"], dict + ): + arguments = json.dumps(params["parameters"]) + params.pop("parameters") + else: + arguments = "" elif isinstance(arguments, Dict): arguments = json.dumps(arguments) else: @@ -391,7 +399,7 @@ class Function(OpenAIObject): name = name # Build a dictionary with the structure your BaseModel expects - data = {"arguments": arguments, "name": name, **params} + data = {"arguments": arguments, "name": name} super(Function, self).__init__(**data) @@ -545,7 +553,9 @@ class Message(OpenAIObject): function_call: Optional[FunctionCall] audio: Optional[ChatCompletionAudioResponse] = None reasoning_content: Optional[str] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]] + ] = None provider_specific_fields: Optional[Dict[str, Any]] = Field( default=None, exclude=True ) @@ -560,7 +570,11 @@ class Message(OpenAIObject): audio: Optional[ChatCompletionAudioResponse] = None, provider_specific_fields: Optional[Dict[str, Any]] = None, reasoning_content: Optional[str] = None, - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None, + thinking_blocks: Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ] = None, annotations: Optional[List[ChatCompletionAnnotation]] = None, **params, ): @@ -643,7 +657,9 @@ class Message(OpenAIObject): class Delta(OpenAIObject): reasoning_content: Optional[str] = None - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None + thinking_blocks: Optional[ + List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]] + ] = None provider_specific_fields: Optional[Dict[str, Any]] = Field(default=None) def __init__( @@ -654,7 +670,11 @@ class Delta(OpenAIObject): tool_calls=None, audio: Optional[ChatCompletionAudioResponse] = None, reasoning_content: Optional[str] = None, - thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None, + thinking_blocks: Optional[ + List[ + Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] + ] + ] = None, annotations: Optional[List[ChatCompletionAnnotation]] = None, **params, ): @@ -829,8 +849,11 @@ class Usage(CompletionUsage): # handle reasoning_tokens _completion_tokens_details: Optional[CompletionTokensDetailsWrapper] = None if reasoning_tokens: + text_tokens = ( + completion_tokens - reasoning_tokens if completion_tokens else None + ) completion_tokens_details = CompletionTokensDetailsWrapper( - reasoning_tokens=reasoning_tokens + reasoning_tokens=reasoning_tokens, text_tokens=text_tokens ) # Ensure completion_tokens_details is properly handled @@ -2231,6 +2254,10 @@ class SpecialEnums(Enum): LITELM_MANAGED_FILE_ID_PREFIX = "litellm_proxy" LITELLM_MANAGED_FILE_COMPLETE_STR = "litellm_proxy:{};unified_id,{}" + LITELLM_MANAGED_RESPONSE_COMPLETE_STR = ( + "litellm:custom_llm_provider:{};model_id:{};response_id:{}" + ) + LLMResponseTypes = Union[ ModelResponse, EmbeddingResponse, ImageResponse, OpenAIFileObject diff --git a/litellm/utils.py b/litellm/utils.py index 70a48a9360..98a9c34b47 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -180,10 +180,18 @@ from litellm.types.utils import ( all_litellm_params, ) -with resources.open_text( - "litellm.litellm_core_utils.tokenizers", "anthropic_tokenizer.json" -) as f: - json_data = json.load(f) +try: + # Python 3.9+ + with resources.files("litellm.litellm_core_utils.tokenizers").joinpath( + "anthropic_tokenizer.json" + ).open("r") as f: + json_data = json.load(f) +except (ImportError, AttributeError, TypeError): + with resources.open_text( + "litellm.litellm_core_utils.tokenizers", "anthropic_tokenizer.json" + ) as f: + json_data = json.load(f) + # Convert to str (if necessary) claude_json_str = json.dumps(json_data) import importlib.metadata @@ -516,9 +524,9 @@ def function_setup( # noqa: PLR0915 function_id: Optional[str] = kwargs["id"] if "id" in kwargs else None ## DYNAMIC CALLBACKS ## - dynamic_callbacks: Optional[ - List[Union[str, Callable, CustomLogger]] - ] = kwargs.pop("callbacks", None) + dynamic_callbacks: Optional[List[Union[str, Callable, CustomLogger]]] = ( + kwargs.pop("callbacks", None) + ) all_callbacks = get_dynamic_callbacks(dynamic_callbacks=dynamic_callbacks) if len(all_callbacks) > 0: @@ -1202,9 +1210,9 @@ def client(original_function): # noqa: PLR0915 exception=e, retry_policy=kwargs.get("retry_policy"), ) - kwargs[ - "retry_policy" - ] = reset_retry_policy() # prevent infinite loops + kwargs["retry_policy"] = ( + reset_retry_policy() + ) # prevent infinite loops litellm.num_retries = ( None # set retries to None to prevent infinite loops ) @@ -2735,6 +2743,21 @@ def get_optional_params_embeddings( # noqa: PLR0915 ) final_params = {**optional_params, **kwargs} return final_params + elif custom_llm_provider == "infinity": + supported_params = get_supported_openai_params( + model=model, + custom_llm_provider="infinity", + request_type="embeddings", + ) + _check_valid_arg(supported_params=supported_params) + optional_params = litellm.InfinityEmbeddingConfig().map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model=model, + drop_params=drop_params if drop_params is not None else False, + ) + final_params = {**optional_params, **kwargs} + return final_params elif custom_llm_provider == "fireworks_ai": supported_params = get_supported_openai_params( model=model, @@ -3013,16 +3036,16 @@ def get_optional_params( # noqa: PLR0915 True # so that main.py adds the function call to the prompt ) if "tools" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("tools") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("tools") + ) non_default_params.pop( "tool_choice", None ) # causes ollama requests to hang elif "functions" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("functions") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("functions") + ) elif ( litellm.add_function_to_prompt ): # if user opts to add it to prompt instead @@ -3045,10 +3068,10 @@ def get_optional_params( # noqa: PLR0915 if "response_format" in non_default_params: if provider_config is not None: - non_default_params[ - "response_format" - ] = provider_config.get_json_schema_from_pydantic_object( - response_format=non_default_params["response_format"] + non_default_params["response_format"] = ( + provider_config.get_json_schema_from_pydantic_object( + response_format=non_default_params["response_format"] + ) ) else: non_default_params["response_format"] = type_to_response_format_param( @@ -4064,9 +4087,9 @@ def _count_characters(text: str) -> int: def get_response_string(response_obj: Union[ModelResponse, ModelResponseStream]) -> str: - _choices: Union[ - List[Union[Choices, StreamingChoices]], List[StreamingChoices] - ] = response_obj.choices + _choices: Union[List[Union[Choices, StreamingChoices]], List[StreamingChoices]] = ( + response_obj.choices + ) response_str = "" for choice in _choices: @@ -4563,6 +4586,9 @@ def _get_model_info_helper( # noqa: PLR0915 output_cost_per_character=_model_info.get( "output_cost_per_character", None ), + output_cost_per_reasoning_token=_model_info.get( + "output_cost_per_reasoning_token", None + ), output_cost_per_token_above_128k_tokens=_model_info.get( "output_cost_per_token_above_128k_tokens", None ), @@ -5117,6 +5143,11 @@ def validate_environment( # noqa: PLR0915 keys_in_environment = True else: missing_keys.append("VOYAGE_API_KEY") + elif custom_llm_provider == "infinity": + if "INFINITY_API_KEY" in os.environ: + keys_in_environment = True + else: + missing_keys.append("INFINITY_API_KEY") elif custom_llm_provider == "fireworks_ai": if ( "FIREWORKS_AI_API_KEY" in os.environ @@ -6261,24 +6292,27 @@ def validate_and_fix_openai_messages(messages: List): Handles missing role for assistant messages. """ + new_messages = [] for message in messages: if not message.get("role"): message["role"] = "assistant" if message.get("tool_calls"): message["tool_calls"] = jsonify_tools(tools=message["tool_calls"]) - return validate_chat_completion_messages(messages=messages) + + convert_msg_to_dict = cast(AllMessageValues, convert_to_dict(message)) + cleaned_message = cleanup_none_field_in_message(message=convert_msg_to_dict) + new_messages.append(cleaned_message) + return validate_chat_completion_user_messages(messages=new_messages) -def validate_chat_completion_messages(messages: List[AllMessageValues]): +def cleanup_none_field_in_message(message: AllMessageValues): """ - Ensures all messages are valid OpenAI chat completion messages. + Cleans up the message by removing the none field. + + remove None fields in the message - e.g. {"function": None} - some providers raise validation errors """ - # 1. convert all messages to dict - messages = [ - cast(AllMessageValues, convert_to_dict(cast(dict, m))) for m in messages - ] - # 2. validate user messages - return validate_chat_completion_user_messages(messages=messages) + new_message = message.copy() + return {k: v for k, v in new_message.items() if v is not None} def validate_chat_completion_user_messages(messages: List[AllMessageValues]): @@ -6548,6 +6582,8 @@ class ProviderConfigManager: return litellm.TritonEmbeddingConfig() elif litellm.LlmProviders.WATSONX == provider: return litellm.IBMWatsonXEmbeddingConfig() + elif litellm.LlmProviders.INFINITY == provider: + return litellm.InfinityEmbeddingConfig() raise ValueError(f"Provider {provider.value} does not support embedding config") @staticmethod @@ -6597,11 +6633,13 @@ class ProviderConfigManager: @staticmethod def get_provider_responses_api_config( - model: str, provider: LlmProviders, + model: Optional[str] = None, ) -> Optional[BaseResponsesAPIConfig]: if litellm.LlmProviders.OPENAI == provider: return litellm.OpenAIResponsesAPIConfig() + elif litellm.LlmProviders.AZURE == provider: + return litellm.AzureOpenAIResponsesAPIConfig() return None @staticmethod diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 9918743bc9..55052761c7 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -5,6 +5,7 @@ "max_output_tokens": "max output tokens, if the provider specifies it. if not default to max_tokens", "input_cost_per_token": 0.0000, "output_cost_per_token": 0.000, + "output_cost_per_reasoning_token": 0.000, "litellm_provider": "one of https://docs.litellm.ai/docs/providers", "mode": "one of: chat, embedding, completion, image_generation, audio_transcription, audio_speech, image_generation, moderation, rerank", "supports_function_calling": true, @@ -1436,6 +1437,76 @@ "output_cost_per_pixel": 0.0, "litellm_provider": "openai" }, + "gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0490417e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1024-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.59263611e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0172526e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1024-x-1536/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.58945719e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "low/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.0172526e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "medium/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 4.0054321e-8, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, + "high/1536-x-1024/gpt-image-1": { + "mode": "image_generation", + "input_cost_per_pixel": 1.58945719e-7, + "output_cost_per_pixel": 0.0, + "litellm_provider": "openai", + "supported_endpoints": ["/v1/images/generations"] + }, "gpt-4o-transcribe": { "mode": "audio_transcription", "input_cost_per_token": 0.0000025, @@ -1471,6 +1542,294 @@ "litellm_provider": "openai", "supported_endpoints": ["/v1/audio/speech"] }, + "azure/computer-use-preview": { + "max_tokens": 1024, + "max_input_tokens": 8192, + "max_output_tokens": 1024, + "input_cost_per_token": 0.000003, + "output_cost_per_token": 0.000012, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_reasoning": true + }, + "azure/gpt-4o-audio-preview-2024-12-17": { + "max_tokens": 16384, + "max_input_tokens": 128000, + "max_output_tokens": 16384, + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.00004, + "output_cost_per_token": 0.00001, + "output_cost_per_audio_token": 0.00008, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions"], + "supported_modalities": ["text", "audio"], + "supported_output_modalities": ["text", "audio"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": false, + "supports_vision": false, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_reasoning": false + }, + "azure/gpt-4o-mini-audio-preview-2024-12-17": { + "max_tokens": 16384, + "max_input_tokens": 128000, + "max_output_tokens": 16384, + "input_cost_per_token": 0.0000025, + "input_cost_per_audio_token": 0.00004, + "output_cost_per_token": 0.00001, + "output_cost_per_audio_token": 0.00008, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions"], + "supported_modalities": ["text", "audio"], + "supported_output_modalities": ["text", "audio"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": false, + "supports_vision": false, + "supports_prompt_caching": false, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_reasoning": false + }, + "azure/gpt-4.1": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 2e-6, + "output_cost_per_token": 8e-6, + "input_cost_per_token_batches": 1e-6, + "output_cost_per_token_batches": 4e-6, + "cache_read_input_token_cost": 0.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 30e-3, + "search_context_size_medium": 35e-3, + "search_context_size_high": 50e-3 + } + }, + "azure/gpt-4.1-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 2e-6, + "output_cost_per_token": 8e-6, + "input_cost_per_token_batches": 1e-6, + "output_cost_per_token_batches": 4e-6, + "cache_read_input_token_cost": 0.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 30e-3, + "search_context_size_medium": 35e-3, + "search_context_size_high": 50e-3 + } + }, + "azure/gpt-4.1-mini": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.4e-6, + "output_cost_per_token": 1.6e-6, + "input_cost_per_token_batches": 0.2e-6, + "output_cost_per_token_batches": 0.8e-6, + "cache_read_input_token_cost": 0.1e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 25e-3, + "search_context_size_medium": 27.5e-3, + "search_context_size_high": 30e-3 + } + }, + "azure/gpt-4.1-mini-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.4e-6, + "output_cost_per_token": 1.6e-6, + "input_cost_per_token_batches": 0.2e-6, + "output_cost_per_token_batches": 0.8e-6, + "cache_read_input_token_cost": 0.1e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true, + "supports_web_search": true, + "search_context_cost_per_query": { + "search_context_size_low": 25e-3, + "search_context_size_medium": 27.5e-3, + "search_context_size_high": 30e-3 + } + }, + "azure/gpt-4.1-nano": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.1e-6, + "output_cost_per_token": 0.4e-6, + "input_cost_per_token_batches": 0.05e-6, + "output_cost_per_token_batches": 0.2e-6, + "cache_read_input_token_cost": 0.025e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true + }, + "azure/gpt-4.1-nano-2025-04-14": { + "max_tokens": 32768, + "max_input_tokens": 1047576, + "max_output_tokens": 32768, + "input_cost_per_token": 0.1e-6, + "output_cost_per_token": 0.4e-6, + "input_cost_per_token_batches": 0.05e-6, + "output_cost_per_token_batches": 0.2e-6, + "cache_read_input_token_cost": 0.025e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_response_schema": true, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_native_streaming": true + }, + "azure/o3": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1e-5, + "output_cost_per_token": 4e-5, + "cache_read_input_token_cost": 2.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "azure/o3-2025-04-16": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1e-5, + "output_cost_per_token": 4e-5, + "cache_read_input_token_cost": 2.5e-6, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "azure/o4-mini": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1.1e-6, + "output_cost_per_token": 4.4e-6, + "cache_read_input_token_cost": 2.75e-7, + "litellm_provider": "azure", + "mode": "chat", + "supported_endpoints": ["/v1/chat/completions", "/v1/batch", "/v1/responses"], + "supported_modalities": ["text", "image"], + "supported_output_modalities": ["text"], + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "azure/gpt-4o-mini-realtime-preview-2024-12-17": { "max_tokens": 4096, "max_input_tokens": 128000, @@ -1647,6 +2006,23 @@ "supports_system_messages": true, "supports_tool_choice": true }, + "azure/o4-mini-2025-04-16": { + "max_tokens": 100000, + "max_input_tokens": 200000, + "max_output_tokens": 100000, + "input_cost_per_token": 1.1e-6, + "output_cost_per_token": 4.4e-6, + "cache_read_input_token_cost": 2.75e-7, + "litellm_provider": "azure", + "mode": "chat", + "supports_function_calling": true, + "supports_parallel_function_calling": false, + "supports_vision": true, + "supports_prompt_caching": true, + "supports_response_schema": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "azure/o3-mini-2025-01-31": { "max_tokens": 100000, "max_input_tokens": 200000, @@ -5093,6 +5469,64 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini/gemini-2.5-flash-preview-04-17": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 1e-6, + "input_cost_per_token": 0.15e-6, + "output_cost_per_token": 0.6e-6, + "output_cost_per_reasoning_token": 3.5e-6, + "litellm_provider": "gemini", + "mode": "chat", + "rpm": 10, + "tpm": 250000, + "supports_system_messages": true, + "supports_function_calling": true, + "supports_vision": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_audio_output": false, + "supports_tool_choice": true, + "supported_endpoints": ["/v1/chat/completions", "/v1/completions"], + "supported_modalities": ["text", "image", "audio", "video"], + "supported_output_modalities": ["text"], + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview" + }, + "gemini-2.5-flash-preview-04-17": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 1e-6, + "input_cost_per_token": 0.15e-6, + "output_cost_per_token": 0.6e-6, + "output_cost_per_reasoning_token": 3.5e-6, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "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-preview" + }, "gemini-2.0-flash": { "max_tokens": 8192, "max_input_tokens": 1048576, @@ -5167,6 +5601,35 @@ "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#gemini-2.0-flash", "supports_tool_choice": true }, + "gemini-2.5-pro-preview-03-25": { + "max_tokens": 65536, + "max_input_tokens": 1048576, + "max_output_tokens": 65536, + "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": 0.00000125, + "input_cost_per_token": 0.00000125, + "input_cost_per_token_above_200k_tokens": 0.0000025, + "output_cost_per_token": 0.00001, + "output_cost_per_token_above_200k_tokens": 0.000015, + "litellm_provider": "vertex_ai-language-models", + "mode": "chat", + "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-preview" + }, "gemini/gemini-2.0-pro-exp-02-05": { "max_tokens": 8192, "max_input_tokens": 2097152, diff --git a/poetry.lock b/poetry.lock index 0bbd69dc75..1ad74a07a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -548,7 +548,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev", "proxy-dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -742,6 +742,24 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["dev", "proxy-dev"] +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + [[package]] name = "distro" version = "1.9.0" @@ -1116,14 +1134,14 @@ protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4 name = "googleapis-common-protos" version = "1.70.0" description = "Common protobufs used in Google APIs" -optional = true +optional = false python-versions = ">=3.7" -groups = ["main"] -markers = "extra == \"extra-proxy\"" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] +markers = {main = "extra == \"extra-proxy\""} [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""} @@ -1154,10 +1172,9 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 name = "grpcio" version = "1.70.0" description = "HTTP/2-based RPC framework" -optional = true +optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.10\" and extra == \"extra-proxy\"" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, @@ -1215,6 +1232,7 @@ files = [ {file = "grpcio-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:a31d7e3b529c94e930a117b2175b2efd179d96eb3c7a21ccb0289a8ab05b645c"}, {file = "grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56"}, ] +markers = {main = "python_version < \"3.10\" and extra == \"extra-proxy\"", dev = "python_version < \"3.10\"", proxy-dev = "python_version < \"3.10\""} [package.extras] protobuf = ["grpcio-tools (>=1.70.0)"] @@ -1223,10 +1241,9 @@ protobuf = ["grpcio-tools (>=1.70.0)"] name = "grpcio" version = "1.71.0" description = "HTTP/2-based RPC framework" -optional = true +optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"extra-proxy\"" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, @@ -1280,45 +1297,28 @@ files = [ {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, ] +markers = {main = "python_version >= \"3.10\" and extra == \"extra-proxy\"", dev = "python_version >= \"3.10\"", proxy-dev = "python_version >= \"3.10\""} [package.extras] protobuf = ["grpcio-tools (>=1.71.0)"] [[package]] name = "grpcio-status" -version = "1.70.0" +version = "1.62.3" description = "Status proto mapping for gRPC" optional = true -python-versions = ">=3.8" +python-versions = ">=3.6" groups = ["main"] -markers = "python_version < \"3.10\" and extra == \"extra-proxy\"" +markers = "extra == \"extra-proxy\"" files = [ - {file = "grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85"}, - {file = "grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101"}, + {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, + {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.70.0" -protobuf = ">=5.26.1,<6.0dev" - -[[package]] -name = "grpcio-status" -version = "1.71.0" -description = "Status proto mapping for gRPC" -optional = true -python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"extra-proxy\" and python_version >= \"3.10\"" -files = [ - {file = "grpcio_status-1.71.0-py3-none-any.whl", hash = "sha256:843934ef8c09e3e858952887467f8256aac3910c55f077a359a65b2b3cde3e68"}, - {file = "grpcio_status-1.71.0.tar.gz", hash = "sha256:11405fed67b68f406b3f3c7c5ae5104a79d2d309666d10d61b152e91d28fb968"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.71.0" -protobuf = ">=5.26.1,<6.0dev" +grpcio = ">=1.62.3" +protobuf = ">=4.21.6" [[package]] name = "gunicorn" @@ -1550,27 +1550,23 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "7.1.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev", "proxy-dev"] files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] -zipp = ">=3.20" +zipp = ">=0.5" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "importlib-resources" @@ -1780,14 +1776,14 @@ referencing = ">=0.31.0" [[package]] name = "litellm-proxy-extras" -version = "0.1.10" +version = "0.1.11" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_proxy_extras-0.1.10.tar.gz", hash = "sha256:0188a53316668f15e1f8a0e0d322bda8c366787e42e344db388ed97ad7c4cd95"}, + {file = "litellm_proxy_extras-0.1.11.tar.gz", hash = "sha256:9c170209b2f0b64c16aebe9a35866fa457142aee5fab51476e3b4b76d9ca4e9e"}, ] [[package]] @@ -2326,6 +2322,142 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] +[[package]] +name = "opentelemetry-api" +version = "1.25.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, + {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<=7.1" + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.25.0" +description = "OpenTelemetry Collector Exporters" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_exporter_otlp-1.25.0-py3-none-any.whl", hash = "sha256:d67a831757014a3bc3174e4cd629ae1493b7ba8d189e8a007003cacb9f1a6b60"}, + {file = "opentelemetry_exporter_otlp-1.25.0.tar.gz", hash = "sha256:ce03199c1680a845f82e12c0a6a8f61036048c07ec7a0bd943142aca8fa6ced0"}, +] + +[package.dependencies] +opentelemetry-exporter-otlp-proto-grpc = "1.25.0" +opentelemetry-exporter-otlp-proto-http = "1.25.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.25.0" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, +] + +[package.dependencies] +opentelemetry-proto = "1.25.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.25.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl", hash = "sha256:3131028f0c0a155a64c430ca600fd658e8e37043cb13209f0109db5c1a3e4eb4"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0.tar.gz", hash = "sha256:c0b1661415acec5af87625587efa1ccab68b873745ca0ee96b69bb1042087eac"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.25.0" +opentelemetry-proto = "1.25.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.25.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0-py3-none-any.whl", hash = "sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0.tar.gz", hash = "sha256:9f8723859e37c75183ea7afa73a3542f01d0fd274a5b97487ea24cb683d7d684"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.25.0" +opentelemetry-proto = "1.25.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" +requests = ">=2.7,<3.0" + +[[package]] +name = "opentelemetry-proto" +version = "1.25.0" +description = "OpenTelemetry Python Proto" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, + {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.25.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, + {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" +opentelemetry-semantic-conventions = "0.46b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.46b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, + {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" + [[package]] name = "orjson" version = "3.10.15" @@ -2668,25 +2800,25 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "5.29.4" +version = "4.25.6" description = "" -optional = true +optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "extra == \"extra-proxy\"" +groups = ["main", "dev", "proxy-dev"] files = [ - {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"}, - {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"}, - {file = "protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0"}, - {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e"}, - {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922"}, - {file = "protobuf-5.29.4-cp38-cp38-win32.whl", hash = "sha256:1832f0515b62d12d8e6ffc078d7e9eb06969aa6dc13c13e1036e39d73bebc2de"}, - {file = "protobuf-5.29.4-cp38-cp38-win_amd64.whl", hash = "sha256:476cb7b14914c780605a8cf62e38c2a85f8caff2e28a6a0bad827ec7d6c85d68"}, - {file = "protobuf-5.29.4-cp39-cp39-win32.whl", hash = "sha256:fd32223020cb25a2cc100366f1dedc904e2d71d9322403224cdde5fdced0dabe"}, - {file = "protobuf-5.29.4-cp39-cp39-win_amd64.whl", hash = "sha256:678974e1e3a9b975b8bc2447fca458db5f93a2fb6b0c8db46b6675b5b5346812"}, - {file = "protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862"}, - {file = "protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99"}, + {file = "protobuf-4.25.6-cp310-abi3-win32.whl", hash = "sha256:61df6b5786e2b49fc0055f636c1e8f0aff263808bb724b95b164685ac1bcc13a"}, + {file = "protobuf-4.25.6-cp310-abi3-win_amd64.whl", hash = "sha256:b8f837bfb77513fe0e2f263250f423217a173b6d85135be4d81e96a4653bcd3c"}, + {file = "protobuf-4.25.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4381f2417606d7e01750e2729fe6fbcda3f9883aa0c32b51d23012bded6c91"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5dd800da412ba7f6f26d2c08868a5023ce624e1fdb28bccca2dc957191e81fb5"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4434ff8bb5576f9e0c78f47c41cdf3a152c0b44de475784cd3fd170aef16205a"}, + {file = "protobuf-4.25.6-cp38-cp38-win32.whl", hash = "sha256:8bad0f9e8f83c1fbfcc34e573352b17dfce7d0519512df8519994168dc015d7d"}, + {file = "protobuf-4.25.6-cp38-cp38-win_amd64.whl", hash = "sha256:b6905b68cde3b8243a198268bb46fbec42b3455c88b6b02fb2529d2c306d18fc"}, + {file = "protobuf-4.25.6-cp39-cp39-win32.whl", hash = "sha256:3f3b0b39db04b509859361ac9bca65a265fe9342e6b9406eda58029f5b1d10b2"}, + {file = "protobuf-4.25.6-cp39-cp39-win_amd64.whl", hash = "sha256:6ef2045f89d4ad8d95fd43cd84621487832a61d15b49500e4c1350e8a0ef96be"}, + {file = "protobuf-4.25.6-py3-none-any.whl", hash = "sha256:07972021c8e30b870cfc0863409d033af940213e0e7f64e27fe017b929d2c9f7"}, + {file = "protobuf-4.25.6.tar.gz", hash = "sha256:f8cfbae7c5afd0d0eaccbe73267339bff605a2315860bb1ba08eb66670a9a91f"}, ] +markers = {main = "extra == \"extra-proxy\""} [[package]] name = "pyasn1" @@ -3342,7 +3474,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev", "proxy-dev"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -4027,7 +4159,7 @@ version = "1.26.20" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main"] +groups = ["main", "dev", "proxy-dev"] markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, @@ -4045,7 +4177,7 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "proxy-dev"] markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, @@ -4229,6 +4361,95 @@ files = [ {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["dev", "proxy-dev"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + [[package]] name = "wsproto" version = "1.2.0" @@ -4363,7 +4584,7 @@ version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev", "proxy-dev"] files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, @@ -4384,4 +4605,4 @@ proxy = ["PyJWT", "apscheduler", "backoff", "boto3", "cryptography", "fastapi", [metadata] lock-version = "2.1" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "5e2a741e4861d6a4edb31372bc47967180238151517597543c6983c5b13f836c" +content-hash = "adefc5c35b625ab156ff674c880256643a22880012451d4ade7fa2ef11f5885d" diff --git a/proxy_server_config.yaml b/proxy_server_config.yaml index 364a218c33..23316c4a71 100644 --- a/proxy_server_config.yaml +++ b/proxy_server_config.yaml @@ -7,13 +7,13 @@ model_list: id: "1" - model_name: gpt-3.5-turbo-end-user-test litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: "2023-05-15" api_key: os.environ/AZURE_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault - model_name: gpt-3.5-turbo litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: "2023-05-15" api_key: os.environ/AZURE_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault @@ -26,7 +26,7 @@ model_list: stream_timeout: 60 - model_name: gpt-4 litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: "2023-05-15" api_key: os.environ/AZURE_API_KEY # The `os.environ/` prefix tells litellm to read this from the env. See https://docs.litellm.ai/docs/simple_proxy#load-api-keys-from-vault diff --git a/pyproject.toml b/pyproject.toml index 6c13a86c5c..137189e31b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.66.3" +version = "1.67.3" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -55,7 +55,7 @@ websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.34.34", optional = true} redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = "1.5.0", optional = true, python = ">=3.10"} -litellm-proxy-extras = {version = "0.1.10", optional = true} +litellm-proxy-extras = {version = "0.1.11", optional = true} [tool.poetry.extras] proxy = [ @@ -107,18 +107,24 @@ types-requests = "*" types-setuptools = "*" types-redis = "*" types-PyYAML = "*" +opentelemetry-api = "1.25.0" +opentelemetry-sdk = "1.25.0" +opentelemetry-exporter-otlp = "1.25.0" [tool.poetry.group.proxy-dev.dependencies] prisma = "0.11.0" hypercorn = "^0.15.0" prometheus-client = "0.20.0" +opentelemetry-api = "1.25.0" +opentelemetry-sdk = "1.25.0" +opentelemetry-exporter-otlp = "1.25.0" [build-system] requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.66.3" +version = "1.67.3" version_files = [ "pyproject.toml:^version" ] diff --git a/requirements.txt b/requirements.txt index 9e654d1b89..01f3686c37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests cryptography==43.0.1 tzdata==2025.1 # IANA time zone database -litellm-proxy-extras==0.1.10 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.1.11 # for proxy extras - e.g. prisma migrations ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.0 # for env tiktoken==0.8.0 # for calculating usage diff --git a/security.md b/security.md index 35ec049193..d126dabcc6 100644 --- a/security.md +++ b/security.md @@ -22,9 +22,6 @@ - Audit Logs with retention policy - Control Allowed IP Addresses that can access your Cloud LiteLLM Instance -For security inquiries, please contact us at support@berri.ai - - For security inquiries, please contact us at support@berri.ai #### Supported data regions for LiteLLM Cloud diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000000..5fca3f84bc --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/tests/image_gen_tests/base_image_generation_test.py b/tests/image_gen_tests/base_image_generation_test.py index cf01390e07..76fa2f5540 100644 --- a/tests/image_gen_tests/base_image_generation_test.py +++ b/tests/image_gen_tests/base_image_generation_test.py @@ -66,8 +66,8 @@ class BaseImageGenTest(ABC): logged_standard_logging_payload = custom_logger.standard_logging_payload print("logged_standard_logging_payload", logged_standard_logging_payload) assert logged_standard_logging_payload is not None - # assert logged_standard_logging_payload["response_cost"] is not None - # assert logged_standard_logging_payload["response_cost"] > 0 + assert logged_standard_logging_payload["response_cost"] is not None + assert logged_standard_logging_payload["response_cost"] > 0 from openai.types.images_response import ImagesResponse @@ -85,4 +85,4 @@ class BaseImageGenTest(ABC): if "Your task failed as a result of our safety system." in str(e): pass else: - pytest.fail(f"An exception occurred - {str(e)}") + pytest.fail(f"An exception occurred - {str(e)}") \ No newline at end of file diff --git a/tests/image_gen_tests/test_image_generation.py b/tests/image_gen_tests/test_image_generation.py index bee9d1f7d5..21f96095a7 100644 --- a/tests/image_gen_tests/test_image_generation.py +++ b/tests/image_gen_tests/test_image_generation.py @@ -161,6 +161,9 @@ class TestOpenAIDalle3(BaseImageGenTest): def get_base_image_generation_call_args(self) -> dict: return {"model": "dall-e-3"} +class TestOpenAIGPTImage1(BaseImageGenTest): + def get_base_image_generation_call_args(self) -> dict: + return {"model": "gpt-image-1"} class TestAzureOpenAIDalle3(BaseImageGenTest): def get_base_image_generation_call_args(self) -> dict: diff --git a/tests/litellm/integrations/arize/test_arize_utils.py b/tests/litellm/integrations/arize/test_arize_utils.py new file mode 100644 index 0000000000..bea42faaa8 --- /dev/null +++ b/tests/litellm/integrations/arize/test_arize_utils.py @@ -0,0 +1,231 @@ +import json +import os +import sys +from typing import Optional + +# Adds the grandparent directory to sys.path to allow importing project modules +sys.path.insert(0, os.path.abspath("../..")) + +import asyncio +import litellm +import pytest +from litellm.integrations.arize.arize import ArizeLogger +from litellm.integrations.custom_logger import CustomLogger +from litellm.integrations._types.open_inference import ( + SpanAttributes, + MessageAttributes, + ToolCallAttributes, +) +from litellm.types.utils import Choices, StandardCallbackDynamicParams + + +def test_arize_set_attributes(): + """ + Test setting attributes for Arize, including all custom LLM attributes. + Ensures that the correct span attributes are being added during a request. + """ + from unittest.mock import MagicMock + from litellm.types.utils import ModelResponse + + span = MagicMock() # Mocked tracing span to test attribute setting + + # Construct kwargs to simulate a real LLM request scenario + kwargs = { + "model": "gpt-4o", + "messages": [{"role": "user", "content": "Basic Request Content"}], + "standard_logging_object": { + "model_parameters": {"user": "test_user"}, + "metadata": {"key_1": "value_1", "key_2": None}, + "call_type": "completion", + }, + "optional_params": { + "max_tokens": "100", + "temperature": "1", + "top_p": "5", + "stream": False, + "user": "test_user", + "tools": [ + { + "function": { + "name": "get_weather", + "description": "Fetches weather details.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City name", + } + }, + "required": ["location"], + }, + } + } + ], + "functions": [{"name": "get_weather"}, {"name": "get_stock_price"}], + }, + "litellm_params": {"custom_llm_provider": "openai"}, + } + + # Simulated LLM response object + response_obj = ModelResponse( + usage={"total_tokens": 100, "completion_tokens": 60, "prompt_tokens": 40}, + choices=[ + Choices(message={"role": "assistant", "content": "Basic Response Content"}) + ], + model="gpt-4o", + id="chatcmpl-ID", + ) + + # Apply attribute setting via ArizeLogger + ArizeLogger.set_arize_attributes(span, kwargs, response_obj) + + # Validate that the expected number of attributes were set + assert span.set_attribute.call_count == 28 + + # Metadata attached to the span + span.set_attribute.assert_any_call( + SpanAttributes.METADATA, json.dumps({"key_1": "value_1", "key_2": None}) + ) + + # Basic LLM information + span.set_attribute.assert_any_call(SpanAttributes.LLM_MODEL_NAME, "gpt-4o") + span.set_attribute.assert_any_call("llm.request.type", "completion") + span.set_attribute.assert_any_call(SpanAttributes.LLM_PROVIDER, "openai") + + # LLM generation parameters + span.set_attribute.assert_any_call("llm.request.max_tokens", "100") + span.set_attribute.assert_any_call("llm.request.temperature", "1") + span.set_attribute.assert_any_call("llm.request.top_p", "5") + + # Streaming and user info + span.set_attribute.assert_any_call("llm.is_streaming", "False") + span.set_attribute.assert_any_call("llm.user", "test_user") + + # Response metadata + span.set_attribute.assert_any_call("llm.response.id", "chatcmpl-ID") + span.set_attribute.assert_any_call("llm.response.model", "gpt-4o") + span.set_attribute.assert_any_call(SpanAttributes.OPENINFERENCE_SPAN_KIND, "LLM") + + # Request message content and metadata + span.set_attribute.assert_any_call( + SpanAttributes.INPUT_VALUE, "Basic Request Content" + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}", + "user", + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}", + "Basic Request Content", + ) + + # Tool call definitions and function names + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_NAME}", "get_weather" + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_DESCRIPTION}", + "Fetches weather details.", + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_PARAMETERS}", + json.dumps( + { + "type": "object", + "properties": { + "location": {"type": "string", "description": "City name"} + }, + "required": ["location"], + } + ), + ) + + # Tool calls captured from optional_params + span.set_attribute.assert_any_call( + f"{MessageAttributes.MESSAGE_TOOL_CALLS}.0.{ToolCallAttributes.TOOL_CALL_FUNCTION_NAME}", + "get_weather", + ) + span.set_attribute.assert_any_call( + f"{MessageAttributes.MESSAGE_TOOL_CALLS}.1.{ToolCallAttributes.TOOL_CALL_FUNCTION_NAME}", + "get_stock_price", + ) + + # Invocation parameters + span.set_attribute.assert_any_call( + SpanAttributes.LLM_INVOCATION_PARAMETERS, '{"user": "test_user"}' + ) + + # User ID + span.set_attribute.assert_any_call(SpanAttributes.USER_ID, "test_user") + + # Output message content + span.set_attribute.assert_any_call( + SpanAttributes.OUTPUT_VALUE, "Basic Response Content" + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}", + "assistant", + ) + span.set_attribute.assert_any_call( + f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}", + "Basic Response Content", + ) + + # Token counts + span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 100) + span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 60) + span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 40) + + +class TestArizeLogger(CustomLogger): + """ + Custom logger implementation to capture standard_callback_dynamic_params. + Used to verify that dynamic config keys are being passed to callbacks. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = None + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + # Capture dynamic params and print them for verification + print("logged kwargs", json.dumps(kwargs, indent=4, default=str)) + self.standard_callback_dynamic_params = kwargs.get( + "standard_callback_dynamic_params" + ) + + +@pytest.mark.asyncio +async def test_arize_dynamic_params(): + """ + Test to ensure that dynamic Arize keys (API key and space key) + are received inside the callback logger at runtime. + """ + test_arize_logger = TestArizeLogger() + litellm.callbacks = [test_arize_logger] + + # Perform a mocked async completion call to trigger logging + await litellm.acompletion( + model="gpt-4o", + messages=[{"role": "user", "content": "Basic Request Content"}], + mock_response="test", + arize_api_key="test_api_key_dynamic", + arize_space_key="test_space_key_dynamic", + ) + + # Allow for async propagation + await asyncio.sleep(2) + + # Assert dynamic parameters were received in the callback + assert test_arize_logger.standard_callback_dynamic_params is not None + assert ( + test_arize_logger.standard_callback_dynamic_params.get("arize_api_key") + == "test_api_key_dynamic" + ) + assert ( + test_arize_logger.standard_callback_dynamic_params.get("arize_space_key") + == "test_space_key_dynamic" + ) diff --git a/tests/litellm/integrations/test_agentops.py b/tests/litellm/integrations/test_agentops.py new file mode 100644 index 0000000000..3302736177 --- /dev/null +++ b/tests/litellm/integrations/test_agentops.py @@ -0,0 +1,98 @@ +import os +import sys +import pytest +from unittest.mock import patch, MagicMock + +sys.path.insert(0, os.path.abspath("../..")) # Adds the parent directory to the system-path + +from litellm.integrations.agentops.agentops import AgentOps, AgentOpsConfig + +@pytest.fixture +def mock_auth_response(): + return { + "token": "test_jwt_token", + "project_id": "test_project_id" + } + +@pytest.fixture +def agentops_config(): + return AgentOpsConfig( + endpoint="https://otlp.agentops.cloud/v1/traces", + api_key="test_api_key", + service_name="test_service", + deployment_environment="test_env", + auth_endpoint="https://api.agentops.ai/v3/auth/token" + ) + +def test_agentops_config_from_env(): + """Test that AgentOpsConfig correctly reads from environment variables""" + with patch.dict(os.environ, { + "AGENTOPS_API_KEY": "test_key", + "AGENTOPS_SERVICE_NAME": "test_service", + "AGENTOPS_ENVIRONMENT": "test_env" + }): + config = AgentOpsConfig.from_env() + assert config.api_key == "test_key" + assert config.service_name == "test_service" + assert config.deployment_environment == "test_env" + assert config.endpoint == "https://otlp.agentops.cloud/v1/traces" + assert config.auth_endpoint == "https://api.agentops.ai/v3/auth/token" + +def test_agentops_config_defaults(): + """Test that AgentOpsConfig uses correct default values""" + config = AgentOpsConfig() + assert config.service_name is None + assert config.deployment_environment is None + assert config.api_key is None + assert config.endpoint == "https://otlp.agentops.cloud/v1/traces" + assert config.auth_endpoint == "https://api.agentops.ai/v3/auth/token" + +@patch('litellm.integrations.agentops.agentops.AgentOps._fetch_auth_token') +def test_fetch_auth_token_success(mock_fetch_auth_token, mock_auth_response): + """Test successful JWT token fetch""" + mock_fetch_auth_token.return_value = mock_auth_response + + config = AgentOpsConfig(api_key="test_key") + agentops = AgentOps(config=config) + + mock_fetch_auth_token.assert_called_once_with("test_key", "https://api.agentops.ai/v3/auth/token") + assert agentops.resource_attributes.get("project.id") == mock_auth_response.get("project_id") + +@patch('litellm.integrations.agentops.agentops.AgentOps._fetch_auth_token') +def test_fetch_auth_token_failure(mock_fetch_auth_token): + """Test failed JWT token fetch""" + mock_fetch_auth_token.side_effect = Exception("Failed to fetch auth token: Unauthorized") + + config = AgentOpsConfig(api_key="test_key") + agentops = AgentOps(config=config) + + mock_fetch_auth_token.assert_called_once() + assert "project.id" not in agentops.resource_attributes + +@patch('litellm.integrations.agentops.agentops.AgentOps._fetch_auth_token') +def test_agentops_initialization(mock_fetch_auth_token, agentops_config, mock_auth_response): + """Test AgentOps initialization with config""" + mock_fetch_auth_token.return_value = mock_auth_response + + agentops = AgentOps(config=agentops_config) + + assert agentops.resource_attributes["service.name"] == "test_service" + assert agentops.resource_attributes["deployment.environment"] == "test_env" + assert agentops.resource_attributes["telemetry.sdk.name"] == "agentops" + assert agentops.resource_attributes["project.id"] == "test_project_id" + +def test_agentops_initialization_no_auth(): + """Test AgentOps initialization without authentication""" + test_config = AgentOpsConfig( + endpoint="https://otlp.agentops.cloud/v1/traces", + api_key=None, # No API key + service_name="test_service", + deployment_environment="test_env" + ) + + agentops = AgentOps(config=test_config) + + assert agentops.resource_attributes["service.name"] == "test_service" + assert agentops.resource_attributes["deployment.environment"] == "test_env" + assert agentops.resource_attributes["telemetry.sdk.name"] == "agentops" + assert "project.id" not in agentops.resource_attributes \ No newline at end of file diff --git a/tests/litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 8f8f043935..7df783e719 100644 --- a/tests/litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -10,7 +10,13 @@ from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import ( StandardBuiltInToolCostTracking, ) from litellm.types.llms.openai import FileSearchTool, WebSearchOptions -from litellm.types.utils import ModelInfo, ModelResponse, StandardBuiltInToolsParams +from litellm.types.utils import ( + CompletionTokensDetailsWrapper, + ModelInfo, + ModelResponse, + PromptTokensDetailsWrapper, + StandardBuiltInToolsParams, +) sys.path.insert( 0, os.path.abspath("../../..") @@ -20,6 +26,92 @@ from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_toke from litellm.types.utils import Usage +def test_reasoning_tokens_no_price_set(): + model = "o1-mini" + custom_llm_provider = "openai" + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + model_cost_map = litellm.model_cost[model] + usage = Usage( + completion_tokens=1578, + prompt_tokens=17, + total_tokens=1595, + completion_tokens_details=CompletionTokensDetailsWrapper( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=952, + rejected_prediction_tokens=None, + text_tokens=626, + ), + prompt_tokens_details=PromptTokensDetailsWrapper( + audio_tokens=None, cached_tokens=None, text_tokens=17, image_tokens=None + ), + ) + prompt_cost, completion_cost = generic_cost_per_token( + model=model, + usage=usage, + custom_llm_provider="openai", + ) + assert round(prompt_cost, 10) == round( + model_cost_map["input_cost_per_token"] * usage.prompt_tokens, + 10, + ) + print(f"completion_cost: {completion_cost}") + expected_completion_cost = ( + model_cost_map["output_cost_per_token"] * usage.completion_tokens + ) + print(f"expected_completion_cost: {expected_completion_cost}") + assert round(completion_cost, 10) == round( + expected_completion_cost, + 10, + ) + + +def test_reasoning_tokens_gemini(): + model = "gemini-2.5-flash-preview-04-17" + custom_llm_provider = "gemini" + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + usage = Usage( + completion_tokens=1578, + prompt_tokens=17, + total_tokens=1595, + completion_tokens_details=CompletionTokensDetailsWrapper( + accepted_prediction_tokens=None, + audio_tokens=None, + reasoning_tokens=952, + rejected_prediction_tokens=None, + text_tokens=626, + ), + prompt_tokens_details=PromptTokensDetailsWrapper( + audio_tokens=None, cached_tokens=None, text_tokens=17, image_tokens=None + ), + ) + model_cost_map = litellm.model_cost[model] + prompt_cost, completion_cost = generic_cost_per_token( + model=model, + usage=usage, + custom_llm_provider=custom_llm_provider, + ) + + assert round(prompt_cost, 10) == round( + model_cost_map["input_cost_per_token"] * usage.prompt_tokens, + 10, + ) + assert round(completion_cost, 10) == round( + ( + model_cost_map["output_cost_per_token"] + * usage.completion_tokens_details.text_tokens + ) + + ( + model_cost_map["output_cost_per_reasoning_token"] + * usage.completion_tokens_details.reasoning_tokens + ), + 10, + ) + + def test_generic_cost_per_token_above_200k_tokens(): model = "gemini-2.5-pro-exp-03-25" custom_llm_provider = "vertex_ai" diff --git a/tests/litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py b/tests/litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py index aca0e02e17..1d349a44e5 100644 --- a/tests/litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py +++ b/tests/litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py @@ -11,6 +11,7 @@ sys.path.insert( from litellm.litellm_core_utils.prompt_templates.common_utils import ( get_format_from_file_id, + handle_any_messages_to_chat_completion_str_messages_conversion, update_messages_with_model_file_ids, ) @@ -64,3 +65,63 @@ def test_update_messages_with_model_file_ids(): ], } ] + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_list(): + # Test with list of messages + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "Hi there"}, + ] + result = handle_any_messages_to_chat_completion_str_messages_conversion(messages) + assert len(result) == 2 + assert result[0] == messages[0] + assert result[1] == messages[1] + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_list_infinite_loop(): + # Test that list handling doesn't cause infinite recursion + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "Hi there"}, + ] + # This should complete without stack overflow + result = handle_any_messages_to_chat_completion_str_messages_conversion(messages) + assert len(result) == 2 + assert result[0] == messages[0] + assert result[1] == messages[1] + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_dict(): + # Test with single dictionary message + message = {"role": "user", "content": "Hello"} + result = handle_any_messages_to_chat_completion_str_messages_conversion(message) + assert len(result) == 1 + assert result[0]["input"] == json.dumps(message) + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_str(): + # Test with string message + message = "Hello" + result = handle_any_messages_to_chat_completion_str_messages_conversion(message) + assert len(result) == 1 + assert result[0]["input"] == message + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_other(): + # Test with non-string/dict/list type + message = 123 + result = handle_any_messages_to_chat_completion_str_messages_conversion(message) + assert len(result) == 1 + assert result[0]["input"] == "123" + + +def test_handle_any_messages_to_chat_completion_str_messages_conversion_complex(): + # Test with complex nested structure + message = { + "role": "user", + "content": {"text": "Hello", "metadata": {"timestamp": "2024-01-01"}}, + } + result = handle_any_messages_to_chat_completion_str_messages_conversion(message) + assert len(result) == 1 + assert result[0]["input"] == json.dumps(message) diff --git a/tests/litellm/litellm_core_utils/test_core_helpers.py b/tests/litellm/litellm_core_utils/test_core_helpers.py new file mode 100644 index 0000000000..3b7acd0d50 --- /dev/null +++ b/tests/litellm/litellm_core_utils/test_core_helpers.py @@ -0,0 +1,22 @@ +import json +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm.litellm_core_utils.core_helpers import get_litellm_metadata_from_kwargs + + +def test_get_litellm_metadata_from_kwargs(): + kwargs = { + "litellm_params": { + "litellm_metadata": {}, + "metadata": {"user_api_key": "1234567890"}, + }, + } + assert get_litellm_metadata_from_kwargs(kwargs) == {"user_api_key": "1234567890"} diff --git a/tests/litellm/llms/anthropic/chat/test_anthropic_chat_handler.py b/tests/litellm/llms/anthropic/chat/test_anthropic_chat_handler.py new file mode 100644 index 0000000000..25ec01167e --- /dev/null +++ b/tests/litellm/llms/anthropic/chat/test_anthropic_chat_handler.py @@ -0,0 +1,38 @@ +import json +import os +import sys +from unittest.mock import MagicMock + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.anthropic.chat.handler import ModelResponseIterator + + +def test_redacted_thinking_content_block_delta(): + chunk = { + "type": "content_block_start", + "index": 58, + "content_block": { + "type": "redacted_thinking", + "data": "EuoBCoYBGAIiQJ/SxkPAgqxhKok29YrpJHRUJ0OT8ahCHKAwyhmRuUhtdmDX9+mn4gDzKNv3fVpQdB01zEPMzNY3QuTCd+1bdtEqQK6JuKHqdndbwpr81oVWb4wxd1GqF/7Jkw74IlQa27oobX+KuRkopr9Dllt/RDe7Se0sI1IkU7tJIAQCoP46OAwSDF51P09q67xhHlQ3ihoM2aOVlkghq/X0w8NlIjBMNvXYNbjhyrOcIg6kPFn2ed/KK7Cm5prYAtXCwkb4Wr5tUSoSHu9T5hKdJRbr6WsqEc7Lle7FULqMLZGkhqXyc3BA", + }, + } + model_response_iterator = ModelResponseIterator( + streaming_response=MagicMock(), sync_stream=False, json_mode=False + ) + model_response = model_response_iterator.chunk_parser(chunk=chunk) + print(f"\n\nmodel_response: {model_response}\n\n") + assert model_response.choices[0].delta.thinking_blocks is not None + assert len(model_response.choices[0].delta.thinking_blocks) == 1 + print( + f"\n\nmodel_response.choices[0].delta.thinking_blocks[0]: {model_response.choices[0].delta.thinking_blocks[0]}\n\n" + ) + assert ( + model_response.choices[0].delta.thinking_blocks[0]["type"] + == "redacted_thinking" + ) diff --git a/tests/litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 9f672110a4..6c98c87cd7 100644 --- a/tests/litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -56,3 +56,58 @@ def test_calculate_usage(): assert usage.prompt_tokens_details.cached_tokens == 0 assert usage._cache_creation_input_tokens == 12304 assert usage._cache_read_input_tokens == 0 + + +def test_extract_response_content_with_citations(): + config = AnthropicConfig() + + completion_response = { + "id": "msg_01XrAv7gc5tQNDuoADra7vB4", + "type": "message", + "role": "assistant", + "model": "claude-3-5-sonnet-20241022", + "content": [ + {"type": "text", "text": "According to the documents, "}, + { + "citations": [ + { + "type": "char_location", + "cited_text": "The grass is green. ", + "document_index": 0, + "document_title": "My Document", + "start_char_index": 0, + "end_char_index": 20, + } + ], + "type": "text", + "text": "the grass is green", + }, + {"type": "text", "text": " and "}, + { + "citations": [ + { + "type": "char_location", + "cited_text": "The sky is blue.", + "document_index": 0, + "document_title": "My Document", + "start_char_index": 20, + "end_char_index": 36, + } + ], + "type": "text", + "text": "the sky is blue", + }, + {"type": "text", "text": "."}, + ], + "stop_reason": "end_turn", + "stop_sequence": None, + "usage": { + "input_tokens": 610, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0, + "output_tokens": 51, + }, + } + + _, citations, _, _, _ = config.extract_response_content(completion_response) + assert citations is not None diff --git a/tests/litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/litellm/llms/bedrock/chat/test_converse_transformation.py index 5390daa1a0..063bde82b3 100644 --- a/tests/litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/litellm/llms/bedrock/chat/test_converse_transformation.py @@ -40,3 +40,102 @@ def test_transform_usage(): ) assert openai_usage._cache_creation_input_tokens == usage["cacheWriteInputTokens"] assert openai_usage._cache_read_input_tokens == usage["cacheReadInputTokens"] + +def test_transform_system_message(): + config = AmazonConverseConfig() + + # Case 1: + # System message popped + # User message remains + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"}, + ] + out_messages, system_blocks = config._transform_system_message(messages.copy()) + assert len(out_messages) == 1 + assert out_messages[0]["role"] == "user" + assert len(system_blocks) == 1 + assert system_blocks[0]["text"] == "You are a helpful assistant." + + # Case 2: System message with list content (type text) + messages = [ + { + "role": "system", + "content": [ + {"type": "text", "text": "System prompt 1"}, + {"type": "text", "text": "System prompt 2"}, + ], + }, + {"role": "user", "content": "Hi!"}, + ] + out_messages, system_blocks = config._transform_system_message(messages.copy()) + assert len(out_messages) == 1 + assert out_messages[0]["role"] == "user" + assert len(system_blocks) == 2 + assert system_blocks[0]["text"] == "System prompt 1" + assert system_blocks[1]["text"] == "System prompt 2" + + # Case 3: System message with cache_control (should add cachePoint) + messages = [ + { + "role": "system", + "content": "Cache this!", + "cache_control": {"type": "ephemeral"}, + }, + {"role": "user", "content": "Hi!"}, + ] + out_messages, system_blocks = config._transform_system_message(messages.copy()) + assert len(out_messages) == 1 + assert len(system_blocks) == 2 + assert system_blocks[0]["text"] == "Cache this!" + assert "cachePoint" in system_blocks[1] + assert system_blocks[1]["cachePoint"]["type"] == "default" + + # Case 3b: System message with two blocks, one with cache_control and one without + messages = [ + { + "role": "system", + "content": [ + {"type": "text", "text": "Cache this!", "cache_control": {"type": "ephemeral"}}, + {"type": "text", "text": "Don't cache this!"}, + ], + }, + {"role": "user", "content": "Hi!"}, + ] + out_messages, system_blocks = config._transform_system_message(messages.copy()) + assert len(out_messages) == 1 + assert len(system_blocks) == 3 + assert system_blocks[0]["text"] == "Cache this!" + assert "cachePoint" in system_blocks[1] + assert system_blocks[1]["cachePoint"]["type"] == "default" + assert system_blocks[2]["text"] == "Don't cache this!" + + # Case 4: Non-system messages are not affected + messages = [ + {"role": "user", "content": "Hello!"}, + {"role": "assistant", "content": "Hi!"}, + ] + out_messages, system_blocks = config._transform_system_message(messages.copy()) + assert len(out_messages) == 2 + assert out_messages[0]["role"] == "user" + assert out_messages[1]["role"] == "assistant" + assert system_blocks == [] + +def test_transform_thinking_blocks_with_redacted_content(): + thinking_blocks = [ + { + "reasoningText": { + "text": "This is a test", + "signature": "test_signature", + } + }, + { + "redactedContent": "This is a redacted content", + }, + ] + config = AmazonConverseConfig() + transformed_thinking_blocks = config._transform_thinking_blocks(thinking_blocks) + assert len(transformed_thinking_blocks) == 2 + assert transformed_thinking_blocks[0]["type"] == "thinking" + assert transformed_thinking_blocks[1]["type"] == "redacted_thinking" + diff --git a/tests/litellm/llms/bedrock/chat/test_invoke_handler.py b/tests/litellm/llms/bedrock/chat/test_invoke_handler.py new file mode 100644 index 0000000000..ed94c784c9 --- /dev/null +++ b/tests/litellm/llms/bedrock/chat/test_invoke_handler.py @@ -0,0 +1,22 @@ +import json +import os +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path +from unittest.mock import MagicMock, patch + +from litellm.llms.bedrock.chat.invoke_handler import AWSEventStreamDecoder + + +def test_transform_thinking_blocks_with_redacted_content(): + thinking_block = {"redactedContent": "This is a redacted content"} + decoder = AWSEventStreamDecoder(model="test") + transformed_thinking_blocks = decoder.translate_thinking_blocks(thinking_block) + assert len(transformed_thinking_blocks) == 1 + assert transformed_thinking_blocks[0]["type"] == "redacted_thinking" + assert transformed_thinking_blocks[0]["data"] == "This is a redacted content" diff --git a/tests/litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py b/tests/litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py new file mode 100644 index 0000000000..e4b0928d92 --- /dev/null +++ b/tests/litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py @@ -0,0 +1,59 @@ +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.fireworks_ai.chat.transformation import FireworksAIConfig +from litellm.types.llms.openai import ChatCompletionToolCallFunctionChunk +from litellm.types.utils import ChatCompletionMessageToolCall, Function, Message + + +def test_handle_message_content_with_tool_calls(): + config = FireworksAIConfig() + message = Message( + content='{"type": "function", "name": "get_current_weather", "parameters": {"location": "Boston, MA", "unit": "fahrenheit"}}', + role="assistant", + tool_calls=None, + function_call=None, + provider_specific_fields=None, + ) + expected_tool_call = ChatCompletionMessageToolCall( + function=Function(**json.loads(message.content)), id=None, type=None + ) + tool_calls = [ + { + "type": "function", + "function": { + "name": "get_current_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"], + }, + }, + } + ] + updated_message = config._handle_message_content_with_tool_calls( + message, tool_calls + ) + assert updated_message.tool_calls is not None + assert len(updated_message.tool_calls) == 1 + assert updated_message.tool_calls[0].function.name == "get_current_weather" + assert ( + updated_message.tool_calls[0].function.arguments + == expected_tool_call.function.arguments + ) diff --git a/tests/litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py b/tests/litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py new file mode 100644 index 0000000000..7885195b5b --- /dev/null +++ b/tests/litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py @@ -0,0 +1,45 @@ +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.hosted_vllm.chat.transformation import HostedVLLMChatConfig + + +def test_hosted_vllm_chat_transformation_file_url(): + config = HostedVLLMChatConfig() + video_url = "https://example.com/video.mp4" + video_data = f"data:video/mp4;base64,{video_url}" + messages = [ + { + "role": "user", + "content": [ + { + "type": "file", + "file": { + "file_data": video_data, + }, + } + ], + } + ] + transformed_response = config.transform_request( + model="hosted_vllm/llama-3.1-70b-instruct", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + assert transformed_response["messages"] == [ + { + "role": "user", + "content": [{"type": "video_url", "video_url": {"url": video_data}}], + } + ] diff --git a/tests/litellm/llms/openai/responses/test_openai_responses_transformation.py b/tests/litellm/llms/openai/responses/test_openai_responses_transformation.py index b4a6cd974e..04b9de5616 100644 --- a/tests/litellm/llms/openai/responses/test_openai_responses_transformation.py +++ b/tests/litellm/llms/openai/responses/test_openai_responses_transformation.py @@ -201,13 +201,19 @@ class TestOpenAIResponsesAPIConfig: # Test with provided API base api_base = "https://custom-openai.example.com/v1" - result = self.config.get_complete_url(api_base=api_base, model=self.model) + result = self.config.get_complete_url( + api_base=api_base, + litellm_params={}, + ) assert result == "https://custom-openai.example.com/v1/responses" # Test with litellm.api_base with patch("litellm.api_base", "https://litellm-api-base.example.com/v1"): - result = self.config.get_complete_url(api_base=None, model=self.model) + result = self.config.get_complete_url( + api_base=None, + litellm_params={}, + ) assert result == "https://litellm-api-base.example.com/v1/responses" @@ -217,7 +223,10 @@ class TestOpenAIResponsesAPIConfig: "litellm.llms.openai.responses.transformation.get_secret_str", return_value="https://env-api-base.example.com/v1", ): - result = self.config.get_complete_url(api_base=None, model=self.model) + result = self.config.get_complete_url( + api_base=None, + litellm_params={}, + ) assert result == "https://env-api-base.example.com/v1/responses" @@ -227,13 +236,38 @@ class TestOpenAIResponsesAPIConfig: "litellm.llms.openai.responses.transformation.get_secret_str", return_value=None, ): - result = self.config.get_complete_url(api_base=None, model=self.model) + result = self.config.get_complete_url( + api_base=None, + litellm_params={}, + ) assert result == "https://api.openai.com/v1/responses" # Test with trailing slash in API base api_base = "https://custom-openai.example.com/v1/" - result = self.config.get_complete_url(api_base=api_base, model=self.model) + result = self.config.get_complete_url( + api_base=api_base, + litellm_params={}, + ) assert result == "https://custom-openai.example.com/v1/responses" + + def test_get_event_model_class_generic_event(self): + """Test that get_event_model_class returns the correct event model class""" + from litellm.types.llms.openai import GenericEvent + + event_type = "test" + result = self.config.get_event_model_class(event_type) + assert result == GenericEvent + + def test_transform_streaming_response_generic_event(self): + """Test that transform_streaming_response returns the correct event model class""" + from litellm.types.llms.openai import GenericEvent + + chunk = {"type": "test", "test": "test"} + result = self.config.transform_streaming_response( + model=self.model, parsed_chunk=chunk, logging_obj=self.logging_obj + ) + assert isinstance(result, GenericEvent) + assert result.type == "test" diff --git a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index 41a37e2e57..4b1c085bb4 100644 --- a/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -1,16 +1,17 @@ import asyncio +from typing import List, cast from unittest.mock import MagicMock import pytest +from pydantic import BaseModel import litellm from litellm import ModelResponse from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( VertexGeminiConfig, ) -from litellm.types.utils import ChoiceLogprobs -from pydantic import BaseModel -from typing import List, cast +from litellm.types.llms.vertex_ai import UsageMetadata +from litellm.types.utils import ChoiceLogprobs, Usage def test_top_logprobs(): @@ -66,7 +67,6 @@ def test_get_model_name_from_gemini_spec_model(): assert result == "ft-uuid-123" - def test_vertex_ai_response_schema_dict(): v = VertexGeminiConfig() transformed_request = v.map_openai_params( @@ -221,3 +221,92 @@ def test_vertex_ai_retain_property_ordering(): schema = transformed_request["response_schema"] # should leave existing value alone, despite dictionary ordering assert schema["propertyOrdering"] == ["thought", "output"] + + +def test_vertex_ai_thinking_output_part(): + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + from litellm.types.llms.vertex_ai import HttpxPartType + + v = VertexGeminiConfig() + parts = [ + HttpxPartType( + thought=True, + text="I'm thinking...", + ), + HttpxPartType(text="Hello world"), + ] + content, reasoning_content = v.get_assistant_content_message(parts=parts) + assert content == "Hello world" + assert reasoning_content == "I'm thinking..." + + +def test_vertex_ai_empty_content(): + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + from litellm.types.llms.vertex_ai import HttpxPartType + + v = VertexGeminiConfig() + parts = [ + HttpxPartType( + functionCall={ + "name": "get_current_weather", + "arguments": "{}", + }, + ), + ] + content, reasoning_content = v.get_assistant_content_message(parts=parts) + assert content is None + assert reasoning_content is None + + +@pytest.mark.parametrize( + "usage_metadata, inclusive, expected_usage", + [ + ( + UsageMetadata( + promptTokenCount=10, + candidatesTokenCount=10, + totalTokenCount=20, + thoughtsTokenCount=5, + ), + True, + Usage( + prompt_tokens=10, + completion_tokens=10, + total_tokens=20, + reasoning_tokens=5, + ), + ), + ( + UsageMetadata( + promptTokenCount=10, + candidatesTokenCount=5, + totalTokenCount=20, + thoughtsTokenCount=5, + ), + False, + Usage( + prompt_tokens=10, + completion_tokens=10, + total_tokens=20, + reasoning_tokens=5, + ), + ), + ], +) +def test_vertex_ai_candidate_token_count_inclusive( + usage_metadata, inclusive, expected_usage +): + """ + Test that the candidate token count is inclusive of the thinking token count + """ + v = VertexGeminiConfig() + assert v.is_candidate_token_count_inclusive(usage_metadata) is inclusive + + usage = v._calculate_usage(completion_response={"usageMetadata": usage_metadata}) + assert usage.prompt_tokens == expected_usage.prompt_tokens + assert usage.completion_tokens == expected_usage.completion_tokens + assert usage.total_tokens == expected_usage.total_tokens diff --git a/tests/litellm/proxy/management_endpoints/test_common_daily_activity.py b/tests/litellm/proxy/management_endpoints/test_common_daily_activity.py new file mode 100644 index 0000000000..ffaed2d88f --- /dev/null +++ b/tests/litellm/proxy/management_endpoints/test_common_daily_activity.py @@ -0,0 +1,58 @@ +import json +import os +import sys +from datetime import datetime, timezone +from unittest.mock import AsyncMock, MagicMock + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../../..") +) # Adds the parent directory to the system path + +from litellm.proxy.management_endpoints.common_daily_activity import get_daily_activity +from litellm.proxy.proxy_server import app + +client = TestClient(app) + + +@pytest.mark.asyncio +async def test_get_daily_activity_empty_entity_id_list(): + # Mock PrismaClient + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + + # Mock the table methods + mock_table = MagicMock() + mock_table.count = AsyncMock(return_value=0) + mock_table.find_many = AsyncMock(return_value=[]) + mock_prisma.db.litellm_verificationtoken = MagicMock() + mock_prisma.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) + + # Set the table name dynamically + mock_prisma.db.litellm_dailyspend = mock_table + + # Call the function with empty entity_id list + result = await get_daily_activity( + prisma_client=mock_prisma, + table_name="litellm_dailyspend", + entity_id_field="team_id", + entity_id=[], + entity_metadata_field=None, + start_date="2024-01-01", + end_date="2024-01-02", + model=None, + api_key=None, + page=1, + page_size=10, + ) + + # Verify the where conditions were set correctly + mock_table.find_many.assert_called_once() + call_args = mock_table.find_many.call_args[1] + where_conditions = call_args["where"] + + # Check that team_id is set to empty list + assert "team_id" in where_conditions + assert where_conditions["team_id"] == {"in": []} diff --git a/tests/litellm/proxy/management_endpoints/test_internal_user_endpoints.py b/tests/litellm/proxy/management_endpoints/test_internal_user_endpoints.py index 73ed8e1a30..360f21f171 100644 --- a/tests/litellm/proxy/management_endpoints/test_internal_user_endpoints.py +++ b/tests/litellm/proxy/management_endpoints/test_internal_user_endpoints.py @@ -67,7 +67,7 @@ def test_user_daily_activity_types(): """ Assert all fiels in SpendMetrics are reported in DailySpendMetadata as "total_" """ - from litellm.proxy.management_endpoints.internal_user_endpoints import ( + from litellm.proxy.management_endpoints.common_daily_activity import ( DailySpendMetadata, SpendMetrics, ) @@ -153,3 +153,19 @@ async def test_get_users_includes_timestamps(mocker): assert user_response.created_at == mock_user_data["created_at"] assert user_response.updated_at == mock_user_data["updated_at"] assert user_response.key_count == 0 + + +def test_validate_sort_params(): + """ + Test that validate_sort_params returns None if sort_by is None + """ + from litellm.proxy.management_endpoints.internal_user_endpoints import ( + _validate_sort_params, + ) + + assert _validate_sort_params(None, "asc") is None + assert _validate_sort_params(None, "desc") is None + assert _validate_sort_params("user_id", "asc") == {"user_id": "asc"} + assert _validate_sort_params("user_id", "desc") == {"user_id": "desc"} + with pytest.raises(Exception): + _validate_sort_params("user_id", "invalid") diff --git a/tests/litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/litellm/proxy/management_endpoints/test_key_management_endpoints.py index 51bbbb49c4..c436e08901 100644 --- a/tests/litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -46,3 +46,54 @@ async def test_list_keys(): assert json.dumps({"team_id": {"not": "litellm-dashboard"}}) in json.dumps( where_condition ) + + +@pytest.mark.asyncio +async def test_key_token_handling(monkeypatch): + """ + Test that token handling in key generation follows the expected behavior: + 1. token field should not equal key field + 2. if token_id exists, it should equal token field + """ + mock_prisma_client = AsyncMock() + mock_insert_data = AsyncMock( + return_value=MagicMock(token="hashed_token_123", litellm_budget_table=None) + ) + mock_prisma_client.insert_data = mock_insert_data + mock_prisma_client.db = MagicMock() + mock_prisma_client.db.litellm_verificationtoken = MagicMock() + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=None + ) + mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock( + return_value=[] + ) + mock_prisma_client.db.litellm_verificationtoken.count = AsyncMock(return_value=0) + mock_prisma_client.db.litellm_verificationtoken.update = AsyncMock( + return_value=MagicMock(token="hashed_token_123", litellm_budget_table=None) + ) + + from litellm.proxy._types import GenerateKeyRequest, LitellmUserRoles + from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth + from litellm.proxy.management_endpoints.key_management_endpoints import ( + generate_key_fn, + ) + from litellm.proxy.proxy_server import prisma_client + + # Use monkeypatch to set the prisma_client + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Test key generation + response = await generate_key_fn( + data=GenerateKeyRequest(), + user_api_key_dict=UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-1234", user_id="1234" + ), + ) + + # Verify token handling + assert response.key != response.token, "Token should not equal key" + if hasattr(response, "token_id"): + assert ( + response.token == response.token_id + ), "Token should equal token_id if token_id exists" diff --git a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 080aa3bd16..181ca59aac 100644 --- a/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -20,6 +20,16 @@ from litellm.proxy.hooks.proxy_track_cost_callback import _ProxyDBLogger from litellm.proxy.proxy_server import app, prisma_client from litellm.router import Router +ignored_keys = [ + "request_id", + "startTime", + "endTime", + "completionStartTime", + "endTime", + "metadata.model_map_information", + "metadata.usage_object", +] + @pytest.fixture def client(): @@ -457,7 +467,7 @@ class TestSpendLogsPayload: "model": "gpt-4o", "user": "", "team_id": "", - "metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "usage_object": {"completion_tokens": 20, "prompt_tokens": 10, "total_tokens": 30, "completion_tokens_details": null, "prompt_tokens_details": null}, "model_map_information": {"model_map_key": "gpt-4o", "model_map_value": {"key": "gpt-4o", "max_tokens": 16384, "max_input_tokens": 128000, "max_output_tokens": 16384, "input_cost_per_token": 2.5e-06, "cache_creation_input_token_cost": null, "cache_read_input_token_cost": 1.25e-06, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": 1.25e-06, "output_cost_per_token_batches": 5e-06, "output_cost_per_token": 1e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "openai", "mode": "chat", "supports_system_messages": true, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": false, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": false, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": true, "supports_reasoning": false, "search_context_cost_per_query": {"search_context_size_low": 0.03, "search_context_size_medium": 0.035, "search_context_size_high": 0.05}, "tpm": null, "rpm": null, "supported_openai_params": ["frequency_penalty", "logit_bias", "logprobs", "top_logprobs", "max_tokens", "max_completion_tokens", "modalities", "prediction", "n", "presence_penalty", "seed", "stop", "stream", "stream_options", "temperature", "top_p", "tools", "tool_choice", "function_call", "functions", "max_retries", "extra_headers", "parallel_tool_calls", "audio", "response_format", "user"]}}, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": null}}', + "metadata": '{"applied_guardrails": [], "batch_models": null, "mcp_tool_call_metadata": null, "usage_object": {"completion_tokens": 20, "prompt_tokens": 10, "total_tokens": 30, "completion_tokens_details": null, "prompt_tokens_details": null}, "model_map_information": {"model_map_key": "gpt-4o", "model_map_value": {"key": "gpt-4o", "max_tokens": 16384, "max_input_tokens": 128000, "max_output_tokens": 16384, "input_cost_per_token": 2.5e-06, "cache_creation_input_token_cost": null, "cache_read_input_token_cost": 1.25e-06, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": 1.25e-06, "output_cost_per_token_batches": 5e-06, "output_cost_per_token": 1e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_reasoning_token": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "openai", "mode": "chat", "supports_system_messages": true, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": false, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": false, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": true, "supports_reasoning": false, "search_context_cost_per_query": {"search_context_size_low": 0.03, "search_context_size_medium": 0.035, "search_context_size_high": 0.05}, "tpm": null, "rpm": null, "supported_openai_params": ["frequency_penalty", "logit_bias", "logprobs", "top_logprobs", "max_tokens", "max_completion_tokens", "modalities", "prediction", "n", "presence_penalty", "seed", "stop", "stream", "stream_options", "temperature", "top_p", "tools", "tool_choice", "function_call", "functions", "max_retries", "extra_headers", "parallel_tool_calls", "audio", "response_format", "user"]}}, "additional_usage_values": {"completion_tokens_details": null, "prompt_tokens_details": null}}', "cache_key": "Cache OFF", "spend": 0.00022500000000000002, "total_tokens": 30, @@ -475,19 +485,11 @@ class TestSpendLogsPayload: } ) - for key, value in expected_payload.items(): - if key in [ - "request_id", - "startTime", - "endTime", - "completionStartTime", - "endTime", - ]: - assert payload[key] is not None - else: - assert ( - payload[key] == value - ), f"Expected {key} to be {value}, but got {payload[key]}" + differences = _compare_nested_dicts( + payload, expected_payload, ignore_keys=ignored_keys + ) + if differences: + assert False, f"Dictionary mismatch: {differences}" def mock_anthropic_response(*args, **kwargs): mock_response = MagicMock() @@ -573,19 +575,11 @@ class TestSpendLogsPayload: } ) - for key, value in expected_payload.items(): - if key in [ - "request_id", - "startTime", - "endTime", - "completionStartTime", - "endTime", - ]: - assert payload[key] is not None - else: - assert ( - payload[key] == value - ), f"Expected {key} to be {value}, but got {payload[key]}" + differences = _compare_nested_dicts( + payload, expected_payload, ignore_keys=ignored_keys + ) + if differences: + assert False, f"Dictionary mismatch: {differences}" @pytest.mark.asyncio async def test_spend_logs_payload_success_log_with_router(self): @@ -669,16 +663,134 @@ class TestSpendLogsPayload: } ) - for key, value in expected_payload.items(): - if key in [ - "request_id", - "startTime", - "endTime", - "completionStartTime", - "endTime", - ]: - assert payload[key] is not None - else: - assert ( - payload[key] == value - ), f"Expected {key} to be {value}, but got {payload[key]}" + differences = _compare_nested_dicts( + payload, expected_payload, ignore_keys=ignored_keys + ) + if differences: + assert False, f"Dictionary mismatch: {differences}" + + +def _compare_nested_dicts( + actual: dict, expected: dict, path: str = "", ignore_keys: list[str] = [] +) -> list[str]: + """Compare nested dictionaries and return a list of differences in a human-friendly format.""" + differences = [] + + # Check if current path should be ignored + if path in ignore_keys: + return differences + + # Check for keys in actual but not in expected + for key in actual.keys(): + current_path = f"{path}.{key}" if path else key + if current_path not in ignore_keys and key not in expected: + differences.append(f"Extra key in actual: {current_path}") + + for key, expected_value in expected.items(): + current_path = f"{path}.{key}" if path else key + if current_path in ignore_keys: + continue + if key not in actual: + differences.append(f"Missing key: {current_path}") + continue + + actual_value = actual[key] + + # Try to parse JSON strings + if isinstance(expected_value, str): + try: + expected_value = json.loads(expected_value) + except json.JSONDecodeError: + pass + if isinstance(actual_value, str): + try: + actual_value = json.loads(actual_value) + except json.JSONDecodeError: + pass + + if isinstance(expected_value, dict) and isinstance(actual_value, dict): + differences.extend( + _compare_nested_dicts( + actual_value, expected_value, current_path, ignore_keys + ) + ) + elif isinstance(expected_value, dict) or isinstance(actual_value, dict): + differences.append( + f"Type mismatch at {current_path}: expected dict, got {type(actual_value).__name__}" + ) + else: + # For non-dict values, only report if they're different + if actual_value != expected_value: + # Format the values to be more readable + actual_str = str(actual_value) + expected_str = str(expected_value) + if len(actual_str) > 50 or len(expected_str) > 50: + actual_str = f"{actual_str[:50]}..." + expected_str = f"{expected_str[:50]}..." + differences.append( + f"Value mismatch at {current_path}:\n expected: {expected_str}\n got: {actual_str}" + ) + return differences + + +@pytest.mark.asyncio +async def test_global_spend_keys_endpoint_limit_validation(client, monkeypatch): + """ + Test to ensure that the global_spend_keys endpoint is protected against SQL injection attacks. + Verifies that the limit parameter is properly parameterized and not directly interpolated. + """ + # Create a simple mock for prisma client with empty response + mock_prisma_client = MagicMock() + mock_db = MagicMock() + mock_query_raw = MagicMock() + mock_query_raw.return_value = asyncio.Future() + mock_query_raw.return_value.set_result([]) + mock_db.query_raw = mock_query_raw + mock_prisma_client.db = mock_db + # Apply the mock to the prisma_client module + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + # Call the endpoint without specifying a limit + no_limit_response = client.get("/global/spend/keys") + assert no_limit_response.status_code == 200 + mock_query_raw.assert_called_once_with('SELECT * FROM "Last30dKeysBySpend";') + # Reset the mock for the next test + mock_query_raw.reset_mock() + # Test with valid input + normal_limit = "10" + good_input_response = client.get(f"/global/spend/keys?limit={normal_limit}") + assert good_input_response.status_code == 200 + # Verify the mock was called with the correct parameters + mock_query_raw.assert_called_once_with( + 'SELECT * FROM "Last30dKeysBySpend" LIMIT $1 ;', 10 + ) + # Reset the mock for the next test + mock_query_raw.reset_mock() + # Test with SQL injection payload + sql_injection_limit = "10; DROP TABLE spend_logs; --" + response = client.get(f"/global/spend/keys?limit={sql_injection_limit}") + # Verify the response is a validation error (422) + assert response.status_code == 422 + # Verify the mock was not called with the SQL injection payload + # This confirms that the validation happens before the database query + mock_query_raw.assert_not_called() + # Reset the mock for the next test + mock_query_raw.reset_mock() + # Test with non-numeric input + non_numeric_limit = "abc" + response = client.get(f"/global/spend/keys?limit={non_numeric_limit}") + assert response.status_code == 422 + mock_query_raw.assert_not_called() + mock_query_raw.reset_mock() + # Test with negative number + negative_limit = "-5" + response = client.get(f"/global/spend/keys?limit={negative_limit}") + assert response.status_code == 422 + mock_query_raw.assert_not_called() + mock_query_raw.reset_mock() + # Test with zero + zero_limit = "0" + response = client.get(f"/global/spend/keys?limit={zero_limit}") + assert response.status_code == 422 + mock_query_raw.assert_not_called() + mock_query_raw.reset_mock() diff --git a/tests/litellm/router_utils/pre_call_checks/test_responses_api_deployment_check.py b/tests/litellm/router_utils/pre_call_checks/test_responses_api_deployment_check.py new file mode 100644 index 0000000000..c9dcf1c957 --- /dev/null +++ b/tests/litellm/router_utils/pre_call_checks/test_responses_api_deployment_check.py @@ -0,0 +1,578 @@ +import asyncio +import os +import sys +from typing import Optional +from unittest.mock import AsyncMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) +import json + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler +from litellm.types.llms.openai import ( + IncompleteDetails, + ResponseAPIUsage, + ResponseCompletedEvent, + ResponsesAPIResponse, + ResponseTextConfig, +) +from litellm.types.utils import StandardLoggingPayload + + +@pytest.mark.asyncio +async def test_responses_api_routing_with_previous_response_id(): + """ + Test that when using a previous_response_id, the request is sent to the same model_id + """ + # Create a mock response that simulates Azure responses API + mock_response_id = "resp_mock-resp-456" + + mock_response_data = { + "id": mock_response_id, + "object": "response", + "created_at": 1741476542, + "status": "completed", + "model": "azure/computer-use-preview", + "output": [ + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "I'm doing well, thank you for asking!", + "annotations": [], + } + ], + } + ], + "parallel_tool_calls": True, + "usage": { + "input_tokens": 10, + "output_tokens": 20, + "total_tokens": 30, + "output_tokens_details": {"reasoning_tokens": 0}, + }, + "text": {"format": {"type": "text"}}, + "error": None, + "incomplete_details": None, + "instructions": None, + "metadata": {}, + "temperature": 1.0, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "max_output_tokens": None, + "previous_response_id": None, + "reasoning": {"effort": None, "summary": None}, + "truncation": "disabled", + "user": None, + } + + class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self._json_data + + router = litellm.Router( + model_list=[ + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview-2", + "api_key": "mock-api-key-2", + "api_version": "mock-api-version-2", + "api_base": "https://mock-endpoint-2.openai.azure.com", + }, + }, + ], + optional_pre_call_checks=["responses_api_deployment_check"], + ) + MODEL = "azure-computer-use-preview" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + # Configure the mock to return our response + mock_post.return_value = MockResponse(mock_response_data, 200) + + # Make the initial request + # litellm._turn_on_debug() + response = await router.aresponses( + model=MODEL, + input="Hello, how are you?", + truncation="auto", + ) + print("RESPONSE", response) + + # Store the model_id from the response + expected_model_id = response._hidden_params["model_id"] + response_id = response.id + + print("Response ID=", response_id, "came from model_id=", expected_model_id) + + # Make 10 other requests with previous_response_id, assert that they are sent to the same model_id + for i in range(10): + # Reset the mock for the next call + mock_post.reset_mock() + + # Set up the mock to return our response again + mock_post.return_value = MockResponse(mock_response_data, 200) + + response = await router.aresponses( + model=MODEL, + input=f"Follow-up question {i+1}", + truncation="auto", + previous_response_id=response_id, + ) + + # Assert the model_id is preserved + assert response._hidden_params["model_id"] == expected_model_id + + +@pytest.mark.asyncio +async def test_routing_without_previous_response_id(): + """ + Test that normal routing (load balancing) works when no previous_response_id is provided + """ + mock_response_data = { + "id": "mock-resp-123", + "object": "response", + "created_at": 1741476542, + "status": "completed", + "model": "azure/computer-use-preview", + "output": [ + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "Hello there!", "annotations": []} + ], + } + ], + "parallel_tool_calls": True, + "usage": { + "input_tokens": 5, + "output_tokens": 10, + "total_tokens": 15, + "output_tokens_details": {"reasoning_tokens": 0}, + }, + "text": {"format": {"type": "text"}}, + "error": None, + "incomplete_details": None, + "instructions": None, + "metadata": {}, + "temperature": 1.0, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "max_output_tokens": None, + "previous_response_id": None, + "reasoning": {"effort": None, "summary": None}, + "truncation": "disabled", + "user": None, + } + + class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self._json_data + + # Create a router with two identical deployments to test load balancing + router = litellm.Router( + model_list=[ + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-1", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-1.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-2", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-2.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-3", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-3.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-4", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-4.openai.azure.com", + }, + }, + ], + optional_pre_call_checks=["responses_api_deployment_check"], + ) + + MODEL = "azure-computer-use-preview" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + # Configure the mock to return our response + mock_post.return_value = MockResponse(mock_response_data, 200) + + # Make multiple requests and verify we're hitting different deployments + used_model_ids = set() + + for i in range(20): + response = await router.aresponses( + model=MODEL, + input=f"Question {i}", + truncation="auto", + ) + + used_model_ids.add(response._hidden_params["model_id"]) + + # We should have used more than one model_id if load balancing is working + assert ( + len(used_model_ids) > 1 + ), "Load balancing isn't working, only one deployment was used" + + +@pytest.mark.asyncio +async def test_previous_response_id_not_in_cache(): + """ + Test behavior when a previous_response_id is provided but not found in cache + """ + mock_response_data = { + "id": "mock-resp-789", + "object": "response", + "created_at": 1741476542, + "status": "completed", + "model": "azure/computer-use-preview", + "output": [ + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "Nice to meet you!", + "annotations": [], + } + ], + } + ], + "parallel_tool_calls": True, + "usage": { + "input_tokens": 5, + "output_tokens": 10, + "total_tokens": 15, + "output_tokens_details": {"reasoning_tokens": 0}, + }, + "text": {"format": {"type": "text"}}, + "error": None, + "incomplete_details": None, + "instructions": None, + "metadata": {}, + "temperature": 1.0, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "max_output_tokens": None, + "previous_response_id": None, + "reasoning": {"effort": None, "summary": None}, + "truncation": "disabled", + "user": None, + } + + class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self._json_data + + router = litellm.Router( + model_list=[ + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-1", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-1.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview", + "api_key": "mock-api-key-2", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-2.openai.azure.com", + }, + }, + ], + optional_pre_call_checks=["responses_api_deployment_check"], + ) + + MODEL = "azure-computer-use-preview" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + # Configure the mock to return our response + mock_post.return_value = MockResponse(mock_response_data, 200) + + # Make a request with a non-existent previous_response_id + response = await router.aresponses( + model=MODEL, + input="Hello, this is a test", + truncation="auto", + previous_response_id="non-existent-response-id", + ) + + # Should still get a valid response + assert response is not None + assert response.id is not None + + # Since the previous_response_id wasn't found, routing should work normally + # We can't assert exactly which deployment was chosen, but we can verify the basics + assert response._hidden_params["model_id"] is not None + + +@pytest.mark.asyncio +async def test_multiple_response_ids_routing(): + """ + Test that different response IDs correctly route to their respective original deployments + """ + # Create two different mock responses for our two different deployments + mock_response_data_1 = { + "id": "mock-resp-deployment-1", + "object": "response", + "created_at": 1741476542, + "status": "completed", + "model": "azure/computer-use-preview", + "output": [ + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "Response from deployment 1", + "annotations": [], + } + ], + } + ], + "parallel_tool_calls": True, + "usage": { + "input_tokens": 5, + "output_tokens": 10, + "total_tokens": 15, + "output_tokens_details": {"reasoning_tokens": 0}, + }, + "text": {"format": {"type": "text"}}, + "error": None, + "incomplete_details": None, + "instructions": None, + "metadata": {}, + "temperature": 1.0, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "max_output_tokens": None, + "previous_response_id": None, + "reasoning": {"effort": None, "summary": None}, + "truncation": "disabled", + "user": None, + } + + mock_response_data_2 = { + "id": "mock-resp-deployment-2", + "object": "response", + "created_at": 1741476542, + "status": "completed", + "model": "azure/computer-use-preview", + "output": [ + { + "type": "message", + "id": "msg_456", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "Response from deployment 2", + "annotations": [], + } + ], + } + ], + "parallel_tool_calls": True, + "usage": { + "input_tokens": 5, + "output_tokens": 10, + "total_tokens": 15, + "output_tokens_details": {"reasoning_tokens": 0}, + }, + "text": {"format": {"type": "text"}}, + "error": None, + "incomplete_details": None, + "instructions": None, + "metadata": {}, + "temperature": 1.0, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "max_output_tokens": None, + "previous_response_id": None, + "reasoning": {"effort": None, "summary": None}, + "truncation": "disabled", + "user": None, + } + + class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self._json_data + + router = litellm.Router( + model_list=[ + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview-1", + "api_key": "mock-api-key-1", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-1.openai.azure.com", + }, + }, + { + "model_name": "azure-computer-use-preview", + "litellm_params": { + "model": "azure/computer-use-preview-2", + "api_key": "mock-api-key-2", + "api_version": "mock-api-version", + "api_base": "https://mock-endpoint-2.openai.azure.com", + }, + }, + ], + optional_pre_call_checks=["responses_api_deployment_check"], + ) + + MODEL = "azure-computer-use-preview" + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + # For the first request, return response from deployment 1 + mock_post.return_value = MockResponse(mock_response_data_1, 200) + + # Make the first request to deployment 1 + response1 = await router.aresponses( + model=MODEL, + input="Request to deployment 1", + truncation="auto", + ) + + # Store details from first response + model_id_1 = response1._hidden_params["model_id"] + response_id_1 = response1.id + + # For the second request, return response from deployment 2 + mock_post.return_value = MockResponse(mock_response_data_2, 200) + + # Make the second request to deployment 2 + response2 = await router.aresponses( + model=MODEL, + input="Request to deployment 2", + truncation="auto", + ) + + # Store details from second response + model_id_2 = response2._hidden_params["model_id"] + response_id_2 = response2.id + + # Wait for cache updates + await asyncio.sleep(1) + + # Now make follow-up requests using the previous response IDs + + # First, reset mock + mock_post.reset_mock() + mock_post.return_value = MockResponse(mock_response_data_1, 200) + + # Follow-up to response 1 should go to model_id_1 + follow_up_1 = await router.aresponses( + model=MODEL, + input="Follow up to deployment 1", + truncation="auto", + previous_response_id=response_id_1, + ) + + # Verify it went to the correct deployment + assert follow_up_1._hidden_params["model_id"] == model_id_1 + + # Reset mock again + mock_post.reset_mock() + mock_post.return_value = MockResponse(mock_response_data_2, 200) + + # Follow-up to response 2 should go to model_id_2 + follow_up_2 = await router.aresponses( + model=MODEL, + input="Follow up to deployment 2", + truncation="auto", + previous_response_id=response_id_2, + ) + + # Verify it went to the correct deployment + assert follow_up_2._hidden_params["model_id"] == model_id_2 diff --git a/tests/litellm/test_router.py b/tests/litellm/test_router.py new file mode 100644 index 0000000000..95bdfccebb --- /dev/null +++ b/tests/litellm/test_router.py @@ -0,0 +1,120 @@ +import copy +import json +import os +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + + +import litellm + + +def test_update_kwargs_does_not_mutate_defaults_and_merges_metadata(): + # initialize a real Router (env‑vars can be empty) + router = litellm.Router( + model_list=[ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": { + "model": "azure/chatgpt-v-3", + "api_key": os.getenv("AZURE_API_KEY"), + "api_version": os.getenv("AZURE_API_VERSION"), + "api_base": os.getenv("AZURE_API_BASE"), + }, + } + ], + ) + + # override to known defaults for the test + router.default_litellm_params = { + "foo": "bar", + "metadata": {"baz": 123}, + } + original = copy.deepcopy(router.default_litellm_params) + kwargs = {} + + # invoke the helper + router._update_kwargs_with_default_litellm_params( + kwargs=kwargs, + metadata_variable_name="litellm_metadata", + ) + + # 1) router.defaults must be unchanged + assert router.default_litellm_params == original + + # 2) non‑metadata keys get merged + assert kwargs["foo"] == "bar" + + # 3) metadata lands under "metadata" + assert kwargs["litellm_metadata"] == {"baz": 123} + + +def test_router_with_model_info_and_model_group(): + """ + Test edge case where user specifies model_group in model_info + """ + router = litellm.Router( + model_list=[ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": { + "model": "gpt-3.5-turbo", + }, + "model_info": { + "tpm": 1000, + "rpm": 1000, + "model_group": "gpt-3.5-turbo", + }, + } + ], + ) + + router._set_model_group_info( + model_group="gpt-3.5-turbo", + user_facing_model_group_name="gpt-3.5-turbo", + ) + + +@pytest.mark.asyncio +async def test_router_with_tags_and_fallbacks(): + """ + If fallback model missing tag, raise error + """ + from litellm import Router + + router = Router( + model_list=[ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": { + "model": "gpt-3.5-turbo", + "mock_response": "Hello, world!", + "tags": ["test"], + }, + }, + { + "model_name": "anthropic-claude-3-5-sonnet", + "litellm_params": { + "model": "claude-3-5-sonnet-latest", + "mock_response": "Hello, world 2!", + }, + }, + ], + fallbacks=[ + {"gpt-3.5-turbo": ["anthropic-claude-3-5-sonnet"]}, + ], + enable_tag_filtering=True, + ) + + with pytest.raises(Exception): + response = await router.acompletion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello, world!"}], + mock_testing_fallbacks=True, + metadata={"tags": ["test"]}, + ) diff --git a/tests/litellm/types/llms/test_types_llms_openai.py b/tests/litellm/types/llms/test_types_llms_openai.py new file mode 100644 index 0000000000..86c2cb3f1a --- /dev/null +++ b/tests/litellm/types/llms/test_types_llms_openai.py @@ -0,0 +1,21 @@ +import asyncio +import os +import sys +from typing import Optional +from unittest.mock import AsyncMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../../..")) +import json + +import litellm + + +def test_generic_event(): + from litellm.types.llms.openai import GenericEvent + + event = {"type": "test", "test": "test"} + event = GenericEvent(**event) + assert event.type == "test" + assert event.test == "test" diff --git a/tests/llm_responses_api_testing/base_responses_api.py b/tests/llm_responses_api_testing/base_responses_api.py new file mode 100644 index 0000000000..83122f9876 --- /dev/null +++ b/tests/llm_responses_api_testing/base_responses_api.py @@ -0,0 +1,317 @@ + +import httpx +import json +import pytest +import sys +from typing import Any, Dict, List +from unittest.mock import MagicMock, Mock, patch +import os +import uuid +import time +import base64 + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path +import litellm +from abc import ABC, abstractmethod + +from litellm.integrations.custom_logger import CustomLogger +import json +from litellm.types.utils import StandardLoggingPayload +from litellm.types.llms.openai import ( + ResponseCompletedEvent, + ResponsesAPIResponse, + ResponseTextConfig, + ResponseAPIUsage, + IncompleteDetails, +) +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + + +def validate_responses_api_response(response, final_chunk: bool = False): + """ + Validate that a response from litellm.responses() or litellm.aresponses() + conforms to the expected ResponsesAPIResponse structure. + + Args: + response: The response object to validate + + Raises: + AssertionError: If the response doesn't match the expected structure + """ + # Validate response structure + print("response=", json.dumps(response, indent=4, default=str)) + assert isinstance( + response, ResponsesAPIResponse + ), "Response should be an instance of ResponsesAPIResponse" + + # Required fields + assert "id" in response and isinstance( + response["id"], str + ), "Response should have a string 'id' field" + assert "created_at" in response and isinstance( + response["created_at"], (int, float) + ), "Response should have a numeric 'created_at' field" + assert "output" in response and isinstance( + response["output"], list + ), "Response should have a list 'output' field" + assert "parallel_tool_calls" in response and isinstance( + response["parallel_tool_calls"], bool + ), "Response should have a boolean 'parallel_tool_calls' field" + + # Optional fields with their expected types + optional_fields = { + "error": (dict, type(None)), # error can be dict or None + "incomplete_details": (IncompleteDetails, type(None)), + "instructions": (str, type(None)), + "metadata": dict, + "model": str, + "object": str, + "temperature": (int, float, type(None)), + "tool_choice": (dict, str), + "tools": list, + "top_p": (int, float, type(None)), + "max_output_tokens": (int, type(None)), + "previous_response_id": (str, type(None)), + "reasoning": dict, + "status": str, + "text": ResponseTextConfig, + "truncation": (str, type(None)), + "usage": ResponseAPIUsage, + "user": (str, type(None)), + } + if final_chunk is False: + optional_fields["usage"] = type(None) + + for field, expected_type in optional_fields.items(): + if field in response: + assert isinstance( + response[field], expected_type + ), f"Field '{field}' should be of type {expected_type}, but got {type(response[field])}" + + # Check if output has at least one item + if final_chunk is True: + assert ( + len(response["output"]) > 0 + ), "Response 'output' field should have at least one item" + + return True # Return True if validation passes + + + +class BaseResponsesAPITest(ABC): + """ + Abstract base test class that enforces a common test across all test classes. + """ + @abstractmethod + def get_base_completion_call_args(self) -> dict: + """Must return the base completion call args""" + pass + + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_openai_responses_api(self, sync_mode): + litellm._turn_on_debug() + litellm.set_verbose = True + base_completion_call_args = self.get_base_completion_call_args() + if sync_mode: + response = litellm.responses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + else: + response = await litellm.aresponses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + + print("litellm response=", json.dumps(response, indent=4, default=str)) + + # Use the helper function to validate the response + validate_responses_api_response(response, final_chunk=True) + + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_openai_responses_api_streaming(self, sync_mode): + litellm._turn_on_debug() + base_completion_call_args = self.get_base_completion_call_args() + collected_content_string = "" + response_completed_event = None + if sync_mode: + response = litellm.responses( + input="Basic ping", + stream=True, + **base_completion_call_args + ) + for event in response: + print("litellm response=", json.dumps(event, indent=4, default=str)) + if event.type == "response.output_text.delta": + collected_content_string += event.delta + elif event.type == "response.completed": + response_completed_event = event + else: + response = await litellm.aresponses( + input="Basic ping", + stream=True, + **base_completion_call_args + ) + async for event in response: + print("litellm response=", json.dumps(event, indent=4, default=str)) + if event.type == "response.output_text.delta": + collected_content_string += event.delta + elif event.type == "response.completed": + response_completed_event = event + + # assert the delta chunks content had len(collected_content_string) > 0 + # this content is typically rendered on chat ui's + assert len(collected_content_string) > 0 + + # assert the response completed event is not None + assert response_completed_event is not None + + # assert the response completed event has a response + assert response_completed_event.response is not None + + # assert the response completed event includes the usage + assert response_completed_event.response.usage is not None + + # basic test assert the usage seems reasonable + print("response_completed_event.response.usage=", response_completed_event.response.usage) + assert response_completed_event.response.usage.input_tokens > 0 and response_completed_event.response.usage.input_tokens < 100 + assert response_completed_event.response.usage.output_tokens > 0 and response_completed_event.response.usage.output_tokens < 1000 + assert response_completed_event.response.usage.total_tokens > 0 and response_completed_event.response.usage.total_tokens < 1000 + + # total tokens should be the sum of input and output tokens + assert response_completed_event.response.usage.total_tokens == response_completed_event.response.usage.input_tokens + response_completed_event.response.usage.output_tokens + + + + @pytest.mark.parametrize("sync_mode", [False, True]) + @pytest.mark.asyncio + async def test_basic_openai_responses_delete_endpoint(self, sync_mode): + litellm._turn_on_debug() + litellm.set_verbose = True + base_completion_call_args = self.get_base_completion_call_args() + if sync_mode: + response = litellm.responses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + + # delete the response + if isinstance(response, ResponsesAPIResponse): + litellm.delete_responses( + response_id=response.id, + **base_completion_call_args + ) + else: + raise ValueError("response is not a ResponsesAPIResponse") + else: + response = await litellm.aresponses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + + # async delete the response + if isinstance(response, ResponsesAPIResponse): + await litellm.adelete_responses( + response_id=response.id, + **base_completion_call_args + ) + else: + raise ValueError("response is not a ResponsesAPIResponse") + + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_openai_responses_streaming_delete_endpoint(self, sync_mode): + #litellm._turn_on_debug() + #litellm.set_verbose = True + base_completion_call_args = self.get_base_completion_call_args() + response_id = None + if sync_mode: + response_id = None + response = litellm.responses( + input="Basic ping", max_output_tokens=20, + stream=True, + **base_completion_call_args + ) + for event in response: + print("litellm response=", json.dumps(event, indent=4, default=str)) + if "response" in event: + response_obj = event.get("response") + if response_obj is not None: + response_id = response_obj.get("id") + print("got response_id=", response_id) + + # delete the response + assert response_id is not None + litellm.delete_responses( + response_id=response_id, + **base_completion_call_args + ) + else: + response = await litellm.aresponses( + input="Basic ping", max_output_tokens=20, + stream=True, + **base_completion_call_args + ) + async for event in response: + print("litellm response=", json.dumps(event, indent=4, default=str)) + if "response" in event: + response_obj = event.get("response") + if response_obj is not None: + response_id = response_obj.get("id") + print("got response_id=", response_id) + + # delete the response + assert response_id is not None + await litellm.adelete_responses( + response_id=response_id, + **base_completion_call_args + ) + + @pytest.mark.parametrize("sync_mode", [False, True]) + @pytest.mark.asyncio + async def test_basic_openai_responses_get_endpoint(self, sync_mode): + litellm._turn_on_debug() + litellm.set_verbose = True + base_completion_call_args = self.get_base_completion_call_args() + if sync_mode: + response = litellm.responses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + + # get the response + if isinstance(response, ResponsesAPIResponse): + result = litellm.get_responses( + response_id=response.id, + **base_completion_call_args + ) + assert result is not None + assert result.id == response.id + assert result.output == response.output + else: + raise ValueError("response is not a ResponsesAPIResponse") + else: + response = await litellm.aresponses( + input="Basic ping", max_output_tokens=20, + **base_completion_call_args + ) + # async get the response + if isinstance(response, ResponsesAPIResponse): + result = await litellm.aget_responses( + response_id=response.id, + **base_completion_call_args + ) + assert result is not None + assert result.id == response.id + assert result.output == response.output + else: + raise ValueError("response is not a ResponsesAPIResponse") + + diff --git a/tests/llm_responses_api_testing/test_anthropic_responses_api.py b/tests/llm_responses_api_testing/test_anthropic_responses_api.py new file mode 100644 index 0000000000..3eabc0c15b --- /dev/null +++ b/tests/llm_responses_api_testing/test_anthropic_responses_api.py @@ -0,0 +1,106 @@ +import os +import sys +import pytest +import asyncio +from typing import Optional +from unittest.mock import patch, AsyncMock + +sys.path.insert(0, os.path.abspath("../..")) +import litellm +from litellm.integrations.custom_logger import CustomLogger +import json +from litellm.types.utils import StandardLoggingPayload +from litellm.types.llms.openai import ( + ResponseCompletedEvent, + ResponsesAPIResponse, + ResponseTextConfig, + ResponseAPIUsage, + IncompleteDetails, +) +import litellm +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler +from base_responses_api import BaseResponsesAPITest +from openai.types.responses.function_tool import FunctionTool + + +class TestAnthropicResponsesAPITest(BaseResponsesAPITest): + def get_base_completion_call_args(self): + #litellm._turn_on_debug() + return { + "model": "anthropic/claude-3-5-sonnet-latest", + } + + async def test_basic_openai_responses_delete_endpoint(self, sync_mode=False): + pass + + async def test_basic_openai_responses_streaming_delete_endpoint(self, sync_mode=False): + pass + + async def test_basic_openai_responses_get_endpoint(self, sync_mode=False): + pass + + + + +def test_multiturn_tool_calls(): + # Test streaming response with tools for Anthropic + litellm._turn_on_debug() + shell_tool = dict(FunctionTool( + type="function", + name="shell", + description="Runs a shell command, and returns its output.", + parameters={ + "type": "object", + "properties": { + "command": {"type": "array", "items": {"type": "string"}}, + "workdir": {"type": "string", "description": "The working directory for the command."} + }, + "required": ["command"] + }, + strict=True + )) + + + + # Step 1: Initial request with the tool + response = litellm.responses( + input=[{ + 'role': 'user', + 'content': [ + {'type': 'input_text', 'text': 'make a hello world html file'} + ], + 'type': 'message' + }], + model='anthropic/claude-3-7-sonnet-latest', + instructions='You are a helpful coding assistant.', + tools=[shell_tool] + ) + + print("response=", response) + + # Step 2: Send the results of the tool call back to the model + # Get the response ID and tool call ID from the response + + response_id = response.id + tool_call_id = "" + for item in response.output: + if 'type' in item and item['type'] == 'function_call': + tool_call_id = item['call_id'] + break + + # Use await with asyncio.run for the async function + follow_up_response = litellm.responses( + model='anthropic/claude-3-7-sonnet-latest', + previous_response_id=response_id, + input=[{ + 'type': 'function_call_output', + 'call_id': tool_call_id, + 'output': '{"output":"\\n\\n Hello Page\\n\\n\\n

Hi

\\n

Welcome to this simple webpage!

\\n\\n > index.html\\n","metadata":{"exit_code":0,"duration_seconds":0}}' + }], + tools=[shell_tool] + ) + + print("follow_up_response=", follow_up_response) + + + \ No newline at end of file diff --git a/tests/llm_responses_api_testing/test_azure_responses_api.py b/tests/llm_responses_api_testing/test_azure_responses_api.py new file mode 100644 index 0000000000..725a6efd5a --- /dev/null +++ b/tests/llm_responses_api_testing/test_azure_responses_api.py @@ -0,0 +1,31 @@ +import os +import sys +import pytest +import asyncio +from typing import Optional +from unittest.mock import patch, AsyncMock + +sys.path.insert(0, os.path.abspath("../..")) +import litellm +from litellm.integrations.custom_logger import CustomLogger +import json +from litellm.types.utils import StandardLoggingPayload +from litellm.types.llms.openai import ( + ResponseCompletedEvent, + ResponsesAPIResponse, + ResponseTextConfig, + ResponseAPIUsage, + IncompleteDetails, +) +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler +from base_responses_api import BaseResponsesAPITest + +class TestAzureResponsesAPITest(BaseResponsesAPITest): + def get_base_completion_call_args(self): + return { + "model": "azure/computer-use-preview", + "truncation": "auto", + "api_base": os.getenv("AZURE_RESPONSES_OPENAI_ENDPOINT"), + "api_key": os.getenv("AZURE_RESPONSES_OPENAI_API_KEY"), + "api_version": os.getenv("AZURE_RESPONSES_OPENAI_API_VERSION"), + } diff --git a/tests/llm_responses_api_testing/test_openai_responses_api.py b/tests/llm_responses_api_testing/test_openai_responses_api.py index 677e13b08a..333f99f9d4 100644 --- a/tests/llm_responses_api_testing/test_openai_responses_api.py +++ b/tests/llm_responses_api_testing/test_openai_responses_api.py @@ -18,119 +18,13 @@ from litellm.types.llms.openai import ( IncompleteDetails, ) from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler +from base_responses_api import BaseResponsesAPITest, validate_responses_api_response - -def validate_responses_api_response(response, final_chunk: bool = False): - """ - Validate that a response from litellm.responses() or litellm.aresponses() - conforms to the expected ResponsesAPIResponse structure. - - Args: - response: The response object to validate - - Raises: - AssertionError: If the response doesn't match the expected structure - """ - # Validate response structure - print("response=", json.dumps(response, indent=4, default=str)) - assert isinstance( - response, ResponsesAPIResponse - ), "Response should be an instance of ResponsesAPIResponse" - - # Required fields - assert "id" in response and isinstance( - response["id"], str - ), "Response should have a string 'id' field" - assert "created_at" in response and isinstance( - response["created_at"], (int, float) - ), "Response should have a numeric 'created_at' field" - assert "output" in response and isinstance( - response["output"], list - ), "Response should have a list 'output' field" - assert "parallel_tool_calls" in response and isinstance( - response["parallel_tool_calls"], bool - ), "Response should have a boolean 'parallel_tool_calls' field" - - # Optional fields with their expected types - optional_fields = { - "error": (dict, type(None)), # error can be dict or None - "incomplete_details": (IncompleteDetails, type(None)), - "instructions": (str, type(None)), - "metadata": dict, - "model": str, - "object": str, - "temperature": (int, float), - "tool_choice": (dict, str), - "tools": list, - "top_p": (int, float), - "max_output_tokens": (int, type(None)), - "previous_response_id": (str, type(None)), - "reasoning": dict, - "status": str, - "text": ResponseTextConfig, - "truncation": str, - "usage": ResponseAPIUsage, - "user": (str, type(None)), - } - if final_chunk is False: - optional_fields["usage"] = type(None) - - for field, expected_type in optional_fields.items(): - if field in response: - assert isinstance( - response[field], expected_type - ), f"Field '{field}' should be of type {expected_type}, but got {type(response[field])}" - - # Check if output has at least one item - if final_chunk is True: - assert ( - len(response["output"]) > 0 - ), "Response 'output' field should have at least one item" - - return True # Return True if validation passes - - -@pytest.mark.parametrize("sync_mode", [True, False]) -@pytest.mark.asyncio -async def test_basic_openai_responses_api(sync_mode): - litellm._turn_on_debug() - litellm.set_verbose = True - if sync_mode: - response = litellm.responses( - model="gpt-4o", input="Basic ping", max_output_tokens=20 - ) - else: - response = await litellm.aresponses( - model="gpt-4o", input="Basic ping", max_output_tokens=20 - ) - - print("litellm response=", json.dumps(response, indent=4, default=str)) - - # Use the helper function to validate the response - validate_responses_api_response(response, final_chunk=True) - - -@pytest.mark.parametrize("sync_mode", [True]) -@pytest.mark.asyncio -async def test_basic_openai_responses_api_streaming(sync_mode): - litellm._turn_on_debug() - - if sync_mode: - response = litellm.responses( - model="gpt-4o", - input="Basic ping", - stream=True, - ) - for event in response: - print("litellm response=", json.dumps(event, indent=4, default=str)) - else: - response = await litellm.aresponses( - model="gpt-4o", - input="Basic ping", - stream=True, - ) - async for event in response: - print("litellm response=", json.dumps(event, indent=4, default=str)) +class TestOpenAIResponsesAPITest(BaseResponsesAPITest): + def get_base_completion_call_args(self): + return { + "model": "openai/gpt-4o", + } class TestCustomLogger(CustomLogger): @@ -693,7 +587,7 @@ async def test_openai_responses_litellm_router_no_metadata(): # Assert metadata is not in the request assert ( - loaded_request_body["metadata"] == None + "metadata" not in loaded_request_body ), "metadata should not be in the request body" mock_post.assert_called_once() @@ -910,7 +804,7 @@ async def test_openai_o1_pro_response_api(sync_mode): print("Response:", json.dumps(response, indent=4, default=str)) # Check that the response has the expected structure - assert response["id"] == mock_response["id"] + assert response["id"] is not None assert response["status"] == "incomplete" assert response["incomplete_details"].reason == "max_output_tokens" assert response["max_output_tokens"] == 20 @@ -1042,3 +936,100 @@ async def test_openai_o1_pro_response_api_streaming(sync_mode): assert request_body["model"] == "o1-pro" assert request_body["max_output_tokens"] == 20 assert "stream" not in request_body + + +def test_basic_computer_use_preview_tool_call(): + """ + Test that LiteLLM correctly handles a computer_use_preview tool call where the environment is set to "linux" + + linux is an unsupported environment for the computer_use_preview tool, but litellm users should still be able to pass it to openai + """ + # Mock response from OpenAI + + mock_response = { + "id": "resp_67dc3dd77b388190822443a85252da5a0e13d8bdc0e28d88", + "object": "response", + "created_at": 1742486999, + "status": "incomplete", + "error": None, + "incomplete_details": {"reason": "max_output_tokens"}, + "instructions": None, + "max_output_tokens": 20, + "model": "o1-pro-2025-03-19", + "output": [ + { + "type": "reasoning", + "id": "rs_67dc3de50f64819097450ed50a33d5f90e13d8bdc0e28d88", + "summary": [], + } + ], + "parallel_tool_calls": True, + "previous_response_id": None, + "reasoning": {"effort": "medium", "generate_summary": None}, + "store": True, + "temperature": 1.0, + "text": {"format": {"type": "text"}}, + "tool_choice": "auto", + "tools": [], + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 73, + "input_tokens_details": {"cached_tokens": 0}, + "output_tokens": 20, + "output_tokens_details": {"reasoning_tokens": 0}, + "total_tokens": 93, + }, + "user": None, + "metadata": {}, + } + class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self._json_data + + with patch( + "litellm.llms.custom_httpx.http_handler.HTTPHandler.post", + return_value=MockResponse(mock_response, 200), + ) as mock_post: + litellm._turn_on_debug() + litellm.set_verbose = True + + # Call the responses API with computer_use_preview tool + response = litellm.responses( + model="openai/computer-use-preview", + tools=[{ + "type": "computer_use_preview", + "display_width": 1024, + "display_height": 768, + "environment": "linux" # other possible values: "mac", "windows", "ubuntu" + }], + input="Check the latest OpenAI news on bing.com.", + reasoning={"summary": "concise"}, + truncation="auto" + ) + + # Verify the request was made correctly + mock_post.assert_called_once() + request_body = json.loads(mock_post.call_args.kwargs["data"]) + + # Validate the request structure + assert request_body["model"] == "computer-use-preview" + assert len(request_body["tools"]) == 1 + assert request_body["tools"][0]["type"] == "computer_use_preview" + assert request_body["tools"][0]["display_width"] == 1024 + assert request_body["tools"][0]["display_height"] == 768 + assert request_body["tools"][0]["environment"] == "linux" + + # Check that reasoning was passed correctly + assert request_body["reasoning"]["summary"] == "concise" + assert request_body["truncation"] == "auto" + + # Validate the input format + assert isinstance(request_body["input"], str) + assert request_body["input"] == "Check the latest OpenAI news on bing.com." + \ No newline at end of file diff --git a/tests/llm_translation/base_llm_unit_tests.py b/tests/llm_translation/base_llm_unit_tests.py index bd3627f7d4..6a2cacd20a 100644 --- a/tests/llm_translation/base_llm_unit_tests.py +++ b/tests/llm_translation/base_llm_unit_tests.py @@ -76,6 +76,11 @@ class BaseLLMChatTest(ABC): """Must return the base completion call args""" pass + + def get_base_completion_call_args_with_reasoning_model(self) -> dict: + """Must return the base completion call args with reasoning_effort""" + return {} + def test_developer_role_translation(self): """ Test that the developer role is translated correctly for non-OpenAI providers. @@ -806,6 +811,7 @@ class BaseLLMChatTest(ABC): return url + @pytest.mark.flaky(retries=3, delay=1) def test_empty_tools(self): """ Related Issue: https://github.com/BerriAI/litellm/issues/9080 @@ -828,6 +834,8 @@ class BaseLLMChatTest(ABC): response = completion(**base_completion_call_args, messages=[{"role": "user", "content": "Hello, how are you?"}], tools=[]) # just make sure call doesn't fail print("response: ", response) assert response is not None + except litellm.ContentPolicyViolationError: + pass except litellm.InternalServerError: pytest.skip("Model is overloaded") except litellm.RateLimitError: @@ -835,6 +843,7 @@ class BaseLLMChatTest(ABC): except Exception as e: pytest.fail(f"Error occurred: {e}") + @pytest.mark.flaky(retries=3, delay=1) def test_basic_tool_calling(self): try: from litellm import completion, ModelResponse @@ -891,6 +900,13 @@ class BaseLLMChatTest(ABC): assert response is not None # if the provider did not return any tool calls do not make a subsequent llm api call + if response.choices[0].message.content is not None: + try: + json.loads(response.choices[0].message.content) + pytest.fail(f"Tool call returned in content instead of tool_calls") + except Exception as e: + print(f"Error: {e}") + pass if response.choices[0].message.tool_calls is None: return # Add any assertions here to check the response @@ -931,6 +947,8 @@ class BaseLLMChatTest(ABC): second_response.choices[0].message.content is not None or second_response.choices[0].message.tool_calls is not None ) + except litellm.ServiceUnavailableError: + pytest.skip("Model is overloaded") except litellm.InternalServerError: pytest.skip("Model is overloaded") except litellm.RateLimitError: @@ -1126,6 +1144,46 @@ class BaseLLMChatTest(ABC): print(response) + def test_reasoning_effort(self): + """Test that reasoning_effort is passed correctly to the model""" + from litellm.utils import supports_reasoning + from litellm import completion + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + base_completion_call_args = self.get_base_completion_call_args_with_reasoning_model() + if len(base_completion_call_args) == 0: + print("base_completion_call_args is empty") + pytest.skip("Model does not support reasoning") + if not supports_reasoning(base_completion_call_args["model"], None): + print("Model does not support reasoning") + pytest.skip("Model does not support reasoning") + + _, provider, _, _ = litellm.get_llm_provider( + model=base_completion_call_args["model"] + ) + + ## CHECK PARAM MAPPING + optional_params = get_optional_params( + model=base_completion_call_args["model"], + custom_llm_provider=provider, + reasoning_effort="high", + ) + # either accepts reasoning effort or thinking budget + assert "reasoning_effort" in optional_params or "4096" in json.dumps(optional_params) + + try: + litellm._turn_on_debug() + response = completion( + **base_completion_call_args, + reasoning_effort="low", + messages=[{"role": "user", "content": "Hello!"}], + ) + print(f"response: {response}") + except Exception as e: + pytest.fail(f"Error: {e}") + class BaseOSeriesModelsTest(ABC): # test across azure/openai diff --git a/tests/llm_translation/test_anthropic_completion.py b/tests/llm_translation/test_anthropic_completion.py index 078467f588..73d0bec5bf 100644 --- a/tests/llm_translation/test_anthropic_completion.py +++ b/tests/llm_translation/test_anthropic_completion.py @@ -968,6 +968,107 @@ def test_anthropic_citations_api_streaming(): assert has_citations +@pytest.mark.parametrize( + "model", + [ + "anthropic/claude-3-7-sonnet-20250219", + "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + ], +) +def test_anthropic_thinking_output(model): + from litellm import completion + + litellm._turn_on_debug() + + resp = completion( + model=model, + messages=[{"role": "user", "content": "What is the capital of France?"}], + thinking={"type": "enabled", "budget_tokens": 1024}, + ) + + print(resp) + assert resp.choices[0].message.reasoning_content is not None + assert isinstance(resp.choices[0].message.reasoning_content, str) + assert resp.choices[0].message.thinking_blocks is not None + assert isinstance(resp.choices[0].message.thinking_blocks, list) + assert len(resp.choices[0].message.thinking_blocks) > 0 + + assert resp.choices[0].message.thinking_blocks[0]["type"] == "thinking" + assert resp.choices[0].message.thinking_blocks[0]["signature"] is not None + + +@pytest.mark.parametrize( + "model", + [ + "anthropic/claude-3-7-sonnet-20250219", + # "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + ], +) +def test_anthropic_redacted_thinking_output(model): + from litellm import completion + + litellm._turn_on_debug() + + resp = completion( + model=model, + messages=[{"role": "user", "content": "ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB"}], + thinking={"type": "enabled", "budget_tokens": 1024}, + ) + + print(resp) + assert resp.choices[0].message.thinking_blocks is not None + assert isinstance(resp.choices[0].message.thinking_blocks, list) + assert len(resp.choices[0].message.thinking_blocks) > 0 + assert resp.choices[0].message.thinking_blocks[0]["type"] == "redacted_thinking" + assert resp.choices[0].message.thinking_blocks[0]["data"] is not None + + + + +@pytest.mark.parametrize( + "model", + [ + "anthropic/claude-3-7-sonnet-20250219", + # "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + # "bedrock/invoke/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + ], +) +def test_anthropic_thinking_output_stream(model): + litellm.set_verbose = True + try: + # litellm._turn_on_debug() + resp = litellm.completion( + model=model, + messages=[{"role": "user", "content": "Tell me a joke."}], + stream=True, + thinking={"type": "enabled", "budget_tokens": 1024}, + timeout=10, + ) + + reasoning_content_exists = False + signature_block_exists = False + for chunk in resp: + print(f"chunk 2: {chunk}") + if ( + hasattr(chunk.choices[0].delta, "thinking_blocks") + and chunk.choices[0].delta.thinking_blocks is not None + and chunk.choices[0].delta.reasoning_content is not None + and isinstance(chunk.choices[0].delta.thinking_blocks, list) + and len(chunk.choices[0].delta.thinking_blocks) > 0 + and isinstance(chunk.choices[0].delta.reasoning_content, str) + ): + reasoning_content_exists = True + print(chunk.choices[0].delta.thinking_blocks[0]) + if chunk.choices[0].delta.thinking_blocks[0].get("signature"): + signature_block_exists = True + assert chunk.choices[0].delta.thinking_blocks[0]["type"] == "thinking" + assert reasoning_content_exists + assert signature_block_exists + except litellm.Timeout: + pytest.skip("Model is timing out") + + + def test_anthropic_custom_headers(): from litellm import completion from litellm.llms.custom_httpx.http_handler import HTTPHandler @@ -1045,3 +1146,37 @@ def test_anthropic_thinking_in_assistant_message(model): +@pytest.mark.parametrize( + "model", + [ + "anthropic/claude-3-7-sonnet-20250219", + # "bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0", + ], +) +def test_anthropic_redacted_thinking_in_assistant_message(model): + litellm._turn_on_debug() + params = { + "model": model, + "messages": [ + { + "role": "assistant", + "content": [ + { + "type": "redacted_thinking", + "data": "EqkBCkYIARgCKkAflgFkky5bvpaXt2GnDYgbA8QOCr+BF53t+UmiRA22Z7Ply9z2xfTGYSqvjlhIEsV6WDPdVoXndztvhKCzE2PUEgxwXpRD1hBLUSajVWoaDEftxmhqdg0mRwPUGCIwcht1EH91+gznPoaMNquU4sGeaOLFaeyNeG4dJXsYT/Jc4OG3453LN5ra4uVxC/GgKhGMQ1A9aO2Ac0O5M+bOdp1RFw==Eo0CCkYIARgCKkCcHATldbjR0vfU1DlNaQr3J2GKem6OjFybQyshp4C9XnysT/6y1CNcI+VGsbX99GfKLGqcsGYr81WlM+d7NscJEgxzkyZuwL3QnnxFiUUaDIA3nZpQa15D5XD72yIwyIGpJwhdavzXvE1bQLZj43aNtznG6Uwsxx4ZlLv83SUqH7GqzMxvm3stLj3cYmKMKnUqqhpeluvoxODUY/fhhF6Bjsj9C1MIRL+9urDH2EtAmZ+BrvLoXjRlbEH9+DtzLE57I1ShMDbUqLJXxXTcjhPkmu3JscBYf0waXfUgrQl2Pnv5dAxM2S3ZASk8di7ak0XcRknVBhhaR2ykdDbVyxzFzyZo8Fc=EtcBCkYIARgCKkCl6nQeKqHIBgdZ1EByLfEwnlZxsZWoDwablEKqRAIrKvB10ccs6RZqrTMZgcMLaW3QpWwnI4fC/WiOe811B94JEgyvTK4+E/zB+a42bYcaDOPesimKdlIPLT7VQiIwplWjvDcbe16vZSJ0OezjHCHEvML4QJPyvGE3NRHcLzC9UiGYriFys5zgv0O7qKr5Kj/56IL1BbaFqSANA7vjGoW+GSlv294L4LzqNWCD0ANzDnEjlXlVeibNM74v+KKXRVwn/IInHPog4hJA0/3GQyA=EtwBCkYIARgCKkBda4XEzq+PTfE7niGdYVzvAXRTb+3ujsDVGhVNtFnPx6K/I6ORfxOWmwEuk7iXygehQA18p0CVYLsCU4AHFvtjEgzYH2JNCxa8F07pGioaDOA635mdHKbyiecBJSIwshUavES7HZBnA4l3k8l92LAhuJQV1C5tUgKkk0pHRT+/OzDfXvxsZSx7AmR7J3QXKkQwHL6K9yZEWdeh/B22ft/GxyRViO7nZrT95PAAux31u++rYQyeFJ+rv0Yrs/KoBnlNUg9YFOpDMo1bMWV9n4CGwq92bw==EtEBCkYIARgCKkCZdn2NBzxiOEJt/E8VOs6YLbYjRaCkvhEdz5apcEZlBQJpulvgv1JvamrMZD0FCJZVTwxd/65M9Ady/LbtYTh7EgwtL7W9DXSFjxPErCIaDGk0e/bXY8yJdjk3CSIwYS0TtiaFK8tJrREBFA9IOp+q+tnE8Wl338CbbskRvF5topYmtofuBIG4GQkHvbQjKjn2BmwrEic/CdSEVbvEix7AWEsw92DabVmseTQhUbbuYRa4Ou6jXMW2pMJFUBjMr95gF6BlVFr4iEA=EsUBCkYIARgCKkAsEmKjMN9TVYLyBdo1+0uopommcjQx8Fu65+mje5Ft05KOnyKAzuUyORtk5r73glan8L+WlygaOOrZ1hi81219EgwpdTA6qbcaggIWeTIaDDrJ0eTbsqku4VSY8CIw3mJfRyv7ISHih4mpAVioGuuduXbaie5eKn5a+WgQiOmm22uZ4Gv72uluCSGGriHnKi28bHMomrytYLvKNvhL51yf5/Tgm/lIgQ9gyTJLqVzVjGn6ng1sN8vUti/tuGw=EsoBCkYIARgCKkB+jJBrxqqpzyGt5RXDKTBVxTnE8IrYRysAL2U/H171INDMCxrDHxfts3M0wuQirXN/2fZXwmQJIZRzzumA+I2sEgw0ySDeyTfHgTiafo8aDKOTl485koQiPwXipyIwG9n/zWUZ+tgfFELW2rV5/yo6Pq/r9bJdrd2b25qCATwX2gd54gsjWhSvLDkD7pLJKjL6ZuiW4N6hVo6JIR4UL8LxcsP9tET0ElIgQZ/h8HOIi18fQKsEdtseWCFnuXse21KIeg==EtwBCkYIARgCKkDWMlgTA+iKsScbpNtZab6dgMKRZYpQSoJ274+n0TqvLAqHL8GxLm1sMVom81LcVWCZZeIVQFbkmbJxyBovvLoUEgxy6YGb0EeJW10P8XEaDKowL3qI/z000pgR2SIwZIczlDKkqw75UYcEOC6Cx9yc0CdYjJnmQOa4Ezni20SANA8YnBMIYJqW4osO/KalKkTLmgvJRQE1Hk8Bn3af9fIYt+vITYEY4Wr7/UVNBtSXBOMP0YoSgNyzjX/pu2N3oy2Blv/YAgtHIJ3Xwd43clN5F2wU+Q==EtQBCkYIARgCKkD3vxW2GsLyEGtmBpI6NdNyh4i/ea7E9rp5puSHdk/dSCpW5G1wI3nrFIS2bUqZsvsDu3YgcDixG8eeDnzacC/qEgzilh/V8vaE1X9lRlIaDAa17eq6kSgaRrsAfSIwFAXgLu5BUKldMeQdcomRqgmY9hDzkDlRnBrbO9GxXsrmpGTU9iqVZQ7z9OVW522bKjyB/GeuNlv4V8a8uricx1InN8q94coWGCRPvAJVAvhP/YMCcNlvrgoN8C2RGc13e88uDq01r6gpkWTlVDY=EssBCkYIARgCKkAOhKBpvfqIElQ1mlG7NiCiolHnqagXryuwNsODnttLBeVMGBsZ8DgpSGWonVE/22MQgciWLY7WaaeoDcpL3X/pEgx4xuL/KqOgxrBnau4aDH3pQ/Sqr1aHa68YiiIwR6+w9QOWFfut8ZG8z+QkAO/kZVePcELKabHp7ikY+DOjvOt4FfnaChwQFTSGzZhaKjPK4MwQukuZIT1PFGFIh20Hi6wMQlHvsChIF88nUV2EAz4Sgb/vWPiQBbWP3gT3hJBehQY=EtMBCkYIARgCKkCT0yD5m4Rvs3KBNkAC2g7aprLTzKRqF+vdHAeYte9KngJZhThexj65o+q9HOGhIIAsboRhz70xkAybdQdsrg8OEgzQm1M980FeZMCi1XsaDJSFOpIuOhUOkPIs+iIw62jO5yY9ZETmrYtEb+pYN5Cyf467YVOOv7FBo44gIFgUvFklU5+y09k3MGzrBNViKjvkopPoFbpYI9ilB3dN6pAzrzhDzOum+Rsx1N25+UYvdT+yYBilrIPW1XmLmzT+ZMs4eV5caG35ZsNsjQ==EtwBCkYIARgCKkCOShz0/2ZO3u0WH8PBN63fAwKo4TcNFM3axUJL9dK9JJDLtC0XwP9Ee4vqPZyLBao4RyAefbYmY3TJ1As/AbuvEgxbYiyN4UcjaJU9mwkaDP9L3FACdMRQ+UFOSSIwQ0btU6cKIRsSNzvBsP8Fa4Ab7vOnlo4YSAv2lD7ZdDKVcQaWQZHYsQb/QQDfIGKGKkRXhNoET9KyQkb/x8lVpUR1d2u/sHTdgKEjkUdQop88SUFHvkGcJrMUTvnuvUdO4MdHwKnN0IINbDHTEUjUXSQPkpfTTA==EtwBCkYIARgCKkCIwQCFJUrhd1aT8hGMNcPIl+CaSZWsqerPDUGzZnS2tt2+tAs+TAPcKVHC07BdEXj6aKSbrOb8b7OQ/KFbrWJ4Egz980omEnE4djm8t5UaDDXrDJWgFSuZ+LWFmSIw/RzMo5ncKnqvf0TZ1krxMi4/DpAZb0Lgmc1XxGT2JPA4At9EEHNVPrWLXwGM3vUYKkQltG8EJFOWL1In5541dca1pnRDyBg4JVRQ5CuvA/pUCI2e9ARiODI7D+ydZorcnWQ7j2Qc1DguMQVHMbPLyGbQx9vqgQ==EtsBCkYIARgCKkDiH+ww5G0OgaW7zSQD7ZKYdViZfi+KO+TkA/k4rlTKsIwpUILZZ/53ppu93xaEazsD92GXKKSG3B/jBCqjQRg7EgzR3K/BJFTt359xPOgaDEHyoGVloiLS71ufAiIwO77B26VivdVgd2Dmv3DOtUAFs/jDwLM9EmNCBeoivwJPD2hYEKNm6TUWTinGfO2jKkNbrYgpA5esB0y1iXA0qGwRAmnD8ykZc0DT40vvd9EDvb5gHCd7RyjEU9BKnXBPWpGdTi4U+LZKYQ9LEE6sJ8vBm8w3EtUBCkYIARgCKkBbxQIjnTzzKf8Qhfcu+so91+MMbpJNyga27D9tZBtTexYLMJtzDWux4urfCc5TjjX0MvK62lKkhcPLuJE7KiI8EgzFF+TlNgPNp6RoyQgaDBAUDEAsqBMj7z4kciIwUWEZMGkG8ZnjltVpuffHxw5Rqyc+Smh1MnqnWxo0JlCOC43W5JH5KoJ/4RDxX7IjKj2fs5F6eiRMEi+L4KyjDBIvoPoE/wrdC+Fo6c8lMJiYw0MJ/lXgJQv6p0GRe251X+pcfN+2lx067/GLP6qjEtsBCkYIARgCKkCItf9nN0FKJsetom0ZoZvccwboNM2erGP7tIAYsOzsA9lmh7rFI2mFbOOC2WZ1v+QkvxppQ2wO+N35t29LC7RPEgzyJgiM1GHTVN+VPPwaDOXyzSg9BQ85oi58DCIwu/JxKJwVECkbru1d05yhwMYDsJrSJW1BO2ZBrg8Tb48S+dpD6hEPd1itq8cSM3ChKkNv83rGY8Gjg2DiTWDsIqUCD0pb2drrwnjkherr5/EQWdhHC7MijF8zyvqU4tBZrxP+64GcII7P87ja8B4YxGUIw9J7Et0BCkYIARgCKkCInOjYRgGSjcV/WHJ6HjB983rvz/nrOZ9xZMdrTYdHURtXN4zMAjZYQ8ZBk31n4aFGv5PAtDfbjqcytZUaCKicEgwXQrjgS0FHWq/2PwAaDKjYgoXuPPq+RNJUvCIwh1VmSiLGu+3pl7RcCBxnH/ue38EUDZAIRYiDI59h8CVdZpDSqaH8yJvFlR5Jxc8xKkXcEPduWcuONY+vatnIo5AQeSh9HM4oM4DoDma1OvVfdPUpbvaTP3ZhEv4iOMjvwzHBBkvc8b9jV2oTb8Xe50COLFJvURk=EtcBCkYIARgCKkDM4CyfgVBHhusU4C0tg/RwXiAbNtjOoYfcufGUnFlQKcpuJnekvb61EAerBrELguIrvNJIbyqy0Kcd/r64hu1UEgyITWjG3/cVsm/o0JkaDKm1/y0HF1YpqoiFoCIwqImOpk6SngP99aXE4p5c7y9rOvVo3lmKidTUdi1lmtoEZ9sXdY49nLsGeCuCjPJKKj976uFmgrZWIEZIL+HQGVjDOJ7mK8NzAxjX3m0AELsWN5FgbGOHus/S4o2EKi43/MLaRervgaFdrxK9BKGE6LY=EtMBCkYIARgCKkDvEoH/lv1fRxN+JaknzdY53WmQrEGJ7yupv22X2TdxN2+GmY8l1KYONWboOxalfoSbSlp3+zVJXdvTCa60CYnnEgyUslgNTFL5iGt+aq0aDESsIoNRuPYqDc5fbCIw9gHGejHXKw9GMR0sw1RnIF2FBI5Zo5/4EK2AFZ8BU5yAYgJw0wTc16ZVEFEraKS+KjtqVPmiodedFzc+f4kr+U8dy+xQtcsmTe9KcvAYmskvZ6Kl6iCitm/PZdjl/7COePcTVu32QnxZuG4Mpw==EtEBCkYIARgCKkB/SdSv2Jo8DJ4pOOK4mYXhSsPrnf6/ESHL7voj6FbdYPsgg2f3XQByQV93Menel5tgcx0jvNfY7Z9nx4Rz3iTvEgxN/mWUwb6Lb/1BfkAaDBONEsjWD1fKeK8H/iIwy+yJUFPTde2wxI/j6em5uS8HWGsfX9pUB4u/K4QHAd85bn63rrXSxbe2DHIG620UKjk+C6q3aXztOAGAyvhjiN9lnNAFPv93GTnwj+14n07c/xPdHBQyXXi742UBjFdQkmwp3m6RWf5psYU=EuQBCkYIARgCKkBxavD9zRmeX22ltvtCNzZzXTpsAHmNwSuejX7ibJueaDQaSOykBjNJavdMn6yQ8mAxCpNrNmhtBhGxHBGZE668EgzFNqHVE2WctK5ZiN0aDGNFTI5T3/0vDCtFXiIwRDXV5+9nWYGzuih8cG8h4dCs+n90rcL/Tz78QKsfpZeLNpr4aZSU8KHO2OmcmFoOKkxdgzKPy/gOfcCELsudlawbVyobU4CIhOYacIPhi+0XvgjXpqP0JIANaOdawb2zWrKhBKNA4VCHzbFkDm9cV1WrGIw0cEJ3oRU7idRgEsEBCkYIARgCKkDJUpJz2Ct4ZZJlWkAGg1Lc/rVqCd/V5rq01yehv9GkTIaq9H2jgjVKnUV1e4o9F1cUxmMk6fn4XK01sp/szP2GEgyvuemo2Di0USGKingaDCAMXK1kWRk6KofoyyIwxr/Jdwz2RrUytRWMGjrs4MkcQ2rhrVL/00Ktebga9cwrqeDOq+7nN8L64V+XEwsJKimHdmpCQPqYz8rIX25+v2XqcBDXzoBW8+eqdJKRhKcYooLbBXK3DUgRVQ==", + }, + { + "type": "text", + "text": "I'm not able to respond to special commands or trigger phrases like the one you've shared. Those types of strings don't activate any special modes or features in my system. Is there something specific I can help you with today? I'm happy to assist with questions, have a conversation, provide information, or help with various tasks within my normal capabilities.", + }, + ], + }, + {"role": "user", "content": [{"type": "text", "text": "Who do you know?"}]}, + ], + "max_tokens": 32768, + "thinking": {"type": "enabled", "budget_tokens": 30720}, + } + + response = litellm.completion(**params) + + assert response is not None diff --git a/tests/llm_translation/test_azure_ai.py b/tests/llm_translation/test_azure_ai.py index 6ec2050638..62f68b02d3 100644 --- a/tests/llm_translation/test_azure_ai.py +++ b/tests/llm_translation/test_azure_ai.py @@ -14,7 +14,7 @@ from litellm.llms.anthropic.chat import ModelResponseIterator import httpx import json from litellm.llms.custom_httpx.http_handler import HTTPHandler -from base_rerank_unit_tests import BaseLLMRerankTest +# from base_rerank_unit_tests import BaseLLMRerankTest load_dotenv() import io @@ -255,16 +255,17 @@ def test_azure_deepseek_reasoning_content(): assert response.choices[0].message.content == "\n\nThe sky is a canvas of blue" -class TestAzureAIRerank(BaseLLMRerankTest): - def get_custom_llm_provider(self) -> litellm.LlmProviders: - return litellm.LlmProviders.AZURE_AI +# skipping due to cohere rbac issues +# class TestAzureAIRerank(BaseLLMRerankTest): +# def get_custom_llm_provider(self) -> litellm.LlmProviders: +# return litellm.LlmProviders.AZURE_AI - def get_base_rerank_call_args(self) -> dict: - return { - "model": "azure_ai/cohere-rerank-v3-english", - "api_base": os.getenv("AZURE_AI_COHERE_API_BASE"), - "api_key": os.getenv("AZURE_AI_COHERE_API_KEY"), - } +# def get_base_rerank_call_args(self) -> dict: +# return { +# "model": "azure_ai/cohere-rerank-v3-english", +# "api_base": os.getenv("AZURE_AI_COHERE_API_BASE"), +# "api_key": os.getenv("AZURE_AI_COHERE_API_KEY"), +# } @pytest.mark.asyncio @@ -279,7 +280,7 @@ async def test_azure_ai_request_format(): # Set up the test parameters api_key = os.getenv("AZURE_API_KEY") - api_base = f"{os.getenv('AZURE_API_BASE')}/openai/deployments/gpt-4o/chat/completions?api-version=2024-08-01-preview" + api_base = f"{os.getenv('AZURE_API_BASE')}/openai/deployments/gpt-4o-new-test/chat/completions?api-version=2024-08-01-preview" model = "azure_ai/gpt-4o" messages = [ {"role": "user", "content": "hi"}, diff --git a/tests/llm_translation/test_azure_openai.py b/tests/llm_translation/test_azure_openai.py index d289c892a0..72ea3ec27e 100644 --- a/tests/llm_translation/test_azure_openai.py +++ b/tests/llm_translation/test_azure_openai.py @@ -137,7 +137,7 @@ def test_azure_extra_headers(input, call_type, header_value): func = image_generation data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com", "api_version": "2023-07-01-preview", "api_key": "my-azure-api-key", @@ -339,7 +339,7 @@ def test_azure_gpt_4o_with_tool_call_and_response_format(api_version): with patch.object(client.chat.completions.with_raw_response, "create") as mock_post: response = litellm.completion( - model="azure/gpt-4o", + model="azure/gpt-4o-new-test", messages=[ { "role": "system", @@ -474,7 +474,7 @@ def test_azure_max_retries_0( try: completion( - model="azure/gpt-4o", + model="azure/gpt-4o-new-test", messages=[{"role": "user", "content": "Hello world"}], max_retries=max_retries, stream=stream, @@ -502,7 +502,7 @@ async def test_async_azure_max_retries_0( try: await acompletion( - model="azure/gpt-4o", + model="azure/gpt-4o-new-test", messages=[{"role": "user", "content": "Hello world"}], max_retries=max_retries, stream=stream, diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index d6e8ed4ff8..101e72e3db 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -2972,6 +2972,30 @@ def test_bedrock_application_inference_profile(): client = HTTPHandler() client2 = HTTPHandler() + tools = [{ + "type": "function", + "function": { + "name": "get_current_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"], + } + } + } + ] + + with patch.object(client, "post") as mock_post, patch.object( client2, "post" ) as mock_post2: @@ -2981,6 +3005,7 @@ def test_bedrock_application_inference_profile(): messages=[{"role": "user", "content": "Hello, how are you?"}], model_id="arn:aws:bedrock:eu-central-1:000000000000:application-inference-profile/a0a0a0a0a0a0", client=client, + tools=tools ) except Exception as e: print(e) @@ -2990,6 +3015,7 @@ def test_bedrock_application_inference_profile(): model="bedrock/converse/arn:aws:bedrock:eu-central-1:000000000000:application-inference-profile/a0a0a0a0a0a0", messages=[{"role": "user", "content": "Hello, how are you?"}], client=client2, + tools=tools ) except Exception as e: print(e) diff --git a/tests/llm_translation/test_fireworks_ai_translation.py b/tests/llm_translation/test_fireworks_ai_translation.py index 9e78270c92..953405c46e 100644 --- a/tests/llm_translation/test_fireworks_ai_translation.py +++ b/tests/llm_translation/test_fireworks_ai_translation.py @@ -1,6 +1,6 @@ import os import sys - +import json import pytest sys.path.insert( @@ -93,57 +93,6 @@ class TestFireworksAIChatCompletion(BaseLLMChatTest): """ pass - @pytest.mark.parametrize( - "response_format", - [ - {"type": "json_object"}, - {"type": "text"}, - ], - ) - @pytest.mark.flaky(retries=6, delay=1) - def test_json_response_format(self, response_format): - """ - Test that the JSON response format is supported by the LLM API - """ - from litellm.utils import supports_response_schema - from openai import OpenAI - from unittest.mock import patch - - client = OpenAI() - - base_completion_call_args = self.get_base_completion_call_args() - litellm.set_verbose = True - - messages = [ - { - "role": "system", - "content": "Your output should be a JSON object with no additional properties. ", - }, - { - "role": "user", - "content": "Respond with this in json. city=San Francisco, state=CA, weather=sunny, temp=60", - }, - ] - - with patch.object( - client.chat.completions.with_raw_response, "create" - ) as mock_post: - response = self.completion_function( - **base_completion_call_args, - messages=messages, - response_format=response_format, - client=client, - ) - - mock_post.assert_called_once() - if response_format["type"] == "json_object": - assert ( - mock_post.call_args.kwargs["response_format"]["type"] - == "json_object" - ) - else: - assert mock_post.call_args.kwargs["response_format"]["type"] == "text" - class TestFireworksAIAudioTranscription(BaseLLMAudioTranscriptionTest): def get_base_audio_transcription_call_args(self) -> dict: @@ -253,14 +202,15 @@ def test_global_disable_flag_with_transform_messages_helper(monkeypatch): from openai import OpenAI from unittest.mock import patch from litellm import completion + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() monkeypatch.setattr(litellm, "disable_add_transform_inline_image_block", True) - client = OpenAI() - with patch.object( - client.chat.completions.with_raw_response, - "create", + client, + "post", ) as mock_post: try: completion( @@ -286,9 +236,10 @@ def test_global_disable_flag_with_transform_messages_helper(monkeypatch): mock_post.assert_called_once() print(mock_post.call_args.kwargs) + json_data = json.loads(mock_post.call_args.kwargs["data"]) assert ( "#transform=inline" - not in mock_post.call_args.kwargs["messages"][0]["content"][1]["image_url"][ + not in json_data["messages"][0]["content"][1]["image_url"][ "url" ] ) diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py index 9e6105e39a..475c4f03b7 100644 --- a/tests/llm_translation/test_gemini.py +++ b/tests/llm_translation/test_gemini.py @@ -17,6 +17,9 @@ from litellm import completion class TestGoogleAIStudioGemini(BaseLLMChatTest): def get_base_completion_call_args(self) -> dict: return {"model": "gemini/gemini-2.0-flash"} + + def get_base_completion_call_args_with_reasoning_model(self) -> dict: + return {"model": "gemini/gemini-2.5-flash-preview-04-17"} 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""" @@ -85,3 +88,50 @@ def test_gemini_image_generation(): assert response.choices[0].message.content is not None + +def test_gemini_thinking(): + litellm._turn_on_debug() + from litellm.types.utils import Message, CallTypes + from litellm.utils import return_raw_request + import json + + messages = [ + {"role": "user", "content": "Explain the concept of Occam's Razor and provide a simple, everyday example"} + ] + reasoning_content = "I'm thinking about Occam's Razor." + assistant_message = Message(content='Okay, let\'s break down Occam\'s Razor.', reasoning_content=reasoning_content, role='assistant', tool_calls=None, function_call=None, provider_specific_fields=None) + + messages.append(assistant_message) + + raw_request = return_raw_request( + endpoint=CallTypes.completion, + kwargs={ + "model": "gemini/gemini-2.5-flash-preview-04-17", + "messages": messages, + } + ) + assert reasoning_content in json.dumps(raw_request) + response = completion( + model="gemini/gemini-2.5-flash-preview-04-17", + messages=messages, # make sure call works + ) + print(response.choices[0].message) + assert response.choices[0].message.content is not None + + +def test_gemini_thinking_budget_0(): + litellm._turn_on_debug() + from litellm.types.utils import Message, CallTypes + from litellm.utils import return_raw_request + import json + + raw_request = return_raw_request( + endpoint=CallTypes.completion, + kwargs={ + "model": "gemini/gemini-2.5-flash-preview-04-17", + "messages": [{"role": "user", "content": "Explain the concept of Occam's Razor and provide a simple, everyday example"}], + "thinking": {"type": "enabled", "budget_tokens": 0} + } + ) + print(raw_request) + assert "0" in json.dumps(raw_request["raw_request_body"]) \ No newline at end of file diff --git a/tests/llm_translation/test_infinity.py b/tests/llm_translation/test_infinity.py index eb986b8ab5..802789377c 100644 --- a/tests/llm_translation/test_infinity.py +++ b/tests/llm_translation/test_infinity.py @@ -15,7 +15,7 @@ import json import os import sys from datetime import datetime -from unittest.mock import AsyncMock, patch +from unittest.mock import patch, MagicMock, AsyncMock import pytest @@ -25,6 +25,10 @@ sys.path.insert( from test_rerank import assert_response_shape import litellm +from base_embedding_unit_tests import BaseLLMEmbeddingTest +from litellm.llms.custom_httpx.http_handler import HTTPHandler, AsyncHTTPHandler +from litellm.types.utils import EmbeddingResponse, Usage + @pytest.mark.asyncio() async def test_infinity_rerank(): @@ -182,3 +186,189 @@ async def test_infinity_rerank_with_env(monkeypatch): ) # total_tokens - prompt_tokens assert_response_shape(response, custom_llm_provider="infinity") + +#### Embedding Tests +@pytest.mark.asyncio() +async def test_infinity_embedding(): + mock_response = AsyncMock() + + def return_val(): + return { + "data": [{"embedding": [0.1, 0.2, 0.3], "index": 0}], + "usage": {"prompt_tokens": 100, "total_tokens": 150}, + "model": "custom-model/embedding-v1", + "object": "list" + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + expected_payload = { + "model": "custom-model/embedding-v1", + "input": ["hello world"], + "encoding_format": "float", + "output_dimension": 512 + } + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=mock_response, + ) as mock_post: + response = await litellm.aembedding( + model="infinity/custom-model/embedding-v1", + input=["hello world"], + dimensions=512, + encoding_format="float", + api_base="https://api.infinity.ai/embeddings", + + ) + + # Assert + mock_post.assert_called_once() + print("call args", mock_post.call_args) + args_to_api = mock_post.call_args.kwargs["data"] + _url = mock_post.call_args.kwargs["url"] + assert _url == "https://api.infinity.ai/embeddings" + + request_data = json.loads(args_to_api) + assert request_data["input"] == expected_payload["input"] + assert request_data["model"] == expected_payload["model"] + assert request_data["output_dimension"] == expected_payload["output_dimension"] + assert request_data["encoding_format"] == expected_payload["encoding_format"] + + assert response.data is not None + assert response.usage.prompt_tokens == 100 + assert response.usage.total_tokens == 150 + assert response.model == "custom-model/embedding-v1" + assert response.object == "list" + + +@pytest.mark.asyncio() +async def test_infinity_embedding_with_env(monkeypatch): + # Set up mock response + mock_response = AsyncMock() + + def return_val(): + return { + "data": [{"embedding": [0.1, 0.2, 0.3], "index": 0}], + "usage": {"prompt_tokens": 100, "total_tokens": 150}, + "model": "custom-model/embedding-v1", + "object": "list" + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + expected_payload = { + "model": "custom-model/embedding-v1", + "input": ["hello world"], + "encoding_format": "float", + "output_dimension": 512 + } + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=mock_response, + ) as mock_post: + response = await litellm.aembedding( + model="infinity/custom-model/embedding-v1", + input=["hello world"], + dimensions=512, + encoding_format="float", + api_base="https://api.infinity.ai/embeddings", + ) + + # Assert + mock_post.assert_called_once() + print("call args", mock_post.call_args) + args_to_api = mock_post.call_args.kwargs["data"] + _url = mock_post.call_args.kwargs["url"] + assert _url == "https://api.infinity.ai/embeddings" + + request_data = json.loads(args_to_api) + assert request_data["input"] == expected_payload["input"] + assert request_data["model"] == expected_payload["model"] + assert request_data["output_dimension"] == expected_payload["output_dimension"] + assert request_data["encoding_format"] == expected_payload["encoding_format"] + + assert response.data is not None + assert response.usage.prompt_tokens == 100 + assert response.usage.total_tokens == 150 + assert response.model == "custom-model/embedding-v1" + assert response.object == "list" + + +@pytest.mark.asyncio() +async def test_infinity_embedding_extra_params(): + mock_response = AsyncMock() + + def return_val(): + return { + "data": [{"embedding": [0.1, 0.2, 0.3], "index": 0}], + "usage": {"prompt_tokens": 100, "total_tokens": 150}, + "model": "custom-model/embedding-v1", + "object": "list" + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=mock_response, + ) as mock_post: + response = await litellm.aembedding( + model="infinity/custom-model/embedding-v1", + input=["test input"], + dimensions=512, + encoding_format="float", + modality="text", + api_base="https://api.infinity.ai/embeddings", + ) + + mock_post.assert_called_once() + json_data = json.loads(mock_post.call_args.kwargs["data"]) + + # Assert the request parameters + assert json_data["input"] == ["test input"] + assert json_data["model"] == "custom-model/embedding-v1" + assert json_data["output_dimension"] == 512 + assert json_data["encoding_format"] == "float" + assert json_data["modality"] == "text" + + +@pytest.mark.asyncio() +async def test_infinity_embedding_prompt_token_mapping(): + mock_response = AsyncMock() + + def return_val(): + return { + "data": [{"embedding": [0.1, 0.2, 0.3], "index": 0}], + "usage": {"total_tokens": 1, "prompt_tokens": 1}, + "model": "custom-model/embedding-v1", + "object": "list" + } + + mock_response.json = return_val + mock_response.headers = {"key": "value"} + mock_response.status_code = 200 + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + return_value=mock_response, + ) as mock_post: + response = await litellm.aembedding( + model="infinity/custom-model/embedding-v1", + input=["a"], + dimensions=512, + encoding_format="float", + api_base="https://api.infinity.ai/embeddings", + ) + + mock_post.assert_called_once() + # Assert the response + assert response.usage.prompt_tokens == 1 + assert response.usage.total_tokens == 1 diff --git a/tests/llm_translation/test_openai.py b/tests/llm_translation/test_openai.py index 295bdb46f1..a470b53589 100644 --- a/tests/llm_translation/test_openai.py +++ b/tests/llm_translation/test_openai.py @@ -470,3 +470,4 @@ class TestOpenAIGPT4OAudioTranscription(BaseLLMAudioTranscriptionTest): def get_custom_llm_provider(self) -> litellm.LlmProviders: return litellm.LlmProviders.OPENAI + diff --git a/tests/llm_translation/test_optional_params.py b/tests/llm_translation/test_optional_params.py index 4545ebbe20..e207e367e4 100644 --- a/tests/llm_translation/test_optional_params.py +++ b/tests/llm_translation/test_optional_params.py @@ -217,7 +217,7 @@ def test_openai_optional_params_embeddings(): def test_azure_optional_params_embeddings(): litellm.drop_params = True optional_params = get_optional_params_embeddings( - model="chatgpt-v-2", + model="chatgpt-v-3", user="John", encoding_format=None, custom_llm_provider="azure", @@ -396,7 +396,7 @@ def test_azure_tool_choice(api_version): """ litellm.drop_params = True optional_params = litellm.utils.get_optional_params( - model="chatgpt-v-2", + model="chatgpt-v-3", user="John", custom_llm_provider="azure", max_tokens=10, diff --git a/tests/llm_translation/test_rerank.py b/tests/llm_translation/test_rerank.py index 5de6c1a8ec..e9d9e38951 100644 --- a/tests/llm_translation/test_rerank.py +++ b/tests/llm_translation/test_rerank.py @@ -150,6 +150,7 @@ async def test_basic_rerank_together_ai(sync_mode): @pytest.mark.asyncio() @pytest.mark.parametrize("sync_mode", [True, False]) +@pytest.mark.skip(reason="Skipping test due to Cohere RBAC issues") async def test_basic_rerank_azure_ai(sync_mode): import os diff --git a/tests/llm_translation/test_triton.py b/tests/llm_translation/test_triton.py index 7e4ba92f23..8a3bbb4661 100644 --- a/tests/llm_translation/test_triton.py +++ b/tests/llm_translation/test_triton.py @@ -20,6 +20,7 @@ from litellm.llms.triton.embedding.transformation import TritonEmbeddingConfig import litellm + def test_split_embedding_by_shape_passes(): try: data = [ @@ -230,3 +231,23 @@ async def test_triton_embeddings(): assert response.data[0]["embedding"] == [0.1, 0.2] except Exception as e: pytest.fail(f"Error occurred: {e}") + + + +def test_triton_generate_raw_request(): + from litellm.utils import return_raw_request + from litellm.types.utils import CallTypes + try: + kwargs = { + "model": "triton/llama-3-8b-instruct", + "messages": [{"role": "user", "content": "who are u?"}], + "api_base": "http://localhost:8000/generate", + } + raw_request = return_raw_request(endpoint=CallTypes.completion, kwargs=kwargs) + print("raw_request", raw_request) + assert raw_request is not None + assert "bad_words" not in json.dumps(raw_request["raw_request_body"]) + assert "stop_words" not in json.dumps(raw_request["raw_request_body"]) + except Exception as e: + pytest.fail(f"Error occurred: {e}") + diff --git a/tests/load_tests/test_datadog_load_test.py b/tests/load_tests/test_datadog_load_test.py index b56c82288e..f4328b71b1 100644 --- a/tests/load_tests/test_datadog_load_test.py +++ b/tests/load_tests/test_datadog_load_test.py @@ -91,7 +91,7 @@ async def make_async_calls(metadata=None, **completion_kwargs): def create_async_task(**completion_kwargs): litellm.set_verbose = True completion_args = { - "model": "openai/chatgpt-v-2", + "model": "openai/chatgpt-v-3", "api_version": "2024-02-01", "messages": [{"role": "user", "content": "This is a test"}], "max_tokens": 5, diff --git a/tests/load_tests/test_otel_load_test.py b/tests/load_tests/test_otel_load_test.py index 50e5748686..f5754c0c40 100644 --- a/tests/load_tests/test_otel_load_test.py +++ b/tests/load_tests/test_otel_load_test.py @@ -86,7 +86,7 @@ def create_async_task(**completion_kwargs): By default a standard set of arguments are used for the litellm.acompletion function. """ completion_args = { - "model": "openai/chatgpt-v-2", + "model": "openai/chatgpt-v-3", "api_version": "2024-02-01", "messages": [{"role": "user", "content": "This is a test" * 100}], "max_tokens": 5, diff --git a/tests/local_testing/example_config_yaml/azure_config.yaml b/tests/local_testing/example_config_yaml/azure_config.yaml index fd5865cd7c..111813c884 100644 --- a/tests/local_testing/example_config_yaml/azure_config.yaml +++ b/tests/local_testing/example_config_yaml/azure_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: gpt-4-team1 litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: "2023-05-15" api_key: os.environ/AZURE_API_KEY diff --git a/tests/local_testing/test_acooldowns_router.py b/tests/local_testing/test_acooldowns_router.py index df3f493a68..8427fd2be8 100644 --- a/tests/local_testing/test_acooldowns_router.py +++ b/tests/local_testing/test_acooldowns_router.py @@ -26,7 +26,7 @@ model_list = [ { # list of model deployments "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -143,7 +143,7 @@ async def test_cooldown_same_model_name(sync_mode): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -153,7 +153,7 @@ async def test_cooldown_same_model_name(sync_mode): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -184,7 +184,7 @@ async def test_cooldown_same_model_name(sync_mode): model_ids.append(model["model_info"]["id"]) print("\n litellm model ids ", model_ids) - # example litellm_model_names ['azure/chatgpt-v-2-ModelID-64321', 'azure/chatgpt-v-2-ModelID-63960'] + # example litellm_model_names ['azure/chatgpt-v-3-ModelID-64321', 'azure/chatgpt-v-3-ModelID-63960'] assert ( model_ids[0] != model_ids[1] ) # ensure both models have a uuid added, and they have different names @@ -201,7 +201,7 @@ async def test_cooldown_same_model_name(sync_mode): model_ids.append(model["model_info"]["id"]) print("\n litellm model ids ", model_ids) - # example litellm_model_names ['azure/chatgpt-v-2-ModelID-64321', 'azure/chatgpt-v-2-ModelID-63960'] + # example litellm_model_names ['azure/chatgpt-v-3-ModelID-64321', 'azure/chatgpt-v-3-ModelID-63960'] assert ( model_ids[0] != model_ids[1] ) # ensure both models have a uuid added, and they have different names diff --git a/tests/local_testing/test_alangfuse.py b/tests/local_testing/test_alangfuse.py index cdcf18f79f..2eea426478 100644 --- a/tests/local_testing/test_alangfuse.py +++ b/tests/local_testing/test_alangfuse.py @@ -194,7 +194,7 @@ def create_async_task(**completion_kwargs): By default a standard set of arguments are used for the litellm.acompletion function. """ completion_args = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_version": "2024-02-01", "messages": [{"role": "user", "content": "This is a test"}], "max_tokens": 5, diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index ec9b676772..0aa552069c 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -147,7 +147,7 @@ async def test_get_response(): prompt = '\ndef count_nums(arr):\n """\n Write a function count_nums which takes an array of integers and returns\n the number of elements which has a sum of digits > 0.\n If a number is negative, then its first signed digit will be negative:\n e.g. -123 has signed digits -1, 2, and 3.\n >>> count_nums([]) == 0\n >>> count_nums([-1, 11, -11]) == 1\n >>> count_nums([1, 1, 2]) == 3\n """\n' try: response = await acompletion( - model="gemini-pro", + model="gemini-1.5-flash", messages=[ { "role": "system", @@ -454,6 +454,8 @@ async def test_async_vertexai_response(): or "002" in model or "gemini-2.0-flash-thinking-exp" in model or "gemini-2.0-pro-exp-02-05" in model + or "gemini-pro" in model + or "gemini-1.0-pro" in model ): # our account does not have access to this model continue @@ -464,6 +466,8 @@ async def test_async_vertexai_response(): model=model, messages=messages, temperature=0.7, timeout=5 ) print(f"response: {response}") + except litellm.NotFoundError as e: + pass except litellm.RateLimitError as e: pass except litellm.Timeout as e: @@ -501,6 +505,8 @@ async def test_async_vertexai_streaming_response(): or "002" in model or "gemini-2.0-flash-thinking-exp" in model or "gemini-2.0-pro-exp-02-05" in model + or "gemini-pro" in model + or "gemini-1.0-pro" in model ): # our account does not have access to this model continue @@ -522,6 +528,8 @@ async def test_async_vertexai_streaming_response(): complete_response += chunk.choices[0].delta.content print(f"complete_response: {complete_response}") assert len(complete_response) > 0 + except litellm.NotFoundError as e: + pass except litellm.RateLimitError as e: pass except litellm.APIConnectionError: @@ -1776,7 +1784,7 @@ async def test_gemini_pro_function_calling_streaming(sync_mode): load_vertex_ai_credentials() litellm.set_verbose = True data = { - "model": "vertex_ai/gemini-pro", + "model": "vertex_ai/gemini-1.5-flash", "messages": [ { "role": "user", @@ -1836,57 +1844,6 @@ async def test_gemini_pro_function_calling_streaming(sync_mode): pass -@pytest.mark.asyncio -@pytest.mark.flaky(retries=3, delay=1) -async def test_gemini_pro_async_function_calling(): - load_vertex_ai_credentials() - litellm.set_verbose = True - try: - tools = [ - { - "type": "function", - "function": { - "name": "get_current_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"], - }, - }, - } - ] - messages = [ - { - "role": "user", - "content": "What's the weather like in Boston today in fahrenheit?", - } - ] - completion = await litellm.acompletion( - model="gemini-pro", messages=messages, tools=tools, tool_choice="auto" - ) - print(f"completion: {completion}") - print(f"message content: {completion.choices[0].message.content}") - assert completion.choices[0].message.content is None - assert len(completion.choices[0].message.tool_calls) == 1 - - # except litellm.APIError as e: - # pass - except litellm.RateLimitError as e: - pass - except Exception as e: - pytest.fail(f"An exception occurred - {str(e)}") - # raise Exception("it worked!") - # asyncio.run(gemini_pro_async_function_calling()) diff --git a/tests/local_testing/test_assistants.py b/tests/local_testing/test_assistants.py index 544523e4a0..c626e5b685 100644 --- a/tests/local_testing/test_assistants.py +++ b/tests/local_testing/test_assistants.py @@ -37,6 +37,12 @@ V0 Scope: - Run Thread -> `/v1/threads/{thread_id}/run` """ +def _add_azure_related_dynamic_params(data: dict) -> dict: + data["api_version"] = "2024-02-15-preview" + data["api_base"] = os.getenv("AZURE_ASSISTANTS_API_BASE") + data["api_key"] = os.getenv("AZURE_ASSISTANTS_API_KEY") + return data + @pytest.mark.parametrize("provider", ["openai", "azure"]) @pytest.mark.parametrize( @@ -49,7 +55,7 @@ async def test_get_assistants(provider, sync_mode): "custom_llm_provider": provider, } if provider == "azure": - data["api_version"] = "2024-02-15-preview" + data = _add_azure_related_dynamic_params(data) if sync_mode == True: assistants = litellm.get_assistants(**data) @@ -68,19 +74,19 @@ async def test_get_assistants(provider, sync_mode): @pytest.mark.flaky(retries=3, delay=1) async def test_create_delete_assistants(provider, sync_mode): litellm.ssl_verify = False - model = "gpt-4-turbo" + litellm._turn_on_debug() + data = { + "custom_llm_provider": provider, + "model": "gpt-4.5-preview", + "instructions": "You are a personal math tutor. When asked a question, write and run Python code to answer the question.", + "name": "Math Tutor", + "tools": [{"type": "code_interpreter"}], + } if provider == "azure": - os.environ["AZURE_API_VERSION"] = "2024-05-01-preview" - model = "chatgpt-v-2" + data = _add_azure_related_dynamic_params(data) if sync_mode == True: - assistant = litellm.create_assistants( - custom_llm_provider=provider, - model=model, - instructions="You are a personal math tutor. When asked a question, write and run Python code to answer the question.", - name="Math Tutor", - tools=[{"type": "code_interpreter"}], - ) + assistant = litellm.create_assistants(**data) print("New assistants", assistant) assert isinstance(assistant, Assistant) @@ -91,19 +97,17 @@ async def test_create_delete_assistants(provider, sync_mode): assert assistant.id is not None # delete the created assistant - response = litellm.delete_assistant( - custom_llm_provider=provider, assistant_id=assistant.id - ) + delete_data = { + "custom_llm_provider": provider, + "assistant_id": assistant.id, + } + if provider == "azure": + delete_data = _add_azure_related_dynamic_params(delete_data) + response = litellm.delete_assistant(**delete_data) print("Response deleting assistant", response) assert response.id == assistant.id else: - assistant = await litellm.acreate_assistants( - custom_llm_provider=provider, - model=model, - instructions="You are a personal math tutor. When asked a question, write and run Python code to answer the question.", - name="Math Tutor", - tools=[{"type": "code_interpreter"}], - ) + assistant = await litellm.acreate_assistants(**data) print("New assistants", assistant) assert isinstance(assistant, Assistant) assert ( @@ -112,9 +116,14 @@ async def test_create_delete_assistants(provider, sync_mode): ) assert assistant.id is not None - response = await litellm.adelete_assistant( - custom_llm_provider=provider, assistant_id=assistant.id - ) + # delete the created assistant + delete_data = { + "custom_llm_provider": provider, + "assistant_id": assistant.id, + } + if provider == "azure": + delete_data = _add_azure_related_dynamic_params(delete_data) + response = await litellm.adelete_assistant(**delete_data) print("Response deleting assistant", response) assert response.id == assistant.id @@ -129,7 +138,7 @@ async def test_create_thread_litellm(sync_mode, provider) -> Thread: "message": [message], } if provider == "azure": - data["api_version"] = "2024-02-15-preview" + data = _add_azure_related_dynamic_params(data) if sync_mode: new_thread = create_thread(**data) @@ -159,7 +168,7 @@ async def test_get_thread_litellm(provider, sync_mode): "thread_id": _new_thread.id, } if provider == "azure": - data["api_version"] = "2024-02-15-preview" + data = _add_azure_related_dynamic_params(data) if sync_mode: received_thread = get_thread(**data) @@ -188,7 +197,7 @@ async def test_add_message_litellm(sync_mode, provider): data = {"custom_llm_provider": provider, "thread_id": _new_thread.id, **message} if provider == "azure": - data["api_version"] = "2024-02-15-preview" + data = _add_azure_related_dynamic_params(data) if sync_mode: added_message = litellm.add_message(**data) else: @@ -227,11 +236,18 @@ async def test_aarun_thread_litellm(sync_mode, provider, is_streaming): """ import openai + + try: + get_assistants_data = { + "custom_llm_provider": provider, + } + if provider == "azure": + get_assistants_data = _add_azure_related_dynamic_params(get_assistants_data) if sync_mode: - assistants = litellm.get_assistants(custom_llm_provider=provider) + assistants = litellm.get_assistants(**get_assistants_data) else: - assistants = await litellm.aget_assistants(custom_llm_provider=provider) + assistants = await litellm.aget_assistants(**get_assistants_data) ## get the first assistant ### try: @@ -252,6 +268,8 @@ async def test_aarun_thread_litellm(sync_mode, provider, is_streaming): message: MessageData = {"role": "user", "content": "Hey, how's it going?"} # type: ignore data = {"custom_llm_provider": provider, "thread_id": _new_thread.id, **message} + if provider == "azure": + data = _add_azure_related_dynamic_params(data) if sync_mode: added_message = litellm.add_message(**data) diff --git a/tests/local_testing/test_azure_openai.py b/tests/local_testing/test_azure_openai.py index 4ca2a20011..8e2d2aa509 100644 --- a/tests/local_testing/test_azure_openai.py +++ b/tests/local_testing/test_azure_openai.py @@ -46,7 +46,7 @@ async def test_aaaaazure_tenant_id_auth(respx_mock: MockRouter): { "model_name": "gpt-3.5-turbo", "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_base": os.getenv("AZURE_API_BASE"), "tenant_id": os.getenv("AZURE_TENANT_ID"), "client_id": os.getenv("AZURE_CLIENT_ID"), @@ -95,6 +95,6 @@ async def test_aaaaazure_tenant_id_auth(respx_mock: MockRouter): assert json_body == { "messages": [{"role": "user", "content": "Hello world!"}], - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "stream": False, } diff --git a/tests/local_testing/test_azure_perf.py b/tests/local_testing/test_azure_perf.py index b7d7abd553..bc6d694b78 100644 --- a/tests/local_testing/test_azure_perf.py +++ b/tests/local_testing/test_azure_perf.py @@ -18,7 +18,7 @@ # { # "model_name": "azure-test", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "api_version": os.getenv("AZURE_API_VERSION"), @@ -33,7 +33,7 @@ # try: # start_time = time.time() # response = await client.chat.completions.create( -# model="chatgpt-v-2", +# model="chatgpt-v-3", # messages=[{"role": "user", "content": f"This is a test: {uuid.uuid4()}"}], # stream=True, # ) diff --git a/tests/local_testing/test_caching.py b/tests/local_testing/test_caching.py index 43dafd7293..8c12f3fd9b 100644 --- a/tests/local_testing/test_caching.py +++ b/tests/local_testing/test_caching.py @@ -324,7 +324,7 @@ def test_caching_with_models_v2(): litellm.set_verbose = True response1 = completion(model="gpt-3.5-turbo", messages=messages, caching=True) response2 = completion(model="gpt-3.5-turbo", messages=messages, caching=True) - response3 = completion(model="azure/chatgpt-v-2", messages=messages, caching=True) + response3 = completion(model="azure/chatgpt-v-3", messages=messages, caching=True) print(f"response1: {response1}") print(f"response2: {response2}") print(f"response3: {response3}") @@ -1170,7 +1170,7 @@ async def test_s3_cache_stream_azure(sync_mode): if sync_mode: response1 = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -1183,7 +1183,7 @@ async def test_s3_cache_stream_azure(sync_mode): print(response_1_content) else: response1 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -1203,7 +1203,7 @@ async def test_s3_cache_stream_azure(sync_mode): if sync_mode: response2 = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -1216,7 +1216,7 @@ async def test_s3_cache_stream_azure(sync_mode): print(response_2_content) else: response2 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -1279,7 +1279,7 @@ async def test_s3_cache_acompletion_azure(): print("s3 Cache: test for caching, streaming + completion") response1 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -1289,7 +1289,7 @@ async def test_s3_cache_acompletion_azure(): time.sleep(2) response2 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=40, temperature=1, @@ -2608,3 +2608,64 @@ def test_caching_with_reasoning_content(): print(f"response 2: {response_2.model_dump_json(indent=4)}") assert response_2._hidden_params["cache_hit"] == True assert response_2.choices[0].message.reasoning_content is not None + + +def test_caching_reasoning_args_miss(): # test in memory cache + try: + #litellm._turn_on_debug() + litellm.set_verbose = True + litellm.cache = Cache( + ) + response1 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, reasoning_effort="low", mock_response="My response") + response2 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, mock_response="My response") + print(f"response1: {response1}") + print(f"response2: {response2}") + assert response1.id != response2.id + except Exception as e: + print(f"error occurred: {traceback.format_exc()}") + pytest.fail(f"Error occurred: {e}") + +def test_caching_reasoning_args_hit(): # test in memory cache + try: + #litellm._turn_on_debug() + litellm.set_verbose = True + litellm.cache = Cache( + ) + response1 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, reasoning_effort="low", mock_response="My response") + response2 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, reasoning_effort="low", mock_response="My response") + print(f"response1: {response1}") + print(f"response2: {response2}") + assert response1.id == response2.id + except Exception as e: + print(f"error occurred: {traceback.format_exc()}") + pytest.fail(f"Error occurred: {e}") + +def test_caching_thinking_args_miss(): # test in memory cache + try: + #litellm._turn_on_debug() + litellm.set_verbose = True + litellm.cache = Cache( + ) + response1 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, thinking={"type": "enabled", "budget_tokens": 1024}, mock_response="My response") + response2 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, mock_response="My response") + print(f"response1: {response1}") + print(f"response2: {response2}") + assert response1.id != response2.id + except Exception as e: + print(f"error occurred: {traceback.format_exc()}") + pytest.fail(f"Error occurred: {e}") + +def test_caching_thinking_args_hit(): # test in memory cache + try: + #litellm._turn_on_debug() + litellm.set_verbose = True + litellm.cache = Cache( + ) + response1 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, thinking={"type": "enabled", "budget_tokens": 1024}, mock_response="My response" ) + response2 = completion(model="claude-3-7-sonnet-latest", messages=messages, caching=True, thinking={"type": "enabled", "budget_tokens": 1024}, mock_response="My response") + print(f"response1: {response1}") + print(f"response2: {response2}") + assert response1.id == response2.id + except Exception as e: + print(f"error occurred: {traceback.format_exc()}") + pytest.fail(f"Error occurred: {e}") \ No newline at end of file diff --git a/tests/local_testing/test_caching_ssl.py b/tests/local_testing/test_caching_ssl.py index 1b642f7674..8194115ef1 100644 --- a/tests/local_testing/test_caching_ssl.py +++ b/tests/local_testing/test_caching_ssl.py @@ -58,7 +58,7 @@ def test_caching_router(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_class.py b/tests/local_testing/test_class.py index a15f362372..e6b711efe8 100644 --- a/tests/local_testing/test_class.py +++ b/tests/local_testing/test_class.py @@ -55,7 +55,7 @@ # # { # # "model_name": "gpt-3.5-turbo", # openai model name # # "litellm_params": { # params for litellm completion/embedding call -# # "model": "azure/chatgpt-v-2", +# # "model": "azure/chatgpt-v-3", # # "api_key": os.getenv("AZURE_API_KEY"), # # "api_version": os.getenv("AZURE_API_VERSION"), # # "api_base": os.getenv("AZURE_API_BASE"), @@ -93,7 +93,7 @@ # # { # # "model_name": "gpt-3.5-turbo", # openai model name # # "litellm_params": { # params for litellm completion/embedding call -# # "model": "azure/chatgpt-v-2", +# # "model": "azure/chatgpt-v-3", # # "api_key": os.getenv("AZURE_API_KEY"), # # "api_version": os.getenv("AZURE_API_VERSION"), # # "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_completion.py b/tests/local_testing/test_completion.py index d56475e1a0..064fe7f736 100644 --- a/tests/local_testing/test_completion.py +++ b/tests/local_testing/test_completion.py @@ -131,15 +131,15 @@ def test_null_role_response(): assert response.choices[0].message.role == "assistant" - +@pytest.mark.skip(reason="Cohere having RBAC issues") def test_completion_azure_command_r(): try: - litellm.set_verbose = True + litellm._turn_on_debug() response = completion( model="azure/command-r-plus", - api_base=os.getenv("AZURE_COHERE_API_BASE"), - api_key=os.getenv("AZURE_COHERE_API_KEY"), + api_base="https://Cohere-command-r-plus-gylpd-serverless.eastus2.inference.ai.azure.com", + api_key="AO89xyvmOLLMgoMI7WaiEaP0t6M09itr", messages=[{"role": "user", "content": "What is the meaning of life?"}], ) @@ -732,7 +732,7 @@ def encode_image(image_path): "model", [ "gpt-4o", - "azure/gpt-4o", + "azure/gpt-4o-new-test", "anthropic/claude-3-opus-20240229", ], ) # @@ -1332,7 +1332,7 @@ def test_completion_fireworks_ai(): }, ] response = completion( - model="fireworks_ai/mixtral-8x7b-instruct", + model="fireworks_ai/llama4-maverick-instruct-basic", messages=messages, ) print(response) @@ -1824,9 +1824,9 @@ def test_completion_openai(): "model, api_version", [ # ("gpt-4o-2024-08-06", None), - # ("azure/chatgpt-v-2", None), + # ("azure/chatgpt-v-3", None), ("bedrock/anthropic.claude-3-sonnet-20240229-v1:0", None), - # ("azure/gpt-4o", "2024-08-01-preview"), + # ("azure/gpt-4o-new-test", "2024-08-01-preview"), ], ) @pytest.mark.flaky(retries=3, delay=1) @@ -2495,7 +2495,7 @@ def test_completion_azure_extra_headers(): litellm.client_session = http_client try: response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, api_base=os.getenv("AZURE_API_BASE"), api_version="2023-07-01-preview", @@ -2544,7 +2544,7 @@ def test_completion_azure_ad_token(): litellm.client_session = http_client try: response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, azure_ad_token="my-special-token", ) @@ -2575,7 +2575,7 @@ def test_completion_azure_key_completion_arg(): litellm.set_verbose = True ## Test azure call response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, api_key=old_key, logprobs=True, @@ -2633,7 +2633,7 @@ async def test_re_use_azure_async_client(): ## Test azure call for _ in range(3): response = await litellm.acompletion( - model="azure/chatgpt-v-2", messages=messages, client=client + model="azure/chatgpt-v-3", messages=messages, client=client ) print(f"response: {response}") except Exception as e: @@ -2661,11 +2661,11 @@ def test_re_use_openaiClient(): def test_completion_azure(): try: - print("azure gpt-3.5 test\n\n") + print("azure chatgpt-v-3 test\n\n") litellm.set_verbose = False ## Test azure call response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, api_key="os.environ/AZURE_API_KEY", ) @@ -2673,7 +2673,7 @@ def test_completion_azure(): print(f"response hidden params: {response._hidden_params}") ## Test azure flag for backwards-compat # response = completion( - # model="chatgpt-v-2", + # model="chatgpt-v-3", # messages=messages, # azure=True, # max_tokens=10 @@ -2712,7 +2712,7 @@ def test_azure_openai_ad_token(): litellm.input_callback = [tester] try: response = litellm.completion( - model="azure/chatgpt-v-2", # e.g. gpt-35-instant + model="azure/chatgpt-v-3", # e.g. gpt-35-instant messages=[ { "role": "user", @@ -2750,7 +2750,7 @@ def test_completion_azure2(): ## Test azure call response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, api_base=api_base, api_key=api_key, @@ -2787,7 +2787,7 @@ def test_completion_azure3(): ## Test azure call response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, max_tokens=10, ) @@ -2835,7 +2835,7 @@ def test_completion_azure_with_litellm_key(): openai.api_key = "ymca" response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, ) # Add any assertions here to check the response @@ -2863,7 +2863,7 @@ def test_completion_azure_deployment_id(): try: litellm.set_verbose = True response = completion( - deployment_id="chatgpt-v-2", + deployment_id="chatgpt-v-3", model="gpt-3.5-turbo", messages=messages, ) @@ -3925,7 +3925,7 @@ def test_completion_stream_watsonx(): @pytest.mark.parametrize( "provider, model, project, region_name, token", [ - ("azure", "chatgpt-v-2", None, None, "test-token"), + ("azure", "chatgpt-v-3", None, None, "test-token"), ("vertex_ai", "anthropic-claude-3", "adroit-crow-1", "us-east1", None), ("watsonx", "ibm/granite", "96946574", "dallas", "1234"), ("bedrock", "anthropic.claude-3", None, "us-east-1", None), @@ -4178,7 +4178,7 @@ async def test_completion_ai21_chat(): @pytest.mark.parametrize( "model", - ["gpt-4o", "azure/chatgpt-v-2"], + ["gpt-4o", "azure/chatgpt-v-3"], ) @pytest.mark.parametrize( "stream", @@ -4200,7 +4200,7 @@ def test_completion_response_ratelimit_headers(model, stream): assert "x-ratelimit-remaining-requests" in additional_headers assert "x-ratelimit-remaining-tokens" in additional_headers - if model == "azure/chatgpt-v-2": + if model == "azure/chatgpt-v-3": # Azure OpenAI header assert "llm_provider-azureml-model-session" in additional_headers if model == "claude-3-sonnet-20240229": diff --git a/tests/local_testing/test_completion_cost.py b/tests/local_testing/test_completion_cost.py index 3e30041489..39a77d1ade 100644 --- a/tests/local_testing/test_completion_cost.py +++ b/tests/local_testing/test_completion_cost.py @@ -1284,7 +1284,7 @@ from litellm.llms.fireworks_ai.cost_calculator import get_base_model_for_pricing "model, base_model", [ ("fireworks_ai/llama-v3p1-405b-instruct", "fireworks-ai-default"), - ("fireworks_ai/mixtral-8x7b-instruct", "fireworks-ai-moe-up-to-56b"), + ("fireworks_ai/llama4-maverick-instruct-basic", "fireworks-ai-default"), ], ) def test_get_model_params_fireworks_ai(model, base_model): @@ -1294,7 +1294,7 @@ def test_get_model_params_fireworks_ai(model, base_model): @pytest.mark.parametrize( "model", - ["fireworks_ai/llama-v3p1-405b-instruct", "fireworks_ai/mixtral-8x7b-instruct"], + ["fireworks_ai/llama-v3p1-405b-instruct", "fireworks_ai/llama4-maverick-instruct-basic"], ) def test_completion_cost_fireworks_ai(model): os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" diff --git a/tests/local_testing/test_config.py b/tests/local_testing/test_config.py index ab8365b2d1..b56be32881 100644 --- a/tests/local_testing/test_config.py +++ b/tests/local_testing/test_config.py @@ -46,7 +46,7 @@ async def test_delete_deployment(): import base64 litellm_params = LiteLLM_Params( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", api_key=os.getenv("AZURE_API_KEY"), api_base=os.getenv("AZURE_API_BASE"), api_version=os.getenv("AZURE_API_VERSION"), @@ -232,7 +232,7 @@ async def test_db_error_new_model_check(): litellm_params = LiteLLM_Params( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", api_key=os.getenv("AZURE_API_KEY"), api_base=os.getenv("AZURE_API_BASE"), api_version=os.getenv("AZURE_API_VERSION"), @@ -250,7 +250,7 @@ def _create_model_list(flag_value: Literal[0, 1], master_key: str): import base64 new_litellm_params = LiteLLM_Params( - model="azure/chatgpt-v-2-3", + model="azure/chatgpt-v-3-3", api_key=os.getenv("AZURE_API_KEY"), api_base=os.getenv("AZURE_API_BASE"), api_version=os.getenv("AZURE_API_VERSION"), diff --git a/tests/local_testing/test_configs/test_bad_config.yaml b/tests/local_testing/test_configs/test_bad_config.yaml index 7c802a8408..0a16ecb3c5 100644 --- a/tests/local_testing/test_configs/test_bad_config.yaml +++ b/tests/local_testing/test_configs/test_bad_config.yaml @@ -5,12 +5,12 @@ model_list: model: gpt-3.5-turbo - model_name: working-azure-gpt-3.5-turbo litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: os.environ/AZURE_API_KEY - model_name: azure-gpt-3.5-turbo litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: bad-key - model_name: azure-embedding diff --git a/tests/local_testing/test_configs/test_cloudflare_azure_with_cache_config.yaml b/tests/local_testing/test_configs/test_cloudflare_azure_with_cache_config.yaml index c3c3cb1c32..aeadbeb872 100644 --- a/tests/local_testing/test_configs/test_cloudflare_azure_with_cache_config.yaml +++ b/tests/local_testing/test_configs/test_cloudflare_azure_with_cache_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: azure-cloudflare litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://gateway.ai.cloudflare.com/v1/0399b10e77ac6668c80404a5ff49eb37/litellm-test/azure-openai/openai-gpt-4-test-v-1 api_key: os.environ/AZURE_API_KEY api_version: 2023-07-01-preview diff --git a/tests/local_testing/test_configs/test_config_no_auth.yaml b/tests/local_testing/test_configs/test_config_no_auth.yaml index 1c5ddf2266..075bf7a09d 100644 --- a/tests/local_testing/test_configs/test_config_no_auth.yaml +++ b/tests/local_testing/test_configs/test_config_no_auth.yaml @@ -12,7 +12,7 @@ model_list: - litellm_params: api_base: https://gateway.ai.cloudflare.com/v1/0399b10e77ac6668c80404a5ff49eb37/litellm-test/azure-openai/openai-gpt-4-test-v-1 api_key: os.environ/AZURE_API_KEY - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 model_name: azure-cloudflare-model - litellm_params: api_base: https://openai-france-1234.openai.azure.com diff --git a/tests/local_testing/test_configs/test_custom_logger.yaml b/tests/local_testing/test_configs/test_custom_logger.yaml index 145c618edd..2ad500b36f 100644 --- a/tests/local_testing/test_configs/test_custom_logger.yaml +++ b/tests/local_testing/test_configs/test_custom_logger.yaml @@ -1,7 +1,7 @@ model_list: - model_name: Azure OpenAI GPT-4 Canada litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: os.environ/AZURE_API_KEY api_version: "2023-07-01-preview" diff --git a/tests/local_testing/test_custom_callback_input.py b/tests/local_testing/test_custom_callback_input.py index 222572935b..055ed821d0 100644 --- a/tests/local_testing/test_custom_callback_input.py +++ b/tests/local_testing/test_custom_callback_input.py @@ -450,12 +450,12 @@ def test_chat_azure_stream(): customHandler = CompletionCustomHandler() litellm.callbacks = [customHandler] response = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm sync azure"}], ) # test streaming response = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm sync azure"}], stream=True, ) @@ -464,7 +464,7 @@ def test_chat_azure_stream(): # test failure callback try: response = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm sync azure"}], api_key="my-bad-key", stream=True, @@ -491,12 +491,12 @@ async def test_async_chat_azure_stream(): customHandler = CompletionCustomHandler() litellm.callbacks = [customHandler] response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm async azure"}], ) ## test streaming response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm async azure"}], stream=True, ) @@ -507,7 +507,7 @@ async def test_async_chat_azure_stream(): # test failure callback try: response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "Hi 👋 - i'm async azure"}], api_key="my-bad-key", stream=True, @@ -1018,7 +1018,7 @@ async def test_async_completion_azure_caching(): litellm.callbacks = [customHandler_caching] unique_time = time.time() response1 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": f"Hi 👋 - i'm async azure {unique_time}"} ], @@ -1027,7 +1027,7 @@ async def test_async_completion_azure_caching(): await asyncio.sleep(1) print(f"customHandler_caching.states pre-cache hit: {customHandler_caching.states}") response2 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": f"Hi 👋 - i'm async azure {unique_time}"} ], @@ -1056,7 +1056,7 @@ async def test_async_completion_azure_caching_streaming(): litellm.callbacks = [customHandler_caching] unique_time = uuid.uuid4() response1 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": f"Hi 👋 - i'm async azure {unique_time}"} ], @@ -1069,7 +1069,7 @@ async def test_async_completion_azure_caching_streaming(): initial_customhandler_caching_states = len(customHandler_caching.states) print(f"customHandler_caching.states pre-cache hit: {customHandler_caching.states}") response2 = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": f"Hi 👋 - i'm async azure {unique_time}"} ], @@ -1207,7 +1207,7 @@ def test_turn_off_message_logging(): "model", [ "ft:gpt-3.5-turbo:my-org:custom_suffix:id" - ], # "gpt-3.5-turbo", "azure/chatgpt-v-2", + ], # "gpt-3.5-turbo", "azure/chatgpt-v-3", ) @pytest.mark.parametrize( "turn_off_message_logging", diff --git a/tests/local_testing/test_custom_callback_router.py b/tests/local_testing/test_custom_callback_router.py index 310a497922..83289abf5f 100644 --- a/tests/local_testing/test_custom_callback_router.py +++ b/tests/local_testing/test_custom_callback_router.py @@ -284,7 +284,7 @@ class CompletionCustomHandler( ) if ( - kwargs["model"] == "chatgpt-v-2" + kwargs["model"] == "chatgpt-v-3" and base_model is not None and kwargs["stream"] != True ): @@ -394,7 +394,7 @@ async def test_async_chat_azure(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -438,7 +438,7 @@ async def test_async_chat_azure(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "my-bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -545,7 +545,7 @@ async def test_async_chat_azure_with_fallbacks(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "my-bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -606,7 +606,7 @@ async def test_async_completion_azure_caching(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_custom_logger.py b/tests/local_testing/test_custom_logger.py index d9eb50eb73..ba9973e11d 100644 --- a/tests/local_testing/test_custom_logger.py +++ b/tests/local_testing/test_custom_logger.py @@ -160,7 +160,7 @@ def test_completion_azure_stream_moderation_failure(): ] try: response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=messages, mock_response="Exception: content_filter_policy", stream=True, @@ -195,7 +195,7 @@ def test_async_custom_handler_stream(): async def test_1(): nonlocal complete_streaming_response response = await litellm.acompletion( - model="azure/chatgpt-v-2", messages=messages, stream=True + model="azure/chatgpt-v-3", messages=messages, stream=True ) async for chunk in response: complete_streaming_response += ( @@ -239,7 +239,7 @@ def test_azure_completion_stream(): complete_streaming_response = "" response = litellm.completion( - model="azure/chatgpt-v-2", messages=messages, stream=True + model="azure/chatgpt-v-3", messages=messages, stream=True ) for chunk in response: complete_streaming_response += chunk["choices"][0]["delta"]["content"] or "" diff --git a/tests/local_testing/test_exceptions.py b/tests/local_testing/test_exceptions.py index 229ea07c7a..be7710f58a 100644 --- a/tests/local_testing/test_exceptions.py +++ b/tests/local_testing/test_exceptions.py @@ -51,7 +51,7 @@ async def test_content_policy_exception_azure(): # this is ony a test - we needed some way to invoke the exception :( litellm.set_verbose = True response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "where do I buy lethal drugs from"}], mock_response="Exception: content_filter_policy", ) @@ -124,7 +124,7 @@ def test_context_window_with_fallbacks(model): ctx_window_fallback_dict = { "command-nightly": "claude-2.1", "gpt-3.5-turbo-instruct": "gpt-3.5-turbo-16k", - "azure/chatgpt-v-2": "gpt-3.5-turbo-16k", + "azure/chatgpt-v-3": "gpt-3.5-turbo-16k", } sample_text = "how does a court case get to the Supreme Court?" * 1000 messages = [{"content": sample_text, "role": "user"}] @@ -161,7 +161,7 @@ def invalid_auth(model): # set the model key to an invalid key, depending on th os.environ["AWS_REGION_NAME"] = "bad-key" temporary_secret_key = os.environ["AWS_SECRET_ACCESS_KEY"] os.environ["AWS_SECRET_ACCESS_KEY"] = "bad-key" - elif model == "azure/chatgpt-v-2": + elif model == "azure/chatgpt-v-3": temporary_key = os.environ["AZURE_API_KEY"] os.environ["AZURE_API_KEY"] = "bad-key" elif model == "claude-3-5-haiku-20241022": @@ -262,7 +262,7 @@ def test_completion_azure_exception(): old_azure_key = os.environ["AZURE_API_KEY"] os.environ["AZURE_API_KEY"] = "good morning" response = completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "hello"}], ) os.environ["AZURE_API_KEY"] = old_azure_key @@ -309,7 +309,7 @@ async def asynctest_completion_azure_exception(): old_azure_key = os.environ["AZURE_API_KEY"] os.environ["AZURE_API_KEY"] = "good morning" response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "hello"}], ) print(f"response: {response}") @@ -528,7 +528,7 @@ def test_content_policy_violation_error_streaming(): async def test_get_response(): try: response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "say 1"}], temperature=0, top_p=1, @@ -557,7 +557,7 @@ def test_content_policy_violation_error_streaming(): async def test_get_error(): try: response = await litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": "where do i buy lethal drugs from"} ], @@ -754,7 +754,7 @@ def test_litellm_predibase_exception(): # return False # # Repeat each model 500 times # # extended_models = [model for model in models for _ in range(250)] -# extended_models = ["azure/chatgpt-v-2" for _ in range(250)] +# extended_models = ["azure/chatgpt-v-3" for _ in range(250)] # def worker(model): # return test_model_call(model) @@ -934,7 +934,7 @@ def _pre_call_utils_httpx( ("openai", "gpt-3.5-turbo", "chat_completion", False), ("openai", "gpt-3.5-turbo", "chat_completion", True), ("openai", "gpt-3.5-turbo-instruct", "completion", True), - ("azure", "azure/chatgpt-v-2", "chat_completion", True), + ("azure", "azure/chatgpt-v-3", "chat_completion", True), ("azure", "azure/text-embedding-ada-002", "embedding", True), ("azure", "azure_text/gpt-3.5-turbo-instruct", "completion", True), ], @@ -1158,7 +1158,7 @@ async def test_exception_with_headers_httpx( @pytest.mark.asyncio -@pytest.mark.parametrize("model", ["azure/chatgpt-v-2", "openai/gpt-3.5-turbo"]) +@pytest.mark.parametrize("model", ["azure/chatgpt-v-3", "openai/gpt-3.5-turbo"]) async def test_bad_request_error_contains_httpx_response(model): """ Test that the BadRequestError contains the httpx response @@ -1209,7 +1209,7 @@ def test_context_window_exceeded_error_from_litellm_proxy(): @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.parametrize("stream_mode", [True, False]) -@pytest.mark.parametrize("model", ["azure/gpt-4o"]) # "gpt-4o-mini", +@pytest.mark.parametrize("model", ["azure/gpt-4o-new-test"]) # "gpt-4o-mini", @pytest.mark.asyncio async def test_exception_bubbling_up(sync_mode, stream_mode, model): """ diff --git a/tests/local_testing/test_gcs_bucket.py b/tests/local_testing/test_gcs_bucket.py index b64475c227..0004fae7c4 100644 --- a/tests/local_testing/test_gcs_bucket.py +++ b/tests/local_testing/test_gcs_bucket.py @@ -108,14 +108,14 @@ async def test_aaabasic_gcs_logger(): }, "endpoint": "http://localhost:4000/chat/completions", "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", "model_info": { "id": "4bad40a1eb6bebd1682800f16f44b9f06c52a6703444c99c7f9f32e9de3693b4", "db_model": False, }, "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "caching_groups": None, - "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-2', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", + "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-3', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", }, ) @@ -216,14 +216,14 @@ async def test_basic_gcs_logger_failure(): }, "endpoint": "http://localhost:4000/chat/completions", "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", "model_info": { "id": "4bad40a1eb6bebd1682800f16f44b9f06c52a6703444c99c7f9f32e9de3693b4", "db_model": False, }, "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "caching_groups": None, - "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-2', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", + "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-3', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", }, ) except Exception: @@ -626,14 +626,14 @@ async def test_basic_gcs_logger_with_folder_in_bucket_name(): }, "endpoint": "http://localhost:4000/chat/completions", "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", "model_info": { "id": "4bad40a1eb6bebd1682800f16f44b9f06c52a6703444c99c7f9f32e9de3693b4", "db_model": False, }, "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "caching_groups": None, - "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-2', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", + "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-3', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", }, ) diff --git a/tests/local_testing/test_get_model_info.py b/tests/local_testing/test_get_model_info.py index eccd34f7f9..41af51dee7 100644 --- a/tests/local_testing/test_get_model_info.py +++ b/tests/local_testing/test_get_model_info.py @@ -489,6 +489,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "output_cost_per_token_above_128k_tokens": {"type": "number"}, "output_cost_per_token_above_200k_tokens": {"type": "number"}, "output_cost_per_token_batches": {"type": "number"}, + "output_cost_per_reasoning_token": {"type": "number"}, "output_db_cost_per_token": {"type": "number"}, "output_dbu_cost_per_token": {"type": "number"}, "output_vector_size": {"type": "number"}, diff --git a/tests/local_testing/test_health_check.py b/tests/local_testing/test_health_check.py index 809cd1ccbd..bf326d884b 100644 --- a/tests/local_testing/test_health_check.py +++ b/tests/local_testing/test_health_check.py @@ -20,7 +20,7 @@ import litellm async def test_azure_health_check(): response = await litellm.ahealth_check( model_params={ - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [{"role": "user", "content": "Hey, how's it going?"}], "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_helicone_integration.py b/tests/local_testing/test_helicone_integration.py index 968a9aa5b1..3a6fa0309b 100644 --- a/tests/local_testing/test_helicone_integration.py +++ b/tests/local_testing/test_helicone_integration.py @@ -78,7 +78,7 @@ async def make_async_calls(metadata=None, **completion_kwargs): def create_async_task(**completion_kwargs): completion_args = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_version": "2024-02-01", "messages": [{"role": "user", "content": "This is a test"}], "max_tokens": 5, diff --git a/tests/local_testing/test_least_busy_routing.py b/tests/local_testing/test_least_busy_routing.py index cf69f596d9..7e4393da0b 100644 --- a/tests/local_testing/test_least_busy_routing.py +++ b/tests/local_testing/test_least_busy_routing.py @@ -33,7 +33,7 @@ def test_model_added(): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": "1234"}, } @@ -47,7 +47,7 @@ def test_get_available_deployments(): test_cache = DualCache() least_busy_logger = LeastBusyLoggingHandler(router_cache=test_cache, model_list=[]) model_group = "gpt-3.5-turbo" - deployment = "azure/chatgpt-v-2" + deployment = "azure/chatgpt-v-3" kwargs = { "litellm_params": { "metadata": { @@ -113,7 +113,7 @@ async def test_router_get_available_deployments(async_test): router.leastbusy_logger.test_flag = True model_group = "azure-model" - deployment = "azure/chatgpt-v-2" + deployment = "azure/chatgpt-v-3" request_count_dict = {1: 10, 2: 54, 3: 100} cache_key = f"{model_group}_request_count" if async_test is True: diff --git a/tests/local_testing/test_load_test_router_s3.py b/tests/local_testing/test_load_test_router_s3.py index 3a022ae991..3a2567b686 100644 --- a/tests/local_testing/test_load_test_router_s3.py +++ b/tests/local_testing/test_load_test_router_s3.py @@ -46,7 +46,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "api_version": os.getenv("AZURE_API_VERSION"), diff --git a/tests/local_testing/test_loadtest_router.py b/tests/local_testing/test_loadtest_router.py index a12a45b514..0d8a09ca62 100644 --- a/tests/local_testing/test_loadtest_router.py +++ b/tests/local_testing/test_loadtest_router.py @@ -38,7 +38,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "api_version": os.getenv("AZURE_API_VERSION"), diff --git a/tests/local_testing/test_lowest_cost_routing.py b/tests/local_testing/test_lowest_cost_routing.py index 4e3105b5ff..caca007052 100644 --- a/tests/local_testing/test_lowest_cost_routing.py +++ b/tests/local_testing/test_lowest_cost_routing.py @@ -60,7 +60,7 @@ async def test_get_available_deployments_custom_price(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "input_cost_per_token": 0.00003, "output_cost_per_token": 0.00003, }, diff --git a/tests/local_testing/test_lowest_latency_routing.py b/tests/local_testing/test_lowest_latency_routing.py index 4234490982..74dae25c1f 100644 --- a/tests/local_testing/test_lowest_latency_routing.py +++ b/tests/local_testing/test_lowest_latency_routing.py @@ -48,7 +48,7 @@ async def test_latency_memory_leak(sync_mode): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -130,7 +130,7 @@ def test_latency_updated(): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -173,7 +173,7 @@ def test_latency_updated_custom_ttl(): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -200,12 +200,12 @@ def test_get_available_deployments(): model_list = [ { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "1234"}, }, { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "5678"}, }, ] @@ -219,7 +219,7 @@ def test_get_available_deployments(): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -240,7 +240,7 @@ def test_get_available_deployments(): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -275,7 +275,7 @@ async def _deploy(lowest_latency_logger, deployment_id, tokens_used, duration): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -317,12 +317,12 @@ def test_get_available_endpoints_tpm_rpm_check_async(ans_rpm): model_list = [ { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "1234", "rpm": ans_rpm}, }, { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "5678", "rpm": non_ans_rpm}, }, ] @@ -366,12 +366,12 @@ def test_get_available_endpoints_tpm_rpm_check(ans_rpm): model_list = [ { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "1234", "rpm": ans_rpm}, }, { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "5678", "rpm": non_ans_rpm}, }, ] @@ -385,7 +385,7 @@ def test_get_available_endpoints_tpm_rpm_check(ans_rpm): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } @@ -407,7 +407,7 @@ def test_get_available_endpoints_tpm_rpm_check(ans_rpm): "litellm_params": { "metadata": { "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", }, "model_info": {"id": deployment_id}, } diff --git a/tests/local_testing/test_mem_usage.py b/tests/local_testing/test_mem_usage.py index 4a804b4033..9f18fb1e2d 100644 --- a/tests/local_testing/test_mem_usage.py +++ b/tests/local_testing/test_mem_usage.py @@ -29,7 +29,7 @@ # { # "model_name": "gpt-3.5-turbo", # openai model name # "litellm_params": { # params for litellm completion/embedding call -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -40,7 +40,7 @@ # { # "model_name": "bad-model", # openai model name # "litellm_params": { # params for litellm completion/embedding call -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": "bad-key", # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_mock_request.py b/tests/local_testing/test_mock_request.py index 6842767d9d..6a9c5239f4 100644 --- a/tests/local_testing/test_mock_request.py +++ b/tests/local_testing/test_mock_request.py @@ -157,7 +157,7 @@ def test_router_mock_request_with_mock_timeout_with_fallbacks(): { "model_name": "azure-gpt", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), }, diff --git a/tests/local_testing/test_prometheus_service.py b/tests/local_testing/test_prometheus_service.py index c640532a07..cfbd6a1a83 100644 --- a/tests/local_testing/test_prometheus_service.py +++ b/tests/local_testing/test_prometheus_service.py @@ -104,12 +104,12 @@ async def test_router_with_caching(): model_list = [ { "model_name": "azure/gpt-4", - "litellm_params": get_azure_params("chatgpt-v-2"), + "litellm_params": get_azure_params("chatgpt-v-3"), "tpm": 100, }, { "model_name": "azure/gpt-4", - "litellm_params": get_azure_params("chatgpt-v-2"), + "litellm_params": get_azure_params("chatgpt-v-3"), "tpm": 1000, }, ] diff --git a/tests/local_testing/test_prompt_injection_detection.py b/tests/local_testing/test_prompt_injection_detection.py index c493a37227..8443aadcc6 100644 --- a/tests/local_testing/test_prompt_injection_detection.py +++ b/tests/local_testing/test_prompt_injection_detection.py @@ -107,7 +107,7 @@ async def test_prompt_injection_llm_eval(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_provider_specific_config.py b/tests/local_testing/test_provider_specific_config.py index fc382bd3e9..8fc4c6ec21 100644 --- a/tests/local_testing/test_provider_specific_config.py +++ b/tests/local_testing/test_provider_specific_config.py @@ -729,7 +729,7 @@ def azure_openai_test_completion(): try: # OVERRIDE WITH DYNAMIC MAX TOKENS response_1 = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ { "content": "Hello, how are you? Be as verbose as possible", @@ -743,7 +743,7 @@ def azure_openai_test_completion(): # USE CONFIG TOKENS response_2 = litellm.completion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ { "content": "Hello, how are you? Be as verbose as possible", diff --git a/tests/local_testing/test_router.py b/tests/local_testing/test_router.py index 13eaeb09ab..eb845559e2 100644 --- a/tests/local_testing/test_router.py +++ b/tests/local_testing/test_router.py @@ -266,7 +266,7 @@ def test_router_sensitive_keys(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "special-key", }, "model_info": {"id": 12345}, @@ -334,7 +334,7 @@ async def test_router_retries(sync_mode): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -417,7 +417,7 @@ def test_exception_raising(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -479,7 +479,7 @@ def test_reading_key_from_model_list(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": old_api_key, "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -535,7 +535,7 @@ def test_reading_key_from_model_list(): def test_call_one_endpoint(): # [PROD TEST CASE] # user passes one deployment they want to call on the router, we call the specified one - # this test makes a completion calls azure/chatgpt-v-2, it should work + # this test makes a completion calls azure/chatgpt-v-3, it should work try: print("Testing calling a specific deployment") old_api_key = os.environ["AZURE_API_KEY"] @@ -544,7 +544,7 @@ def test_call_one_endpoint(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": old_api_key, "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -574,7 +574,7 @@ def test_call_one_endpoint(): async def call_azure_completion(): response = await router.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "hello this request will pass"}], specific_deployment=True, ) @@ -620,7 +620,7 @@ def test_router_azure_acompletion(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": old_api_key, "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -793,7 +793,7 @@ def test_router_context_window_check_pre_call_check_in_group_custom_model_info() { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -847,7 +847,7 @@ def test_router_context_window_check_pre_call_check(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -901,7 +901,7 @@ def test_router_context_window_check_pre_call_check_out_group(): { "model_name": "gpt-3.5-turbo-small", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -980,7 +980,7 @@ def test_router_region_pre_call_check(allowed_model_region): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -2616,7 +2616,7 @@ def test_is_team_specific_model(): # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "tpm": 100000, @@ -2626,7 +2626,7 @@ def test_is_team_specific_model(): # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "tpm": 500, diff --git a/tests/local_testing/test_router_budget_limiter.py b/tests/local_testing/test_router_budget_limiter.py index 8d4948f8f9..9c20b6d098 100644 --- a/tests/local_testing/test_router_budget_limiter.py +++ b/tests/local_testing/test_router_budget_limiter.py @@ -74,7 +74,7 @@ async def test_provider_budgets_e2e_test(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -268,7 +268,7 @@ async def test_prometheus_metric_tracking(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_caching.py b/tests/local_testing/test_router_caching.py index 53a79b9434..574f133ace 100644 --- a/tests/local_testing/test_router_caching.py +++ b/tests/local_testing/test_router_caching.py @@ -96,7 +96,7 @@ async def test_acompletion_caching_on_router(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -213,7 +213,7 @@ async def test_acompletion_caching_with_ttl_on_router(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -279,7 +279,7 @@ async def test_acompletion_caching_on_router_caching_groups(): { "model_name": "azure-gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), diff --git a/tests/local_testing/test_router_client_init.py b/tests/local_testing/test_router_client_init.py index 1440dfecaa..42fc49a4c7 100644 --- a/tests/local_testing/test_router_client_init.py +++ b/tests/local_testing/test_router_client_init.py @@ -43,7 +43,7 @@ async def test_router_init(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), diff --git a/tests/local_testing/test_router_cooldowns.py b/tests/local_testing/test_router_cooldowns.py index 80ceb33c01..8428200109 100644 --- a/tests/local_testing/test_router_cooldowns.py +++ b/tests/local_testing/test_router_cooldowns.py @@ -41,7 +41,7 @@ async def test_cooldown_badrequest_error(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_debug_logs.py b/tests/local_testing/test_router_debug_logs.py index ba59a3c2fd..bce09404d8 100644 --- a/tests/local_testing/test_router_debug_logs.py +++ b/tests/local_testing/test_router_debug_logs.py @@ -33,7 +33,7 @@ def test_async_fallbacks(caplog): { "model_name": "azure/gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -93,7 +93,7 @@ def test_async_fallbacks(caplog): # - error request, falling back notice, success notice expected_logs = [ "Falling back to model_group = azure/gpt-3.5-turbo", - "litellm.acompletion(model=azure/chatgpt-v-2)\x1b[32m 200 OK\x1b[0m", + "litellm.acompletion(model=azure/chatgpt-v-3)\x1b[32m 200 OK\x1b[0m", "Successful fallback b/w models.", ] diff --git a/tests/local_testing/test_router_fallbacks.py b/tests/local_testing/test_router_fallbacks.py index 576ad0fcaa..ced2c4dd9e 100644 --- a/tests/local_testing/test_router_fallbacks.py +++ b/tests/local_testing/test_router_fallbacks.py @@ -67,7 +67,7 @@ def test_sync_fallbacks(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -78,7 +78,7 @@ def test_sync_fallbacks(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -150,7 +150,7 @@ async def test_async_fallbacks(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -161,7 +161,7 @@ async def test_async_fallbacks(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -349,7 +349,7 @@ def test_dynamic_fallbacks_sync(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -360,7 +360,7 @@ def test_dynamic_fallbacks_sync(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -426,7 +426,7 @@ async def test_dynamic_fallbacks_async(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -437,7 +437,7 @@ async def test_dynamic_fallbacks_async(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -509,7 +509,7 @@ async def test_async_fallbacks_streaming(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -520,7 +520,7 @@ async def test_async_fallbacks_streaming(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -594,7 +594,7 @@ def test_sync_fallbacks_streaming(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -605,7 +605,7 @@ def test_sync_fallbacks_streaming(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -675,7 +675,7 @@ async def test_async_fallbacks_max_retries_per_request(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -686,7 +686,7 @@ async def test_async_fallbacks_max_retries_per_request(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -808,13 +808,13 @@ def test_ausage_based_routing_fallbacks(): model_list = [ { "model_name": "azure/gpt-4-fast", - "litellm_params": get_azure_params("chatgpt-v-2"), + "litellm_params": get_azure_params("chatgpt-v-3"), "model_info": {"id": 1}, "rpm": AZURE_FAST_RPM, }, { "model_name": "azure/gpt-4-basic", - "litellm_params": get_azure_params("chatgpt-v-2"), + "litellm_params": get_azure_params("chatgpt-v-3"), "model_info": {"id": 2}, "rpm": AZURE_BASIC_RPM, }, @@ -889,7 +889,7 @@ def test_custom_cooldown_times(): { # list of model deployments "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -899,7 +899,7 @@ def test_custom_cooldown_times(): { # list of model deployments "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -993,7 +993,7 @@ async def test_service_unavailable_fallbacks(sync_mode): { "model_name": "gpt-3.5-turbo-0125-preview", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_get_deployments.py b/tests/local_testing/test_router_get_deployments.py index efbb5d16e7..ff88824d4a 100644 --- a/tests/local_testing/test_router_get_deployments.py +++ b/tests/local_testing/test_router_get_deployments.py @@ -41,7 +41,7 @@ def test_weighted_selection_router(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -54,7 +54,7 @@ def test_weighted_selection_router(): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = router.get_available_deployment("gpt-3.5-turbo") selected_model_id = selected_model["litellm_params"]["model"] @@ -64,10 +64,10 @@ def test_weighted_selection_router(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" router.reset() except Exception as e: @@ -97,7 +97,7 @@ def test_weighted_selection_router_tpm(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -110,7 +110,7 @@ def test_weighted_selection_router_tpm(): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = router.get_available_deployment("gpt-3.5-turbo") selected_model_id = selected_model["litellm_params"]["model"] @@ -120,10 +120,10 @@ def test_weighted_selection_router_tpm(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" router.reset() except Exception as e: @@ -153,7 +153,7 @@ def test_weighted_selection_router_tpm_as_router_param(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -166,7 +166,7 @@ def test_weighted_selection_router_tpm_as_router_param(): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = router.get_available_deployment("gpt-3.5-turbo") selected_model_id = selected_model["litellm_params"]["model"] @@ -176,10 +176,10 @@ def test_weighted_selection_router_tpm_as_router_param(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" router.reset() except Exception as e: @@ -210,7 +210,7 @@ def test_weighted_selection_router_rpm_as_router_param(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -224,7 +224,7 @@ def test_weighted_selection_router_rpm_as_router_param(): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = router.get_available_deployment("gpt-3.5-turbo") selected_model_id = selected_model["litellm_params"]["model"] @@ -234,10 +234,10 @@ def test_weighted_selection_router_rpm_as_router_param(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" router.reset() except Exception as e: @@ -266,7 +266,7 @@ def test_weighted_selection_router_no_rpm_set(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -286,7 +286,7 @@ def test_weighted_selection_router_no_rpm_set(): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = router.get_available_deployment("claude-1") selected_model_id = selected_model["litellm_params"]["model"] @@ -296,7 +296,7 @@ def test_weighted_selection_router_no_rpm_set(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( selection_counts["bedrock/claude1.2"] / total_requests == 1 ), f"Assertion failed: Selection counts {selection_counts}" @@ -325,7 +325,7 @@ def test_model_group_aliases(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -358,7 +358,7 @@ def test_model_group_aliases(): ) # test that - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time selection_counts = defaultdict(int) for _ in range(1000): selected_model = router.get_available_deployment("gpt-3.5-turbo") @@ -369,10 +369,10 @@ def test_model_group_aliases(): total_requests = sum(selection_counts.values()) - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" router.reset() except Exception as e: @@ -552,7 +552,7 @@ async def test_weighted_selection_router_async(rpm_list, tpm_list): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "api_version": os.getenv("AZURE_API_VERSION"), @@ -566,7 +566,7 @@ async def test_weighted_selection_router_async(rpm_list, tpm_list): ) selection_counts = defaultdict(int) - # call get_available_deployment 1k times, it should pick azure/chatgpt-v-2 about 90% of the time + # call get_available_deployment 1k times, it should pick azure/chatgpt-v-3 about 90% of the time for _ in range(1000): selected_model = await router.async_get_available_deployment( "gpt-3.5-turbo", request_kwargs={} @@ -579,13 +579,13 @@ async def test_weighted_selection_router_async(rpm_list, tpm_list): total_requests = sum(selection_counts.values()) if rpm_list[0] is not None or tpm_list[0] is not None: - # Assert that 'azure/chatgpt-v-2' has about 90% of the total requests + # Assert that 'azure/chatgpt-v-3' has about 90% of the total requests assert ( - selection_counts["azure/chatgpt-v-2"] / total_requests > 0.89 - ), f"Assertion failed: 'azure/chatgpt-v-2' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" + selection_counts["azure/chatgpt-v-3"] / total_requests > 0.89 + ), f"Assertion failed: 'azure/chatgpt-v-3' does not have about 90% of the total requests in the weighted load balancer. Selection counts {selection_counts}" else: # Assert both are used - assert selection_counts["azure/chatgpt-v-2"] > 0 + assert selection_counts["azure/chatgpt-v-3"] > 0 assert selection_counts["gpt-3.5-turbo"] > 0 router.reset() except Exception as e: diff --git a/tests/local_testing/test_router_init.py b/tests/local_testing/test_router_init.py index 00b2daa764..dd2d43dc26 100644 --- a/tests/local_testing/test_router_init.py +++ b/tests/local_testing/test_router_init.py @@ -40,7 +40,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -96,7 +96,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -134,7 +134,7 @@ # { # "model_name": "azure-cloudflare", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": "https://gateway.ai.cloudflare.com/v1/0399b10e77ac6668c80404a5ff49eb37/litellm-test/azure-openai/openai-gpt-4-test-v-1", @@ -201,7 +201,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -254,7 +254,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -615,7 +615,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), @@ -660,7 +660,7 @@ # { # "model_name": "gpt-3.5-turbo", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_version": os.getenv("AZURE_API_VERSION"), # "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_policy_violation.py b/tests/local_testing/test_router_policy_violation.py index 52f50eb591..1e72868db6 100644 --- a/tests/local_testing/test_router_policy_violation.py +++ b/tests/local_testing/test_router_policy_violation.py @@ -69,7 +69,7 @@ async def test_async_fallbacks(): { # list of model deployments "model_name": "azure/gpt-3.5-turbo-context-fallback", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_retries.py b/tests/local_testing/test_router_retries.py index 12bd71cfd1..d028010afa 100644 --- a/tests/local_testing/test_router_retries.py +++ b/tests/local_testing/test_router_retries.py @@ -166,7 +166,7 @@ async def test_router_retry_policy(error_type): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -175,7 +175,7 @@ async def test_router_retry_policy(error_type): { "model_name": "bad-model", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -275,7 +275,7 @@ async def test_dynamic_router_retry_policy(model_group): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -287,7 +287,7 @@ async def test_dynamic_router_retry_policy(model_group): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -299,7 +299,7 @@ async def test_dynamic_router_retry_policy(model_group): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -311,7 +311,7 @@ async def test_dynamic_router_retry_policy(model_group): { "model_name": "bad-model", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -393,7 +393,7 @@ def test_retry_rate_limit_error_with_healthy_deployments(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -426,7 +426,7 @@ def test_do_retry_rate_limit_error_with_no_fallbacks_and_no_healthy_deployments( { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -459,14 +459,14 @@ def test_raise_context_window_exceeded_error(): llm_provider="azure", model="gpt-3.5-turbo", ) - context_window_fallbacks = [{"gpt-3.5-turbo": ["azure/chatgpt-v-2"]}] + context_window_fallbacks = [{"gpt-3.5-turbo": ["azure/chatgpt-v-3"]}] router = Router( model_list=[ { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -508,7 +508,7 @@ def test_raise_context_window_exceeded_error_no_retry(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -562,7 +562,7 @@ def test_timeout_for_rate_limit_error_with_healthy_deployments( { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -589,7 +589,7 @@ def test_timeout_for_rate_limit_error_with_healthy_deployments( "litellm_params": { "api_key": "my-key", "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com", - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", }, "model_info": { "id": "0e30bc8a63fa91ae4415d4234e231b3f9e6dd900cac57d118ce13a720d95e9d6", @@ -615,7 +615,7 @@ def test_timeout_for_rate_limit_error_with_no_healthy_deployments(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -650,7 +650,7 @@ def test_no_retry_for_not_found_error_404(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -709,7 +709,7 @@ def test_no_retry_when_no_healthy_deployments(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_router_timeout.py b/tests/local_testing/test_router_timeout.py index 3f149a4342..c8d7502eee 100644 --- a/tests/local_testing/test_router_timeout.py +++ b/tests/local_testing/test_router_timeout.py @@ -30,7 +30,7 @@ def test_router_timeouts(): { "model_name": "openai-gpt-4", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "os.environ/AZURE_API_KEY", "api_base": "os.environ/AZURE_API_BASE", "api_version": "os.environ/AZURE_API_VERSION", diff --git a/tests/local_testing/test_router_utils.py b/tests/local_testing/test_router_utils.py index 067aaf032a..cd26f8ad60 100644 --- a/tests/local_testing/test_router_utils.py +++ b/tests/local_testing/test_router_utils.py @@ -32,7 +32,7 @@ def test_returned_settings(): { "model_name": "gpt-3.5-turbo", # openai model name "litellm_params": { # params for litellm completion/embedding call - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -96,7 +96,7 @@ def test_update_kwargs_before_fallbacks_unit_test(): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), @@ -133,7 +133,7 @@ async def test_update_kwargs_before_fallbacks(call_type): { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "bad-key", "api_version": os.getenv("AZURE_API_VERSION"), "api_base": os.getenv("AZURE_API_BASE"), diff --git a/tests/local_testing/test_streaming.py b/tests/local_testing/test_streaming.py index 1f0730ef29..78226d0eab 100644 --- a/tests/local_testing/test_streaming.py +++ b/tests/local_testing/test_streaming.py @@ -241,7 +241,7 @@ tools_schema = [ def test_completion_azure_stream_special_char(): litellm.set_verbose = True messages = [{"role": "user", "content": "hi. respond with the tag only"}] - response = completion(model="azure/chatgpt-v-2", messages=messages, stream=True) + response = completion(model="azure/chatgpt-v-3", messages=messages, stream=True) response_str = "" for part in response: response_str += part.choices[0].delta.content or "" @@ -449,7 +449,7 @@ def test_completion_azure_stream(): }, ] response = completion( - model="azure/chatgpt-v-2", messages=messages, stream=True, max_tokens=50 + model="azure/chatgpt-v-3", messages=messages, stream=True, max_tokens=50 ) complete_response = "" # Add any assertions here to check the response @@ -2070,7 +2070,7 @@ def test_openai_chat_completion_complete_response_call(): "model", [ "gpt-3.5-turbo", - "azure/chatgpt-v-2", + "azure/chatgpt-v-3", "claude-3-haiku-20240307", "o1-preview", "o1", diff --git a/tests/local_testing/test_text_completion.py b/tests/local_testing/test_text_completion.py index 675f941b0e..35929f6f35 100644 --- a/tests/local_testing/test_text_completion.py +++ b/tests/local_testing/test_text_completion.py @@ -4163,7 +4163,7 @@ def test_completion_vllm(provider): def test_completion_fireworks_ai_multiple_choices(): - litellm.set_verbose = True + litellm._turn_on_debug() response = litellm.text_completion( model="fireworks_ai/llama-v3p1-8b-instruct", prompt=["halo", "hi", "halo", "hi"], diff --git a/tests/local_testing/test_timeout.py b/tests/local_testing/test_timeout.py index b74cf89eaa..9342e789b4 100644 --- a/tests/local_testing/test_timeout.py +++ b/tests/local_testing/test_timeout.py @@ -23,7 +23,7 @@ import litellm [ ("gpt-3.5-turbo", "openai"), ("anthropic.claude-instant-v1", "bedrock"), - ("azure/chatgpt-v-2", "azure"), + ("azure/chatgpt-v-3", "azure"), ], ) @pytest.mark.parametrize("sync_mode", [True, False]) @@ -104,7 +104,7 @@ def test_hanging_request_azure(): { "model_name": "azure-gpt", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_base": os.environ["AZURE_API_BASE"], "api_key": os.environ["AZURE_API_KEY"], }, @@ -158,7 +158,7 @@ def test_hanging_request_openai(): { "model_name": "azure-gpt", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_base": os.environ["AZURE_API_BASE"], "api_key": os.environ["AZURE_API_KEY"], }, diff --git a/tests/local_testing/test_tpm_rpm_routing_v2.py b/tests/local_testing/test_tpm_rpm_routing_v2.py index d2b951a187..57443bbe4c 100644 --- a/tests/local_testing/test_tpm_rpm_routing_v2.py +++ b/tests/local_testing/test_tpm_rpm_routing_v2.py @@ -45,7 +45,7 @@ def test_tpm_rpm_updated(): ) model_group = "gpt-3.5-turbo" deployment_id = "1234" - deployment = "azure/chatgpt-v-2" + deployment = "azure/chatgpt-v-3" total_tokens = 50 standard_logging_payload: StandardLoggingPayload = create_standard_logging_payload() standard_logging_payload["model_group"] = model_group @@ -100,12 +100,12 @@ def test_get_available_deployments(): model_list = [ { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "1234"}, }, { "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "azure/chatgpt-v-2"}, + "litellm_params": {"model": "azure/chatgpt-v-3"}, "model_info": {"id": "5678"}, }, ] @@ -116,7 +116,7 @@ def test_get_available_deployments(): ## DEPLOYMENT 1 ## total_tokens = 50 deployment_id = "1234" - deployment = "azure/chatgpt-v-2" + deployment = "azure/chatgpt-v-3" standard_logging_payload = create_standard_logging_payload() standard_logging_payload["model_group"] = model_group standard_logging_payload["model_id"] = deployment_id @@ -721,7 +721,7 @@ async def test_tpm_rpm_routing_model_name_checks(): deployment = { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": os.getenv("AZURE_API_BASE"), "mock_response": "Hey, how's it going?", @@ -763,5 +763,5 @@ async def test_tpm_rpm_routing_model_name_checks(): assert ( standard_logging_payload["hidden_params"]["litellm_model_name"] - == "azure/chatgpt-v-2" + == "azure/chatgpt-v-3" ) diff --git a/tests/logging_callback_tests/test_alerting.py b/tests/logging_callback_tests/test_alerting.py index fc2eae00f7..26a5e0822f 100644 --- a/tests/logging_callback_tests/test_alerting.py +++ b/tests/logging_callback_tests/test_alerting.py @@ -56,7 +56,7 @@ def test_get_api_base_unit_test(model, optional_params, expected_api_base): async def test_get_api_base(): _pl = ProxyLogging(user_api_key_cache=DualCache()) _pl.update_values(alerting=["slack"], alerting_threshold=100, redis_cache=None) - model = "chatgpt-v-2" + model = "chatgpt-v-3" messages = [{"role": "user", "content": "Hey how's it going?"}] litellm_params = { "acompletion": True, diff --git a/tests/logging_callback_tests/test_amazing_s3_logs.py b/tests/logging_callback_tests/test_amazing_s3_logs.py index 17efb177d0..915041e714 100644 --- a/tests/logging_callback_tests/test_amazing_s3_logs.py +++ b/tests/logging_callback_tests/test_amazing_s3_logs.py @@ -244,7 +244,7 @@ async def make_async_calls(): for _ in range(5): task = asyncio.create_task( litellm.acompletion( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[{"role": "user", "content": "This is a test"}], max_tokens=5, temperature=0.7, diff --git a/tests/logging_callback_tests/test_arize_logging.py b/tests/logging_callback_tests/test_arize_logging.py deleted file mode 100644 index aca3ae9a02..0000000000 --- a/tests/logging_callback_tests/test_arize_logging.py +++ /dev/null @@ -1,111 +0,0 @@ -import os -import sys -import time -from unittest.mock import Mock, patch -import json -import opentelemetry.exporter.otlp.proto.grpc.trace_exporter -from typing import Optional - -sys.path.insert( - 0, os.path.abspath("../..") -) # Adds the parent directory to the system-path -from litellm.integrations._types.open_inference import SpanAttributes -from litellm.integrations.arize.arize import ArizeConfig, ArizeLogger -from litellm.integrations.custom_logger import CustomLogger -from litellm.main import completion -import litellm -from litellm.types.utils import Choices, StandardCallbackDynamicParams -import pytest -import asyncio - - -def test_arize_set_attributes(): - """ - Test setting attributes for Arize - """ - from unittest.mock import MagicMock - from litellm.types.utils import ModelResponse - - span = MagicMock() - kwargs = { - "role": "user", - "content": "simple arize test", - "model": "gpt-4o", - "messages": [{"role": "user", "content": "basic arize test"}], - "standard_logging_object": { - "model_parameters": {"user": "test_user"}, - "metadata": {"key": "value", "key2": None}, - }, - } - response_obj = ModelResponse( - usage={"total_tokens": 100, "completion_tokens": 60, "prompt_tokens": 40}, - choices=[Choices(message={"role": "assistant", "content": "response content"})], - ) - - ArizeLogger.set_arize_attributes(span, kwargs, response_obj) - - assert span.set_attribute.call_count == 14 - span.set_attribute.assert_any_call( - SpanAttributes.METADATA, json.dumps({"key": "value", "key2": None}) - ) - span.set_attribute.assert_any_call(SpanAttributes.LLM_MODEL_NAME, "gpt-4o") - span.set_attribute.assert_any_call(SpanAttributes.OPENINFERENCE_SPAN_KIND, "LLM") - span.set_attribute.assert_any_call(SpanAttributes.INPUT_VALUE, "basic arize test") - span.set_attribute.assert_any_call("llm.input_messages.0.message.role", "user") - span.set_attribute.assert_any_call( - "llm.input_messages.0.message.content", "basic arize test" - ) - span.set_attribute.assert_any_call( - SpanAttributes.LLM_INVOCATION_PARAMETERS, '{"user": "test_user"}' - ) - span.set_attribute.assert_any_call(SpanAttributes.USER_ID, "test_user") - span.set_attribute.assert_any_call(SpanAttributes.OUTPUT_VALUE, "response content") - span.set_attribute.assert_any_call( - "llm.output_messages.0.message.role", "assistant" - ) - span.set_attribute.assert_any_call( - "llm.output_messages.0.message.content", "response content" - ) - span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 100) - span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 60) - span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 40) - - -class TestArizeLogger(CustomLogger): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.standard_callback_dynamic_params: Optional[ - StandardCallbackDynamicParams - ] = None - - async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): - print("logged kwargs", json.dumps(kwargs, indent=4, default=str)) - self.standard_callback_dynamic_params = kwargs.get( - "standard_callback_dynamic_params" - ) - - -@pytest.mark.asyncio -async def test_arize_dynamic_params(): - """verify arize ai dynamic params are recieved by a callback""" - test_arize_logger = TestArizeLogger() - litellm.callbacks = [test_arize_logger] - await litellm.acompletion( - model="gpt-4o", - messages=[{"role": "user", "content": "basic arize test"}], - mock_response="test", - arize_api_key="test_api_key_dynamic", - arize_space_key="test_space_key_dynamic", - ) - - await asyncio.sleep(2) - - assert test_arize_logger.standard_callback_dynamic_params is not None - assert ( - test_arize_logger.standard_callback_dynamic_params.get("arize_api_key") - == "test_api_key_dynamic" - ) - assert ( - test_arize_logger.standard_callback_dynamic_params.get("arize_space_key") - == "test_space_key_dynamic" - ) diff --git a/tests/logging_callback_tests/test_gcs_pub_sub.py b/tests/logging_callback_tests/test_gcs_pub_sub.py index d9cb9edb73..92ccb2ff9b 100644 --- a/tests/logging_callback_tests/test_gcs_pub_sub.py +++ b/tests/logging_callback_tests/test_gcs_pub_sub.py @@ -29,6 +29,79 @@ from litellm.types.utils import ( verbose_logger.setLevel(logging.DEBUG) +ignored_keys = [ + "request_id", + "startTime", + "endTime", + "completionStartTime", + "endTime", + "metadata.model_map_information", + "metadata.usage_object", +] + + +def _compare_nested_dicts( + actual: dict, expected: dict, path: str = "", ignore_keys: list[str] = [] +) -> list[str]: + """Compare nested dictionaries and return a list of differences in a human-friendly format.""" + differences = [] + + # Check if current path should be ignored + if path in ignore_keys: + return differences + + # Check for keys in actual but not in expected + for key in actual.keys(): + current_path = f"{path}.{key}" if path else key + if current_path not in ignore_keys and key not in expected: + differences.append(f"Extra key in actual: {current_path}") + + for key, expected_value in expected.items(): + current_path = f"{path}.{key}" if path else key + if current_path in ignore_keys: + continue + if key not in actual: + differences.append(f"Missing key: {current_path}") + continue + + actual_value = actual[key] + + # Try to parse JSON strings + if isinstance(expected_value, str): + try: + expected_value = json.loads(expected_value) + except json.JSONDecodeError: + pass + if isinstance(actual_value, str): + try: + actual_value = json.loads(actual_value) + except json.JSONDecodeError: + pass + + if isinstance(expected_value, dict) and isinstance(actual_value, dict): + differences.extend( + _compare_nested_dicts( + actual_value, expected_value, current_path, ignore_keys + ) + ) + elif isinstance(expected_value, dict) or isinstance(actual_value, dict): + differences.append( + f"Type mismatch at {current_path}: expected dict, got {type(actual_value).__name__}" + ) + else: + # For non-dict values, only report if they're different + if actual_value != expected_value: + # Format the values to be more readable + actual_str = str(actual_value) + expected_str = str(expected_value) + if len(actual_str) > 50 or len(expected_str) > 50: + actual_str = f"{actual_str[:50]}..." + expected_str = f"{expected_str[:50]}..." + differences.append( + f"Value mismatch at {current_path}:\n expected: {expected_str}\n got: {actual_str}" + ) + return differences + def assert_gcs_pubsub_request_matches_expected( actual_request_body: dict, @@ -49,24 +122,11 @@ def assert_gcs_pubsub_request_matches_expected( expected_request_body = json.load(f) # Replace dynamic values in actual request body - dynamic_fields = [ - "startTime", - "endTime", - "completionStartTime", - "request_id", - "id", - "response_time", - ] - for field in dynamic_fields: - if field in actual_request_body: - actual_request_body[field] = expected_request_body[field] - - # Assert the entire request body matches - print("actual_request_body", actual_request_body) - assert ( - actual_request_body == expected_request_body - ), f"Difference in request bodies: {json.dumps(actual_request_body, indent=2)} != {json.dumps(expected_request_body, indent=2)}" - + differences = _compare_nested_dicts( + actual_request_body, expected_request_body, ignore_keys=ignored_keys + ) + if differences: + assert False, f"Dictionary mismatch: {differences}" def assert_gcs_pubsub_request_matches_expected_standard_logging_payload( actual_request_body: dict, diff --git a/tests/logging_callback_tests/test_spend_logs.py b/tests/logging_callback_tests/test_spend_logs.py index 972e636b48..d592931f25 100644 --- a/tests/logging_callback_tests/test_spend_logs.py +++ b/tests/logging_callback_tests/test_spend_logs.py @@ -40,7 +40,7 @@ def test_spend_logs_payload(model_id: Optional[str]): input_args: dict = { "kwargs": { - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "messages": [ {"role": "system", "content": "you are a helpful assistant.\n"}, {"role": "user", "content": "bom dia"}, @@ -89,7 +89,7 @@ def test_spend_logs_payload(model_id: Optional[str]): }, "endpoint": "http://localhost:4000/chat/completions", "model_group": "gpt-3.5-turbo", - "deployment": "azure/chatgpt-v-2", + "deployment": "azure/chatgpt-v-3", "model_info": { "id": "4bad40a1eb6bebd1682800f16f44b9f06c52a6703444c99c7f9f32e9de3693b4", "db_model": False, @@ -99,7 +99,7 @@ def test_spend_logs_payload(model_id: Optional[str]): "error_information": None, "status": "success", "proxy_server_request": "{}", - "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-2', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", + "raw_request": "\n\nPOST Request Sent from LiteLLM:\ncurl -X POST \\\nhttps://openai-gpt-4-test-v-1.openai.azure.com//openai/ \\\n-H 'Authorization: *****' \\\n-d '{'model': 'chatgpt-v-3', 'messages': [{'role': 'system', 'content': 'you are a helpful assistant.\\n'}, {'role': 'user', 'content': 'bom dia'}], 'stream': False, 'max_tokens': 10, 'user': '116544810872468347480', 'extra_body': {}}'\n", }, "model_info": { "id": "4bad40a1eb6bebd1682800f16f44b9f06c52a6703444c99c7f9f32e9de3693b4", @@ -158,7 +158,7 @@ def test_spend_logs_payload(model_id: Optional[str]): "api_base": "openai-gpt-4-test-v-1.openai.azure.com", "acompletion": True, "complete_input_dict": { - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "messages": [ {"role": "system", "content": "you are a helpful assistant.\n"}, {"role": "user", "content": "bom dia"}, diff --git a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py index 3ffa97be9f..445c773d99 100644 --- a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py +++ b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py @@ -38,6 +38,7 @@ from litellm.integrations.langfuse.langfuse_prompt_management import ( LangfusePromptManagement, ) from litellm.integrations.azure_storage.azure_storage import AzureBlobStorageLogger +from litellm.integrations.agentops import AgentOps from litellm.integrations.humanloop import HumanloopLogger from litellm.proxy.hooks.dynamic_rate_limiter import _PROXY_DynamicRateLimitHandler from unittest.mock import patch @@ -75,6 +76,7 @@ callback_class_str_to_classType = { "pagerduty": PagerDutyAlerting, "gcs_pubsub": GcsPubSubLogger, "anthropic_cache_control_hook": AnthropicCacheControlHook, + "agentops": AgentOps, } expected_env_vars = { diff --git a/tests/old_proxy_tests/tests/load_test_q.py b/tests/old_proxy_tests/tests/load_test_q.py index 17fa185215..a0e22eda5a 100644 --- a/tests/old_proxy_tests/tests/load_test_q.py +++ b/tests/old_proxy_tests/tests/load_test_q.py @@ -25,7 +25,7 @@ config = { { "model_name": "gpt-3.5-turbo", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.environ["AZURE_API_KEY"], "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "api_version": "2023-07-01-preview", diff --git a/tests/old_proxy_tests/tests/test_langchain_request.py b/tests/old_proxy_tests/tests/test_langchain_request.py index e94a077cc8..901edd783a 100644 --- a/tests/old_proxy_tests/tests/test_langchain_request.py +++ b/tests/old_proxy_tests/tests/test_langchain_request.py @@ -9,7 +9,7 @@ # chat = ChatOpenAI( # openai_api_base="http://0.0.0.0:8000", -# model = "azure/chatgpt-v-2", +# model = "azure/chatgpt-v-3", # temperature=0.1, # extra_body={ # "metadata": { diff --git a/tests/old_proxy_tests/tests/test_openai_exception_request.py b/tests/old_proxy_tests/tests/test_openai_exception_request.py index 46090e1c89..68b8997766 100644 --- a/tests/old_proxy_tests/tests/test_openai_exception_request.py +++ b/tests/old_proxy_tests/tests/test_openai_exception_request.py @@ -39,7 +39,7 @@ client = openai.AzureOpenAI( ) try: response = client.chat.completions.create( - model="chatgpt-v-2", + model="chatgpt-v-3", messages=[ { "role": "user", diff --git a/tests/old_proxy_tests/tests/test_openai_request.py b/tests/old_proxy_tests/tests/test_openai_request.py index bb7bf22687..41b8c43f2d 100644 --- a/tests/old_proxy_tests/tests/test_openai_request.py +++ b/tests/old_proxy_tests/tests/test_openai_request.py @@ -4,7 +4,7 @@ client = openai.OpenAI(api_key="hi", base_url="http://0.0.0.0:8000") # # request sent to model set on litellm proxy, `litellm --model` response = client.chat.completions.create( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": "this is a test request, write a short poem"} ], diff --git a/tests/pass_through_unit_tests/test_custom_logger_passthrough.py b/tests/pass_through_unit_tests/test_custom_logger_passthrough.py new file mode 100644 index 0000000000..747eb4bdbe --- /dev/null +++ b/tests/pass_through_unit_tests/test_custom_logger_passthrough.py @@ -0,0 +1,155 @@ +import json +import os +import sys +from datetime import datetime +from unittest.mock import AsyncMock, Mock, patch, MagicMock +from typing import Optional +from fastapi import Request +import pytest +import asyncio + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.proxy._types import UserAPIKeyAuth +from litellm.types.passthrough_endpoints.pass_through_endpoints import PassthroughStandardLoggingPayload +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy.pass_through_endpoints.pass_through_endpoints import pass_through_request + +class TestCustomLogger(CustomLogger): + def __init__(self): + self.logged_kwargs: Optional[dict] = None + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + print("in async log success event kwargs", json.dumps(kwargs, indent=4, default=str)) + self.logged_kwargs = kwargs + +@pytest.mark.asyncio +async def test_assistants_passthrough_logging(): + test_custom_logger = TestCustomLogger() + litellm._async_success_callback = [test_custom_logger] + + TARGET_URL = "https://api.openai.com/v1/assistants" + REQUEST_BODY = { + "instructions": "You are a personal math tutor. When asked a question, write and run Python code to answer the question.", + "name": "Math Tutor", + "tools": [{"type": "code_interpreter"}], + "model": "gpt-4o" + } + TARGET_METHOD = "POST" + + result = await pass_through_request( + request=Request( + scope={ + "type": "http", + "method": TARGET_METHOD, + "path": "/v1/assistants", + "query_string": b"", + "headers": [ + (b"content-type", b"application/json"), + (b"authorization", f"Bearer {os.getenv('OPENAI_API_KEY')}".encode()), + (b"openai-beta", b"assistants=v2") + ] + }, + ), + target=TARGET_URL, + custom_headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}", + "OpenAI-Beta": "assistants=v2" + }, + user_api_key_dict=UserAPIKeyAuth( + api_key="test", + user_id="test", + team_id="test", + end_user_id="test", + ), + custom_body=REQUEST_BODY, + forward_headers=False, + merge_query_params=False, + ) + + print("got result", result) + print("result status code", result.status_code) + print("result content", result.body) + + await asyncio.sleep(1) + + assert test_custom_logger.logged_kwargs is not None + passthrough_logging_payload: Optional[PassthroughStandardLoggingPayload] = test_custom_logger.logged_kwargs["passthrough_logging_payload"] + assert passthrough_logging_payload is not None + assert passthrough_logging_payload["url"] == TARGET_URL + assert passthrough_logging_payload["request_body"] == REQUEST_BODY + + # assert that the response body content matches the response body content + client_facing_response_body = json.loads(result.body) + assert passthrough_logging_payload["response_body"] == client_facing_response_body + + # assert that the request method is correct + assert passthrough_logging_payload["request_method"] == TARGET_METHOD + +@pytest.mark.asyncio +async def test_threads_passthrough_logging(): + test_custom_logger = TestCustomLogger() + litellm._async_success_callback = [test_custom_logger] + + TARGET_URL = "https://api.openai.com/v1/threads" + REQUEST_BODY = {} + TARGET_METHOD = "POST" + + result = await pass_through_request( + request=Request( + scope={ + "type": "http", + "method": TARGET_METHOD, + "path": "/v1/threads", + "query_string": b"", + "headers": [ + (b"content-type", b"application/json"), + (b"authorization", f"Bearer {os.getenv('OPENAI_API_KEY')}".encode()), + (b"openai-beta", b"assistants=v2") + ] + }, + ), + target=TARGET_URL, + custom_headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}", + "OpenAI-Beta": "assistants=v2" + }, + user_api_key_dict=UserAPIKeyAuth( + api_key="test", + user_id="test", + team_id="test", + end_user_id="test", + ), + custom_body=REQUEST_BODY, + forward_headers=False, + merge_query_params=False, + ) + + print("got result", result) + print("result status code", result.status_code) + print("result content", result.body) + + await asyncio.sleep(1) + + assert test_custom_logger.logged_kwargs is not None + passthrough_logging_payload = test_custom_logger.logged_kwargs["passthrough_logging_payload"] + assert passthrough_logging_payload is not None + + # Fix for TypedDict access errors + assert passthrough_logging_payload.get("url") == TARGET_URL + assert passthrough_logging_payload.get("request_body") == REQUEST_BODY + + # Fix for json.loads error with potential memoryview + response_body = result.body + client_facing_response_body = json.loads(response_body) + + assert passthrough_logging_payload.get("response_body") == client_facing_response_body + assert passthrough_logging_payload.get("request_method") == TARGET_METHOD + + + diff --git a/tests/pass_through_unit_tests/test_pass_through_unit_tests.py b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py index cb9db00324..dd5fc4275e 100644 --- a/tests/pass_through_unit_tests/test_pass_through_unit_tests.py +++ b/tests/pass_through_unit_tests/test_pass_through_unit_tests.py @@ -17,7 +17,7 @@ import pytest import litellm from typing import AsyncGenerator from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.proxy.pass_through_endpoints.types import EndpointType +from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType from litellm.proxy.pass_through_endpoints.success_handler import ( PassThroughEndpointLogging, ) @@ -34,7 +34,7 @@ from litellm.proxy.pass_through_endpoints.pass_through_endpoints import ( _init_kwargs_for_pass_through_endpoint, _update_metadata_with_tags_in_header, ) -from litellm.proxy.pass_through_endpoints.types import PassthroughStandardLoggingPayload +from litellm.types.passthrough_endpoints.pass_through_endpoints import PassthroughStandardLoggingPayload @pytest.fixture @@ -115,6 +115,15 @@ def test_init_kwargs_for_pass_through_endpoint_basic( user_api_key_dict=mock_user_api_key_dict, passthrough_logging_payload=passthrough_payload, litellm_call_id="test-call-id", + logging_obj=LiteLLMLoggingObj( + model="test-model", + messages=[], + stream=False, + call_type="test-call-type", + start_time=datetime.now(), + litellm_call_id="test-call-id", + function_id="test-function-id", + ), ) assert result["call_type"] == "pass_through_endpoint" @@ -158,6 +167,15 @@ def test_init_kwargs_with_litellm_metadata(mock_request, mock_user_api_key_dict) passthrough_logging_payload=passthrough_payload, _parsed_body=parsed_body, litellm_call_id="test-call-id", + logging_obj=LiteLLMLoggingObj( + model="test-model", + messages=[], + stream=False, + call_type="test-call-type", + start_time=datetime.now(), + litellm_call_id="test-call-id", + function_id="test-function-id", + ), ) # Check that litellm_metadata was merged with default metadata @@ -183,6 +201,15 @@ def test_init_kwargs_with_tags_in_header(mock_request, mock_user_api_key_dict): user_api_key_dict=mock_user_api_key_dict, passthrough_logging_payload=passthrough_payload, litellm_call_id="test-call-id", + logging_obj=LiteLLMLoggingObj( + model="test-model", + messages=[], + stream=False, + call_type="test-call-type", + start_time=datetime.now(), + litellm_call_id="test-call-id", + function_id="test-function-id", + ), ) # Check that tags were added to metadata diff --git a/tests/pass_through_unit_tests/test_unit_test_streaming.py b/tests/pass_through_unit_tests/test_unit_test_streaming.py index 710687acc8..d3e0b6b0b0 100644 --- a/tests/pass_through_unit_tests/test_unit_test_streaming.py +++ b/tests/pass_through_unit_tests/test_unit_test_streaming.py @@ -13,7 +13,8 @@ import pytest import litellm from typing import AsyncGenerator from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj -from litellm.proxy.pass_through_endpoints.types import EndpointType +from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType +from litellm.types.passthrough_endpoints.pass_through_endpoints import PassthroughStandardLoggingPayload from litellm.proxy.pass_through_endpoints.success_handler import ( PassThroughEndpointLogging, ) diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/redirect-fail-screenshot.png b/tests/proxy_admin_ui_tests/e2e_ui_tests/redirect-fail-screenshot.png new file mode 100644 index 0000000000..b2e3325126 Binary files /dev/null and b/tests/proxy_admin_ui_tests/e2e_ui_tests/redirect-fail-screenshot.png differ diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts new file mode 100644 index 0000000000..18bc91c097 --- /dev/null +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/require_auth_for_dashboard.spec.ts @@ -0,0 +1,31 @@ +// tests/auth.spec.ts +import { test, expect } from "@playwright/test"; + +test.describe("Authentication Checks", () => { + test("should redirect unauthenticated user from a protected page", async ({ page }) => { + test.setTimeout(30000); + + page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); + + const protectedPageUrl = "http://localhost:4000/ui?page=llm-playground"; + const expectedRedirectUrl = "http://localhost:4000/sso/key/generate"; + + console.log(`Attempting to navigate to protected page: ${protectedPageUrl}`); + + await page.goto(protectedPageUrl); + + console.log(`Navigation initiated. Current URL: ${page.url()}`); + + try { + await page.waitForURL(expectedRedirectUrl, { timeout: 10000 }); + console.log(`Waited for URL. Current URL is now: ${page.url()}`); + } catch (error) { + console.error(`Timeout waiting for URL: ${expectedRedirectUrl}. Current URL: ${page.url()}`); + await page.screenshot({ path: "redirect-fail-screenshot.png" }); + throw error; + } + + await expect(page).toHaveURL(expectedRedirectUrl); + console.log(`Assertion passed: Page URL is ${expectedRedirectUrl}`); + }); +}); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts new file mode 100644 index 0000000000..7b9da6a27d --- /dev/null +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/search_users.spec.ts @@ -0,0 +1,214 @@ +/* +Search Users in Admin UI +E2E Test for user search functionality + +Tests: +1. Navigate to Internal Users tab +2. Verify search input exists +3. Test search functionality +4. Verify results update +5. Test filtering by email, user ID, and SSO user ID +*/ + +import { test, expect } from "@playwright/test"; + +test("user search test", async ({ page }) => { + // Set a longer timeout for the entire test + test.setTimeout(60000); + + // Enable console logging + page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); + + // Login first + await page.goto("http://localhost:4000/ui"); + console.log("Navigated to login page"); + + // Wait for login form to be visible + await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + console.log("Login form is visible"); + + await page.fill('input[name="username"]', "admin"); + await page.fill('input[name="password"]', "gm"); + console.log("Filled login credentials"); + + const loginButton = page.locator('input[type="submit"]'); + await expect(loginButton).toBeEnabled(); + await loginButton.click(); + console.log("Clicked login button"); + + // Wait for navigation to complete and dashboard to load + await page.waitForLoadState("networkidle"); + console.log("Page loaded after login"); + + // Take a screenshot for debugging + await page.screenshot({ path: "after-login.png" }); + console.log("Took screenshot after login"); + + // Try to find the Internal User tab with more debugging + console.log("Looking for Internal User tab..."); + const internalUserTab = page.locator("span.ant-menu-title-content", { + hasText: "Internal User", + }); + + // Wait for the tab to be visible + await internalUserTab.waitFor({ state: "visible", timeout: 10000 }); + console.log("Internal User tab is visible"); + + // Take another screenshot before clicking + await page.screenshot({ path: "before-tab-click.png" }); + console.log("Took screenshot before tab click"); + + await internalUserTab.click(); + console.log("Clicked Internal User tab"); + + // Wait for the page to load and table to be visible + await page.waitForSelector("tbody tr", { timeout: 30000 }); + await page.waitForTimeout(2000); // Additional wait for table to stabilize + console.log("Table is visible"); + + // Take a final screenshot + await page.screenshot({ path: "after-tab-click.png" }); + console.log("Took screenshot after tab click"); + + // Verify search input exists + const searchInput = page.locator('input[placeholder="Search by email..."]'); + await expect(searchInput).toBeVisible(); + console.log("Search input is visible"); + + // Test search functionality + const initialUserCount = await page.locator("tbody tr").count(); + console.log(`Initial user count: ${initialUserCount}`); + + // Perform a search + const testEmail = "test@"; + await searchInput.fill(testEmail); + console.log("Filled search input"); + + // Wait for the debounced search to complete + await page.waitForTimeout(500); + console.log("Waited for debounce"); + + // Wait for the results count to update + await page.waitForFunction((initialCount) => { + const currentCount = document.querySelectorAll("tbody tr").length; + return currentCount !== initialCount; + }, initialUserCount); + console.log("Results updated"); + + const filteredUserCount = await page.locator("tbody tr").count(); + console.log(`Filtered user count: ${filteredUserCount}`); + + expect(filteredUserCount).toBeDefined(); + + // Clear the search + await searchInput.clear(); + console.log("Cleared search"); + + await page.waitForTimeout(500); + console.log("Waited for debounce after clear"); + + await page.waitForFunction((initialCount) => { + const currentCount = document.querySelectorAll("tbody tr").length; + return currentCount === initialCount; + }, initialUserCount); + console.log("Results reset"); + + const resetUserCount = await page.locator("tbody tr").count(); + console.log(`Reset user count: ${resetUserCount}`); + + expect(resetUserCount).toBe(initialUserCount); +}); + +test("user filter test", async ({ page }) => { + // Set a longer timeout for the entire test + test.setTimeout(60000); + + // Enable console logging + page.on("console", (msg) => console.log("PAGE LOG:", msg.text())); + + // Login first + await page.goto("http://localhost:4000/ui"); + console.log("Navigated to login page"); + + // Wait for login form to be visible + await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + console.log("Login form is visible"); + + await page.fill('input[name="username"]', "admin"); + await page.fill('input[name="password"]', "gm"); + console.log("Filled login credentials"); + + const loginButton = page.locator('input[type="submit"]'); + await expect(loginButton).toBeEnabled(); + await loginButton.click(); + console.log("Clicked login button"); + + // Wait for navigation to complete and dashboard to load + await page.waitForLoadState("networkidle"); + console.log("Page loaded after login"); + + // Navigate to Internal Users tab + const internalUserTab = page.locator("span.ant-menu-title-content", { + hasText: "Internal User", + }); + await internalUserTab.waitFor({ state: "visible", timeout: 10000 }); + await internalUserTab.click(); + console.log("Clicked Internal User tab"); + + // Wait for the page to load and table to be visible + await page.waitForSelector("tbody tr", { timeout: 30000 }); + await page.waitForTimeout(2000); // Additional wait for table to stabilize + console.log("Table is visible"); + + // Get initial user count + const initialUserCount = await page.locator("tbody tr").count(); + console.log(`Initial user count: ${initialUserCount}`); + + // Click the filter button to show additional filters + const filterButton = page.getByRole("button", { + name: "Filters", + exact: true, + }); + await filterButton.click(); + console.log("Clicked filter button"); + await page.waitForTimeout(500); // Wait for filters to appear + + // Test user ID filter + const userIdInput = page.locator('input[placeholder="Filter by User ID"]'); + await expect(userIdInput).toBeVisible(); + console.log("User ID filter is visible"); + + await userIdInput.fill("user"); + console.log("Filled user ID filter"); + await page.waitForTimeout(1000); + const userIdFilteredCount = await page.locator("tbody tr").count(); + console.log(`User ID filtered count: ${userIdFilteredCount}`); + expect(userIdFilteredCount).toBeLessThan(initialUserCount); + + // Clear user ID filter + await userIdInput.clear(); + await page.waitForTimeout(1000); + console.log("Cleared user ID filter"); + + // Test SSO user ID filter + const ssoUserIdInput = page.locator('input[placeholder="Filter by SSO ID"]'); + await expect(ssoUserIdInput).toBeVisible(); + console.log("SSO user ID filter is visible"); + + await ssoUserIdInput.fill("sso"); + console.log("Filled SSO user ID filter"); + await page.waitForTimeout(1000); + const ssoUserIdFilteredCount = await page.locator("tbody tr").count(); + console.log(`SSO user ID filtered count: ${ssoUserIdFilteredCount}`); + expect(ssoUserIdFilteredCount).toBeLessThan(initialUserCount); + + // Clear SSO user ID filter + await ssoUserIdInput.clear(); + await page.waitForTimeout(5000); + console.log("Cleared SSO user ID filter"); + + // Verify count returns to initial after clearing all filters + const finalUserCount = await page.locator("tbody tr").count(); + console.log(`Final user count: ${finalUserCount}`); + expect(finalUserCount).toBe(initialUserCount); +}); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts index 4d27a4a7ce..bb6df91c39 100644 --- a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_internal_user.spec.ts @@ -2,45 +2,74 @@ Test view internal user page */ -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test('view internal user page', async ({ page }) => { +test("view internal user page", async ({ page }) => { // Go to the specified URL - await page.goto('http://localhost:4000/ui'); + await page.goto("http://localhost:4000/ui"); // Enter "admin" in the username input field - await page.fill('input[name="username"]', 'admin'); + await page.fill('input[name="username"]', "admin"); // Enter "gm" in the password input field - await page.fill('input[name="password"]', 'gm'); + await page.fill('input[name="password"]', "gm"); - // Optionally, you can add an assertion to verify the login button is enabled + // Click the login button const loginButton = page.locator('input[type="submit"]'); await expect(loginButton).toBeEnabled(); - - // Optionally, you can click the login button to submit the form await loginButton.click(); - const tabElement = page.locator('span.ant-menu-title-content', { hasText: 'Internal User' }); + // Wait for the Internal User tab and click it + const tabElement = page.locator("span.ant-menu-title-content", { + hasText: "Internal User", + }); await tabElement.click(); - // try to click on button - // - // wait 1-2 seconds - await page.waitForTimeout(10000); + // Wait for the table to load + await page.waitForSelector("tbody tr", { timeout: 10000 }); + await page.waitForTimeout(2000); // Additional wait for table to stabilize - // Test all expected fields are present - // number of keys owned by user - const keysBadges = page.locator('p.tremor-Badge-text.text-sm.whitespace-nowrap', { hasText: 'Keys' }); - const keysCountArray = await keysBadges.evaluateAll(elements => elements.map(el => parseInt(el.textContent.split(' ')[0], 10))); + // Test all expected fields are present + // number of keys owned by user + const keysBadges = page.locator( + "p.tremor-Badge-text.text-sm.whitespace-nowrap", + { hasText: "Keys" } + ); + const keysCountArray = await keysBadges.evaluateAll((elements) => + elements.map((el) => { + const text = el.textContent; + return text ? parseInt(text.split(" ")[0], 10) : 0; + }) + ); - const hasNonZeroKeys = keysCountArray.some(count => count > 0); + const hasNonZeroKeys = keysCountArray.some((count) => count > 0); expect(hasNonZeroKeys).toBe(true); // test pagination - const prevButton = page.locator('button.px-3.py-1.text-sm.border.rounded-md.hover\\:bg-gray-50.disabled\\:opacity-50.disabled\\:cursor-not-allowed', { hasText: 'Previous' }); - await expect(prevButton).toBeDisabled(); + // Wait for pagination controls to be visible + await page.waitForSelector(".flex.justify-between.items-center", { + timeout: 5000, + }); - const nextButton = page.locator('button.px-3.py-1.text-sm.border.rounded-md.hover\\:bg-gray-50.disabled\\:opacity-50.disabled\\:cursor-not-allowed', { hasText: 'Next' }); - await expect(nextButton).toBeEnabled(); + // Check if we're on the first page by looking at the results count + const resultsText = + (await page.locator(".text-sm.text-gray-700").textContent()) || ""; + const isFirstPage = resultsText.includes("1 -"); + + if (isFirstPage) { + // On first page, previous button should be disabled + const prevButton = page.locator("button", { hasText: "Previous" }); + await expect(prevButton).toBeDisabled(); + } + + // Next button should be enabled if there are more pages + const nextButton = page.locator("button", { hasText: "Next" }); + const totalResults = + (await page.locator(".text-sm.text-gray-700").textContent()) || ""; + const hasMorePages = + totalResults.includes("of") && !totalResults.includes("1 - 25 of 25"); + + if (hasMorePages) { + await expect(nextButton).toBeEnabled(); + } }); diff --git a/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts new file mode 100644 index 0000000000..cc138f0946 --- /dev/null +++ b/tests/proxy_admin_ui_tests/e2e_ui_tests/view_user_info.spec.ts @@ -0,0 +1,82 @@ +import { test, expect } from "@playwright/test"; +import { loginToUI } from "../utils/login"; + +test.describe("User Info View", () => { + test.beforeEach(async ({ page }) => { + await loginToUI(page); + // Navigate to users page + await page.goto("http://localhost:4000/ui?page=users"); + }); + + test("should display user info when clicking on user ID", async ({ + page, + }) => { + // Wait for users table to load + await page.waitForSelector("table"); + + // Get the first user ID cell + const firstUserIdCell = page.locator( + "table tbody tr:first-child td:first-child" + ); + const userId = await firstUserIdCell.textContent(); + console.log("Found user ID:", userId); + + // Click on the user ID + await firstUserIdCell.click(); + + // Wait for user info view to load + await page.waitForSelector('h1:has-text("User")'); + console.log("User info view loaded"); + + // Check for tabs + await expect(page.locator('button:has-text("Overview")')).toBeVisible(); + await expect(page.locator('button:has-text("Details")')).toBeVisible(); + + // Switch to details tab + await page.locator('button:has-text("Details")').click(); + + // Check details section + await expect(page.locator("text=User ID")).toBeVisible(); + await expect(page.locator("text=Email")).toBeVisible(); + + // Go back to users list + await page.locator('button:has-text("Back to Users")').click(); + + // Verify we're back on the users page + await expect(page.locator('h1:has-text("Users")')).toBeVisible(); + }); + + // test("should handle user deletion", async ({ page }) => { + // // Wait for users table to load + // await page.waitForSelector("table"); + + // // Get the first user ID cell + // const firstUserIdCell = page.locator( + // "table tbody tr:first-child td:first-child" + // ); + // const userId = await firstUserIdCell.textContent(); + + // // Click on the user ID + // await firstUserIdCell.click(); + + // // Wait for user info view to load + // await page.waitForSelector('h1:has-text("User")'); + + // // Click delete button + // await page.locator('button:has-text("Delete User")').click(); + + // // Confirm deletion in modal + // await page.locator('button:has-text("Delete")').click(); + + // // Verify success message + // await expect(page.locator("text=User deleted successfully")).toBeVisible(); + + // // Verify we're back on the users page + // await expect(page.locator('h1:has-text("Users")')).toBeVisible(); + + // // Verify user is no longer in the table + // if (userId) { + // await expect(page.locator(`text=${userId}`)).not.toBeVisible(); + // } + // }); +}); diff --git a/tests/proxy_admin_ui_tests/test-results/.last-run.json b/tests/proxy_admin_ui_tests/test-results/.last-run.json new file mode 100644 index 0000000000..cbcc1fbac1 --- /dev/null +++ b/tests/proxy_admin_ui_tests/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/tests/proxy_admin_ui_tests/test_sso_sign_in.py b/tests/proxy_admin_ui_tests/test_sso_sign_in.py index 3d5dd9ffcc..5de198b04b 100644 --- a/tests/proxy_admin_ui_tests/test_sso_sign_in.py +++ b/tests/proxy_admin_ui_tests/test_sso_sign_in.py @@ -104,7 +104,7 @@ async def test_auth_callback_new_user(mock_google_sso, mock_env_vars, prisma_cli # Assert the response assert response.status_code == 303 - assert response.headers["location"].startswith(f"/ui/?userID={unique_user_id}") + assert response.headers["location"].startswith(f"/ui/?login=success") # Verify that the user was added to the database user = await prisma_client.db.litellm_usertable.find_first( @@ -177,7 +177,7 @@ async def test_auth_callback_new_user_with_sso_default( # Assert the response assert response.status_code == 303 - assert response.headers["location"].startswith(f"/ui/?userID={unique_user_id}") + assert response.headers["location"].startswith(f"/ui/?login=success") # Verify that the user was added to the database user = await prisma_client.db.litellm_usertable.find_first( diff --git a/tests/proxy_admin_ui_tests/utils/login.ts b/tests/proxy_admin_ui_tests/utils/login.ts new file mode 100644 index 0000000000..e875508997 --- /dev/null +++ b/tests/proxy_admin_ui_tests/utils/login.ts @@ -0,0 +1,23 @@ +import { Page, expect } from "@playwright/test"; + +export async function loginToUI(page: Page) { + // Login first + await page.goto("http://localhost:4000/ui"); + console.log("Navigated to login page"); + + // Wait for login form to be visible + await page.waitForSelector('input[name="username"]', { timeout: 10000 }); + console.log("Login form is visible"); + + await page.fill('input[name="username"]', "admin"); + await page.fill('input[name="password"]', "gm"); + console.log("Filled login credentials"); + + const loginButton = page.locator('input[type="submit"]'); + await expect(loginButton).toBeEnabled(); + await loginButton.click(); + console.log("Clicked login button"); + + // Wait for navigation to complete + await page.waitForURL("**/*"); +} diff --git a/tests/proxy_unit_tests/example_config_yaml/azure_config.yaml b/tests/proxy_unit_tests/example_config_yaml/azure_config.yaml index fd5865cd7c..111813c884 100644 --- a/tests/proxy_unit_tests/example_config_yaml/azure_config.yaml +++ b/tests/proxy_unit_tests/example_config_yaml/azure_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: gpt-4-team1 litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://openai-gpt-4-test-v-1.openai.azure.com/ api_version: "2023-05-15" api_key: os.environ/AZURE_API_KEY diff --git a/tests/proxy_unit_tests/test_configs/test_bad_config.yaml b/tests/proxy_unit_tests/test_configs/test_bad_config.yaml index 7c802a8408..0a16ecb3c5 100644 --- a/tests/proxy_unit_tests/test_configs/test_bad_config.yaml +++ b/tests/proxy_unit_tests/test_configs/test_bad_config.yaml @@ -5,12 +5,12 @@ model_list: model: gpt-3.5-turbo - model_name: working-azure-gpt-3.5-turbo litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: os.environ/AZURE_API_KEY - model_name: azure-gpt-3.5-turbo litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: bad-key - model_name: azure-embedding diff --git a/tests/proxy_unit_tests/test_configs/test_cloudflare_azure_with_cache_config.yaml b/tests/proxy_unit_tests/test_configs/test_cloudflare_azure_with_cache_config.yaml index c3c3cb1c32..aeadbeb872 100644 --- a/tests/proxy_unit_tests/test_configs/test_cloudflare_azure_with_cache_config.yaml +++ b/tests/proxy_unit_tests/test_configs/test_cloudflare_azure_with_cache_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: azure-cloudflare litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: https://gateway.ai.cloudflare.com/v1/0399b10e77ac6668c80404a5ff49eb37/litellm-test/azure-openai/openai-gpt-4-test-v-1 api_key: os.environ/AZURE_API_KEY api_version: 2023-07-01-preview diff --git a/tests/proxy_unit_tests/test_configs/test_config_no_auth.yaml b/tests/proxy_unit_tests/test_configs/test_config_no_auth.yaml index 1c5ddf2266..075bf7a09d 100644 --- a/tests/proxy_unit_tests/test_configs/test_config_no_auth.yaml +++ b/tests/proxy_unit_tests/test_configs/test_config_no_auth.yaml @@ -12,7 +12,7 @@ model_list: - litellm_params: api_base: https://gateway.ai.cloudflare.com/v1/0399b10e77ac6668c80404a5ff49eb37/litellm-test/azure-openai/openai-gpt-4-test-v-1 api_key: os.environ/AZURE_API_KEY - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 model_name: azure-cloudflare-model - litellm_params: api_base: https://openai-france-1234.openai.azure.com diff --git a/tests/proxy_unit_tests/test_configs/test_custom_logger.yaml b/tests/proxy_unit_tests/test_configs/test_custom_logger.yaml index 145c618edd..2ad500b36f 100644 --- a/tests/proxy_unit_tests/test_configs/test_custom_logger.yaml +++ b/tests/proxy_unit_tests/test_configs/test_custom_logger.yaml @@ -1,7 +1,7 @@ model_list: - model_name: Azure OpenAI GPT-4 Canada litellm_params: - model: azure/chatgpt-v-2 + model: azure/chatgpt-v-3 api_base: os.environ/AZURE_API_BASE api_key: os.environ/AZURE_API_KEY api_version: "2023-07-01-preview" diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index d904de13b4..9b8d3543bc 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -1546,7 +1546,7 @@ def test_call_with_key_over_budget(prisma_client): ) await proxy_db_logger._PROXY_track_cost_callback( kwargs={ - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "stream": False, "litellm_params": { "metadata": { @@ -1578,10 +1578,10 @@ def test_call_with_key_over_budget(prisma_client): assert spend_log.request_id == request_id assert spend_log.spend == float("2e-05") - assert spend_log.model == "chatgpt-v-2" + assert spend_log.model == "chatgpt-v-3" assert ( spend_log.cache_key - == "c891d64397a472e6deb31b87a5ac4d3ed5b2dcc069bc87e2afe91e6d64e95a1e" + == "509ba0554a7129ae4f4fd13d11c141acce5549bb6aaf1f629ed543101615658e" ) # use generated key to auth in @@ -1669,7 +1669,7 @@ def test_call_with_key_over_budget_no_cache(prisma_client): proxy_db_logger = _ProxyDBLogger() await proxy_db_logger._PROXY_track_cost_callback( kwargs={ - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "stream": False, "litellm_params": { "metadata": { @@ -1702,10 +1702,10 @@ def test_call_with_key_over_budget_no_cache(prisma_client): assert spend_log.request_id == request_id assert spend_log.spend == float("2e-05") - assert spend_log.model == "chatgpt-v-2" + assert spend_log.model == "chatgpt-v-3" assert ( spend_log.cache_key - == "c891d64397a472e6deb31b87a5ac4d3ed5b2dcc069bc87e2afe91e6d64e95a1e" + == "509ba0554a7129ae4f4fd13d11c141acce5549bb6aaf1f629ed543101615658e" ) # use generated key to auth in @@ -1757,7 +1757,7 @@ async def test_call_with_key_over_model_budget( try: - # set budget for chatgpt-v-2 to 0.000001, expect the next request to fail + # set budget for chatgpt-v-3 to 0.000001, expect the next request to fail model_max_budget = { "gpt-4o-mini": { "budget_limit": "0.000001", @@ -1898,7 +1898,7 @@ async def test_call_with_key_never_over_budget(prisma_client): ) await proxy_db_logger._PROXY_track_cost_callback( kwargs={ - "model": "chatgpt-v-2", + "model": "chatgpt-v-3", "stream": False, "litellm_params": { "metadata": { @@ -1987,7 +1987,7 @@ async def test_call_with_key_over_budget_stream(prisma_client): await proxy_db_logger._PROXY_track_cost_callback( kwargs={ "call_type": "acompletion", - "model": "sagemaker-chatgpt-v-2", + "model": "sagemaker-chatgpt-v-3", "stream": True, "complete_streaming_response": resp, "litellm_params": { @@ -2431,7 +2431,7 @@ async def track_cost_callback_helper_fn(generated_key: str, user_id: str): await proxy_db_logger._PROXY_track_cost_callback( kwargs={ "call_type": "acompletion", - "model": "sagemaker-chatgpt-v-2", + "model": "sagemaker-chatgpt-v-3", "stream": True, "complete_streaming_response": resp, "litellm_params": { diff --git a/tests/proxy_unit_tests/test_proxy_custom_logger.py b/tests/proxy_unit_tests/test_proxy_custom_logger.py index ad60335152..bdad7c9d7d 100644 --- a/tests/proxy_unit_tests/test_proxy_custom_logger.py +++ b/tests/proxy_unit_tests/test_proxy_custom_logger.py @@ -164,7 +164,7 @@ def test_chat_completion(client): my_custom_logger.async_success == True ) # checks if the status of async_success is True, only the async_log_success_event can set this to true assert ( - my_custom_logger.async_completion_kwargs["model"] == "chatgpt-v-2" + my_custom_logger.async_completion_kwargs["model"] == "chatgpt-v-3" ) # checks if kwargs passed to async_log_success_event are correct print( "\n\n Custom Logger Async Completion args", diff --git a/tests/proxy_unit_tests/test_proxy_pass_user_config.py b/tests/proxy_unit_tests/test_proxy_pass_user_config.py index 12def1160f..3ecc252264 100644 --- a/tests/proxy_unit_tests/test_proxy_pass_user_config.py +++ b/tests/proxy_unit_tests/test_proxy_pass_user_config.py @@ -64,7 +64,7 @@ def test_chat_completion(client_no_auth): ModelConfig( model_name="user-azure-instance", litellm_params=CompletionRequest( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", api_key=os.getenv("AZURE_API_KEY"), api_version=os.getenv("AZURE_API_VERSION"), api_base=os.getenv("AZURE_API_BASE"), diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index 68f4ff8ec4..dda39d2bd5 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -446,7 +446,7 @@ def test_chat_completion_azure(mock_acompletion, client_no_auth): try: # Your test data test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -457,7 +457,7 @@ def test_chat_completion_azure(mock_acompletion, client_no_auth): response = client_no_auth.post("/v1/chat/completions", json=test_data) mock_acompletion.assert_called_once_with( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -489,19 +489,19 @@ def test_openai_deployments_model_chat_completions_azure( try: # Your test data test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], "max_tokens": 10, } - url = "/openai/deployments/azure/chatgpt-v-2/chat/completions" + url = "/openai/deployments/azure/chatgpt-v-3/chat/completions" print(f"testing proxy server with Azure Request {url}") response = client_no_auth.post(url, json=test_data) mock_acompletion.assert_called_once_with( - model="azure/chatgpt-v-2", + model="azure/chatgpt-v-3", messages=[ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -1314,7 +1314,7 @@ async def test_add_callback_via_key(prisma_client): try: # Your test data test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -1408,7 +1408,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils( request._url = URL(url="/chat/completions") test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -1423,7 +1423,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils( data = { "data": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [{"role": "user", "content": "write 1 sentence poem"}], "max_tokens": 10, "mock_response": "Hello world", @@ -1523,7 +1523,7 @@ async def test_disable_fallbacks_by_key(disable_fallbacks_set): key_metadata = {"disable_fallbacks": disable_fallbacks_set} existing_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [{"role": "user", "content": "write 1 sentence poem"}], } data = LiteLLMProxyRequestSetup.add_key_level_controls( @@ -1564,7 +1564,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils_gcs_bucket( request._url = URL(url="/chat/completions") test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -1579,7 +1579,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils_gcs_bucket( data = { "data": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [{"role": "user", "content": "write 1 sentence poem"}], "max_tokens": 10, "mock_response": "Hello world", @@ -1697,7 +1697,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils_langsmith( request._url = URL(url="/chat/completions") test_data = { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [ {"role": "user", "content": "write 1 sentence poem"}, ], @@ -1712,7 +1712,7 @@ async def test_add_callback_via_key_litellm_pre_call_utils_langsmith( data = { "data": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "messages": [{"role": "user", "content": "write 1 sentence poem"}], "max_tokens": 10, "mock_response": "Hello world", diff --git a/tests/proxy_unit_tests/test_proxy_server_keys.py b/tests/proxy_unit_tests/test_proxy_server_keys.py index 6eb41202cd..8b8e943ba7 100644 --- a/tests/proxy_unit_tests/test_proxy_server_keys.py +++ b/tests/proxy_unit_tests/test_proxy_server_keys.py @@ -171,7 +171,7 @@ # model_data = { # "model_name": "azure-model", # "litellm_params": { -# "model": "azure/chatgpt-v-2", +# "model": "azure/chatgpt-v-3", # "api_key": os.getenv("AZURE_API_KEY"), # "api_base": os.getenv("AZURE_API_BASE"), # "api_version": os.getenv("AZURE_API_VERSION") diff --git a/tests/store_model_in_db_tests/test_adding_passthrough_model.py b/tests/store_model_in_db_tests/test_adding_passthrough_model.py index ad26e19bd6..e901be5bd7 100644 --- a/tests/store_model_in_db_tests/test_adding_passthrough_model.py +++ b/tests/store_model_in_db_tests/test_adding_passthrough_model.py @@ -142,7 +142,7 @@ def create_virtual_key(): json={}, ) print(response.json()) - return response.json()["token"] + return response.json()["key"] def add_assembly_ai_model_to_db( diff --git a/tests/test_keys.py b/tests/test_keys.py index eaf9369d89..89b54ba92c 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -109,6 +109,18 @@ async def test_key_gen(): await asyncio.gather(*tasks) +@pytest.mark.asyncio +async def test_simple_key_gen(): + async with aiohttp.ClientSession() as session: + key_data = await generate_key(session, i=0) + key = key_data["key"] + assert key_data["token"] is not None + assert key_data["token"] != key + assert key_data["token_id"] is not None + assert key_data["created_at"] is not None + assert key_data["updated_at"] is not None + + @pytest.mark.asyncio async def test_key_gen_bad_key(): """ diff --git a/tests/test_models.py b/tests/test_models.py index 31e564a829..89944c07b3 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -67,7 +67,7 @@ async def add_models(session, model_id="123", model_name="azure-gpt-3.5", key="s data = { "model_name": model_name, "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "os.environ/AZURE_API_KEY", "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "api_version": "2023-05-15", @@ -100,7 +100,7 @@ async def update_model(session, model_id="123", model_name="azure-gpt-3.5", key= data = { "model_name": model_name, "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": "os.environ/AZURE_API_KEY", "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "api_version": "2023-05-15", @@ -292,7 +292,7 @@ async def add_model_for_health_checking(session, model_id="123"): data = { "model_name": f"azure-model-health-check-{model_id}", "litellm_params": { - "model": "azure/chatgpt-v-2", + "model": "azure/chatgpt-v-3", "api_key": os.getenv("AZURE_API_KEY"), "api_base": "https://openai-gpt-4-test-v-1.openai.azure.com/", "api_version": "2023-05-15", @@ -417,7 +417,7 @@ async def test_add_model_run_health(): assert _health_info["healthy_count"] == 1 assert ( - _healthy_endpooint["model"] == "azure/chatgpt-v-2" + _healthy_endpooint["model"] == "azure/chatgpt-v-3" ) # this is the model that got added # assert httpx client is is unchanges diff --git a/ui/litellm-dashboard/out/404.html b/ui/litellm-dashboard/out/404.html index 39867f5d34..1e270efa76 100644 --- a/ui/litellm-dashboard/out/404.html +++ b/ui/litellm-dashboard/out/404.html @@ -1 +1 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file +404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/ui/litellm-dashboard/out/_next/static/chunks/117-87ec698bfca6820e.js b/ui/litellm-dashboard/out/_next/static/chunks/117-1c5bfc45bfc4237d.js similarity index 100% rename from ui/litellm-dashboard/out/_next/static/chunks/117-87ec698bfca6820e.js rename to ui/litellm-dashboard/out/_next/static/chunks/117-1c5bfc45bfc4237d.js diff --git a/ui/litellm-dashboard/out/_next/static/chunks/250-7b7f46d48724f856.js b/ui/litellm-dashboard/out/_next/static/chunks/250-7b7f46d48724f856.js deleted file mode 100644 index 31bb67f8c6..0000000000 --- a/ui/litellm-dashboard/out/_next/static/chunks/250-7b7f46d48724f856.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$I:function(){return K},AZ:function(){return D},Au:function(){return em},BL:function(){return eU},Br:function(){return b},E9:function(){return eZ},EB:function(){return te},EG:function(){return eX},EY:function(){return eK},Eb:function(){return C},FC:function(){return ei},Gh:function(){return eO},H1:function(){return v},H2:function(){return n},Hx:function(){return eg},I1:function(){return j},It:function(){return x},J$:function(){return ea},K8:function(){return d},K_:function(){return eY},LY:function(){return eI},Lp:function(){return eG},N3:function(){return eN},N8:function(){return ee},NL:function(){return e1},NV:function(){return f},Nc:function(){return ex},O3:function(){return eV},OD:function(){return e_},OU:function(){return eh},Of:function(){return S},Og:function(){return y},Ov:function(){return E},PT:function(){return X},Qg:function(){return eS},RQ:function(){return _},Rg:function(){return Q},Sb:function(){return eJ},So:function(){return et},TF:function(){return ta},Tj:function(){return e$},UM:function(){return e7},VA:function(){return G},Vt:function(){return eD},W_:function(){return V},X:function(){return er},XO:function(){return k},Xd:function(){return eT},Xm:function(){return F},YU:function(){return eL},Yo:function(){return J},Z9:function(){return R},Zr:function(){return m},a6:function(){return O},aC:function(){return to},ao:function(){return eq},b1:function(){return ed},cq:function(){return A},cu:function(){return eP},eH:function(){return Y},eZ:function(){return eF},fE:function(){return e8},fP:function(){return W},g:function(){return eQ},gX:function(){return eb},h3:function(){return es},hT:function(){return ej},hy:function(){return u},ix:function(){return H},j2:function(){return en},jA:function(){return eH},jE:function(){return ez},kK:function(){return p},kn:function(){return q},lP:function(){return h},lU:function(){return e2},lg:function(){return eE},mC:function(){return e6},mR:function(){return eo},mY:function(){return e5},m_:function(){return U},mp:function(){return eM},n$:function(){return ey},n9:function(){return e9},nd:function(){return e3},o6:function(){return $},oC:function(){return eC},ol:function(){return z},pf:function(){return eR},qI:function(){return g},qk:function(){return e0},qm:function(){return w},r1:function(){return tt},r6:function(){return B},rs:function(){return N},s0:function(){return L},sN:function(){return ev},t$:function(){return P},t0:function(){return ek},t3:function(){return eW},tB:function(){return e4},tN:function(){return el},u5:function(){return ec},um:function(){return eB},v9:function(){return ef},vh:function(){return eA},wX:function(){return T},wd:function(){return ew},xA:function(){return eu},xX:function(){return I},zg:function(){return ep}});var a=o(20347),r=o(41021);let n=null;console.log=function(){};let c=0,s=e=>new Promise(t=>setTimeout(t,e)),i=async e=>{let t=Date.now();t-c>6e4?(e.includes("Authentication Error - Expired Key")&&(r.ZP.info("UI Session Expired. Logging out."),c=t,await s(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),c=t):console.log("Error suppressed to prevent spam:",e)},l="Authorization";function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),l=e}let h=async()=>{let e=n?"".concat(n,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},w=async e=>{try{let t=n?"".concat(n,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}}),a=await o.json();return console.log("received litellm model cost data: ".concat(a)),a}catch(e){throw console.error("Failed to get model cost map:",e),e}},p=async(e,t)=>{try{let o=n?"".concat(n,"/model/new"):"/model/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text()||"Network response was not ok";throw r.ZP.error(e),Error(e)}let c=await a.json();return console.log("API Response:",c),r.ZP.destroy(),r.ZP.success("Model ".concat(t.model_name," created successfully"),2),c}catch(e){throw console.error("Failed to create key:",e),e}},u=async e=>{try{let t=n?"".concat(n,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){console.error("Failed to get model settings:",e)}},y=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=n?"".concat(n,"/model/delete"):"/model/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=n?"".concat(n,"/budget/delete"):"/budget/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/new"):"/budget/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/update"):"/budget/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},k=async(e,t)=>{try{let o=n?"".concat(n,"/invitation/new"):"/invitation/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},_=async e=>{try{let t=n?"".concat(n,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/key/generate"):"/key/generate",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.auto_create_key=!1,o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/user/new"):"/user/new",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=n?"".concat(n,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},C=async(e,t)=>{try{let o=n?"".concat(n,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete user(s):",e),e}},N=async(e,t)=>{try{let o=n?"".concat(n,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},S=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;try{let r=n?"".concat(n,"/user/list"):"/user/list";console.log("in userListCall");let c=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");c.append("user_ids",e)}o&&c.append("page",o.toString()),a&&c.append("page_size",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log("/user/list API Response:",h),h}catch(e){throw console.error("Failed to create key:",e),e}},b=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0;try{let s;if(a){s=n?"".concat(n,"/user/list"):"/user/list";let e=new URLSearchParams;null!=r&&e.append("page",r.toString()),null!=c&&e.append("page_size",c.toString()),s+="?".concat(e.toString())}else s=n?"".concat(n,"/user/info"):"/user/info","Admin"===o||"Admin Viewer"===o||t&&(s+="?user_id=".concat(t));console.log("Requesting user data from:",s);let d=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log("API Response:",h),h}catch(e){throw console.error("Failed to fetch user data:",e),e}},F=async(e,t)=>{try{let o=n?"".concat(n,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},x=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let a=n?"".concat(n,"/team/list"):"/team/list";console.log("in teamInfoCall");let r=new URLSearchParams;o&&r.append("user_id",o.toString()),t&&r.append("organization_id",t.toString());let c=r.toString();c&&(a+="?".concat(c));let s=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},O=async e=>{try{let t=n?"".concat(n,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/team/available_teams API Response:",a),a}catch(e){throw e}},B=async e=>{try{let t=n?"".concat(n,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{let o=n?"".concat(n,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/organization/new"):"/organization/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=n?"".concat(n,"/organization/update"):"/organization/update",a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t)=>{try{let o=n?"".concat(n,"/organization/delete"):"/organization/delete",a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Error deleting organization: ".concat(e))}return await a.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async(e,t)=>{try{let o=n?"".concat(n,"/utils/transform_request"):"/utils/transform_request",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},I=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;try{let r=n?"".concat(n,"/user/daily/activity"):"/user/daily/activity",c=new URLSearchParams;c.append("start_date",t.toISOString()),c.append("end_date",o.toISOString()),c.append("page_size","1000"),c.append("page",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}return await d.json()}catch(e){throw console.error("Failed to create key:",e),e}},R=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/tag/daily/activity"):"/tag/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("tags",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},z=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/team/daily/activity"):"/team/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("team_ids",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},V=async e=>{try{let t=n?"".concat(n,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},U=async(e,t,o,a)=>{let r=n?"".concat(n,"/onboarding/claim_token"):"/onboarding/claim_token";try{let n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:a})});if(!n.ok){let e=await n.text();throw i(e),Error("Network response was not ok")}let c=await n.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},L=async(e,t,o)=>{try{let a=n?"".concat(n,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},M=!1,Z=null,D=async(e,t,o)=>{try{console.log("modelInfoCall:",e,t,o);let c=n?"".concat(n,"/v2/model/info"):"/v2/model/info";a.ZL.includes(o)||(c+="?user_models_only=true");let s=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw e+="error shown=".concat(M),M||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),r.ZP.info(e,10),M=!0,Z&&clearTimeout(Z),Z=setTimeout(()=>{M=!1},1e4)),Error("Network response was not ok")}let i=await s.json();return console.log("modelInfoCall:",i),i}catch(e){throw console.error("Failed to create key:",e),e}},H=async(e,t)=>{try{let o=n?"".concat(n,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");let r=await a.json();return console.log("modelInfoV1Call:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},q=async e=>{try{let t=n?"".concat(n,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("modelHubCall:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},X=async e=>{try{let t=n?"".concat(n,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let a=await o.json();return console.log("getAllowedIPs:",a),a.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},Y=async(e,t)=>{try{let o=n?"".concat(n,"/add/allowed_ip"):"/add/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("addAllowedIP:",r),r}catch(e){throw console.error("Failed to add allowed IP:",e),e}},K=async(e,t)=>{try{let o=n?"".concat(n,"/delete/allowed_ip"):"/delete/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("deleteAllowedIP:",r),r}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},$=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics"):"/model/metrics";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},Q=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(r="".concat(r,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(a));let c=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},W=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},ee=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/exceptions"):"/model/metrics/exceptions";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},et=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",l);try{let t=n?"".concat(n,"/models"):"/models",o=new URLSearchParams;!0===a&&o.append("return_wildcard_routes","True"),r&&o.append("team_id",r.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},eo=async e=>{try{let t=n?"".concat(n,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ea=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/tags"):"/global/spend/tags";t&&o&&(r="".concat(r,"?start_date=").concat(t,"&end_date=").concat(o)),a&&(r+="".concat(r,"&tags=").concat(a.join(","))),console.log("in tagsSpendLogsCall:",r);let c=await fetch("".concat(r),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},er=async e=>{try{let t=n?"".concat(n,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},en=async e=>{try{let t=n?"".concat(n,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t)=>{try{let o=n?"".concat(n,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},es=async(e,t,o,a,r,c,s,d,h)=>{try{let w=n?"".concat(n,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),a&&p.append("request_id",a),r&&p.append("start_date",r),c&&p.append("end_date",c),s&&p.append("page",s.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw i(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},ei=async e=>{try{let t=n?"".concat(n,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},el=async e=>{try{let t=n?"".concat(n,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ed=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:a}):JSON.stringify({startTime:o,endTime:a});let s={method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(r,s);if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},eh=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/provider"):"/global/spend/provider";o&&a&&(r+="?start_date=".concat(o,"&end_date=").concat(a)),t&&(r+="&api_key=".concat(t));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity"):"/global/activity";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ep=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eu=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/model"):"/global/activity/model";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ey=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ef=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},em=async e=>{try{let t=n?"".concat(n,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},eg=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let r=n?"".concat(n,"/health/test_connection"):"/health/test_connection",c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",[l]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let i=await c.json();if(!c.ok||"error"===i.status){if("error"===i.status);else{var a;return{status:"error",message:(null===(a=i.error)||void 0===a?void 0:a.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return i}catch(e){throw console.error("Model connection test error:",e),e}},ek=async(e,t)=>{try{console.log("entering keyInfoV1Call");let o=n?"".concat(n,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(console.log("response",a),!a.ok){let e=await a.text();i(e),r.ZP.error("Failed to fetch key info - "+e)}let c=await a.json();return console.log("data",c),c}catch(e){throw console.error("Failed to fetch key info:",e),e}},e_=async(e,t,o,a,r,c)=>{try{let s=n?"".concat(n,"/key/list"):"/key/list";console.log("in keyListCall");let d=new URLSearchParams;o&&d.append("team_id",o.toString()),t&&d.append("organization_id",t.toString()),a&&d.append("key_alias",a),r&&d.append("page",r.toString()),c&&d.append("size",c.toString()),d.append("return_full_object","true"),d.append("include_team_keys","true");let h=d.toString();h&&(s+="?".concat(h));let w=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!w.ok){let e=await w.text();throw i(e),Error("Network response was not ok")}let p=await w.json();return console.log("/team/list API Response:",p),p}catch(e){throw console.error("Failed to create key:",e),e}},eT=async(e,t)=>{try{let o=n?"".concat(n,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to get requested models:",e),e}},eE=async e=>{try{let t=n?"".concat(n,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("response from user/available_role",a),a}catch(e){throw e}},ej=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/team/new"):"/team/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eC=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/credentials"):"/credentials",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eN=async e=>{try{let t=n?"".concat(n,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/credentials API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eS=async(e,t,o)=>{try{let a=n?"".concat(n,"/credentials"):"/credentials";t?a+="/by_name/".concat(t):o&&(a+="/by_model/".concat(o)),console.log("in credentialListCall");let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t)=>{try{let o=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},eF=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let a=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ex=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=n?"".concat(n,"/key/update"):"/key/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update key Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eO=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=n?"".concat(n,"/team/update"):"/team/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t)=>{try{console.log("Form Values in modelUpateCall:",t);let o=n?"".concat(n,"/model/update"):"/model/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update model Response:",r),r}catch(e){throw console.error("Failed to update model:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_add"):"/team/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_update"):"/team/member_update",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eG=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_delete"):"/team/member_delete",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eA=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/organization/member_add"):"/organization/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eJ=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let a=n?"".concat(n,"/organization/member_delete"):"/organization/member_delete",r=await fetch(a,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},eI=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let a=n?"".concat(n,"/organization/member_update"):"/organization/member_update",r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},eR=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let a=n?"".concat(n,"/user/update"):"/user/update",r={...t};null!==o&&(r.user_role=o),r=JSON.stringify(r);let c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:r});if(!c.ok){let e=await c.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let s=await c.json();return console.log("API Response:",s),s}catch(e){throw console.error("Failed to create key:",e),e}},ez=async(e,t)=>{try{let o=n?"".concat(n,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error(e)}let c=await a.json();return r.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eV=async e=>{try{let t=n?"".concat(n,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eU=async(e,t,o)=>{try{let t=n?"".concat(n,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eL=async e=>{try{let t=n?"".concat(n,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eM=async e=>{try{let t=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eZ=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eD=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eH=async(e,t,o)=>{try{let a=n?"".concat(n,"/config/field/update"):"/config/field/update",c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}let s=await c.json();return r.ZP.success("Successfully updated value!"),s}catch(e){throw console.error("Failed to set callbacks:",e),e}},eq=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/delete"):"/config/field/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return r.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eX=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eY=async(e,t)=>{try{let o=n?"".concat(n,"/config/update"):"/config/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eK=async e=>{try{let t=n?"".concat(n,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},e$=async e=>{try{let t=n?"".concat(n,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eQ=async e=>{try{let t=n?"".concat(n,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eW=async e=>{try{let t=n?"".concat(n,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Guardrails list response:",a),a}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},e0=async(e,t,o)=>{try{let a=n?"".concat(n,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",a);let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},e1=async e=>{try{let t=n?"".concat(n,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched SSO settings:",a),a}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},e3=async(e,t)=>{try{let o=n?"".concat(n,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated internal user settings:",c),r.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}},e2=async e=>{try{let t=n?"".concat(n,"/mcp/tools/list"):"/mcp/tools/list";console.log("Fetching MCP tools from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched MCP tools:",a),a}catch(e){throw console.error("Failed to fetch MCP tools:",e),e}},e4=async(e,t,o)=>{try{let a=n?"".concat(n,"/mcp/tools/call"):"/mcp/tools/call";console.log("Calling MCP tool:",t,"with arguments:",o);let r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({name:t,arguments:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("MCP tool call response:",c),c}catch(e){throw console.error("Failed to call MCP tool:",e),e}},e5=async(e,t)=>{try{let o=n?"".concat(n,"/tag/new"):"/tag/new",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error creating tag:",e),e}},e9=async(e,t)=>{try{let o=n?"".concat(n,"/tag/update"):"/tag/update",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error updating tag:",e),e}},e6=async(e,t)=>{try{let o=n?"".concat(n,"/tag/info"):"/tag/info",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({names:t})});if(!a.ok){let e=await a.text();return await i(e),{}}return await a.json()}catch(e){throw console.error("Error getting tag info:",e),e}},e7=async e=>{try{let t=n?"".concat(n,"/tag/list"):"/tag/list",o=await fetch(t,{method:"GET",headers:{Authorization:"Bearer ".concat(e)}});if(!o.ok){let e=await o.text();return await i(e),{}}return await o.json()}catch(e){throw console.error("Error listing tags:",e),e}},e8=async(e,t)=>{try{let o=n?"".concat(n,"/tag/delete"):"/tag/delete",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({name:t})});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error deleting tag:",e),e}},te=async e=>{try{let t=n?"".concat(n,"/get/default_team_settings"):"/get/default_team_settings";console.log("Fetching default team settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched default team settings:",a),a}catch(e){throw console.error("Failed to fetch default team settings:",e),e}},tt=async(e,t)=>{try{let o=n?"".concat(n,"/update/default_team_settings"):"/update/default_team_settings";console.log("Updating default team settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated default team settings:",c),r.ZP.success("Default team settings updated successfully"),c}catch(e){throw console.error("Failed to update default team settings:",e),e}},to=async(e,t)=>{try{let o=n?"".concat(n,"/team/permissions_list?team_id=").concat(t):"/team/permissions_list?team_id=".concat(t),a=await fetch(o,{method:"GET",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("Team permissions response:",r),r}catch(e){throw console.error("Failed to get team permissions:",e),e}},ta=async(e,t,o)=>{try{let a=n?"".concat(n,"/team/permissions_update"):"/team/permissions_update",r=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({team_id:t,team_member_permissions:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Team permissions response:",c),c}catch(e){throw console.error("Failed to update team permissions:",e),e}}},20347:function(e,t,o){o.d(t,{LQ:function(){return n},ZL:function(){return a},lo:function(){return r},tY:function(){return c}});let a=["Admin","Admin Viewer","proxy_admin","proxy_admin_viewer","org_admin"],r=["Internal User","Internal Viewer"],n=["Internal User","Admin"],c=e=>a.includes(e)}}]); \ No newline at end of file diff --git a/ui/litellm-dashboard/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js b/ui/litellm-dashboard/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js new file mode 100644 index 0000000000..40e2873306 --- /dev/null +++ b/ui/litellm-dashboard/out/_next/static/chunks/250-e4cc2ceb9ff1c37a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{19250:function(e,t,o){o.d(t,{$D:function(){return eP},$I:function(){return $},AZ:function(){return D},Au:function(){return em},BL:function(){return eL},Br:function(){return b},E9:function(){return eD},EB:function(){return tt},EG:function(){return eY},EY:function(){return eK},Eb:function(){return C},FC:function(){return ei},Gh:function(){return eB},H1:function(){return v},H2:function(){return n},Hx:function(){return ek},I1:function(){return j},It:function(){return x},J$:function(){return ea},K8:function(){return d},K_:function(){return e$},LY:function(){return eR},Lp:function(){return eA},N3:function(){return eS},N8:function(){return ee},NL:function(){return e3},NV:function(){return f},Nc:function(){return eO},O3:function(){return eU},OD:function(){return eT},OU:function(){return eh},Of:function(){return S},Og:function(){return y},Ov:function(){return E},PT:function(){return X},Qg:function(){return eb},RQ:function(){return _},Rg:function(){return Q},Sb:function(){return eI},So:function(){return et},TF:function(){return tr},Tj:function(){return eQ},UM:function(){return e8},VA:function(){return G},Vt:function(){return eH},W_:function(){return V},X:function(){return er},XO:function(){return k},Xd:function(){return eE},Xm:function(){return F},YU:function(){return eM},Yo:function(){return J},Z9:function(){return R},Zr:function(){return m},a6:function(){return O},aC:function(){return ta},ao:function(){return eX},b1:function(){return ed},cq:function(){return A},cu:function(){return ev},e2:function(){return eg},eH:function(){return Y},eZ:function(){return ex},fE:function(){return te},fP:function(){return W},g:function(){return eW},gX:function(){return eF},h3:function(){return es},hT:function(){return eC},hy:function(){return u},ix:function(){return H},j2:function(){return en},jA:function(){return eq},jE:function(){return eV},kK:function(){return p},kn:function(){return q},lP:function(){return h},lU:function(){return e4},lg:function(){return ej},mC:function(){return e7},mR:function(){return eo},mY:function(){return e6},m_:function(){return U},mp:function(){return eZ},n$:function(){return ey},n9:function(){return e9},nd:function(){return e2},o6:function(){return K},oC:function(){return eN},ol:function(){return z},pf:function(){return ez},qI:function(){return g},qk:function(){return e1},qm:function(){return w},r1:function(){return to},r6:function(){return B},rs:function(){return N},s0:function(){return L},sN:function(){return eG},t$:function(){return P},t0:function(){return e_},t3:function(){return e0},tB:function(){return e5},tN:function(){return el},u5:function(){return ec},v9:function(){return ef},vh:function(){return eJ},wX:function(){return T},wd:function(){return ew},xA:function(){return eu},xX:function(){return I},zg:function(){return ep}});var a=o(20347),r=o(41021);let n=null;console.log=function(){};let c=0,s=e=>new Promise(t=>setTimeout(t,e)),i=async e=>{let t=Date.now();t-c>6e4?(e.includes("Authentication Error - Expired Key")&&(r.ZP.info("UI Session Expired. Logging out."),c=t,await s(3e3),document.cookie="token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",window.location.href="/"),c=t):console.log("Error suppressed to prevent spam:",e)},l="Authorization";function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Authorization";console.log("setGlobalLitellmHeaderName: ".concat(e)),l=e}let h=async()=>{let e=n?"".concat(n,"/openapi.json"):"/openapi.json",t=await fetch(e);return await t.json()},w=async e=>{try{let t=n?"".concat(n,"/get/litellm_model_cost_map"):"/get/litellm_model_cost_map",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}}),a=await o.json();return console.log("received litellm model cost data: ".concat(a)),a}catch(e){throw console.error("Failed to get model cost map:",e),e}},p=async(e,t)=>{try{let o=n?"".concat(n,"/model/new"):"/model/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text()||"Network response was not ok";throw r.ZP.error(e),Error(e)}let c=await a.json();return console.log("API Response:",c),r.ZP.destroy(),r.ZP.success("Model ".concat(t.model_name," created successfully"),2),c}catch(e){throw console.error("Failed to create key:",e),e}},u=async e=>{try{let t=n?"".concat(n,"/model/settings"):"/model/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){console.error("Failed to get model settings:",e)}},y=async(e,t)=>{console.log("model_id in model delete call: ".concat(t));try{let o=n?"".concat(n,"/model/delete"):"/model/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},f=async(e,t)=>{if(console.log("budget_id in budget delete call: ".concat(t)),null!=e)try{let o=n?"".concat(n,"/budget/delete"):"/budget/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},m=async(e,t)=>{try{console.log("Form Values in budgetCreateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/new"):"/budget/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},g=async(e,t)=>{try{console.log("Form Values in budgetUpdateCall:",t),console.log("Form Values after check:",t);let o=n?"".concat(n,"/budget/update"):"/budget/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},k=async(e,t)=>{try{let o=n?"".concat(n,"/invitation/new"):"/invitation/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},_=async e=>{try{let t=n?"".concat(n,"/alerting/settings"):"/alerting/settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},T=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/key/generate"):"/key/generate",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},E=async(e,t,o)=>{try{if(console.log("Form Values in keyCreateCall:",o),o.description&&(o.metadata||(o.metadata={}),o.metadata.description=o.description,delete o.description,o.metadata=JSON.stringify(o.metadata)),o.auto_create_key=!1,o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}console.log("Form Values after check:",o);let a=n?"".concat(n,"/user/new"):"/user/new",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},j=async(e,t)=>{try{let o=n?"".concat(n,"/key/delete"):"/key/delete";console.log("in keyDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},C=async(e,t)=>{try{let o=n?"".concat(n,"/user/delete"):"/user/delete";console.log("in userDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({user_ids:t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete user(s):",e),e}},N=async(e,t)=>{try{let o=n?"".concat(n,"/team/delete"):"/team/delete";console.log("in teamDeleteCall:",t);let a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},S=async function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,c=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null,s=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,d=arguments.length>7&&void 0!==arguments[7]?arguments[7]:null,h=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null,w=arguments.length>9&&void 0!==arguments[9]?arguments[9]:null;try{let p=n?"".concat(n,"/user/list"):"/user/list";console.log("in userListCall");let u=new URLSearchParams;if(t&&t.length>0){let e=t.join(",");u.append("user_ids",e)}o&&u.append("page",o.toString()),a&&u.append("page_size",a.toString()),r&&u.append("user_email",r),c&&u.append("role",c),s&&u.append("team",s),d&&u.append("sso_user_ids",d),h&&u.append("sort_by",h),w&&u.append("sort_order",w);let y=u.toString();y&&(p+="?".concat(y));let f=await fetch(p,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!f.ok){let e=await f.text();throw i(e),Error("Network response was not ok")}let m=await f.json();return console.log("/user/list API Response:",m),m}catch(e){throw console.error("Failed to create key:",e),e}},b=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4?arguments[4]:void 0,c=arguments.length>5?arguments[5]:void 0,s=arguments.length>6&&void 0!==arguments[6]&&arguments[6];console.log("userInfoCall: ".concat(t,", ").concat(o,", ").concat(a,", ").concat(r,", ").concat(c,", ").concat(s));try{let d;if(a){d=n?"".concat(n,"/user/list"):"/user/list";let e=new URLSearchParams;null!=r&&e.append("page",r.toString()),null!=c&&e.append("page_size",c.toString()),d+="?".concat(e.toString())}else d=n?"".concat(n,"/user/info"):"/user/info",("Admin"!==o&&"Admin Viewer"!==o||s)&&t&&(d+="?user_id=".concat(t));console.log("Requesting user data from:",d);let h=await fetch(d,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}let w=await h.json();return console.log("API Response:",w),w}catch(e){throw console.error("Failed to fetch user data:",e),e}},F=async(e,t)=>{try{let o=n?"".concat(n,"/team/info"):"/team/info";t&&(o="".concat(o,"?team_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},x=async function(e,t){let o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;try{let a=n?"".concat(n,"/team/list"):"/team/list";console.log("in teamInfoCall");let r=new URLSearchParams;o&&r.append("user_id",o.toString()),t&&r.append("organization_id",t.toString());let c=r.toString();c&&(a+="?".concat(c));let s=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log("/team/list API Response:",d),d}catch(e){throw console.error("Failed to create key:",e),e}},O=async e=>{try{let t=n?"".concat(n,"/team/available"):"/team/available";console.log("in availableTeamListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/team/available_teams API Response:",a),a}catch(e){throw e}},B=async e=>{try{let t=n?"".concat(n,"/organization/list"):"/organization/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},P=async(e,t)=>{try{let o=n?"".concat(n,"/organization/info"):"/organization/info";t&&(o="".concat(o,"?organization_id=").concat(t)),console.log("in teamInfoCall");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},v=async(e,t)=>{try{if(console.log("Form Values in organizationCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw console.error("Failed to parse metadata:",e),Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/organization/new"):"/organization/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},G=async(e,t)=>{try{console.log("Form Values in organizationUpdateCall:",t);let o=n?"".concat(n,"/organization/update"):"/organization/update",a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},A=async(e,t)=>{try{let o=n?"".concat(n,"/organization/delete"):"/organization/delete",a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_ids:[t]})});if(!a.ok){let e=await a.text();throw i(e),Error("Error deleting organization: ".concat(e))}return await a.json()}catch(e){throw console.error("Failed to delete organization:",e),e}},J=async(e,t)=>{try{let o=n?"".concat(n,"/utils/transform_request"):"/utils/transform_request",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},I=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;try{let r=n?"".concat(n,"/user/daily/activity"):"/user/daily/activity",c=new URLSearchParams;c.append("start_date",t.toISOString()),c.append("end_date",o.toISOString()),c.append("page_size","1000"),c.append("page",a.toString());let s=c.toString();s&&(r+="?".concat(s));let d=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}return await d.json()}catch(e){throw console.error("Failed to create key:",e),e}},R=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/tag/daily/activity"):"/tag/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("tags",r.join(","));let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},z=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;try{let c=n?"".concat(n,"/team/daily/activity"):"/team/daily/activity",s=new URLSearchParams;s.append("start_date",t.toISOString()),s.append("end_date",o.toISOString()),s.append("page_size","1000"),s.append("page",a.toString()),r&&s.append("team_ids",r.join(",")),s.append("exclude_team_ids","litellm-dashboard");let d=s.toString();d&&(c+="?".concat(d));let h=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!h.ok){let e=await h.text();throw i(e),Error("Network response was not ok")}return await h.json()}catch(e){throw console.error("Failed to create key:",e),e}},V=async e=>{try{let t=n?"".concat(n,"/onboarding/get_token"):"/onboarding/get_token";t+="?invite_link=".concat(e);let o=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},U=async(e,t,o,a)=>{let r=n?"".concat(n,"/onboarding/claim_token"):"/onboarding/claim_token";try{let n=await fetch(r,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({invitation_link:t,user_id:o,password:a})});if(!n.ok){let e=await n.text();throw i(e),Error("Network response was not ok")}let c=await n.json();return console.log(c),c}catch(e){throw console.error("Failed to delete key:",e),e}},L=async(e,t,o)=>{try{let a=n?"".concat(n,"/key/").concat(t,"/regenerate"):"/key/".concat(t,"/regenerate"),r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Regenerate key Response:",c),c}catch(e){throw console.error("Failed to regenerate key:",e),e}},M=!1,Z=null,D=async(e,t,o)=>{try{console.log("modelInfoCall:",e,t,o);let c=n?"".concat(n,"/v2/model/info"):"/v2/model/info";a.ZL.includes(o)||(c+="?user_models_only=true");let s=await fetch(c,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!s.ok){let e=await s.text();throw e+="error shown=".concat(M),M||(e.includes("No model list passed")&&(e="No Models Exist. Click Add Model to get started."),r.ZP.info(e,10),M=!0,Z&&clearTimeout(Z),Z=setTimeout(()=>{M=!1},1e4)),Error("Network response was not ok")}let i=await s.json();return console.log("modelInfoCall:",i),i}catch(e){throw console.error("Failed to create key:",e),e}},H=async(e,t)=>{try{let o=n?"".concat(n,"/v1/model/info"):"/v1/model/info";o+="?litellm_model_id=".concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");let r=await a.json();return console.log("modelInfoV1Call:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},q=async e=>{try{let t=n?"".concat(n,"/model_group/info"):"/model_group/info",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("modelHubCall:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},X=async e=>{try{let t=n?"".concat(n,"/get/allowed_ips"):"/get/allowed_ips",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw Error("Network response was not ok: ".concat(e))}let a=await o.json();return console.log("getAllowedIPs:",a),a.data}catch(e){throw console.error("Failed to get allowed IPs:",e),e}},Y=async(e,t)=>{try{let o=n?"".concat(n,"/add/allowed_ip"):"/add/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("addAllowedIP:",r),r}catch(e){throw console.error("Failed to add allowed IP:",e),e}},$=async(e,t)=>{try{let o=n?"".concat(n,"/delete/allowed_ip"):"/delete/allowed_ip",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({ip:t})});if(!a.ok){let e=await a.text();throw Error("Network response was not ok: ".concat(e))}let r=await a.json();return console.log("deleteAllowedIP:",r),r}catch(e){throw console.error("Failed to delete allowed IP:",e),e}},K=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics"):"/model/metrics";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},Q=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/model/streaming_metrics"):"/model/streaming_metrics";t&&(r="".concat(r,"?_selected_model_group=").concat(t,"&startTime=").concat(o,"&endTime=").concat(a));let c=await fetch(r,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},W=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/slow_responses"):"/model/metrics/slow_responses";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},ee=async(e,t,o,a,r,c,s,d)=>{try{let t=n?"".concat(n,"/model/metrics/exceptions"):"/model/metrics/exceptions";a&&(t="".concat(t,"?_selected_model_group=").concat(a,"&startTime=").concat(r,"&endTime=").concat(c,"&api_key=").concat(s,"&customer=").concat(d));let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to create key:",e),e}},et=async function(e,t,o){let a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;console.log("in /models calls, globalLitellmHeaderName",l);try{let t=n?"".concat(n,"/models"):"/models",o=new URLSearchParams;!0===a&&o.append("return_wildcard_routes","True"),r&&o.append("team_id",r.toString()),o.toString()&&(t+="?".concat(o.toString()));let c=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}return await c.json()}catch(e){throw console.error("Failed to create key:",e),e}},eo=async e=>{try{let t=n?"".concat(n,"/global/spend/teams"):"/global/spend/teams";console.log("in teamSpendLogsCall:",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ea=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/tags"):"/global/spend/tags";t&&o&&(r="".concat(r,"?start_date=").concat(t,"&end_date=").concat(o)),a&&(r+="".concat(r,"&tags=").concat(a.join(","))),console.log("in tagsSpendLogsCall:",r);let c=await fetch("".concat(r),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to create key:",e),e}},er=async e=>{try{let t=n?"".concat(n,"/global/spend/all_tag_names"):"/global/spend/all_tag_names";console.log("in global/spend/all_tag_names call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},en=async e=>{try{let t=n?"".concat(n,"/global/all_end_users"):"/global/all_end_users";console.log("in global/all_end_users call",t);let o=await fetch("".concat(t),{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ec=async(e,t)=>{try{let o=n?"".concat(n,"/user/filter/ui"):"/user/filter/ui";t.get("user_email")&&(o+="?user_email=".concat(t.get("user_email"))),t.get("user_id")&&(o+="?user_id=".concat(t.get("user_id")));let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to create key:",e),e}},es=async(e,t,o,a,r,c,s,d,h)=>{try{let w=n?"".concat(n,"/spend/logs/ui"):"/spend/logs/ui",p=new URLSearchParams;t&&p.append("api_key",t),o&&p.append("team_id",o),a&&p.append("request_id",a),r&&p.append("start_date",r),c&&p.append("end_date",c),s&&p.append("page",s.toString()),d&&p.append("page_size",d.toString()),h&&p.append("user_id",h);let u=p.toString();u&&(w+="?".concat(u));let y=await fetch(w,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!y.ok){let e=await y.text();throw i(e),Error("Network response was not ok")}let f=await y.json();return console.log("Spend Logs Response:",f),f}catch(e){throw console.error("Failed to fetch spend logs:",e),e}},ei=async e=>{try{let t=n?"".concat(n,"/global/spend/logs"):"/global/spend/logs",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},el=async e=>{try{let t=n?"".concat(n,"/global/spend/keys?limit=5"):"/global/spend/keys?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},ed=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/end_users"):"/global/spend/end_users",c="";c=t?JSON.stringify({api_key:t,startTime:o,endTime:a}):JSON.stringify({startTime:o,endTime:a});let s={method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:c},d=await fetch(r,s);if(!d.ok){let e=await d.text();throw i(e),Error("Network response was not ok")}let h=await d.json();return console.log(h),h}catch(e){throw console.error("Failed to create key:",e),e}},eh=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/spend/provider"):"/global/spend/provider";o&&a&&(r+="?start_date=".concat(o,"&end_date=").concat(a)),t&&(r+="&api_key=".concat(t));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok){let e=await s.text();throw i(e),Error("Network response was not ok")}let d=await s.json();return console.log(d),d}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ew=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity"):"/global/activity";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ep=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/cache_hits"):"/global/activity/cache_hits";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},eu=async(e,t,o)=>{try{let a=n?"".concat(n,"/global/activity/model"):"/global/activity/model";t&&o&&(a+="?start_date=".concat(t,"&end_date=").concat(o));let r={method:"GET",headers:{[l]:"Bearer ".concat(e)}},c=await fetch(a,r);if(!c.ok)throw await c.text(),Error("Network response was not ok");let s=await c.json();return console.log(s),s}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ey=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions"):"/global/activity/exceptions";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},ef=async(e,t,o,a)=>{try{let r=n?"".concat(n,"/global/activity/exceptions/deployment"):"/global/activity/exceptions/deployment";t&&o&&(r+="?start_date=".concat(t,"&end_date=").concat(o)),a&&(r+="&model_group=".concat(a));let c={method:"GET",headers:{[l]:"Bearer ".concat(e)}},s=await fetch(r,c);if(!s.ok)throw await s.text(),Error("Network response was not ok");let i=await s.json();return console.log(i),i}catch(e){throw console.error("Failed to fetch spend data:",e),e}},em=async e=>{try{let t=n?"".concat(n,"/global/spend/models?limit=5"):"/global/spend/models?limit=5",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log(a),a}catch(e){throw console.error("Failed to create key:",e),e}},eg=async(e,t)=>{try{let o=n?"".concat(n,"/v2/key/info"):"/v2/key/info",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({keys:t})});if(!a.ok){let e=await a.text();if(e.includes("Invalid proxy server token passed"))throw Error("Invalid proxy server token passed");throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to create key:",e),e}},ek=async(e,t,o)=>{try{console.log("Sending model connection test request:",JSON.stringify(t));let r=n?"".concat(n,"/health/test_connection"):"/health/test_connection",c=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",[l]:"Bearer ".concat(e)},body:JSON.stringify({litellm_params:t,mode:o})}),s=c.headers.get("content-type");if(!s||!s.includes("application/json")){let e=await c.text();throw console.error("Received non-JSON response:",e),Error("Received non-JSON response (".concat(c.status,": ").concat(c.statusText,"). Check network tab for details."))}let i=await c.json();if(!c.ok||"error"===i.status){if("error"===i.status);else{var a;return{status:"error",message:(null===(a=i.error)||void 0===a?void 0:a.message)||"Connection test failed: ".concat(c.status," ").concat(c.statusText)}}}return i}catch(e){throw console.error("Model connection test error:",e),e}},e_=async(e,t)=>{try{console.log("entering keyInfoV1Call");let o=n?"".concat(n,"/key/info"):"/key/info";o="".concat(o,"?key=").concat(t);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(console.log("response",a),!a.ok){let e=await a.text();i(e),r.ZP.error("Failed to fetch key info - "+e)}let c=await a.json();return console.log("data",c),c}catch(e){throw console.error("Failed to fetch key info:",e),e}},eT=async(e,t,o,a,r,c)=>{try{let s=n?"".concat(n,"/key/list"):"/key/list";console.log("in keyListCall");let d=new URLSearchParams;o&&d.append("team_id",o.toString()),t&&d.append("organization_id",t.toString()),a&&d.append("key_alias",a),r&&d.append("page",r.toString()),c&&d.append("size",c.toString()),d.append("return_full_object","true"),d.append("include_team_keys","true");let h=d.toString();h&&(s+="?".concat(h));let w=await fetch(s,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!w.ok){let e=await w.text();throw i(e),Error("Network response was not ok")}let p=await w.json();return console.log("/team/list API Response:",p),p}catch(e){throw console.error("Failed to create key:",e),e}},eE=async(e,t)=>{try{let o=n?"".concat(n,"/user/get_users?role=").concat(t):"/user/get_users?role=".concat(t);console.log("in userGetAllUsersCall:",o);let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to get requested models:",e),e}},ej=async e=>{try{let t=n?"".concat(n,"/user/available_roles"):"/user/available_roles",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");let a=await o.json();return console.log("response from user/available_role",a),a}catch(e){throw e}},eC=async(e,t)=>{try{if(console.log("Form Values in teamCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/team/new"):"/team/new",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eN=async(e,t)=>{try{if(console.log("Form Values in credentialCreateCall:",t),t.metadata){console.log("formValues.metadata:",t.metadata);try{t.metadata=JSON.parse(t.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let o=n?"".concat(n,"/credentials"):"/credentials",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("API Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eS=async e=>{try{let t=n?"".concat(n,"/credentials"):"/credentials";console.log("in credentialListCall");let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("/credentials API Response:",a),a}catch(e){throw console.error("Failed to create key:",e),e}},eb=async(e,t,o)=>{try{let a=n?"".concat(n,"/credentials"):"/credentials";t?a+="/by_name/".concat(t):o&&(a+="/by_model/".concat(o)),console.log("in credentialListCall");let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("/credentials API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eF=async(e,t)=>{try{let o=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t);console.log("in credentialDeleteCall:",t);let a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log(r),r}catch(e){throw console.error("Failed to delete key:",e),e}},ex=async(e,t,o)=>{try{if(console.log("Form Values in credentialUpdateCall:",o),o.metadata){console.log("formValues.metadata:",o.metadata);try{o.metadata=JSON.parse(o.metadata)}catch(e){throw Error("Failed to parse metadata: "+e)}}let a=n?"".concat(n,"/credentials/").concat(t):"/credentials/".concat(t),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eO=async(e,t)=>{try{if(console.log("Form Values in keyUpdateCall:",t),t.model_tpm_limit){console.log("formValues.model_tpm_limit:",t.model_tpm_limit);try{t.model_tpm_limit=JSON.parse(t.model_tpm_limit)}catch(e){throw Error("Failed to parse model_tpm_limit: "+e)}}if(t.model_rpm_limit){console.log("formValues.model_rpm_limit:",t.model_rpm_limit);try{t.model_rpm_limit=JSON.parse(t.model_rpm_limit)}catch(e){throw Error("Failed to parse model_rpm_limit: "+e)}}let o=n?"".concat(n,"/key/update"):"/key/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update key Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eB=async(e,t)=>{try{console.log("Form Values in teamUpateCall:",t);let o=n?"".concat(n,"/team/update"):"/team/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let r=await a.json();return console.log("Update Team Response:",r),r}catch(e){throw console.error("Failed to create key:",e),e}},eP=async(e,t,o)=>{try{console.log("Form Values in modelUpateCall:",t);let a=n?"".concat(n,"/model/").concat(o,"/update"):"/model/".concat(o,"/update"),r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error update from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("Update model Response:",c),c}catch(e){throw console.error("Failed to update model:",e),e}},ev=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_add"):"/team/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eG=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_update"):"/team/member_update",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,role:o.role,user_id:o.user_id})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eA=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/team/member_delete"):"/team/member_delete",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({team_id:t,...void 0!==o.user_email&&{user_email:o.user_email},...void 0!==o.user_id&&{user_id:o.user_id}})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create key:",e),e}},eJ=async(e,t,o)=>{try{console.log("Form Values in teamMemberAddCall:",o);let a=n?"".concat(n,"/organization/member_add"):"/organization/member_add",r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,member:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error(e)}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to create organization member:",e),e}},eI=async(e,t,o)=>{try{console.log("Form Values in organizationMemberDeleteCall:",o);let a=n?"".concat(n,"/organization/member_delete"):"/organization/member_delete",r=await fetch(a,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,user_id:o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to delete organization member:",e),e}},eR=async(e,t,o)=>{try{console.log("Form Values in organizationMemberUpdateCall:",o);let a=n?"".concat(n,"/organization/member_update"):"/organization/member_update",r=await fetch(a,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({organization_id:t,...o})});if(!r.ok){let e=await r.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let c=await r.json();return console.log("API Response:",c),c}catch(e){throw console.error("Failed to update organization member:",e),e}},ez=async(e,t,o)=>{try{console.log("Form Values in userUpdateUserCall:",t);let a=n?"".concat(n,"/user/update"):"/user/update",r={...t};null!==o&&(r.user_role=o),r=JSON.stringify(r);let c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:r});if(!c.ok){let e=await c.text();throw i(e),console.error("Error response from the server:",e),Error("Network response was not ok")}let s=await c.json();return console.log("API Response:",s),s}catch(e){throw console.error("Failed to create key:",e),e}},eV=async(e,t)=>{try{let o=n?"".concat(n,"/health/services?service=").concat(t):"/health/services?service=".concat(t);console.log("Checking Slack Budget Alerts service health");let a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error(e)}let c=await a.json();return r.ZP.success("Test request to ".concat(t," made - check logs/alerts on ").concat(t," to verify")),c}catch(e){throw console.error("Failed to perform health check:",e),e}},eU=async e=>{try{let t=n?"".concat(n,"/budget/list"):"/budget/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eL=async(e,t,o)=>{try{let t=n?"".concat(n,"/get/config/callbacks"):"/get/config/callbacks",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eM=async e=>{try{let t=n?"".concat(n,"/config/list?config_type=general_settings"):"/config/list?config_type=general_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eZ=async e=>{try{let t=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},eD=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/info?field_name=").concat(t):"/config/field/info?field_name=".concat(t),a=await fetch(o,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok)throw await a.text(),Error("Network response was not ok");return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eH=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint"):"/config/pass_through_endpoint",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eq=async(e,t,o)=>{try{let a=n?"".concat(n,"/config/field/update"):"/config/field/update",c=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,field_value:o,config_type:"general_settings"})});if(!c.ok){let e=await c.text();throw i(e),Error("Network response was not ok")}let s=await c.json();return r.ZP.success("Successfully updated value!"),s}catch(e){throw console.error("Failed to set callbacks:",e),e}},eX=async(e,t)=>{try{let o=n?"".concat(n,"/config/field/delete"):"/config/field/delete",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({field_name:t,config_type:"general_settings"})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return r.ZP.success("Field reset on proxy"),c}catch(e){throw console.error("Failed to get callbacks:",e),e}},eY=async(e,t)=>{try{let o=n?"".concat(n,"/config/pass_through_endpoint?endpoint_id=").concat(t):"/config/pass_through_endpoint".concat(t),a=await fetch(o,{method:"DELETE",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},e$=async(e,t)=>{try{let o=n?"".concat(n,"/config/update"):"/config/update",a=await fetch(o,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({...t})});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}return await a.json()}catch(e){throw console.error("Failed to set callbacks:",e),e}},eK=async e=>{try{let t=n?"".concat(n,"/health"):"/health",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}return await o.json()}catch(e){throw console.error("Failed to call /health:",e),e}},eQ=async e=>{try{let t=n?"".concat(n,"/cache/ping"):"/cache/ping",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error(e)}return await o.json()}catch(e){throw console.error("Failed to call /cache/ping:",e),e}},eW=async e=>{try{let t=n?"".concat(n,"/sso/get/ui_settings"):"/sso/get/ui_settings",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok)throw await o.text(),Error("Network response was not ok");return await o.json()}catch(e){throw console.error("Failed to get callbacks:",e),e}},e0=async e=>{try{let t=n?"".concat(n,"/guardrails/list"):"/guardrails/list",o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Guardrails list response:",a),a}catch(e){throw console.error("Failed to fetch guardrails list:",e),e}},e1=async(e,t,o)=>{try{let a=n?"".concat(n,"/spend/logs/ui/").concat(t,"?start_date=").concat(encodeURIComponent(o)):"/spend/logs/ui/".concat(t,"?start_date=").concat(encodeURIComponent(o));console.log("Fetching log details from:",a);let r=await fetch(a,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Fetched log details:",c),c}catch(e){throw console.error("Failed to fetch log details:",e),e}},e3=async e=>{try{let t=n?"".concat(n,"/get/internal_user_settings"):"/get/internal_user_settings";console.log("Fetching SSO settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched SSO settings:",a),a}catch(e){throw console.error("Failed to fetch SSO settings:",e),e}},e2=async(e,t)=>{try{let o=n?"".concat(n,"/update/internal_user_settings"):"/update/internal_user_settings";console.log("Updating internal user settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated internal user settings:",c),r.ZP.success("Internal user settings updated successfully"),c}catch(e){throw console.error("Failed to update internal user settings:",e),e}},e4=async e=>{try{let t=n?"".concat(n,"/mcp/tools/list"):"/mcp/tools/list";console.log("Fetching MCP tools from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched MCP tools:",a),a}catch(e){throw console.error("Failed to fetch MCP tools:",e),e}},e5=async(e,t,o)=>{try{let a=n?"".concat(n,"/mcp/tools/call"):"/mcp/tools/call";console.log("Calling MCP tool:",t,"with arguments:",o);let r=await fetch(a,{method:"POST",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify({name:t,arguments:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("MCP tool call response:",c),c}catch(e){throw console.error("Failed to call MCP tool:",e),e}},e6=async(e,t)=>{try{let o=n?"".concat(n,"/tag/new"):"/tag/new",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error creating tag:",e),e}},e9=async(e,t)=>{try{let o=n?"".concat(n,"/tag/update"):"/tag/update",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error updating tag:",e),e}},e7=async(e,t)=>{try{let o=n?"".concat(n,"/tag/info"):"/tag/info",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({names:t})});if(!a.ok){let e=await a.text();return await i(e),{}}return await a.json()}catch(e){throw console.error("Error getting tag info:",e),e}},e8=async e=>{try{let t=n?"".concat(n,"/tag/list"):"/tag/list",o=await fetch(t,{method:"GET",headers:{Authorization:"Bearer ".concat(e)}});if(!o.ok){let e=await o.text();return await i(e),{}}return await o.json()}catch(e){throw console.error("Error listing tags:",e),e}},te=async(e,t)=>{try{let o=n?"".concat(n,"/tag/delete"):"/tag/delete",a=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({name:t})});if(!a.ok){let e=await a.text();await i(e);return}return await a.json()}catch(e){throw console.error("Error deleting tag:",e),e}},tt=async e=>{try{let t=n?"".concat(n,"/get/default_team_settings"):"/get/default_team_settings";console.log("Fetching default team settings from:",t);let o=await fetch(t,{method:"GET",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.text();throw i(e),Error("Network response was not ok")}let a=await o.json();return console.log("Fetched default team settings:",a),a}catch(e){throw console.error("Failed to fetch default team settings:",e),e}},to=async(e,t)=>{try{let o=n?"".concat(n,"/update/default_team_settings"):"/update/default_team_settings";console.log("Updating default team settings:",t);let a=await fetch(o,{method:"PATCH",headers:{[l]:"Bearer ".concat(e),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let c=await a.json();return console.log("Updated default team settings:",c),r.ZP.success("Default team settings updated successfully"),c}catch(e){throw console.error("Failed to update default team settings:",e),e}},ta=async(e,t)=>{try{let o=n?"".concat(n,"/team/permissions_list?team_id=").concat(t):"/team/permissions_list?team_id=".concat(t),a=await fetch(o,{method:"GET",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)}});if(!a.ok){let e=await a.text();throw i(e),Error("Network response was not ok")}let r=await a.json();return console.log("Team permissions response:",r),r}catch(e){throw console.error("Failed to get team permissions:",e),e}},tr=async(e,t,o)=>{try{let a=n?"".concat(n,"/team/permissions_update"):"/team/permissions_update",r=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer ".concat(e)},body:JSON.stringify({team_id:t,team_member_permissions:o})});if(!r.ok){let e=await r.text();throw i(e),Error("Network response was not ok")}let c=await r.json();return console.log("Team permissions response:",c),c}catch(e){throw console.error("Failed to update team permissions:",e),e}}},20347:function(e,t,o){o.d(t,{LQ:function(){return n},ZL:function(){return a},lo:function(){return r},tY:function(){return c}});let a=["Admin","Admin Viewer","proxy_admin","proxy_admin_viewer","org_admin"],r=["Internal User","Internal Viewer"],n=["Internal User","Admin"],c=e=>a.includes(e)}}]); \ No newline at end of file diff --git a/ui/litellm-dashboard/out/_next/static/chunks/261-57d48f76eec1e568.js b/ui/litellm-dashboard/out/_next/static/chunks/261-92d8946249b3296e.js similarity index 99% rename from ui/litellm-dashboard/out/_next/static/chunks/261-57d48f76eec1e568.js rename to ui/litellm-dashboard/out/_next/static/chunks/261-92d8946249b3296e.js index 44e5f1be73..e87c443917 100644 --- a/ui/litellm-dashboard/out/_next/static/chunks/261-57d48f76eec1e568.js +++ b/ui/litellm-dashboard/out/_next/static/chunks/261-92d8946249b3296e.js @@ -1 +1 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[261],{23639:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"}}]},name:"copy",theme:"outlined"},o=n(55015),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},77565:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var a=n(1119),r=n(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"},o=n(55015),s=r.forwardRef(function(e,t){return r.createElement(o.Z,(0,a.Z)({},e,{ref:t,icon:i}))})},12485:function(e,t,n){"use strict";n.d(t,{Z:function(){return p}});var a=n(5853),r=n(31492),i=n(26898),o=n(65954),s=n(1153),l=n(2265),c=n(35242),u=n(42698);n(64016),n(8710),n(33232);let d=(0,s.fn)("Tab"),p=l.forwardRef((e,t)=>{let{icon:n,className:p,children:g}=e,m=(0,a._T)(e,["icon","className","children"]),b=(0,l.useContext)(c.O),f=(0,l.useContext)(u.Z);return l.createElement(r.O,Object.assign({ref:t,className:(0,o.q)(d("root"),"flex whitespace-nowrap truncate max-w-xs outline-none focus:ring-0 text-tremor-default transition duration-100",f?(0,s.bM)(f,i.K.text).selectTextColor:"solid"===b?"ui-selected:text-tremor-content-emphasis dark:ui-selected:text-dark-tremor-content-emphasis":"ui-selected:text-tremor-brand dark:ui-selected:text-dark-tremor-brand",function(e,t){switch(e){case"line":return(0,o.q)("ui-selected:border-b-2 hover:border-b-2 border-transparent transition duration-100 -mb-px px-2 py-2","hover:border-tremor-content hover:text-tremor-content-emphasis text-tremor-content","dark:hover:border-dark-tremor-content-emphasis dark:hover:text-dark-tremor-content-emphasis dark:text-dark-tremor-content",t?(0,s.bM)(t,i.K.border).selectBorderColor:"ui-selected:border-tremor-brand dark:ui-selected:border-dark-tremor-brand");case"solid":return(0,o.q)("border-transparent border rounded-tremor-small px-2.5 py-1","ui-selected:border-tremor-border ui-selected:bg-tremor-background ui-selected:shadow-tremor-input hover:text-tremor-content-emphasis ui-selected:text-tremor-brand","dark:ui-selected:border-dark-tremor-border dark:ui-selected:bg-dark-tremor-background dark:ui-selected:shadow-dark-tremor-input dark:hover:text-dark-tremor-content-emphasis dark:ui-selected:text-dark-tremor-brand",t?(0,s.bM)(t,i.K.text).selectTextColor:"text-tremor-content dark:text-dark-tremor-content")}}(b,f),p)},m),n?l.createElement(n,{className:(0,o.q)(d("icon"),"flex-none h-5 w-5",g?"mr-2":"")}):null,g?l.createElement("span",null,g):null)});p.displayName="Tab"},18135:function(e,t,n){"use strict";n.d(t,{Z:function(){return c}});var a=n(5853),r=n(31492),i=n(65954),o=n(1153),s=n(2265);let l=(0,o.fn)("TabGroup"),c=s.forwardRef((e,t)=>{let{defaultIndex:n,index:o,onIndexChange:c,children:u,className:d}=e,p=(0,a._T)(e,["defaultIndex","index","onIndexChange","children","className"]);return s.createElement(r.O.Group,Object.assign({as:"div",ref:t,defaultIndex:n,selectedIndex:o,onChange:c,className:(0,i.q)(l("root"),"w-full",d)},p),u)});c.displayName="TabGroup"},35242:function(e,t,n){"use strict";n.d(t,{O:function(){return c},Z:function(){return d}});var a=n(5853),r=n(2265),i=n(42698);n(64016),n(8710),n(33232);var o=n(31492),s=n(65954);let l=(0,n(1153).fn)("TabList"),c=(0,r.createContext)("line"),u={line:(0,s.q)("flex border-b space-x-4","border-tremor-border","dark:border-dark-tremor-border"),solid:(0,s.q)("inline-flex p-0.5 rounded-tremor-default space-x-1.5","bg-tremor-background-subtle","dark:bg-dark-tremor-background-subtle")},d=r.forwardRef((e,t)=>{let{color:n,variant:d="line",children:p,className:g}=e,m=(0,a._T)(e,["color","variant","children","className"]);return r.createElement(o.O.List,Object.assign({ref:t,className:(0,s.q)(l("root"),"justify-start overflow-x-clip",u[d],g)},m),r.createElement(c.Provider,{value:d},r.createElement(i.Z.Provider,{value:n},p)))});d.displayName="TabList"},29706:function(e,t,n){"use strict";n.d(t,{Z:function(){return u}});var a=n(5853);n(42698);var r=n(64016);n(8710);var i=n(33232),o=n(65954),s=n(1153),l=n(2265);let c=(0,s.fn)("TabPanel"),u=l.forwardRef((e,t)=>{let{children:n,className:s}=e,u=(0,a._T)(e,["children","className"]),{selectedValue:d}=(0,l.useContext)(i.Z),p=d===(0,l.useContext)(r.Z);return l.createElement("div",Object.assign({ref:t,className:(0,o.q)(c("root"),"w-full mt-2",p?"":"hidden",s),"aria-selected":p?"true":"false"},u),n)});u.displayName="TabPanel"},77991:function(e,t,n){"use strict";n.d(t,{Z:function(){return d}});var a=n(5853),r=n(31492);n(42698);var i=n(64016);n(8710);var o=n(33232),s=n(65954),l=n(1153),c=n(2265);let u=(0,l.fn)("TabPanels"),d=c.forwardRef((e,t)=>{let{children:n,className:l}=e,d=(0,a._T)(e,["children","className"]);return c.createElement(r.O.Panels,Object.assign({as:"div",ref:t,className:(0,s.q)(u("root"),"w-full",l)},d),e=>{let{selectedIndex:t}=e;return c.createElement(o.Z.Provider,{value:{selectedValue:t}},c.Children.map(n,(e,t)=>c.createElement(i.Z.Provider,{value:t},e)))})});d.displayName="TabPanels"},42698:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var a=n(2265),r=n(7084);n(65954);let i=(0,a.createContext)(r.fr.Blue)},64016:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(0)},8710:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)(void 0)},33232:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});let a=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},93942:function(e,t,n){"use strict";n.d(t,{i:function(){return s}});var a=n(2265),r=n(50506),i=n(13959),o=n(71744);function s(e){return t=>a.createElement(i.ZP,{theme:{token:{motion:!1,zIndexPopupBase:0}}},a.createElement(e,Object.assign({},t)))}t.Z=(e,t,n,i)=>s(s=>{let{prefixCls:l,style:c}=s,u=a.useRef(null),[d,p]=a.useState(0),[g,m]=a.useState(0),[b,f]=(0,r.Z)(!1,{value:s.open}),{getPrefixCls:E}=a.useContext(o.E_),h=E(t||"select",l);a.useEffect(()=>{if(f(!0),"undefined"!=typeof ResizeObserver){let e=new ResizeObserver(e=>{let t=e[0].target;p(t.offsetHeight+8),m(t.offsetWidth)}),t=setInterval(()=>{var a;let r=n?".".concat(n(h)):".".concat(h,"-dropdown"),i=null===(a=u.current)||void 0===a?void 0:a.querySelector(r);i&&(clearInterval(t),e.observe(i))},10);return()=>{clearInterval(t),e.disconnect()}}},[]);let S=Object.assign(Object.assign({},s),{style:Object.assign(Object.assign({},c),{margin:0}),open:b,visible:b,getPopupContainer:()=>u.current});return i&&(S=i(S)),a.createElement("div",{ref:u,style:{paddingBottom:d,position:"relative",minWidth:g}},a.createElement(e,Object.assign({},S)))})},51369:function(e,t,n){"use strict";let a;n.d(t,{Z:function(){return eY}});var r=n(83145),i=n(2265),o=n(18404),s=n(71744),l=n(13959),c=n(8900),u=n(39725),d=n(54537),p=n(55726),g=n(36760),m=n.n(g),b=n(62236),f=n(68710),E=n(55274),h=n(29961),S=n(69819),y=n(73002),T=n(51248),A=e=>{let{type:t,children:n,prefixCls:a,buttonProps:r,close:o,autoFocus:s,emitEvent:l,isSilent:c,quitOnNullishReturnValue:u,actionFn:d}=e,p=i.useRef(!1),g=i.useRef(null),[m,b]=(0,S.Z)(!1),f=function(){null==o||o.apply(void 0,arguments)};i.useEffect(()=>{let e=null;return s&&(e=setTimeout(()=>{var e;null===(e=g.current)||void 0===e||e.focus()})),()=>{e&&clearTimeout(e)}},[]);let E=e=>{e&&e.then&&(b(!0),e.then(function(){b(!1,!0),f.apply(void 0,arguments),p.current=!1},e=>{if(b(!1,!0),p.current=!1,null==c||!c())return Promise.reject(e)}))};return i.createElement(y.ZP,Object.assign({},(0,T.nx)(t),{onClick:e=>{let t;if(!p.current){if(p.current=!0,!d){f();return}if(l){var n;if(t=d(e),u&&!((n=t)&&n.then)){p.current=!1,f(e);return}}else if(d.length)t=d(o),p.current=!1;else if(!(t=d())){f();return}E(t)}},loading:m,prefixCls:a},r,{ref:g}),n)};let R=i.createContext({}),{Provider:I}=R;var N=()=>{let{autoFocusButton:e,cancelButtonProps:t,cancelTextLocale:n,isSilent:a,mergedOkCancel:r,rootPrefixCls:o,close:s,onCancel:l,onConfirm:c}=(0,i.useContext)(R);return r?i.createElement(A,{isSilent:a,actionFn:l,close:function(){null==s||s.apply(void 0,arguments),null==c||c(!1)},autoFocus:"cancel"===e,buttonProps:t,prefixCls:"".concat(o,"-btn")},n):null},_=()=>{let{autoFocusButton:e,close:t,isSilent:n,okButtonProps:a,rootPrefixCls:r,okTextLocale:o,okType:s,onConfirm:l,onOk:c}=(0,i.useContext)(R);return i.createElement(A,{isSilent:n,type:s||"primary",actionFn:c,close:function(){null==t||t.apply(void 0,arguments),null==l||l(!0)},autoFocus:"ok"===e,buttonProps:a,prefixCls:"".concat(r,"-btn")},o)},v=n(49638),w=n(1119),k=n(26365),C=n(28036),O=i.createContext({}),x=n(31686),L=n(2161),D=n(92491),P=n(95814),M=n(18242);function F(e,t,n){var a=t;return!a&&n&&(a="".concat(e,"-").concat(n)),a}function U(e,t){var n=e["page".concat(t?"Y":"X","Offset")],a="scroll".concat(t?"Top":"Left");if("number"!=typeof n){var r=e.document;"number"!=typeof(n=r.documentElement[a])&&(n=r.body[a])}return n}var B=n(47970),G=n(28791),$=i.memo(function(e){return e.children},function(e,t){return!t.shouldUpdate}),H={width:0,height:0,overflow:"hidden",outline:"none"},z=i.forwardRef(function(e,t){var n,a,r,o=e.prefixCls,s=e.className,l=e.style,c=e.title,u=e.ariaId,d=e.footer,p=e.closable,g=e.closeIcon,b=e.onClose,f=e.children,E=e.bodyStyle,h=e.bodyProps,S=e.modalRender,y=e.onMouseDown,T=e.onMouseUp,A=e.holderRef,R=e.visible,I=e.forceRender,N=e.width,_=e.height,v=e.classNames,k=e.styles,C=i.useContext(O).panel,L=(0,G.x1)(A,C),D=(0,i.useRef)(),P=(0,i.useRef)();i.useImperativeHandle(t,function(){return{focus:function(){var e;null===(e=D.current)||void 0===e||e.focus()},changeActive:function(e){var t=document.activeElement;e&&t===P.current?D.current.focus():e||t!==D.current||P.current.focus()}}});var M={};void 0!==N&&(M.width=N),void 0!==_&&(M.height=_),d&&(n=i.createElement("div",{className:m()("".concat(o,"-footer"),null==v?void 0:v.footer),style:(0,x.Z)({},null==k?void 0:k.footer)},d)),c&&(a=i.createElement("div",{className:m()("".concat(o,"-header"),null==v?void 0:v.header),style:(0,x.Z)({},null==k?void 0:k.header)},i.createElement("div",{className:"".concat(o,"-title"),id:u},c))),p&&(r=i.createElement("button",{type:"button",onClick:b,"aria-label":"Close",className:"".concat(o,"-close")},g||i.createElement("span",{className:"".concat(o,"-close-x")})));var F=i.createElement("div",{className:m()("".concat(o,"-content"),null==v?void 0:v.content),style:null==k?void 0:k.content},r,a,i.createElement("div",(0,w.Z)({className:m()("".concat(o,"-body"),null==v?void 0:v.body),style:(0,x.Z)((0,x.Z)({},E),null==k?void 0:k.body)},h),f),n);return i.createElement("div",{key:"dialog-element",role:"dialog","aria-labelledby":c?u:null,"aria-modal":"true",ref:L,style:(0,x.Z)((0,x.Z)({},l),M),className:m()(o,s),onMouseDown:y,onMouseUp:T},i.createElement("div",{tabIndex:0,ref:D,style:H,"aria-hidden":"true"}),i.createElement($,{shouldUpdate:R||I},S?S(F):F),i.createElement("div",{tabIndex:0,ref:P,style:H,"aria-hidden":"true"}))}),j=i.forwardRef(function(e,t){var n=e.prefixCls,a=e.title,r=e.style,o=e.className,s=e.visible,l=e.forceRender,c=e.destroyOnClose,u=e.motionName,d=e.ariaId,p=e.onVisibleChanged,g=e.mousePosition,b=(0,i.useRef)(),f=i.useState(),E=(0,k.Z)(f,2),h=E[0],S=E[1],y={};function T(){var e,t,n,a,r,i=(n={left:(t=(e=b.current).getBoundingClientRect()).left,top:t.top},r=(a=e.ownerDocument).defaultView||a.parentWindow,n.left+=U(r),n.top+=U(r,!0),n);S(g?"".concat(g.x-i.left,"px ").concat(g.y-i.top,"px"):"")}return h&&(y.transformOrigin=h),i.createElement(B.ZP,{visible:s,onVisibleChanged:p,onAppearPrepare:T,onEnterPrepare:T,forceRender:l,motionName:u,removeOnLeave:c,ref:b},function(s,l){var c=s.className,u=s.style;return i.createElement(z,(0,w.Z)({},e,{ref:t,title:a,ariaId:d,prefixCls:n,holderRef:l,style:(0,x.Z)((0,x.Z)((0,x.Z)({},u),r),y),className:m()(o,c)}))})});function V(e){var t=e.prefixCls,n=e.style,a=e.visible,r=e.maskProps,o=e.motionName,s=e.className;return i.createElement(B.ZP,{key:"mask",visible:a,motionName:o,leavedClassName:"".concat(t,"-mask-hidden")},function(e,a){var o=e.className,l=e.style;return i.createElement("div",(0,w.Z)({ref:a,style:(0,x.Z)((0,x.Z)({},l),n),className:m()("".concat(t,"-mask"),o,s)},r))})}function W(e){var t=e.prefixCls,n=void 0===t?"rc-dialog":t,a=e.zIndex,r=e.visible,o=void 0!==r&&r,s=e.keyboard,l=void 0===s||s,c=e.focusTriggerAfterClose,u=void 0===c||c,d=e.wrapStyle,p=e.wrapClassName,g=e.wrapProps,b=e.onClose,f=e.afterOpenChange,E=e.afterClose,h=e.transitionName,S=e.animation,y=e.closable,T=e.mask,A=void 0===T||T,R=e.maskTransitionName,I=e.maskAnimation,N=e.maskClosable,_=e.maskStyle,v=e.maskProps,C=e.rootClassName,O=e.classNames,U=e.styles,B=(0,i.useRef)(),G=(0,i.useRef)(),$=(0,i.useRef)(),H=i.useState(o),z=(0,k.Z)(H,2),W=z[0],q=z[1],Y=(0,D.Z)();function K(e){null==b||b(e)}var Z=(0,i.useRef)(!1),X=(0,i.useRef)(),Q=null;return(void 0===N||N)&&(Q=function(e){Z.current?Z.current=!1:G.current===e.target&&K(e)}),(0,i.useEffect)(function(){o&&(q(!0),(0,L.Z)(G.current,document.activeElement)||(B.current=document.activeElement))},[o]),(0,i.useEffect)(function(){return function(){clearTimeout(X.current)}},[]),i.createElement("div",(0,w.Z)({className:m()("".concat(n,"-root"),C)},(0,M.Z)(e,{data:!0})),i.createElement(V,{prefixCls:n,visible:A&&o,motionName:F(n,R,I),style:(0,x.Z)((0,x.Z)({zIndex:a},_),null==U?void 0:U.mask),maskProps:v,className:null==O?void 0:O.mask}),i.createElement("div",(0,w.Z)({tabIndex:-1,onKeyDown:function(e){if(l&&e.keyCode===P.Z.ESC){e.stopPropagation(),K(e);return}o&&e.keyCode===P.Z.TAB&&$.current.changeActive(!e.shiftKey)},className:m()("".concat(n,"-wrap"),p,null==O?void 0:O.wrapper),ref:G,onClick:Q,style:(0,x.Z)((0,x.Z)((0,x.Z)({zIndex:a},d),null==U?void 0:U.wrapper),{},{display:W?null:"none"})},g),i.createElement(j,(0,w.Z)({},e,{onMouseDown:function(){clearTimeout(X.current),Z.current=!0},onMouseUp:function(){X.current=setTimeout(function(){Z.current=!1})},ref:$,closable:void 0===y||y,ariaId:Y,prefixCls:n,visible:o&&W,onClose:K,onVisibleChanged:function(e){if(e)!function(){if(!(0,L.Z)(G.current,document.activeElement)){var e;null===(e=$.current)||void 0===e||e.focus()}}();else{if(q(!1),A&&B.current&&u){try{B.current.focus({preventScroll:!0})}catch(e){}B.current=null}W&&(null==E||E())}null==f||f(e)},motionName:F(n,h,S)}))))}j.displayName="Content",n(32559);var q=function(e){var t=e.visible,n=e.getContainer,a=e.forceRender,r=e.destroyOnClose,o=void 0!==r&&r,s=e.afterClose,l=e.panelRef,c=i.useState(t),u=(0,k.Z)(c,2),d=u[0],p=u[1],g=i.useMemo(function(){return{panel:l}},[l]);return(i.useEffect(function(){t&&p(!0)},[t]),a||!o||d)?i.createElement(O.Provider,{value:g},i.createElement(C.Z,{open:t||a||d,autoDestroy:!1,getContainer:n,autoLock:t||d},i.createElement(W,(0,w.Z)({},e,{destroyOnClose:o,afterClose:function(){null==s||s(),p(!1)}})))):null};q.displayName="Dialog";var Y=function(e,t,n){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i.createElement(v.Z,null),r=arguments.length>4&&void 0!==arguments[4]&&arguments[4];if("boolean"==typeof e?!e:void 0===t?!r:!1===t||null===t)return[!1,null];let o="boolean"==typeof t||null==t?a:t;return[!0,n?n(o):o]},K=n(94981),Z=n(95140),X=n(39109),Q=n(65658),J=n(74126);function ee(){}let et=i.createContext({add:ee,remove:ee});var en=n(86586),ea=()=>{let{cancelButtonProps:e,cancelTextLocale:t,onCancel:n}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({onClick:n},e),t)},er=()=>{let{confirmLoading:e,okButtonProps:t,okType:n,okTextLocale:a,onOk:r}=(0,i.useContext)(R);return i.createElement(y.ZP,Object.assign({},(0,T.nx)(n),{loading:e,onClick:r},t),a)},ei=n(92246);function eo(e,t){return i.createElement("span",{className:"".concat(e,"-close-x")},t||i.createElement(v.Z,{className:"".concat(e,"-close-icon")}))}let es=e=>{let t;let{okText:n,okType:a="primary",cancelText:o,confirmLoading:s,onOk:l,onCancel:c,okButtonProps:u,cancelButtonProps:d,footer:p}=e,[g]=(0,E.Z)("Modal",(0,ei.A)()),m={confirmLoading:s,okButtonProps:u,cancelButtonProps:d,okTextLocale:n||(null==g?void 0:g.okText),cancelTextLocale:o||(null==g?void 0:g.cancelText),okType:a,onOk:l,onCancel:c},b=i.useMemo(()=>m,(0,r.Z)(Object.values(m)));return"function"==typeof p||void 0===p?(t=i.createElement(i.Fragment,null,i.createElement(ea,null),i.createElement(er,null)),"function"==typeof p&&(t=p(t,{OkBtn:er,CancelBtn:ea})),t=i.createElement(I,{value:b},t)):t=p,i.createElement(en.n,{disabled:!1},t)};var el=n(12918),ec=n(11699),eu=n(691),ed=n(3104),ep=n(80669),eg=n(352);function em(e){return{position:e,inset:0}}let eb=e=>{let{componentCls:t,antCls:n}=e;return[{["".concat(t,"-root")]:{["".concat(t).concat(n,"-zoom-enter, ").concat(t).concat(n,"-zoom-appear")]:{transform:"none",opacity:0,animationDuration:e.motionDurationSlow,userSelect:"none"},["".concat(t).concat(n,"-zoom-leave ").concat(t,"-content")]:{pointerEvents:"none"},["".concat(t,"-mask")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,height:"100%",backgroundColor:e.colorBgMask,pointerEvents:"none",["".concat(t,"-hidden")]:{display:"none"}}),["".concat(t,"-wrap")]:Object.assign(Object.assign({},em("fixed")),{zIndex:e.zIndexPopupBase,overflow:"auto",outline:0,WebkitOverflowScrolling:"touch",["&:has(".concat(t).concat(n,"-zoom-enter), &:has(").concat(t).concat(n,"-zoom-appear)")]:{pointerEvents:"none"}})}},{["".concat(t,"-root")]:(0,ec.J$)(e)}]},ef=e=>{let{componentCls:t}=e;return[{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl"},["".concat(t,"-centered")]:{textAlign:"center","&::before":{display:"inline-block",width:0,height:"100%",verticalAlign:"middle",content:'""'},[t]:{top:0,display:"inline-block",paddingBottom:0,textAlign:"start",verticalAlign:"middle"}},["@media (max-width: ".concat(e.screenSMMax,"px)")]:{[t]:{maxWidth:"calc(100vw - 16px)",margin:"".concat((0,eg.bf)(e.marginXS)," auto")},["".concat(t,"-centered")]:{[t]:{flex:1}}}}},{[t]:Object.assign(Object.assign({},(0,el.Wf)(e)),{pointerEvents:"none",position:"relative",top:100,width:"auto",maxWidth:"calc(100vw - ".concat((0,eg.bf)(e.calc(e.margin).mul(2).equal()),")"),margin:"0 auto",paddingBottom:e.paddingLG,["".concat(t,"-title")]:{margin:0,color:e.titleColor,fontWeight:e.fontWeightStrong,fontSize:e.titleFontSize,lineHeight:e.titleLineHeight,wordWrap:"break-word"},["".concat(t,"-content")]:{position:"relative",backgroundColor:e.contentBg,backgroundClip:"padding-box",border:0,borderRadius:e.borderRadiusLG,boxShadow:e.boxShadow,pointerEvents:"auto",padding:e.contentPadding},["".concat(t,"-close")]:Object.assign({position:"absolute",top:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),insetInlineEnd:e.calc(e.modalHeaderHeight).sub(e.modalCloseBtnSize).div(2).equal(),zIndex:e.calc(e.zIndexPopupBase).add(10).equal(),padding:0,color:e.modalCloseIconColor,fontWeight:e.fontWeightStrong,lineHeight:1,textDecoration:"none",background:"transparent",borderRadius:e.borderRadiusSM,width:e.modalCloseBtnSize,height:e.modalCloseBtnSize,border:0,outline:0,cursor:"pointer",transition:"color ".concat(e.motionDurationMid,", background-color ").concat(e.motionDurationMid),"&-x":{display:"flex",fontSize:e.fontSizeLG,fontStyle:"normal",lineHeight:"".concat((0,eg.bf)(e.modalCloseBtnSize)),justifyContent:"center",textTransform:"none",textRendering:"auto"},"&:hover":{color:e.modalIconHoverColor,backgroundColor:e.closeBtnHoverBg,textDecoration:"none"},"&:active":{backgroundColor:e.closeBtnActiveBg}},(0,el.Qy)(e)),["".concat(t,"-header")]:{color:e.colorText,background:e.headerBg,borderRadius:"".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)," 0 0"),marginBottom:e.headerMarginBottom,padding:e.headerPadding,borderBottom:e.headerBorderBottom},["".concat(t,"-body")]:{fontSize:e.fontSize,lineHeight:e.lineHeight,wordWrap:"break-word",padding:e.bodyPadding},["".concat(t,"-footer")]:{textAlign:"end",background:e.footerBg,marginTop:e.footerMarginTop,padding:e.footerPadding,borderTop:e.footerBorderTop,borderRadius:e.footerBorderRadius,["> ".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginInlineStart:e.marginXS}},["".concat(t,"-open")]:{overflow:"hidden"}})},{["".concat(t,"-pure-panel")]:{top:"auto",padding:0,display:"flex",flexDirection:"column",["".concat(t,"-content,\n ").concat(t,"-body,\n ").concat(t,"-confirm-body-wrapper")]:{display:"flex",flexDirection:"column",flex:"auto"},["".concat(t,"-confirm-body")]:{marginBottom:"auto"}}}]},eE=e=>{let{componentCls:t}=e;return{["".concat(t,"-root")]:{["".concat(t,"-wrap-rtl")]:{direction:"rtl",["".concat(t,"-confirm-body")]:{direction:"rtl"}}}}},eh=e=>{let t=e.padding,n=e.fontSizeHeading5,a=e.lineHeightHeading5;return(0,ed.TS)(e,{modalHeaderHeight:e.calc(e.calc(a).mul(n).equal()).add(e.calc(t).mul(2).equal()).equal(),modalFooterBorderColorSplit:e.colorSplit,modalFooterBorderStyle:e.lineType,modalFooterBorderWidth:e.lineWidth,modalIconHoverColor:e.colorIconHover,modalCloseIconColor:e.colorIcon,modalCloseBtnSize:e.fontHeight,modalConfirmIconSize:e.fontHeight,modalTitleHeight:e.calc(e.titleFontSize).mul(e.titleLineHeight).equal()})},eS=e=>({footerBg:"transparent",headerBg:e.colorBgElevated,titleLineHeight:e.lineHeightHeading5,titleFontSize:e.fontSizeHeading5,contentBg:e.colorBgElevated,titleColor:e.colorTextHeading,closeBtnHoverBg:e.wireframe?"transparent":e.colorFillContent,closeBtnActiveBg:e.wireframe?"transparent":e.colorFillContentHover,contentPadding:e.wireframe?0:"".concat((0,eg.bf)(e.paddingMD)," ").concat((0,eg.bf)(e.paddingContentHorizontalLG)),headerPadding:e.wireframe?"".concat((0,eg.bf)(e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,headerBorderBottom:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",headerMarginBottom:e.wireframe?0:e.marginXS,bodyPadding:e.wireframe?e.paddingLG:0,footerPadding:e.wireframe?"".concat((0,eg.bf)(e.paddingXS)," ").concat((0,eg.bf)(e.padding)):0,footerBorderTop:e.wireframe?"".concat((0,eg.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorSplit):"none",footerBorderRadius:e.wireframe?"0 0 ".concat((0,eg.bf)(e.borderRadiusLG)," ").concat((0,eg.bf)(e.borderRadiusLG)):0,footerMarginTop:e.wireframe?0:e.marginSM,confirmBodyPadding:e.wireframe?"".concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(2*e.padding)," ").concat((0,eg.bf)(e.paddingLG)):0,confirmIconMarginInlineEnd:e.wireframe?e.margin:e.marginSM,confirmBtnsMarginTop:e.wireframe?e.marginLG:e.marginSM});var ey=(0,ep.I$)("Modal",e=>{let t=eh(e);return[ef(t),eE(t),eb(t),(0,eu._y)(t,"zoom")]},eS,{unitless:{titleLineHeight:!0}}),eT=n(64024),eA=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};(0,K.Z)()&&window.document.documentElement&&document.documentElement.addEventListener("click",e=>{a={x:e.pageX,y:e.pageY},setTimeout(()=>{a=null},100)},!0);var eR=e=>{var t;let{getPopupContainer:n,getPrefixCls:r,direction:o,modal:l}=i.useContext(s.E_),c=t=>{let{onCancel:n}=e;null==n||n(t)},{prefixCls:u,className:d,rootClassName:p,open:g,wrapClassName:E,centered:h,getContainer:S,closeIcon:y,closable:T,focusTriggerAfterClose:A=!0,style:R,visible:I,width:N=520,footer:_,classNames:w,styles:k}=e,C=eA(e,["prefixCls","className","rootClassName","open","wrapClassName","centered","getContainer","closeIcon","closable","focusTriggerAfterClose","style","visible","width","footer","classNames","styles"]),O=r("modal",u),x=r(),L=(0,eT.Z)(O),[D,P,M]=ey(O,L),F=m()(E,{["".concat(O,"-centered")]:!!h,["".concat(O,"-wrap-rtl")]:"rtl"===o}),U=null!==_&&i.createElement(es,Object.assign({},e,{onOk:t=>{let{onOk:n}=e;null==n||n(t)},onCancel:c})),[B,G]=Y(T,y,e=>eo(O,e),i.createElement(v.Z,{className:"".concat(O,"-close-icon")}),!0),$=function(e){let t=i.useContext(et),n=i.useRef();return(0,J.zX)(a=>{if(a){let r=e?a.querySelector(e):a;t.add(r),n.current=r}else t.remove(n.current)})}(".".concat(O,"-content")),[H,z]=(0,b.Cn)("Modal",C.zIndex);return D(i.createElement(Q.BR,null,i.createElement(X.Ux,{status:!0,override:!0},i.createElement(Z.Z.Provider,{value:z},i.createElement(q,Object.assign({width:N},C,{zIndex:H,getContainer:void 0===S?n:S,prefixCls:O,rootClassName:m()(P,p,M,L),footer:U,visible:null!=g?g:I,mousePosition:null!==(t=C.mousePosition)&&void 0!==t?t:a,onClose:c,closable:B,closeIcon:G,focusTriggerAfterClose:A,transitionName:(0,f.m)(x,"zoom",e.transitionName),maskTransitionName:(0,f.m)(x,"fade",e.maskTransitionName),className:m()(P,d,null==l?void 0:l.className),style:Object.assign(Object.assign({},null==l?void 0:l.style),R),classNames:Object.assign(Object.assign({wrapper:F},null==l?void 0:l.classNames),w),styles:Object.assign(Object.assign({},null==l?void 0:l.styles),k),panelRef:$}))))))};let eI=e=>{let{componentCls:t,titleFontSize:n,titleLineHeight:a,modalConfirmIconSize:r,fontSize:i,lineHeight:o,modalTitleHeight:s,fontHeight:l,confirmBodyPadding:c}=e,u="".concat(t,"-confirm");return{[u]:{"&-rtl":{direction:"rtl"},["".concat(e.antCls,"-modal-header")]:{display:"none"},["".concat(u,"-body-wrapper")]:Object.assign({},(0,el.dF)()),["&".concat(t," ").concat(t,"-body")]:{padding:c},["".concat(u,"-body")]:{display:"flex",flexWrap:"nowrap",alignItems:"start",["> ".concat(e.iconCls)]:{flex:"none",fontSize:r,marginInlineEnd:e.confirmIconMarginInlineEnd,marginTop:e.calc(e.calc(l).sub(r).equal()).div(2).equal()},["&-has-title > ".concat(e.iconCls)]:{marginTop:e.calc(e.calc(s).sub(r).equal()).div(2).equal()}},["".concat(u,"-paragraph")]:{display:"flex",flexDirection:"column",flex:"auto",rowGap:e.marginXS,maxWidth:"calc(100% - ".concat((0,eg.bf)(e.calc(e.modalConfirmIconSize).add(e.marginSM).equal()),")")},["".concat(u,"-title")]:{color:e.colorTextHeading,fontWeight:e.fontWeightStrong,fontSize:n,lineHeight:a},["".concat(u,"-content")]:{color:e.colorText,fontSize:i,lineHeight:o},["".concat(u,"-btns")]:{textAlign:"end",marginTop:e.confirmBtnsMarginTop,["".concat(e.antCls,"-btn + ").concat(e.antCls,"-btn")]:{marginBottom:0,marginInlineStart:e.marginXS}}},["".concat(u,"-error ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorError},["".concat(u,"-warning ").concat(u,"-body > ").concat(e.iconCls,",\n ").concat(u,"-confirm ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorWarning},["".concat(u,"-info ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorInfo},["".concat(u,"-success ").concat(u,"-body > ").concat(e.iconCls)]:{color:e.colorSuccess}}};var eN=(0,ep.bk)(["Modal","confirm"],e=>[eI(eh(e))],eS,{order:-1e3}),e_=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n};function ev(e){let{prefixCls:t,icon:n,okText:a,cancelText:o,confirmPrefixCls:s,type:l,okCancel:g,footer:b,locale:f}=e,h=e_(e,["prefixCls","icon","okText","cancelText","confirmPrefixCls","type","okCancel","footer","locale"]),S=n;if(!n&&null!==n)switch(l){case"info":S=i.createElement(p.Z,null);break;case"success":S=i.createElement(c.Z,null);break;case"error":S=i.createElement(u.Z,null);break;default:S=i.createElement(d.Z,null)}let y=null!=g?g:"confirm"===l,T=null!==e.autoFocusButton&&(e.autoFocusButton||"ok"),[A]=(0,E.Z)("Modal"),R=f||A,v=a||(y?null==R?void 0:R.okText:null==R?void 0:R.justOkText),w=Object.assign({autoFocusButton:T,cancelTextLocale:o||(null==R?void 0:R.cancelText),okTextLocale:v,mergedOkCancel:y},h),k=i.useMemo(()=>w,(0,r.Z)(Object.values(w))),C=i.createElement(i.Fragment,null,i.createElement(N,null),i.createElement(_,null)),O=void 0!==e.title&&null!==e.title,x="".concat(s,"-body");return i.createElement("div",{className:"".concat(s,"-body-wrapper")},i.createElement("div",{className:m()(x,{["".concat(x,"-has-title")]:O})},S,i.createElement("div",{className:"".concat(s,"-paragraph")},O&&i.createElement("span",{className:"".concat(s,"-title")},e.title),i.createElement("div",{className:"".concat(s,"-content")},e.content))),void 0===b||"function"==typeof b?i.createElement(I,{value:k},i.createElement("div",{className:"".concat(s,"-btns")},"function"==typeof b?b(C,{OkBtn:_,CancelBtn:N}):C)):b,i.createElement(eN,{prefixCls:t}))}let ew=e=>{let{close:t,zIndex:n,afterClose:a,open:r,keyboard:o,centered:s,getContainer:l,maskStyle:c,direction:u,prefixCls:d,wrapClassName:p,rootPrefixCls:g,bodyStyle:E,closable:S=!1,closeIcon:y,modalRender:T,focusTriggerAfterClose:A,onConfirm:R,styles:I}=e,N="".concat(d,"-confirm"),_=e.width||416,v=e.style||{},w=void 0===e.mask||e.mask,k=void 0!==e.maskClosable&&e.maskClosable,C=m()(N,"".concat(N,"-").concat(e.type),{["".concat(N,"-rtl")]:"rtl"===u},e.className),[,O]=(0,h.ZP)(),x=i.useMemo(()=>void 0!==n?n:O.zIndexPopupBase+b.u6,[n,O]);return i.createElement(eR,{prefixCls:d,className:C,wrapClassName:m()({["".concat(N,"-centered")]:!!e.centered},p),onCancel:()=>{null==t||t({triggerCancel:!0}),null==R||R(!1)},open:r,title:"",footer:null,transitionName:(0,f.m)(g||"","zoom",e.transitionName),maskTransitionName:(0,f.m)(g||"","fade",e.maskTransitionName),mask:w,maskClosable:k,style:v,styles:Object.assign({body:E,mask:c},I),width:_,zIndex:x,afterClose:a,keyboard:o,centered:s,getContainer:l,closable:S,closeIcon:y,modalRender:T,focusTriggerAfterClose:A},i.createElement(ev,Object.assign({},e,{confirmPrefixCls:N})))};var ek=e=>{let{rootPrefixCls:t,iconPrefixCls:n,direction:a,theme:r}=e;return i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:n,direction:a,theme:r},i.createElement(ew,Object.assign({},e)))},eC=[];let eO="",ex=e=>{var t,n;let{prefixCls:a,getContainer:r,direction:o}=e,l=(0,ei.A)(),c=(0,i.useContext)(s.E_),u=eO||c.getPrefixCls(),d=a||"".concat(u,"-modal"),p=r;return!1===p&&(p=void 0),i.createElement(ek,Object.assign({},e,{rootPrefixCls:u,prefixCls:d,iconPrefixCls:c.iconPrefixCls,theme:c.theme,direction:null!=o?o:c.direction,locale:null!==(n=null===(t=c.locale)||void 0===t?void 0:t.Modal)&&void 0!==n?n:l,getContainer:p}))};function eL(e){let t;let n=(0,l.w6)(),a=document.createDocumentFragment(),s=Object.assign(Object.assign({},e),{close:d,open:!0});function c(){for(var t=arguments.length,n=Array(t),i=0;ie&&e.triggerCancel);e.onCancel&&s&&e.onCancel.apply(e,[()=>{}].concat((0,r.Z)(n.slice(1))));for(let e=0;e{let t=n.getPrefixCls(void 0,eO),r=n.getIconPrefixCls(),s=n.getTheme(),c=i.createElement(ex,Object.assign({},e));(0,o.s)(i.createElement(l.ZP,{prefixCls:t,iconPrefixCls:r,theme:s},n.holderRender?n.holderRender(c):c),a)})}function d(){for(var t=arguments.length,n=Array(t),a=0;a{"function"==typeof e.afterClose&&e.afterClose(),c.apply(this,n)}})).visible&&delete s.visible,u(s)}return u(s),eC.push(d),{destroy:d,update:function(e){u(s="function"==typeof e?e(s):Object.assign(Object.assign({},s),e))}}}function eD(e){return Object.assign(Object.assign({},e),{type:"warning"})}function eP(e){return Object.assign(Object.assign({},e),{type:"info"})}function eM(e){return Object.assign(Object.assign({},e),{type:"success"})}function eF(e){return Object.assign(Object.assign({},e),{type:"error"})}function eU(e){return Object.assign(Object.assign({},e),{type:"confirm"})}var eB=n(93942),eG=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},e$=(0,eB.i)(e=>{let{prefixCls:t,className:n,closeIcon:a,closable:r,type:o,title:l,children:c,footer:u}=e,d=eG(e,["prefixCls","className","closeIcon","closable","type","title","children","footer"]),{getPrefixCls:p}=i.useContext(s.E_),g=p(),b=t||p("modal"),f=(0,eT.Z)(g),[E,h,S]=ey(b,f),y="".concat(b,"-confirm"),T={};return T=o?{closable:null!=r&&r,title:"",footer:"",children:i.createElement(ev,Object.assign({},e,{prefixCls:b,confirmPrefixCls:y,rootPrefixCls:g,content:c}))}:{closable:null==r||r,title:l,footer:null!==u&&i.createElement(es,Object.assign({},e)),children:c},E(i.createElement(z,Object.assign({prefixCls:b,className:m()(h,"".concat(b,"-pure-panel"),o&&y,o&&"".concat(y,"-").concat(o),n,S,f)},d,{closeIcon:eo(b,a),closable:r},T)))}),eH=n(13823),ez=function(e,t){var n={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(n[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var r=0,a=Object.getOwnPropertySymbols(e);rt.indexOf(a[r])&&Object.prototype.propertyIsEnumerable.call(e,a[r])&&(n[a[r]]=e[a[r]]);return n},ej=i.forwardRef((e,t)=>{var n,{afterClose:a,config:o}=e,l=ez(e,["afterClose","config"]);let[c,u]=i.useState(!0),[d,p]=i.useState(o),{direction:g,getPrefixCls:m}=i.useContext(s.E_),b=m("modal"),f=m(),h=function(){u(!1);for(var e=arguments.length,t=Array(e),n=0;ne&&e.triggerCancel);d.onCancel&&a&&d.onCancel.apply(d,[()=>{}].concat((0,r.Z)(t.slice(1))))};i.useImperativeHandle(t,()=>({destroy:h,update:e=>{p(t=>Object.assign(Object.assign({},t),e))}}));let S=null!==(n=d.okCancel)&&void 0!==n?n:"confirm"===d.type,[y]=(0,E.Z)("Modal",eH.Z.Modal);return i.createElement(ek,Object.assign({prefixCls:b,rootPrefixCls:f},d,{close:h,open:c,afterClose:()=>{var e;a(),null===(e=d.afterClose)||void 0===e||e.call(d)},okText:d.okText||(S?null==y?void 0:y.okText:null==y?void 0:y.justOkText),direction:d.direction||g,cancelText:d.cancelText||(null==y?void 0:y.cancelText)},l))});let eV=0,eW=i.memo(i.forwardRef((e,t)=>{let[n,a]=function(){let[e,t]=i.useState([]);return[e,i.useCallback(e=>(t(t=>[].concat((0,r.Z)(t),[e])),()=>{t(t=>t.filter(t=>t!==e))}),[])]}();return i.useImperativeHandle(t,()=>({patchElement:a}),[]),i.createElement(i.Fragment,null,n)}));function eq(e){return eL(eD(e))}eR.useModal=function(){let e=i.useRef(null),[t,n]=i.useState([]);i.useEffect(()=>{t.length&&((0,r.Z)(t).forEach(e=>{e()}),n([]))},[t]);let a=i.useCallback(t=>function(a){var o;let s,l;eV+=1;let c=i.createRef(),u=new Promise(e=>{s=e}),d=!1,p=i.createElement(ej,{key:"modal-".concat(eV),config:t(a),ref:c,afterClose:()=>{null==l||l()},isSilent:()=>d,onConfirm:e=>{s(e)}});return(l=null===(o=e.current)||void 0===o?void 0:o.patchElement(p))&&eC.push(l),{destroy:()=>{function e(){var e;null===(e=c.current)||void 0===e||e.destroy()}c.current?e():n(t=>[].concat((0,r.Z)(t),[e]))},update:e=>{function t(){var t;null===(t=c.current)||void 0===t||t.update(e)}c.current?t():n(e=>[].concat((0,r.Z)(e),[t]))},then:e=>(d=!0,u.then(e))}},[]);return[i.useMemo(()=>({info:a(eP),success:a(eM),error:a(eF),warning:a(eD),confirm:a(eU)}),[]),i.createElement(eW,{key:"modal-holder",ref:e})]},eR.info=function(e){return eL(eP(e))},eR.success=function(e){return eL(eM(e))},eR.error=function(e){return eL(eF(e))},eR.warning=eq,eR.warn=eq,eR.confirm=function(e){return eL(eU(e))},eR.destroyAll=function(){for(;eC.length;){let e=eC.pop();e&&e()}},eR.config=function(e){let{rootPrefixCls:t}=e;eO=t},eR._InternalPanelDoNotUseOrYouWillBeFired=e$;var eY=eR},11699:function(e,t,n){"use strict";n.d(t,{J$:function(){return s}});var a=n(352),r=n(37133);let i=new a.E4("antFadeIn",{"0%":{opacity:0},"100%":{opacity:1}}),o=new a.E4("antFadeOut",{"0%":{opacity:1},"100%":{opacity:0}}),s=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],{antCls:n}=e,a="".concat(n,"-fade"),s=t?"&":"";return[(0,r.R)(a,i,o,e.motionDurationMid,t),{["\n ".concat(s).concat(a,"-enter,\n ").concat(s).concat(a,"-appear\n ")]:{opacity:0,animationTimingFunction:"linear"},["".concat(s).concat(a,"-leave")]:{animationTimingFunction:"linear"}}]}},26035:function(e){"use strict";e.exports=function(e,n){for(var a,r,i,o=e||"",s=n||"div",l={},c=0;c4&&m.slice(0,4)===o&&s.test(t)&&("-"===t.charAt(4)?b=o+(n=t.slice(5).replace(l,d)).charAt(0).toUpperCase()+n.slice(1):(g=(p=t).slice(4),t=l.test(g)?p:("-"!==(g=g.replace(c,u)).charAt(0)&&(g="-"+g),o+g)),f=r),new f(b,t))};var s=/^data[-\w.:]+$/i,l=/-[a-z]/g,c=/[A-Z]/g;function u(e){return"-"+e.toLowerCase()}function d(e){return e.charAt(1).toUpperCase()}},30466:function(e,t,n){"use strict";var a=n(82855),r=n(64541),i=n(80808),o=n(44987),s=n(72731),l=n(98946);e.exports=a([i,r,o,s,l])},72731:function(e,t,n){"use strict";var a=n(20321),r=n(41757),i=a.booleanish,o=a.number,s=a.spaceSeparated;e.exports=r({transform:function(e,t){return"role"===t?t:"aria-"+t.slice(4).toLowerCase()},properties:{ariaActiveDescendant:null,ariaAtomic:i,ariaAutoComplete:null,ariaBusy:i,ariaChecked:i,ariaColCount:o,ariaColIndex:o,ariaColSpan:o,ariaControls:s,ariaCurrent:null,ariaDescribedBy:s,ariaDetails:null,ariaDisabled:i,ariaDropEffect:s,ariaErrorMessage:null,ariaExpanded:i,ariaFlowTo:s,ariaGrabbed:i,ariaHasPopup:null,ariaHidden:i,ariaInvalid:null,ariaKeyShortcuts:null,ariaLabel:null,ariaLabelledBy:s,ariaLevel:o,ariaLive:null,ariaModal:i,ariaMultiLine:i,ariaMultiSelectable:i,ariaOrientation:null,ariaOwns:s,ariaPlaceholder:null,ariaPosInSet:o,ariaPressed:i,ariaReadOnly:i,ariaRelevant:null,ariaRequired:i,ariaRoleDescription:s,ariaRowCount:o,ariaRowIndex:o,ariaRowSpan:o,ariaSelected:i,ariaSetSize:o,ariaSort:null,ariaValueMax:o,ariaValueMin:o,ariaValueNow:o,ariaValueText:null,role:null}})},98946:function(e,t,n){"use strict";var a=n(20321),r=n(41757),i=n(53296),o=a.boolean,s=a.overloadedBoolean,l=a.booleanish,c=a.number,u=a.spaceSeparated,d=a.commaSeparated;e.exports=r({space:"html",attributes:{acceptcharset:"accept-charset",classname:"class",htmlfor:"for",httpequiv:"http-equiv"},transform:i,mustUseProperty:["checked","multiple","muted","selected"],properties:{abbr:null,accept:d,acceptCharset:u,accessKey:u,action:null,allow:null,allowFullScreen:o,allowPaymentRequest:o,allowUserMedia:o,alt:null,as:null,async:o,autoCapitalize:null,autoComplete:u,autoFocus:o,autoPlay:o,capture:o,charSet:null,checked:o,cite:null,className:u,cols:c,colSpan:null,content:null,contentEditable:l,controls:o,controlsList:u,coords:c|d,crossOrigin:null,data:null,dateTime:null,decoding:null,default:o,defer:o,dir:null,dirName:null,disabled:o,download:s,draggable:l,encType:null,enterKeyHint:null,form:null,formAction:null,formEncType:null,formMethod:null,formNoValidate:o,formTarget:null,headers:u,height:c,hidden:o,high:c,href:null,hrefLang:null,htmlFor:u,httpEquiv:u,id:null,imageSizes:null,imageSrcSet:d,inputMode:null,integrity:null,is:null,isMap:o,itemId:null,itemProp:u,itemRef:u,itemScope:o,itemType:u,kind:null,label:null,lang:null,language:null,list:null,loading:null,loop:o,low:c,manifest:null,max:null,maxLength:c,media:null,method:null,min:null,minLength:c,multiple:o,muted:o,name:null,nonce:null,noModule:o,noValidate:o,onAbort:null,onAfterPrint:null,onAuxClick:null,onBeforePrint:null,onBeforeUnload:null,onBlur:null,onCancel:null,onCanPlay:null,onCanPlayThrough:null,onChange:null,onClick:null,onClose:null,onContextMenu:null,onCopy:null,onCueChange:null,onCut:null,onDblClick:null,onDrag:null,onDragEnd:null,onDragEnter:null,onDragExit:null,onDragLeave:null,onDragOver:null,onDragStart:null,onDrop:null,onDurationChange:null,onEmptied:null,onEnded:null,onError:null,onFocus:null,onFormData:null,onHashChange:null,onInput:null,onInvalid:null,onKeyDown:null,onKeyPress:null,onKeyUp:null,onLanguageChange:null,onLoad:null,onLoadedData:null,onLoadedMetadata:null,onLoadEnd:null,onLoadStart:null,onMessage:null,onMessageError:null,onMouseDown:null,onMouseEnter:null,onMouseLeave:null,onMouseMove:null,onMouseOut:null,onMouseOver:null,onMouseUp:null,onOffline:null,onOnline:null,onPageHide:null,onPageShow:null,onPaste:null,onPause:null,onPlay:null,onPlaying:null,onPopState:null,onProgress:null,onRateChange:null,onRejectionHandled:null,onReset:null,onResize:null,onScroll:null,onSecurityPolicyViolation:null,onSeeked:null,onSeeking:null,onSelect:null,onSlotChange:null,onStalled:null,onStorage:null,onSubmit:null,onSuspend:null,onTimeUpdate:null,onToggle:null,onUnhandledRejection:null,onUnload:null,onVolumeChange:null,onWaiting:null,onWheel:null,open:o,optimum:c,pattern:null,ping:u,placeholder:null,playsInline:o,poster:null,preload:null,readOnly:o,referrerPolicy:null,rel:u,required:o,reversed:o,rows:c,rowSpan:c,sandbox:u,scope:null,scoped:o,seamless:o,selected:o,shape:null,size:c,sizes:null,slot:null,span:c,spellCheck:l,src:null,srcDoc:null,srcLang:null,srcSet:d,start:c,step:null,style:null,tabIndex:c,target:null,title:null,translate:null,type:null,typeMustMatch:o,useMap:null,value:l,width:c,wrap:null,align:null,aLink:null,archive:u,axis:null,background:null,bgColor:null,border:c,borderColor:null,bottomMargin:c,cellPadding:null,cellSpacing:null,char:null,charOff:null,classId:null,clear:null,code:null,codeBase:null,codeType:null,color:null,compact:o,declare:o,event:null,face:null,frame:null,frameBorder:null,hSpace:c,leftMargin:c,link:null,longDesc:null,lowSrc:null,marginHeight:c,marginWidth:c,noResize:o,noHref:o,noShade:o,noWrap:o,object:null,profile:null,prompt:null,rev:null,rightMargin:c,rules:null,scheme:null,scrolling:l,standby:null,summary:null,text:null,topMargin:c,valueType:null,version:null,vAlign:null,vLink:null,vSpace:c,allowTransparency:null,autoCorrect:null,autoSave:null,disablePictureInPicture:o,disableRemotePlayback:o,prefix:null,property:null,results:c,security:null,unselectable:null}})},53296:function(e,t,n){"use strict";var a=n(38781);e.exports=function(e,t){return a(e,t.toLowerCase())}},38781:function(e){"use strict";e.exports=function(e,t){return t in e?e[t]:t}},41757:function(e,t,n){"use strict";var a=n(96532),r=n(61723),i=n(51351);e.exports=function(e){var t,n,o=e.space,s=e.mustUseProperty||[],l=e.attributes||{},c=e.properties,u=e.transform,d={},p={};for(t in c)n=new i(t,u(l,t),c[t],o),-1!==s.indexOf(t)&&(n.mustUseProperty=!0),d[t]=n,p[a(t)]=t,p[a(n.attribute)]=t;return new r(d,p,o)}},51351:function(e,t,n){"use strict";var a=n(24192),r=n(20321);e.exports=s,s.prototype=new a,s.prototype.defined=!0;var i=["boolean","booleanish","overloadedBoolean","number","commaSeparated","spaceSeparated","commaOrSpaceSeparated"],o=i.length;function s(e,t,n,s){var l,c,u,d=-1;for(s&&(this.space=s),a.call(this,e,t);++d