feat: support Azure OpenAI container API

- Register AzureOpenAIContainerConfig in ProviderConfigManager and proxy handler_factory
- Add AZURE_DEFAULT_CONTAINERS_API_VERSION constant
- Update custom_llm_provider type hints to accept "azure" in containers/main.py
- Update containers and container_files docs with Azure examples
This commit is contained in:
zooneon 2026-03-27 22:41:44 +09:00
parent cec3e9e7d4
commit 29b83cb016
8 changed files with 571 additions and 66 deletions

View file

@ -15,7 +15,7 @@ Looking for how to use Code Interpreter? See the [Code Interpreter Guide](/docs/
|---------|-----------|
| Cost Tracking | ✅ |
| Logging | ✅ |
| Supported Providers | `openai` |
| Supported Providers | `openai`, `azure` |
## Endpoints
@ -27,12 +27,18 @@ Looking for how to use Code Interpreter? See the [Code Interpreter Guide](/docs/
| `/v1/containers/{container_id}/files/{file_id}/content` | GET | Download file content |
| `/v1/containers/{container_id}/files/{file_id}` | DELETE | Delete file |
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## LiteLLM Python SDK
### Upload Container File
Upload files directly to a container session. This is useful when `/chat/completions` or `/responses` sends files to the container but the input file type is limited to PDF. This endpoint lets you work with other file types like CSV, Excel, Python scripts, etc.
<Tabs>
<TabItem value="openai" label="OpenAI">
```python showLineNumbers title="upload_container_file.py"
from litellm import upload_container_file
@ -47,6 +53,29 @@ print(f"Uploaded: {file.id}")
print(f"Path: {file.path}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python showLineNumbers title="upload_container_file_azure.py"
from litellm import upload_container_file
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
file = upload_container_file(
container_id="cntr_123...",
file=("data.csv", open("data.csv", "rb").read(), "text/csv"),
custom_llm_provider="azure"
)
print(f"Uploaded: {file.id}")
print(f"Path: {file.path}")
```
</TabItem>
</Tabs>
**Async:**
```python showLineNumbers title="aupload_container_file.py"
@ -55,7 +84,7 @@ from litellm import aupload_container_file
file = await aupload_container_file(
container_id="cntr_123...",
file=("script.py", b"print('hello world')", "text/x-python"),
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
```
@ -70,6 +99,9 @@ file = await aupload_container_file(
### List Container Files
<Tabs>
<TabItem value="openai" label="OpenAI">
```python showLineNumbers title="list_container_files.py"
from litellm import list_container_files
@ -82,6 +114,28 @@ for file in files.data:
print(f" - {file.id}: {file.filename}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python showLineNumbers title="list_container_files_azure.py"
from litellm import list_container_files
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
files = list_container_files(
container_id="cntr_123...",
custom_llm_provider="azure"
)
for file in files.data:
print(f" - {file.id}: {file.filename}")
```
</TabItem>
</Tabs>
**Async:**
```python showLineNumbers title="alist_container_files.py"
@ -89,7 +143,7 @@ from litellm import alist_container_files
files = await alist_container_files(
container_id="cntr_123...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
```
@ -101,7 +155,7 @@ from litellm import retrieve_container_file
file = retrieve_container_file(
container_id="cntr_123...",
file_id="cfile_456...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
print(f"File: {file.filename}")
@ -116,7 +170,7 @@ from litellm import retrieve_container_file_content
content = retrieve_container_file_content(
container_id="cntr_123...",
file_id="cfile_456...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
# content is raw bytes
@ -132,7 +186,7 @@ from litellm import delete_container_file
result = delete_container_file(
container_id="cntr_123...",
file_id="cfile_456...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
print(f"Deleted: {result.deleted}")
@ -140,9 +194,6 @@ print(f"Deleted: {result.deleted}")
## LiteLLM AI Gateway (Proxy)
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
### Upload File
<Tabs>
@ -377,6 +428,7 @@ curl -X DELETE "http://localhost:4000/v1/containers/cntr_123.../files/cfile_456.
| Provider | Status |
|----------|--------|
| OpenAI | ✅ Supported |
| Azure OpenAI | ✅ Supported |
## Related

View file

@ -1,6 +1,6 @@
# /containers
Manage OpenAI code interpreter containers (sessions) for executing code in isolated environments.
Manage code interpreter containers (sessions) for executing code in isolated environments.
:::tip
Looking for how to use Code Interpreter? See the [Code Interpreter Guide](/docs/guides/code_interpreter).
@ -13,7 +13,7 @@ Looking for how to use Code Interpreter? See the [Code Interpreter Guide](/docs/
| Load Balancing | ✅ |
| Proxy Server Support | ✅ Full proxy integration with virtual keys |
| Spend Management | ✅ Budget tracking and rate limiting |
| Supported Providers | `openai`|
| Supported Providers | `openai`, `azure`|
:::tip
@ -23,15 +23,20 @@ Containers provide isolated execution environments for code interpreter sessions
## **LiteLLM Python SDK Usage**
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
### Quick Start
**Create a Container**
<Tabs>
<TabItem value="openai" label="OpenAI">
```python
import litellm
import os
import os
# setup env
os.environ["OPENAI_API_KEY"] = "sk-.."
container = litellm.create_container(
@ -47,11 +52,40 @@ print(f"Container ID: {container.id}")
print(f"Container Name: {container.name}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python
import litellm
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
container = litellm.create_container(
name="My Code Interpreter Container",
custom_llm_provider="azure",
expires_after={
"anchor": "last_active_at",
"minutes": 20
}
)
print(f"Container ID: {container.id}")
print(f"Container Name: {container.name}")
```
</TabItem>
</Tabs>
### Async Usage
<Tabs>
<TabItem value="openai" label="OpenAI">
```python
from litellm import acreate_container
import os
import os
os.environ["OPENAI_API_KEY"] = "sk-.."
@ -68,11 +102,40 @@ print(f"Container ID: {container.id}")
print(f"Container Name: {container.name}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python
from litellm import acreate_container
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
container = await acreate_container(
name="My Code Interpreter Container",
custom_llm_provider="azure",
expires_after={
"anchor": "last_active_at",
"minutes": 20
}
)
print(f"Container ID: {container.id}")
print(f"Container Name: {container.name}")
```
</TabItem>
</Tabs>
### List Containers
<Tabs>
<TabItem value="openai" label="OpenAI">
```python
from litellm import list_containers
import os
import os
os.environ["OPENAI_API_KEY"] = "sk-.."
@ -87,13 +150,37 @@ for container in containers.data:
print(f" - {container.id}: {container.name}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python
from litellm import list_containers
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
containers = list_containers(
custom_llm_provider="azure",
limit=20,
order="desc"
)
print(f"Found {len(containers.data)} containers")
for container in containers.data:
print(f" - {container.id}: {container.name}")
```
</TabItem>
</Tabs>
**Async Usage:**
```python
from litellm import alist_containers
containers = await alist_containers(
custom_llm_provider="openai",
custom_llm_provider="openai", # or "azure"
limit=20,
order="desc"
)
@ -105,9 +192,12 @@ for container in containers.data:
### Retrieve a Container
<Tabs>
<TabItem value="openai" label="OpenAI">
```python
from litellm import retrieve_container
import os
import os
os.environ["OPENAI_API_KEY"] = "sk-.."
@ -121,6 +211,29 @@ print(f"Status: {container.status}")
print(f"Created: {container.created_at}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python
from litellm import retrieve_container
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
container = retrieve_container(
container_id="cntr_123...",
custom_llm_provider="azure"
)
print(f"Container: {container.name}")
print(f"Status: {container.status}")
print(f"Created: {container.created_at}")
```
</TabItem>
</Tabs>
**Async Usage:**
```python
@ -128,7 +241,7 @@ from litellm import aretrieve_container
container = await aretrieve_container(
container_id="cntr_123...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
print(f"Container: {container.name}")
@ -138,9 +251,12 @@ print(f"Created: {container.created_at}")
### Delete a Container
<Tabs>
<TabItem value="openai" label="OpenAI">
```python
from litellm import delete_container
import os
import os
os.environ["OPENAI_API_KEY"] = "sk-.."
@ -153,6 +269,28 @@ print(f"Deleted: {result.deleted}")
print(f"Container ID: {result.id}")
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```python
from litellm import delete_container
import os
os.environ["AZURE_API_KEY"] = "your-azure-api-key"
os.environ["AZURE_API_BASE"] = "https://your-resource.openai.azure.com"
result = delete_container(
container_id="cntr_123...",
custom_llm_provider="azure"
)
print(f"Deleted: {result.deleted}")
print(f"Container ID: {result.id}")
```
</TabItem>
</Tabs>
**Async Usage:**
```python
@ -160,7 +298,7 @@ from litellm import adelete_container
result = await adelete_container(
container_id="cntr_123...",
custom_llm_provider="openai"
custom_llm_provider="openai" # or "azure"
)
print(f"Deleted: {result.deleted}")
@ -177,8 +315,13 @@ LiteLLM provides OpenAI API compatible container endpoints for managing code int
**Setup**
```bash
# For OpenAI
$ export OPENAI_API_KEY="sk-..."
# For Azure OpenAI
$ export AZURE_API_KEY="your-azure-api-key"
$ export AZURE_API_BASE="https://your-resource.openai.azure.com"
$ litellm
# RUNNING on http://0.0.0.0:4000
@ -187,15 +330,17 @@ $ litellm
**Custom Provider Specification**
You can specify the custom LLM provider in multiple ways (priority order):
1. Header: `-H "custom-llm-provider: openai"`
2. Query param: `?custom_llm_provider=openai`
3. Request body: `{"custom_llm_provider": "openai", ...}`
1. Header: `-H "custom-llm-provider: openai"` (or `azure`)
2. Query param: `?custom_llm_provider=openai` (or `azure`)
3. Request body: `{"custom_llm_provider": "openai", ...}` (or `"azure"`)
4. Defaults to "openai" if not specified
**Create a Container**
<Tabs>
<TabItem value="openai" label="OpenAI">
```bash
# Default provider (openai)
curl -X POST "http://localhost:4000/v1/containers" \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
@ -208,32 +353,36 @@ curl -X POST "http://localhost:4000/v1/containers" \
}'
```
</TabItem>
<TabItem value="azure" label="Azure OpenAI">
```bash
# Via header
curl -X POST "http://localhost:4000/v1/containers" \
-H "Authorization: Bearer sk-1234" \
-H "custom-llm-provider: openai" \
-H "custom-llm-provider: azure" \
-H "Content-Type: application/json" \
-d '{
"name": "My Container"
"name": "My Container",
"expires_after": {
"anchor": "last_active_at",
"minutes": 20
}
}'
```
```bash
# Via query parameter
curl -X POST "http://localhost:4000/v1/containers?custom_llm_provider=openai" \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{
"name": "My Container"
}'
```
</TabItem>
</Tabs>
**List Containers**
```bash
# OpenAI (default)
curl "http://localhost:4000/v1/containers?limit=20&order=desc" \
-H "Authorization: Bearer sk-1234"
# Azure OpenAI
curl "http://localhost:4000/v1/containers?limit=20&order=desc&custom_llm_provider=azure" \
-H "Authorization: Bearer sk-1234"
```
**Retrieve a Container**
@ -460,12 +609,7 @@ print(f"Deleted: {result.deleted}")
| Provider | Support Status | Notes |
|-------------|----------------|-------|
| OpenAI | ✅ Supported | Full support for all container operations |
:::info
Currently, only OpenAI supports container management for code interpreter sessions. Support for additional providers may be added in the future.
:::
| Azure OpenAI | ✅ Supported | Full support for all container operations |
## Related

View file

@ -10,6 +10,9 @@ DEFAULT_HEALTH_CHECK_PROMPT = str(
AZURE_DEFAULT_RESPONSES_API_VERSION = str(
os.getenv("AZURE_DEFAULT_RESPONSES_API_VERSION", "preview")
)
AZURE_DEFAULT_CONTAINERS_API_VERSION = str(
os.getenv("AZURE_DEFAULT_CONTAINERS_API_VERSION", "preview")
)
ROUTER_MAX_FALLBACKS = int(os.getenv("ROUTER_MAX_FALLBACKS", 5))
DEFAULT_BATCH_SIZE = int(os.getenv("DEFAULT_BATCH_SIZE", 512))
DEFAULT_FLUSH_INTERVAL_SECONDS = int(os.getenv("DEFAULT_FLUSH_INTERVAL_SECONDS", 5))

View file

@ -48,7 +48,7 @@ async def acreate_container(
file_ids: Optional[List[str]] = None,
timeout=600, # default to 10 minutes
# LiteLLM specific params,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -122,7 +122,7 @@ def create_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
acreate_container: Literal[True],
**kwargs,
@ -139,7 +139,7 @@ def create_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
acreate_container: Literal[False] = False,
**kwargs,
@ -158,7 +158,7 @@ def create_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -275,7 +275,7 @@ async def alist_containers(
limit: Optional[int] = None,
order: Optional[str] = None,
timeout=600, # default to 10 minutes
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -348,7 +348,7 @@ def list_containers(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
alist_containers: Literal[True],
**kwargs,
@ -365,7 +365,7 @@ def list_containers(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
alist_containers: Literal[False] = False,
**kwargs,
@ -384,7 +384,7 @@ def list_containers(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -481,7 +481,7 @@ def list_containers(
async def aretrieve_container(
container_id: str,
timeout=600, # default to 10 minutes
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -548,7 +548,7 @@ def retrieve_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
aretrieve_container: Literal[True],
**kwargs,
@ -563,7 +563,7 @@ def retrieve_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
aretrieve_container: Literal[False] = False,
**kwargs,
@ -580,7 +580,7 @@ def retrieve_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -667,7 +667,7 @@ def retrieve_container(
async def adelete_container(
container_id: str,
timeout=600, # default to 10 minutes
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -734,7 +734,7 @@ def delete_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
adelete_container: Literal[True],
**kwargs,
@ -749,7 +749,7 @@ def delete_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
adelete_container: Literal[False] = False,
**kwargs,
@ -766,7 +766,7 @@ def delete_container(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@ -856,7 +856,7 @@ async def alist_container_files(
limit: Optional[int] = None,
order: Optional[str] = None,
timeout=600, # default to 10 minutes
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
extra_headers: Optional[Dict[str, Any]] = None,
extra_query: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
@ -930,7 +930,7 @@ def list_container_files(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
alist_container_files: Literal[True],
**kwargs,
@ -948,7 +948,7 @@ def list_container_files(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
alist_container_files: Literal[False] = False,
**kwargs,
@ -968,7 +968,7 @@ def list_container_files(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
extra_headers: Optional[Dict[str, Any]] = None,
extra_query: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
@ -1062,7 +1062,7 @@ async def aupload_container_file(
container_id: str,
file: FileTypes,
timeout=600, # default to 10 minutes
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
extra_headers: Optional[Dict[str, Any]] = None,
extra_query: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,
@ -1151,7 +1151,7 @@ def upload_container_file(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
aupload_container_file: Literal[True],
**kwargs,
@ -1167,7 +1167,7 @@ def upload_container_file(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
*,
aupload_container_file: Literal[False] = False,
**kwargs,
@ -1185,7 +1185,7 @@ def upload_container_file(
api_key: Optional[str] = None,
api_base: Optional[str] = None,
api_version: Optional[str] = None,
custom_llm_provider: Literal["openai"] = "openai",
custom_llm_provider: Literal["openai", "azure"] = "openai",
extra_headers: Optional[Dict[str, Any]] = None,
extra_query: Optional[Dict[str, Any]] = None,
extra_body: Optional[Dict[str, Any]] = None,

View file

@ -0,0 +1,48 @@
from typing import Optional
from litellm.constants import AZURE_DEFAULT_CONTAINERS_API_VERSION
from litellm.llms.azure.common_utils import BaseAzureLLM
from litellm.llms.openai.containers.transformation import OpenAIContainerConfig
from litellm.types.router import GenericLiteLLMParams
class AzureOpenAIContainerConfig(OpenAIContainerConfig):
"""Azure OpenAI Container Config.
Inherits from OpenAIContainerConfig and overrides only Azure-specific methods.
Request/response transformations are identical to OpenAI.
"""
def get_complete_url(
self,
api_base: Optional[str],
litellm_params: dict,
) -> str:
"""Get the complete URL for Azure container API.
Constructs Azure-specific URLs like:
https://{resource}.openai.azure.com/openai/v1/containers?api-version=xxx
"""
return BaseAzureLLM._get_base_azure_url(
api_base=api_base,
litellm_params=litellm_params,
route="/openai/v1/containers",
default_api_version=AZURE_DEFAULT_CONTAINERS_API_VERSION,
)
def validate_environment(
self,
headers: dict,
api_key: Optional[str] = None,
) -> dict:
"""Validate and set up Azure authentication headers.
Uses Azure api-key header (not Bearer token like OpenAI).
"""
# Create a GenericLiteLLMParams with the api_key if provided
litellm_params = GenericLiteLLMParams(api_key=api_key) if api_key else None
# Azure uses BaseAzureLLM's validation which handles api-key header
return BaseAzureLLM._base_validate_azure_environment(
headers=headers, litellm_params=litellm_params
)

View file

@ -40,6 +40,12 @@ def _get_container_provider_config(custom_llm_provider: str):
from litellm.llms.openai.containers.transformation import OpenAIContainerConfig
return OpenAIContainerConfig()
elif custom_llm_provider == "azure":
from litellm.llms.azure.containers.transformation import (
AzureOpenAIContainerConfig,
)
return AzureOpenAIContainerConfig()
else:
raise ValueError(
f"Container API not supported for provider: {custom_llm_provider}"

View file

@ -8846,6 +8846,12 @@ class ProviderConfigManager:
)
return OpenAIContainerConfig()
elif LlmProviders.AZURE == provider:
from litellm.llms.azure.containers.transformation import (
AzureOpenAIContainerConfig,
)
return AzureOpenAIContainerConfig()
return None
@staticmethod

View file

@ -0,0 +1,246 @@
import os
import sys
from unittest.mock import MagicMock
import httpx
import pytest
sys.path.insert(
0, os.path.abspath("../../..")
) # Adds the parent directory to the system path
import litellm
from litellm.llms.azure.containers.transformation import AzureOpenAIContainerConfig
from litellm.llms.openai.containers.transformation import OpenAIContainerConfig
from litellm.types.containers.main import (
ContainerFileListResponse,
ContainerListResponse,
ContainerObject,
DeleteContainerResult,
)
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from litellm.types.router import GenericLiteLLMParams
from litellm.utils import ProviderConfigManager
class TestAzureContainerConfigInheritance:
"""Test that AzureOpenAIContainerConfig properly inherits from OpenAI."""
def test_inherits_from_openai(self):
config = AzureOpenAIContainerConfig()
assert isinstance(config, OpenAIContainerConfig)
def test_get_supported_openai_params(self):
config = AzureOpenAIContainerConfig()
supported_params = config.get_supported_openai_params()
assert "name" in supported_params
assert "expires_after" in supported_params
assert "file_ids" in supported_params
class TestAzureContainerValidateEnvironment:
"""Test Azure-specific authentication headers."""
def test_validate_environment_with_api_key(self):
config = AzureOpenAIContainerConfig()
headers = {}
result = config.validate_environment(headers=headers, api_key="test-azure-key")
assert "api-key" in result
assert result["api-key"] == "test-azure-key"
def test_validate_environment_does_not_use_bearer_token(self):
config = AzureOpenAIContainerConfig()
headers = {}
result = config.validate_environment(headers=headers, api_key="test-azure-key")
assert "Authorization" not in result
def test_validate_environment_preserves_existing_api_key_header(self):
config = AzureOpenAIContainerConfig()
headers = {"api-key": "existing-key"}
result = config.validate_environment(headers=headers, api_key="new-key")
assert result["api-key"] == "existing-key"
class TestAzureContainerGetCompleteUrl:
"""Test Azure-specific URL construction."""
def test_get_complete_url_constructs_azure_url(self):
config = AzureOpenAIContainerConfig()
api_base = "https://my-resource.openai.azure.com"
litellm_params = {"api_version": "2024-12-01-preview"}
url = config.get_complete_url(
api_base=api_base,
litellm_params=litellm_params,
)
assert "my-resource.openai.azure.com" in url
assert "/openai/v1/containers" in url or "/openai/containers" in url
assert "api-version" in url
def test_get_complete_url_uses_default_api_version(self):
config = AzureOpenAIContainerConfig()
api_base = "https://my-resource.openai.azure.com"
litellm_params = {}
url = config.get_complete_url(
api_base=api_base,
litellm_params=litellm_params,
)
assert "api-version" in url
def test_get_complete_url_differs_from_openai(self):
azure_config = AzureOpenAIContainerConfig()
openai_config = OpenAIContainerConfig()
azure_url = azure_config.get_complete_url(
api_base="https://my-resource.openai.azure.com",
litellm_params={},
)
openai_url = openai_config.get_complete_url(
api_base="https://api.openai.com/v1",
litellm_params={},
)
assert azure_url != openai_url
assert "azure" in azure_url
assert "api-version" in azure_url
class TestAzureContainerTransformations:
"""Test that request/response transformations work for Azure (inherited from OpenAI)."""
def setup_method(self):
self.config = AzureOpenAIContainerConfig()
self.logging_obj = LiteLLMLogging(
model="",
messages=[],
stream=False,
call_type="create_container",
start_time=None,
litellm_call_id="test_call_id",
function_id="test_function_id",
)
def test_transform_container_create_request(self):
litellm_params = GenericLiteLLMParams()
headers = {"api-key": "test-azure-key"}
data = self.config.transform_container_create_request(
name="Test Container",
container_create_optional_request_params={
"expires_after": {"anchor": "last_active_at", "minutes": 20},
},
litellm_params=litellm_params,
headers=headers,
)
assert data["name"] == "Test Container"
assert data["expires_after"]["minutes"] == 20
def test_transform_container_create_response(self):
mock_response = MagicMock(spec=httpx.Response)
mock_response.json.return_value = {
"id": "cntr_azure_123",
"object": "container",
"created_at": 1747857508,
"status": "running",
"expires_after": {"anchor": "last_active_at", "minutes": 20},
"last_active_at": 1747857508,
"name": "Azure Container",
}
container = self.config.transform_container_create_response(
raw_response=mock_response,
logging_obj=self.logging_obj,
)
assert isinstance(container, ContainerObject)
assert container.id == "cntr_azure_123"
assert container.name == "Azure Container"
def test_transform_container_list_response(self):
mock_response = MagicMock(spec=httpx.Response)
mock_response.json.return_value = {
"object": "list",
"data": [
{
"id": "cntr_1",
"object": "container",
"created_at": 1747857508,
"status": "running",
"expires_after": {"anchor": "last_active_at", "minutes": 20},
"last_active_at": 1747857508,
"name": "Container 1",
}
],
"first_id": "cntr_1",
"last_id": "cntr_1",
"has_more": False,
}
result = self.config.transform_container_list_response(
raw_response=mock_response,
logging_obj=self.logging_obj,
)
assert isinstance(result, ContainerListResponse)
assert len(result.data) == 1
def test_transform_container_file_list_request(self):
api_base = "https://my-resource.openai.azure.com/openai/v1/containers"
url, params = self.config.transform_container_file_list_request(
container_id="cntr_123",
api_base=api_base,
litellm_params={},
headers={"api-key": "test"},
limit=10,
)
assert "cntr_123/files" in url
assert params["limit"] == "10"
def test_transform_container_file_content_request(self):
api_base = "https://my-resource.openai.azure.com/openai/v1/containers"
url, params = self.config.transform_container_file_content_request(
container_id="cntr_123",
file_id="file_456",
api_base=api_base,
litellm_params={},
headers={"api-key": "test"},
)
assert "cntr_123/files/file_456/content" in url
def test_transform_container_file_content_response(self):
mock_response = MagicMock(spec=httpx.Response)
mock_response.content = b"file content bytes"
result = self.config.transform_container_file_content_response(
raw_response=mock_response,
logging_obj=self.logging_obj,
)
assert result == b"file content bytes"
def test_transform_container_delete_response(self):
mock_response = MagicMock(spec=httpx.Response)
mock_response.json.return_value = {
"id": "cntr_delete_123",
"object": "container.deleted",
"deleted": True,
}
result = self.config.transform_container_delete_response(
raw_response=mock_response,
logging_obj=self.logging_obj,
)
assert isinstance(result, DeleteContainerResult)
assert result.deleted is True
class TestAzureContainerProviderRegistration:
"""Test that Azure is properly registered as a container provider."""
def test_provider_config_manager_returns_azure_config(self):
config = ProviderConfigManager.get_provider_container_config(
provider=litellm.LlmProviders.AZURE,
)
assert config is not None
assert isinstance(config, AzureOpenAIContainerConfig)
def test_provider_config_manager_returns_none_for_unsupported(self):
config = ProviderConfigManager.get_provider_container_config(
provider=litellm.LlmProviders.ANTHROPIC,
)
assert config is None