Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
08166b8
fix(proxy): bump health-check max_tokens default to 16 for GPT-5 comp…
tcconnally Jun 18, 2026
1856499
fix: increase health check max_tokens from 5 to 16 (#23836) (#26610)
hannahmadison Jun 18, 2026
3edfc0c
fix: ensure checks show gemini-3-flash-preview supports responseJsonS…
clpatterson Jun 18, 2026
ada3eee
fix: make Bedrock Mantle Responses routing data-driven per model (#30…
6matt Jun 18, 2026
0544eed
fix(proxy): fail fast on non-PostgreSQL DATABASE_URL instead of hangi…
krishvsoni Jun 18, 2026
4f58bd0
fix(dashscope): treat an explicit 0.0 tier cost as a real price, not …
devYRPauli Jun 18, 2026
f530b22
feat(proxy): show session-aggregate cost and duration in request logs…
nitishagar Jun 18, 2026
2d99433
fix(anthropic): don't leak tool 'type' into OpenAI function parameter…
hclsys Jun 18, 2026
85828da
fix(proxy): stop IAM-refresh engine restart from cascading reconnects…
tushar8408 Jun 18, 2026
df029ad
feat(bedrock): support file content retrieval for batch output files …
ad1269 Jun 18, 2026
50f34e0
fix(oci): make Cohere {{trace}} judges work (tool param types + agent…
fede-kamel Jun 18, 2026
cd4bd92
feat: integrate Repelloai Argus guardrail (#30673)
lavish619 Jun 18, 2026
1fda0db
feat: add Pinstripes as an OpenAI-compatible provider (#30567)
max-amos Jun 18, 2026
64e5685
fix(rag): attach existing OpenAI file ids (#30628)
Nithish-Yenaganti Jun 18, 2026
c9e8a17
fix(anthropic-messages): apply cache_control_injection_points on /v1/…
anneheartrecord Jun 18, 2026
9f0cfc1
fix(proxy): release budget reservation when a request is cancelled mi…
Bytechoreographer Jun 18, 2026
e6ec635
fix(caching): encode object name in GCS cache GET path (#30378)
darktheorys Jun 18, 2026
9dc5395
chore: add soniox stt-async-v5 model (#30672)
dan2k3k4 Jun 18, 2026
b9b5e4b
fix(proxy): include model group aliases in v1 model info (#30626)
vanika02 Jun 18, 2026
a961f2c
fix(lint): remove redundant noqa directive in proxy_cli.py
Sameerlite Jun 18, 2026
52c7a07
fix: address greptile review - restore bedrock_mantle auth symbols, g…
Sameerlite Jun 18, 2026
4dd217d
Revert "fix: address greptile review - restore bedrock_mantle auth sy…
Sameerlite Jun 18, 2026
6606d0a
Revert "fix(anthropic-messages): apply cache_control_injection_points…
Sameerlite Jun 18, 2026
5b85e60
Revert "fix(proxy): stop IAM-refresh engine restart from cascading re…
Sameerlite Jun 18, 2026
ecd1444
fix(proxy): stop IAM-refresh engine restart from cascading reconnects…
tushar8408 Jun 18, 2026
396e4e6
fix(lint): modernize type annotations in IAM-refresh prisma client fi…
Sameerlite Jun 18, 2026
e2d84cd
Revert "feat(proxy): show session-aggregate cost and duration in requ…
Sameerlite Jun 18, 2026
bba8bc6
Revert "fix(dashscope): treat an explicit 0.0 tier cost as a real pri…
Sameerlite Jun 18, 2026
15a7c68
Revert "fix(oci): make Cohere {{trace}} judges work (tool param types…
Sameerlite Jun 18, 2026
3ea7fc6
Revert "fix(proxy): fail fast on non-PostgreSQL DATABASE_URL instead …
Sameerlite Jun 18, 2026
2e295f3
fix(bedrock_mantle): restore BedrockMantleAuthMixin and constants rem…
Sameerlite Jun 18, 2026
ce20c7a
fix(key management): restore exact /key/list user_id & key_alias matc…
jgowdy-godaddy Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ curl -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
| [OVHCloud AI Endpoints (`ovhcloud`)](https://docs.litellm.ai/docs/providers/ovhcloud) | ✅ | ✅ | ✅ | | | | | | | |
| [Perplexity AI (`perplexity`)](https://docs.litellm.ai/docs/providers/perplexity) | ✅ | ✅ | ✅ | | | | | | | |
| [Petals (`petals`)](https://docs.litellm.ai/docs/providers/petals) | ✅ | ✅ | ✅ | | | | | | | |
| [Pinstripes (`pinstripes`)](https://docs.litellm.ai/docs/providers/pinstripes) | ✅ | ✅ | ✅ | | | | | | | |
| [Predibase (`predibase`)](https://docs.litellm.ai/docs/providers/predibase) | ✅ | ✅ | ✅ | | | | | | | |
| [Recraft (`recraft`)](https://docs.litellm.ai/docs/providers/recraft) | | | | | ✅ | | | | | |
| [Replicate (`replicate`)](https://docs.litellm.ai/docs/providers/replicate) | ✅ | ✅ | ✅ | | | | | | | |
Expand Down
23 changes: 18 additions & 5 deletions enterprise/litellm_enterprise/proxy/hooks/managed_files.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# What is this?
## This hook is used to check for LiteLLM managed files in the request body, and replace them with model-specific file id

import asyncio
import base64
import json
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast

from fastapi import HTTPException
Expand Down Expand Up @@ -1472,8 +1472,8 @@ async def _check_file_deletion_allowed(self, file_id: str) -> None:
error_message += f" (showing {MAX_BATCHES_IN_ERROR} most recent): {', '.join(batch_statuses)}. "

error_message += (
f"To delete this file before complete cost tracking, please delete or cancel the referencing batch(es) first. "
f"Alternatively, wait for all batches to complete and for cost to be computed (batch_processed=true)."
"To delete this file before complete cost tracking, please delete or cancel the referencing batch(es) first. "
"Alternatively, wait for all batches to complete and for cost to be computed (batch_processed=true)."
)

# Record blocked deletion metric
Expand Down Expand Up @@ -1550,9 +1550,22 @@ async def afile_content(

if specific_model_file_id_mapping:
exception_dict = {}
for model_id, file_id in specific_model_file_id_mapping.items():
for model_id, provider_file_id in specific_model_file_id_mapping.items():
try:
return await llm_router.afile_content(model=model_id, file_id=file_id, **data) # type: ignore
# Cloud-storage providers (e.g. Bedrock S3) validate file ids
# against the deployment's configured bucket, which they only
# trust from this immutable server-side snapshot, never from
# request params.
credentials = llm_router.get_deployment_credentials_with_provider(
model_id=model_id
)
if credentials is not None:
data["_litellm_internal_model_credentials"] = cast(
Dict, MappingProxyType(dict(credentials))
)
else:
data.pop("_litellm_internal_model_credentials", None)
return await llm_router.afile_content(model=model_id, file_id=provider_file_id, **data) # type: ignore
except Exception as e:
exception_dict[model_id] = str(e)
raise Exception(
Expand Down
9 changes: 5 additions & 4 deletions litellm/caching/gcs_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import asyncio
from typing import Optional
from urllib.parse import quote

from litellm._logging import print_verbose, verbose_logger
from litellm.integrations.gcs_bucket.gcs_bucket_base import GCSBucketBase
Expand Down Expand Up @@ -48,7 +49,7 @@ def set_cache(self, key, value, **kwargs):
headers = self._construct_headers()
object_name = self.key_prefix + key
bucket_name = self.bucket_name
url = f"https://storage.googleapis.com/upload/storage/v1/b/{bucket_name}/o?uploadType=media&name={object_name}"
url = f"https://storage.googleapis.com/upload/storage/v1/b/{bucket_name}/o?uploadType=media&name={quote(object_name, safe='')}"
data = json.dumps(value)
self.sync_client.post(url=url, data=data, headers=headers)
except Exception as e:
Expand All @@ -59,7 +60,7 @@ async def async_set_cache(self, key, value, **kwargs):
headers = self._construct_headers()
object_name = self.key_prefix + key
bucket_name = self.bucket_name
url = f"https://storage.googleapis.com/upload/storage/v1/b/{bucket_name}/o?uploadType=media&name={object_name}"
url = f"https://storage.googleapis.com/upload/storage/v1/b/{bucket_name}/o?uploadType=media&name={quote(object_name, safe='')}"
data = json.dumps(value)
await self.async_client.post(url=url, data=data, headers=headers)
except Exception as e:
Expand All @@ -72,7 +73,7 @@ def get_cache(self, key, **kwargs):
headers = self._construct_headers()
object_name = self.key_prefix + key
bucket_name = self.bucket_name
url = f"https://storage.googleapis.com/storage/v1/b/{bucket_name}/o/{object_name}?alt=media"
url = f"https://storage.googleapis.com/storage/v1/b/{bucket_name}/o/{quote(object_name, safe='')}?alt=media"
response = self.sync_client.get(url=url, headers=headers)
if response.status_code == 200:
cached_response = json.loads(response.text)
Expand All @@ -91,7 +92,7 @@ async def async_get_cache(self, key, **kwargs):
headers = self._construct_headers()
object_name = self.key_prefix + key
bucket_name = self.bucket_name
url = f"https://storage.googleapis.com/storage/v1/b/{bucket_name}/o/{object_name}?alt=media"
url = f"https://storage.googleapis.com/storage/v1/b/{bucket_name}/o/{quote(object_name, safe='')}?alt=media"
response = await self.async_client.get(url=url, headers=headers)
if response.status_code == 200:
return json.loads(response.text)
Expand Down
2 changes: 2 additions & 0 deletions litellm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@
"https://api.inference.wandb.ai/v1",
"https://api.clarifai.com/v2/ext/openai/v1",
"https://api.libertai.io/v1",
"https://pinstripes.io/v1",
]


Expand Down Expand Up @@ -865,6 +866,7 @@
"clarifai",
"docker_model_runner",
"ragflow",
"pinstripes", # Pinstripes - JSON-configured provider
]
openai_text_completion_compatible_providers: List = (
[ # providers that support `/v1/completions`
Expand Down
15 changes: 15 additions & 0 deletions litellm/litellm_core_utils/cloud_storage_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,23 @@
BEDROCK_MANAGED_S3_UPLOAD_PREFIX,
BEDROCK_MANAGED_S3_OUTPUT_PREFIX,
)
MANAGED_CLOUD_STORAGE_SCHEMES = ("s3://", "gs://")
_MAPPING_PROXY_TYPE: type = type(MappingProxyType({}))


def is_managed_cloud_storage_uri(file_id: str) -> bool:
"""
True if file_id is a raw cloud-storage object URI (e.g. ``s3://bucket/key``).

These are internal provider artifacts. On the multi-tenant proxy they must be
retrieved through their managed unified file id so owner/team access is enforced;
a raw URI supplied by a caller bypasses that check.
"""
return isinstance(file_id, str) and file_id.startswith(
MANAGED_CLOUD_STORAGE_SCHEMES
)


_SAFE_OBJECT_COMPONENT_PATTERN = re.compile(r"[^A-Za-z0-9._-]+")


Expand Down
5 changes: 4 additions & 1 deletion litellm/litellm_core_utils/get_llm_provider_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ def get_llm_provider(
elif endpoint == "https://api.inference.wandb.ai/v1":
custom_llm_provider = "wandb"
dynamic_api_key = get_secret_str("WANDB_API_KEY")
elif endpoint == "https://pinstripes.io/v1":
custom_llm_provider = "pinstripes"
dynamic_api_key = get_secret_str("PINSTRIPES_API_KEY")

if api_base is not None and not isinstance(api_base, str):
raise Exception(
Expand Down Expand Up @@ -641,7 +644,7 @@ def _get_openai_compatible_provider_info(
api_base,
dynamic_api_key,
) = litellm.BedrockMantleChatConfig()._get_openai_compatible_provider_info(
api_base, api_key, litellm_params=litellm_params
api_base, api_key, litellm_params=litellm_params, model=model
)
elif custom_llm_provider == "nvidia_nim":
# nvidia_nim is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.endpoints.anyscale.com/v1
Expand Down
4 changes: 2 additions & 2 deletions litellm/litellm_core_utils/health_check_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ async def ahealth_check_wildcard_models(
model_params["litellm_logging_obj"] = litellm_logging_obj
model_params["fallbacks"] = fallback_models
model_params["max_tokens"] = model_params.get(
"max_tokens", 10
) # gpt-5-nano throws errors for max_tokens=1
"max_tokens", 16
) # GPT-5 models require max_output_tokens >= 16
await acompletion(**model_params)
return {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,17 @@ def translate_anthropic_tools_to_openai(
"""
new_tools: List[ChatCompletionToolParam] = []
tool_name_mapping: Dict[str, str] = {}
mapped_tool_params = ["name", "input_schema", "description", "cache_control"]
# "type" is the Anthropic tool type (e.g. "custom"); it must not be
# merged into the OpenAI function `parameters` schema below, or it
# overwrites the real parameters.type ("object") and the provider
# rejects the request. See #30557.
mapped_tool_params = [
"name",
"input_schema",
"description",
"cache_control",
"type",
]

for idx, tool in enumerate(tools):
# Check if this is an Anthropic-native tool that should be kept as-is
Expand Down
68 changes: 10 additions & 58 deletions litellm/llms/bedrock/files/handler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import asyncio
import base64
import os
from types import MappingProxyType
from typing import Any, Coroutine, Mapping, Optional, Tuple, Union, cast
from collections.abc import Mapping
from typing import Any, Coroutine, Optional, Tuple, Union

import httpx

Expand All @@ -17,7 +15,6 @@
FileContentRequest,
HttpxBinaryResponseContent,
)
from litellm.types.utils import SpecialEnums

from ..base_aws_llm import BaseAWSLLM

Expand All @@ -37,40 +34,9 @@ def __init__(self):
)

def _extract_s3_uri_from_file_id(self, file_id: str) -> str:
"""
Extract S3 URI from encoded file ID.

The file ID can be in two formats:
1. Base64-encoded unified file ID containing: llm_output_file_id,s3://bucket/path
2. Direct S3 URI: s3://bucket/litellm-managed-prefix/path

Args:
file_id: Encoded file ID or direct S3 URI
from .transformation import extract_s3_uri_from_file_id

Returns:
S3 URI (e.g., "s3://bucket-name/path/to/file")
"""
# First, try to decode if it's a base64-encoded unified file ID
try:
# Add padding if needed
padded = file_id + "=" * (-len(file_id) % 4)
decoded = base64.urlsafe_b64decode(padded).decode()

# Check if it's a unified file ID format
if decoded.startswith(SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value):
# Extract llm_output_file_id from the decoded string
if "llm_output_file_id," in decoded:
s3_uri = decoded.split("llm_output_file_id,")[1].split(";")[0]
return s3_uri
except Exception:
pass

# If not base64 encoded or doesn't contain llm_output_file_id, accept only
# explicit S3 URIs. Bucket and key validation happens before any S3 call.
if file_id.startswith("s3://"):
return file_id

raise ValueError("file_id must be a managed LiteLLM S3 file id")
return extract_s3_uri_from_file_id(file_id)

def _parse_s3_uri(
self,
Expand All @@ -95,26 +61,12 @@ def _parse_s3_uri(
allow_legacy_cloud_file_ids=allow_legacy_cloud_file_ids,
)

def _get_configured_s3_bucket_name(self, litellm_params: dict) -> str:
trusted_model_credentials = litellm_params.get(
"_litellm_internal_model_credentials"
)
bucket_name = None
if isinstance(trusted_model_credentials, type(MappingProxyType({}))):
trusted_model_credentials_mapping = cast(
Mapping[str, Any], trusted_model_credentials
)
candidate_bucket_name = trusted_model_credentials_mapping.get(
"s3_bucket_name"
)
if isinstance(candidate_bucket_name, str):
bucket_name = candidate_bucket_name
bucket_name = bucket_name or os.getenv("AWS_S3_BUCKET_NAME")
if not bucket_name:
raise ValueError(
"S3 bucket_name is required. Set 's3_bucket_name' in proxy config or AWS_S3_BUCKET_NAME for Bedrock file content retrieval."
)
return bucket_name
def _get_configured_s3_bucket_name(
self, litellm_params: Mapping[str, object]
) -> str:
from .transformation import get_configured_s3_bucket_name

return get_configured_s3_bucket_name(litellm_params)

async def afile_content(
self,
Expand Down
Loading
Loading