Skip to content

Commit e58a561

Browse files
authored
Merge pull request BerriAI#27906 from BerriAI/litellm_internal_staging
[Infra] Promote internal staging to main
2 parents 7af0f05 + de1747d commit e58a561

453 files changed

Lines changed: 8152 additions & 1335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-unit-proxy-db.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ jobs:
143143
tests/proxy_unit_tests/test_proxy_pass_user_config.py
144144
tests/proxy_unit_tests/test_proxy_token_counter.py
145145
tests/proxy_unit_tests/test_request_size_limit_middleware.py
146+
tests/proxy_unit_tests/test_multipart_bypass_repro.py
146147
workers: 4
147148
dist: loadscope
148149
timeout: 15
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
-- AlterTable
2-
ALTER TABLE "LiteLLM_TeamMembership" ADD COLUMN "total_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
2+
ALTER TABLE "LiteLLM_TeamMembership" ADD COLUMN IF NOT EXISTS "total_spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0;
33

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "delegate_auth_to_upstream" BOOLEAN NOT NULL DEFAULT false;

litellm-proxy-extras/litellm_proxy_extras/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ model LiteLLM_MCPServerTable {
323323
registration_url String?
324324
allow_all_keys Boolean @default(false)
325325
available_on_public_internet Boolean @default(true)
326+
delegate_auth_to_upstream Boolean @default(false)
326327
is_byok Boolean @default(false)
327328
byok_description String[] @default([])
328329
byok_api_key_help_url String?

litellm-proxy-extras/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "litellm-proxy-extras"
3-
version = "0.4.71"
3+
version = "0.4.72"
44
description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package."
55
readme = "README.md"
66
requires-python = ">=3.9"
@@ -26,7 +26,7 @@ required-version = ">=0.10.9"
2626
module-root = ""
2727

2828
[tool.commitizen]
29-
version = "0.4.71"
29+
version = "0.4.72"
3030
version_files = [
3131
"pyproject.toml:^version",
3232
"../pyproject.toml:litellm-proxy-extras==",

litellm/_redis.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _get_redis_kwargs():
4141
"retry",
4242
}
4343

44-
include_args = [
44+
include_args = {
4545
"url",
4646
"redis_connect_func",
4747
"gcp_service_account",
@@ -50,9 +50,9 @@ def _get_redis_kwargs():
5050
"azure_client_id",
5151
"azure_tenant_id",
5252
"azure_client_secret",
53-
]
53+
}
5454

55-
available_args = [x for x in arg_spec.args if x not in exclude_args] + include_args
55+
available_args = {x for x in arg_spec.args if x not in exclude_args} | include_args
5656

5757
return available_args
5858

@@ -84,23 +84,23 @@ def _get_redis_cluster_kwargs(client=None):
8484
# Only allow primitive arguments
8585
exclude_args = {"self", "connection_pool", "retry", "host", "port", "startup_nodes"}
8686

87-
available_args = [x for x in arg_spec.args if x not in exclude_args]
88-
available_args.append("password")
89-
available_args.append("username")
90-
available_args.append("ssl")
91-
available_args.append("ssl_cert_reqs")
92-
available_args.append("ssl_check_hostname")
93-
available_args.append("ssl_ca_certs")
94-
available_args.append(
95-
"redis_connect_func"
96-
) # Needed for sync clusters and IAM detection
97-
available_args.append("gcp_service_account")
98-
available_args.append("gcp_ssl_ca_certs")
99-
available_args.append("azure_redis_ad_token")
100-
available_args.append("azure_client_id")
101-
available_args.append("azure_tenant_id")
102-
available_args.append("azure_client_secret")
103-
available_args.append("max_connections")
87+
available_args = {x for x in arg_spec.args if x not in exclude_args}
88+
available_args |= {
89+
"password",
90+
"username",
91+
"ssl",
92+
"ssl_cert_reqs",
93+
"ssl_check_hostname",
94+
"ssl_ca_certs",
95+
"redis_connect_func", # Needed for sync clusters and IAM detection
96+
"gcp_service_account",
97+
"gcp_ssl_ca_certs",
98+
"azure_redis_ad_token",
99+
"azure_client_id",
100+
"azure_tenant_id",
101+
"azure_client_secret",
102+
"max_connections",
103+
}
104104

105105
return available_args
106106

@@ -479,10 +479,24 @@ def init_redis_cluster(redis_kwargs) -> redis.RedisCluster:
479479
return redis.RedisCluster(startup_nodes=new_startup_nodes, **cluster_kwargs) # type: ignore
480480

481481

482+
def _get_redis_sentinel_connection_kwargs(redis_kwargs: dict) -> dict:
483+
connection_kwargs = {}
484+
args = _get_redis_kwargs()
485+
for arg in redis_kwargs:
486+
if arg in args:
487+
connection_kwargs[arg] = redis_kwargs[arg]
488+
489+
return connection_kwargs
490+
491+
482492
def _init_redis_sentinel(redis_kwargs) -> redis.Redis:
483493
sentinel_nodes = redis_kwargs.get("sentinel_nodes")
484494
sentinel_password = redis_kwargs.get("sentinel_password")
485495
service_name = redis_kwargs.get("service_name")
496+
connection_kwargs = _get_redis_sentinel_connection_kwargs(redis_kwargs)
497+
connection_kwargs.setdefault("socket_timeout", REDIS_SOCKET_TIMEOUT)
498+
sentinel_kwargs = dict(connection_kwargs)
499+
sentinel_kwargs["password"] = sentinel_password
486500

487501
if not sentinel_nodes or not service_name:
488502
raise ValueError(
@@ -494,19 +508,22 @@ def _init_redis_sentinel(redis_kwargs) -> redis.Redis:
494508
# Set up the Sentinel client
495509
sentinel = redis.Sentinel(
496510
sentinel_nodes,
497-
socket_timeout=REDIS_SOCKET_TIMEOUT,
498-
password=sentinel_password,
511+
sentinel_kwargs=sentinel_kwargs,
499512
)
500513

501514
# Return the master instance for the given service
502515

503-
return sentinel.master_for(service_name)
516+
return sentinel.master_for(service_name, **connection_kwargs)
504517

505518

506519
def _init_async_redis_sentinel(redis_kwargs) -> async_redis.Redis:
507520
sentinel_nodes = redis_kwargs.get("sentinel_nodes")
508521
sentinel_password = redis_kwargs.get("sentinel_password")
509522
service_name = redis_kwargs.get("service_name")
523+
connection_kwargs = _get_redis_sentinel_connection_kwargs(redis_kwargs)
524+
connection_kwargs.setdefault("socket_timeout", REDIS_SOCKET_TIMEOUT)
525+
sentinel_kwargs = dict(connection_kwargs)
526+
sentinel_kwargs["password"] = sentinel_password
510527

511528
if not sentinel_nodes or not service_name:
512529
raise ValueError(
@@ -518,13 +535,12 @@ def _init_async_redis_sentinel(redis_kwargs) -> async_redis.Redis:
518535
# Set up the Sentinel client
519536
sentinel = async_redis.Sentinel(
520537
sentinel_nodes,
521-
socket_timeout=REDIS_SOCKET_TIMEOUT,
522-
password=sentinel_password,
538+
sentinel_kwargs=sentinel_kwargs,
523539
)
524540

525541
# Return the master instance for the given service
526542

527-
return sentinel.master_for(service_name)
543+
return sentinel.master_for(service_name, **connection_kwargs)
528544

529545

530546
def get_redis_client(**env_overrides):

litellm/budget_manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ def get_users(self):
178178
return list(self.user_dict.keys())
179179

180180
def reset_cost(self, user):
181+
"""
182+
Reset the tracked spend for a user back to zero.
183+
184+
Clears both the aggregate ``current_cost`` and the per-model
185+
``model_cost`` breakdown stored for the given user.
186+
187+
Args:
188+
user: The user identifier whose cost should be reset.
189+
190+
Returns:
191+
dict: ``{"user": <updated user record>}`` reflecting the reset state.
192+
"""
181193
self.user_dict[user]["current_cost"] = 0
182194
self.user_dict[user]["model_cost"] = {}
183195
return {"user": self.user_dict[user]}

litellm/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,15 @@
15691569
os.getenv("DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL", 60)
15701570
)
15711571
DEFAULT_ACCESS_GROUP_CACHE_TTL = int(os.getenv("DEFAULT_ACCESS_GROUP_CACHE_TTL", 600))
1572+
# Short TTL for negative MCP access-group existence lookups. Keeps unauthenticated
1573+
# callers from forcing a DB query per request for unknown names, while bounding
1574+
# staleness so a transient DB error (which surfaces as an empty list) cannot
1575+
# hide a real group for long.
1576+
DEFAULT_MCP_ACCESS_GROUP_NEGATIVE_CACHE_TTL = 10
1577+
# Maximum number of comma-separated MCP server / access-group tokens accepted
1578+
# in a single ``/{name1,name2,...}/mcp`` URL. Bounds the per-request DB / cache
1579+
# fan-out an authenticated caller can trigger by stuffing the path with tokens.
1580+
DEFAULT_MCP_NAMESPACE_CSV_MAX_TOKENS = 16
15721581

15731582
# Sentry Scrubbing Configuration
15741583
SENTRY_DENYLIST = [

litellm/integrations/prometheus.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,17 @@ async def async_log_success_event(self, kwargs, response_obj, start_time, end_ti
12261226
label_context=label_context,
12271227
)
12281228

1229+
# Provider-agnostic fallback: providers like Bedrock and Vertex don't return
1230+
# x-ratelimit-remaining-* headers, so the gauges above only fire for OpenAI /
1231+
# Anthropic / Azure. When the proxy router has tpm/rpm configured for the
1232+
# model_group, derive remaining from configured-limit minus current usage so
1233+
# the same metric is populated for any provider.
1234+
await self._async_set_router_remaining_metrics(
1235+
standard_logging_payload=standard_logging_payload, # type: ignore
1236+
enum_values=enum_values,
1237+
label_context=label_context,
1238+
)
1239+
12291240
# cache metrics
12301241
self._increment_cache_metrics(
12311242
standard_logging_payload=standard_logging_payload, # type: ignore
@@ -2199,6 +2210,99 @@ def _set_deployment_tpm_rpm_limit_metrics(
21992210
)
22002211
self.litellm_deployment_rpm_limit.labels(**_labels).set(rpm)
22012212

2213+
async def _async_set_router_remaining_metrics(
2214+
self,
2215+
standard_logging_payload: StandardLoggingPayload,
2216+
enum_values: UserAPIKeyLabelValues,
2217+
label_context: Optional[PrometheusLabelFactoryContext] = None,
2218+
) -> None:
2219+
"""
2220+
Populate ``litellm_remaining_tokens_metric`` /
2221+
``litellm_remaining_requests_metric`` from the router's internal usage
2222+
counters when the upstream provider did not return
2223+
``x-ratelimit-remaining-*`` response headers.
2224+
2225+
OpenAI / Anthropic / Azure return remaining tokens/requests in response
2226+
headers, but Bedrock and Vertex AI do not. This fallback computes
2227+
``configured_limit - current_usage`` via
2228+
``Router.get_remaining_model_group_usage`` so the same gauges are
2229+
emitted for every provider when tpm/rpm is configured on the
2230+
deployment.
2231+
"""
2232+
try:
2233+
additional_headers = (
2234+
standard_logging_payload.get("hidden_params", {}) or {}
2235+
).get("additional_headers") or {}
2236+
2237+
already_have_tokens = (
2238+
additional_headers.get("x_ratelimit_remaining_tokens") is not None
2239+
)
2240+
already_have_requests = (
2241+
additional_headers.get("x_ratelimit_remaining_requests") is not None
2242+
)
2243+
if already_have_tokens and already_have_requests:
2244+
return
2245+
2246+
model_group = standard_logging_payload.get("model_group")
2247+
if not model_group:
2248+
return
2249+
2250+
try:
2251+
from litellm.proxy.proxy_server import llm_router
2252+
except ImportError:
2253+
llm_router = None
2254+
2255+
if llm_router is None:
2256+
return
2257+
2258+
try:
2259+
remaining_usage = await llm_router.get_remaining_model_group_usage(
2260+
model_group
2261+
)
2262+
except Exception as e:
2263+
verbose_logger.exception(
2264+
"Prometheus: get_remaining_model_group_usage failed for "
2265+
"model_group=%s: %s",
2266+
model_group,
2267+
e,
2268+
)
2269+
return
2270+
2271+
if not remaining_usage:
2272+
return
2273+
2274+
remaining_tokens = remaining_usage.get("x-ratelimit-remaining-tokens")
2275+
remaining_requests = remaining_usage.get("x-ratelimit-remaining-requests")
2276+
2277+
if not already_have_tokens and remaining_tokens is not None:
2278+
_labels = prometheus_label_factory(
2279+
supported_enum_labels=self.get_labels_for_metric(
2280+
metric_name="litellm_remaining_tokens_metric"
2281+
),
2282+
enum_values=enum_values,
2283+
label_context=label_context,
2284+
)
2285+
self.litellm_remaining_tokens_metric.labels(**_labels).set(
2286+
remaining_tokens
2287+
)
2288+
2289+
if not already_have_requests and remaining_requests is not None:
2290+
_labels = prometheus_label_factory(
2291+
supported_enum_labels=self.get_labels_for_metric(
2292+
metric_name="litellm_remaining_requests_metric"
2293+
),
2294+
enum_values=enum_values,
2295+
label_context=label_context,
2296+
)
2297+
self.litellm_remaining_requests_metric.labels(**_labels).set(
2298+
remaining_requests
2299+
)
2300+
except Exception as e:
2301+
verbose_logger.exception(
2302+
"Prometheus Error: _async_set_router_remaining_metrics. "
2303+
"Exception occured - {}".format(str(e))
2304+
)
2305+
22022306
def set_llm_deployment_success_metrics(
22032307
self,
22042308
request_kwargs: dict,

litellm/litellm_core_utils/prompt_templates/factory.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4977,8 +4977,9 @@ def add_thinking_blocks_to_assistant_content(
49774977
)
49784978
if reasoning_text and not reasoning_text.get("signature"):
49794979
reasoning_text_text = reasoning_text["text"]
4980-
assistants_part = BedrockContentBlock(text=reasoning_text_text)
4981-
assistant_parts.append(assistants_part)
4980+
if reasoning_text_text.strip():
4981+
assistants_part = BedrockContentBlock(text=reasoning_text_text)
4982+
assistant_parts.append(assistants_part)
49824983
else:
49834984
filtered_thinking_blocks.append(block)
49844985
if len(filtered_thinking_blocks) > 0:

0 commit comments

Comments
 (0)