diff --git a/.circleci/config.yml b/.circleci/config.yml index 63b06e6f2b..0ebf912703 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -563,8 +563,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.13 command: | @@ -1770,8 +1771,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | @@ -1908,8 +1910,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | @@ -2050,8 +2053,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | @@ -2234,8 +2238,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | @@ -2342,8 +2347,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | @@ -2475,8 +2481,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version sudo systemctl restart docker - run: name: Install Python 3.9 @@ -2684,8 +2691,9 @@ jobs: - run: name: Install Docker CLI (In case it's not already installed) command: | - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version - run: name: Install Python 3.9 command: | diff --git a/docs/my-website/docs/adding_provider/simple_guardrail_tutorial.md b/docs/my-website/docs/adding_provider/simple_guardrail_tutorial.md new file mode 100644 index 0000000000..2722a4a024 --- /dev/null +++ b/docs/my-website/docs/adding_provider/simple_guardrail_tutorial.md @@ -0,0 +1,184 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Adding a New Guardrail Integration + +You're going to create a class that checks text before it goes to the LLM or after it comes back. If it violates your rules, you block it. + +## How It Works + +Request with guardrail: + +```bash +curl --location 'http://localhost:4000/chat/completions' \ +--header 'Authorization: Bearer sk-1234' \ +--header 'Content-Type: application/json' \ +--data '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "How do I hack a system?"}], + "guardrails": ["my-guardrail"] +}' +``` + +Your guardrail checks input, then output. If something's wrong, raise an exception. + +## Build Your Guardrail + +### Create Your Directory + +```bash +mkdir -p litellm/proxy/guardrails/guardrail_hooks/my_guardrail +cd litellm/proxy/guardrails/guardrail_hooks/my_guardrail +``` + +Two files: `my_guardrail.py` (main class) and `__init__.py` (initialization). + +### Write the Main Class + +`my_guardrail.py`: + +```python +import os +from typing import Optional, List +from fastapi import HTTPException + +from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.types.guardrails import PiiEntityType +from litellm._logging import verbose_proxy_logger +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) + +class MyGuardrail(CustomGuardrail): + def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs): + self.api_key = api_key or os.getenv("MY_GUARDRAIL_API_KEY") + self.api_base = api_base or os.getenv("MY_GUARDRAIL_API_BASE", "https://api.myguardrail.com") + super().__init__(default_on=True) + + async def apply_guardrail( + self, + text: str, + language: Optional[str] = None, + entities: Optional[List[PiiEntityType]] = None, + request_data: Optional[dict] = None, + ) -> str: + result = await self._check_with_api(text, request_data) + + if result.get("action") == "BLOCK": + raise Exception(f"Content blocked: {result.get('reason', 'Policy violation')}") + + return text + + async def _check_with_api(self, text: str, request_data: Optional[dict]) -> dict: + async_client = get_async_httpx_client(llm_provider=httpxSpecialProvider.LoggingCallback) + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}", + } + + response = await async_client.post( + f"{self.api_base}/check", + headers=headers, + json={"text": text}, + timeout=5, + ) + + response.raise_for_status() + return response.json() +``` + +### Create the Init File + +`__init__.py`: + +```python +from typing import TYPE_CHECKING + +from litellm.types.guardrails import SupportedGuardrailIntegrations + +from .my_guardrail import MyGuardrail + +if TYPE_CHECKING: + from litellm.types.guardrails import Guardrail, LitellmParams + + +def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail"): + import litellm + + _my_guardrail_callback = MyGuardrail( + api_base=litellm_params.api_base, + api_key=litellm_params.api_key, + guardrail_name=guardrail.get("guardrail_name", ""), + event_hook=litellm_params.mode, + default_on=litellm_params.default_on, + ) + + litellm.logging_callback_manager.add_litellm_callback(_my_guardrail_callback) + return _my_guardrail_callback + + +guardrail_initializer_registry = { + SupportedGuardrailIntegrations.MY_GUARDRAIL.value: initialize_guardrail, +} + +guardrail_class_registry = { + SupportedGuardrailIntegrations.MY_GUARDRAIL.value: MyGuardrail, +} +``` + +### Register Your Guardrail Type + +Add to `litellm/types/guardrails.py`: + +```python +class SupportedGuardrailIntegrations(str, Enum): + LAKERA = "lakera_prompt_injection" + APORIA = "aporia" + BEDROCK = "bedrock_guardrails" + PRESIDIO = "presidio" + ZSCALER_AI_GUARD = "zscaler_ai_guard" + MY_GUARDRAIL = "my_guardrail" +``` + +## Usage + +### Config File + +```yaml +model_list: + - model_name: gpt-4 + litellm_params: + model: gpt-4 + api_key: os.environ/OPENAI_API_KEY + +litellm_settings: + guardrails: + - guardrail_name: my_guardrail + litellm_params: + guardrail: my_guardrail + mode: during_call + api_key: os.environ/MY_GUARDRAIL_API_KEY + api_base: https://api.myguardrail.com +``` + +### Per-Request + +```bash +curl --location 'http://localhost:4000/chat/completions' \ +--header 'Authorization: Bearer sk-1234' \ +--header 'Content-Type: application/json' \ +--data '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Test message"}], + "guardrails": ["my_guardrail"] +}' +``` + +## Testing + +Add unit tests inside `test_litellm/` folder. + + + diff --git a/docs/my-website/docs/providers/bedrock_batches.md b/docs/my-website/docs/providers/bedrock_batches.md index c262eef0e8..a1116f4107 100644 --- a/docs/my-website/docs/providers/bedrock_batches.md +++ b/docs/my-website/docs/providers/bedrock_batches.md @@ -40,6 +40,8 @@ model_list: s3_access_key_id: os.environ/AWS_ACCESS_KEY_ID s3_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY aws_batch_role_arn: arn:aws:iam::888602223428:role/service-role/AmazonBedrockExecutionRoleForAgents_BB9HNW6V4CV + # Optional: Custom KMS encryption key for S3 output + # s3_encryption_key_id: arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012 model_info: mode: batch # 👈 SPECIFY MODE AS BATCH, to tell user this is a batch model ``` @@ -55,6 +57,12 @@ model_list: | `aws_batch_role_arn` | IAM role ARN for Bedrock batch operations. Bedrock Batch APIs require an IAM role ARN to be set. | | `mode: batch` | Indicates to LiteLLM this is a batch model | +**Optional Parameters:** + +| Parameter | Description | +|-----------|-------------| +| `s3_encryption_key_id` | Custom KMS encryption key ID for S3 output data. If not specified, Bedrock uses AWS managed encryption keys. | + ### 2. Create Virtual Key ```bash showLineNumbers title="create_virtual_key.sh" @@ -174,6 +182,29 @@ When a `target_model_names` is specified, the file is written to the S3 bucket c LiteLLM only supports Bedrock Anthropic Models for Batch API. If you want other bedrock models file an issue [here](https://github.com/BerriAI/litellm/issues/new/choose). +### How do I use a custom KMS encryption key? + +If your S3 bucket requires a custom KMS encryption key, you can specify it in your configuration using `s3_encryption_key_id`. This is useful for enterprise customers with specific encryption requirements. + +You can set the encryption key in 2 ways: + +1. **In config.yaml** (recommended): +```yaml +model_list: + - model_name: "bedrock-batch-claude" + litellm_params: + model: bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0 + s3_encryption_key_id: arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012 + # ... other params +``` + +2. **As an environment variable**: +```bash +export AWS_S3_ENCRYPTION_KEY_ID=arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012 +``` + + + ## Further Reading - [AWS Bedrock Batch Inference Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/batch-inference.html) diff --git a/docs/my-website/docs/providers/fal_ai.md b/docs/my-website/docs/providers/fal_ai.md index d42182b57a..e50ef919da 100644 --- a/docs/my-website/docs/providers/fal_ai.md +++ b/docs/my-website/docs/providers/fal_ai.md @@ -31,6 +31,7 @@ Get your API key from [fal.ai](https://fal.ai/). | Model Name | Description | Documentation | |------------|-------------|---------------| +| `fal_ai/flux/schnell` | Flux Schnell - Low-latency generation with `image_size` support | [Docs ↗](https://fal.ai/models/fal-ai/flux/schnell) | | `fal_ai/fal-ai/flux-pro/v1.1-ultra` | FLUX Pro v1.1 Ultra - High-quality image generation | [Docs ↗](https://fal.ai/models/fal-ai/flux-pro/v1.1-ultra) | | `fal_ai/fal-ai/imagen4/preview` | Google's Imagen 4 - Highest quality model | [Docs ↗](https://fal.ai/models/fal-ai/imagen4/preview) | | `fal_ai/fal-ai/recraft/v3/text-to-image` | Recraft v3 - Multiple style options | [Docs ↗](https://fal.ai/models/fal-ai/recraft/v3/text-to-image) | diff --git a/docs/my-website/docs/providers/openai.md b/docs/my-website/docs/providers/openai.md index 51ebc881d2..e288f51155 100644 --- a/docs/my-website/docs/providers/openai.md +++ b/docs/my-website/docs/providers/openai.md @@ -412,7 +412,7 @@ Expected Response: ### Advanced: Using `reasoning_effort` with `summary` field -By default, `reasoning_effort` accepts a string value (`"low"`, `"medium"`, `"high"`, `"minimal"`) and only sets the effort level without including a reasoning summary. +By default, `reasoning_effort` accepts a string value (`"none"`, `"minimal"`, `"low"`, `"medium"`, `"high"`) and only sets the effort level without including a reasoning summary. To opt-in to the `summary` feature, you can pass `reasoning_effort` as a dictionary. **Note:** The `summary` field requires your OpenAI organization to have verification status. Using `summary` without verification will result in a 400 error from OpenAI. @@ -472,12 +472,17 @@ curl -X POST 'http://0.0.0.0:4000/chat/completions' \ | Model | Default (when not set) | Supported Values | |-------|----------------------|------------------| +| `gpt-5.1` | `none` | `none`, `low`, `medium`, `high` | | `gpt-5` | `medium` | `minimal`, `low`, `medium`, `high` | -| `gpt-5-mini` | `medium` | `minimal`, `low`, `medium`, `high` | +| `gpt-5-mini` | `medium` | `none`, `minimal`, `low`, `medium`, `high` | +| `gpt-5-nano` | `none` | `none`, `low`, `medium`, `high` | | `gpt-5-codex` | `adaptive` | `low`, `medium`, `high` (no `minimal`) | | `gpt-5-pro` | `high` | `high` only | -**Note:** `gpt-5-pro` only accepts `reasoning_effort="high"`. Other values will return an error. When `reasoning_effort` is not set (None), OpenAI defaults to the value shown in the "Default" column. +**Note:** +- GPT-5.1 introduced a new `reasoning_effort="none"` setting for faster, lower-latency responses. This replaces the `"minimal"` setting from GPT-5. +- `gpt-5-pro` only accepts `reasoning_effort="high"`. Other values will return an error. +- When `reasoning_effort` is not set (None), OpenAI defaults to the value shown in the "Default" column. See [OpenAI Reasoning documentation](https://platform.openai.com/docs/guides/reasoning) for more details on organization verification requirements. diff --git a/docs/my-website/docs/providers/vertex.md b/docs/my-website/docs/providers/vertex.md index 4d7e85f388..874b637e4d 100644 --- a/docs/my-website/docs/providers/vertex.md +++ b/docs/my-website/docs/providers/vertex.md @@ -1604,53 +1604,6 @@ litellm.vertex_location = "us-central1 # Your Location | gemini-2.5-flash-preview-09-2025 | `completion('gemini-2.5-flash-preview-09-2025', messages)`, `completion('vertex_ai/gemini-2.5-flash-preview-09-2025', messages)` | | gemini-2.5-flash-lite-preview-09-2025 | `completion('gemini-2.5-flash-lite-preview-09-2025', messages)`, `completion('vertex_ai/gemini-2.5-flash-lite-preview-09-2025', messages)` | -## Private Service Connect (PSC) Endpoints - -LiteLLM supports Vertex AI models deployed to Private Service Connect (PSC) endpoints, allowing you to use custom `api_base` URLs for private deployments. - -### Usage - -```python -from litellm import completion - -# Use PSC endpoint with custom api_base -response = completion( - model="vertex_ai/1234567890", # Numeric endpoint ID - messages=[{"role": "user", "content": "Hello!"}], - api_base="http://10.96.32.8", # Your PSC endpoint - vertex_project="my-project-id", - vertex_location="us-central1" -) -``` - -**Key Features:** -- Supports both numeric endpoint IDs and custom model names -- Works with both completion and embedding endpoints -- Automatically constructs full PSC URL: `{api_base}/v1/projects/{project}/locations/{location}/endpoints/{model}:{endpoint}` -- Compatible with streaming requests - -### Configuration - -Add PSC endpoints to your `config.yaml`: - -```yaml -model_list: - - model_name: psc-gemini - litellm_params: - model: vertex_ai/1234567890 # Numeric endpoint ID - api_base: "http://10.96.32.8" # Your PSC endpoint - vertex_project: "my-project-id" - vertex_location: "us-central1" - vertex_credentials: "/path/to/service_account.json" - - model_name: psc-embedding - litellm_params: - model: vertex_ai/text-embedding-004 - api_base: "http://10.96.32.8" # Your PSC endpoint - vertex_project: "my-project-id" - vertex_location: "us-central1" - vertex_credentials: "/path/to/service_account.json" -``` - ## Fine-tuned Models You can call fine-tuned Vertex AI Gemini models through LiteLLM @@ -2089,6 +2042,515 @@ curl http://0.0.0.0:4000/v1/chat/completions \ | code-gecko@latest| `completion('code-gecko@latest', messages)` | +## **Embedding Models** + +#### Usage - Embedding + + + + +```python +import litellm +from litellm import embedding +litellm.vertex_project = "hardy-device-38811" # Your Project ID +litellm.vertex_location = "us-central1" # proj location + +response = embedding( + model="vertex_ai/textembedding-gecko", + input=["good morning from litellm"], +) +print(response) +``` + + + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: snowflake-arctic-embed-m-long-1731622468876 + litellm_params: + model: vertex_ai/ + vertex_project: "adroit-crow-413218" + vertex_location: "us-central1" + vertex_credentials: adroit-crow-413218-a956eef1a2a8.json + +litellm_settings: + drop_params: True +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request using OpenAI Python SDK, Langchain Python SDK + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +response = client.embeddings.create( + model="snowflake-arctic-embed-m-long-1731622468876", + input = ["good morning from litellm", "this is another item"], +) + +print(response) +``` + + + + + +#### Supported Embedding Models +All models listed [here](https://github.com/BerriAI/litellm/blob/57f37f743886a0249f630a6792d49dffc2c5d9b7/model_prices_and_context_window.json#L835) are supported + +| Model Name | Function Call | +|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| text-embedding-004 | `embedding(model="vertex_ai/text-embedding-004", input)` | +| text-multilingual-embedding-002 | `embedding(model="vertex_ai/text-multilingual-embedding-002", input)` | +| textembedding-gecko | `embedding(model="vertex_ai/textembedding-gecko", input)` | +| textembedding-gecko-multilingual | `embedding(model="vertex_ai/textembedding-gecko-multilingual", input)` | +| textembedding-gecko-multilingual@001 | `embedding(model="vertex_ai/textembedding-gecko-multilingual@001", input)` | +| textembedding-gecko@001 | `embedding(model="vertex_ai/textembedding-gecko@001", input)` | +| textembedding-gecko@003 | `embedding(model="vertex_ai/textembedding-gecko@003", input)` | +| text-embedding-preview-0409 | `embedding(model="vertex_ai/text-embedding-preview-0409", input)` | +| text-multilingual-embedding-preview-0409 | `embedding(model="vertex_ai/text-multilingual-embedding-preview-0409", input)` | +| Fine-tuned OR Custom Embedding models | `embedding(model="vertex_ai/", input)` | + +### Supported OpenAI (Unified) Params + +| [param](../embedding/supported_embedding.md#input-params-for-litellmembedding) | type | [vertex equivalent](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api) | +|-------|-------------|--------------------| +| `input` | **string or List[string]** | `instances` | +| `dimensions` | **int** | `output_dimensionality` | +| `input_type` | **Literal["RETRIEVAL_QUERY","RETRIEVAL_DOCUMENT", "SEMANTIC_SIMILARITY", "CLASSIFICATION", "CLUSTERING", "QUESTION_ANSWERING", "FACT_VERIFICATION"]** | `task_type` | + +#### Usage with OpenAI (Unified) Params + + + + + +```python +response = litellm.embedding( + model="vertex_ai/text-embedding-004", + input=["good morning from litellm", "gm"] + input_type = "RETRIEVAL_DOCUMENT", + dimensions=1, +) +``` + + + + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +response = client.embeddings.create( + model="text-embedding-004", + input = ["good morning from litellm", "gm"], + dimensions=1, + extra_body = { + "input_type": "RETRIEVAL_QUERY", + } +) + +print(response) +``` + + + + +### Supported Vertex Specific Params + +| param | type | +|-------|-------------| +| `auto_truncate` | **bool** | +| `task_type` | **Literal["RETRIEVAL_QUERY","RETRIEVAL_DOCUMENT", "SEMANTIC_SIMILARITY", "CLASSIFICATION", "CLUSTERING", "QUESTION_ANSWERING", "FACT_VERIFICATION"]** | +| `title` | **str** | + +#### Usage with Vertex Specific Params (Use `task_type` and `title`) + +You can pass any vertex specific params to the embedding model. Just pass them to the embedding function like this: + +[Relevant Vertex AI doc with all embedding params](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api#request_body) + + + + +```python +response = litellm.embedding( + model="vertex_ai/text-embedding-004", + input=["good morning from litellm", "gm"] + task_type = "RETRIEVAL_DOCUMENT", + title = "test", + dimensions=1, + auto_truncate=True, +) +``` + + + + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +response = client.embeddings.create( + model="text-embedding-004", + input = ["good morning from litellm", "gm"], + dimensions=1, + extra_body = { + "task_type": "RETRIEVAL_QUERY", + "auto_truncate": True, + "title": "test", + } +) + +print(response) +``` + + + +## **Multi-Modal Embeddings** + + +Known Limitations: +- Only supports 1 image / video / image per request +- Only supports GCS or base64 encoded images / videos + +### Usage + + + + +Using GCS Images + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input="gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" # will be sent as a gcs image +) +``` + +Using base 64 encoded images + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input="data:image/jpeg;base64,..." # will be sent as a base64 encoded image +) +``` + + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: multimodalembedding@001 + litellm_params: + model: vertex_ai/multimodalembedding@001 + vertex_project: "adroit-crow-413218" + vertex_location: "us-central1" + vertex_credentials: adroit-crow-413218-a956eef1a2a8.json + +litellm_settings: + drop_params: True +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request use OpenAI Python SDK, Langchain Python SDK + + + + + + +Requests with GCS Image / Video URI + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", +) + +print(response) +``` + +Requests with base64 encoded images + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = "data:image/jpeg;base64,...", +) + +print(response) +``` + + + + + +Requests with GCS Image / Video URI +```python +from langchain_openai import OpenAIEmbeddings + +embeddings_models = "multimodalembedding@001" + +embeddings = OpenAIEmbeddings( + model="multimodalembedding@001", + base_url="http://0.0.0.0:4000", + api_key="sk-1234", # type: ignore +) + + +query_result = embeddings.embed_query( + "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" +) +print(query_result) + +``` + +Requests with base64 encoded images + +```python +from langchain_openai import OpenAIEmbeddings + +embeddings_models = "multimodalembedding@001" + +embeddings = OpenAIEmbeddings( + model="multimodalembedding@001", + base_url="http://0.0.0.0:4000", + api_key="sk-1234", # type: ignore +) + + +query_result = embeddings.embed_query( + "data:image/jpeg;base64,..." +) +print(query_result) + +``` + + + + + + + + + +1. Add model to config.yaml +```yaml +default_vertex_config: + vertex_project: "adroit-crow-413218" + vertex_location: "us-central1" + vertex_credentials: adroit-crow-413218-a956eef1a2a8.json +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request use OpenAI Python SDK + +```python +import vertexai + +from vertexai.vision_models import Image, MultiModalEmbeddingModel, Video +from vertexai.vision_models import VideoSegmentConfig +from google.auth.credentials import Credentials + + +LITELLM_PROXY_API_KEY = "sk-1234" +LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" + +import datetime + +class CredentialsWrapper(Credentials): + def __init__(self, token=None): + super().__init__() + self.token = token + self.expiry = None # or set to a future date if needed + + def refresh(self, request): + pass + + def apply(self, headers, token=None): + headers['Authorization'] = f'Bearer {self.token}' + + @property + def expired(self): + return False # Always consider the token as non-expired + + @property + def valid(self): + return True # Always consider the credentials as valid + +credentials = CredentialsWrapper(token=LITELLM_PROXY_API_KEY) + +vertexai.init( + project="adroit-crow-413218", + location="us-central1", + api_endpoint=LITELLM_PROXY_BASE, + credentials = credentials, + api_transport="rest", + +) + +model = MultiModalEmbeddingModel.from_pretrained("multimodalembedding") +image = Image.load_from_file( + "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" +) + +embeddings = model.get_embeddings( + image=image, + contextual_text="Colosseum", + dimension=1408, +) +print(f"Image Embedding: {embeddings.image_embedding}") +print(f"Text Embedding: {embeddings.text_embedding}") +``` + + + + + +### Text + Image + Video Embeddings + + + + +Text + Image + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"] # will be sent as a gcs image +) +``` + +Text + Video + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image +) +``` + +Image + Video + +```python +response = await litellm.aembedding( + model="vertex_ai/multimodalembedding@001", + input=["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image +) +``` + + + + + +1. Add model to config.yaml +```yaml +model_list: + - model_name: multimodalembedding@001 + litellm_params: + model: vertex_ai/multimodalembedding@001 + vertex_project: "adroit-crow-413218" + vertex_location: "us-central1" + vertex_credentials: adroit-crow-413218-a956eef1a2a8.json + +litellm_settings: + drop_params: True +``` + +2. Start Proxy + +``` +$ litellm --config /path/to/config.yaml +``` + +3. Make Request use OpenAI Python SDK, Langchain Python SDK + + +Text + Image + +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"], +) + +print(response) +``` + +Text + Video +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"], +) + +print(response) +``` + +Image + Video +```python +import openai + +client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") + +# # request sent to model set on litellm proxy, `litellm --model` +response = client.embeddings.create( + model="multimodalembedding@001", + input = ["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"], +) + +print(response) +``` + + + + + ## **Gemini TTS (Text-to-Speech) Audio Output** :::info diff --git a/docs/my-website/docs/providers/vertex_embedding.md b/docs/my-website/docs/providers/vertex_embedding.md deleted file mode 100644 index 5656ade337..0000000000 --- a/docs/my-website/docs/providers/vertex_embedding.md +++ /dev/null @@ -1,587 +0,0 @@ -import Image from '@theme/IdealImage'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Vertex AI Embedding - -## Usage - Embedding - - - - -```python -import litellm -from litellm import embedding -litellm.vertex_project = "hardy-device-38811" # Your Project ID -litellm.vertex_location = "us-central1" # proj location - -response = embedding( - model="vertex_ai/textembedding-gecko", - input=["good morning from litellm"], -) -print(response) -``` - - - - - -1. Add model to config.yaml -```yaml -model_list: - - model_name: snowflake-arctic-embed-m-long-1731622468876 - litellm_params: - model: vertex_ai/ - vertex_project: "adroit-crow-413218" - vertex_location: "us-central1" - vertex_credentials: adroit-crow-413218-a956eef1a2a8.json - -litellm_settings: - drop_params: True -``` - -2. Start Proxy - -``` -$ litellm --config /path/to/config.yaml -``` - -3. Make Request using OpenAI Python SDK, Langchain Python SDK - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -response = client.embeddings.create( - model="snowflake-arctic-embed-m-long-1731622468876", - input = ["good morning from litellm", "this is another item"], -) - -print(response) -``` - - - - - -#### Supported Embedding Models -All models listed [here](https://github.com/BerriAI/litellm/blob/57f37f743886a0249f630a6792d49dffc2c5d9b7/model_prices_and_context_window.json#L835) are supported - -| Model Name | Function Call | -|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| text-embedding-004 | `embedding(model="vertex_ai/text-embedding-004", input)` | -| text-multilingual-embedding-002 | `embedding(model="vertex_ai/text-multilingual-embedding-002", input)` | -| textembedding-gecko | `embedding(model="vertex_ai/textembedding-gecko", input)` | -| textembedding-gecko-multilingual | `embedding(model="vertex_ai/textembedding-gecko-multilingual", input)` | -| textembedding-gecko-multilingual@001 | `embedding(model="vertex_ai/textembedding-gecko-multilingual@001", input)` | -| textembedding-gecko@001 | `embedding(model="vertex_ai/textembedding-gecko@001", input)` | -| textembedding-gecko@003 | `embedding(model="vertex_ai/textembedding-gecko@003", input)` | -| text-embedding-preview-0409 | `embedding(model="vertex_ai/text-embedding-preview-0409", input)` | -| text-multilingual-embedding-preview-0409 | `embedding(model="vertex_ai/text-multilingual-embedding-preview-0409", input)` | -| Fine-tuned OR Custom Embedding models | `embedding(model="vertex_ai/", input)` | - -### Supported OpenAI (Unified) Params - -| [param](../embedding/supported_embedding.md#input-params-for-litellmembedding) | type | [vertex equivalent](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api) | -|-------|-------------|--------------------| -| `input` | **string or List[string]** | `instances` | -| `dimensions` | **int** | `output_dimensionality` | -| `input_type` | **Literal["RETRIEVAL_QUERY","RETRIEVAL_DOCUMENT", "SEMANTIC_SIMILARITY", "CLASSIFICATION", "CLUSTERING", "QUESTION_ANSWERING", "FACT_VERIFICATION"]** | `task_type` | - -#### Usage with OpenAI (Unified) Params - - - - - -```python -response = litellm.embedding( - model="vertex_ai/text-embedding-004", - input=["good morning from litellm", "gm"] - input_type = "RETRIEVAL_DOCUMENT", - dimensions=1, -) -``` - - - - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -response = client.embeddings.create( - model="text-embedding-004", - input = ["good morning from litellm", "gm"], - dimensions=1, - extra_body = { - "input_type": "RETRIEVAL_QUERY", - } -) - -print(response) -``` - - - - -### Supported Vertex Specific Params - -| param | type | -|-------|-------------| -| `auto_truncate` | **bool** | -| `task_type` | **Literal["RETRIEVAL_QUERY","RETRIEVAL_DOCUMENT", "SEMANTIC_SIMILARITY", "CLASSIFICATION", "CLUSTERING", "QUESTION_ANSWERING", "FACT_VERIFICATION"]** | -| `title` | **str** | - -#### Usage with Vertex Specific Params (Use `task_type` and `title`) - -You can pass any vertex specific params to the embedding model. Just pass them to the embedding function like this: - -[Relevant Vertex AI doc with all embedding params](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api#request_body) - - - - -```python -response = litellm.embedding( - model="vertex_ai/text-embedding-004", - input=["good morning from litellm", "gm"] - task_type = "RETRIEVAL_DOCUMENT", - title = "test", - dimensions=1, - auto_truncate=True, -) -``` - - - - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -response = client.embeddings.create( - model="text-embedding-004", - input = ["good morning from litellm", "gm"], - dimensions=1, - extra_body = { - "task_type": "RETRIEVAL_QUERY", - "auto_truncate": True, - "title": "test", - } -) - -print(response) -``` - - - -## **BGE Embeddings** - -Use BGE (Baidu General Embedding) models deployed on Vertex AI. - -### Usage - - - - -```python showLineNumbers title="Using BGE on Vertex AI" -import litellm - -response = litellm.embedding( - model="vertex_ai/bge/", - input=["Hello", "World"], - vertex_project="your-project-id", - vertex_location="your-location" -) - -print(response) -``` - - - - - -1. Add model to config.yaml -```yaml showLineNumbers title="config.yaml" -model_list: - - model_name: bge-embedding - litellm_params: - model: vertex_ai/bge/ - vertex_project: "your-project-id" - vertex_location: "us-central1" - vertex_credentials: your-credentials.json - -litellm_settings: - drop_params: True -``` - -2. Start Proxy - -```bash -$ litellm --config /path/to/config.yaml -``` - -3. Make Request using OpenAI Python SDK - -```python showLineNumbers title="Making requests to BGE" -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -response = client.embeddings.create( - model="bge-embedding", - input=["good morning from litellm", "this is another item"] -) - -print(response) -``` - -Using a Private Service Connect (PSC) endpoint - -```yaml showLineNumbers title="config.yaml (PSC)" -model_list: - - model_name: bge-small-en-v1.5 - litellm_params: - model: vertex_ai/bge/1234567890 - api_base: http://10.96.32.8 # Your PSC IP - vertex_project: my-project-id #optional - vertex_location: us-central1 #optional -``` - - - - -## **Multi-Modal Embeddings** - - -Known Limitations: -- Only supports 1 image / video / image per request -- Only supports GCS or base64 encoded images / videos - -### Usage - - - - -Using GCS Images - -```python -response = await litellm.aembedding( - model="vertex_ai/multimodalembedding@001", - input="gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" # will be sent as a gcs image -) -``` - -Using base 64 encoded images - -```python -response = await litellm.aembedding( - model="vertex_ai/multimodalembedding@001", - input="data:image/jpeg;base64,..." # will be sent as a base64 encoded image -) -``` - - - - -1. Add model to config.yaml -```yaml -model_list: - - model_name: multimodalembedding@001 - litellm_params: - model: vertex_ai/multimodalembedding@001 - vertex_project: "adroit-crow-413218" - vertex_location: "us-central1" - vertex_credentials: adroit-crow-413218-a956eef1a2a8.json - -litellm_settings: - drop_params: True -``` - -2. Start Proxy - -``` -$ litellm --config /path/to/config.yaml -``` - -3. Make Request use OpenAI Python SDK, Langchain Python SDK - - - - - - -Requests with GCS Image / Video URI - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -# # request sent to model set on litellm proxy, `litellm --model` -response = client.embeddings.create( - model="multimodalembedding@001", - input = "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", -) - -print(response) -``` - -Requests with base64 encoded images - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -# # request sent to model set on litellm proxy, `litellm --model` -response = client.embeddings.create( - model="multimodalembedding@001", - input = "data:image/jpeg;base64,...", -) - -print(response) -``` - - - - - -Requests with GCS Image / Video URI -```python -from langchain_openai import OpenAIEmbeddings - -embeddings_models = "multimodalembedding@001" - -embeddings = OpenAIEmbeddings( - model="multimodalembedding@001", - base_url="http://0.0.0.0:4000", - api_key="sk-1234", # type: ignore -) - - -query_result = embeddings.embed_query( - "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" -) -print(query_result) - -``` - -Requests with base64 encoded images - -```python -from langchain_openai import OpenAIEmbeddings - -embeddings_models = "multimodalembedding@001" - -embeddings = OpenAIEmbeddings( - model="multimodalembedding@001", - base_url="http://0.0.0.0:4000", - api_key="sk-1234", # type: ignore -) - - -query_result = embeddings.embed_query( - "data:image/jpeg;base64,..." -) -print(query_result) - -``` - - - - - - - - - -1. Add model to config.yaml -```yaml -default_vertex_config: - vertex_project: "adroit-crow-413218" - vertex_location: "us-central1" - vertex_credentials: adroit-crow-413218-a956eef1a2a8.json -``` - -2. Start Proxy - -``` -$ litellm --config /path/to/config.yaml -``` - -3. Make Request use OpenAI Python SDK - -```python -import vertexai - -from vertexai.vision_models import Image, MultiModalEmbeddingModel, Video -from vertexai.vision_models import VideoSegmentConfig -from google.auth.credentials import Credentials - - -LITELLM_PROXY_API_KEY = "sk-1234" -LITELLM_PROXY_BASE = "http://0.0.0.0:4000/vertex-ai" - -import datetime - -class CredentialsWrapper(Credentials): - def __init__(self, token=None): - super().__init__() - self.token = token - self.expiry = None # or set to a future date if needed - - def refresh(self, request): - pass - - def apply(self, headers, token=None): - headers['Authorization'] = f'Bearer {self.token}' - - @property - def expired(self): - return False # Always consider the token as non-expired - - @property - def valid(self): - return True # Always consider the credentials as valid - -credentials = CredentialsWrapper(token=LITELLM_PROXY_API_KEY) - -vertexai.init( - project="adroit-crow-413218", - location="us-central1", - api_endpoint=LITELLM_PROXY_BASE, - credentials = credentials, - api_transport="rest", - -) - -model = MultiModalEmbeddingModel.from_pretrained("multimodalembedding") -image = Image.load_from_file( - "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png" -) - -embeddings = model.get_embeddings( - image=image, - contextual_text="Colosseum", - dimension=1408, -) -print(f"Image Embedding: {embeddings.image_embedding}") -print(f"Text Embedding: {embeddings.text_embedding}") -``` - - - - - -### Text + Image + Video Embeddings - - - - -Text + Image - -```python -response = await litellm.aembedding( - model="vertex_ai/multimodalembedding@001", - input=["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"] # will be sent as a gcs image -) -``` - -Text + Video - -```python -response = await litellm.aembedding( - model="vertex_ai/multimodalembedding@001", - input=["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image -) -``` - -Image + Video - -```python -response = await litellm.aembedding( - model="vertex_ai/multimodalembedding@001", - input=["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"] # will be sent as a gcs image -) -``` - - - - - -1. Add model to config.yaml -```yaml -model_list: - - model_name: multimodalembedding@001 - litellm_params: - model: vertex_ai/multimodalembedding@001 - vertex_project: "adroit-crow-413218" - vertex_location: "us-central1" - vertex_credentials: adroit-crow-413218-a956eef1a2a8.json - -litellm_settings: - drop_params: True -``` - -2. Start Proxy - -``` -$ litellm --config /path/to/config.yaml -``` - -3. Make Request use OpenAI Python SDK, Langchain Python SDK - - -Text + Image - -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -# # request sent to model set on litellm proxy, `litellm --model` -response = client.embeddings.create( - model="multimodalembedding@001", - input = ["hey", "gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png"], -) - -print(response) -``` - -Text + Video -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -# # request sent to model set on litellm proxy, `litellm --model` -response = client.embeddings.create( - model="multimodalembedding@001", - input = ["hey", "gs://my-bucket/embeddings/supermarket-video.mp4"], -) - -print(response) -``` - -Image + Video -```python -import openai - -client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000") - -# # request sent to model set on litellm proxy, `litellm --model` -response = client.embeddings.create( - model="multimodalembedding@001", - input = ["gs://cloud-samples-data/vertex-ai/llm/prompts/landmark1.png", "gs://my-bucket/embeddings/supermarket-video.mp4"], -) - -print(response) -``` - - - \ No newline at end of file diff --git a/docs/my-website/docs/providers/voyage.md b/docs/my-website/docs/providers/voyage.md index 4b729bc9f5..b1e4cf932e 100644 --- a/docs/my-website/docs/providers/voyage.md +++ b/docs/my-website/docs/providers/voyage.md @@ -14,12 +14,41 @@ import os os.environ['VOYAGE_API_KEY'] = "" response = embedding( - model="voyage/voyage-3-large", + model="voyage/voyage-3.5", input=["good morning from litellm"], ) print(response) ``` +## Supported Parameters + +VoyageAI embeddings support the following optional parameters: + +- `input_type`: Specifies the type of input for retrieval optimization + - `"query"`: Use for search queries + - `"document"`: Use for documents being indexed +- `dimensions`: Output embedding dimensions (256, 512, 1024, or 2048) +- `encoding_format`: Output format (`"float"`, `"int8"`, `"uint8"`, `"binary"`, `"ubinary"`) +- `truncation`: Whether to truncate inputs exceeding max tokens (default: `True`) + +### Example with Parameters + +```python +from litellm import embedding +import os + +os.environ['VOYAGE_API_KEY'] = "your-api-key" + +# Embedding with custom dimensions and input type +response = embedding( + model="voyage/voyage-3.5", + input=["Your text here"], + dimensions=512, + input_type="document" +) +print(f"Embedding dimensions: {len(response.data[0]['embedding'])}") +``` + ## Supported Models All models listed here https://docs.voyageai.com/embeddings/#models-and-specifics are supported @@ -40,5 +69,84 @@ All models listed here https://docs.voyageai.com/embeddings/#models-and-specific | voyage-2 | `embedding(model="voyage/voyage-2", input)` | | voyage-lite-02-instruct | `embedding(model="voyage/voyage-lite-02-instruct", input)` | | voyage-01 | `embedding(model="voyage/voyage-01", input)` | -| voyage-lite-01 | `embedding(model="voyage/voyage-lite-01", input)` | -| voyage-lite-01-instruct | `embedding(model="voyage/voyage-lite-01-instruct", input)` | +| voyage-lite-01 | `embedding(model="voyage/voyage-lite-01", input)` | +| voyage-lite-01-instruct | `embedding(model="voyage/voyage-lite-01-instruct", input)` | + +## Contextual Embeddings (voyage-context-3) + +VoyageAI's `voyage-context-3` model provides contextualized chunk embeddings, where each chunk is embedded with awareness of its surrounding document context. This significantly improves retrieval quality compared to standard context-agnostic embeddings. + +### Key Benefits +- Chunks understand their position and role within the full document +- Improved retrieval accuracy for long documents (outperforms competitors by 7-23%) +- Better handling of ambiguous references and cross-chunk dependencies +- Seamless drop-in replacement for standard embeddings in RAG pipelines + +### Usage + +Contextual embeddings require a **nested input format** where each inner list represents chunks from a single document: + +```python +from litellm import embedding +import os + +os.environ['VOYAGE_API_KEY'] = "your-api-key" + +# Single document with multiple chunks +response = embedding( + model="voyage/voyage-context-3", + input=[ + [ + "Chapter 1: Introduction to AI", + "This chapter covers the basics of artificial intelligence.", + "We will explore machine learning and deep learning." + ] + ] +) +print(f"Number of chunk groups: {len(response.data)}") + +# Multiple documents +response = embedding( + model="voyage/voyage-context-3", + input=[ + ["Paris is the capital of France.", "It is known for the Eiffel Tower."], + ["Tokyo is the capital of Japan.", "It is a major economic hub."] + ] +) +print(f"Processed {len(response.data)} documents") +``` + +### Specifications +- Model: `voyage-context-3` +- Context length: 32,000 tokens per document +- Output dimensions: 256, 512, 1024 (default), or 2048 +- Max inputs: 1,000 per request +- Max total tokens: 120,000 +- Max chunks: 16,000 +- Pricing: $0.18 per million tokens + +### When to Use Contextual Embeddings + +**Use `voyage-context-3` when:** +- Processing long documents split into chunks +- Document structure and flow are important +- References between sections matter +- You need to preserve document hierarchy + +**Use standard models (voyage-3.5, voyage-3-large) when:** +- Embedding independent pieces of text +- Processing short queries +- Document context is not relevant +- You need faster/cheaper processing + +## Model Selection Guide + +| Model | Best For | Context Length | Price/M Tokens | +|-------|----------|----------------|----------------| +| voyage-3.5 | General-purpose, multilingual | 32K | $0.06 | +| voyage-3.5-lite | Latency-sensitive applications | 32K | $0.02 | +| voyage-3-large | Best overall quality | 32K | $0.18 | +| voyage-code-3 | Code retrieval and search | 32K | $0.18 | +| voyage-finance-2 | Financial documents | 32K | $0.12 | +| voyage-law-2 | Legal documents | 16K | $0.12 | +| voyage-context-3 | Contextual document embeddings | 32K | $0.18 | diff --git a/docs/my-website/docs/proxy/model_access.md b/docs/my-website/docs/proxy/model_access.md index e08530d90c..961207cad5 100644 --- a/docs/my-website/docs/proxy/model_access.md +++ b/docs/my-website/docs/proxy/model_access.md @@ -1,7 +1,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Control Model Access +# Restrict Model Access ## **Restrict models by Virtual Key** @@ -114,238 +114,6 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ ### [API Reference](https://litellm-api.up.railway.app/#/team%20management/new_team_team_new_post) -## **Model Access Groups** - -Use model access groups to give users access to select models, and add new ones to it over time (e.g. mistral, llama-2, etc.) - -**Step 1. Assign model, access group in config.yaml** - -```yaml -model_list: - - model_name: gpt-4 - litellm_params: - model: openai/fake - api_key: fake-key - api_base: https://exampleopenaiendpoint-production.up.railway.app/ - model_info: - access_groups: ["beta-models"] # 👈 Model Access Group - - model_name: fireworks-llama-v3-70b-instruct - litellm_params: - model: fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct - api_key: "os.environ/FIREWORKS" - model_info: - access_groups: ["beta-models"] # 👈 Model Access Group -``` - - - - - -**Create key with access group** - -```bash -curl --location 'http://localhost:4000/key/generate' \ --H 'Authorization: Bearer ' \ --H 'Content-Type: application/json' \ --d '{"models": ["beta-models"], # 👈 Model Access Group - "max_budget": 0,}' -``` - -Test Key - - - - -```shell -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "gpt-4", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - - - -:::info - -Expect this to fail since gpt-4o is not in the `beta-models` access group - -::: - -```shell -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "gpt-4o", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - - - - - - - -Create Team - -```shell -curl --location 'http://localhost:4000/team/new' \ --H 'Authorization: Bearer sk-' \ --H 'Content-Type: application/json' \ --d '{"models": ["beta-models"]}' -``` - -Create Key for Team - -```shell -curl --location 'http://0.0.0.0:4000/key/generate' \ ---header 'Authorization: Bearer sk-' \ ---header 'Content-Type: application/json' \ ---data '{"team_id": "0ac97648-c194-4c90-8cd6-40af7b0d2d2a"} -``` - - -Test Key - - - - -```shell -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "gpt-4", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - - - -:::info - -Expect this to fail since gpt-4o is not in the `beta-models` access group - -::: - -```shell -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "gpt-4o", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - - - - - - - - -### ✨ Control Access on Wildcard Models - -Control access to all models with a specific prefix (e.g. `openai/*`). - -Use this to also give users access to all models, except for a few that you don't want them to use (e.g. `openai/o1-*`). - -:::info - -Setting model access groups on wildcard models is an Enterprise feature. - -See pricing [here](https://litellm.ai/#pricing) - -Get a trial key [here](https://litellm.ai/#trial) -::: - - -1. Setup config.yaml - - -```yaml -model_list: - - model_name: openai/* - litellm_params: - model: openai/* - api_key: os.environ/OPENAI_API_KEY - model_info: - access_groups: ["default-models"] - - model_name: openai/o1-* - litellm_params: - model: openai/o1-* - api_key: os.environ/OPENAI_API_KEY - model_info: - access_groups: ["restricted-models"] -``` - -2. Generate a key with access to `default-models` - -```bash -curl -L -X POST 'http://0.0.0.0:4000/key/generate' \ --H 'Authorization: Bearer sk-1234' \ --H 'Content-Type: application/json' \ --d '{ - "models": ["default-models"], -}' -``` - -3. Test the key - - - - -```bash -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "openai/gpt-4", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - -```bash -curl -i http://localhost:4000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-" \ - -d '{ - "model": "openai/o1-mini", - "messages": [ - {"role": "user", "content": "Hello"} - ] - }' -``` - - - - - ## **View Available Fallback Models** Use the `/v1/models` endpoint to discover available fallback models for a given model. This helps you understand which backup models are available when your primary model is unavailable or restricted. @@ -451,4 +219,8 @@ When `include_metadata=true` is specified, the response includes fallback inform | `include_metadata` | boolean | Include additional model metadata including fallbacks | | `fallback_type` | string | Filter fallbacks by type: `general`, `context_window`, or `content_policy` | +## Advanced: Model Access Groups + +For advanced use cases, use [Model Access Groups](./model_access_groups) to dynamically group multiple models and manage access without restarting the proxy. + ## [Role Based Access Control (RBAC)](./jwt_auth_arch) \ No newline at end of file diff --git a/docs/my-website/docs/proxy/model_access_groups.md b/docs/my-website/docs/proxy/model_access_groups.md new file mode 100644 index 0000000000..f97c3c3d90 --- /dev/null +++ b/docs/my-website/docs/proxy/model_access_groups.md @@ -0,0 +1,503 @@ + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Model Access Groups + +### Overview + +Group multiple models under a single name, then grant keys or teams access to the entire group. Add or remove models from a group without updating individual keys. + +Use cases: +- Separate production and development models +- Restrict expensive models to specific teams +- Organize models by provider or capability +- Control access to model families with wildcards (e.g., `openai/*`) + +### How It Works + +```mermaid +graph LR + subgraph AG1["Access Group: 'prod-models'"] + M1["gpt-4o"] + M2["claude-opus"] + end + + subgraph AG2["Access Group: 'dev-models'"] + M3["gpt-4o-mini"] + M4["claude-haiku"] + end + + K1["Production API Key"] --> AG1 + K2["Development API Key"] --> AG2 + + style AG1 fill:#e3f2fd + style AG2 fill:#fff8e1 +``` + +**Key Concept:** Group models together → Attach group to key → Key gets access to all models in group + +**Step 1. Assign model, access group in config.yaml** + +```yaml showLineNumbers title="config.yaml" +model_list: + - model_name: gpt-4 + litellm_params: + model: openai/fake + api_key: fake-key + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + model_info: + access_groups: ["beta-models"] # 👈 Model Access Group + - model_name: fireworks-llama-v3-70b-instruct + litellm_params: + model: fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct + api_key: "os.environ/FIREWORKS" + model_info: + access_groups: ["beta-models"] # 👈 Model Access Group +``` + + + + + +**Create key with access group** + +```bash showLineNumbers title="Create Key with Access Group" +curl --location 'http://localhost:4000/key/generate' \ +-H 'Authorization: Bearer ' \ +-H 'Content-Type: application/json' \ +-d '{"models": ["beta-models"], # 👈 Model Access Group + "max_budget": 0,}' +``` + +Test Key + + + + +```bash showLineNumbers title="Test Key - Allowed Access" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "gpt-4", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + + + +:::info + +Expect this to fail since gpt-4o is not in the `beta-models` access group + +::: + +```bash showLineNumbers title="Test Key - Disallowed Access" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "gpt-4o", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + + + + + + + +Create Team + +```bash showLineNumbers title="Create Team" +curl --location 'http://localhost:4000/team/new' \ +-H 'Authorization: Bearer sk-' \ +-H 'Content-Type: application/json' \ +-d '{"models": ["beta-models"]}' +``` + +Create Key for Team + +```bash showLineNumbers title="Create Key for Team" +curl --location 'http://0.0.0.0:4000/key/generate' \ +--header 'Authorization: Bearer sk-' \ +--header 'Content-Type: application/json' \ +--data '{"team_id": "0ac97648-c194-4c90-8cd6-40af7b0d2d2a"} +``` + + +Test Key + + + + +```bash showLineNumbers title="Test Team Key - Allowed Access" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "gpt-4", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + + + +:::info + +Expect this to fail since gpt-4o is not in the `beta-models` access group + +::: + +```bash showLineNumbers title="Test Team Key - Disallowed Access" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "gpt-4o", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + + + + + + + + +### ✨ Control Access on Wildcard Models + +Control access to all models with a specific prefix (e.g. `openai/*`). + +Use this to also give users access to all models, except for a few that you don't want them to use (e.g. `openai/o1-*`). + +:::info + +Setting model access groups on wildcard models is an Enterprise feature. + +See pricing [here](https://litellm.ai/#pricing) + +Get a trial key [here](https://litellm.ai/#trial) +::: + + +1. Setup config.yaml + + +```yaml showLineNumbers title="config.yaml - Wildcard Models" +model_list: + - model_name: openai/* + litellm_params: + model: openai/* + api_key: os.environ/OPENAI_API_KEY + model_info: + access_groups: ["default-models"] + - model_name: openai/o1-* + litellm_params: + model: openai/o1-* + api_key: os.environ/OPENAI_API_KEY + model_info: + access_groups: ["restricted-models"] +``` + +2. Generate a key with access to `default-models` + +```bash showLineNumbers title="Generate Key for Wildcard Access Group" +curl -L -X POST 'http://0.0.0.0:4000/key/generate' \ +-H 'Authorization: Bearer sk-1234' \ +-H 'Content-Type: application/json' \ +-d '{ + "models": ["default-models"], +}' +``` + +3. Test the key + + + + +```bash showLineNumbers title="Test Wildcard Access - Allowed" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "openai/gpt-4", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + +```bash showLineNumbers title="Test Wildcard Access - Rejected" +curl -i http://localhost:4000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-" \ + -d '{ + "model": "openai/o1-mini", + "messages": [ + {"role": "user", "content": "Hello"} + ] + }' +``` + + + + +## Managing Access Groups via API + +:::warning Database Models Only +Access group management APIs only work with models stored in the database (added via `/model/new`). + +Models defined in `config.yaml` cannot be managed through these APIs and must be configured directly in the config file. +::: + +Use the access group management endpoints to dynamically create, update, and delete access groups without restarting the proxy. + +### Tutorial: Complete Access Group Workflow + +This tutorial shows how to create an access group, view its details, attach it to a key, and update the models in the group. + +**Prerequisites:** +- Models must be added to the database first (not just in config.yaml) +- You need your master key for authorization + +#### Step 1: Add Models to Database + +First, add some models to the database: + +```bash showLineNumbers title="Add Models to Database" +# Add GPT-4 to database +curl -X POST 'http://localhost:4000/model/new' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "model_name": "gpt-4", + "litellm_params": { + "model": "gpt-4", + "api_key": "os.environ/OPENAI_API_KEY" + } + }' + +# Add Claude to database +curl -X POST 'http://localhost:4000/model/new' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "model_name": "claude-3-opus", + "litellm_params": { + "model": "claude-3-opus-20240229", + "api_key": "os.environ/ANTHROPIC_API_KEY" + } + }' +``` + +#### Step 2: Create Access Group + +Create an access group containing multiple models: + +```bash showLineNumbers title="Create Access Group" +curl -X POST 'http://localhost:4000/access_group/new' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "access_group": "production-models", + "model_names": ["gpt-4", "claude-3-opus"] + }' +``` + +**Response:** +```json showLineNumbers title="Response" +{ + "access_group": "production-models", + "model_names": ["gpt-4", "claude-3-opus"], + "models_updated": 2 +} +``` + +#### Step 3: View Access Group Info + +Check the access group details: + +```bash showLineNumbers title="Get Access Group Info" +curl -X GET 'http://localhost:4000/access_group/production-models/info' \ + -H 'Authorization: Bearer sk-1234' +``` + +**Response:** +```json showLineNumbers title="Response" +{ + "access_group": "production-models", + "model_names": ["gpt-4", "claude-3-opus"], + "deployment_count": 2 +} +``` + +#### Step 4: Create Key with Access Group + +Create an API key that can access all models in the group: + +```bash showLineNumbers title="Create Key with Access Group" +curl -X POST 'http://localhost:4000/key/generate' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "models": ["production-models"], + "max_budget": 100 + }' +``` + +**Response:** +```json showLineNumbers title="Response" +{ + "key": "sk-...", + "models": ["production-models"] +} +``` + +**Test the key:** +```bash showLineNumbers title="Test Key Access" +# This succeeds - gpt-4 is in production-models +curl -X POST 'http://localhost:4000/v1/chat/completions' \ + -H 'Authorization: Bearer sk-...' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}] + }' + +# This succeeds - claude-3-opus is in production-models +curl -X POST 'http://localhost:4000/v1/chat/completions' \ + -H 'Authorization: Bearer sk-...' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "claude-3-opus", + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +#### Step 5: Update Access Group + +Add or remove models from the access group: + +```bash showLineNumbers title="Update Access Group" +curl -X PUT 'http://localhost:4000/access_group/production-models/update' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "model_names": ["gpt-4", "claude-3-opus", "gemini-pro"] + }' +``` + +**Response:** +```json showLineNumbers title="Response" +{ + "access_group": "production-models", + "model_names": ["gpt-4", "claude-3-opus", "gemini-pro"], + "models_updated": 3 +} +``` + +The API key from Step 4 now automatically has access to `gemini-pro` without any changes to the key itself. +### API Reference - Access Group Management + +For complete API documentation including all endpoints, parameters, and response schemas, see the [Access Group Management API Reference](https://litellm-api.up.railway.app/#/model%20management/create_model_group_access_group_new_post). + +## Managing Access Groups via UI + +You can also manage access groups through the LiteLLM Admin UI. + +### Step 1: Add Model to Access Group + +When adding a model to the database, assign it to an access group using the "Model Access Group" field: + +![Add Model with Access Group](../../img/add_model_access.png) + +In this example, `gpt-4` is added to the `production-models` access group. + +### Step 2: Create Key with Access Group + +When creating an API key, specify the access group in the "Models" field: + +![Create Key with Access Group](../../img/add_model_key.png) + +The key will have access to all models in the `production-models` group. + +### Step 3: Test the Key + +Use the generated key to make requests: + +```bash showLineNumbers title="Test Key with Access Group" +# This succeeds - gpt-4 is in production-models +curl -X POST 'http://localhost:4000/v1/chat/completions' \ + -H 'Authorization: Bearer sk-...' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +**Response:** +```json showLineNumbers title="Success Response" +{ + "id": "chatcmpl-...", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I help you today?" + }, + "finish_reason": "stop" + } + ] +} +``` + +If you try to access a model not in the access group, the request will be rejected: + +```bash showLineNumbers title="Test Rejected Request" +# This fails - gpt-4o is not in production-models +curl -X POST 'http://localhost:4000/v1/chat/completions' \ + -H 'Authorization: Bearer sk-...' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-4o", + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +**Response:** +```json showLineNumbers title="Error Response" +{ + "error": { + "message": "Invalid model for key", + "type": "invalid_request_error" + } +} +``` + diff --git a/docs/my-website/docs/proxy/model_access_guide.md b/docs/my-website/docs/proxy/model_access_guide.md index 4eb273facb..c6cca1d934 100644 --- a/docs/my-website/docs/proxy/model_access_guide.md +++ b/docs/my-website/docs/proxy/model_access_guide.md @@ -85,4 +85,9 @@ litellm_settings: fallbacks: [{"my-custom-model": ["my-other-model"]}] ``` -Fallbacks are done sequentially, so the first model group in the list will be tried first. If it fails, the next model group will be tried. \ No newline at end of file +Fallbacks are done sequentially, so the first model group in the list will be tried first. If it fails, the next model group will be tried. + + +## Advanced: Model Access Groups + +For advanced use cases, use [Model Access Groups](./model_access_groups) to dynamically group multiple models and manage access without restarting the proxy. \ No newline at end of file diff --git a/docs/my-website/docs/proxy/sync_models_github.md b/docs/my-website/docs/proxy/sync_models_github.md index d2f410e549..f390ed0cb9 100644 --- a/docs/my-website/docs/proxy/sync_models_github.md +++ b/docs/my-website/docs/proxy/sync_models_github.md @@ -1,8 +1,21 @@ -# Syncing Models to GitHub model_context_window +# Auto Sync New Models (Day-0 Launches) -Sync model pricing data from GitHub's `model_prices_and_context_window.json` file outside of the LiteLLM UI. +Automatically keep your model pricing and context window data up to date without restarting your service. **This allows you to add day-0 support for new models without restarting your service.** -> **📹 Video Tutorial**: [Watch how to sync models via the Admin UI](https://www.loom.com/share/ba41acc1882d41b284bbddbb0e9c27ce?sid=bdae351e-2026-4e39-932b-fcb185ff612c) +## Overview + +When providers like OpenAI or Anthropic release new models (e.g., GPT-5, Claude 4), you typically need to restart your LiteLLM service to get the latest pricing and context window data. + +With auto-sync, LiteLLM automatically pulls the latest model data from GitHub's [`model_prices_and_context_window.json`](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json) without requiring a restart. This means: + +- **Zero downtime** when new models are released +- **Always accurate pricing** for cost tracking and budgets +- **Automatic updates** - set it once and forget it + + + +
+
## Quick Start diff --git a/docs/my-website/img/add_model_access.png b/docs/my-website/img/add_model_access.png new file mode 100644 index 0000000000..3de54a48a0 Binary files /dev/null and b/docs/my-website/img/add_model_access.png differ diff --git a/docs/my-website/img/add_model_key.png b/docs/my-website/img/add_model_key.png new file mode 100644 index 0000000000..9376d324ff Binary files /dev/null and b/docs/my-website/img/add_model_key.png differ diff --git a/docs/my-website/package-lock.json b/docs/my-website/package-lock.json index b71a15cc8e..cc20e0d830 100644 --- a/docs/my-website/package-lock.json +++ b/docs/my-website/package-lock.json @@ -10296,18 +10296,6 @@ "node": ">=8.0.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -11092,26 +11080,6 @@ "node": ">=6.0" } }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -12148,9 +12116,10 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -19035,11 +19004,6 @@ "node": ">= 6" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, "node_modules/srcset": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", diff --git a/docs/my-website/package.json b/docs/my-website/package.json index 955e63c2d8..d73633817b 100644 --- a/docs/my-website/package.json +++ b/docs/my-website/package.json @@ -50,6 +50,7 @@ "overrides": { "webpack-dev-server": ">=5.2.1", "form-data": ">=4.0.4", - "mermaid": ">=11.10.0" + "mermaid": ">=11.10.0", + "js-yaml": ">=4.1.1" } } diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 809e0bc0b7..99472981f0 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -31,9 +31,16 @@ const sidebars = { label: "Guardrails", items: [ "proxy/guardrails/quick_start", + { + type: "category", + "label": "Contributing to Guardrails", + items: [ + "adding_provider/simple_guardrail_tutorial", + "adding_provider/adding_guardrail_support", + ] + }, "proxy/guardrails/test_playground", ...[ - "adding_provider/adding_guardrail_support", "proxy/guardrails/aim_security", "proxy/guardrails/aporia_api", "proxy/guardrails/azure_content_guardrail", @@ -252,6 +259,7 @@ const sidebars = { items: [ "proxy/model_access_guide", "proxy/model_access", + "proxy/model_access_groups", "proxy/team_model_add" ] }, @@ -277,6 +285,7 @@ const sidebars = { items: [ "proxy/cost_tracking", "proxy/custom_pricing", + "proxy/sync_models_github", "proxy/billing", ], }, @@ -488,7 +497,6 @@ const sidebars = { "providers/vertex_ai/videos", "providers/vertex_partner", "providers/vertex_self_deployed", - "providers/vertex_embedding", "providers/vertex_image", "providers/vertex_batch", "providers/vertex_ocr", diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4-py3-none-any.whl new file mode 100644 index 0000000000..ef931a15b7 Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4-py3-none-any.whl differ diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4.tar.gz new file mode 100644 index 0000000000..85f8db49fa Binary files /dev/null and b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.4.tar.gz differ diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114173537_add_request_id_to_daily_tag_spend/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114173537_add_request_id_to_daily_tag_spend/migration.sql new file mode 100644 index 0000000000..6871e27a28 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251114173537_add_request_id_to_daily_tag_spend/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "LiteLLM_DailyTagSpend" ADD COLUMN "request_id" TEXT; + diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index 6c782eace2..0451a6c453 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.4.3" +version = "0.4.4" 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.4.3" +version = "0.4.4" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/litellm/__init__.py b/litellm/__init__.py index 487b94d0f8..4993570ade 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -485,6 +485,7 @@ vertex_ai_ai21_models: Set = set() vertex_mistral_models: Set = set() vertex_openai_models: Set = set() vertex_minimax_models: Set = set() +vertex_moonshot_models: Set = set() ai21_models: Set = set() ai21_chat_models: Set = set() nlp_cloud_models: Set = set() @@ -500,6 +501,7 @@ watsonx_models: Set = set() gemini_models: Set = set() xai_models: Set = set() deepseek_models: Set = set() +runwayml_models: Set = set() azure_ai_models: Set = set() jina_ai_models: Set = set() voyage_models: Set = set() @@ -648,6 +650,9 @@ def add_known_models(): elif value.get("litellm_provider") == "vertex_ai-minimax_models": key = key.replace("vertex_ai/", "") vertex_minimax_models.add(key) + elif value.get("litellm_provider") == "vertex_ai-moonshot_models": + key = key.replace("vertex_ai/", "") + vertex_moonshot_models.add(key) elif value.get("litellm_provider") == "ai21": if value.get("mode") == "chat": ai21_chat_models.add(key) @@ -687,6 +692,8 @@ def add_known_models(): fal_ai_models.add(key) elif value.get("litellm_provider") == "deepseek": deepseek_models.add(key) + elif value.get("litellm_provider") == "runwayml": + runwayml_models.add(key) elif value.get("litellm_provider") == "meta_llama": llama_models.add(key) elif value.get("litellm_provider") == "nscale": @@ -830,6 +837,7 @@ model_list = list( | deepinfra_models | perplexity_models | set(maritalk_models) + | runwayml_models | vertex_language_models | watsonx_models | gemini_models @@ -904,7 +912,8 @@ models_by_provider: dict = { | vertex_vision_models | vertex_language_models | vertex_deepseek_models - | vertex_minimax_models, + | vertex_minimax_models + | vertex_moonshot_models, "ai21": ai21_models, "bedrock": bedrock_models | bedrock_converse_models, "petals": petals_models, @@ -921,6 +930,7 @@ models_by_provider: dict = { "xai": xai_models, "fal_ai": fal_ai_models, "deepseek": deepseek_models, + "runwayml": runwayml_models, "mistral": mistral_chat_models, "azure_ai": azure_ai_models, "voyage": voyage_models, diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 3ba75666b8..8c3ebd5103 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -544,7 +544,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return Reasoning(**reasoning_effort) # type: ignore[typeddict-item] # If string is passed, map without summary (default) - if reasoning_effort == "high": + if reasoning_effort == "none": + return Reasoning(effort="none") # type: ignore + elif reasoning_effort == "high": return Reasoning(effort="high") elif reasoning_effort == "medium": return Reasoning(effort="medium") diff --git a/litellm/images/main.py b/litellm/images/main.py index ce6da640f9..333a751b04 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -400,6 +400,8 @@ def image_generation( # noqa: PLR0915 or custom_llm_provider == LlmProviders.LITELLM_PROXY.value or custom_llm_provider in litellm.openai_compatible_providers ): + # Forward OpenAI organization if present (set by proxy pre-call utils) + organization: Optional[str] = kwargs.get("organization", None) model_response = openai_chat_completions.image_generation( model=model, prompt=prompt, @@ -409,6 +411,7 @@ def image_generation( # noqa: PLR0915 logging_obj=litellm_logging_obj, optional_params=optional_params, model_response=model_response, + organization=organization, aimg_generation=aimg_generation, client=client, ) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py index 88a63fc6f5..795f9a4cd0 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py @@ -152,32 +152,27 @@ class LiteLLMMessagesToCompletionTransformationHandler: ) ) - try: - completion_response = await litellm.acompletion(**completion_kwargs) + completion_response = await litellm.acompletion(**completion_kwargs) - if stream: - transformed_stream = ( - ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( - completion_response, - model=model, - ) + if stream: + transformed_stream = ( + ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( + completion_response, + model=model, ) - if transformed_stream is not None: - return transformed_stream - raise ValueError("Failed to transform streaming response") - else: - anthropic_response = ( - ANTHROPIC_ADAPTER.translate_completion_output_params( - cast(ModelResponse, completion_response) - ) - ) - if anthropic_response is not None: - return anthropic_response - raise ValueError("Failed to transform response to Anthropic format") - except Exception as e: # noqa: BLE001 - raise ValueError( - f"Error calling litellm.acompletion for non-Anthropic model: {str(e)}" ) + if transformed_stream is not None: + return transformed_stream + raise ValueError("Failed to transform streaming response") + else: + anthropic_response = ( + ANTHROPIC_ADAPTER.translate_completion_output_params( + cast(ModelResponse, completion_response) + ) + ) + if anthropic_response is not None: + return anthropic_response + raise ValueError("Failed to transform response to Anthropic format") @staticmethod def anthropic_messages_handler( @@ -239,29 +234,24 @@ class LiteLLMMessagesToCompletionTransformationHandler: ) ) - try: - completion_response = litellm.completion(**completion_kwargs) + completion_response = litellm.completion(**completion_kwargs) - if stream: - transformed_stream = ( - ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( - completion_response, - model=model, - ) + if stream: + transformed_stream = ( + ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( + completion_response, + model=model, ) - if transformed_stream is not None: - return transformed_stream - raise ValueError("Failed to transform streaming response") - else: - anthropic_response = ( - ANTHROPIC_ADAPTER.translate_completion_output_params( - cast(ModelResponse, completion_response) - ) - ) - if anthropic_response is not None: - return anthropic_response - raise ValueError("Failed to transform response to Anthropic format") - except Exception as e: # noqa: BLE001 - raise ValueError( - f"Error calling litellm.completion for non-Anthropic model: {str(e)}" ) + if transformed_stream is not None: + return transformed_stream + raise ValueError("Failed to transform streaming response") + else: + anthropic_response = ( + ANTHROPIC_ADAPTER.translate_completion_output_params( + cast(ModelResponse, completion_response) + ) + ) + if anthropic_response is not None: + return anthropic_response + raise ValueError("Failed to transform response to Anthropic format") diff --git a/litellm/llms/bedrock/batches/transformation.py b/litellm/llms/bedrock/batches/transformation.py index 2f3d00dddd..a9bc1b26c8 100644 --- a/litellm/llms/bedrock/batches/transformation.py +++ b/litellm/llms/bedrock/batches/transformation.py @@ -6,6 +6,7 @@ from httpx import Headers, Response from litellm.llms.base_llm.batches.transformation import BaseBatchesConfig from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.secret_managers.main import get_secret_str from litellm.types.llms.bedrock import ( BedrockCreateBatchRequest, BedrockCreateBatchResponse, @@ -140,10 +141,20 @@ class BedrockBatchesConfig(BaseAWSLLM, BaseBatchesConfig): } # Build output data config + s3_output_config: BedrockS3OutputDataConfig = BedrockS3OutputDataConfig( + s3Uri=f"s3://{output_bucket}/{output_key}" + ) + + # Add optional KMS encryption key ID if provided + s3_encryption_key_id = ( + litellm_params.get("s3_encryption_key_id") + or get_secret_str("AWS_S3_ENCRYPTION_KEY_ID") + ) + if s3_encryption_key_id: + s3_output_config["s3EncryptionKeyId"] = s3_encryption_key_id + output_data_config: BedrockOutputDataConfig = { - "s3OutputDataConfig": BedrockS3OutputDataConfig( - s3Uri=f"s3://{output_bucket}/{output_key}" - ) + "s3OutputDataConfig": s3_output_config } # Create Bedrock batch request with proper typing diff --git a/litellm/llms/bedrock/chat/agentcore/sse_iterator.py b/litellm/llms/bedrock/chat/agentcore/sse_iterator.py index 35407337fd..e0da4fcd44 100644 --- a/litellm/llms/bedrock/chat/agentcore/sse_iterator.py +++ b/litellm/llms/bedrock/chat/agentcore/sse_iterator.py @@ -19,21 +19,151 @@ if TYPE_CHECKING: class AgentCoreSSEStreamIterator: - """Async iterator for AgentCore SSE streaming responses.""" + """Iterator for AgentCore SSE streaming responses. Supports both sync and async iteration.""" def __init__(self, response: httpx.Response, model: str): self.response = response self.model = model self.finished = False - self.line_iterator = self.response.aiter_lines() + self.line_iterator = None + self.async_line_iterator = None - def __aiter__(self): + def __iter__(self): + """Initialize sync iteration.""" + self.line_iterator = self.response.iter_lines() return self - async def __anext__(self) -> ModelResponse: - """Parse SSE events and yield ModelResponse chunks.""" + def __aiter__(self): + """Initialize async iteration.""" + self.async_line_iterator = self.response.aiter_lines() + return self + + def __next__(self) -> ModelResponse: + """Sync iteration - parse SSE events and yield ModelResponse chunks.""" try: - async for line in self.line_iterator: + if self.line_iterator is None: + raise StopIteration + for line in self.line_iterator: + line = line.strip() + + if not line or not line.startswith('data:'): + continue + + # Extract JSON from SSE line + json_str = line[5:].strip() + if not json_str: + continue + + try: + data = json.loads(json_str) + + # Skip non-dict data + if not isinstance(data, dict): + continue + + # Process content delta events + if "event" in data and isinstance(data["event"], dict): + event_payload = data["event"] + content_block_delta = event_payload.get("contentBlockDelta") + + if content_block_delta: + delta = content_block_delta.get("delta", {}) + text = delta.get("text", "") + + if text: + # Yield chunk with text + chunk = ModelResponse( + id=f"chatcmpl-{uuid.uuid4()}", + created=0, + model=self.model, + object="chat.completion.chunk", + ) + + chunk.choices = [ + StreamingChoices( + finish_reason=None, + index=0, + delta=Delta(content=text, role="assistant"), + ) + ] + + return chunk + + # Check for metadata/usage + metadata = event_payload.get("metadata") + if metadata and "usage" in metadata: + # This is the final chunk with usage + chunk = ModelResponse( + id=f"chatcmpl-{uuid.uuid4()}", + created=0, + model=self.model, + object="chat.completion.chunk", + ) + + chunk.choices = [ + StreamingChoices( + finish_reason="stop", + index=0, + delta=Delta(), + ) + ] + + usage_data: AgentCoreUsage = metadata["usage"] # type: ignore + setattr(chunk, "usage", Usage( + prompt_tokens=usage_data.get("inputTokens", 0), + completion_tokens=usage_data.get("outputTokens", 0), + total_tokens=usage_data.get("totalTokens", 0), + )) + + self.finished = True + return chunk + + # Check for final message (alternative finish signal) + if "message" in data and isinstance(data["message"], dict): + if not self.finished: + chunk = ModelResponse( + id=f"chatcmpl-{uuid.uuid4()}", + created=0, + model=self.model, + object="chat.completion.chunk", + ) + + chunk.choices = [ + StreamingChoices( + finish_reason="stop", + index=0, + delta=Delta(), + ) + ] + + self.finished = True + return chunk + + except json.JSONDecodeError: + verbose_logger.debug(f"Skipping non-JSON SSE line: {line[:100]}") + continue + + # Stream ended naturally + raise StopIteration + + except StopIteration: + raise + except httpx.StreamConsumed: + # This is expected when the stream has been fully consumed + raise StopIteration + except httpx.StreamClosed: + # This is expected when the stream is closed + raise StopIteration + except Exception as e: + verbose_logger.error(f"Error in AgentCore SSE stream: {str(e)}") + raise StopIteration + + async def __anext__(self) -> ModelResponse: + """Async iteration - parse SSE events and yield ModelResponse chunks.""" + try: + if self.async_line_iterator is None: + raise StopAsyncIteration + async for line in self.async_line_iterator: line = line.strip() if not line or not line.startswith('data:'): diff --git a/litellm/llms/fal_ai/__init__.py b/litellm/llms/fal_ai/__init__.py index 492197951e..1f4cbe0e9c 100644 --- a/litellm/llms/fal_ai/__init__.py +++ b/litellm/llms/fal_ai/__init__.py @@ -3,6 +3,7 @@ from .image_generation import ( FalAIBaseConfig, FalAIBriaConfig, FalAIFluxProV11UltraConfig, + FalAIFluxSchnellConfig, FalAIImageGenerationConfig, FalAIImagen4Config, FalAIRecraftV3Config, @@ -18,6 +19,7 @@ __all__ = [ "FalAIRecraftV3Config", "FalAIBriaConfig", "FalAIFluxProV11UltraConfig", + "FalAIFluxSchnellConfig", "FalAIStableDiffusionConfig", "get_fal_ai_image_generation_config", ] diff --git a/litellm/llms/fal_ai/image_generation/__init__.py b/litellm/llms/fal_ai/image_generation/__init__.py index 74d3b434b8..b4ae6734c6 100644 --- a/litellm/llms/fal_ai/image_generation/__init__.py +++ b/litellm/llms/fal_ai/image_generation/__init__.py @@ -4,6 +4,7 @@ from litellm.llms.base_llm.image_generation.transformation import ( from .bria_transformation import FalAIBriaConfig from .flux_pro_v11_ultra_transformation import FalAIFluxProV11UltraConfig +from .flux_schnell_transformation import FalAIFluxSchnellConfig from .imagen4_transformation import FalAIImagen4Config from .recraft_v3_transformation import FalAIRecraftV3Config from .stable_diffusion_transformation import FalAIStableDiffusionConfig @@ -16,6 +17,7 @@ __all__ = [ "FalAIRecraftV3Config", "FalAIBriaConfig", "FalAIFluxProV11UltraConfig", + "FalAIFluxSchnellConfig", "FalAIStableDiffusionConfig", ] @@ -41,6 +43,8 @@ def get_fal_ai_image_generation_config(model: str) -> BaseImageGenerationConfig: return FalAIBriaConfig() elif "flux-pro" in model_lower and "ultra" in model_lower: return FalAIFluxProV11UltraConfig() + elif "flux/schnell" in model_lower or "flux-schnell" in model_lower or "schnell" in model_lower: + return FalAIFluxSchnellConfig() elif "stable-diffusion" in model_lower: return FalAIStableDiffusionConfig() diff --git a/litellm/llms/fal_ai/image_generation/flux_schnell_transformation.py b/litellm/llms/fal_ai/image_generation/flux_schnell_transformation.py new file mode 100644 index 0000000000..ed6ed37fb4 --- /dev/null +++ b/litellm/llms/fal_ai/image_generation/flux_schnell_transformation.py @@ -0,0 +1,88 @@ +from typing import Any + +from .flux_pro_v11_ultra_transformation import FalAIFluxProV11UltraConfig + + +class FalAIFluxSchnellConfig(FalAIFluxProV11UltraConfig): + """ + Configuration for Fal AI Flux Schnell model. + + Flux Schnell shares the same response format as Flux Pro models but expects + the OpenAI `size` parameter to be translated into Fal AI's `image_size` + enum/object. + + Model endpoint: fal-ai/flux/schnell + Documentation: https://fal.ai/models/fal-ai/flux/schnell + """ + + IMAGE_GENERATION_ENDPOINT: str = "fal-ai/flux/schnell" + + _OPENAI_SIZE_TO_IMAGE_SIZE = { + "1024x1024": "square_hd", + "512x512": "square", + "1792x1024": "landscape_16_9", + "1024x1792": "portrait_16_9", + "1024x768": "landscape_4_3", + "768x1024": "portrait_4_3", + } + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + supported_params = self.get_supported_openai_params(model) + + param_mapping = { + "n": "num_images", + "response_format": "output_format", + "size": "image_size", + } + + for k in non_default_params.keys(): + if k not in optional_params.keys(): + if k in supported_params: + mapped_key = param_mapping.get(k, k) + mapped_value = non_default_params[k] + + if k == "response_format": + if mapped_value in ["b64_json", "url"]: + mapped_value = "jpeg" + elif k == "size": + mapped_value = self._map_image_size(mapped_value) + + optional_params[mapped_key] = mapped_value + elif drop_params: + continue + else: + raise ValueError( + f"Parameter {k} is not supported for model {model}. " + f"Supported parameters are {supported_params}. " + "Set drop_params=True to drop unsupported parameters." + ) + + return optional_params + + def _map_image_size(self, size: Any) -> Any: + if isinstance(size, dict): + return size + + if not isinstance(size, str): + return size + + if size in self._OPENAI_SIZE_TO_IMAGE_SIZE: + return self._OPENAI_SIZE_TO_IMAGE_SIZE[size] + + if "x" in size: + try: + width_str, height_str = size.split("x") + width = int(width_str) + height = int(height_str) + return {"width": width, "height": height} + except (ValueError, AttributeError, ZeroDivisionError): + pass + + return "landscape_4_3" + diff --git a/litellm/llms/fal_ai/image_generation/imagen4_transformation.py b/litellm/llms/fal_ai/image_generation/imagen4_transformation.py index f38ced6531..4e7708c9f4 100644 --- a/litellm/llms/fal_ai/image_generation/imagen4_transformation.py +++ b/litellm/llms/fal_ai/image_generation/imagen4_transformation.py @@ -23,7 +23,7 @@ class FalAIImagen4Config(FalAIBaseConfig): Model variants: - fal-ai/imagen4/preview (Standard): $0.05 per image - - fal-ai/imagen4/preview/fast (Fast): $0.04 per image + - fal-ai/imagen4/preview/fast (Fast): $0.02 per image - fal-ai/imagen4/preview/ultra (Ultra): $0.06 per image Documentation: https://fal.ai/models/fal-ai/imagen4/preview diff --git a/litellm/llms/groq/chat/transformation.py b/litellm/llms/groq/chat/transformation.py index 165301efb5..20e0d412ed 100644 --- a/litellm/llms/groq/chat/transformation.py +++ b/litellm/llms/groq/chat/transformation.py @@ -1,9 +1,27 @@ """ Translate from OpenAI's `/v1/chat/completions` to Groq's `/v1/chat/completions` """ -from typing import Any, Coroutine, List, Literal, Optional, Tuple, Union, cast, overload +from typing import ( + Any, + Coroutine, + List, + Literal, + Optional, + Tuple, + Union, + cast, + overload, + Iterator, + AsyncIterator, +) import httpx + +from litellm.llms.openai.chat.gpt_transformation import ( + OpenAIChatCompletionStreamingHandler, +) +from litellm.llms.openai.common_utils import OpenAIError + from pydantic import BaseModel import litellm @@ -16,7 +34,7 @@ from litellm.types.llms.openai import ( ChatCompletionToolParam, ChatCompletionToolParamFunctionChunk, ) -from litellm.types.utils import ModelResponse +from litellm.types.utils import ModelResponse, ModelResponseStream from ...openai_like.chat.transformation import OpenAILikeChatConfig @@ -65,6 +83,18 @@ class GroqChatConfig(OpenAILikeChatConfig): def get_config(cls): return super().get_config() + def get_model_response_iterator( + self, + streaming_response: Union[Iterator[str], AsyncIterator[str], ModelResponse], + sync_stream: bool, + json_mode: Optional[bool] = False, + ) -> Any: + return GroqChatCompletionStreamingHandler( + streaming_response=streaming_response, + sync_stream=sync_stream, + json_mode=json_mode, + ) + def get_supported_openai_params(self, model: str) -> list: base_params = super().get_supported_openai_params(model) try: @@ -209,7 +239,6 @@ class GroqChatConfig(OpenAILikeChatConfig): ) return optional_params - def transform_response( self, @@ -239,12 +268,17 @@ class GroqChatConfig(OpenAILikeChatConfig): json_mode=json_mode, ) - mapped_service_tier: Literal["auto", "default", "flex"] = self._map_groq_service_tier(original_service_tier=getattr(model_response, "service_tier")) + mapped_service_tier: Literal[ + "auto", "default", "flex" + ] = self._map_groq_service_tier( + original_service_tier=getattr(model_response, "service_tier") + ) setattr(model_response, "service_tier", mapped_service_tier) return model_response - - def _map_groq_service_tier(self, original_service_tier: Optional[str]) -> Literal["auto", "default", "flex"]: + def _map_groq_service_tier( + self, original_service_tier: Optional[str] + ) -> Literal["auto", "default", "flex"]: """ Ensure groq service tier is OpenAI compatible. """ @@ -252,5 +286,16 @@ class GroqChatConfig(OpenAILikeChatConfig): return "auto" if original_service_tier not in ["auto", "default", "flex"]: return "auto" - - return cast(Literal["auto", "default", "flex"], original_service_tier) \ No newline at end of file + + return cast(Literal["auto", "default", "flex"], original_service_tier) + + +class GroqChatCompletionStreamingHandler(OpenAIChatCompletionStreamingHandler): + def chunk_parser(self, chunk: dict) -> ModelResponseStream: + error = chunk.get("error") + if error: + raise OpenAIError( + status_code=error.get("code"), message=error.get("message"), body=error + ) + + return super().chunk_parser(chunk) diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 492ed62423..2949e35e5e 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -1285,6 +1285,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): api_base: Optional[str] = None, client=None, max_retries=None, + organization: Optional[str] = None, ): response = None try: @@ -1294,6 +1295,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): api_base=api_base, timeout=timeout, max_retries=max_retries, + organization=organization, client=client, ) @@ -1328,6 +1330,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): model_response: Optional[ImageResponse] = None, client=None, aimg_generation=None, + organization: Optional[str] = None, ) -> ImageResponse: data = {} try: @@ -1337,7 +1340,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): raise OpenAIError(status_code=422, message="max retries must be an int") if aimg_generation is True: - return self.aimage_generation(data=data, prompt=prompt, logging_obj=logging_obj, model_response=model_response, api_base=api_base, api_key=api_key, timeout=timeout, client=client, max_retries=max_retries) # type: ignore + return self.aimage_generation(data=data, prompt=prompt, logging_obj=logging_obj, model_response=model_response, api_base=api_base, api_key=api_key, timeout=timeout, client=client, max_retries=max_retries, organization=organization) # type: ignore openai_client: OpenAI = self._get_openai_client( # type: ignore is_async=False, @@ -1345,6 +1348,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): api_base=api_base, timeout=timeout, max_retries=max_retries, + organization=organization, client=client, ) diff --git a/litellm/llms/sambanova/chat.py b/litellm/llms/sambanova/chat.py index 57a39ec8bb..b0534347c9 100644 --- a/litellm/llms/sambanova/chat.py +++ b/litellm/llms/sambanova/chat.py @@ -4,9 +4,13 @@ Sambanova Chat Completions API this is OpenAI compatible - no translation needed / occurs """ -from typing import Optional, Union +from typing import Any, Coroutine, List, Literal, Optional, Union, overload +from litellm.litellm_core_utils.prompt_templates.common_utils import ( + handle_messages_with_content_list_to_str_conversion, +) from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.types.llms.openai import AllMessageValues class SambanovaConfig(OpenAIGPTConfig): @@ -92,3 +96,30 @@ class SambanovaConfig(OpenAIGPTConfig): elif param in supported_openai_params: optional_params[param] = value return optional_params + + @overload + def _transform_messages( + self, messages: List[AllMessageValues], model: str, is_async: Literal[True] + ) -> Coroutine[Any, Any, List[AllMessageValues]]: + ... + + @overload + def _transform_messages( + self, + messages: List[AllMessageValues], + model: str, + is_async: Literal[False] = False, + ) -> List[AllMessageValues]: + ... + + def _transform_messages( + self, messages: List[AllMessageValues], model: str, is_async: bool = False + ) -> Union[List[AllMessageValues], Coroutine[Any, Any, List[AllMessageValues]]]: + """ + Transform messages to handle content list conversion. + + SambaNova API doesn't support content as a list - only string content. + This converts content lists like [{"type": "text", "text": "..."}] to strings. + """ + messages = handle_messages_with_content_list_to_str_conversion(messages) + return messages diff --git a/litellm/llms/vertex_ai/batches/handler.py b/litellm/llms/vertex_ai/batches/handler.py index b40f0a72a5..7932881f48 100644 --- a/litellm/llms/vertex_ai/batches/handler.py +++ b/litellm/llms/vertex_ai/batches/handler.py @@ -61,10 +61,6 @@ class VertexAIBatchPrediction(VertexLLM): stream=None, auth_header=None, url=default_api_base, - model=None, - vertex_project=vertex_project or project_id, - vertex_location=vertex_location or "us-central1", - vertex_api_version="v1", ) headers = { @@ -170,10 +166,6 @@ class VertexAIBatchPrediction(VertexLLM): stream=None, auth_header=None, url=default_api_base, - model=None, - vertex_project=vertex_project or project_id, - vertex_location=vertex_location or "us-central1", - vertex_api_version="v1", ) headers = { diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 02b0f79280..2c53457736 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -31,11 +31,9 @@ class VertexAIModelRoute(str, Enum): PARTNER_MODELS = "partner_models" GEMINI = "gemini" GEMMA = "gemma" - BGE = "bge" MODEL_GARDEN = "model_garden" NON_GEMINI = "non_gemini" -VERTEX_AI_MODEL_ROUTES = [f"{route.value}/" for route in VertexAIModelRoute] def get_vertex_ai_model_route( model: str, litellm_params: Optional[dict] = None @@ -62,9 +60,6 @@ def get_vertex_ai_model_route( >>> get_vertex_ai_model_route("openai/gpt-oss-120b") VertexAIModelRoute.MODEL_GARDEN - - >>> get_vertex_ai_model_route("1234567890", {"api_base": "http://10.96.32.8"}) - VertexAIModelRoute.GEMINI # Numeric endpoints with api_base use HTTP path """ from litellm.llms.vertex_ai.vertex_ai_partner_models.main import ( VertexAIPartnerModels, @@ -74,20 +69,11 @@ def get_vertex_ai_model_route( if litellm_params and litellm_params.get("base_model") is not None: if "gemini" in litellm_params["base_model"]: return VertexAIModelRoute.GEMINI - - # Check if numeric endpoint ID with custom api_base (PSC endpoint) - # Route to GEMINI (HTTP path) to support PSC endpoints properly - if model.isdigit() and litellm_params and litellm_params.get("api_base"): - return VertexAIModelRoute.GEMINI - + # Check for partner models (llama, mistral, claude, etc.) if VertexAIPartnerModels.is_vertex_partner_model(model=model): return VertexAIModelRoute.PARTNER_MODELS - - # Check for BGE models - if "bge/" in model or "bge" in model.lower(): - return VertexAIModelRoute.BGE - + # Check for gemma models if "gemma/" in model: return VertexAIModelRoute.GEMMA @@ -150,71 +136,6 @@ all_gemini_url_modes = Literal[ ] -def get_vertex_base_model_name(model: str) -> str: - """ - Strip routing prefixes from model name for PSC/endpoint URL construction. - - Patterns like "bge/", "gemma/", "openai/" are used for internal routing but - should not appear in the actual endpoint URL. Routing prefixes are derived - from VertexAIModelRoute enum values. - - Args: - model: The model name with potential prefix (e.g., "bge/123456", "gemma/gemma-3-12b-it") - - Returns: - str: The model name without routing prefix (e.g., "123456", "gemma-3-12b-it") - - Examples: - >>> get_vertex_base_model_name("bge/378943383978115072") - "378943383978115072" - - >>> get_vertex_base_model_name("gemma/gemma-3-12b-it") - "gemma-3-12b-it" - - >>> get_vertex_base_model_name("openai/gpt-oss-120b") - "gpt-oss-120b" - - >>> get_vertex_base_model_name("1234567890") - "1234567890" - """ - # Derive routing prefixes from VertexAIModelRoute enum - # Map specific routes to their prefixes (some routes like PARTNER_MODELS, GEMINI don't have prefixes) - - - for route in VERTEX_AI_MODEL_ROUTES: - if model.startswith(route): - return model.replace(route, "", 1) - - return model - - -def _get_embedding_url( - model: str, - vertex_project: Optional[str], - vertex_location: Optional[str], - vertex_api_version: Literal["v1", "v1beta1"], -) -> Tuple[str, str]: - """ - Get URL for embedding models. - - Handles special patterns: - - bge/endpoint_id -> strips to endpoint_id for endpoints/ routing - - numeric model -> routes to endpoints/ - - regular model -> routes to publishers/google/models/ - """ - endpoint = "predict" - - # Strip routing prefixes (bge/, gemma/, etc.) for endpoint URL construction - model = get_vertex_base_model_name(model=model) - - url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" - if model.isdigit(): - # https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/endpoints/$ENDPOINT_ID:predict - url = f"https://{vertex_location}-aiplatform.googleapis.com/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}" - - return url, endpoint - - def _get_vertex_url( mode: all_gemini_url_modes, model: str, @@ -227,7 +148,6 @@ def _get_vertex_url( endpoint: Optional[str] = None model = litellm.VertexGeminiConfig.get_model_for_vertex_ai_url(model=model) - if mode == "chat": ### SET RUNTIME ENDPOINT ### endpoint = "generateContent" @@ -252,12 +172,11 @@ def _get_vertex_url( if stream is True: url += "?alt=sse" elif mode == "embedding": - return _get_embedding_url( - model=model, - vertex_project=vertex_project, - vertex_location=vertex_location, - vertex_api_version=vertex_api_version, - ) + endpoint = "predict" + url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" + if model.isdigit(): + # https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/endpoints/$ENDPOINT_ID:predict + url = f"https://{vertex_location}-aiplatform.googleapis.com/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}" elif mode == "image_generation": endpoint = "predict" url = f"https://{vertex_location}-aiplatform.googleapis.com/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}" diff --git a/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py b/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py index dabc620a6d..70b068b5a4 100644 --- a/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py +++ b/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py @@ -79,10 +79,6 @@ class ContextCachingEndpoints(VertexBase): stream=None, auth_header=auth_header, url=url, - model=None, - vertex_project=vertex_project, - vertex_location=vertex_location, - vertex_api_version="v1beta1" if custom_llm_provider == "vertex_ai_beta" else "v1", ) def check_cache( diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py index 624e682ec5..712a06dece 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/main.py @@ -39,6 +39,7 @@ class PartnerModelPrefixes(str, Enum): QWEN_PREFIX = "qwen" GPT_OSS_PREFIX = "openai/gpt-oss-" MINIMAX_PREFIX = "minimaxai/" + MOONSHOT_PREFIX = "moonshotai/" class VertexAIPartnerModels(VertexBase): @@ -64,6 +65,7 @@ class VertexAIPartnerModels(VertexBase): or model.startswith(PartnerModelPrefixes.QWEN_PREFIX) or model.startswith(PartnerModelPrefixes.GPT_OSS_PREFIX) or model.startswith(PartnerModelPrefixes.MINIMAX_PREFIX) + or model.startswith(PartnerModelPrefixes.MOONSHOT_PREFIX) ): return True return False @@ -76,6 +78,7 @@ class VertexAIPartnerModels(VertexBase): PartnerModelPrefixes.QWEN_PREFIX, PartnerModelPrefixes.GPT_OSS_PREFIX, PartnerModelPrefixes.MINIMAX_PREFIX, + PartnerModelPrefixes.MOONSHOT_PREFIX, ] if any(provider in model for provider in OPENAI_LIKE_VERTEX_PROVIDERS): return True diff --git a/litellm/llms/vertex_ai/vertex_embeddings/bge.py b/litellm/llms/vertex_ai/vertex_embeddings/bge.py deleted file mode 100644 index 2eff0ba96d..0000000000 --- a/litellm/llms/vertex_ai/vertex_embeddings/bge.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Vertex AI BGE (BAAI General Embedding) Configuration - -BGE models deployed on Vertex AI require different input/output format: -- Request: Use "prompt" instead of "content" as the input field -- Response: Embeddings are returned directly as arrays, not wrapped in objects - -Model name handling: -- Model names like "bge/endpoint_id" are automatically transformed in common_utils._get_vertex_url() -- This module focuses on request/response transformation only -""" - -from typing import List, Optional, Union - -from litellm.types.utils import EmbeddingResponse, Usage - -from .types import ( - EmbeddingParameters, - TaskType, - TextEmbeddingBGEInput, - VertexEmbeddingRequest, -) - - -class VertexBGEConfig: - """ - Configuration and transformation logic for BGE models on Vertex AI. - - BGE (BAAI General Embedding) models use a different request format - where the input field is named "prompt" instead of "content". - - Supported model patterns (after provider split in main.py): - - "bge-small-en-v1.5" (model name) - - "bge/204379420394258432" (endpoint ID pattern) - - Note: Model name transformation (bge/ -> numeric ID) is handled automatically - in common_utils._get_vertex_url(). This class focuses on request/response format only. - """ - - @staticmethod - def is_bge_model(model: str) -> bool: - """ - Check if the model is a BGE (BAAI General Embedding) model. - - After provider split in main.py, supports: - - "bge-small-en-v1.5" (model name) - - "bge/204379420394258432" (endpoint ID pattern) - - Args: - model: The model name after provider split - - Returns: - bool: True if the model is a BGE model - """ - model_lower = model.lower() - # Check for "bge/" prefix (endpoint pattern) or "bge" in model name - return model_lower.startswith("bge/") or "bge" in model_lower - - @staticmethod - def transform_request( - input: Union[list, str], optional_params: dict, model: str - ) -> VertexEmbeddingRequest: - """ - Transforms an OpenAI request to a Vertex BGE embedding request. - - BGE models use "prompt" instead of "content" as the input field. - - Args: - input: The input text(s) to embed - optional_params: Optional parameters for the request - model: The model name - - Returns: - VertexEmbeddingRequest: The transformed request - """ - vertex_request: VertexEmbeddingRequest = VertexEmbeddingRequest() - vertex_text_embedding_input_list: List[TextEmbeddingBGEInput] = [] - task_type: Optional[TaskType] = optional_params.get("task_type") - title = optional_params.get("title") - - if isinstance(input, str): - input = [input] - - for text in input: - embedding_input = VertexBGEConfig._create_embedding_input( - prompt=text, task_type=task_type, title=title - ) - vertex_text_embedding_input_list.append(embedding_input) - - vertex_request["instances"] = vertex_text_embedding_input_list - vertex_request["parameters"] = EmbeddingParameters(**optional_params) - - return vertex_request - - @staticmethod - def _create_embedding_input( - prompt: str, - task_type: Optional[TaskType] = None, - title: Optional[str] = None, - ) -> TextEmbeddingBGEInput: - """ - Creates a TextEmbeddingBGEInput object for BGE models. - - BGE models use "prompt" instead of "content" as the input field. - - Args: - prompt: The prompt to be embedded - task_type: The type of task to be performed - title: The title of the document to be embedded - - Returns: - TextEmbeddingBGEInput: A TextEmbeddingBGEInput object - """ - text_embedding_input = TextEmbeddingBGEInput(prompt=prompt) - if task_type is not None: - text_embedding_input["task_type"] = task_type - if title is not None: - text_embedding_input["title"] = title - return text_embedding_input - - @staticmethod - def transform_response( - response: dict, model: str, model_response: EmbeddingResponse - ) -> EmbeddingResponse: - """ - Transforms a Vertex BGE embedding response to OpenAI format. - - BGE models return embeddings directly as arrays in predictions: - { - "predictions": [ - [0.002, 0.021, ...], - [0.003, 0.022, ...] - ] - } - - Args: - response: The raw response from Vertex AI - model: The model name - model_response: The EmbeddingResponse object to populate - - Returns: - EmbeddingResponse: The transformed response in OpenAI format - - Raises: - KeyError: If response doesn't contain 'predictions' - ValueError: If predictions is not a list or contains invalid data - """ - if "predictions" not in response: - raise KeyError("Response missing 'predictions' field") - - _predictions = response["predictions"] - - if not isinstance(_predictions, list): - raise ValueError(f"Expected 'predictions' to be a list, got {type(_predictions)}") - - embedding_response = [] - # BGE models don't return token counts, so we estimate or set to 0 - input_tokens = 0 - - for idx, embedding_values in enumerate(_predictions): - if not isinstance(embedding_values, list): - raise ValueError( - f"Expected embedding at index {idx} to be a list, got {type(embedding_values)}" - ) - - embedding_response.append( - { - "object": "embedding", - "index": idx, - "embedding": embedding_values, - } - ) - - model_response.object = "list" - model_response.data = embedding_response - model_response.model = model - usage = Usage( - prompt_tokens=input_tokens, completion_tokens=0, total_tokens=input_tokens - ) - setattr(model_response, "usage", usage) - return model_response - diff --git a/litellm/llms/vertex_ai/vertex_embeddings/transformation.py b/litellm/llms/vertex_ai/vertex_embeddings/transformation.py index 5a3a4a7188..97af558041 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/transformation.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/transformation.py @@ -105,16 +105,10 @@ class VertexAITextEmbeddingConfig(BaseModel): """ Transforms an openai request to a vertex embedding request. """ - # Import here to avoid circular import issues with litellm.__init__ - from litellm.llms.vertex_ai.vertex_embeddings.bge import VertexBGEConfig if model.isdigit(): return self._transform_openai_request_to_fine_tuned_embedding_request( input, optional_params, model ) - if VertexBGEConfig.is_bge_model(model): - return VertexBGEConfig.transform_request( - input=input, optional_params=optional_params, model=model - ) vertex_request: VertexEmbeddingRequest = VertexEmbeddingRequest() vertex_text_embedding_input_list: List[TextEmbeddingInput] = [] @@ -173,9 +167,6 @@ class VertexAITextEmbeddingConfig(BaseModel): vertex_request["parameters"] = TextEmbeddingFineTunedParameters( **optional_params ) - # Remove 'shared_session' from parameters if present - if vertex_request["parameters"] is not None and "shared_session" in vertex_request["parameters"]: - del vertex_request["parameters"]["shared_session"] # type: ignore[typeddict-item] return vertex_request @@ -192,8 +183,8 @@ class VertexAITextEmbeddingConfig(BaseModel): Args: content (str): The content to be embedded. - task_type (Optional[TaskType]): The type of task to be performed. - title (Optional[str]): The title of the document to be embedded. + task_type (Optional[TaskType]): The type of task to be performed". + title (Optional[str]): The title of the document to be embedded Returns: TextEmbeddingInput: A TextEmbeddingInput object. @@ -215,14 +206,6 @@ class VertexAITextEmbeddingConfig(BaseModel): return self._transform_vertex_response_to_openai_for_fine_tuned_models( response, model, model_response ) - - # Import here to avoid circular import issues with litellm.__init__ - from litellm.llms.vertex_ai.vertex_embeddings.bge import VertexBGEConfig - - if VertexBGEConfig.is_bge_model(model): - return VertexBGEConfig.transform_response( - response=response, model=model, model_response=model_response - ) _predictions = response["predictions"] diff --git a/litellm/llms/vertex_ai/vertex_embeddings/types.py b/litellm/llms/vertex_ai/vertex_embeddings/types.py index fa9794d79a..7f85ea46f3 100644 --- a/litellm/llms/vertex_ai/vertex_embeddings/types.py +++ b/litellm/llms/vertex_ai/vertex_embeddings/types.py @@ -25,12 +25,6 @@ class TextEmbeddingInput(TypedDict, total=False): title: Optional[str] -class TextEmbeddingBGEInput(TypedDict, total=False): - prompt: str - task_type: Optional[TaskType] - title: Optional[str] - - # Fine-tuned models require a different input format # Ref: https://console.cloud.google.com/vertex-ai/model-garden?hl=en&project=adroit-crow-413218&pageState=(%22galleryStateKey%22:(%22f%22:(%22g%22:%5B%5D,%22o%22:%5B%5D),%22s%22:%22%22)) class TextEmbeddingFineTunedInput(TypedDict, total=False): @@ -50,7 +44,7 @@ class EmbeddingParameters(TypedDict, total=False): class VertexEmbeddingRequest(TypedDict, total=False): - instances: Union[List[TextEmbeddingInput], List[TextEmbeddingBGEInput], List[TextEmbeddingFineTunedInput]] + instances: Union[List[TextEmbeddingInput], List[TextEmbeddingFineTunedInput]] parameters: Optional[Union[EmbeddingParameters, TextEmbeddingFineTunedParameters]] diff --git a/litellm/llms/vertex_ai/vertex_gemma_models/main.py b/litellm/llms/vertex_ai/vertex_gemma_models/main.py index 41bd6b5431..8203b285eb 100644 --- a/litellm/llms/vertex_ai/vertex_gemma_models/main.py +++ b/litellm/llms/vertex_ai/vertex_gemma_models/main.py @@ -25,7 +25,7 @@ import httpx # type: ignore from litellm.utils import ModelResponse -from ..common_utils import VertexAIError, get_vertex_base_model_name +from ..common_utils import VertexAIError from ..vertex_llm_base import VertexBase @@ -82,8 +82,7 @@ class VertexAIGemmaModels(VertexBase): message="""Upgrade vertex ai. Run `pip install "google-cloud-aiplatform>=1.38"`""", ) try: - - model = get_vertex_base_model_name(model=model) + model = model.replace("gemma/", "") vertex_httpx_logic = VertexLLM() access_token, project_id = vertex_httpx_logic._ensure_access_token( diff --git a/litellm/llms/vertex_ai/vertex_llm_base.py b/litellm/llms/vertex_ai/vertex_llm_base.py index ce50bf311e..9ddbc461a7 100644 --- a/litellm/llms/vertex_ai/vertex_llm_base.py +++ b/litellm/llms/vertex_ai/vertex_llm_base.py @@ -19,7 +19,6 @@ from .common_utils import ( _get_gemini_url, _get_vertex_url, all_gemini_url_modes, - get_vertex_base_model_name, is_global_only_vertex_model, ) @@ -242,9 +241,6 @@ class VertexBase: auth_header=None, url=default_api_base, model=model, - vertex_project=vertex_project or project_id, - vertex_location=vertex_location or "us-central1", - vertex_api_version="v1", # Partner models typically use v1 ) return api_base @@ -293,18 +289,9 @@ class VertexBase: auth_header: Optional[str], url: str, model: Optional[str] = None, - vertex_project: Optional[str] = None, - vertex_location: Optional[str] = None, - vertex_api_version: Optional[Literal["v1", "v1beta1"]] = None, ) -> Tuple[Optional[str], str]: """ for cloudflare ai gateway - https://github.com/BerriAI/litellm/issues/4317 - - Handles custom api_base for: - 1. Gemini (Google AI Studio) - constructs /models/{model}:{endpoint} - 2. Vertex AI with standard proxies - constructs {api_base}:{endpoint} - 3. Vertex AI with PSC endpoints - constructs full path structure - {api_base}/v1/projects/{project}/locations/{location}/endpoints/{model}:{endpoint} ## Returns - (auth_header, url) - Tuple[Optional[str], str] @@ -324,37 +311,8 @@ class VertexBase: if gemini_api_key is not None: auth_header = {"x-goog-api-key": gemini_api_key} # type: ignore[assignment] else: - # For Vertex AI - # Check if this is a PSC endpoint or custom deployment - # PSC/custom endpoints need the full path structure - if vertex_project and vertex_location and model: - # Strip routing prefixes (bge/, gemma/, etc.) for endpoint URL construction - model_for_url = get_vertex_base_model_name(model=model) - - # Check if model is numeric (endpoint ID) or if api_base doesn't contain googleapis.com - # These are indicators of PSC/custom endpoints - is_psc_or_custom = ( - "googleapis.com" not in api_base.lower() or model_for_url.isdigit() - ) - - if is_psc_or_custom: - # Construct full PSC/custom endpoint URL - # Format: {api_base}/v1/projects/{project}/locations/{location}/endpoints/{model}:{endpoint} - version = vertex_api_version or "v1" - url = "{}/{}/projects/{}/locations/{}/endpoints/{}:{}".format( - api_base.rstrip("/"), - version, - vertex_project, - vertex_location, - model_for_url, - endpoint, - ) - else: - # Standard proxy - just append endpoint - url = "{}:{}".format(api_base, endpoint) - else: - # Fallback to simple format if we don't have all parameters - url = "{}:{}".format(api_base, endpoint) + url = "{}:{}".format(api_base, endpoint) + if stream is True: url = url + "?alt=sse" return auth_header, url @@ -381,7 +339,6 @@ class VertexBase: Returns token, url """ - version: Optional[Literal["v1beta1", "v1"]] = None if custom_llm_provider == "gemini": url, endpoint = _get_gemini_url( mode=mode, @@ -397,7 +354,7 @@ class VertexBase: ) ### SET RUNTIME ENDPOINT ### - version = ( + version: Literal["v1beta1", "v1"] = ( "v1beta1" if should_use_v1beta1_features is True else "v1" ) url, endpoint = _get_vertex_url( @@ -418,9 +375,6 @@ class VertexBase: stream=stream, url=url, model=model, - vertex_project=vertex_project, - vertex_location=vertex_location, - vertex_api_version=version, ) def _handle_reauthentication( diff --git a/litellm/llms/vertex_ai/vertex_model_garden/main.py b/litellm/llms/vertex_ai/vertex_model_garden/main.py index fe7d0862e0..1c57096734 100644 --- a/litellm/llms/vertex_ai/vertex_model_garden/main.py +++ b/litellm/llms/vertex_ai/vertex_model_garden/main.py @@ -22,7 +22,7 @@ import httpx # type: ignore from litellm.utils import ModelResponse -from ..common_utils import VertexAIError, get_vertex_base_model_name +from ..common_utils import VertexAIError from ..vertex_llm_base import VertexBase @@ -89,7 +89,7 @@ class VertexAIModelGardenModels(VertexBase): message="""Upgrade vertex ai. Run `pip install "google-cloud-aiplatform>=1.38"`""", ) try: - model = get_vertex_base_model_name(model=model) + model = model.replace("openai/", "") vertex_httpx_logic = VertexLLM() access_token, project_id = vertex_httpx_logic._ensure_access_token( @@ -123,10 +123,6 @@ class VertexAIModelGardenModels(VertexBase): stream=stream, auth_header=None, url=default_api_base, - model=model, - vertex_project=vertex_project or project_id, - vertex_location=vertex_location or "us-central1", - vertex_api_version="v1beta1", ) model = "" return openai_like_chat_completions.completion( diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index fa36e2d608..0fe71e4541 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -8523,6 +8523,14 @@ "/v1/images/generations" ] }, + "fal_ai/fal-ai/flux/schnell": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.003, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "fal_ai/fal-ai/imagen4/preview": { "litellm_provider": "fal_ai", "mode": "image_generation", @@ -8531,6 +8539,22 @@ "/v1/images/generations" ] }, + "fal_ai/fal-ai/imagen4/preview/fast": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.02, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/fal-ai/imagen4/preview/ultra": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.06, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "fal_ai/fal-ai/recraft/v3/text-to-image": { "litellm_provider": "fal_ai", "mode": "image_generation", @@ -23408,6 +23432,19 @@ "supports_function_calling": true, "supports_tool_choice": true }, + "vertex_ai/moonshotai/kimi-k2-thinking-maas": { + "input_cost_per_token": 6e-07, + "litellm_provider": "vertex_ai-moonshot_models", + "max_input_tokens": 256000, + "max_output_tokens": 256000, + "max_tokens": 256000, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#partner-models", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "vertex_ai/mistral-medium-3": { "input_cost_per_token": 4e-07, "litellm_provider": "vertex_ai-mistral_models", @@ -23744,6 +23781,22 @@ "mode": "embedding", "output_cost_per_token": 0.0 }, + "voyage/voyage-3.5": { + "input_cost_per_token": 6e-08, + "litellm_provider": "voyage", + "max_input_tokens": 32000, + "max_tokens": 32000, + "mode": "embedding", + "output_cost_per_token": 0.0 + }, + "voyage/voyage-3.5-lite": { + "input_cost_per_token": 2e-08, + "litellm_provider": "voyage", + "max_input_tokens": 32000, + "max_tokens": 32000, + "mode": "embedding", + "output_cost_per_token": 0.0 + }, "voyage/voyage-code-2": { "input_cost_per_token": 1.2e-07, "litellm_provider": "voyage", @@ -24789,7 +24842,9 @@ "1280x720", "720x1280" ], - "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + "metadata": { + "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + } }, "runwayml/gen4_aleph": { "litellm_provider": "runwayml", @@ -24807,7 +24862,9 @@ "1280x720", "720x1280" ], - "comment": "15 credits per second @ $0.01 per credit = $0.15 per second" + "metadata": { + "comment": "15 credits per second @ $0.01 per credit = $0.15 per second" + } }, "runwayml/gen3a_turbo": { "litellm_provider": "runwayml", @@ -24825,7 +24882,9 @@ "1280x720", "720x1280" ], - "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + "metadata": { + "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + } }, "runwayml/gen4_image": { "litellm_provider": "runwayml", @@ -24844,7 +24903,9 @@ "1280x720", "1920x1080" ], - "comment": "5 credits per 720p image or 8 credits per 1080p image @ $0.01 per credit. Using 5 credits ($0.05) as base cost" + "metadata": { + "comment": "5 credits per 720p image or 8 credits per 1080p image @ $0.01 per credit. Using 5 credits ($0.05) as base cost" + } }, "runwayml/gen4_image_turbo": { "litellm_provider": "runwayml", @@ -24863,6 +24924,17 @@ "1280x720", "1920x1080" ], - "comment": "2 credits per image (any resolution) @ $0.01 per credit = $0.02 per image" + "metadata": { + "comment": "2 credits per image (any resolution) @ $0.01 per credit = $0.02 per image" + } + }, + "runwayml/eleven_multilingual_v2": { + "litellm_provider": "runwayml", + "mode": "audio_speech", + "input_cost_per_character": 3e-07, + "source": "https://docs.dev.runwayml.com/guides/pricing/", + "metadata": { + "comment": "Estimated cost based on standard TTS pricing. RunwayML uses ElevenLabs models." + } } } diff --git a/litellm/proxy/_experimental/out/assets/logos/runway.png b/litellm/proxy/_experimental/out/assets/logos/runway.png new file mode 100644 index 0000000000..c909cb9e0f Binary files /dev/null and b/litellm/proxy/_experimental/out/assets/logos/runway.png differ diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 448528bd68..1029b1964a 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -292,6 +292,7 @@ class ProxyBaseLLMRequestProcessing: proxy_config: ProxyConfig, route_type: Literal[ "acompletion", + "aembedding", "aresponses", "_arealtime", "aget_responses", @@ -403,6 +404,7 @@ class ProxyBaseLLMRequestProcessing: user_api_key_dict: UserAPIKeyAuth, route_type: Literal[ "acompletion", + "aembedding", "aresponses", "_arealtime", "aget_responses", @@ -772,10 +774,12 @@ class ProxyBaseLLMRequestProcessing: @staticmethod def _get_pre_call_type( - route_type: Literal["acompletion", "aresponses"], - ) -> Literal["completion", "responses"]: + route_type: Literal["acompletion", "aembedding", "aresponses"], + ) -> Literal["completion", "embeddings", "responses"]: if route_type == "acompletion": return "completion" + elif route_type == "aembedding": + return "embeddings" elif route_type == "aresponses": return "responses" diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index c21a25b51c..ac78967140 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -1158,7 +1158,7 @@ def _enforced_params_check( ) if enforced_params is None: return True - if enforced_params is not None and premium_user is not True: + if enforced_params and premium_user is not True: raise ValueError( f"Enforced Params is an Enterprise feature. Enforced Params: {enforced_params}. {CommonProxyErrors.not_premium_user.value}" ) diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index 22874ca8f1..4e8db85e5e 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -68,7 +68,9 @@ if MCP_AVAILABLE: except AttributeError: redacted_server = mcp_server.copy(deep=True) # type: ignore[attr-defined] - redacted_server.credentials = None + if hasattr(redacted_server, "credentials"): + setattr(redacted_server, "credentials", None) + return redacted_server def _redact_mcp_credentials_list( diff --git a/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py b/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py new file mode 100644 index 0000000000..0c820f6b78 --- /dev/null +++ b/litellm/proxy/management_endpoints/model_access_group_management_endpoints.py @@ -0,0 +1,688 @@ +""" +Allow proxy admin to manage model access groups + +Endpoints here: +- POST /model_group/new - Create a new access group with multiple model names +""" + +import json +from typing import Any, Dict, List, Tuple + +from fastapi import APIRouter, Depends, HTTPException + +from litellm._logging import verbose_proxy_logger +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth + +# Clear cache and reload models to pick up the access group changes +from litellm.proxy.management_endpoints.model_management_endpoints import ( + clear_cache, +) +from litellm.proxy.utils import PrismaClient +from litellm.types.proxy.management_endpoints.model_management_endpoints import ( + AccessGroupInfo, + DeleteModelGroupResponse, + ListAccessGroupsResponse, + NewModelGroupRequest, + NewModelGroupResponse, + UpdateModelGroupRequest, +) + +router = APIRouter() + + +def validate_models_exist( + model_names: List[str], llm_router +) -> Tuple[bool, List[str]]: + """ + Validate that all requested model names exist in the router. + Checks only exact model name matches. + + Returns: + Tuple[bool, List[str]]: (all_valid, missing_models) + """ + if llm_router is None: + return False, model_names + + router_model_names = set(llm_router.get_model_names()) + missing = [m for m in model_names if m not in router_model_names] + return (len(missing) == 0, missing) + + +def add_access_group_to_deployment( + model_info: Dict[str, Any], access_group: str +) -> Tuple[Dict[str, Any], bool]: + """ + Add an access group to a deployment's model_info. + + Args: + model_info: The model_info dictionary from the deployment + access_group: The access group name to add + + Returns: + Tuple[Dict[str, Any], bool]: (updated_model_info, was_modified) + """ + access_groups = model_info.get("access_groups", []) + + # Check if access group already exists + if access_group in access_groups: + return model_info, False + + # Add the access group + access_groups.append(access_group) + model_info["access_groups"] = access_groups + + return model_info, True + + +async def update_deployments_with_access_group( + model_names: List[str], + access_group: str, + prisma_client: PrismaClient, +) -> int: + """ + Update all deployments for the given model names to include the access group. + + Args: + model_names: List of model names whose deployments should be updated + access_group: The access group name to add + prisma_client: Database client + + Returns: + int: Number of deployments updated + """ + models_updated = 0 + + for model_name in model_names: + verbose_proxy_logger.debug( + f"Updating deployments for model_name: {model_name}" + ) + + # Get all deployments with this model_name + deployments = await prisma_client.db.litellm_proxymodeltable.find_many( + where={"model_name": model_name} + ) + + verbose_proxy_logger.debug( + f"Found {len(deployments)} deployments for model_name: {model_name}" + ) + + # If no deployments found, this is a config model (not in DB) + if len(deployments) == 0: + raise HTTPException( + status_code=400, + detail={ + "error": f"Can't find model '{model_name}' in Database. Access group management is only supported for database models." + }, + ) + + # Update each deployment + for deployment in deployments: + model_info = deployment.model_info or {} + + # Add access group using helper + updated_model_info, was_modified = add_access_group_to_deployment( + model_info=model_info, + access_group=access_group, + ) + + # Only update in DB if modified + if was_modified: + await prisma_client.db.litellm_proxymodeltable.update( + where={"model_id": deployment.model_id}, + data={"model_info": json.dumps(updated_model_info)}, + ) + + models_updated += 1 + verbose_proxy_logger.debug( + f"Updated deployment {deployment.model_id} with access group: {access_group}" + ) + + return models_updated + + +def remove_access_group_from_deployment( + model_info: Dict[str, Any], access_group: str +) -> Tuple[Dict[str, Any], bool]: + """ + Remove an access group from a deployment's model_info. + + Args: + model_info: The model_info dictionary from the deployment + access_group: The access group name to remove + + Returns: + Tuple[Dict[str, Any], bool]: (updated_model_info, was_modified) + """ + access_groups = model_info.get("access_groups", []) + + # Check if access group exists + if access_group not in access_groups: + return model_info, False + + # Remove the access group + access_groups.remove(access_group) + model_info["access_groups"] = access_groups + + return model_info, True + + +async def get_all_access_groups_from_db( + prisma_client: PrismaClient, +) -> Dict[str, AccessGroupInfo]: + """ + Get all access groups from the database. + + Returns: + Dict[str, AccessGroupInfo]: Dictionary mapping access_group name to info + """ + # Get all deployments + deployments = await prisma_client.db.litellm_proxymodeltable.find_many() + + # Build access group map + access_group_map: Dict[str, Dict[str, Any]] = {} + + for deployment in deployments: + model_info = deployment.model_info or {} + access_groups = model_info.get("access_groups", []) + model_name = deployment.model_name + + for access_group in access_groups: + if access_group not in access_group_map: + access_group_map[access_group] = { + "model_names": set(), + "deployment_count": 0, + } + + access_group_map[access_group]["model_names"].add(model_name) + access_group_map[access_group]["deployment_count"] += 1 + + # Convert to AccessGroupInfo objects + result = {} + for access_group, data in access_group_map.items(): + result[access_group] = AccessGroupInfo( + access_group=access_group, + model_names=sorted(list(data["model_names"])), + deployment_count=data["deployment_count"], + ) + + return result + + +@router.post( + "/access_group/new", + tags=["model management"], + dependencies=[Depends(user_api_key_auth)], + response_model=NewModelGroupResponse, +) +async def create_model_group( + data: NewModelGroupRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Create a new access group containing multiple model names. + + An access group is a named collection of model groups that can be referenced + by teams/keys for simplified access control. + + Example: + ```bash + curl -X POST 'http://localhost:4000/access_group/new' \\ + -H 'Authorization: Bearer sk-1234' \\ + -H 'Content-Type: application/json' \\ + -d '{ + "access_group": "production-models", + "model_names": ["gpt-4", "claude-3-opus", "gemini-pro"] + }' + ``` + + Parameters: + - access_group: str - The access group name (e.g., "production-models") + - model_names: List[str] - List of existing model groups to include + + Returns: + - NewModelGroupResponse with the created access group details + + Raises: + - HTTPException 400: If any model names don't exist + - HTTPException 500: If database operations fail + """ + from litellm.proxy.proxy_server import ( + llm_router, + prisma_client, + ) + + verbose_proxy_logger.debug( + f"Creating access group: {data.access_group} with models: {data.model_names}" + ) + + # Validation: Check if access_group is provided + if not data.access_group or not data.access_group.strip(): + raise HTTPException( + status_code=400, + detail={"error": "access_group is required and cannot be empty"}, + ) + + # Validation: Check if model_names list is provided and not empty + if not data.model_names or len(data.model_names) == 0: + raise HTTPException( + status_code=400, + detail={"error": "model_names list is required and cannot be empty"}, + ) + + # Validation: Check if all models exist in the router + all_valid, missing_models = validate_models_exist( + model_names=data.model_names, + llm_router=llm_router, + ) + + if not all_valid: + raise HTTPException( + status_code=400, + detail={"error": f"Model(s) not found: {', '.join(missing_models)}"}, + ) + + # Check if database is connected + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected. Cannot create access group."}, + ) + + try: + # Check if access group already exists + existing_access_groups = await get_all_access_groups_from_db( + prisma_client=prisma_client + ) + + if data.access_group in existing_access_groups: + raise HTTPException( + status_code=409, + detail={"error": f"Access group '{data.access_group}' already exists. Use PUT /access_group/{data.access_group}/update to modify it."}, + ) + + # Update deployments using helper function + models_updated = await update_deployments_with_access_group( + model_names=data.model_names, + access_group=data.access_group, + prisma_client=prisma_client, + ) + + await clear_cache() + + verbose_proxy_logger.info( + f"Successfully created access group '{data.access_group}' with {models_updated} models updated" + ) + + return NewModelGroupResponse( + access_group=data.access_group, + model_names=data.model_names, + models_updated=models_updated, + ) + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception( + f"Error creating access group '{data.access_group}': {str(e)}" + ) + raise HTTPException( + status_code=500, + detail={"error": f"Failed to create access group: {str(e)}"}, + ) + + +@router.get( + "/access_group/list", + tags=["model management"], + dependencies=[Depends(user_api_key_auth)], + response_model=ListAccessGroupsResponse, +) +async def list_access_groups( + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + List all access groups. + + Returns a list of all access groups with their model names and deployment counts. + + Example: + ```bash + curl -X GET 'http://localhost:4000/access_group/list' \\ + -H 'Authorization: Bearer sk-1234' + ``` + + Returns: + - ListAccessGroupsResponse with all access groups + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected."}, + ) + + try: + access_groups_map = await get_all_access_groups_from_db( + prisma_client=prisma_client + ) + + # Sort by access group name + access_groups_list = sorted( + access_groups_map.values(), + key=lambda x: x.access_group, + ) + + return ListAccessGroupsResponse(access_groups=access_groups_list) + + except Exception as e: + verbose_proxy_logger.exception(f"Error listing access groups: {str(e)}") + raise HTTPException( + status_code=500, + detail={"error": f"Failed to list access groups: {str(e)}"}, + ) + + +@router.get( + "/access_group/{access_group}/info", + tags=["model management"], + dependencies=[Depends(user_api_key_auth)], + response_model=AccessGroupInfo, +) +async def get_access_group_info( + access_group: str, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Get information about a specific access group. + + Example: + ```bash + curl -X GET 'http://localhost:4000/access_group/production-models/info' \\ + -H 'Authorization: Bearer sk-1234' + ``` + + Parameters: + - access_group: str - The access group name (URL path parameter) + + Returns: + - AccessGroupInfo with the access group details + + Raises: + - HTTPException 404: If access group not found + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected."}, + ) + + try: + access_groups_map = await get_all_access_groups_from_db( + prisma_client=prisma_client + ) + + if access_group not in access_groups_map: + raise HTTPException( + status_code=404, + detail={"error": f"Access group '{access_group}' not found"}, + ) + + return access_groups_map[access_group] + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception( + f"Error getting access group info for '{access_group}': {str(e)}" + ) + raise HTTPException( + status_code=500, + detail={"error": f"Failed to get access group info: {str(e)}"}, + ) + + +@router.put( + "/access_group/{access_group}/update", + tags=["model management"], + dependencies=[Depends(user_api_key_auth)], + response_model=NewModelGroupResponse, +) +async def update_access_group( + access_group: str, + data: UpdateModelGroupRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Update an access group's model names. + + This will: + 1. Remove the access group from all current deployments + 2. Add the access group to all deployments for the new model_names list + + Example: + ```bash + curl -X PUT 'http://localhost:4000/access_group/production-models/update' \\ + -H 'Authorization: Bearer sk-1234' \\ + -H 'Content-Type: application/json' \\ + -d '{ + "model_names": ["gpt-4", "claude-3-sonnet"] + }' + ``` + + Parameters: + - access_group: str - The access group name (URL path parameter) + - model_names: List[str] - New list of model groups to include + + Returns: + - NewModelGroupResponse with the updated access group details + + Raises: + - HTTPException 400: If any model names don't exist + - HTTPException 404: If access group not found + """ + from litellm.proxy.proxy_server import llm_router, prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected."}, + ) + + verbose_proxy_logger.debug( + f"Updating access group: {access_group} with models: {data.model_names}" + ) + + # Validation: Check if model_names list is provided and not empty + if not data.model_names or len(data.model_names) == 0: + raise HTTPException( + status_code=400, + detail={"error": "model_names list is required and cannot be empty"}, + ) + + # Validation: Check if access group exists + try: + access_groups_map = await get_all_access_groups_from_db( + prisma_client=prisma_client + ) + if access_group not in access_groups_map: + raise HTTPException( + status_code=404, + detail={"error": f"Access group '{access_group}' not found"}, + ) + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=500, + detail={"error": f"Failed to check access group existence: {str(e)}"}, + ) + + # Validation: Check if all new models exist + all_valid, missing_models = validate_models_exist( + model_names=data.model_names, + llm_router=llm_router, + ) + + if not all_valid: + raise HTTPException( + status_code=400, + detail={"error": f"Model(s) not found: {', '.join(missing_models)}"}, + ) + + try: + # Step 1: Remove access group from ALL DB deployments (skip config models) + all_deployments = await prisma_client.db.litellm_proxymodeltable.find_many() + + for deployment in all_deployments: + model_info = deployment.model_info or {} + + + updated_model_info, was_modified = remove_access_group_from_deployment( + model_info=model_info, + access_group=access_group, + ) + + if was_modified: + await prisma_client.db.litellm_proxymodeltable.update( + where={"model_id": deployment.model_id}, + data={"model_info": json.dumps(updated_model_info)}, + ) + + # Step 2: Add access group to new model_names + models_updated = await update_deployments_with_access_group( + model_names=data.model_names, + access_group=access_group, + prisma_client=prisma_client, + ) + + # Clear cache and reload models to pick up the access group changes + await clear_cache() + + verbose_proxy_logger.info( + f"Successfully updated access group '{access_group}' with {models_updated} models updated" + ) + + return NewModelGroupResponse( + access_group=access_group, + model_names=data.model_names, + models_updated=models_updated, + ) + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception( + f"Error updating access group '{access_group}': {str(e)}" + ) + raise HTTPException( + status_code=500, + detail={"error": f"Failed to update access group: {str(e)}"}, + ) + + +@router.delete( + "/access_group/{access_group}/delete", + tags=["model management"], + dependencies=[Depends(user_api_key_auth)], + response_model=DeleteModelGroupResponse, +) +async def delete_access_group( + access_group: str, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Delete an access group. + + Removes the access group from all deployments that have it. + + Example: + ```bash + curl -X DELETE 'http://localhost:4000/access_group/production-models/delete' \\ + -H 'Authorization: Bearer sk-1234' + ``` + + Parameters: + - access_group: str - The access group name (URL path parameter) + + Returns: + - DeleteModelGroupResponse with deletion details + + Raises: + - HTTPException 404: If access group not found + """ + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected."}, + ) + + verbose_proxy_logger.debug(f"Deleting access group: {access_group}") + + # Validation: Check if access group exists + try: + access_groups_map = await get_all_access_groups_from_db( + prisma_client=prisma_client + ) + if access_group not in access_groups_map: + raise HTTPException( + status_code=404, + detail={"error": f"Access group '{access_group}' not found"}, + ) + except HTTPException: + raise + except Exception as e: + raise HTTPException( + status_code=500, + detail={"error": f"Failed to check access group existence: {str(e)}"}, + ) + + try: + # Remove access group from all DB deployments (skip config models) + all_deployments = await prisma_client.db.litellm_proxymodeltable.find_many() + models_updated = 0 + + for deployment in all_deployments: + model_info = deployment.model_info or {} + + updated_model_info, was_modified = remove_access_group_from_deployment( + model_info=model_info, + access_group=access_group, + ) + + if was_modified: + await prisma_client.db.litellm_proxymodeltable.update( + where={"model_id": deployment.model_id}, + data={"model_info": json.dumps(updated_model_info)}, + ) + models_updated += 1 + + # Clear cache and reload models to pick up the access group changes + await clear_cache() + + verbose_proxy_logger.info( + f"Successfully deleted access group '{access_group}' from {models_updated} deployments" + ) + + return DeleteModelGroupResponse( + access_group=access_group, + models_updated=models_updated, + message=f"Access group '{access_group}' deleted successfully", + ) + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception( + f"Error deleting access group '{access_group}': {str(e)}" + ) + raise HTTPException( + status_code=500, + detail={"error": f"Failed to delete access group: {str(e)}"}, + ) + diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 2077955fc5..f81bcd14d5 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -292,6 +292,9 @@ from litellm.proxy.management_endpoints.model_management_endpoints import ( from litellm.proxy.management_endpoints.model_management_endpoints import ( router as model_management_router, ) +from litellm.proxy.management_endpoints.model_access_group_management_endpoints import ( + router as model_access_group_management_router, +) from litellm.proxy.management_endpoints.organization_endpoints import ( router as organization_router, ) @@ -5011,40 +5014,11 @@ async def embeddings( # noqa: PLR0915 global proxy_logging_obj data: Any = {} try: - # Use orjson to parse JSON data, orjson speeds up requests significantly - body = await request.body() - data = orjson.loads(body) - - verbose_proxy_logger.debug( - "Request received by LiteLLM:\n%s", - json.dumps(data, indent=4), - ) - - # Include original request and headers in the data - data = await add_litellm_data_to_request( - data=data, - request=request, - general_settings=general_settings, - user_api_key_dict=user_api_key_dict, - version=version, - proxy_config=proxy_config, - ) - - data["model"] = ( - general_settings.get("embedding_model", None) # server default - or user_model # model name passed via cli args - or model # for azure deployments - or data.get("model", None) # default passed in http request - ) - if user_model: - data["model"] = user_model - - ### MODEL ALIAS MAPPING ### - # check if model name in model alias map - # get the actual model name - if data["model"] in litellm.model_alias_map: - data["model"] = litellm.model_alias_map[data["model"]] + # Use shared request body reading helper (same as chat/completions) + data = await _read_request_body(request=request) + ### HANDLE TOKEN ARRAY INPUT DECODING ### + # This must happen BEFORE base_process_llm_request() since it modifies the input router_model_names = llm_router.model_names if llm_router is not None else [] if ( "input" in data @@ -5054,126 +5028,61 @@ async def embeddings( # noqa: PLR0915 and isinstance(data["input"][0][0], int) ): # check if array of tokens passed in # check if provider accept list of tokens as input - e.g. for langchain integration - if llm_model_list is not None and data["model"] in router_model_names: - for m in llm_model_list: - if m["model_name"] == data["model"]: - if m["litellm_params"][ - "model" - ] in litellm.open_ai_embedding_models or any( - m["litellm_params"]["model"].startswith(provider) + if llm_router is not None and data.get("model") in router_model_names: + # Use router's O(1) lookup instead of O(N) iteration through llm_model_list + deployment = llm_router.get_deployment(model_id=data["model"]) + if deployment is not None: + litellm_model = deployment.get("litellm_params", {}).get("model", "") + # Check if this provider supports token arrays + supports_token_arrays = ( + litellm_model in litellm.open_ai_embedding_models + or any( + litellm_model.startswith(provider) for provider in LITELLM_EMBEDDING_PROVIDERS_SUPPORTING_INPUT_ARRAY_OF_TOKENS - ): - pass - else: - # non-openai/azure embedding model called with token input - input_list = [] - for i in data["input"]: - input_list.append( - litellm.decode(model="gpt-3.5-turbo", tokens=i) - ) - data["input"] = input_list - break + ) + ) + if not supports_token_arrays: + # non-openai/azure embedding model called with token input - decode tokens + input_list = [] + for i in data["input"]: + input_list.append( + litellm.decode(model="gpt-3.5-turbo", tokens=i) + ) + data["input"] = input_list - ### CALL HOOKS ### - modify incoming data / reject request before calling the model - data = await proxy_logging_obj.pre_call_hook( + # Use unified request processor (same as chat/completions and responses) + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) + + # Process the request with all optimizations (shared sessions, network tuning, etc.) + response = await base_llm_response_processor.base_process_llm_request( + request=request, + fastapi_response=fastapi_response, user_api_key_dict=user_api_key_dict, - data=data, - call_type=CallTypes.aembedding.value, - ) - - tasks = [] - tasks.append( - proxy_logging_obj.during_call_hook( - data=data, - user_api_key_dict=user_api_key_dict, - call_type="aembedding", - ) - ) - - ## ROUTE TO CORRECT ENDPOINT ## - llm_call = await route_request( - data=data, route_type="aembedding", + proxy_logging_obj=proxy_logging_obj, llm_router=llm_router, + general_settings=general_settings, + proxy_config=proxy_config, + select_data_generator=select_data_generator, + model=model, user_model=user_model, + user_temperature=user_temperature, + user_request_timeout=user_request_timeout, + user_max_tokens=user_max_tokens, + user_api_base=user_api_base, + version=version, ) - tasks.append(llm_call) - - # wait for call to end - llm_responses = asyncio.gather( - *tasks - ) # run the moderation check in parallel to the actual llm api call - - responses = await llm_responses - - response = responses[1] - - ### ALERTING ### - asyncio.create_task( - proxy_logging_obj.update_request_status( - litellm_call_id=data.get("litellm_call_id", ""), status="success" - ) - ) - - ### RESPONSE HEADERS ### - hidden_params = getattr(response, "_hidden_params", {}) or {} - model_id = hidden_params.get("model_id", None) or "" - cache_key = hidden_params.get("cache_key", None) or "" - api_base = hidden_params.get("api_base", None) or "" - response_cost = hidden_params.get("response_cost", None) or "" - litellm_call_id = hidden_params.get("litellm_call_id", None) or "" - additional_headers: dict = hidden_params.get("additional_headers", {}) or {} - - fastapi_response.headers.update( - ProxyBaseLLMRequestProcessing.get_custom_headers( - user_api_key_dict=user_api_key_dict, - model_id=model_id, - cache_key=cache_key, - api_base=api_base, - version=version, - response_cost=response_cost, - model_region=getattr(user_api_key_dict, "allowed_model_region", ""), - call_id=litellm_call_id, - request_data=data, - hidden_params=hidden_params, - **additional_headers, - ) - ) - await check_response_size_is_safe(response=response) - + return response except Exception as e: - await proxy_logging_obj.post_call_failure_hook( - user_api_key_dict=user_api_key_dict, original_exception=e, request_data=data + # Use unified error handler + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) + raise await base_llm_response_processor._handle_llm_api_exception( + e=e, + user_api_key_dict=user_api_key_dict, + proxy_logging_obj=proxy_logging_obj, + version=version, ) - litellm_debug_info = getattr(e, "litellm_debug_info", "") - verbose_proxy_logger.debug( - "\033[1;31mAn error occurred: %s %s\n\n Debug this by setting `--debug`, e.g. `litellm --model gpt-3.5-turbo --debug`", - e, - litellm_debug_info, - ) - verbose_proxy_logger.exception( - "litellm.proxy.proxy_server.embeddings(): Exception occured - {}".format( - str(e) - ) - ) - if isinstance(e, HTTPException): - message = get_error_message_str(e) - raise ProxyException( - message=message, - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), - ) - else: - error_msg = f"{str(e)}" - raise ProxyException( - message=getattr(e, "message", error_msg), - type=getattr(e, "type", "None"), - param=getattr(e, "param", "None"), - openai_code=getattr(e, "code", None), - code=getattr(e, "status_code", 500), - ) @router.post( @@ -10180,6 +10089,7 @@ app.include_router(openai_files_router) app.include_router(team_callback_router) app.include_router(budget_management_router) app.include_router(model_management_router) +app.include_router(model_access_group_management_router) app.include_router(tag_management_router) app.include_router(cost_tracking_settings_router) app.include_router(router_settings_router) diff --git a/litellm/proxy/public_endpoints/provider_create_metadata.py b/litellm/proxy/public_endpoints/provider_create_metadata.py new file mode 100644 index 0000000000..bfb2fb2fe0 --- /dev/null +++ b/litellm/proxy/public_endpoints/provider_create_metadata.py @@ -0,0 +1,769 @@ +from __future__ import annotations + +from typing import Any, Dict, List + +from litellm.types.proxy.public_endpoints.public_endpoints import ( + ProviderCreateInfo, + ProviderCredentialField, +) +from litellm.types.utils import LlmProviders + +DEFAULT_MODEL_PLACEHOLDER = "gpt-3.5-turbo" + +_FALLBACK_FIELDS: List[Dict[str, Any]] = [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "required": False, + }, + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": False, + }, +] + +PROVIDER_BASE_INFO: Dict[str, Dict[str, Any]] = { + "AIML": { + "provider_display_name": "AI/ML API", + "litellm_provider": "aiml", + "default_model_placeholder": "aiml/flux-pro/v1.1", + }, + "Anthropic": { + "provider_display_name": "Anthropic", + "litellm_provider": "anthropic", + "default_model_placeholder": "claude-3-opus", + }, + "AssemblyAI": { + "provider_display_name": "AssemblyAI", + "litellm_provider": "assemblyai", + }, + "Azure": { + "provider_display_name": "Azure", + "litellm_provider": "azure", + "default_model_placeholder": "azure/my-deployment", + }, + "Azure_AI_Studio": { + "provider_display_name": "Azure AI Foundry (Studio)", + "litellm_provider": "azure_ai", + "default_model_placeholder": "azure_ai/command-r-plus", + }, + "Bedrock": { + "provider_display_name": "Amazon Bedrock", + "litellm_provider": "bedrock", + "default_model_placeholder": "claude-3-opus", + }, + "Cerebras": { + "provider_display_name": "Cerebras", + "litellm_provider": "cerebras", + }, + "Cohere": { + "provider_display_name": "Cohere", + "litellm_provider": "cohere", + }, + "Dashscope": { + "provider_display_name": "Dashscope", + "litellm_provider": "dashscope", + }, + "Databricks": { + "provider_display_name": "Databricks (Qwen API)", + "litellm_provider": "databricks", + }, + "DeepInfra": { + "provider_display_name": "DeepInfra", + "litellm_provider": "deepinfra", + "default_model_placeholder": "deepinfra/", + }, + "Deepgram": { + "provider_display_name": "Deepgram", + "litellm_provider": "deepgram", + }, + "Deepseek": { + "provider_display_name": "Deepseek", + "litellm_provider": "deepseek", + }, + "ElevenLabs": { + "provider_display_name": "ElevenLabs", + "litellm_provider": "elevenlabs", + }, + "FalAI": { + "provider_display_name": "Fal AI", + "litellm_provider": "fal_ai", + "default_model_placeholder": "fal_ai/fal-ai/flux-pro/v1.1-ultra", + }, + "FireworksAI": { + "provider_display_name": "Fireworks AI", + "litellm_provider": "fireworks_ai", + }, + "Google_AI_Studio": { + "provider_display_name": "Google AI Studio", + "litellm_provider": "gemini", + "default_model_placeholder": "gemini-pro", + }, + "GradientAI": { + "provider_display_name": "GradientAI", + "litellm_provider": "gradient_ai", + }, + "Groq": { + "provider_display_name": "Groq", + "litellm_provider": "groq", + }, + "Hosted_Vllm": { + "provider_display_name": "vllm", + "litellm_provider": "hosted_vllm", + }, + "Infinity": { + "provider_display_name": "Infinity", + "litellm_provider": "infinity", + }, + "JinaAI": { + "provider_display_name": "Jina AI", + "litellm_provider": "jina_ai", + "default_model_placeholder": "jina_ai/", + }, + "MistralAI": { + "provider_display_name": "Mistral AI", + "litellm_provider": "mistral", + }, + "Ollama": { + "provider_display_name": "Ollama", + "litellm_provider": "ollama", + }, + "OpenAI": { + "provider_display_name": "OpenAI", + "litellm_provider": "openai", + }, + "OpenAI_Compatible": { + "provider_display_name": "OpenAI-Compatible Endpoints (Together AI, etc.)", + "litellm_provider": "openai", + }, + "OpenAI_Text": { + "provider_display_name": "OpenAI Text Completion", + "litellm_provider": "text-completion-openai", + }, + "OpenAI_Text_Compatible": { + "provider_display_name": "OpenAI-Compatible Text Completion Models (Together AI, etc.)", + "litellm_provider": "text-completion-openai", + }, + "Openrouter": { + "provider_display_name": "Openrouter", + "litellm_provider": "openrouter", + }, + "Oracle": { + "provider_display_name": "Oracle Cloud Infrastructure (OCI)", + "litellm_provider": "oci", + "default_model_placeholder": "oci/xai.grok-4", + }, + "Perplexity": { + "provider_display_name": "Perplexity", + "litellm_provider": "perplexity", + }, + "SageMaker": { + "provider_display_name": "AWS SageMaker", + "litellm_provider": "sagemaker_chat", + "default_model_placeholder": "sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b", + }, + "Sambanova": { + "provider_display_name": "Sambanova", + "litellm_provider": "sambanova", + }, + "Snowflake": { + "provider_display_name": "Snowflake", + "litellm_provider": "snowflake", + "default_model_placeholder": "snowflake/mistral-7b", + }, + "TogetherAI": { + "provider_display_name": "TogetherAI", + "litellm_provider": "together_ai", + }, + "Triton": { + "provider_display_name": "Triton", + "litellm_provider": "triton", + }, + "Vertex_AI": { + "provider_display_name": "Vertex AI (Anthropic, Gemini, etc.)", + "litellm_provider": "vertex_ai", + "default_model_placeholder": "gemini-pro", + }, + "VolcEngine": { + "provider_display_name": "VolcEngine", + "litellm_provider": "volcengine", + "default_model_placeholder": "volcengine/", + }, + "Voyage": { + "provider_display_name": "Voyage AI", + "litellm_provider": "voyage", + "default_model_placeholder": "voyage/", + }, + "xAI": { + "provider_display_name": "xAI", + "litellm_provider": "xai", + }, +} + +PROVIDER_CREDENTIAL_FIELDS: Dict[str, List[Dict[str, Any]]] = { + "OpenAI": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "placeholder": "https://api.openai.com/v1", + "tooltip": "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", + "default_value": "https://api.openai.com/v1", + }, + { + "key": "organization", + "label": "OpenAI Organization ID", + "placeholder": "[OPTIONAL] my-unique-org", + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "OpenAI_Text": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "placeholder": "https://api.openai.com/v1", + "tooltip": "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", + "default_value": "https://api.openai.com/v1", + }, + { + "key": "organization", + "label": "OpenAI Organization ID", + "placeholder": "[OPTIONAL] my-unique-org", + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Vertex_AI": [ + { + "key": "vertex_project", + "label": "Vertex Project", + "placeholder": "adroit-cadet-1234..", + "required": True, + }, + { + "key": "vertex_location", + "label": "Vertex Location", + "placeholder": "us-east-1", + "required": True, + }, + { + "key": "vertex_credentials", + "label": "Vertex Credentials", + "field_type": "upload", + "required": True, + }, + ], + "AssemblyAI": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "select", + "required": True, + "options": [ + "https://api.assemblyai.com", + "https://api.eu.assemblyai.com", + ], + }, + { + "key": "api_key", + "label": "AssemblyAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Azure": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_version", + "label": "API Version", + "placeholder": "2023-07-01-preview", + "tooltip": "By default litellm will use the latest version. If you want to use a different version, you can specify it here", + }, + { + "key": "base_model", + "label": "Base Model", + "placeholder": "azure/gpt-3.5-turbo", + }, + { + "key": "api_key", + "label": "Azure API Key", + "field_type": "password", + "placeholder": "Enter your Azure API Key", + }, + { + "key": "azure_ad_token", + "label": "Azure AD Token", + "field_type": "password", + "placeholder": "Enter your Azure AD Token", + }, + ], + "Azure_AI_Studio": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", + "tooltip": "Enter your full Target URI from Azure Foundry here. Example: https://litellm8397336933.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", + "required": True, + }, + { + "key": "api_key", + "label": "Azure API Key", + "field_type": "password", + "required": True, + }, + ], + "OpenAI_Compatible": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Dashscope": [ + { + "key": "api_key", + "label": "Dashscope API Key", + "field_type": "password", + "required": True, + }, + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "default_value": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "required": True, + "tooltip": "The base URL for your Dashscope server. Defaults to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 if not specified.", + }, + ], + "OpenAI_Text_Compatible": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Bedrock": [ + { + "key": "aws_access_key_id", + "label": "AWS Access Key ID", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_secret_access_key", + "label": "AWS Secret Access Key", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_session_token", + "label": "AWS Session Token", + "field_type": "password", + "tooltip": "Temporary credentials session token. You can provide the raw token or the environment variable (e.g. `os.environ/MY_SESSION_TOKEN`).", + }, + { + "key": "aws_region_name", + "label": "AWS Region Name", + "placeholder": "us-east-1", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_session_name", + "label": "AWS Session Name", + "placeholder": "my-session", + "tooltip": "Name for the AWS session. You can provide the raw value or the environment variable (e.g. `os.environ/MY_SESSION_NAME`).", + }, + { + "key": "aws_profile_name", + "label": "AWS Profile Name", + "placeholder": "default", + "tooltip": "AWS profile name to use for authentication. You can provide the raw value or the environment variable (e.g. `os.environ/MY_PROFILE_NAME`).", + }, + { + "key": "aws_role_name", + "label": "AWS Role Name", + "placeholder": "MyRole", + "tooltip": "AWS IAM role name to assume. You can provide the raw value or the environment variable (e.g. `os.environ/MY_ROLE_NAME`).", + }, + { + "key": "aws_web_identity_token", + "label": "AWS Web Identity Token", + "field_type": "password", + "tooltip": "Web identity token for OIDC authentication. You can provide the raw token or the environment variable (e.g. `os.environ/MY_WEB_IDENTITY_TOKEN`).", + }, + { + "key": "aws_bedrock_runtime_endpoint", + "label": "AWS Bedrock Runtime Endpoint", + "placeholder": "https://bedrock-runtime.us-east-1.amazonaws.com", + "tooltip": "Custom Bedrock runtime endpoint URL. You can provide the raw value or the environment variable (e.g. `os.environ/MY_BEDROCK_ENDPOINT`).", + }, + ], + "SageMaker": [ + { + "key": "aws_access_key_id", + "label": "AWS Access Key ID", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_secret_access_key", + "label": "AWS Secret Access Key", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_region_name", + "label": "AWS Region Name", + "placeholder": "us-east-1", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + ], + "Ollama": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:11434", + "default_value": "http://localhost:11434", + "tooltip": "The base URL for your Ollama server. Defaults to http://localhost:11434 if not specified.", + }, + ], + "Anthropic": [ + { + "key": "api_key", + "label": "API Key", + "placeholder": "sk-", + "field_type": "password", + "required": True, + }, + ], + "Deepgram": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "ElevenLabs": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Google_AI_Studio": [ + { + "key": "api_key", + "label": "API Key", + "placeholder": "aig-", + "field_type": "password", + "required": True, + }, + ], + "Groq": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "MistralAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Deepseek": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Cohere": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Databricks": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "xAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "AIML": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Cerebras": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Sambanova": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Perplexity": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "TogetherAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Openrouter": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "FireworksAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "GradientAI": [ + { + "key": "api_base", + "label": "GradientAI Endpoint", + "placeholder": "https://...", + }, + { + "key": "api_key", + "label": "GradientAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Triton": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + }, + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:8000/generate", + }, + ], + "Hosted_Vllm": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "vLLM API Key", + "field_type": "password", + }, + ], + "Voyage": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "JinaAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "VolcEngine": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "DeepInfra": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Oracle": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Snowflake": [ + { + "key": "api_key", + "label": "Snowflake API Key / JWT Key for Authentication", + "field_type": "password", + "required": True, + }, + { + "key": "api_base", + "label": "Snowflake API Endpoint", + "placeholder": "https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", + "tooltip": "Enter the full endpoint with path here. Example: https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", + "required": True, + }, + ], + "Infinity": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:7997", + }, + ], + "FalAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], +} + + +def _normalize_field(field: Dict[str, Any]) -> ProviderCredentialField: + return ProviderCredentialField( + key=field["key"], + label=field["label"], + placeholder=field.get("placeholder"), + tooltip=field.get("tooltip"), + required=field.get("required", False), + field_type=field.get("field_type", "text"), + options=field.get("options"), + default_value=field.get("default_value"), + ) + + +def get_provider_create_metadata() -> List[ProviderCreateInfo]: + providers: List[ProviderCreateInfo] = [] + + for provider_key, base_info in PROVIDER_BASE_INFO.items(): + raw_fields = PROVIDER_CREDENTIAL_FIELDS.get(provider_key, _FALLBACK_FIELDS) + normalized_fields = [_normalize_field(field) for field in raw_fields] + + providers.append( + ProviderCreateInfo( + provider=provider_key, + provider_display_name=base_info["provider_display_name"], + litellm_provider=base_info["litellm_provider"], + default_model_placeholder=base_info.get( + "default_model_placeholder", DEFAULT_MODEL_PLACEHOLDER + ), + credential_fields=normalized_fields, + ) + ) + + # Ensure we have metadata entries for all providers defined in LlmProviders. + # If a provider enum value is not already present in the litellm_provider + # field of any entry, create a default entry for it using the fallback + # credential fields (api_key + api_base) and a generated display name. + existing_litellm_providers = {p.litellm_provider for p in providers} + + for provider_enum in LlmProviders: + litellm_provider_value = provider_enum.value + if litellm_provider_value in existing_litellm_providers: + continue + + normalized_fields = [_normalize_field(field) for field in _FALLBACK_FIELDS] + provider_display_name = provider_enum.value.replace("_", " ").title() + + providers.append( + ProviderCreateInfo( + provider=provider_enum.name, + provider_display_name=provider_display_name, + litellm_provider=litellm_provider_value, + default_model_placeholder=DEFAULT_MODEL_PLACEHOLDER, + credential_fields=normalized_fields, + ) + ) + + providers.sort(key=lambda item: item.provider_display_name.lower()) + return providers + diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 2cc3dd0ed4..8c1e6b74b3 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -3,11 +3,17 @@ from typing import List from fastapi import APIRouter, Depends, HTTPException from litellm.proxy._types import CommonProxyErrors +from litellm.proxy.public_endpoints.provider_create_metadata import ( + get_provider_create_metadata, +) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.proxy.management_endpoints.model_management_endpoints import ( ModelGroupInfoProxy, ) -from litellm.types.proxy.public_endpoints.public_endpoints import PublicModelHubInfo +from litellm.types.proxy.public_endpoints.public_endpoints import ( + PublicModelHubInfo, + ProviderCreateInfo, +) from litellm.types.utils import LlmProviders router = APIRouter() @@ -74,3 +80,16 @@ async def get_supported_providers() -> List[str]: """ return sorted(provider.value for provider in LlmProviders) + + +@router.get( + "/public/providers/fields", + tags=["public", "providers"], + response_model=List[ProviderCreateInfo], +) +async def get_provider_fields() -> List[ProviderCreateInfo]: + """ + Return provider metadata required by the dashboard create-model flow. + """ + + return get_provider_create_metadata() diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index b60acd1428..a167f564fd 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -2968,10 +2968,30 @@ async def ui_view_session_spend_logs( session_id: str = fastapi.Query( description="Get all spend logs for a particular session", ), + page: int = fastapi.Query( + default=1, + ge=1, + description="Page number for pagination", + ), + page_size: int = fastapi.Query( + default=50, + ge=1, + le=100, + description="Number of items per page", + ), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ - Get all spend logs for a particular session + Get paginated spend logs for a particular session. + + Returns: + { + "data": List[LiteLLM_SpendLogs], + "total": int, + "page": int, + "page_size": int, + "total_pages": int, + } """ from litellm.proxy.proxy_server import prisma_client @@ -2984,11 +3004,32 @@ async def ui_view_session_spend_logs( # Build query conditions where_conditions = {"session_id": session_id} - # Query the database - result = await prisma_client.db.litellm_spendlogs.find_many( - where=where_conditions, order={"startTime": "asc"} + + # Calculate pagination offsets + skip = (page - 1) * page_size + + # Get total count for pagination metadata + total_records = await prisma_client.db.litellm_spendlogs.count( + where=where_conditions ) - return result + + # Query the database with pagination + result = await prisma_client.db.litellm_spendlogs.find_many( + where=where_conditions, + order={"startTime": "asc"}, + skip=skip, + take=page_size, + ) + + total_pages = (total_records + page_size - 1) // page_size + + return { + "data": result, + "total": total_records, + "page": page, + "page_size": page_size, + "total_pages": total_pages, + } except Exception as e: if isinstance(e, HTTPException): raise e diff --git a/litellm/responses/mcp/litellm_proxy_mcp_handler.py b/litellm/responses/mcp/litellm_proxy_mcp_handler.py index 7ad26e0a86..c7322cab09 100644 --- a/litellm/responses/mcp/litellm_proxy_mcp_handler.py +++ b/litellm/responses/mcp/litellm_proxy_mcp_handler.py @@ -526,8 +526,10 @@ class LiteLLM_Proxy_MCP_Handler: else: assistant_message_content.append(content) - # Add assistant message with content and function calls - if assistant_message_content or function_calls: + # Add assistant message only if there's actual content (not empty) + # For example, gemini requires that function call turns come immediately after user turns, + # so we should not add empty assistant messages + if assistant_message_content: follow_up_input.append( { "type": "message", @@ -536,9 +538,9 @@ class LiteLLM_Proxy_MCP_Handler: } ) - # Add function calls after assistant message - for function_call in function_calls: - follow_up_input.append(function_call) + # Add function calls (these can come directly after user message for LLM) + for function_call in function_calls: + follow_up_input.append(function_call) # Add tool results (function call outputs) for tool_result in tool_results: diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index bc752dd26a..330308e179 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -679,10 +679,11 @@ class BedrockInputDataConfig(TypedDict): s3InputDataConfig: BedrockS3InputDataConfig -class BedrockS3OutputDataConfig(TypedDict): +class BedrockS3OutputDataConfig(TypedDict, total=False): """S3 output data configuration for Bedrock batch jobs.""" s3Uri: str + s3EncryptionKeyId: Optional[str] class BedrockOutputDataConfig(TypedDict): diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 7dab61b151..fd2f9b9d9c 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -1475,7 +1475,7 @@ ResponsesAPIStreamingResponse = Annotated[ ] -REASONING_EFFORT = Literal["minimal", "low", "medium", "high"] +REASONING_EFFORT = Literal["none", "minimal", "low", "medium", "high"] class OpenAIRealtimeStreamSession(TypedDict, total=False): diff --git a/litellm/types/proxy/management_endpoints/model_management_endpoints.py b/litellm/types/proxy/management_endpoints/model_management_endpoints.py index 165562d32f..cb9dcc63e2 100644 --- a/litellm/types/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/types/proxy/management_endpoints/model_management_endpoints.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, List from pydantic import BaseModel, Field @@ -11,3 +11,34 @@ class ModelGroupInfoProxy(ModelGroupInfo): class UpdateUsefulLinksRequest(BaseModel): useful_links: Dict[str, str] + + +class NewModelGroupRequest(BaseModel): + access_group: str # The access group name (e.g., "production-models") + model_names: List[str] # Existing model groups to include (e.g., ["gpt-4", "claude-3"]) + + +class NewModelGroupResponse(BaseModel): + access_group: str + model_names: List[str] + models_updated: int # Number of models updated + + +class UpdateModelGroupRequest(BaseModel): + model_names: List[str] # Updated list of model groups to include + + +class DeleteModelGroupResponse(BaseModel): + access_group: str + models_updated: int # Number of deployments where the access group was removed + message: str + + +class AccessGroupInfo(BaseModel): + access_group: str + model_names: List[str] # List of model names in this access group + deployment_count: int # Total number of deployments with this access group + + +class ListAccessGroupsResponse(BaseModel): + access_groups: List[AccessGroupInfo] \ No newline at end of file diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index b2949a719e..7edf05dc94 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Dict, List, Literal, Optional from pydantic import BaseModel @@ -8,3 +8,22 @@ class PublicModelHubInfo(BaseModel): custom_docs_description: Optional[str] litellm_version: str useful_links: Optional[Dict[str, str]] + + +class ProviderCredentialField(BaseModel): + key: str + label: str + placeholder: Optional[str] = None + tooltip: Optional[str] = None + required: bool = False + field_type: Literal["text", "password", "select", "upload"] = "text" + options: Optional[List[str]] = None + default_value: Optional[str] = None + + +class ProviderCreateInfo(BaseModel): + provider: str + provider_display_name: str + litellm_provider: str + credential_fields: List[ProviderCredentialField] + default_model_placeholder: Optional[str] = None diff --git a/litellm/types/router.py b/litellm/types/router.py index 3801d5bb78..2bf126211c 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -205,6 +205,7 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): # Batch/File API Params s3_bucket_name: Optional[str] = None + s3_encryption_key_id: Optional[str] = None gcs_bucket_name: Optional[str] = None # Vector Store Params @@ -262,6 +263,7 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): auto_router_embedding_model: Optional[str] = None, # Batch/File API Params s3_bucket_name: Optional[str] = None, + s3_encryption_key_id: Optional[str] = None, gcs_bucket_name: Optional[str] = None, **params, ): diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index fa36e2d608..0fe71e4541 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -8523,6 +8523,14 @@ "/v1/images/generations" ] }, + "fal_ai/fal-ai/flux/schnell": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.003, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "fal_ai/fal-ai/imagen4/preview": { "litellm_provider": "fal_ai", "mode": "image_generation", @@ -8531,6 +8539,22 @@ "/v1/images/generations" ] }, + "fal_ai/fal-ai/imagen4/preview/fast": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.02, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/fal-ai/imagen4/preview/ultra": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.06, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "fal_ai/fal-ai/recraft/v3/text-to-image": { "litellm_provider": "fal_ai", "mode": "image_generation", @@ -23408,6 +23432,19 @@ "supports_function_calling": true, "supports_tool_choice": true }, + "vertex_ai/moonshotai/kimi-k2-thinking-maas": { + "input_cost_per_token": 6e-07, + "litellm_provider": "vertex_ai-moonshot_models", + "max_input_tokens": 256000, + "max_output_tokens": 256000, + "max_tokens": 256000, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#partner-models", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_web_search": true + }, "vertex_ai/mistral-medium-3": { "input_cost_per_token": 4e-07, "litellm_provider": "vertex_ai-mistral_models", @@ -23744,6 +23781,22 @@ "mode": "embedding", "output_cost_per_token": 0.0 }, + "voyage/voyage-3.5": { + "input_cost_per_token": 6e-08, + "litellm_provider": "voyage", + "max_input_tokens": 32000, + "max_tokens": 32000, + "mode": "embedding", + "output_cost_per_token": 0.0 + }, + "voyage/voyage-3.5-lite": { + "input_cost_per_token": 2e-08, + "litellm_provider": "voyage", + "max_input_tokens": 32000, + "max_tokens": 32000, + "mode": "embedding", + "output_cost_per_token": 0.0 + }, "voyage/voyage-code-2": { "input_cost_per_token": 1.2e-07, "litellm_provider": "voyage", @@ -24789,7 +24842,9 @@ "1280x720", "720x1280" ], - "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + "metadata": { + "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + } }, "runwayml/gen4_aleph": { "litellm_provider": "runwayml", @@ -24807,7 +24862,9 @@ "1280x720", "720x1280" ], - "comment": "15 credits per second @ $0.01 per credit = $0.15 per second" + "metadata": { + "comment": "15 credits per second @ $0.01 per credit = $0.15 per second" + } }, "runwayml/gen3a_turbo": { "litellm_provider": "runwayml", @@ -24825,7 +24882,9 @@ "1280x720", "720x1280" ], - "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + "metadata": { + "comment": "5 credits per second @ $0.01 per credit = $0.05 per second" + } }, "runwayml/gen4_image": { "litellm_provider": "runwayml", @@ -24844,7 +24903,9 @@ "1280x720", "1920x1080" ], - "comment": "5 credits per 720p image or 8 credits per 1080p image @ $0.01 per credit. Using 5 credits ($0.05) as base cost" + "metadata": { + "comment": "5 credits per 720p image or 8 credits per 1080p image @ $0.01 per credit. Using 5 credits ($0.05) as base cost" + } }, "runwayml/gen4_image_turbo": { "litellm_provider": "runwayml", @@ -24863,6 +24924,17 @@ "1280x720", "1920x1080" ], - "comment": "2 credits per image (any resolution) @ $0.01 per credit = $0.02 per image" + "metadata": { + "comment": "2 credits per image (any resolution) @ $0.01 per credit = $0.02 per image" + } + }, + "runwayml/eleven_multilingual_v2": { + "litellm_provider": "runwayml", + "mode": "audio_speech", + "input_cost_per_character": 3e-07, + "source": "https://docs.dev.runwayml.com/guides/pricing/", + "metadata": { + "comment": "Estimated cost based on standard TTS pricing. RunwayML uses ElevenLabs models." + } } } diff --git a/poetry.lock b/poetry.lock index d71712dd9f..6bc2bc376c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -17,6 +18,7 @@ version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, @@ -121,7 +123,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.12.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -129,6 +131,7 @@ version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, @@ -143,6 +146,8 @@ version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" optional = true python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, @@ -154,6 +159,8 @@ version = "1.17.1" description = "A database migration tool for SQLAlchemy." optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "alembic-1.17.1-py3-none-any.whl", hash = "sha256:cbc2386e60f89608bb63f30d2d6cc66c7aaed1fe105bd862828600e5ad167023"}, {file = "alembic-1.17.1.tar.gz", hash = "sha256:8a289f6778262df31571d29cca4c7fbacd2f0f582ea0816f4c399b6da7528486"}, @@ -174,10 +181,12 @@ version = "0.0.3" description = "Document parameters, class attributes, return types, and variables inline, with Annotated." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580"}, {file = "annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda"}, ] +markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\""} [[package]] name = "annotated-types" @@ -185,6 +194,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -199,6 +209,7 @@ version = "4.5.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, @@ -212,7 +223,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -221,6 +232,8 @@ version = "3.11.1" description = "In-process task scheduler with Cron-like capabilities" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "apscheduler-3.11.1-py3-none-any.whl", hash = "sha256:6162cb5683cb09923654fa9bdd3130c4be4bfda6ad8990971c9597ecd52965d2"}, {file = "apscheduler-3.11.1.tar.gz", hash = "sha256:0db77af6400c84d1747fe98a04b8b58f0080c77d11d338c4f507a9752880f221"}, @@ -238,7 +251,7 @@ mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "anyio (>=4.5.2)", "gevent ; python_version < \"3.14\"", "pytest", "pytz", "twisted ; python_version < \"3.14\""] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -247,8 +260,10 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "5.0.1" description = "Timeout context manager for asyncio programs" -optional = false +optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\" and (extra == \"extra-proxy\" or extra == \"proxy\") or python_version <= \"3.10\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -260,18 +275,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "azure-core" @@ -279,6 +295,7 @@ version = "1.33.0" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "azure_core-1.33.0-py3-none-any.whl", hash = "sha256:9b5b6d0223a1d38c37500e6971118c1e0f13f54951e6893968b38910bc9cda8f"}, {file = "azure_core-1.33.0.tar.gz", hash = "sha256:f367aa07b5e3005fec2c1e184b882b0b039910733907d001c20fb08ebb8c0eb9"}, @@ -299,6 +316,7 @@ version = "1.21.0" description = "Microsoft Azure Identity Library for Python" optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9"}, {file = "azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6"}, @@ -317,6 +335,8 @@ version = "4.9.0" description = "Microsoft Azure Key Vault Secrets Client Library for Python" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "azure_keyvault_secrets-4.9.0-py3-none-any.whl", hash = "sha256:33c7e2aca2cc2092cebc8c6e96eca36a5cc30c767e16ea429c5fa21270e9fba6"}, {file = "azure_keyvault_secrets-4.9.0.tar.gz", hash = "sha256:2a03bb2ffd9a0d6c8ad1c330d9d0310113985a9de06607ece378fd72a5889fe1"}, @@ -333,6 +353,8 @@ version = "12.26.0" description = "Microsoft Azure Blob Storage Client Library for Python" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe"}, {file = "azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f"}, @@ -353,6 +375,8 @@ version = "2.17.0" description = "Internationalization utilities" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, @@ -362,7 +386,7 @@ files = [ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] -dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "backoff" @@ -370,10 +394,12 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main", "dev"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +markers = {main = "python_version >= \"3.9\" and (extra == \"semantic-router\" or extra == \"proxy\") or extra == \"proxy\""} [[package]] name = "backports-zoneinfo" @@ -381,6 +407,8 @@ version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = true python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"proxy\" and python_version < \"3.9\"" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, @@ -409,6 +437,7 @@ version = "23.12.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, @@ -445,7 +474,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -455,6 +484,8 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -466,6 +497,8 @@ version = "1.36.0" description = "The AWS SDK for Python" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "boto3-1.36.0-py3-none-any.whl", hash = "sha256:d0ca7a58ce25701a52232cc8df9d87854824f1f2964b929305722ebc7959d5a9"}, {file = "boto3-1.36.0.tar.gz", hash = "sha256:159898f51c2997a12541c0e02d6e5a8fe2993ddb307b9478fd9a339f98b57e00"}, @@ -485,6 +518,8 @@ version = "1.36.26" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "botocore-1.36.26-py3-none-any.whl", hash = "sha256:4e3f19913887a58502e71ef8d696fe7eaa54de7813ff73390cd5883f837dfa6e"}, {file = "botocore-1.36.26.tar.gz", hash = "sha256:4a63bcef7ecf6146fd3a61dc4f9b33b7473b49bdaf1770e9aaca6eee0c9eab62"}, @@ -494,8 +529,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -507,6 +542,8 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\"" files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -518,6 +555,7 @@ version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, @@ -529,6 +567,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -598,16 +637,116 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or extra == \"proxy\") and python_version < \"3.14\"", dev = "python_version < \"3.14\" and platform_python_implementation != \"PyPy\"", proxy-dev = "python_version < \"3.14\" and platform_python_implementation != \"PyPy\""} [package.dependencies] pycparser = "*" +[[package]] +name = "cffi" +version = "2.0.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev", "proxy-dev"] +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, +] +markers = {main = "(platform_python_implementation != \"PyPy\" or extra == \"proxy\") and python_version >= \"3.14\"", dev = "python_version >= \"3.14\" and platform_python_implementation != \"PyPy\"", proxy-dev = "python_version >= \"3.14\" and platform_python_implementation != \"PyPy\""} + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + [[package]] name = "charset-normalizer" version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, @@ -730,6 +869,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -744,6 +884,8 @@ version = "3.1.2" description = "Pickler class to extend the standard pickle.Pickler functionality" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a"}, {file = "cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414"}, @@ -755,6 +897,8 @@ version = "4.57" description = "Python SDK for the Cohere API" optional = true python-versions = ">=3.8,<4.0" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"semantic-router\"" files = [ {file = "cohere-4.57-py3-none-any.whl", hash = "sha256:479bdea81ae119e53f671f1ae808fcff9df88211780525d7ef2f7b99dfb32e59"}, {file = "cohere-4.57.tar.gz", hash = "sha256:71ace0204a92d1a2a8d4b949b88b353b4f22fc645486851924284cc5a0eb700d"}, @@ -774,10 +918,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" and (extra == \"utils\" or extra == \"semantic-router\") and python_version >= \"3.9\" or sys_platform == \"win32\" and extra == \"utils\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", proxy-dev = "platform_system == \"Windows\""} [[package]] name = "coloredlogs" @@ -785,6 +931,8 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -802,6 +950,8 @@ version = "6.10.1" description = "Add colours to the output of Python's logging module." optional = true python-versions = ">=3.6" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"semantic-router\"" files = [ {file = "colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c"}, {file = "colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321"}, @@ -819,6 +969,8 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -895,6 +1047,7 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -944,6 +1097,8 @@ version = "0.12.1" description = "Composable style cycles" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -959,6 +1114,8 @@ version = "0.73.0" description = "Databricks SDK for Python (Beta)" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "databricks_sdk-0.73.0-py3-none-any.whl", hash = "sha256:a4d3cfd19357a2b459d2dc3101454d7f0d1b62865ce099c35d0c342b66ac64ff"}, {file = "databricks_sdk-0.73.0.tar.gz", hash = "sha256:db09eaaacd98e07dded78d3e7ab47d2f6c886e0380cb577977bd442bace8bd8d"}, @@ -970,9 +1127,9 @@ protobuf = ">=4.25.8,<5.26.dev0 || >5.29.0,<5.29.1 || >5.29.1,<5.29.2 || >5.29.2 requests = ">=2.28.1,<3" [package.extras] -dev = ["autoflake", "black", "build", "databricks-connect", "httpx", "ipython", "ipywidgets", "isort", "langchain-openai", "openai", "pycodestyle", "pyfakefs", "pytest", "pytest-cov", "pytest-mock", "pytest-rerunfailures", "pytest-xdist (>=3.6.1,<4.0)", "requests-mock", "wheel"] +dev = ["autoflake", "black", "build", "databricks-connect", "httpx", "ipython", "ipywidgets", "isort", "langchain-openai ; python_version > \"3.7\"", "openai", "pycodestyle", "pyfakefs", "pytest", "pytest-cov", "pytest-mock", "pytest-rerunfailures", "pytest-xdist (>=3.6.1,<4.0)", "requests-mock", "wheel"] notebook = ["ipython (>=8,<10)", "ipywidgets (>=8,<9)"] -openai = ["httpx", "langchain-openai", "openai"] +openai = ["httpx", "langchain-openai ; python_version > \"3.7\"", "openai"] [[package]] name = "deprecated" @@ -980,16 +1137,18 @@ version = "1.3.1" 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 = ["main", "dev", "proxy-dev"] files = [ {file = "deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f"}, {file = "deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223"}, ] +markers = {main = "python_version >= \"3.10\""} [package.dependencies] wrapt = ">=1.10,<3" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] [[package]] name = "diskcache" @@ -997,6 +1156,8 @@ version = "5.6.3" description = "Disk Cache -- Disk and file backed persistent cache." optional = true python-versions = ">=3" +groups = ["main"] +markers = "extra == \"caching\"" files = [ {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, @@ -1008,6 +1169,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -1019,6 +1181,8 @@ version = "2.6.1" description = "DNS toolkit" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -1039,6 +1203,8 @@ version = "7.1.0" description = "A Python library for the Docker Engine API." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, @@ -1061,6 +1227,8 @@ version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, @@ -1072,6 +1240,8 @@ version = "2.3.0" description = "A robust email address syntax and deliverability validation library." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"}, {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"}, @@ -1087,6 +1257,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -1104,10 +1276,12 @@ version = "0.121.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "fastapi-0.121.0-py3-none-any.whl", hash = "sha256:8bdf1b15a55f4e4b0d6201033da9109ea15632cb76cf156e7b8b4019f2172106"}, {file = "fastapi-0.121.0.tar.gz", hash = "sha256:06663356a0b1ee93e875bbf05a31fb22314f5bed455afaaad2b2dad7f26e98fa"}, ] +markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\""} [package.dependencies] annotated-doc = ">=0.0.2" @@ -1126,6 +1300,7 @@ version = "1.7.5" description = "FastAPI without reliance on CDNs for docs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "fastapi_offline-1.7.5-py3-none-any.whl", hash = "sha256:00369632d604e8156b9ca9ab9c65e58ad8beff83d1ffc7bdbcec4a86173d51b4"}, {file = "fastapi_offline-1.7.5.tar.gz", hash = "sha256:07a58cb8d8fab68ba625698414b4cac833bb2d94d82dc0fbc2a8519bee7af87d"}, @@ -1143,6 +1318,8 @@ version = "0.16.0" description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" optional = true python-versions = "<4.0,>=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "fastapi_sso-0.16.0-py3-none-any.whl", hash = "sha256:3a66a942474ef9756d3a9d8b945d55bd9faf99781facdb9b87a40b73d6d6b0c3"}, {file = "fastapi_sso-0.16.0.tar.gz", hash = "sha256:f3941f986347566b7d3747c710cf474a907f581bfb6697ff3bb3e44eb76b438c"}, @@ -1161,6 +1338,8 @@ version = "1.12.1" description = "Fast read/write of AVRO files" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"semantic-router\"" files = [ {file = "fastavro-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:00650ca533907361edda22e6ffe8cf87ab2091c5d8aee5c8000b0f2dcdda7ed3"}, {file = "fastavro-1.12.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac76d6d95f909c72ee70d314b460b7e711d928845771531d823eb96a10952d26"}, @@ -1222,6 +1401,7 @@ version = "0.14.0" description = "Python bindings to Rust's UUID library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a"}, {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00"}, @@ -1309,6 +1489,7 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -1317,7 +1498,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "flake8" @@ -1325,6 +1506,7 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -1341,6 +1523,8 @@ version = "3.1.2" description = "A simple framework for building complex web applications." optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c"}, {file = "flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87"}, @@ -1364,6 +1548,8 @@ version = "6.0.1" description = "A Flask extension simplifying CORS support" optional = true python-versions = "<4.0,>=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "flask_cors-6.0.1-py3-none-any.whl", hash = "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c"}, {file = "flask_cors-6.0.1.tar.gz", hash = "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db"}, @@ -1379,6 +1565,8 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -1441,17 +1629,17 @@ files = [ ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" @@ -1459,6 +1647,7 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -1560,6 +1749,7 @@ version = "2025.3.0" description = "File-system specification" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"}, {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"}, @@ -1599,6 +1789,8 @@ version = "4.0.12" description = "Git Object Database" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, @@ -1613,6 +1805,8 @@ version = "3.1.45" description = "GitPython is a Python library used to interact with Git repositories" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, @@ -1623,7 +1817,7 @@ gitdb = ">=4.0.1,<5" [package.extras] doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "google-api-core" @@ -1631,6 +1825,8 @@ version = "2.25.2" description = "Google API client core library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.14\" and extra == \"extra-proxy\"" files = [ {file = "google_api_core-2.25.2-py3-none-any.whl", hash = "sha256:e9a8f62d363dc8424a8497f4c2a47d6bcda6c16514c935629c257ab5d10210e7"}, {file = "google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300"}, @@ -1647,7 +1843,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] @@ -1657,6 +1853,8 @@ version = "2.28.1" description = "Google API client core library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.14\" and extra == \"extra-proxy\"" files = [ {file = "google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c"}, {file = "google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8"}, @@ -1666,15 +1864,15 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ + {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, - {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\" and python_version < \"3.14\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, - {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\" and python_version < \"3.14\""}, + {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, ] proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.22.3,<2.0.0"}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" @@ -1682,7 +1880,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0)", "grpcio (>=1.75.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)", "grpcio-status (>=1.75.1,<2.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] @@ -1692,6 +1890,8 @@ version = "2.43.0" description = "Google Authentication Library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\"" files = [ {file = "google_auth-2.43.0-py2.py3-none-any.whl", hash = "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16"}, {file = "google_auth-2.43.0.tar.gz", hash = "sha256:88228eee5fc21b62a1b5fe773ca15e67778cb07dc8363adcb4a8827b52d81483"}, @@ -1705,37 +1905,21 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] urllib3 = ["packaging", "urllib3"] -[[package]] -name = "google-cloud-iam" -version = "2.19.1" -description = "Google Cloud Iam API client library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google_cloud_iam-2.19.1-py3-none-any.whl", hash = "sha256:11b08b86d82510021f9dd9f0beb5a08219e070deab09e28d4c0ce49f8c70997d"}, - {file = "google_cloud_iam-2.19.1.tar.gz", hash = "sha256:f059c369ad98af6be3401f0f5d087775d775fb96833be1e9ab8048c422fb1bf4"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0" -proto-plus = {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""} -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - [[package]] name = "google-cloud-iam" version = "2.20.0" description = "Google Cloud Iam API client library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "google_cloud_iam-2.20.0-py3-none-any.whl", hash = "sha256:643fcf6db3100772f222c7173bc1af15541a05ec1c43785191e835146ed150b8"}, {file = "google_cloud_iam-2.20.0.tar.gz", hash = "sha256:06568ed8313f59fac46d21a5aae4c54eb1dda9f6bcecf2736c58ab1065dc9173"}, @@ -1745,9 +1929,12 @@ files = [ google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0", extras = ["grpc"]} google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" grpc-google-iam-v1 = ">=0.12.4,<1.0.0" -grpcio = {version = ">=1.33.2,<2.0.0", markers = "python_version < \"3.14\""} +grpcio = [ + {version = ">=1.33.2,<2.0.0"}, + {version = ">=1.75.1,<2.0.0", markers = "python_version >= \"3.14\""}, +] proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.22.3,<2.0.0"}, {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, ] protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" @@ -1758,6 +1945,8 @@ version = "2.24.2" description = "Google Cloud Kms API client library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "google_cloud_kms-2.24.2-py2.py3-none-any.whl", hash = "sha256:368209b035dfac691a467c1cf50986d8b1b26cac1166bdfbaa25d738df91ff7b"}, {file = "google_cloud_kms-2.24.2.tar.gz", hash = "sha256:e9e18bbfafd1a4035c76c03fb5ff03f4f57f596d08e1a9ede7e69ec0151b27a1"}, @@ -1776,10 +1965,12 @@ version = "1.72.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, ] +markers = {main = "extra == \"extra-proxy\""} [package.dependencies] grpcio = {version = ">=1.44.0,<2.0.0", optional = true, markers = "extra == \"grpc\""} @@ -1794,6 +1985,8 @@ version = "3.4.3" description = "GraphQL Framework for Python" optional = true python-versions = "*" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "graphene-3.4.3-py2.py3-none-any.whl", hash = "sha256:820db6289754c181007a150db1f7fff544b94142b556d12e3ebc777a7bf36c71"}, {file = "graphene-3.4.3.tar.gz", hash = "sha256:2a3786948ce75fe7e078443d37f609cbe5bb36ad8d6b828740ad3b95ed1a0aaa"}, @@ -1815,6 +2008,8 @@ version = "3.2.7" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." optional = true python-versions = "<4,>=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0"}, {file = "graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c"}, @@ -1826,6 +2021,8 @@ version = "3.2.0" description = "Relay library for graphql-core" optional = true python-versions = ">=3.6,<4" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "graphql-relay-3.2.0.tar.gz", hash = "sha256:1ff1c51298356e481a0be009ccdff249832ce53f30559c1338f22a0e0d17250c"}, {file = "graphql_relay-3.2.0-py3-none-any.whl", hash = "sha256:c9b22bd28b170ba1fe674c74384a8ff30a76c8e26f88ac3aa1584dd3179953e5"}, @@ -1840,6 +2037,8 @@ version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"mlflow\"" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -1849,6 +2048,8 @@ files = [ {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8"}, {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, @@ -1858,6 +2059,8 @@ files = [ {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5"}, {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, @@ -1867,6 +2070,8 @@ files = [ {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d"}, {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, @@ -1876,6 +2081,8 @@ files = [ {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929"}, {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, @@ -1883,6 +2090,8 @@ files = [ {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269"}, + {file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681"}, {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, @@ -1892,6 +2101,8 @@ files = [ {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be"}, {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, @@ -1907,6 +2118,8 @@ version = "0.14.3" description = "IAM API client library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6"}, {file = "grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389"}, @@ -1923,6 +2136,7 @@ version = "1.70.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" +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"}, @@ -1980,16 +2194,97 @@ 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.14\" and extra == \"extra-proxy\"", dev = "python_version < \"3.14\"", proxy-dev = "python_version < \"3.14\""} [package.extras] protobuf = ["grpcio-tools (>=1.70.0)"] +[[package]] +name = "grpcio" +version = "1.76.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev", "proxy-dev"] +files = [ + {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, + {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3"}, + {file = "grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b"}, + {file = "grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b"}, + {file = "grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a"}, + {file = "grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00"}, + {file = "grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054"}, + {file = "grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d"}, + {file = "grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8"}, + {file = "grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882"}, + {file = "grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958"}, + {file = "grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347"}, + {file = "grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2"}, + {file = "grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42"}, + {file = "grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f"}, + {file = "grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8"}, + {file = "grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62"}, + {file = "grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc"}, + {file = "grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e"}, + {file = "grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e"}, + {file = "grpcio-1.76.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783"}, + {file = "grpcio-1.76.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886"}, + {file = "grpcio-1.76.0-cp39-cp39-win32.whl", hash = "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f"}, + {file = "grpcio-1.76.0-cp39-cp39-win_amd64.whl", hash = "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a"}, + {file = "grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73"}, +] +markers = {main = "python_version >= \"3.14\" and extra == \"extra-proxy\"", dev = "python_version >= \"3.14\"", proxy-dev = "python_version >= \"3.14\""} + +[package.dependencies] +typing-extensions = ">=4.12,<5.0" + +[package.extras] +protobuf = ["grpcio-tools (>=1.76.0)"] + [[package]] name = "grpcio-status" version = "1.62.3" description = "Status proto mapping for gRPC" optional = true python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, @@ -2006,6 +2301,8 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "platform_system != \"Windows\" and (extra == \"mlflow\" or extra == \"proxy\") and python_version >= \"3.10\" or extra == \"proxy\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -2027,6 +2324,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -2038,6 +2336,7 @@ version = "4.1.0" description = "HTTP/2 State-Machine based protocol implementation" optional = false python-versions = ">=3.6.1" +groups = ["proxy-dev"] files = [ {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, @@ -2053,6 +2352,8 @@ version = "1.2.0" description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" files = [ {file = "hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649"}, {file = "hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813"}, @@ -2087,6 +2388,7 @@ version = "4.0.0" description = "Pure-Python HPACK header compression" optional = false python-versions = ">=3.6.1" +groups = ["proxy-dev"] files = [ {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, @@ -2098,6 +2400,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -2119,6 +2422,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -2131,7 +2435,7 @@ httpcore = "==1.*" idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -2143,6 +2447,8 @@ version = "0.4.3" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -2154,6 +2460,8 @@ version = "2.5.4" description = "huey, a little task queue" optional = true python-versions = "*" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "huey-2.5.4-py3-none-any.whl", hash = "sha256:0eac1fb2711f6366a1db003629354a0cea470a3db720d5bab0d140c28e993f9c"}, {file = "huey-2.5.4.tar.gz", hash = "sha256:4b7fb217b640fbb46efc4f4681b446b40726593522f093e8ef27c4a8fcb6cfbb"}, @@ -2169,6 +2477,7 @@ version = "0.36.0" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d"}, {file = "huggingface_hub-0.36.0.tar.gz", hash = "sha256:47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25"}, @@ -2185,16 +2494,16 @@ tqdm = ">=4.42.1" typing-extensions = ">=3.7.4.3" [package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] hf-transfer = ["hf-transfer (>=0.1.4)"] hf-xet = ["hf-xet (>=1.1.2,<2.0.0)"] inference = ["aiohttp"] mcp = ["aiohttp", "mcp (>=1.8.0)", "typer"] oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] -quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "ruff (>=0.9.0)", "ty"] +quality = ["libcst (>=1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "ruff (>=0.9.0)", "ty"] tensorflow = ["graphviz", "pydot", "tensorflow"] tensorflow-testing = ["keras (<3.0)", "tensorflow"] testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] @@ -2207,6 +2516,8 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -2221,6 +2532,7 @@ version = "0.15.0" description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn" optional = false python-versions = ">=3.7" +groups = ["proxy-dev"] files = [ {file = "hypercorn-0.15.0-py3-none-any.whl", hash = "sha256:5008944999612fd188d7a1ca02e89d20065642b89503020ac392dfed11840730"}, {file = "hypercorn-0.15.0.tar.gz", hash = "sha256:d517f68d5dc7afa9a9d50ecefb0f769f466ebe8c1c18d2c2f447a24e763c9a63"}, @@ -2238,7 +2550,7 @@ wsproto = ">=0.14.0" docs = ["pydata_sphinx_theme", "sphinxcontrib_mermaid"] h3 = ["aioquic (>=0.9.0,<1.0)"] trio = ["exceptiongroup (>=1.1.0)", "trio (>=0.22.0)"] -uvloop = ["uvloop"] +uvloop = ["uvloop ; platform_system != \"Windows\""] [[package]] name = "hyperframe" @@ -2246,6 +2558,7 @@ version = "6.0.1" description = "HTTP/2 framing layer for Python" optional = false python-versions = ">=3.6.1" +groups = ["proxy-dev"] files = [ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, @@ -2257,6 +2570,7 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -2271,6 +2585,8 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -2282,6 +2598,7 @@ version = "6.11.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, @@ -2293,7 +2610,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "importlib-resources" @@ -2301,6 +2618,8 @@ version = "6.4.5" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\"" files = [ {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, @@ -2310,7 +2629,7 @@ files = [ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +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)"] @@ -2323,6 +2642,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -2334,6 +2654,8 @@ version = "0.7.2" description = "An ISO 8601 date/time/duration parser and formatter" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\" or extra == \"proxy\"" files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -2345,6 +2667,8 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -2356,6 +2680,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "proxy-dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2373,6 +2698,7 @@ version = "0.9.1" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jiter-0.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c0163baa7ee85860fdc14cc39263014500df901eeffdf94c1eab9a2d713b2a9d"}, {file = "jiter-0.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:514d4dd845e0af4da15112502e6fcb952f0721f27f17e530454e379472b90c14"}, @@ -2458,6 +2784,8 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -2469,6 +2797,8 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -2480,6 +2810,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -2503,6 +2834,7 @@ version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, @@ -2518,6 +2850,8 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -2628,6 +2962,7 @@ version = "2.54.1" description = "A client library for accessing langfuse" optional = false python-versions = "<4.0,>=3.8.1" +groups = ["dev"] files = [ {file = "langfuse-2.54.1-py3-none-any.whl", hash = "sha256:1f1261cf763886758c70e192133340ff296169cc0930cde725eee52d467eb661"}, {file = "langfuse-2.54.1.tar.gz", hash = "sha256:7efc70799740ffa0ac7e04066e0596fb6433e8e501fc850c6a4e7967de6de8a7"}, @@ -2653,6 +2988,8 @@ version = "0.1.20" description = "Package for LiteLLM Enterprise features" 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_enterprise-0.1.20-py3-none-any.whl", hash = "sha256:744a79956a8cd7748ef4c3f40d5a564c61519834e706beafbc0b931162773ae8"}, {file = "litellm_enterprise-0.1.20.tar.gz", hash = "sha256:f6b8dd75b53bd835c68caf6402a8bae744a150db7bb6b0e617178c6056ac6c01"}, @@ -2660,13 +2997,15 @@ files = [ [[package]] name = "litellm-proxy-extras" -version = "0.4.3" +version = "0.4.4" 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.4.3-py3-none-any.whl", hash = "sha256:e7ab09aa78d04d02dc48975620defa36784e1a0baa6d04a078b98b5717fcae24"}, - {file = "litellm_proxy_extras-0.4.3.tar.gz", hash = "sha256:420400d0db186319695526f6765d3d481206fe025b70bc74a1ce895a7d720bee"}, + {file = "litellm_proxy_extras-0.4.4-py3-none-any.whl", hash = "sha256:73584acaf77de9be448a7ace38dcec92ea2da7dd9e4bb37f455e5c5fdf8eb1ab"}, + {file = "litellm_proxy_extras-0.4.4.tar.gz", hash = "sha256:2c1b02d18ddf93a1b9f3a3c13d30952925ee319bfba59c8c2ca12f9b78bf93f5"}, ] [[package]] @@ -2675,6 +3014,8 @@ version = "1.3.10" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, @@ -2694,6 +3035,8 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2718,6 +3061,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main", "proxy-dev"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -2787,6 +3131,8 @@ version = "3.10.7" description = "Python plotting package" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, @@ -2865,6 +3211,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -2876,6 +3223,8 @@ version = "1.12.4" description = "Model Context Protocol SDK" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789"}, {file = "mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5"}, @@ -2905,6 +3254,8 @@ version = "0.1.2" description = "Markdown URL utilities" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -2916,6 +3267,8 @@ version = "0.4.1" description = "" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5"}, {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24"}, @@ -2938,10 +3291,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">1.20", markers = "python_version < \"3.10\""}, + {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, + {version = ">1.20"}, + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] [package.extras] @@ -2953,6 +3306,8 @@ version = "3.6.0" description = "MLflow is an open source platform for the complete machine learning lifecycle" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "mlflow-3.6.0-py3-none-any.whl", hash = "sha256:04d1691facd412be8e61b963fad859286cfeb2dbcafaea294e6aa0b83a15fc04"}, {file = "mlflow-3.6.0.tar.gz", hash = "sha256:d945d259b5c6b551a9f26846db8979fd84c78114a027b77ada3298f821a9b0e1"}, @@ -2997,6 +3352,8 @@ version = "3.6.0" description = "MLflow is an open source platform for the complete machine learning lifecycle" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "mlflow_skinny-3.6.0-py3-none-any.whl", hash = "sha256:c83b34fce592acb2cc6bddcb507587a6d9ef3f590d9e7a8658c85e0980596d78"}, {file = "mlflow_skinny-3.6.0.tar.gz", hash = "sha256:cc04706b5b6faace9faf95302a6e04119485e1bfe98ddc9b85b81984e80944b6"}, @@ -3042,6 +3399,8 @@ version = "3.6.0" description = "MLflow Tracing SDK is an open-source, lightweight Python package that only includes the minimum set of dependencies and functionality to instrument your code/models/agents with MLflow Tracing." optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "mlflow_tracing-3.6.0-py3-none-any.whl", hash = "sha256:a68ff03ba5129c67dc98e6871e0d5ef512dd3ee66d01e1c1a0c946c08a6d4755"}, {file = "mlflow_tracing-3.6.0.tar.gz", hash = "sha256:ccff80b3aad6caa18233c98ba69922a91a6f914e0a13d12e1977af7523523d4c"}, @@ -3063,6 +3422,7 @@ version = "1.34.0" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1"}, {file = "msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f"}, @@ -3074,7 +3434,7 @@ PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" [package.extras] -broker = ["pymsalruntime (>=0.14,<0.19)", "pymsalruntime (>=0.17,<0.19)", "pymsalruntime (>=0.18,<0.19)"] +broker = ["pymsalruntime (>=0.14,<0.19) ; python_version >= \"3.6\" and platform_system == \"Windows\"", "pymsalruntime (>=0.17,<0.19) ; python_version >= \"3.8\" and platform_system == \"Darwin\"", "pymsalruntime (>=0.18,<0.19) ; python_version >= \"3.8\" and platform_system == \"Linux\""] [[package]] name = "msal-extensions" @@ -3082,6 +3442,7 @@ version = "1.3.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false python-versions = ">=3.7" +groups = ["main", "proxy-dev"] files = [ {file = "msal_extensions-1.3.0-py3-none-any.whl", hash = "sha256:105328ddcbdd342016c9949d8f89e3917554740c8ab26669c0fa0e069e730a0e"}, {file = "msal_extensions-1.3.0.tar.gz", hash = "sha256:96918996642b38c78cd59b55efa0f06fd1373c90e0949be8615697c048fba62c"}, @@ -3099,6 +3460,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -3203,6 +3565,7 @@ version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -3262,6 +3625,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -3273,6 +3637,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "proxy-dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -3284,6 +3649,8 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and (python_version >= \"3.10\" or extra == \"extra-proxy\" or extra == \"semantic-router\") and (python_version < \"3.12\" or extra == \"semantic-router\" or extra == \"mlflow\" or extra == \"extra-proxy\") and (python_version < \"3.14\" or extra == \"semantic-router\" or extra == \"mlflow\") and (extra == \"extra-proxy\" or extra == \"semantic-router\" or extra == \"mlflow\")" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -3329,6 +3696,8 @@ version = "1.7.0" description = "Sphinx extension to support docstrings in Numpy format" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "numpydoc-1.7.0-py3-none-any.whl", hash = "sha256:5a56419d931310d79a06cfc2a126d1558700feeb9b4f3d8dcae1a8134be829c9"}, {file = "numpydoc-1.7.0.tar.gz", hash = "sha256:866e5ae5b6509dcf873fc6381120f5c31acf13b135636c1a81d68c166a95f921"}, @@ -3340,7 +3709,7 @@ tabulate = ">=0.8.10" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] -developer = ["pre-commit (>=3.3)", "tomli"] +developer = ["pre-commit (>=3.3)", "tomli ; python_version < \"3.11\""] doc = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pydata-sphinx-theme (>=0.13.3)", "sphinx (>=7)"] test = ["matplotlib", "pytest", "pytest-cov"] @@ -3350,6 +3719,8 @@ version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, @@ -3366,6 +3737,7 @@ version = "1.109.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315"}, {file = "openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869"}, @@ -3393,10 +3765,12 @@ version = "1.25.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" +groups = ["main", "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"}, ] +markers = {main = "python_version >= \"3.10\""} [package.dependencies] deprecated = ">=1.2.6" @@ -3408,6 +3782,7 @@ 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"}, @@ -3423,6 +3798,7 @@ 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"}, @@ -3437,6 +3813,7 @@ 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"}, @@ -3457,6 +3834,7 @@ 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"}, @@ -3477,10 +3855,12 @@ version = "1.25.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" +groups = ["main", "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"}, ] +markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} [package.dependencies] protobuf = ">=3.19,<5.0" @@ -3491,10 +3871,12 @@ version = "1.25.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" +groups = ["main", "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"}, ] +markers = {main = "python_version >= \"3.10\""} [package.dependencies] opentelemetry-api = "1.25.0" @@ -3507,10 +3889,12 @@ version = "0.46b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" +groups = ["main", "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"}, ] +markers = {main = "python_version >= \"3.10\""} [package.dependencies] opentelemetry-api = "1.25.0" @@ -3521,6 +3905,8 @@ version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, @@ -3609,6 +3995,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -3620,6 +4007,8 @@ version = "2.3.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, @@ -3680,9 +4069,9 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -3719,6 +4108,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -3730,6 +4120,8 @@ version = "12.0.0" description = "Python Imaging Library (fork)" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, @@ -3838,6 +4230,8 @@ version = "1.3.10" description = "Resolve a name to an object." optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version < \"3.9\"" files = [ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, @@ -3849,6 +4243,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -3865,6 +4260,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -3880,6 +4276,8 @@ version = "1.35.1" description = "Blazingly fast DataFrame library" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "polars-1.35.1-py3-none-any.whl", hash = "sha256:c29a933f28aa330d96a633adbd79aa5e6a6247a802a720eead9933f4613bdbf4"}, {file = "polars-1.35.1.tar.gz", hash = "sha256:06548e6d554580151d6ca7452d74bceeec4640b5b9261836889b8e68cfd7a62e"}, @@ -3913,7 +4311,7 @@ rt64 = ["polars-runtime-64 (==1.35.1)"] rtcompat = ["polars-runtime-compat (==1.35.1)"] sqlalchemy = ["polars[pandas]", "sqlalchemy"] style = ["great-tables (>=0.8.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; platform_system == \"Windows\""] xlsx2csv = ["xlsx2csv (>=0.8.0)"] xlsxwriter = ["xlsxwriter"] @@ -3923,6 +4321,8 @@ version = "1.35.1" description = "Blazingly fast DataFrame library" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "polars_runtime_32-1.35.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f051a42f6ae2f26e3bc2cf1f170f2120602976e2a3ffb6cfba742eecc7cc620"}, {file = "polars_runtime_32-1.35.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c2232f9cf05ba59efc72d940b86c033d41fd2d70bf2742e8115ed7112a766aa9"}, @@ -3939,6 +4339,7 @@ version = "2.0.0" description = "A pure-Python implementation of the HTTP/2 priority tree" optional = false python-versions = ">=3.6.1" +groups = ["proxy-dev"] files = [ {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, @@ -3950,6 +4351,7 @@ version = "0.11.0" description = "Prisma Client Python is an auto-generated and fully type-safe database client" optional = false python-versions = ">=3.7.0" +groups = ["main", "proxy-dev"] files = [ {file = "prisma-0.11.0-py3-none-any.whl", hash = "sha256:22bb869e59a2968b99f3483bb417717273ffbc569fd1e9ceed95e5614cbaf53a"}, {file = "prisma-0.11.0.tar.gz", hash = "sha256:3f2f2fd2361e1ec5ff655f2a04c7860c2f2a5bc4c91f78ca9c5c6349735bf693"}, @@ -3975,6 +4377,7 @@ version = "0.20.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" +groups = ["proxy-dev"] files = [ {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, @@ -3989,6 +4392,7 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -4096,6 +4500,8 @@ version = "1.26.1" description = "Beautiful, Pythonic protocol buffers" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -4113,6 +4519,7 @@ version = "4.25.8" description = "" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0"}, {file = "protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9"}, @@ -4126,6 +4533,7 @@ files = [ {file = "protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59"}, {file = "protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd"}, ] +markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\""} [[package]] name = "pyarrow" @@ -4133,6 +4541,8 @@ version = "22.0.0" description = "Python library for Apache Arrow" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88"}, {file = "pyarrow-22.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44d2d26cda26d18f7af7db71453b7b783788322d756e81730acb98f24eb90ace"}, @@ -4192,6 +4602,8 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\"" files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -4203,6 +4615,8 @@ version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\"" files = [ {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, @@ -4217,6 +4631,7 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -4228,10 +4643,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or extra == \"proxy\") and (python_version < \"3.14\" or implementation_name != \"PyPy\")", dev = "platform_python_implementation != \"PyPy\" and (python_version < \"3.14\" or implementation_name != \"PyPy\")", proxy-dev = "platform_python_implementation != \"PyPy\" and (python_version < \"3.14\" or implementation_name != \"PyPy\")"} [[package]] name = "pydantic" @@ -4239,6 +4656,7 @@ version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -4252,7 +4670,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -4260,6 +4678,7 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -4372,6 +4791,8 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -4395,6 +4816,7 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -4406,6 +4828,8 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\" or extra == \"proxy\"" files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -4420,6 +4844,7 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -4434,38 +4859,14 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = true -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - [[package]] name = "pynacl" version = "1.6.0" description = "Python binding to the Networking and Cryptography (NaCl) library" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "pynacl-1.6.0-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:f46386c24a65383a9081d68e9c2de909b1834ec74ff3013271f1bca9c2d233eb"}, {file = "pynacl-1.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dea103a1afcbc333bc0e992e64233d360d393d1e63d0bc88554f572365664348"}, @@ -4497,7 +4898,10 @@ files = [ ] [package.dependencies] -cffi = {version = ">=1.4.1", markers = "platform_python_implementation != \"PyPy\" and python_version < \"3.14\""} +cffi = [ + {version = ">=1.4.1", markers = "platform_python_implementation != \"PyPy\" and python_version < \"3.14\""}, + {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""}, +] [package.extras] docs = ["sphinx (<7)", "sphinx_rtd_theme"] @@ -4509,6 +4913,8 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -4523,6 +4929,8 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.9\" and sys_platform == \"win32\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -4537,6 +4945,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -4559,6 +4968,7 @@ version = "0.21.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, @@ -4577,6 +4987,7 @@ version = "3.14.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, @@ -4594,6 +5005,8 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -4608,6 +5021,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -4622,6 +5036,8 @@ version = "0.0.18" description = "A streaming multipart parser for Python" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, @@ -4633,6 +5049,8 @@ version = "3.1.0" description = "Universally unique lexicographically sortable identifier" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "python_ulid-3.1.0-py3-none-any.whl", hash = "sha256:e2cdc979c8c877029b4b7a38a6fba3bc4578e4f109a308419ff4d3ccf0a46619"}, {file = "python_ulid-3.1.0.tar.gz", hash = "sha256:ff0410a598bc5f6b01b602851a3296ede6f91389f913a5d5f8c496003836f636"}, @@ -4647,6 +5065,8 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = true python-versions = "*" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\" or python_version < \"3.9\" and extra == \"utils\"" files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -4658,6 +5078,8 @@ version = "311" description = "Python for Window Extensions" optional = true python-versions = "*" +groups = ["main"] +markers = "python_version >= \"3.10\" and sys_platform == \"win32\" and (extra == \"proxy\" or extra == \"mlflow\")" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -4687,6 +5109,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -4769,6 +5192,8 @@ version = "5.3.1" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.9\" and (extra == \"extra-proxy\" or extra == \"proxy\") and python_version < \"3.14\" or extra == \"proxy\"" files = [ {file = "redis-5.3.1-py3-none-any.whl", hash = "sha256:dc1909bd24669cc31b5f67a039700b16ec30571096c5f1f0d9d2324bff31af97"}, {file = "redis-5.3.1.tar.gz", hash = "sha256:ca49577a531ea64039b5a36db3d6cd1a0c7a60c34124d46924a45b956e8cf14c"}, @@ -4788,6 +5213,8 @@ version = "0.4.1" description = "Python client library and CLI for using Redis as a vector database" optional = true python-versions = "<3.14,>=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "redisvl-0.4.1-py3-none-any.whl", hash = "sha256:6db5d5bc95b1fe8032a1cdae74ce1c65bc7fe9054e5429b5d34d5a91d28bae5f"}, {file = "redisvl-0.4.1.tar.gz", hash = "sha256:fd6a36426ba94792c0efca20915c31232d4ee3cc58eb23794a62c142696401e6"}, @@ -4812,7 +5239,7 @@ bedrock = ["boto3[bedrock] (>=1.36.0,<2.0.0)"] cohere = ["cohere (>=4.44)"] mistralai = ["mistralai (>=1.0.0)"] openai = ["openai (>=1.13.0,<2.0.0)"] -sentence-transformers = ["scipy (<1.15)", "scipy (>=1.15,<2.0)", "sentence-transformers (>=3.4.0,<4.0.0)"] +sentence-transformers = ["scipy (<1.15) ; python_version < \"3.10\"", "scipy (>=1.15,<2.0) ; python_version >= \"3.10\"", "sentence-transformers (>=3.4.0,<4.0.0)"] vertexai = ["google-cloud-aiplatform (>=1.26,<2.0)", "protobuf (>=5.29.1,<6.0.0)"] voyageai = ["voyageai (>=0.2.2)"] @@ -4822,6 +5249,7 @@ version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -4837,6 +5265,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -4940,6 +5369,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +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"}, @@ -4961,6 +5391,7 @@ version = "1.12.1" description = "Mock out responses from the requests package" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, @@ -4978,6 +5409,8 @@ version = "0.8.0" description = "Resend Python SDK" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"extra-proxy\"" files = [ {file = "resend-0.8.0-py2.py3-none-any.whl", hash = "sha256:adc1515dadf4f4fc6b90db55a237f0f37fc56fd74287a986519a8a187fdb661d"}, {file = "resend-0.8.0.tar.gz", hash = "sha256:94142394701724dbcfcd8f760f675c662a1025013e741dd7cc773ca885526257"}, @@ -4992,6 +5425,7 @@ version = "0.25.8" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c"}, {file = "responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4"}, @@ -5003,7 +5437,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "respx" @@ -5011,6 +5445,7 @@ version = "0.22.0" description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, @@ -5025,6 +5460,8 @@ version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = true python-versions = ">=3.7.0" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, @@ -5044,6 +5481,7 @@ version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, @@ -5156,6 +5594,8 @@ version = "2.3.3" description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "rq-2.3.3-py3-none-any.whl", hash = "sha256:2202c4409c4c527ac4bee409867d6c02515dd110030499eb0de54c7374aee0ce"}, {file = "rq-2.3.3.tar.gz", hash = "sha256:20c41c977b6f27c852a41bd855893717402bae7b8d9607dca21fe9dd55453e22"}, @@ -5171,6 +5611,8 @@ version = "4.9.1" description = "Pure-Python RSA implementation" optional = true python-versions = "<4,>=3.6" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\"" files = [ {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, @@ -5185,6 +5627,7 @@ version = "0.1.15" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, @@ -5211,6 +5654,8 @@ version = "0.11.3" description = "An Amazon S3 Transfer Manager" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "s3transfer-0.11.3-py3-none-any.whl", hash = "sha256:ca855bdeb885174b5ffa95b9913622459d4ad8e331fc98eb01e6d5eb6a30655d"}, {file = "s3transfer-0.11.3.tar.gz", hash = "sha256:edae4977e3a122445660c7c114bba949f9d191bae3b34a096f18a1c8c354527a"}, @@ -5228,6 +5673,8 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -5283,6 +5730,8 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = true python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -5338,7 +5787,7 @@ numpy = ">=1.23.5,<2.5" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "semantic-router" @@ -5346,6 +5795,8 @@ version = "0.0.20" description = "Super fast semantic router for AI decision making" optional = true python-versions = ">=3.9,<4.0" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"semantic-router\"" files = [ {file = "semantic_router-0.0.20-py3-none-any.whl", hash = "sha256:7a713401564fb6cf22b566046ad32a4224e4f357be8de6583ca3b9ee328c8f95"}, {file = "semantic_router-0.0.20.tar.gz", hash = "sha256:26119a4628ca72b2fa9eacd446ea763b6f1925a661a34e26945433d2601efac7"}, @@ -5361,7 +5812,7 @@ pydantic = ">=2.5.3,<3.0.0" pyyaml = ">=6.0.1,<7.0.0" [package.extras] -fastembed = ["fastembed (>=0.1.3,<0.2.0)"] +fastembed = ["fastembed (>=0.1.3,<0.2.0) ; python_version < \"3.12\""] hybrid = ["pinecone-text (>=0.7.1,<0.8.0)"] local = ["llama-cpp-python (>=0.2.28,<0.3.0)", "torch (>=2.1.0,<3.0.0)", "transformers (>=4.36.2,<5.0.0)"] @@ -5371,6 +5822,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "proxy-dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -5382,6 +5834,8 @@ version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, @@ -5393,6 +5847,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -5404,6 +5859,8 @@ version = "3.0.1" description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, @@ -5415,6 +5872,8 @@ version = "0.12.1" description = "An audio library based on libsndfile, CFFI and NumPy" optional = true python-versions = "*" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "soundfile-0.12.1-py2.py3-none-any.whl", hash = "sha256:828a79c2e75abab5359f780c81dccd4953c45a2c4cd4f05ba3e233ddf984b882"}, {file = "soundfile-0.12.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d922be1563ce17a69582a352a86f28ed8c9f6a8bc951df63476ffc310c064bfa"}, @@ -5438,6 +5897,8 @@ version = "7.1.2" description = "Python documentation generator" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, @@ -5473,6 +5934,8 @@ version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, @@ -5488,6 +5951,8 @@ version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = true python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, @@ -5503,6 +5968,8 @@ version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, @@ -5518,6 +5985,8 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = true python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -5532,6 +6001,8 @@ version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = true python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, @@ -5547,6 +6018,8 @@ version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = true python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"utils\"" files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, @@ -5562,6 +6035,8 @@ version = "2.0.44" description = "Database Abstraction Library" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "SQLAlchemy-2.0.44-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632"}, {file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f"}, @@ -5657,6 +6132,8 @@ version = "0.5.3" description = "A non-validating SQL parser." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, @@ -5672,6 +6149,8 @@ version = "2.1.3" description = "SSE plugin for Starlette" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, @@ -5691,14 +6170,15 @@ version = "0.44.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea"}, {file = "starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715"}, ] +markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\""} [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] @@ -5709,6 +6189,8 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = true python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.9\" and (extra == \"extra-proxy\" or extra == \"utils\") and python_version < \"3.14\" or extra == \"utils\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -5723,6 +6205,8 @@ version = "0.2.2" description = "backport of asyncio.TaskGroup, asyncio.Runner and asyncio.timeout" optional = false python-versions = "*" +groups = ["proxy-dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb"}, {file = "taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d"}, @@ -5738,6 +6222,8 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -5753,6 +6239,8 @@ version = "3.6.0" description = "threadpoolctl" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -5764,6 +6252,7 @@ version = "0.7.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, @@ -5816,6 +6305,7 @@ version = "0.21.0" description = "" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, @@ -5848,6 +6338,8 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -5899,6 +6391,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["main", "proxy-dev"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -5910,6 +6403,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -5931,6 +6425,7 @@ version = "1.16.0.20241221" description = "Typing stubs for cffi" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types_cffi-1.16.0.20241221-py3-none-any.whl", hash = "sha256:e5b76b4211d7a9185f6ab8d06a106d56c7eb80af7cdb8bfcb4186ade10fb112f"}, {file = "types_cffi-1.16.0.20241221.tar.gz", hash = "sha256:1c96649618f4b6145f58231acb976e0b448be6b847f7ab733dabe62dfbff6591"}, @@ -5945,6 +6440,7 @@ version = "24.1.0.20240722" description = "Typing stubs for pyOpenSSL" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"}, {file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"}, @@ -5960,6 +6456,7 @@ version = "6.0.12.20241230" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"}, {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"}, @@ -5971,6 +6468,7 @@ version = "4.6.0.20241004" description = "Typing stubs for redis" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"}, {file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"}, @@ -5986,6 +6484,8 @@ version = "2.31.0.6" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, @@ -6000,6 +6500,8 @@ version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, @@ -6014,6 +6516,7 @@ version = "75.8.0.20250110" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"}, {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"}, @@ -6025,6 +6528,8 @@ version = "1.26.25.14" description = "Typing stubs for urllib3" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, @@ -6036,6 +6541,7 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -6047,6 +6553,8 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -6061,6 +6569,8 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = true python-versions = ">=2" +groups = ["main"] +markers = "python_version >= \"3.10\" and platform_system == \"Windows\" and (extra == \"proxy\" or extra == \"mlflow\") or python_version >= \"3.10\" and extra == \"mlflow\" or platform_system == \"Windows\" and extra == \"proxy\"" files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -6072,6 +6582,8 @@ version = "5.2" description = "tzinfo object for the local timezone" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, @@ -6090,14 +6602,16 @@ 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", "dev", "proxy-dev"] +markers = "python_version < \"3.10\"" files = [ {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -6106,13 +6620,15 @@ 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", "proxy-dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -6123,6 +6639,8 @@ version = "0.29.0" description = "The lightning-fast ASGI server." optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\"" files = [ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, @@ -6134,7 +6652,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -6142,6 +6660,8 @@ version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = true python-versions = ">=3.8.0" +groups = ["main"] +markers = "sys_platform != \"win32\" and extra == \"proxy\"" files = [ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, @@ -6193,6 +6713,8 @@ version = "3.0.2" description = "Waitress WSGI server" optional = true python-versions = ">=3.9.0" +groups = ["main"] +markers = "python_version >= \"3.10\" and platform_system == \"Windows\" and extra == \"mlflow\"" files = [ {file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}, {file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}, @@ -6208,6 +6730,8 @@ version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = true python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"proxy\"" files = [ {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, @@ -6303,6 +6827,8 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = true python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"mlflow\"" files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -6320,6 +6846,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "proxy-dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -6403,6 +6930,7 @@ files = [ {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, ] +markers = {main = "python_version >= \"3.10\""} [[package]] name = "wsproto" @@ -6410,6 +6938,7 @@ version = "1.2.0" description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" +groups = ["proxy-dev"] files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -6424,6 +6953,7 @@ version = "1.15.2" description = "Yet another URL library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, @@ -6536,17 +7066,18 @@ version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" +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"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +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)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] @@ -6558,6 +7089,6 @@ semantic-router = ["semantic-router"] utils = ["numpydoc"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.8.1,<4.0, !=3.9.7" -content-hash = "9aa69423e29fd687063c54a6afa789fd19f3828cddcd94cbf4ee5bda17d13b32" +content-hash = "39b2e6a0a4c7711806e649e70c7ee5544d0a00e2be19010a4b2581734004db17" diff --git a/pyproject.toml b/pyproject.toml index ebfa3345fa..025d114a95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.79.3" +version = "1.79.4" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -59,7 +59,7 @@ websockets = {version = "^13.1.0", optional = true} boto3 = {version = "1.36.0", optional = true} redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = "^1.10.0", optional = true, python = ">=3.10"} -litellm-proxy-extras = {version = "0.4.3", optional = true} +litellm-proxy-extras = {version = "0.4.4", optional = true} rich = {version = "13.7.1", optional = true} litellm-enterprise = {version = "0.1.20", optional = true} diskcache = {version = "^5.6.1", optional = true} @@ -159,7 +159,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.79.3" +version = "1.79.4" version_files = [ "pyproject.toml:^version" ] diff --git a/requirements.txt b/requirements.txt index cf2ce85e52..44580e8697 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,7 @@ sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests cryptography==44.0.1 tzdata==2025.1 # IANA time zone database -litellm-proxy-extras==0.4.3 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.4 # for proxy extras - e.g. prisma migrations ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.1 # for env tiktoken==0.8.0 # for calculating usage diff --git a/tests/audio_tests/runwayml_speech.mp3 b/tests/audio_tests/runwayml_speech.mp3 index 22aee2084b..5eaa8b3362 100644 Binary files a/tests/audio_tests/runwayml_speech.mp3 and b/tests/audio_tests/runwayml_speech.mp3 differ diff --git a/tests/audio_tests/test_audio_speech.py b/tests/audio_tests/test_audio_speech.py index 409e3544fc..da6e555c2e 100644 --- a/tests/audio_tests/test_audio_speech.py +++ b/tests/audio_tests/test_audio_speech.py @@ -426,7 +426,7 @@ async def test_runwayml_tts_async(): assert speech_file_path.exists() assert speech_file_path.stat().st_size > 0 - print(f"Azure TTS audio saved to: {speech_file_path}") + print(f"RunwayML TTS audio saved to: {speech_file_path}") # assert response cost is greater than 0 print("Response cost: ", response._hidden_params["response_cost"]) diff --git a/tests/batches_tests/test_bedrock_files_and_batches.py b/tests/batches_tests/test_bedrock_files_and_batches.py index d082ed41ea..6ae373995d 100644 --- a/tests/batches_tests/test_bedrock_files_and_batches.py +++ b/tests/batches_tests/test_bedrock_files_and_batches.py @@ -193,3 +193,53 @@ async def test_bedrock_retrieve_batch(): assert batch_response.input_file_id == "s3://test-bucket/input/test-input.jsonl" assert batch_response.output_file_id == "s3://test-bucket/output/" + +def test_bedrock_batch_with_encryption_key_in_post_request(): + """ + Test that s3_encryption_key_id is included in the AWS POST request payload. + """ + import json + import litellm + + test_kms_key_id = "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012" + + captured_request_body = None + + def mock_post(*args, **kwargs): + nonlocal captured_request_body + if "data" in kwargs: + captured_request_body = kwargs["data"] + + mock_response = MagicMock() + mock_response.json.return_value = { + "jobArn": "arn:aws:bedrock:us-west-2:123456789012:model-invocation-job/test-job", + "jobName": "test-job", + "status": "Submitted" + } + mock_response.status_code = 200 + mock_response.raise_for_status.return_value = None + return mock_response + + with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", side_effect=mock_post): + response = litellm.create_batch( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="s3://test-bucket/input/test.jsonl", + custom_llm_provider="bedrock", + model="us.anthropic.claude-3-5-sonnet-20240620-v1:0", + s3_encryption_key_id=test_kms_key_id, + aws_batch_role_arn="arn:aws:iam::123456789012:role/test-role" + ) + + assert captured_request_body is not None, "Request body was not captured" + + request_data = json.loads(captured_request_body) + print("REQUEST DATA to bedrock batch creation", json.dumps(request_data, indent=4)) + + assert "outputDataConfig" in request_data + assert "s3OutputDataConfig" in request_data["outputDataConfig"] + assert "s3EncryptionKeyId" in request_data["outputDataConfig"]["s3OutputDataConfig"] + assert request_data["outputDataConfig"]["s3OutputDataConfig"]["s3EncryptionKeyId"] == test_kms_key_id + + print("SUCCESS: s3_encryption_key_id properly included in AWS POST request") + diff --git a/tests/image_gen_tests/test_fal_ai_image_generation.py b/tests/image_gen_tests/test_fal_ai_image_generation.py index 949606a58a..7b5415e1d7 100644 --- a/tests/image_gen_tests/test_fal_ai_image_generation.py +++ b/tests/image_gen_tests/test_fal_ai_image_generation.py @@ -14,6 +14,7 @@ from litellm import aimage_generation "model", [ "fal_ai/fal-ai/flux-pro/v1.1-ultra", + "fal_ai/fal-ai/flux/schnell", "fal_ai/fal-ai/recraft/v3/text-to-image", "fal_ai/bria/text-to-image/3.2", "fal_ai/fal-ai/stable-diffusion-v35-medium" diff --git a/tests/llm_translation/test_openai.py b/tests/llm_translation/test_openai.py index 1b96a99621..ba1d9e6ac2 100644 --- a/tests/llm_translation/test_openai.py +++ b/tests/llm_translation/test_openai.py @@ -337,6 +337,48 @@ def test_openai_max_retries_0(mock_get_openai_client): assert mock_get_openai_client.call_args.kwargs["max_retries"] == 0 +@patch("litellm.main.openai_chat_completions._get_openai_client") +def test_openai_image_generation_forwards_organization(mock_get_openai_client): + """Ensure organization flows to OpenAI client for image generation.""" + + class _DummyImages: + def generate(self, **kwargs): # type: ignore + class _Resp: + def model_dump(self_inner): # minimal OpenAI ImagesResponse shape + return { + "created": 123, + "data": [{"url": "http://example.com/image.png"}], + "usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0}, + } + + return _Resp() + + class _DummyClient: + def __init__(self): + self.api_key = "sk-test" + + class _BaseURL: + _uri_reference = "https://api.openai.com/v1" + + self._base_url = _BaseURL() + self.images = _DummyImages() + + mock_get_openai_client.return_value = _DummyClient() + + org = "org_test_123" + resp = litellm.image_generation( + model="gpt-image-1", + prompt="A cute baby sea otter", + organization=org, + ) + + # Assert organization forwarded into OpenAI client factory + assert mock_get_openai_client.call_args.kwargs.get("organization") == org + + # Basic sanity on response shape + assert hasattr(resp, "data") and len(resp.data) == 1 + + @pytest.mark.parametrize("model", ["o1", "o3-mini"]) def test_o1_parallel_tool_calls(model): litellm.completion( diff --git a/tests/llm_translation/test_sambanova_chat_transformation.py b/tests/llm_translation/test_sambanova_chat_transformation.py new file mode 100644 index 0000000000..368c09931d --- /dev/null +++ b/tests/llm_translation/test_sambanova_chat_transformation.py @@ -0,0 +1,127 @@ +""" +Unit tests for SambaNova chat message transformation +""" +import pytest +from litellm.llms.sambanova.chat import SambanovaConfig + + +class TestSambanovaContentListHandling: + """ + Test that SambaNova properly transforms content lists to strings + """ + + def test_content_list_to_string_transformation(self): + """ + Test content list with text objects is converted to string. + + SambaNova API doesn't support content as a list - only string content. + """ + config = SambanovaConfig() + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Hello, how are you?"} + ] + } + ] + + transformed_messages = config._transform_messages( + messages=messages, + model="sambanova/gpt-oss-120b", + is_async=False + ) + + assert len(transformed_messages) == 1 + assert transformed_messages[0]["role"] == "user" + assert isinstance(transformed_messages[0]["content"], str) + assert transformed_messages[0]["content"] == "Hello, how are you?" + + def test_content_list_multiple_text_blocks(self): + """ + Test content list with multiple text blocks is converted to concatenated string. + """ + config = SambanovaConfig() + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Hello, "}, + {"type": "text", "text": "how are you?"} + ] + } + ] + + transformed_messages = config._transform_messages( + messages=messages, + model="sambanova/gpt-oss-120b", + is_async=False + ) + + assert transformed_messages[0]["content"] == "Hello, how are you?" + + def test_string_content_unchanged(self): + """ + Test that string content is passed through unchanged. + """ + config = SambanovaConfig() + + messages = [ + { + "role": "user", + "content": "Hello, how are you?" + } + ] + + transformed_messages = config._transform_messages( + messages=messages, + model="sambanova/gpt-oss-120b", + is_async=False + ) + + assert transformed_messages[0]["content"] == "Hello, how are you?" + + def test_multiple_messages_transformation(self): + """ + Test transformation of multiple messages with mixed content types. + """ + config = SambanovaConfig() + + messages = [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "What is the weather?"} + ] + }, + { + "role": "assistant", + "content": "I need your location." + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "I'm in "}, + {"type": "text", "text": "San Francisco"} + ] + } + ] + + transformed_messages = config._transform_messages( + messages=messages, + model="sambanova/gpt-oss-120b", + is_async=False + ) + + assert len(transformed_messages) == 4 + assert transformed_messages[0]["content"] == "You are a helpful assistant." + assert transformed_messages[1]["content"] == "What is the weather?" + assert transformed_messages[2]["content"] == "I need your location." + assert transformed_messages[3]["content"] == "I'm in San Francisco" + diff --git a/tests/proxy_unit_tests/test_key_generate_prisma.py b/tests/proxy_unit_tests/test_key_generate_prisma.py index 06972c32a6..ca8014f76f 100644 --- a/tests/proxy_unit_tests/test_key_generate_prisma.py +++ b/tests/proxy_unit_tests/test_key_generate_prisma.py @@ -132,6 +132,11 @@ def prisma_client(): ### add connection pool + pool timeout args params = {"connection_limit": 100, "pool_timeout": 60} database_url = os.getenv("DATABASE_URL") + + # If DATABASE_URL is not set, use a default test database URL + if not database_url: + database_url = "postgresql://postgres:postgres@localhost:5432/circle_test" + modified_url = append_query_params(database_url, params) os.environ["DATABASE_URL"] = modified_url @@ -666,7 +671,8 @@ def test_call_with_end_user_over_budget(prisma_client): except Exception as e: print(f"raised error: {e}, traceback: {traceback.format_exc()}") error_detail = e.message - assert "Budget has been exceeded! Current" in error_detail + assert "ExceededBudget: End User=" in error_detail + assert "over budget" in error_detail assert isinstance(e, ProxyException) assert e.type == ProxyErrorTypes.budget_exceeded print(vars(e)) diff --git a/tests/proxy_unit_tests/test_proxy_exception_mapping.py b/tests/proxy_unit_tests/test_proxy_exception_mapping.py index 5fbe389f35..2487c69d9d 100644 --- a/tests/proxy_unit_tests/test_proxy_exception_mapping.py +++ b/tests/proxy_unit_tests/test_proxy_exception_mapping.py @@ -157,6 +157,9 @@ def test_embedding_auth_exception_azure(mock_aembedding, client): metadata=mock.ANY, proxy_server_request=mock.ANY, secret_fields=mock.ANY, + request_timeout=mock.ANY, + litellm_call_id=mock.ANY, + litellm_logging_obj=mock.ANY, ) print("Response from proxy=", response) diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index 8b9c515234..9ae916db0a 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -545,17 +545,22 @@ def test_embedding(mock_aembedding, client_no_auth): "input": ["good morning from litellm"], } - pre_call_return_value = { - **test_data, - "metadata": {"source": "unit-test"}, - "proxy_server_request": {"path": "/v1/embeddings"}, - "secret_fields": [], - } + async def _pre_call_hook_side_effect(**kwargs): + data = kwargs["data"] + metadata = {**(data.get("metadata") or {}), "source": "unit-test"} + data["metadata"] = metadata + proxy_request = {**(data.get("proxy_server_request") or {})} + proxy_request["path"] = "/v1/embeddings" + data["proxy_server_request"] = proxy_request + return data + + async def _post_call_success_side_effect(**kwargs): + return kwargs["response"] with patch.object( litellm.proxy.proxy_server.proxy_logging_obj, "pre_call_hook", - new=AsyncMock(return_value=pre_call_return_value), + new=AsyncMock(side_effect=_pre_call_hook_side_effect), ) as mock_pre_call_hook, patch.object( litellm.proxy.proxy_server.proxy_logging_obj, "during_call_hook", @@ -563,7 +568,7 @@ def test_embedding(mock_aembedding, client_no_auth): ) as mock_during_hook, patch.object( litellm.proxy.proxy_server.proxy_logging_obj, "post_call_success_hook", - new=AsyncMock(return_value=None), + new=AsyncMock(side_effect=_post_call_success_side_effect), ): response = client_no_auth.post("/v1/embeddings", json=test_data) @@ -571,6 +576,9 @@ def test_embedding(mock_aembedding, client_no_auth): model="azure/text-embedding-ada-002", input=["good morning from litellm"], specific_deployment=True, + litellm_call_id=mock.ANY, + litellm_logging_obj=mock.ANY, + request_timeout=mock.ANY, metadata=mock.ANY, proxy_server_request=mock.ANY, secret_fields=mock.ANY, @@ -580,6 +588,9 @@ def test_embedding(mock_aembedding, client_no_auth): print(len(result["data"][0]["embedding"])) assert len(result["data"][0]["embedding"]) > 10 # this usually has len==1536 so + call_metadata = mock_aembedding.call_args.kwargs["metadata"] + assert call_metadata.get("source") == "unit-test" + pre_call_kwargs = mock_pre_call_hook.await_args_list[0].kwargs assert ( pre_call_kwargs.get("call_type") == "aembedding" @@ -587,8 +598,8 @@ def test_embedding(mock_aembedding, client_no_auth): during_call_kwargs = mock_during_hook.await_args_list[0].kwargs assert ( - during_call_kwargs.get("call_type") == "aembedding" - ), f"expected during_call_hook to receive call_type='aembedding', got {during_call_kwargs.get('call_type')}" + during_call_kwargs.get("call_type") == "embeddings" + ), f"expected during_call_hook to receive call_type='embeddings', got {during_call_kwargs.get('call_type')}" except Exception as e: pytest.fail(f"LiteLLM Proxy test failed. Exception - {str(e)}") @@ -609,11 +620,15 @@ def test_bedrock_embedding(mock_aembedding, client_no_auth): mock_aembedding.assert_called_once_with( model="amazon-embeddings", input=["good morning from litellm"], + litellm_call_id=mock.ANY, + litellm_logging_obj=mock.ANY, + request_timeout=mock.ANY, metadata=mock.ANY, proxy_server_request=mock.ANY, secret_fields=mock.ANY, ) assert response.status_code == 200 + print(response.status_code, response.text) result = response.json() print(len(result["data"][0]["embedding"])) assert len(result["data"][0]["embedding"]) > 10 # this usually has len==1536 so diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index 876eb8b29f..42db678d1d 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -153,3 +153,33 @@ def test_gpt5_codex_supports_function_calling(config: OpenAIConfig): assert "functions" in supported_params assert "function_call" in supported_params assert "tools" in supported_params + + +def test_gpt5_1_reasoning_effort_none(config: OpenAIConfig): + """Test that GPT-5.1 supports reasoning_effort='none' parameter. + + Related issue: https://github.com/BerriAI/litellm/issues/16633 + GPT-5.1 introduced 'none' as the new default reasoning effort setting + for faster, lower-latency responses. + """ + # Test that reasoning_effort is a supported parameter + assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5.1") + + # Test that reasoning_effort="none" passes through correctly + params = config.map_openai_params( + non_default_params={"reasoning_effort": "none"}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + assert params["reasoning_effort"] == "none" + + # Test with other valid values for GPT-5.1 + for effort in ["low", "medium", "high"]: + params = config.map_openai_params( + non_default_params={"reasoning_effort": effort}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + assert params["reasoning_effort"] == effort diff --git a/tests/test_litellm/llms/openai_like/chat/test_transformation.py b/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py similarity index 100% rename from tests/test_litellm/llms/openai_like/chat/test_transformation.py rename to tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py diff --git a/tests/test_litellm/llms/vertex_ai/test_bge_embedding.py b/tests/test_litellm/llms/vertex_ai/test_bge_embedding.py deleted file mode 100644 index 156ab95184..0000000000 --- a/tests/test_litellm/llms/vertex_ai/test_bge_embedding.py +++ /dev/null @@ -1,251 +0,0 @@ -""" -Test BGE embeddings with Vertex AI using custom api_base. - -This test ensures that BGE embeddings work correctly with Vertex AI -and that the request body is properly formatted. -""" - -import json -import os -import sys -from unittest.mock import MagicMock, patch - -sys.path.insert( - 0, os.path.abspath("../../../..") -) - -import pytest - -import litellm -from litellm.llms.custom_httpx.http_handler import HTTPHandler - - -def test_vertex_ai_bge_embedding_with_custom_api_base(): - """ - Test Vertex AI BGE embeddings with custom api_base. - - This test verifies that when using a BGE model with Vertex AI and - a custom api_base, the request is properly formatted and sent to - the correct endpoint. - """ - client = HTTPHandler() - - def mock_auth_token(*args, **kwargs): - return "fake-token", "fake-project" - - with patch.object(client, "post") as mock_post, patch( - "litellm.llms.vertex_ai.vertex_embeddings.embedding_handler.VertexEmbedding._ensure_access_token", - side_effect=mock_auth_token - ): - mock_response = MagicMock() - mock_response.status_code = 200 - # BGE models return embeddings directly as arrays, not wrapped in objects - mock_response.json.return_value = { - "predictions": [ - [0.1, 0.2, 0.3, 0.4, 0.5], - [0.6, 0.7, 0.8, 0.9, 1.0] - ], - "deployedModelId": "849506872875548672", - "model": "projects/1060139831167/locations/us-central1/models/baai_bge-small-en-v1.5", - "modelDisplayName": "baai_bge-small-en-v1.5", - "modelVersionId": "1" - } - mock_post.return_value = mock_response - - response = litellm.embedding( - model="vertex_ai/bge-small-en-v1.5", - input=["Hello", "World"], - api_base="http://10.96.32.8", - client=client - ) - - mock_post.assert_called_once() - - call_args = mock_post.call_args - kwargs = call_args.kwargs if hasattr(call_args, 'kwargs') else call_args[1] - - if "url" in kwargs: - api_url_called = kwargs["url"] - elif len(call_args[0]) > 0: - api_url_called = call_args[0][0] - else: - api_url_called = "Unknown" - - # Vertex AI may use 'json' or 'data' parameter - if "json" in kwargs: - request_data = kwargs["json"] - elif "data" in kwargs: - request_data = json.loads(kwargs["data"]) - else: - request_data = {} - - print("\n" + "="*50) - print("Mock Request Body Received:") - print("="*50) - print(json.dumps(request_data, indent=2)) - print("="*50) - print(f"API Base: {api_url_called}") - print("="*50 + "\n") - - assert "instances" in request_data - assert len(request_data["instances"]) == 2 - # BGE models should use "prompt" instead of "content" - assert "prompt" in request_data["instances"][0] - assert request_data["instances"][0]["prompt"] == "Hello" - assert "prompt" in request_data["instances"][1] - assert request_data["instances"][1]["prompt"] == "World" - - assert isinstance(response.data, list) - assert len(response.data) == 2 - assert "embedding" in response.data[0] - - -def test_vertex_ai_bge_with_endpoint_id_pattern(): - """ - Test BGE with vertex_ai/bge/endpoint_id pattern. - - This test verifies that the pattern vertex_ai/bge/204379420394258432 - correctly triggers BGE transformations and routes to the endpoint. - """ - client = HTTPHandler() - - def mock_auth_token(*args, **kwargs): - return "fake-token", "fake-project" - - with patch.object(client, "post") as mock_post, patch( - "litellm.llms.vertex_ai.vertex_embeddings.embedding_handler.VertexEmbedding._ensure_access_token", - side_effect=mock_auth_token - ): - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "predictions": [ - [0.1, 0.2, 0.3, 0.4, 0.5], - [0.6, 0.7, 0.8, 0.9, 1.0] - ], - "deployedModelId": "204379420394258432", - "model": "projects/1060139831167/locations/europe-west4/models/baai_bge-base-en", - "modelDisplayName": "baai_bge-base-en", - "modelVersionId": "1" - } - mock_post.return_value = mock_response - - response = litellm.embedding( - model="vertex_ai/bge/204379420394258432", - input=["Hello", "World"], - vertex_project="1060139831167", - vertex_location="europe-west4", - client=client - ) - - mock_post.assert_called_once() - - call_args = mock_post.call_args - kwargs = call_args.kwargs if hasattr(call_args, 'kwargs') else call_args[1] - - if "url" in kwargs: - api_url_called = kwargs["url"] - elif len(call_args[0]) > 0: - api_url_called = call_args[0][0] - else: - api_url_called = "Unknown" - - # Vertex AI may use 'json' or 'data' parameter - if "json" in kwargs: - request_data = kwargs["json"] - elif "data" in kwargs: - request_data = json.loads(kwargs["data"]) - else: - request_data = {} - - print("\n" + "="*50) - print("BGE Endpoint Pattern Test:") - print("="*50) - print(f"Model: vertex_ai/bge/204379420394258432") - print(f"API URL: {api_url_called}") - print("Request Body:") - print(json.dumps(request_data, indent=2)) - print("="*50 + "\n") - - # Verify URL contains the endpoint ID and uses endpoints/ path - assert "204379420394258432" in api_url_called, f"Endpoint ID not in URL: {api_url_called}" - assert "endpoints" in api_url_called, f"Expected 'endpoints' in URL, got: {api_url_called}" - - # Verify BGE-specific request format (uses "prompt" not "content") - assert "instances" in request_data - assert "prompt" in request_data["instances"][0] - assert request_data["instances"][0]["prompt"] == "Hello" - - # Verify response - assert isinstance(response.data, list) - assert len(response.data) == 2 - - -def test_vertex_ai_bge_psc_endpoint_url_construction(): - """ - Test that BGE models with PSC endpoints construct correct URL without bge/ prefix. - - Verifies that vertex_ai/bge/378943383978115072 with api_base http://10.128.16.2 - constructs URL: http://10.128.16.2/v1/projects/{project}/locations/{location}/endpoints/378943383978115072:predict - - The bge/ prefix should be stripped from the endpoint URL. - """ - client = HTTPHandler() - - def mock_auth_token(*args, **kwargs): - return "fake-token", "gen-lang-client-0682925754" - - with patch.object(client, "post") as mock_post, patch( - "litellm.llms.vertex_ai.vertex_embeddings.embedding_handler.VertexEmbedding._ensure_access_token", - side_effect=mock_auth_token - ): - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "predictions": [ - [0.1, 0.2, 0.3, 0.4, 0.5] - ] - } - mock_post.return_value = mock_response - - response = litellm.embedding( - model="vertex_ai/bge/378943383978115072", - input=["The food was delicious and the waiter.."], - api_base="http://10.128.16.2", - vertex_project="gen-lang-client-0682925754", - vertex_location="us-central1", - client=client - ) - - mock_post.assert_called_once() - - call_args = mock_post.call_args - kwargs = call_args.kwargs if hasattr(call_args, 'kwargs') else call_args[1] - - if "url" in kwargs: - api_url_called = kwargs["url"] - elif len(call_args[0]) > 0: - api_url_called = call_args[0][0] - else: - api_url_called = "Unknown" - - print("\n" + "="*50) - print("PSC Endpoint URL Construction Test:") - print("="*50) - print(f"Model: vertex_ai/bge/378943383978115072") - print(f"API Base: http://10.128.16.2") - print(f"Constructed URL: {api_url_called}") - print("="*50 + "\n") - - # Verify the URL is constructed correctly - expected_url = "http://10.128.16.2/v1/projects/gen-lang-client-0682925754/locations/us-central1/endpoints/378943383978115072:predict" - assert api_url_called == expected_url, f"Expected URL: {expected_url}, Got: {api_url_called}" - - # Verify bge/ prefix is NOT in the URL - assert "bge/" not in api_url_called, f"URL should not contain 'bge/' prefix: {api_url_called}" - - # Verify response works - assert isinstance(response.data, list) - assert len(response.data) == 1 - - diff --git a/tests/test_litellm/llms/vertex_ai/test_bge_response_transformation.py b/tests/test_litellm/llms/vertex_ai/test_bge_response_transformation.py deleted file mode 100644 index 20150501ad..0000000000 --- a/tests/test_litellm/llms/vertex_ai/test_bge_response_transformation.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Test BGE response transformation validation. - -This test verifies that the BGE response transformer properly validates -and handles different response formats. -""" - -import os -import sys - -sys.path.insert( - 0, os.path.abspath("../../../..") -) - -import pytest - -from litellm.llms.vertex_ai.vertex_embeddings.bge import VertexBGEConfig -from litellm.types.utils import EmbeddingResponse - - -def test_is_bge_model_detection(): - """ - Test BGE model detection for post-provider-split patterns. - - After main.py splits the provider, model strings are passed without the provider prefix. - Model name transformation (bge/ -> numeric ID) is handled in common_utils._get_vertex_url(). - """ - # Should detect BGE models (after provider split) - assert VertexBGEConfig.is_bge_model("bge-small-en-v1.5") is True - assert VertexBGEConfig.is_bge_model("bge/204379420394258432") is True - assert VertexBGEConfig.is_bge_model("BGE-large-en-v1.5") is True # case insensitive - - # Should not detect non-BGE models - assert VertexBGEConfig.is_bge_model("textembedding-gecko") is False - assert VertexBGEConfig.is_bge_model("gemma") is False - assert VertexBGEConfig.is_bge_model("123456789") is False - - -def test_bge_response_transformation_success(): - """ - Test successful BGE response transformation. - - Verifies that a valid BGE response is properly transformed - to OpenAI format. - """ - response = { - "predictions": [ - [0.1, 0.2, 0.3], - [0.4, 0.5, 0.6] - ], - "deployedModelId": "123456", - "model": "projects/test/models/bge-base" - } - - model_response = EmbeddingResponse() - result = VertexBGEConfig.transform_response( - response=response, - model="bge-small-en-v1.5", - model_response=model_response - ) - - assert result.object == "list" - assert len(result.data) == 2 - assert result.data[0]["embedding"] == [0.1, 0.2, 0.3] - assert result.data[1]["embedding"] == [0.4, 0.5, 0.6] - assert result.data[0]["index"] == 0 - assert result.data[1]["index"] == 1 - assert result.model == "bge-small-en-v1.5" - - -def test_bge_response_missing_predictions(): - """ - Test BGE response transformation with missing predictions field. - - Verifies that a KeyError is raised when the response doesn't - contain the required 'predictions' field. - """ - response = { - "deployedModelId": "123456", - "model": "projects/test/models/bge-base" - } - - model_response = EmbeddingResponse() - - with pytest.raises(KeyError, match="Response missing 'predictions' field"): - VertexBGEConfig.transform_response( - response=response, - model="bge-small-en-v1.5", - model_response=model_response - ) - - -def test_bge_response_invalid_predictions_type(): - """ - Test BGE response transformation with invalid predictions type. - - Verifies that a ValueError is raised when predictions is not a list. - """ - response = { - "predictions": "not-a-list" - } - - model_response = EmbeddingResponse() - - with pytest.raises(ValueError, match="Expected 'predictions' to be a list"): - VertexBGEConfig.transform_response( - response=response, - model="bge-small-en-v1.5", - model_response=model_response - ) - diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index 98977e06ff..4ea1d81c26 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -967,6 +967,10 @@ async def test_vertex_ai_partner_model_detection(): assert VertexAIPartnerModels.is_vertex_partner_model("meta/llama-3.1-405b") # Test Minimax models assert VertexAIPartnerModels.is_vertex_partner_model("minimaxai/minimax-m2-maas") + # Test Moonshot models + assert VertexAIPartnerModels.is_vertex_partner_model( + "moonshotai/kimi-k2-thinking-maas" + ) # Test Gemini models (should NOT be detected as partner model) assert not VertexAIPartnerModels.is_vertex_partner_model("gemini-1.5-pro") @@ -989,3 +993,16 @@ def test_vertex_ai_minimax_uses_openai_handler(): assert VertexAIPartnerModels.should_use_openai_handler( "minimaxai/minimax-m2-maas" ) + + +def test_vertex_ai_moonshot_uses_openai_handler(): + """ + Ensure Moonshot partner models re-use the OpenAI-format handler. + """ + from litellm.llms.vertex_ai.vertex_ai_partner_models.main import ( + VertexAIPartnerModels, + ) + + assert VertexAIPartnerModels.should_use_openai_handler( + "moonshotai/kimi-k2-thinking-maas" + ) diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py deleted file mode 100644 index 46f365094c..0000000000 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_psc_endpoint_support.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Unit tests for Vertex AI Private Service Connect (PSC) endpoint support - -Tests that LiteLLM properly constructs URLs when using custom api_base -for PSC endpoints. -""" - -import pytest -import sys -import os - -# Add the litellm package to the path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../../..")) - -from litellm.llms.vertex_ai.vertex_llm_base import VertexBase - - -class TestVertexAIPSCEndpointSupport: - """Test cases for PSC endpoint URL construction""" - - def test_psc_endpoint_url_construction_basic(self): - """Test basic PSC endpoint URL construction for predict endpoint""" - vertex_base = VertexBase() - psc_api_base = "http://10.96.32.8" - endpoint_id = "1234567890" - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header="test-token", - url="", # This will be replaced - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - expected_url = f"{psc_api_base}/v1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:predict" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_psc_endpoint_url_construction_with_streaming(self): - """Test PSC endpoint URL construction with streaming enabled""" - vertex_base = VertexBase() - psc_api_base = "http://10.96.32.8" - endpoint_id = "1234567890" - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="streamGenerateContent", - stream=True, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - expected_url = f"{psc_api_base}/v1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:streamGenerateContent?alt=sse" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_psc_endpoint_url_construction_v1beta1(self): - """Test PSC endpoint URL construction with v1beta1 API version""" - vertex_base = VertexBase() - psc_api_base = "http://10.96.32.8" - endpoint_id = "1234567890" - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1beta1", - ) - - expected_url = f"{psc_api_base}/v1beta1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:predict" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_psc_endpoint_url_with_https(self): - """Test PSC endpoint URL construction with HTTPS""" - vertex_base = VertexBase() - psc_api_base = "https://10.96.32.8" - endpoint_id = "1234567890" - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - expected_url = f"{psc_api_base}/v1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:predict" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_psc_endpoint_with_trailing_slash(self): - """Test that trailing slashes in api_base are handled correctly""" - vertex_base = VertexBase() - psc_api_base = "http://10.96.32.8/" - endpoint_id = "1234567890" - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - # rstrip('/') should remove the trailing slash - expected_url = f"{psc_api_base.rstrip('/')}/v1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:predict" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_standard_proxy_with_googleapis(self): - """Test that standard proxies with googleapis.com in URL use simple format""" - vertex_base = VertexBase() - proxy_api_base = "https://my-proxy.googleapis.com" - endpoint_id = "gemini-pro" # Not numeric - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=proxy_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="generateContent", - stream=False, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - # Should use simple format: api_base:endpoint - expected_url = f"{proxy_api_base}:generateContent" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_custom_proxy_with_numeric_model(self): - """Test that numeric model IDs trigger PSC-style URL construction""" - vertex_base = VertexBase() - proxy_api_base = "https://my-custom-proxy.example.com" - endpoint_id = "9876543210" # Numeric endpoint ID - project_id = "test-project" - location = "us-central1" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=proxy_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header="test-token", - url="", - model=endpoint_id, - vertex_project=project_id, - vertex_location=location, - vertex_api_version="v1", - ) - - # Numeric model should trigger full path construction - expected_url = f"{proxy_api_base}/v1/projects/{project_id}/locations/{location}/endpoints/{endpoint_id}:predict" - assert ( - url == expected_url - ), f"Expected {expected_url}, but got {url}" - - def test_no_api_base_returns_original_url(self): - """Test that when api_base is None, the original URL is returned""" - vertex_base = VertexBase() - original_url = "https://us-central1-aiplatform.googleapis.com/v1/projects/test/locations/us-central1/publishers/google/models/gemini-pro:generateContent" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=None, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="generateContent", - stream=False, - auth_header="test-token", - url=original_url, - model="gemini-pro", - vertex_project="test-project", - vertex_location="us-central1", - vertex_api_version="v1", - ) - - # When api_base is None, original URL should be returned unchanged - assert url == original_url, f"Expected {original_url}, but got {url}" - - def test_auth_header_preserved(self): - """Test that auth_header is properly preserved""" - vertex_base = VertexBase() - psc_api_base = "http://10.96.32.8" - test_auth_header = "Bearer test-token-12345" - - auth_header, url = vertex_base._check_custom_proxy( - api_base=psc_api_base, - custom_llm_provider="vertex_ai", - gemini_api_key=None, - endpoint="predict", - stream=False, - auth_header=test_auth_header, - url="", - model="1234567890", - vertex_project="test-project", - vertex_location="us-central1", - vertex_api_version="v1", - ) - - assert ( - auth_header == test_auth_header - ), f"Auth header should be preserved, got {auth_header}" - diff --git a/tests/test_litellm/llms/xai/responses/test_transformation.py b/tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py similarity index 100% rename from tests/test_litellm/llms/xai/responses/test_transformation.py rename to tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py diff --git a/tests/test_litellm/proxy/management_endpoints/test_access_group_management.py b/tests/test_litellm/proxy/management_endpoints/test_access_group_management.py new file mode 100644 index 0000000000..1846ffaeb6 --- /dev/null +++ b/tests/test_litellm/proxy/management_endpoints/test_access_group_management.py @@ -0,0 +1,80 @@ +""" +Test access group management endpoints +""" + +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../..") +) # Adds the parent directory to the system path + +from litellm import Router + + +@pytest.mark.asyncio +async def test_create_duplicate_access_group_fails(): + """ + Test that creating an access group with a name that already exists returns 409 error. + + Scenario: User creates "production-models" access group, then tries to create it again. + Should fail with 409 Conflict. + """ + from fastapi import HTTPException + + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints.model_access_group_management_endpoints import ( + create_model_group, + ) + from litellm.types.proxy.management_endpoints.model_management_endpoints import ( + NewModelGroupRequest, + ) + + # Mock dependencies - use exact model name (not wildcard) + mock_router = Router( + model_list=[ + { + "model_name": "gpt-4", # Exact model name + "litellm_params": { + "model": "gpt-4", + "api_key": "fake-key", + }, + } + ] + ) + + mock_prisma = MagicMock() + mock_prisma.db.litellm_proxymodeltable.find_many = AsyncMock( + return_value=[ + MagicMock( + model_id="1", + model_name="gpt-4", + model_info={"access_groups": ["production-models"]}, # Already exists + ) + ] + ) + + mock_user = UserAPIKeyAuth( + user_id="test_admin", + user_role=LitellmUserRoles.PROXY_ADMIN, + ) + + request_data = NewModelGroupRequest( + access_group="production-models", + model_names=["gpt-4"], + ) + + # Mock the imported dependencies from proxy_server (where they're actually imported from) + with patch("litellm.proxy.proxy_server.llm_router", mock_router), \ + patch("litellm.proxy.proxy_server.prisma_client", mock_prisma): + + # Should raise 409 Conflict + with pytest.raises(HTTPException) as exc_info: + await create_model_group(data=request_data, user_api_key_dict=mock_user) + + assert exc_info.value.status_code == 409 + assert "already exists" in str(exc_info.value.detail) + diff --git a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py index 2102fe71b1..6c22837a09 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py @@ -520,6 +520,51 @@ class TestListMCPServers: assert mock_server.credentials == {"auth_value": "top-secret"} assert result.status == "healthy" + @pytest.mark.asyncio + async def test_fetch_single_mcp_server_handles_missing_credentials_field(self): + mock_server = generate_mock_mcp_server_db_record( + server_id="server-2", alias="Server 2" + ) + # Simulate ORM object without credentials attribute (e.g., older schema) + delattr(mock_server, "credentials") + + mock_prisma_client = MagicMock() + mock_health_result = { + "status": "healthy", + "last_health_check": datetime.now().isoformat(), + "error": None, + } + + mock_user_auth = generate_mock_user_api_key_auth( + user_role=LitellmUserRoles.PROXY_ADMIN + ) + + with patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.get_prisma_client_or_throw", + return_value=mock_prisma_client, + ), patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.get_mcp_server", + AsyncMock(return_value=mock_server), + ), patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints.global_mcp_server_manager.health_check_server", + AsyncMock(return_value=mock_health_result), + ), patch( + "litellm.proxy.management_endpoints.mcp_management_endpoints._user_has_admin_view", + return_value=True, + ): + from litellm.proxy.management_endpoints.mcp_management_endpoints import ( + fetch_mcp_server, + ) + + result = await fetch_mcp_server( + server_id="server-2", user_api_key_dict=mock_user_auth + ) + + assert result.server_id == "server-2" + # credentials attribute should still be absent and no exception raised + assert not hasattr(result, "credentials") + assert result.status == "healthy" + class TestMCPHealthCheckEndpoints: """Test MCP health check endpoints""" diff --git a/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py b/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py new file mode 100644 index 0000000000..6676720b7a --- /dev/null +++ b/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py @@ -0,0 +1,55 @@ +import os +import sys +from copy import deepcopy + +import pytest + +sys.path.insert(0, os.path.abspath("../../..")) + +import litellm.proxy.public_endpoints.provider_create_metadata as pcm # noqa: E402 +from litellm.proxy.public_endpoints.provider_create_metadata import ( # noqa: E402 + _normalize_field, + get_provider_create_metadata, +) + + +def test_get_provider_create_metadata_includes_openai_fields(): + metadata = get_provider_create_metadata() + + openai_info = next(item for item in metadata if item.provider == "OpenAI") + + assert openai_info.provider_display_name == "OpenAI" + assert openai_info.litellm_provider == "openai" + keys = {field.key for field in openai_info.credential_fields} + assert {"api_base", "api_key"}.issubset(keys) + + +def test_get_provider_create_metadata_returns_sorted_display_names(): + metadata = get_provider_create_metadata() + display_names = [item.provider_display_name for item in metadata] + + assert display_names == sorted(display_names, key=str.lower) + + +def test_get_provider_create_metadata_uses_fallback_fields(monkeypatch): + overridden_fields = deepcopy(pcm.PROVIDER_CREDENTIAL_FIELDS) + overridden_fields.pop("Azure", None) + monkeypatch.setattr(pcm, "PROVIDER_CREDENTIAL_FIELDS", overridden_fields) + + metadata = get_provider_create_metadata() + azure_info = next(item for item in metadata if item.provider == "Azure") + + fallback_keys = [field.key for field in azure_info.credential_fields] + assert fallback_keys == ["api_base", "api_key"] + assert all(field.required is False for field in azure_info.credential_fields) + + +def test_normalize_field_applies_defaults(): + normalized = _normalize_field({"key": "api_key", "label": "API Key"}) + + assert normalized.key == "api_key" + assert normalized.label == "API Key" + assert normalized.field_type == "text" + assert normalized.required is False + assert normalized.placeholder is None + assert normalized.options is None diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 89f9dd0987..8456cf5538 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -23,3 +23,44 @@ def test_get_supported_providers_returns_enum_values(): expected_providers = sorted(provider.value for provider in LlmProviders) assert response.json() == expected_providers + +def test_get_provider_fields_returns_metadata(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + response = client.get("/public/providers/fields") + + assert response.status_code == 200 + payload = response.json() + assert isinstance(payload, list) + + provider_lookup = {item["provider"]: item for item in payload} + assert "OpenAI" in provider_lookup + + openai_fields = provider_lookup["OpenAI"] + assert openai_fields["provider_display_name"] == "OpenAI" + assert openai_fields["litellm_provider"] == "openai" + + credential_keys = {field["key"] for field in openai_fields["credential_fields"]} + assert {"api_base", "api_key"}.issubset(credential_keys) + + # Every provider exposed by `/public/providers` (i.e. every LlmProviders value) + # should have a corresponding entry in `/public/providers/fields`. + expected_litellm_providers = {provider.value for provider in LlmProviders} + actual_litellm_providers = {item["litellm_provider"] for item in payload} + assert expected_litellm_providers.issubset(actual_litellm_providers) + + # Sanity check for runwayml specifically – it should be present and use the + # default API base + API key credential fields at minimum. + runway_entries = [ + item for item in payload if item["litellm_provider"] == "runwayml" + ] + assert ( + len(runway_entries) >= 1 + ), "Expected runwayml provider metadata in /public/providers/fields" + runway_credential_keys = { + field["key"] for field in runway_entries[0]["credential_fields"] + } + assert {"api_base", "api_key"}.issubset(runway_credential_keys) + diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 3a9229b3c4..7acca1804f 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -628,6 +628,59 @@ async def test_ui_view_spend_logs_pagination(client, monkeypatch): assert data["page"] == 2 +@pytest.mark.asyncio +async def test_ui_view_session_spend_logs_pagination(client, monkeypatch): + mock_spend_logs = [ + { + "id": "log1", + "request_id": "req1", + "session_id": "session-123", + "startTime": "2024-01-01T00:00:00Z", + }, + { + "id": "log2", + "request_id": "req2", + "session_id": "session-123", + "startTime": "2024-01-02T00:00:00Z", + }, + ] + + class MockDB: + async def count(self, *args, **kwargs): + assert kwargs.get("where") == {"session_id": "session-123"} + return len(mock_spend_logs) + + async def find_many(self, *args, **kwargs): + assert kwargs.get("where") == {"session_id": "session-123"} + assert kwargs.get("order") == {"startTime": "asc"} + assert kwargs.get("skip") == 1 # page=2, page_size=1 + assert kwargs.get("take") == 1 + return [mock_spend_logs[1]] + + class MockPrismaClient: + def __init__(self): + self.db = MockDB() + self.db.litellm_spendlogs = self.db + + mock_prisma_client = MockPrismaClient() + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) + + response = client.get( + "/spend/logs/session/ui", + params={"session_id": "session-123", "page": 2, "page_size": 1}, + headers={"Authorization": "Bearer sk-test"}, + ) + + assert response.status_code == 200 + data = response.json() + assert data["total"] == 2 + assert data["page"] == 2 + assert data["page_size"] == 1 + assert data["total_pages"] == 2 + assert len(data["data"]) == 1 + assert data["data"][0]["request_id"] == "req2" + + @pytest.mark.asyncio async def test_ui_view_spend_logs_date_range_filter(client, monkeypatch): # Create mock data with different dates diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index e78a9689e8..865dc1b19a 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -325,13 +325,20 @@ def test_embedding_input_array_of_tokens(mock_aembedding, client_no_auth): response = client_no_auth.post("/v1/embeddings", json=test_data) - mock_aembedding.assert_called_once_with( - model="vllm_embed_model", - input=[[2046, 13269, 158208]], - metadata=mock.ANY, - proxy_server_request=mock.ANY, - secret_fields=mock.ANY, - ) + # DEPRECATED - mock_aembedding.assert_called_once_with is too strict, and will fail when new kwargs are added to embeddings + # mock_aembedding.assert_called_once_with( + # model="vllm_embed_model", + # input=[[2046, 13269, 158208]], + # metadata=mock.ANY, + # proxy_server_request=mock.ANY, + # secret_fields=mock.ANY, + # ) + # Assert that aembedding was called, and that input was not modified + mock_aembedding.assert_called_once() + call_args, call_kwargs = mock_aembedding.call_args + assert call_kwargs["model"] == "vllm_embed_model" + assert call_kwargs["input"] == [[2046, 13269, 158208]] + assert response.status_code == 200 result = response.json() print(len(result["data"][0]["embedding"])) diff --git a/tests/test_litellm/test_responses_id_security.py b/tests/test_litellm/test_responses_id_security.py index 16e57f73cf..e72a09ee0d 100644 --- a/tests/test_litellm/test_responses_id_security.py +++ b/tests/test_litellm/test_responses_id_security.py @@ -115,14 +115,17 @@ class TestEncryptResponseId: "litellm.proxy.hooks.responses_id_security.encrypt_value_helper" ) as mock_encrypt: mock_encrypt.return_value = "encrypted_base64_value" + + with patch.object( + responses_id_security, "_get_signing_key", return_value="test-key" + ): + result = responses_id_security._encrypt_response_id( + mock_response, mock_user_api_key_dict + ) - result = responses_id_security._encrypt_response_id( - mock_response, mock_user_api_key_dict - ) - - assert result.id == "resp_encrypted_base64_value" - assert result.id.startswith("resp_") - mock_encrypt.assert_called_once() + assert result.id == "resp_encrypted_base64_value" + assert result.id.startswith("resp_") + mock_encrypt.assert_called_once() def test_encrypt_response_id_maintains_prefix( self, responses_id_security, mock_user_api_key_dict @@ -136,12 +139,15 @@ class TestEncryptResponseId: "litellm.proxy.hooks.responses_id_security.encrypt_value_helper" ) as mock_encrypt: mock_encrypt.return_value = "encrypted_value_456" + + with patch.object( + responses_id_security, "_get_signing_key", return_value="test-key" + ): + result = responses_id_security._encrypt_response_id( + mock_response, mock_user_api_key_dict + ) - result = responses_id_security._encrypt_response_id( - mock_response, mock_user_api_key_dict - ) - - assert result.id.startswith("resp_") + assert result.id.startswith("resp_") class TestCheckUserAccessToResponseId: diff --git a/ui/litellm-dashboard/public/assets/logos/runway.png b/ui/litellm-dashboard/public/assets/logos/runway.png new file mode 100644 index 0000000000..c909cb9e0f Binary files /dev/null and b/ui/litellm-dashboard/public/assets/logos/runway.png differ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx index 6e972a97e7..4533d99b4a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx @@ -1,14 +1,4 @@ -import { - Button, - Icon, - Table, - TableBody, - TableCell, - TableHead, - TableHeaderCell, - TableRow, - Text, -} from "@tremor/react"; +import { Button, Icon, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Text } from "@tremor/react"; import { Tooltip } from "antd"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline"; diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index 56c73e8691..df56ab5a5e 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -755,6 +755,7 @@ const Teams: React.FC = ({ Models Organization Info + Actions @@ -937,20 +938,28 @@ const Teams: React.FC = ({ {userRole == "Admin" ? ( <> - { - setSelectedTeamId(team.team_id); - setEditTeam(true); - }} - /> - handleDelete(team.team_id)} - icon={TrashIcon} - size="sm" - data-testid="delete-team-button" - /> + + {" "} + { + setSelectedTeamId(team.team_id); + setEditTeam(true); + }} + /> + + + {" "} + handleDelete(team.team_id)} + icon={TrashIcon} + size="sm" + className="cursor-pointer hover:text-red-600" + data-testid="delete-team-button" + /> + ) : null} diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx index a74c6d283c..a3937c2f2f 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_tab.test.tsx @@ -19,6 +19,15 @@ vi.mock("../networking", async () => { modelAvailableCall: vi.fn().mockResolvedValue({ data: [{ id: "model-group-1" }, { id: "model-group-2" }], }), + getProviderCreateMetadata: vi.fn().mockResolvedValue([ + { + provider: "OpenAI", + provider_display_name: "OpenAI", + litellm_provider: "openai", + default_model_placeholder: "gpt-3.5-turbo", + credential_fields: [], + }, + ]), }; }); diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx index 678ba741ca..4efcd7be90 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_tab.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd"; import type { FormInstance } from "antd"; import type { UploadProps } from "antd/es/upload"; @@ -9,7 +9,14 @@ import ProviderSpecificFields from "./provider_specific_fields"; import AdvancedSettings from "./advanced_settings"; import { Providers, providerLogoMap } from "../provider_info_helpers"; import type { Team } from "../key_team_helpers/key_list"; -import { CredentialItem, getGuardrailsList, modelAvailableCall, tagListCall } from "../networking"; +import { + type CredentialItem, + type ProviderCreateInfo, + getGuardrailsList, + getProviderCreateMetadata, + modelAvailableCall, + tagListCall, +} from "../networking"; import ConnectionErrorDisplay from "./model_connection_test"; import { TEST_MODES } from "./add_model_modes"; import { Row, Col } from "antd"; @@ -68,6 +75,11 @@ const AddModelTab: React.FC = ({ // Using a unique ID to force the ConnectionErrorDisplay to remount and run a fresh test const [connectionTestId, setConnectionTestId] = useState(""); + // Provider metadata for driving the provider select from backend config + const [providerMetadata, setProviderMetadata] = useState(null); + const [isProviderMetadataLoading, setIsProviderMetadataLoading] = useState(false); + const [providerMetadataError, setProviderMetadataError] = useState(null); + useEffect(() => { const fetchGuardrails = async () => { try { @@ -95,6 +107,37 @@ const AddModelTab: React.FC = ({ fetchTags(); }, [accessToken]); + useEffect(() => { + let isMounted = true; + + const fetchProviderMetadata = async () => { + setIsProviderMetadataLoading(true); + setProviderMetadataError(null); + try { + const metadata = await getProviderCreateMetadata(); + if (!isMounted) { + return; + } + setProviderMetadata(metadata); + } catch (error) { + console.error("Failed to fetch provider metadata:", error); + if (isMounted) { + setProviderMetadataError("Failed to load providers"); + } + } finally { + if (isMounted) { + setIsProviderMetadataLoading(false); + } + } + }; + + fetchProviderMetadata(); + + return () => { + isMounted = false; + }; + }, []); + // Test connection when button is clicked const handleTestConnection = async () => { setIsTestingConnection(true); @@ -118,6 +161,13 @@ const AddModelTab: React.FC = ({ fetchModelAccessGroups(); }, [accessToken]); + const sortedProviderMetadata: ProviderCreateInfo[] = useMemo(() => { + if (!providerMetadata) { + return []; + } + return [...providerMetadata].sort((a, b) => a.provider_display_name.localeCompare(b.provider_display_name)); + }, [providerMetadata]); + const isAdmin = all_admin_roles.includes(userRole); const handleAutoRouterOk = () => { @@ -166,41 +216,68 @@ const AddModelTab: React.FC = ({ labelAlign="left" > { - setSelectedProvider(value); - setProviderModelsFn(value); + setSelectedProvider(value as Providers); + setProviderModelsFn(value as Providers); + form.setFieldsValue({ + custom_llm_provider: value, + }); form.setFieldsValue({ model: [], model_name: undefined, }); }} > - {Object.entries(Providers).map(([providerEnum, providerDisplayName]) => ( - -
- {`${providerEnum} { - // Create a div with provider initial as fallback - const target = e.target as HTMLImageElement; - const parent = target.parentElement; - if (parent) { - const fallbackDiv = document.createElement("div"); - fallbackDiv.className = - "w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs"; - fallbackDiv.textContent = providerDisplayName.charAt(0); - parent.replaceChild(fallbackDiv, target); - } - }} - /> - {providerDisplayName} -
+ {providerMetadataError && sortedProviderMetadata.length === 0 && ( + + {providerMetadataError} - ))} + )} + {sortedProviderMetadata.map((providerInfo) => { + const displayName = providerInfo.provider_display_name; + const providerKey = providerInfo.provider; + const logoSrc = providerLogoMap[displayName] ?? ""; + + return ( + +
+ {logoSrc ? ( + {`${displayName} { + const target = e.currentTarget as HTMLImageElement; + const parent = target.parentElement; + if (!parent || !parent.contains(target)) { + return; + } + + try { + const fallbackDiv = document.createElement("div"); + fallbackDiv.className = + "w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs"; + fallbackDiv.textContent = displayName.charAt(0); + parent.replaceChild(fallbackDiv, target); + } catch (error) { + console.error("Failed to replace provider logo fallback:", error); + } + }} + /> + ) : ( +
+ {displayName.charAt(0)} +
+ )} + {displayName} +
+
+ ); + })}
({ + default: { + fromBackend: vi.fn(), + }, +})); + +describe("prepareModelAddRequest", () => { + it("returns deployment data for the most basic form", async () => { + const formValues = { + model_mappings: [ + { + public_name: "Public Model", + litellm_model: "litellm/public", + }, + ], + model_name: "custom-model-name", + base_model: "gpt-4", + team_id: "team-123", + model_access_group: ["group-1"], + input_cost_per_token: "2000000", + output_cost_per_token: "1000000", + }; + + const deployments = await prepareModelAddRequest({ ...formValues }, "token", null); + + expect(deployments).toHaveLength(1); + const [deployment] = deployments!; + expect(deployment.modelName).toBe("Public Model"); + expect(deployment.litellmParamsObj.model).toBe("custom-model-name"); + expect(deployment.litellmParamsObj.input_cost_per_token).toBe(2); + expect(deployment.litellmParamsObj.output_cost_per_token).toBe(1); + expect(deployment.modelInfoObj.base_model).toBe("gpt-4"); + expect(deployment.modelInfoObj.access_groups).toEqual(["group-1"]); + expect(deployment.modelInfoObj.team_id).toBe("team-123"); + }); + + it("uses a lowercase fallback for unrecognized custom providers", async () => { + const fallbackValues = { + model_mappings: [ + { + public_name: "Petals Model", + litellm_model: "petals/model", + }, + ], + model_name: "petals/model", + custom_llm_provider: "Petals", + }; + + const deployments = await prepareModelAddRequest({ ...fallbackValues }, "token", null); + + expect(deployments).toHaveLength(1); + const [deployment] = deployments!; + expect(deployment.litellmParamsObj.custom_llm_provider).toBe("petals"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx b/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx index e41b114c77..8fa5ffd56a 100644 --- a/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx +++ b/ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx @@ -1,6 +1,6 @@ -import { provider_map, Providers } from "../provider_info_helpers"; -import { modelCreateCall, Model } from "../networking"; import NotificationManager from "../molecules/notifications_manager"; +import { Model, modelCreateCall } from "../networking"; +import { provider_map } from "../provider_info_helpers"; export const prepareModelAddRequest = async (formValues: Record, accessToken: string, form: any) => { try { @@ -14,8 +14,10 @@ export const prepareModelAddRequest = async (formValues: Record, ac // Handle wildcard case if (formValues["model"] && formValues["model"].includes("all-wildcard")) { - const customProvider: Providers = formValues["custom_llm_provider"]; - const litellm_custom_provider = provider_map[customProvider as keyof typeof Providers]; + const customProviderKey = formValues["custom_llm_provider"] as string; + const mappedProvider = + provider_map[customProviderKey as keyof typeof provider_map] ?? customProviderKey.toLowerCase(); + const litellm_custom_provider = mappedProvider; const wildcardModel = litellm_custom_provider + "/*"; formValues["model_name"] = wildcardModel; modelMappings.push({ @@ -59,7 +61,8 @@ export const prepareModelAddRequest = async (formValues: Record, ac litellmParamsObj["model"] = value; } else if (key == "custom_llm_provider") { console.log("custom_llm_provider:", value); - const mappingResult = provider_map[value]; // Get the corresponding value from the mapping + const providerKey = value as string; + const mappingResult = provider_map[providerKey as keyof typeof provider_map] ?? providerKey.toLowerCase(); litellmParamsObj["custom_llm_provider"] = mappingResult; console.log("custom_llm_provider mappingResult:", mappingResult); } else if (key == "model") { diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx index 454caf9e60..ab9b3e92e9 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx @@ -1,9 +1,102 @@ import { render, waitFor } from "@testing-library/react"; -import { describe, it, expect, beforeAll } from "vitest"; +import { describe, it, expect, beforeAll, vi } from "vitest"; import { Form } from "antd"; import { Providers } from "../provider_info_helpers"; import ProviderSpecificFields from "./provider_specific_fields"; +vi.mock("../networking", async () => { + const actual = await vi.importActual("../networking"); + return { + ...actual, + getProviderCreateMetadata: vi.fn().mockResolvedValue([ + { + provider: "OpenAI", + provider_display_name: Providers.OpenAI, + litellm_provider: "openai", + default_model_placeholder: "gpt-3.5-turbo", + credential_fields: [ + { + key: "api_base", + label: "API Base", + field_type: "text", + placeholder: "https://api.openai.com/v1", + tooltip: + "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", + default_value: "https://api.openai.com/v1", + }, + { + key: "organization", + label: "OpenAI Organization ID", + placeholder: "[OPTIONAL] my-unique-org", + }, + { + key: "api_key", + label: "OpenAI API Key", + field_type: "password", + required: true, + }, + ], + }, + { + provider: "Hosted_Vllm", + provider_display_name: Providers.Hosted_Vllm, + litellm_provider: "hosted_vllm", + default_model_placeholder: "vllm/any-model", + credential_fields: [ + { + key: "api_base", + label: "API Base", + placeholder: "https://...", + }, + { + key: "api_key", + label: "vLLM API Key", + field_type: "password", + }, + ], + }, + { + provider: "Azure", + provider_display_name: Providers.Azure, + litellm_provider: "azure", + default_model_placeholder: "azure/my-deployment", + credential_fields: [ + { + key: "api_base", + label: "API Base", + placeholder: "https://...", + required: true, + }, + { + key: "api_version", + label: "API Version", + placeholder: "2023-07-01-preview", + tooltip: + "By default litellm will use the latest version. If you want to use a different version, you can specify it here", + }, + { + key: "base_model", + label: "Base Model", + placeholder: "azure/gpt-3.5-turbo", + }, + { + key: "api_key", + label: "Azure API Key", + field_type: "password", + placeholder: "Enter your Azure API Key", + }, + { + key: "azure_ad_token", + label: "Azure AD Token", + field_type: "password", + placeholder: "Enter your Azure AD Token", + }, + ], + }, + ]), + }; +}); + // Mock window.matchMedia for Ant Design components beforeAll(() => { Object.defineProperty(window, "matchMedia", { diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx index 2f29461140..c1b17ff441 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx @@ -4,7 +4,12 @@ import { TextInput, Text } from "@tremor/react"; import { Row, Col, Typography, Button as Button2, Upload, UploadProps } from "antd"; import { UploadOutlined } from "@ant-design/icons"; import { provider_map, Providers } from "../provider_info_helpers"; -import { CredentialItem } from "../networking"; +import { + CredentialItem, + ProviderCreateInfo, + ProviderCredentialFieldMetadata, + getProviderCreateMetadata, +} from "../networking"; const { Link } = Typography; interface ProviderSpecificFieldsProps { @@ -28,6 +33,33 @@ export interface CredentialValues { value: string; } +const mapFieldMetadataToUiField = (field: ProviderCredentialFieldMetadata): ProviderCredentialField => { + const type: ProviderCredentialField["type"] = + field.field_type === "password" + ? "password" + : field.field_type === "select" + ? "select" + : field.field_type === "upload" + ? "upload" + : "text"; + + return { + key: field.key, + label: field.label, + placeholder: field.placeholder ?? undefined, + tooltip: field.tooltip ?? undefined, + required: field.required ?? false, + type, + options: field.options ?? undefined, + defaultValue: field.default_value ?? undefined, + }; +}; + +// In-memory cache of provider credential fields keyed by provider display name. +// This lets us reuse the data across multiple mounts and also supports +// non-React helpers like createCredentialFromModel. +const providerFieldsByDisplayName: Record = {}; + export const createCredentialFromModel = (provider: string, modelData: any): CredentialItem => { console.log("provider", provider); console.log("modelData", modelData); @@ -35,8 +67,8 @@ export const createCredentialFromModel = (provider: string, modelData: any): Cre if (!enumKey) { throw new Error(`Provider ${provider} not found in provider_map`); } - const providerEnum = Providers[enumKey as keyof typeof Providers]; - const providerFields = PROVIDER_CREDENTIAL_FIELDS[providerEnum] || []; + const providerDisplayName = Providers[enumKey as keyof typeof Providers]; + const providerFields = providerFieldsByDisplayName[providerDisplayName] || []; const credentialValues: object = {}; console.log("providerFields", providerFields); @@ -63,544 +95,103 @@ export const createCredentialFromModel = (provider: string, modelData: any): Cre return credential; }; -const PROVIDER_CREDENTIAL_FIELDS: Record = { - [Providers.OpenAI]: [ - { - key: "api_base", - label: "API Base", - type: "text", - placeholder: "https://api.openai.com/v1", - tooltip: "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", - defaultValue: "https://api.openai.com/v1", - }, - { - key: "organization", - label: "OpenAI Organization ID", - placeholder: "[OPTIONAL] my-unique-org", - }, - { - key: "api_key", - label: "OpenAI API Key", - type: "password", - required: true, - }, - ], - [Providers.OpenAI_Text]: [ - { - key: "api_base", - label: "API Base", - type: "text", - placeholder: "https://api.openai.com/v1", - tooltip: "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", - defaultValue: "https://api.openai.com/v1", - }, - { - key: "organization", - label: "OpenAI Organization ID", - placeholder: "[OPTIONAL] my-unique-org", - }, - { - key: "api_key", - label: "OpenAI API Key", - type: "password", - required: true, - }, - ], - [Providers.Vertex_AI]: [ - { - key: "vertex_project", - label: "Vertex Project", - placeholder: "adroit-cadet-1234..", - required: true, - }, - { - key: "vertex_location", - label: "Vertex Location", - placeholder: "us-east-1", - required: true, - }, - { - key: "vertex_credentials", - label: "Vertex Credentials", - required: true, - type: "upload", - }, - ], - [Providers.AssemblyAI]: [ - { - key: "api_base", - label: "API Base", - type: "select", - required: true, - options: ["https://api.assemblyai.com", "https://api.eu.assemblyai.com"], - }, - { - key: "api_key", - label: "AssemblyAI API Key", - type: "password", - required: true, - }, - ], - [Providers.Azure]: [ - { - key: "api_base", - label: "API Base", - placeholder: "https://...", - required: true, - }, - { - key: "api_version", - label: "API Version", - placeholder: "2023-07-01-preview", - tooltip: - "By default litellm will use the latest version. If you want to use a different version, you can specify it here", - }, - { - key: "base_model", - label: "Base Model", - placeholder: "azure/gpt-3.5-turbo", - }, - { - key: "api_key", - label: "Azure API Key", - type: "password", - placeholder: "Enter your Azure API Key", - required: false, - }, - { - key: "azure_ad_token", - label: "Azure AD Token", - type: "password", - placeholder: "Enter your Azure AD Token", - required: false, - }, - ], - [Providers.Azure_AI_Studio]: [ - { - key: "api_base", - label: "API Base", - placeholder: "https://.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", - tooltip: - "Enter your full Target URI from Azure Foundry here. Example: https://litellm8397336933.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", - required: true, - }, - { - key: "api_key", - label: "Azure API Key", - type: "password", - required: true, - }, - ], - [Providers.OpenAI_Compatible]: [ - { - key: "api_base", - label: "API Base", - placeholder: "https://...", - required: true, - }, - { - key: "api_key", - label: "OpenAI API Key", - type: "password", - required: true, - }, - ], - [Providers.Dashscope]: [ - { - key: "api_key", - label: "Dashscope API Key", - type: "password", - required: true, - }, - { - key: "api_base", - label: "API Base", - placeholder: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - defaultValue: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - required: true, - tooltip: - "The base URL for your Dashscope server. Defaults to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 if not specified.", - }, - ], - [Providers.OpenAI_Text_Compatible]: [ - { - key: "api_base", - label: "API Base", - placeholder: "https://...", - required: true, - }, - { - key: "api_key", - label: "OpenAI API Key", - type: "password", - required: true, - }, - ], - [Providers.Bedrock]: [ - { - key: "aws_access_key_id", - label: "AWS Access Key ID", - type: "password", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - { - key: "aws_secret_access_key", - label: "AWS Secret Access Key", - type: "password", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - { - key: "aws_session_token", - label: "AWS Session Token", - type: "password", - required: false, - tooltip: - "Temporary credentials session token. You can provide the raw token or the environment variable (e.g. `os.environ/MY_SESSION_TOKEN`).", - }, - { - key: "aws_region_name", - label: "AWS Region Name", - placeholder: "us-east-1", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - { - key: "aws_session_name", - label: "AWS Session Name", - placeholder: "my-session", - required: false, - tooltip: - "Name for the AWS session. You can provide the raw value or the environment variable (e.g. `os.environ/MY_SESSION_NAME`).", - }, - { - key: "aws_profile_name", - label: "AWS Profile Name", - placeholder: "default", - required: false, - tooltip: - "AWS profile name to use for authentication. You can provide the raw value or the environment variable (e.g. `os.environ/MY_PROFILE_NAME`).", - }, - { - key: "aws_role_name", - label: "AWS Role Name", - placeholder: "MyRole", - required: false, - tooltip: - "AWS IAM role name to assume. You can provide the raw value or the environment variable (e.g. `os.environ/MY_ROLE_NAME`).", - }, - { - key: "aws_web_identity_token", - label: "AWS Web Identity Token", - type: "password", - required: false, - tooltip: - "Web identity token for OIDC authentication. You can provide the raw token or the environment variable (e.g. `os.environ/MY_WEB_IDENTITY_TOKEN`).", - }, - { - key: "aws_bedrock_runtime_endpoint", - label: "AWS Bedrock Runtime Endpoint", - placeholder: "https://bedrock-runtime.us-east-1.amazonaws.com", - required: false, - tooltip: - "Custom Bedrock runtime endpoint URL. You can provide the raw value or the environment variable (e.g. `os.environ/MY_BEDROCK_ENDPOINT`).", - }, - ], - [Providers.SageMaker]: [ - { - key: "aws_access_key_id", - label: "AWS Access Key ID", - type: "password", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - { - key: "aws_secret_access_key", - label: "AWS Secret Access Key", - type: "password", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - { - key: "aws_region_name", - label: "AWS Region Name", - placeholder: "us-east-1", - required: false, - tooltip: "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", - }, - ], - [Providers.Ollama]: [ - { - key: "api_base", - label: "API Base", - placeholder: "http://localhost:11434", - defaultValue: "http://localhost:11434", - required: false, - tooltip: "The base URL for your Ollama server. Defaults to http://localhost:11434 if not specified.", - }, - ], - [Providers.Anthropic]: [ - { - key: "api_key", - label: "API Key", - placeholder: "sk-", - type: "password", - required: true, - }, - ], - [Providers.Deepgram]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.ElevenLabs]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Google_AI_Studio]: [ - { - key: "api_key", - label: "API Key", - placeholder: "aig-", - type: "password", - required: true, - }, - ], - [Providers.Groq]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.MistralAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Deepseek]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Cohere]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Databricks]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.xAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.AIML]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Cerebras]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Sambanova]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Perplexity]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.TogetherAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Openrouter]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.FireworksAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.GradientAI]: [ - { - key: "api_base", - label: "GradientAI Endpoint", - placeholder: "https://...", - required: false, - }, - { - key: "api_key", - label: "GradientAI API Key", - type: "password", - required: true, - }, - ], - [Providers.Triton]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: false, - }, - { - key: "api_base", - label: "API Base", - placeholder: "http://localhost:8000/generate", - required: false, - }, - ], - [Providers.Hosted_Vllm]: [ - { - key: "api_base", - label: "API Base", - placeholder: "https://...", - required: true, - }, - { - key: "api_key", - label: "vLLM API Key", - type: "password", - required: false, - }, - ], - [Providers.Voyage]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.JinaAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.VolcEngine]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.DeepInfra]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Oracle]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], - [Providers.Snowflake]: [ - { - key: "api_key", - label: "Snowflake API Key / JWT Key for Authentication", - type: "password", - required: true, - }, - { - key: "api_base", - label: "Snowflake API Endpoint", - placeholder: "https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", - tooltip: - "Enter the full endpoint with path here. Example: https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", - required: true, - }, - ], - [Providers.Infinity]: [ - { - key: "api_base", - label: "API Base", - placeholder: "http://localhost:7997", - }, - ], - [Providers.FalAI]: [ - { - key: "api_key", - label: "API Key", - type: "password", - required: true, - }, - ], -}; - const ProviderSpecificFields: React.FC = ({ selectedProvider, uploadProps }) => { const selectedProviderEnum = Providers[selectedProvider as keyof typeof Providers] as Providers; const form = Form.useFormInstance(); // Get form instance from context - // Simply use the fields as defined in PROVIDER_CREDENTIAL_FIELDS + const [providerMetadata, setProviderMetadata] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + const [loadError, setLoadError] = React.useState(null); + + React.useEffect(() => { + const hasCachedFields = Object.keys(providerFieldsByDisplayName).length > 0; + if (hasCachedFields) { + // We already have fields cached globally; no need to refetch. + // This is important so we can reuse credential field definitions + // across mounts and in non-React helpers. + return; + } + + let isMounted = true; + + const fetchProviderFields = async () => { + setIsLoading(true); + setLoadError(null); + try { + const metadata = await getProviderCreateMetadata(); + if (!isMounted) { + return; + } + setProviderMetadata(metadata); + + // Populate cache keyed by provider display name and identifiers + metadata.forEach((providerInfo) => { + const displayName = providerInfo.provider_display_name; + const mappedFields = providerInfo.credential_fields.map(mapFieldMetadataToUiField); + + // Primary key: human-readable display name + providerFieldsByDisplayName[displayName] = mappedFields; + + // Also cache by backend identifiers so lookups by provider slug work + if (providerInfo.provider) { + providerFieldsByDisplayName[providerInfo.provider] = mappedFields; + } + if (providerInfo.litellm_provider) { + providerFieldsByDisplayName[providerInfo.litellm_provider] = mappedFields; + } + }); + } catch (error) { + console.error("Failed to load provider credential fields:", error); + if (isMounted) { + setLoadError("Failed to load provider credential fields"); + } + } finally { + if (isMounted) { + setIsLoading(false); + } + } + }; + + fetchProviderFields(); + + return () => { + isMounted = false; + }; + }, []); + const allFields = React.useMemo(() => { - return PROVIDER_CREDENTIAL_FIELDS[selectedProviderEnum] || []; - }, [selectedProviderEnum]); + // First try to resolve from the in-memory cache. We support both the + // enum/display-name form and the raw provider slug (e.g. "petals"). + const cachedFields = + providerFieldsByDisplayName[selectedProviderEnum] ?? providerFieldsByDisplayName[selectedProvider]; + if (cachedFields) { + return cachedFields; + } + + if (!providerMetadata) { + return []; + } + + const providerInfo = providerMetadata.find( + (p) => + p.provider_display_name === selectedProviderEnum || + p.provider === selectedProvider || + p.litellm_provider === selectedProvider, + ); + if (!providerInfo) { + return []; + } + + const mapped = providerInfo.credential_fields.map(mapFieldMetadataToUiField); + providerFieldsByDisplayName[providerInfo.provider_display_name] = mapped; + if (providerInfo.provider) { + providerFieldsByDisplayName[providerInfo.provider] = mapped; + } + if (providerInfo.litellm_provider) { + providerFieldsByDisplayName[providerInfo.litellm_provider] = mapped; + } + return mapped; + }, [selectedProviderEnum, selectedProvider, providerMetadata]); const handleUpload = { name: "file", @@ -633,6 +224,20 @@ const ProviderSpecificFields: React.FC = ({ selecte return ( <> + {isLoading && allFields.length === 0 && ( + + + Loading provider fields... + + + )} + {loadError && allFields.length === 0 && ( + + + {loadError} + + + )} {allFields.map((field) => ( = ({ }, { id: "actions", - header: "", + header: "Actions", cell: ({ row }) => { const guardrail = row.original; const isConfigGuardrail = guardrail.guardrail_definition_location === GuardrailDefinitionLocation.CONFIG; @@ -177,16 +177,17 @@ const GuardrailTable: React.FC = ({ /> ) : ( - - guardrail.guardrail_id && - onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail") - } - className="cursor-pointer hover:text-red-500" - tooltip="Delete guardrail" - /> + + + guardrail.guardrail_id && + onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail") + } + className="cursor-pointer hover:text-red-500" + /> + )} ); diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx new file mode 100644 index 0000000000..8e356baa12 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_add/credentials.test.tsx @@ -0,0 +1,50 @@ +import { CredentialItem } from "@/components/networking"; +import { render, waitFor } from "@testing-library/react"; +import { UploadProps } from "antd/es/upload"; +import { describe, expect, it, vi } from "vitest"; +import CredentialsPanel from "./credentials"; + +const DEFAULT_UPLOAD_PROPS = {} as UploadProps; + +describe("CredentialsPanel", () => { + it("renders without crashing and fetches credentials when token exists", async () => { + const fetchCredentials = vi.fn(() => Promise.resolve()); + + const { getByRole, getByText } = render( + , + ); + + await waitFor(() => { + expect(getByRole("button", { name: /add credential/i })).toBeInTheDocument(); + expect(getByText("Credential Name")).toBeInTheDocument(); + expect(getByText("Provider")).toBeInTheDocument(); + }); + }); + + it("displays provided credentials and still calls the fetch helper", async () => { + const fetchCredentials = vi.fn(() => Promise.resolve()); + const credentials: CredentialItem[] = [ + { + credential_name: "openai-key", + credential_values: {}, + credential_info: { custom_llm_provider: "openai" }, + }, + ]; + + const { getByText } = render( + , + ); + + await waitFor(() => expect(getByText("openai-key")).toBeInTheDocument()); + }); +}); diff --git a/ui/litellm-dashboard/src/components/model_add/credentials.tsx b/ui/litellm-dashboard/src/components/model_add/credentials.tsx index 1368796a67..e36a759294 100644 --- a/ui/litellm-dashboard/src/components/model_add/credentials.tsx +++ b/ui/litellm-dashboard/src/components/model_add/credentials.tsx @@ -143,7 +143,6 @@ const CredentialsPanel: React.FC = ({ Credential Name Provider - Description @@ -160,7 +159,6 @@ const CredentialsPanel: React.FC = ({ {renderProviderBadge((credential.credential_info?.custom_llm_provider as string) || "-")} - {credential.credential_info?.description || "-"} + setIsEditing(false)}> + Cancel + Save Changes diff --git a/ui/litellm-dashboard/src/components/organizations.test.tsx b/ui/litellm-dashboard/src/components/organizations.test.tsx new file mode 100644 index 0000000000..58eb9bb492 --- /dev/null +++ b/ui/litellm-dashboard/src/components/organizations.test.tsx @@ -0,0 +1,33 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import React from "react"; + +vi.mock("./vector_store_management/VectorStoreSelector", () => ({ + __esModule: true, + default: () => null, +})); +vi.mock("./mcp_server_management/MCPServerSelector", () => ({ + __esModule: true, + default: () => null, +})); + +import OrganizationsTable from "./organizations"; + +describe("OrganizationsTable", () => { + it("should render the OrganizationsTable component", () => { + const setOrganizations = vi.fn(); + + const { getByText } = render( + , + ); + + expect(getByText("+ Create New Organization")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/organizations.tsx b/ui/litellm-dashboard/src/components/organizations.tsx index 854073d847..71e9030a24 100644 --- a/ui/litellm-dashboard/src/components/organizations.tsx +++ b/ui/litellm-dashboard/src/components/organizations.tsx @@ -370,19 +370,27 @@ const OrganizationsTable: React.FC = ({ {userRole === "Admin" && ( <> - { - setSelectedOrgId(org.organization_id); - setEditOrg(true); - }} - /> - handleDelete(org.organization_id)} - icon={TrashIcon} - size="sm" - /> + + {" "} + { + setSelectedOrgId(org.organization_id); + setEditOrg(true); + }} + /> + + + {" "} + handleDelete(org.organization_id)} + icon={TrashIcon} + size="sm" + className="cursor-pointer hover:text-red-600" + /> + )} diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index fb6162f304..f3b7357ffa 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -31,6 +31,7 @@ export enum Providers { Openrouter = "Openrouter", Oracle = "Oracle Cloud Infrastructure (OCI)", Perplexity = "Perplexity", + RunwayML = "RunwayML", Sambanova = "Sambanova", Snowflake = "Snowflake", TogetherAI = "TogetherAI", @@ -65,6 +66,7 @@ export const provider_map: Record = { Cerebras: "cerebras", Sambanova: "sambanova", Perplexity: "perplexity", + RunwayML: "runwayml", TogetherAI: "together_ai", Openrouter: "openrouter", Oracle: "oci", @@ -113,6 +115,7 @@ export const providerLogoMap: Record = { [Providers.Openrouter]: `${asset_logos_folder}openrouter.svg`, [Providers.Oracle]: `${asset_logos_folder}oracle.svg`, [Providers.Perplexity]: `${asset_logos_folder}perplexity-ai.svg`, + [Providers.RunwayML]: `${asset_logos_folder}runwayml.png`, [Providers.Sambanova]: `${asset_logos_folder}sambanova.svg`, [Providers.Snowflake]: `${asset_logos_folder}snowflake.svg`, [Providers.TogetherAI]: `${asset_logos_folder}togetherai.svg`, @@ -188,6 +191,8 @@ export const getPlaceholder = (selectedProvider: string): string => { return "deepinfra/"; } else if (selectedProvider == Providers.FalAI) { return "fal_ai/fal-ai/flux-pro/v1.1-ultra"; + } else if (selectedProvider == Providers.RunwayML) { + return "runwayml/gen4_turbo"; } else { return "gpt-3.5-turbo"; } diff --git a/ui/litellm-dashboard/src/components/public_model_hub.tsx b/ui/litellm-dashboard/src/components/public_model_hub.tsx index b1a0cc9021..c32c7462a5 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.tsx @@ -14,7 +14,6 @@ import { getProviderLogoAndName } from "./provider_info_helpers"; import Navbar from "./navbar"; import { ThemeProvider } from "@/contexts/ThemeContext"; import NotificationsManager from "./molecules/notifications_manager"; -// Simple approach without react-markdown dependency interface ModelGroupInfo { model_group: string; diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx index d90b9e32f7..caa9729d4e 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx @@ -1,6 +1,6 @@ import GuardrailSelector from "@/components/guardrails/GuardrailSelector"; import { TextInput, Button as TremorButton } from "@tremor/react"; -import { Button as AntdButton, Form, Input, Select, Tooltip } from "antd"; +import { Form, Input, Select, Tooltip } from "antd"; import { useEffect, useState } from "react"; import { mapInternalToDisplayNames } from "../callback_info_helpers"; import KeyLifecycleSettings from "../common_components/KeyLifecycleSettings"; @@ -548,7 +548,9 @@ export function KeyEditView({
- Cancel + + Cancel + Save Changes
diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index 411d73a90c..58b8363e30 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -545,9 +545,7 @@ export default function KeyInfoView({
Key Settings {!isEditing && userRole && rolesWithWriteAccess.includes(userRole) && ( - + )}
diff --git a/ui/litellm-dashboard/src/components/view_users/columns.tsx b/ui/litellm-dashboard/src/components/view_users/columns.tsx index 8946aac228..cb6c54d4d5 100644 --- a/ui/litellm-dashboard/src/components/view_users/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_users/columns.tsx @@ -109,17 +109,32 @@ export const columns = ( }, { id: "actions", - header: "", + header: "Actions", cell: ({ row }) => (
- - handleUserClick(row.original.user_id, true)} /> + + handleUserClick(row.original.user_id, true)} + className="cursor-pointer hover:text-blue-600" + /> - - handleDelete(row.original.user_id)} /> + + handleDelete(row.original.user_id)} + className="cursor-pointer hover:text-red-600" + /> - - handleResetPassword(row.original.user_id)} /> + + handleResetPassword(row.original.user_id)} + className="cursor-pointer hover:text-green-600" + />
), diff --git a/ui/litellm-dashboard/src/components/view_users/table.test.tsx b/ui/litellm-dashboard/src/components/view_users/table.test.tsx new file mode 100644 index 0000000000..5b612b2732 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_users/table.test.tsx @@ -0,0 +1,46 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import React from "react"; + +import { UserDataTable } from "./table"; + +describe("UserDataTable", () => { + it("should render the UserDataTable component", () => { + const filters = { + email: "", + user_id: "", + user_role: "", + sso_user_id: "", + team: "", + model: "", + min_spend: null, + max_spend: null, + sort_by: "", + sort_order: "asc" as const, + }; + + const updateFilters = vi.fn(); + + const { getByText } = render( + , + ); + + expect(getByText("Filters")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.test.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.test.tsx new file mode 100644 index 0000000000..7334dd72fc --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.test.tsx @@ -0,0 +1,51 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import UserInfoView from "./user_info_view"; + +vi.mock("../networking", () => { + const MOCK_USER_DATA = { + user_id: "user-123", + user_info: { + user_email: "test@example.com", + user_role: "admin", + teams: [], + models: [], + max_budget: 100, + budget_duration: "30d", + spend: 0, + metadata: {}, + created_at: "2025-01-01T00:00:00.000Z", + updated_at: "2025-01-02T00:00:00.000Z", + }, + keys: [], + teams: [], + }; + + return { + userInfoCall: vi.fn().mockResolvedValue(MOCK_USER_DATA), + userDeleteCall: vi.fn(), + userUpdateUserCall: vi.fn(), + modelAvailableCall: vi.fn().mockResolvedValue({ data: [] }), + invitationCreateCall: vi.fn(), + getProxyBaseUrl: () => "https://litellm.test", + }; +}); + +describe("UserInfoView", () => { + const defaultProps = { + userId: "user-123", + onClose: vi.fn(), + accessToken: "test-token", + userRole: null, + possibleUIRoles: null, + }; + + it("renders loading state and then the user email", async () => { + const { getByText, findAllByText } = render(); + + expect(getByText("Loading user data...")).toBeInTheDocument(); + + const emails = await findAllByText("test@example.com"); + expect(emails.length).toBeGreaterThan(0); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx index ab2c177707..65aa7c9d4c 100644 --- a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx +++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx @@ -343,9 +343,7 @@ export default function UserInfoView({
User Settings {!isEditing && userRole && rolesWithWriteAccess.includes(userRole) && ( - + )}