diff --git a/docs/my-website/docs/container_files.md b/docs/my-website/docs/container_files.md
index 1ef7687ea776..d9c2c3098410 100644
--- a/docs/my-website/docs/container_files.md
+++ b/docs/my-website/docs/container_files.md
@@ -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.
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
**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
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
**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
@@ -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
diff --git a/docs/my-website/docs/containers.md b/docs/my-website/docs/containers.md
index 2bfe179ff6b6..abb2db151505 100644
--- a/docs/my-website/docs/containers.md
+++ b/docs/my-website/docs/containers.md
@@ -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**
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
### Async Usage
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
### List Containers
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
**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
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
**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
+
+
+
```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}")
```
+
+
+
+```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}")
+```
+
+
+
+
**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**
+
+
+
```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" \
}'
```
+
+
+
```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"
- }'
-```
+
+
**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
diff --git a/litellm/constants.py b/litellm/constants.py
index 89c59ee93267..a034972a6b25 100644
--- a/litellm/constants.py
+++ b/litellm/constants.py
@@ -10,6 +10,9 @@
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))
diff --git a/litellm/containers/main.py b/litellm/containers/main.py
index 916fc26351b6..477f3f2de889 100644
--- a/litellm/containers/main.py
+++ b/litellm/containers/main.py
@@ -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,
diff --git a/litellm/llms/azure/containers/transformation.py b/litellm/llms/azure/containers/transformation.py
new file mode 100644
index 000000000000..8304c0cca4e2
--- /dev/null
+++ b/litellm/llms/azure/containers/transformation.py
@@ -0,0 +1,166 @@
+from typing import Any, Dict, Optional, Tuple
+from urllib.parse import urlparse, urlunparse
+
+import httpx
+
+from litellm.constants import AZURE_DEFAULT_CONTAINERS_API_VERSION
+from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
+from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
+ StandardBuiltInToolCostTracking,
+)
+from litellm.llms.azure.common_utils import BaseAzureLLM
+from litellm.llms.openai.containers.transformation import OpenAIContainerConfig
+from litellm.types.containers.main import ContainerObject
+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
+ )
+
+ def transform_container_create_response(
+ self,
+ raw_response: httpx.Response,
+ logging_obj: LiteLLMLoggingObj,
+ ) -> ContainerObject:
+ """Transform the Azure container creation response.
+
+ Overrides OpenAI's method to use provider="azure" for cost tracking.
+ """
+ response_data = raw_response.json()
+ container_obj = ContainerObject(**response_data) # type: ignore[arg-type]
+
+ container_cost = StandardBuiltInToolCostTracking.get_cost_for_code_interpreter(
+ sessions=1,
+ provider="azure",
+ )
+
+ if (
+ not hasattr(container_obj, "_hidden_params")
+ or container_obj._hidden_params is None
+ ):
+ container_obj._hidden_params = {}
+ if "additional_headers" not in container_obj._hidden_params:
+ container_obj._hidden_params["additional_headers"] = {}
+ container_obj._hidden_params["additional_headers"][
+ "llm_provider-x-litellm-response-cost"
+ ] = container_cost
+
+ return container_obj
+
+ def _construct_url_with_subpath(self, api_base: str, subpath: str) -> str:
+ """Construct a URL by inserting subpath before query parameters.
+
+ Azure URLs contain ?api-version=... query params. Naively appending
+ path segments via string concatenation would place them after the
+ query string, producing malformed URLs. This helper parses the URL
+ and appends the subpath to the path component only.
+ """
+ parsed = urlparse(api_base)
+ new_path = f"{parsed.path.rstrip('/')}/{subpath}"
+ return urlunparse(
+ (
+ parsed.scheme,
+ parsed.netloc,
+ new_path,
+ parsed.params,
+ parsed.query,
+ parsed.fragment,
+ )
+ )
+
+ def transform_container_retrieve_request(
+ self,
+ container_id: str,
+ api_base: str,
+ litellm_params: GenericLiteLLMParams,
+ headers: dict,
+ ) -> Tuple[str, Dict]:
+ url = self._construct_url_with_subpath(api_base, container_id)
+ data: Dict[str, Any] = {}
+ return url, data
+
+ def transform_container_delete_request(
+ self,
+ container_id: str,
+ api_base: str,
+ litellm_params: GenericLiteLLMParams,
+ headers: dict,
+ ) -> Tuple[str, Dict]:
+ url = self._construct_url_with_subpath(api_base, container_id)
+ data: Dict[str, Any] = {}
+ return url, data
+
+ def transform_container_file_list_request(
+ self,
+ container_id: str,
+ api_base: str,
+ litellm_params: GenericLiteLLMParams,
+ headers: dict,
+ after: Optional[str] = None,
+ limit: Optional[int] = None,
+ order: Optional[str] = None,
+ extra_query: Optional[Dict[str, Any]] = None,
+ ) -> Tuple[str, Dict]:
+ url = self._construct_url_with_subpath(api_base, f"{container_id}/files")
+ params: Dict[str, Any] = {}
+ if after is not None:
+ params["after"] = after
+ if limit is not None:
+ params["limit"] = str(limit)
+ if order is not None:
+ params["order"] = order
+ if extra_query:
+ params.update(extra_query)
+ return url, params
+
+ def transform_container_file_content_request(
+ self,
+ container_id: str,
+ file_id: str,
+ api_base: str,
+ litellm_params: GenericLiteLLMParams,
+ headers: dict,
+ ) -> Tuple[str, Dict]:
+ url = self._construct_url_with_subpath(
+ api_base, f"{container_id}/files/{file_id}/content"
+ )
+ params: Dict[str, Any] = {}
+ return url, params
diff --git a/litellm/proxy/container_endpoints/handler_factory.py b/litellm/proxy/container_endpoints/handler_factory.py
index 078f0c9bc491..88e77cb9988b 100644
--- a/litellm/proxy/container_endpoints/handler_factory.py
+++ b/litellm/proxy/container_endpoints/handler_factory.py
@@ -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}"
diff --git a/litellm/utils.py b/litellm/utils.py
index 81d749ab8216..61c8c371ec80 100644
--- a/litellm/utils.py
+++ b/litellm/utils.py
@@ -8846,6 +8846,12 @@ def get_provider_container_config(
)
return OpenAIContainerConfig()
+ elif LlmProviders.AZURE == provider:
+ from litellm.llms.azure.containers.transformation import (
+ AzureOpenAIContainerConfig,
+ )
+
+ return AzureOpenAIContainerConfig()
return None
@staticmethod
diff --git a/tests/test_litellm/containers/test_azure_container_transformation.py b/tests/test_litellm/containers/test_azure_container_transformation.py
new file mode 100644
index 000000000000..ec6289dd4125
--- /dev/null
+++ b/tests/test_litellm/containers/test_azure_container_transformation.py
@@ -0,0 +1,297 @@
+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_create_response_uses_azure_provider_for_cost(self):
+ mock_response = MagicMock(spec=httpx.Response)
+ mock_response.json.return_value = {
+ "id": "cntr_azure_cost",
+ "object": "container",
+ "created_at": 1747857508,
+ "status": "running",
+ "expires_after": {"anchor": "last_active_at", "minutes": 20},
+ "last_active_at": 1747857508,
+ "name": "Azure Cost Test",
+ }
+ from unittest.mock import patch
+
+ with patch(
+ "litellm.llms.azure.containers.transformation.StandardBuiltInToolCostTracking.get_cost_for_code_interpreter"
+ ) as mock_cost:
+ mock_cost.return_value = 0.03
+ self.config.transform_container_create_response(
+ raw_response=mock_response,
+ logging_obj=self.logging_obj,
+ )
+ mock_cost.assert_called_once_with(sessions=1, provider="azure")
+
+ 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_retrieve_request_with_query_params(self):
+ api_base = "https://my-resource.openai.azure.com/openai/v1/containers?api-version=preview"
+ url, data = self.config.transform_container_retrieve_request(
+ container_id="cntr_123",
+ api_base=api_base,
+ litellm_params={},
+ headers={"api-key": "test"},
+ )
+ assert "/containers/cntr_123" in url
+ assert "?api-version=preview" in url
+ assert url.index("/cntr_123") < url.index("?api-version")
+
+ def test_transform_container_delete_request_with_query_params(self):
+ api_base = "https://my-resource.openai.azure.com/openai/v1/containers?api-version=preview"
+ url, data = self.config.transform_container_delete_request(
+ container_id="cntr_123",
+ api_base=api_base,
+ litellm_params={},
+ headers={"api-key": "test"},
+ )
+ assert "/containers/cntr_123" in url
+ assert "?api-version=preview" in url
+ assert url.index("/cntr_123") < url.index("?api-version")
+
+ def test_transform_container_file_list_request(self):
+ api_base = "https://my-resource.openai.azure.com/openai/v1/containers?api-version=preview"
+ 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 "/containers/cntr_123/files" in url
+ assert "?api-version=preview" in url
+ assert url.index("/cntr_123/files") < url.index("?api-version")
+ assert params["limit"] == "10"
+
+ def test_transform_container_file_content_request(self):
+ api_base = "https://my-resource.openai.azure.com/openai/v1/containers?api-version=preview"
+ 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 "/containers/cntr_123/files/file_456/content" in url
+ assert "?api-version=preview" in url
+ assert url.index("/file_456/content") < url.index("?api-version")
+
+ 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