From 99ef233a69b6fef335a8b8177de6dbd4b77aa100 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 15 Jan 2026 13:30:43 +0530 Subject: [PATCH 001/212] fix: Preserved nullable object fields by carrying schema properties --- litellm/llms/vertex_ai/common_utils.py | 18 ++++++- .../test_amazing_vertex_completion.py | 54 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 1864ef734c0..63decfea854 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -1,4 +1,5 @@ import re +from copy import deepcopy from enum import Enum from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, get_type_hints @@ -617,7 +618,7 @@ def convert_anyof_null_to_nullable(schema, depth=0): if anyof is not None: contains_null = False for atype in anyof: - if atype == {"type": "null"}: + if isinstance(atype, dict) and atype.get("type") == "null": # remove null type anyof.remove(atype) contains_null = True @@ -735,7 +736,20 @@ def _convert_schema_types(schema, depth=0): type_val = schema["type"] if isinstance(type_val, list) and len(type_val) > 1: # Convert ["string", "number"] -> {"anyOf": [{"type": "STRING"}, {"type": "NUMBER"}]} - schema["anyOf"] = [{"type": t} for t in type_val if isinstance(t, str)] + # Preserve other schema fields by copying them into each non-null anyOf item. + base_schema = {k: v for k, v in schema.items() if k not in {"type", "anyOf"}} + any_of: List[Dict[str, Any]] = [] + for t in type_val: + if not isinstance(t, str): + continue + if t == "null": + # Keep null entry minimal so we can strip it later. + any_of.append({"type": "null"}) + continue + item_schema = deepcopy(base_schema) + item_schema["type"] = t + any_of.append(item_schema) + schema["anyOf"] = any_of schema.pop("type") elif isinstance(type_val, list) and len(type_val) == 1: schema["type"] = type_val[0] diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 0373f5f4356..745c90201f3 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -3598,6 +3598,60 @@ def test_vertex_schema_test(): print(response) +def test_gemini_nullable_object_tool_schema_httpx(): + """ + Ensure nullable object tool params preserve nested properties in Vertex schema conversion. + """ + load_vertex_ai_credentials() + litellm._turn_on_debug() + + + tools = [{ + "type": "function", + "strict": True, + "function": { + "name": "create_support_ticket", + "description": "Create a paid user support ticket", + "parameters": { + "type": "object", + "additionalProperties": False, + "required": ["ticket_id", "customer_context"], + "properties": { + "ticket_id": { + "type": "string", + "description": "Unique identifier for the support ticket" + }, + "customer_context": { + "type": ["object", "null"], + "description": "Context about the paid customer, if available", + "additionalProperties": False, + "required": ["user_id", "plan"], + "properties": { + "user_id": { + "type": "string", + "description": "Internal user identifier" + }, + "plan": { + "type": "string", + "description": "Subscription plan name (e.g. pro, enterprise)" + } + } + } + } + } + } + }] + + response = litellm.completion( + model="vertex_ai/gemini-2.5-flash", + messages=[{"role": "user", "content": "call the tool"}], + tools=tools, + tool_choice="required", + ) + + print(response) + + def test_vertex_ai_response_id(): """Test that litellm preserves the response ID from Vertex AI's API for non-streaming responses""" from litellm.llms.custom_httpx.http_handler import HTTPHandler From 52519c26814b260e20e8c55de54ee548ddc5ad70 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 16 Jan 2026 18:09:15 +0530 Subject: [PATCH 002/212] Fix: _convert_schema_types --- litellm/llms/vertex_ai/common_utils.py | 34 +++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 63decfea854..e7d73e421a8 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -735,9 +735,15 @@ def _convert_schema_types(schema, depth=0): if "type" in schema: type_val = schema["type"] if isinstance(type_val, list) and len(type_val) > 1: - # Convert ["string", "number"] -> {"anyOf": [{"type": "STRING"}, {"type": "NUMBER"}]} - # Preserve other schema fields by copying them into each non-null anyOf item. - base_schema = {k: v for k, v in schema.items() if k not in {"type", "anyOf"}} + # Convert type arrays to anyOf format + # For object types, we need to move object-specific fields into the anyOf item + # For primitive types, we only include the type field + + # Fields that should stay at parent level (metadata) + metadata_fields = {"description", "title", "default", "examples"} + # Fields that are specific to object/array types and should move into anyOf + type_specific_fields = {"properties", "required", "additionalProperties", "items", "minItems", "maxItems", "minProperties", "maxProperties"} + any_of: List[Dict[str, Any]] = [] for t in type_val: if not isinstance(t, str): @@ -746,9 +752,25 @@ def _convert_schema_types(schema, depth=0): # Keep null entry minimal so we can strip it later. any_of.append({"type": "null"}) continue - item_schema = deepcopy(base_schema) - item_schema["type"] = t - any_of.append(item_schema) + + # For object/array types, include type-specific fields + if t in ("object", "array"): + item_schema = {"type": t} + # Move type-specific fields into this anyOf item + for field in type_specific_fields: + if field in schema: + item_schema[field] = deepcopy(schema[field]) + any_of.append(item_schema) + else: + # For primitive types, only include the type + any_of.append({"type": t}) + + # Remove type-specific fields from parent if we moved them into anyOf + has_object_or_array = any(t in ("object", "array") for t in type_val if isinstance(t, str)) + if has_object_or_array: + for field in type_specific_fields: + schema.pop(field, None) + schema["anyOf"] = any_of schema.pop("type") elif isinstance(type_val, list) and len(type_val) == 1: From f28340a39d95dd736578eea8b1b390688932bc9e Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 16 Jan 2026 18:15:18 +0530 Subject: [PATCH 003/212] Fix all mypy issues --- litellm/llms/vertex_ai/common_utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index e7d73e421a8..54a6ba3ad7a 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -735,12 +735,7 @@ def _convert_schema_types(schema, depth=0): if "type" in schema: type_val = schema["type"] if isinstance(type_val, list) and len(type_val) > 1: - # Convert type arrays to anyOf format - # For object types, we need to move object-specific fields into the anyOf item - # For primitive types, we only include the type field - - # Fields that should stay at parent level (metadata) - metadata_fields = {"description", "title", "default", "examples"} + # Convert type arrays to anyOf format # Fields that are specific to object/array types and should move into anyOf type_specific_fields = {"properties", "required", "additionalProperties", "items", "minItems", "maxItems", "minProperties", "maxProperties"} From ac5c5ee302729a9833277e99b74afbef7948b51e Mon Sep 17 00:00:00 2001 From: Quentin Machu Date: Mon, 26 Jan 2026 17:42:56 -0500 Subject: [PATCH 004/212] fix: Search tools not found when using per-request routers **Root Cause:** When API keys or teams have router_settings configured, the proxy creates per-request Router instances from user_config. These new routers were missing search_tools from the main router, causing "search tool not found" errors despite search_tools being configured. **The Fix:** 1. **common_request_processing.py (lines 554-556):** Pass search_tools from main router to user_config so per-request routers inherit them 2. **proxy_server.py (line 3171):** Remove `if len(_model_list) > 0` check to allow router creation with empty model list (needed for search-tools-only use case) 3. **proxy_server.py (line 746):** Remove redundant search_tools loading code (already handled by _init_search_tools_in_db() called during startup) --- litellm/proxy/common_request_processing.py | 7 ++++++- litellm/proxy/proxy_server.py | 23 +++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 0d3e61b75c7..4f0c59474f1 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -547,9 +547,14 @@ class ProxyBaseLLMRequestProcessing: # Get model_list from current router model_list = llm_router.get_model_list() if model_list is not None: - # Create user_config with model_list and router_settings + # Create user_config with model_list, search_tools, and router_settings # This creates a per-request router with the hierarchical settings user_config = {"model_list": model_list, **router_settings} + + # Include search_tools from main router so per-request router has them + if hasattr(llm_router, "search_tools") and llm_router.search_tools: + user_config["search_tools"] = llm_router.search_tools + self.data["user_config"] = user_config if "messages" in self.data and self.data["messages"]: diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 16cdd9da64b..056367243f9 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3173,17 +3173,18 @@ class ProxyConfig: _model_list: list = self.decrypt_model_list_from_db( new_models=models_list ) - if len(_model_list) > 0: - verbose_proxy_logger.debug(f"_model_list: {_model_list}") - llm_router = litellm.Router( - model_list=_model_list, - router_general_settings=RouterGeneralSettings( - async_only_mode=True # only init async clients - ), - search_tools=search_tools, - ignore_invalid_deployments=True, - ) - verbose_proxy_logger.debug(f"updated llm_router: {llm_router}") + # Create router even with empty model list to support search_tools + # Router can function with model_list=[] and only search_tools + verbose_proxy_logger.debug(f"_model_list: {_model_list}") + llm_router = litellm.Router( + model_list=_model_list, + router_general_settings=RouterGeneralSettings( + async_only_mode=True # only init async clients + ), + search_tools=search_tools, + ignore_invalid_deployments=True, + ) + verbose_proxy_logger.debug(f"updated llm_router: {llm_router}") else: verbose_proxy_logger.debug(f"len new_models: {len(models_list)}") ## DELETE MODEL LOGIC From a9eae5937fb58a53047e581d4950b10b956286ce Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 31 Jan 2026 16:04:52 -0800 Subject: [PATCH 005/212] Override router settings --- litellm/proxy/common_request_processing.py | 16 +-- litellm/proxy/route_llm_request.py | 35 +++-- litellm/router.py | 11 +- .../proxy/test_common_request_processing.py | 34 +++-- .../proxy/test_route_llm_request.py | 131 ++++++++++++------ 5 files changed, 144 insertions(+), 83 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index b69f175e5d2..ee38d56c855 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -626,20 +626,10 @@ class ProxyBaseLLMRequestProcessing: ) # If router_settings found (from key, team, or global), apply them - # This ensures key/team settings override global settings + # Pass settings as per-request overrides instead of creating a new Router + # This avoids expensive Router instantiation on each request if router_settings is not None and router_settings: - # Get model_list from current router - model_list = llm_router.get_model_list() - if model_list is not None: - # Create user_config with model_list, search_tools, and router_settings - # This creates a per-request router with the hierarchical settings - user_config = {"model_list": model_list, **router_settings} - - # Include search_tools from main router so per-request router has them - if hasattr(llm_router, "search_tools") and llm_router.search_tools: - user_config["search_tools"] = llm_router.search_tools - - self.data["user_config"] = user_config + self.data["router_settings_override"] = router_settings if "messages" in self.data and self.data["messages"]: logging_obj.update_messages(self.data["messages"]) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index c6a93164d49..e2749eb8187 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -197,18 +197,33 @@ async def route_request( models = [model.strip() for model in data.pop("model").split(",")] return llm_router.abatch_completion(models=models, **data) - elif "user_config" in data: - router_config = data.pop("user_config") + elif "router_settings_override" in data: + # Apply per-request router settings overrides from key/team config + # Instead of creating a new Router (expensive), merge settings into kwargs + # The Router already supports per-request overrides for these settings + override_settings = data.pop("router_settings_override") - # Filter router_config to only include valid Router.__init__ arguments - # This prevents TypeError when invalid parameters are stored in the database - valid_args = litellm.Router.get_valid_args() - filtered_config = {k: v for k, v in router_config.items() if k in valid_args} + # Settings that the Router accepts as per-request kwargs + # These override the global router settings for this specific request + per_request_settings = [ + "fallbacks", + "context_window_fallbacks", + "content_policy_fallbacks", + "num_retries", + "timeout", + "model_group_retry_policy", + ] - user_router = litellm.Router(**filtered_config) - ret_val = getattr(user_router, f"{route_type}")(**data) - user_router.discard() - return ret_val + # Merge override settings into data (only if not already set in request) + for key in per_request_settings: + if key in override_settings and key not in data: + data[key] = override_settings[key] + + # Use main router with overridden kwargs + if llm_router is not None: + return getattr(llm_router, f"{route_type}")(**data) + else: + return getattr(litellm, f"{route_type}")(**data) elif llm_router is not None: # Skip model-based routing for container operations if route_type in [ diff --git a/litellm/router.py b/litellm/router.py index 6c191c8ab03..ed480d6468a 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -4880,6 +4880,10 @@ class Router: content_policy_fallbacks = kwargs.pop( "content_policy_fallbacks", self.content_policy_fallbacks ) + # Support per-request model_group_retry_policy override (from key/team settings) + model_group_retry_policy = kwargs.pop( + "model_group_retry_policy", self.model_group_retry_policy + ) model_group: Optional[str] = kwargs.get("model") num_retries = kwargs.pop("num_retries") @@ -4928,7 +4932,7 @@ class Router: _retry_policy_applies = False if ( self.retry_policy is not None - or self.model_group_retry_policy is not None + or model_group_retry_policy is not None ): # get num_retries from retry policy # Use the model_group captured at the start of the function, or get it from metadata @@ -4936,9 +4940,12 @@ class Router: _model_group_for_retry_policy = ( model_group or _metadata.get("model_group") or kwargs.get("model") ) - _retry_policy_retries = self.get_num_retries_from_retry_policy( + # Use per-request model_group_retry_policy if provided, otherwise use self + _retry_policy_retries = _get_num_retries_from_retry_policy( exception=original_exception, model_group=_model_group_for_retry_policy, + model_group_retry_policy=model_group_retry_policy, + retry_policy=self.retry_policy, ) if _retry_policy_retries is not None: num_retries = _retry_policy_retries diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 6edcdab15c0..d2abe80977a 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -78,9 +78,16 @@ class TestProxyBaseLLMRequestProcessing: assert data_passed["litellm_call_id"] == returned_data["litellm_call_id"] @pytest.mark.asyncio - async def test_should_apply_hierarchical_router_settings_to_user_config( + async def test_should_apply_hierarchical_router_settings_as_override( self, monkeypatch ): + """ + Test that hierarchical router settings are stored as router_settings_override + instead of creating a full user_config with model_list. + + This approach avoids expensive per-request Router instantiation by passing + settings as kwargs overrides to the main router. + """ processing_obj = ProxyBaseLLMRequestProcessing(data={}) mock_request = MagicMock(spec=Request) mock_request.headers = {} @@ -117,12 +124,7 @@ class TestProxyBaseLLMRequestProcessing: return_value=mock_router_settings ) - mock_model_list = [ - {"model_name": "gpt-3.5-turbo", "litellm_params": {"model": "gpt-3.5-turbo"}}, - {"model_name": "gpt-4", "litellm_params": {"model": "gpt-4"}}, - ] mock_llm_router = MagicMock() - mock_llm_router.get_model_list = MagicMock(return_value=mock_model_list) mock_prisma_client = MagicMock() monkeypatch.setattr( @@ -146,14 +148,20 @@ class TestProxyBaseLLMRequestProcessing: user_api_key_dict=mock_user_api_key_dict, prisma_client=mock_prisma_client, ) - mock_llm_router.get_model_list.assert_called_once() + # get_model_list should NOT be called - we no longer copy model list for per-request routers + mock_llm_router.get_model_list.assert_not_called() - assert "user_config" in returned_data - user_config = returned_data["user_config"] - assert user_config["model_list"] == mock_model_list - assert user_config["routing_strategy"] == "least-busy" - assert user_config["timeout"] == 30.0 - assert user_config["num_retries"] == 3 + # Settings should be stored as router_settings_override (not user_config) + # This allows passing them as kwargs to the main router instead of creating a new one + assert "router_settings_override" in returned_data + assert "user_config" not in returned_data + + router_settings_override = returned_data["router_settings_override"] + assert router_settings_override["routing_strategy"] == "least-busy" + assert router_settings_override["timeout"] == 30.0 + assert router_settings_override["num_retries"] == 3 + # model_list should NOT be in the override settings + assert "model_list" not in router_settings_override @pytest.mark.asyncio async def test_stream_timeout_header_processing(self): diff --git a/tests/test_litellm/proxy/test_route_llm_request.py b/tests/test_litellm/proxy/test_route_llm_request.py index 90eace63714..1283d2ccbe7 100644 --- a/tests/test_litellm/proxy/test_route_llm_request.py +++ b/tests/test_litellm/proxy/test_route_llm_request.py @@ -137,62 +137,103 @@ async def test_route_request_no_model_required_with_router_settings_and_no_route @pytest.mark.asyncio -async def test_route_request_with_invalid_router_params(): +async def test_route_request_with_router_settings_override(): """ - Test that route_request filters out invalid Router init params from 'user_config'. - This covers the fix for https://github.com/BerriAI/litellm/issues/19693 + Test that route_request handles router_settings_override by merging settings into kwargs + instead of creating a new Router (which is expensive and was the old behavior). """ - import litellm - from litellm.router import Router - from unittest.mock import AsyncMock - - # Mock data with user_config containing invalid keys (simulating DB entry) + # Mock data with router_settings_override containing per-request settings data = { "model": "gpt-3.5-turbo", - "user_config": { - "model_list": [ - { - "model_name": "gpt-3.5-turbo", - "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "test"}, - } - ], - "model_alias_map": {"alias": "real_model"}, # INVALID PARAM - "invalid_garbage_key": "crash_me", # INVALID PARAM + "messages": [{"role": "user", "content": "Hello"}], + "router_settings_override": { + "fallbacks": [{"gpt-3.5-turbo": ["gpt-4"]}], + "num_retries": 5, + "timeout": 30, + "model_group_retry_policy": {"gpt-3.5-turbo": {"RateLimitErrorRetries": 3}}, + # These settings should be ignored (not in per_request_settings list) + "routing_strategy": "least-busy", + "model_group_alias": {"alias": "real_model"}, }, } - # We expect Router(**config) to succeed because of the filtering. - # If filtering fails, this will raise TypeError and fail the test. + llm_router = MagicMock() + llm_router.acompletion.return_value = "success" + + response = await route_request(data, llm_router, None, "acompletion") + + assert response == "success" + # Verify the router method was called with merged settings + call_kwargs = llm_router.acompletion.call_args[1] + assert call_kwargs["fallbacks"] == [{"gpt-3.5-turbo": ["gpt-4"]}] + assert call_kwargs["num_retries"] == 5 + assert call_kwargs["timeout"] == 30 + assert call_kwargs["model_group_retry_policy"] == {"gpt-3.5-turbo": {"RateLimitErrorRetries": 3}} + # Verify unsupported settings were NOT merged + assert "routing_strategy" not in call_kwargs + assert "model_group_alias" not in call_kwargs + # Verify router_settings_override was removed from data + assert "router_settings_override" not in call_kwargs + + +@pytest.mark.asyncio +async def test_route_request_with_router_settings_override_no_router(): + """ + Test that router_settings_override works when no router is provided, + falling back to litellm module directly. + """ + import litellm + + data = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}], + "router_settings_override": { + "fallbacks": [{"gpt-3.5-turbo": ["gpt-4"]}], + "num_retries": 3, + }, + } + + # Use MagicMock explicitly to avoid auto-AsyncMock behavior in Python 3.12+ + mock_completion = MagicMock(return_value="success") + original_acompletion = litellm.acompletion + litellm.acompletion = mock_completion + try: - # route_request calls getattr(user_router, route_type)(**data) - # We'll mock the internal call to avoid making real network requests - with pytest.MonkeyPatch.context() as m: - # Mock the method that gets called on the router instance - # We don't easily have access to the instance created INSIDE existing route_request - # So we will wrap litellm.Router to spy on it or verify it doesn't crash + response = await route_request(data, None, None, "acompletion") - original_router_init = litellm.Router.__init__ + assert response == "success" + # Verify litellm.acompletion was called with merged settings + call_kwargs = mock_completion.call_args[1] + assert call_kwargs["fallbacks"] == [{"gpt-3.5-turbo": ["gpt-4"]}] + assert call_kwargs["num_retries"] == 3 + finally: + litellm.acompletion = original_acompletion - def safe_router_init(self, **kwargs): - # Verify that invalid keys are NOT present in kwargs - assert "model_alias_map" not in kwargs - assert "invalid_garbage_key" not in kwargs - # Call original init (which would raise TypeError if invalid keys were present) - original_router_init(self, **kwargs) - m.setattr(litellm.Router, "__init__", safe_router_init) +@pytest.mark.asyncio +async def test_route_request_with_router_settings_override_preserves_existing(): + """ + Test that router_settings_override does not override settings already in the request. + Request-level settings take precedence over key/team settings. + """ + data = { + "model": "gpt-3.5-turbo", + "messages": [{"role": "user", "content": "Hello"}], + "num_retries": 10, # Request-level setting + "router_settings_override": { + "num_retries": 3, # Key/team setting - should NOT override + "timeout": 30, # Key/team setting - should be applied + }, + } - # Use 'acompletion' as the route_type - # We also need to mock the completion method to avoid real calls - m.setattr(Router, "acompletion", AsyncMock(return_value="success")) + llm_router = MagicMock() + llm_router.acompletion.return_value = "success" - response = await route_request(data, None, None, "acompletion") - assert response == "success" + response = await route_request(data, llm_router, None, "acompletion") - except TypeError as e: - pytest.fail( - f"route_request raised TypeError, implying invalid params were passed to Router: {e}" - ) - except Exception: - # Other exceptions might happen (e.g. valid config issues) but we care about TypeError here - pass + assert response == "success" + call_kwargs = llm_router.acompletion.call_args[1] + # Request-level num_retries should take precedence + assert call_kwargs["num_retries"] == 10 + # Key/team timeout should be applied since not in request + assert call_kwargs["timeout"] == 30 From ec4f7a38ffb740fc4303a17f3475e9d33680e558 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 15:49:05 -0800 Subject: [PATCH 006/212] Add option for authentication for public AI Hub --- .../proxy_setting_endpoints.py | 6 ++ .../UISettings/UISettings.test.tsx | 75 +++++++++++++++++++ .../AdminSettings/UISettings/UISettings.tsx | 31 ++++++++ 3 files changed, 112 insertions(+) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 30ec0766dbf..f626ad7eb14 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -83,6 +83,11 @@ class UISettings(BaseModel): description="List of page keys that internal users (non-admins) can see in the UI sidebar. If not set, all pages are visible based on role permissions.", ) + require_auth_for_public_ai_hub: bool = Field( + default=False, + description="If true, requires authentication for accessing the public AI Hub." + ) + class UISettingsResponse(SettingsResponse): """Response model for UI settings""" @@ -95,6 +100,7 @@ ALLOWED_UI_SETTINGS_FIELDS = { "disable_model_add_for_internal_users", "disable_team_admin_delete_team_user", "enabled_ui_pages_internal_users", + "require_auth_for_public_ai_hub", } diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx index 31dcfc102ec..639564bbd35 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx @@ -20,6 +20,13 @@ vi.mock("@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings", () => ({ useUpdateUISettings: mockUseUpdateUISettings, })); +vi.mock("@/components/molecules/notifications_manager", () => ({ + default: { + success: vi.fn(), + fromBackend: vi.fn(), + }, +})); + const buildSettingsResponse = (overrides?: Partial>) => ({ data: { field_schema: { @@ -28,10 +35,18 @@ const buildSettingsResponse = (overrides?: Partial>) => disable_model_add_for_internal_users: { description: "Disable model add for internal users", }, + disable_team_admin_delete_team_user: { + description: "Disable team admin delete team user", + }, + require_auth_for_public_ai_hub: { + description: "Require authentication for public AI Hub", + }, }, }, values: { disable_model_add_for_internal_users: false, + disable_team_admin_delete_team_user: false, + require_auth_for_public_ai_hub: false, }, }, isLoading: false, @@ -57,6 +72,8 @@ describe("UISettings", () => { expect(screen.getByText("UI Settings")).toBeInTheDocument(); expect(screen.getByRole("switch", { name: "Disable model add for internal users" })).toBeInTheDocument(); + expect(screen.getByRole("switch", { name: "Disable team admin delete team user" })).toBeInTheDocument(); + expect(screen.getByRole("switch", { name: "Require authentication for public AI Hub" })).toBeInTheDocument(); }); it("should toggle setting and call update", () => { @@ -87,4 +104,62 @@ describe("UISettings", () => { ); expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); }); + + it("should toggle disable team admin delete team user setting and call update", () => { + const mutateMock = vi.fn((_settings, options) => { + options?.onSuccess?.(); + }); + + mockUseUpdateUISettings.mockReturnValue({ + mutate: mutateMock, + isPending: false, + error: null, + }); + + render(); + + const toggle = screen.getByRole("switch", { name: "Disable team admin delete team user" }); + + act(() => { + fireEvent.click(toggle); + }); + + expect(mutateMock).toHaveBeenCalledWith( + { disable_team_admin_delete_team_user: true }, + expect.objectContaining({ + onSuccess: expect.any(Function), + onError: expect.any(Function), + }), + ); + expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); + }); + + it("should toggle require auth for public AI Hub setting and call update", () => { + const mutateMock = vi.fn((_settings, options) => { + options?.onSuccess?.(); + }); + + mockUseUpdateUISettings.mockReturnValue({ + mutate: mutateMock, + isPending: false, + error: null, + }); + + render(); + + const toggle = screen.getByRole("switch", { name: "Require authentication for public AI Hub" }); + + act(() => { + fireEvent.click(toggle); + }); + + expect(mutateMock).toHaveBeenCalledWith( + { require_auth_for_public_ai_hub: true }, + expect.objectContaining({ + onSuccess: expect.any(Function), + onError: expect.any(Function), + }), + ); + expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); + }); }); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx index 6cc9cbf4309..a43f0e9d42d 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -15,6 +15,7 @@ export default function UISettings() { const schema = data?.field_schema; const property = schema?.properties?.disable_model_add_for_internal_users; const disableTeamAdminDeleteProperty = schema?.properties?.disable_team_admin_delete_team_user; + const requireAuthForPublicAIHubProperty = schema?.properties?.require_auth_for_public_ai_hub; const enabledPagesProperty = schema?.properties?.enabled_ui_pages_internal_users; const values = data?.values ?? {}; const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users); @@ -59,6 +60,20 @@ export default function UISettings() { }); }; + const handleToggleRequireAuthForPublicAIHub = (checked: boolean) => { + updateSettings( + { require_auth_for_public_ai_hub: checked }, + { + onSuccess: () => { + NotificationManager.success("UI settings updated successfully"); + }, + onError: (error) => { + NotificationManager.fromBackend(error); + }, + }, + ); + }; + return ( {isLoading ? ( @@ -113,6 +128,22 @@ export default function UISettings() { + + + + Require authentication for public AI Hub + {requireAuthForPublicAIHubProperty?.description && ( + {requireAuthForPublicAIHubProperty.description} + )} + + + {/* Page Visibility for Internal Users */} From c47866afb98214d7f5d77353e1090eb56d944085 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 16:37:45 -0800 Subject: [PATCH 007/212] useAuthorized refactor --- .../(dashboard)/hooks/useAuthorized.test.ts | 51 ++++++++---- .../app/(dashboard)/hooks/useAuthorized.ts | 44 ++++------- .../src/utils/jwtUtils.test.ts | 79 ++++++++++++++++++- ui/litellm-dashboard/src/utils/jwtUtils.ts | 15 ++++ 4 files changed, 144 insertions(+), 45 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts index 78eddbd8d3c..ef4a779b50b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts @@ -8,12 +8,13 @@ import useAuthorized from "./useAuthorized"; // Unmock useAuthorized to test the actual implementation vi.unmock("@/app/(dashboard)/hooks/useAuthorized"); -const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock, isJwtExpiredMock } = vi.hoisted(() => ({ +const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock, decodeTokenMock, checkTokenValidityMock } = vi.hoisted(() => ({ replaceMock: vi.fn(), clearTokenCookiesMock: vi.fn(), getProxyBaseUrlMock: vi.fn(() => "http://proxy.example"), getUiConfigMock: vi.fn(), - isJwtExpiredMock: vi.fn(), + decodeTokenMock: vi.fn(), + checkTokenValidityMock: vi.fn(), })); vi.mock("next/navigation", () => ({ @@ -43,7 +44,8 @@ vi.mock("@/utils/jwtUtils", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - isJwtExpired: isJwtExpiredMock, + decodeToken: decodeTokenMock, + checkTokenValidity: checkTokenValidityMock, }; }); @@ -77,7 +79,8 @@ describe("useAuthorized", () => { clearTokenCookiesMock.mockReset(); getProxyBaseUrlMock.mockClear(); getUiConfigMock.mockReset(); - isJwtExpiredMock.mockReset(); + decodeTokenMock.mockReset(); + checkTokenValidityMock.mockReset(); clearCookie(); }); @@ -88,9 +91,8 @@ describe("useAuthorized", () => { auto_redirect_to_sso: false, admin_ui_disabled: false, }); - isJwtExpiredMock.mockReturnValue(false); - - const token = createJwt({ + + const decodedPayload = { key: "api-key-123", user_id: "user-1", user_email: "user@example.com", @@ -98,7 +100,12 @@ describe("useAuthorized", () => { premium_user: true, disabled_non_admin_personal_key_creation: false, login_method: "username_password", - }); + }; + + decodeTokenMock.mockReturnValue(decodedPayload); + checkTokenValidityMock.mockReturnValue(true); + + const token = createJwt(decodedPayload); document.cookie = `token=${token}; path=/;`; const { result } = renderHook(() => useAuthorized(), { wrapper }); @@ -126,6 +133,9 @@ describe("useAuthorized", () => { admin_ui_disabled: false, }); + decodeTokenMock.mockReturnValue(null); + checkTokenValidityMock.mockReturnValue(false); + document.cookie = "token=invalid-token; path=/;"; const { result } = renderHook(() => useAuthorized(), { wrapper }); @@ -146,9 +156,8 @@ describe("useAuthorized", () => { auto_redirect_to_sso: false, admin_ui_disabled: true, }); - isJwtExpiredMock.mockReturnValue(false); - const token = createJwt({ + const decodedPayload = { key: "api-key-123", user_id: "user-1", user_email: "user@example.com", @@ -156,7 +165,12 @@ describe("useAuthorized", () => { premium_user: true, disabled_non_admin_personal_key_creation: false, login_method: "username_password", - }); + }; + + decodeTokenMock.mockReturnValue(decodedPayload); + checkTokenValidityMock.mockReturnValue(true); + + const token = createJwt(decodedPayload); document.cookie = `token=${token}; path=/;`; const { result } = renderHook(() => useAuthorized(), { wrapper }); @@ -178,6 +192,9 @@ describe("useAuthorized", () => { admin_ui_disabled: false, }); + decodeTokenMock.mockReturnValue(null); + checkTokenValidityMock.mockReturnValue(false); + // No token cookie set const { result } = renderHook(() => useAuthorized(), { wrapper }); @@ -196,14 +213,18 @@ describe("useAuthorized", () => { auto_redirect_to_sso: false, admin_ui_disabled: false, }); - isJwtExpiredMock.mockReturnValue(true); - const token = createJwt({ + const decodedPayload = { key: "api-key-123", user_id: "user-1", user_email: "user@example.com", user_role: "app_admin", - }); + }; + + decodeTokenMock.mockReturnValue(decodedPayload); + checkTokenValidityMock.mockReturnValue(false); + + const token = createJwt(decodedPayload); document.cookie = `token=${token}; path=/;`; const { result } = renderHook(() => useAuthorized(), { wrapper }); @@ -213,6 +234,6 @@ describe("useAuthorized", () => { }); expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); - expect(isJwtExpiredMock).toHaveBeenCalledWith(token); + expect(checkTokenValidityMock).toHaveBeenCalledWith(token); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts index 531a240a371..0b60971c1eb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts @@ -2,8 +2,7 @@ import { getProxyBaseUrl } from "@/components/networking"; import { clearTokenCookies, getCookie } from "@/utils/cookieUtils"; -import { isJwtExpired } from "@/utils/jwtUtils"; -import { jwtDecode } from "jwt-decode"; +import { checkTokenValidity, decodeToken } from "@/utils/jwtUtils"; import { useRouter } from "next/navigation"; import { useEffect, useMemo } from "react"; import { useUIConfig } from "./uiConfig/useUIConfig"; @@ -43,44 +42,31 @@ const useAuthorized = () => { const token = typeof document !== "undefined" ? getCookie("token") : null; - // Step 1: Check for missing token or expired JWT - kick out immediately (even if UI Config is loading) + const decoded = useMemo(() => decodeToken(token), [token]); + const isTokenValid = useMemo(() => checkTokenValidity(token), [token]); + const isLoading = isUIConfigLoading; + const isAuthorized = isTokenValid && !uiConfig?.admin_ui_disabled; + + // Single useEffect for all redirect logic useEffect(() => { - if (!token || (token && isJwtExpired(token))) { + if (isLoading) return; + + if (!isAuthorized) { if (token) { clearTokenCookies(); } router.replace(`${getProxyBaseUrl()}/ui/login`); } - }, [token, router]); - - useEffect(() => { - if (isUIConfigLoading) { - return; - } - if (uiConfig?.admin_ui_disabled) { - router.replace(`${getProxyBaseUrl()}/ui/login`); - } - }, [router, isUIConfigLoading, uiConfig]); - - // Decode safely - const decoded = useMemo(() => { - if (!token) return null; - try { - return jwtDecode(token) as Record; - } catch { - // Bad token in cookie — clear and bounce - clearTokenCookies(); - router.replace(`${getProxyBaseUrl()}/ui/login`); - return null; - } - }, [token, router]); + }, [isLoading, isAuthorized, token, router]); return { - token: token, + isLoading, + isAuthorized, + token: isAuthorized ? token : null, accessToken: decoded?.key ?? null, userId: decoded?.user_id ?? null, userEmail: decoded?.user_email ?? null, - userRole: formatUserRole(decoded?.user_role ?? null), + userRole: formatUserRole(decoded?.user_role), premiumUser: decoded?.premium_user ?? null, disabledPersonalKeyCreation: decoded?.disabled_non_admin_personal_key_creation ?? null, showSSOBanner: decoded?.login_method === "username_password", diff --git a/ui/litellm-dashboard/src/utils/jwtUtils.test.ts b/ui/litellm-dashboard/src/utils/jwtUtils.test.ts index d695a3c37bb..bad8d4f6653 100644 --- a/ui/litellm-dashboard/src/utils/jwtUtils.test.ts +++ b/ui/litellm-dashboard/src/utils/jwtUtils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; -import { isJwtExpired } from "./jwtUtils"; +import { isJwtExpired, decodeToken, checkTokenValidity } from "./jwtUtils"; import { jwtDecode } from "jwt-decode"; vi.mock("jwt-decode"); @@ -50,4 +50,81 @@ describe("jwtUtils", () => { expect(isJwtExpired("invalid-token")).toBe(true); }); + + describe("decodeToken", () => { + it("should return null if token is null", () => { + expect(decodeToken(null)).toBeNull(); + }); + + it("should return null if token is empty string", () => { + expect(decodeToken("")).toBeNull(); + }); + + it("should decode a valid token", () => { + const mockPayload = { + key: "api-key-123", + user_id: "user-1", + user_email: "user@example.com", + user_role: "app_admin", + }; + vi.mocked(jwtDecode).mockReturnValue(mockPayload); + + expect(decodeToken("valid-token")).toEqual(mockPayload); + expect(jwtDecode).toHaveBeenCalledWith("valid-token"); + }); + + it("should return null if jwtDecode throws an error", () => { + vi.mocked(jwtDecode).mockImplementation(() => { + throw new Error("Invalid token"); + }); + + expect(decodeToken("invalid-token")).toBeNull(); + }); + }); + + describe("checkTokenValidity", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should return false if token is null", () => { + expect(checkTokenValidity(null)).toBe(false); + }); + + it("should return false if token is empty string", () => { + expect(checkTokenValidity("")).toBe(false); + }); + + it("should return true for a valid, non-expired token", () => { + const mockDateNow = 1716838401000; + vi.spyOn(Date, "now").mockReturnValue(mockDateNow); + const mockPayload = { + exp: Math.floor(mockDateNow / 1000) + 1000, + user_id: "user-1", + }; + vi.mocked(jwtDecode).mockReturnValue(mockPayload); + + expect(checkTokenValidity("valid-token")).toBe(true); + }); + + it("should return false for an expired token", () => { + const mockDateNow = 1716838401000; + vi.spyOn(Date, "now").mockReturnValue(mockDateNow); + const mockPayload = { + exp: Math.floor(mockDateNow / 1000) - 1, + user_id: "user-1", + }; + vi.mocked(jwtDecode).mockReturnValue(mockPayload); + + expect(checkTokenValidity("expired-token")).toBe(false); + }); + + it("should return false if token cannot be decoded", () => { + vi.mocked(jwtDecode).mockImplementation(() => { + throw new Error("Invalid token"); + }); + + expect(checkTokenValidity("invalid-token")).toBe(false); + }); + }); }); diff --git a/ui/litellm-dashboard/src/utils/jwtUtils.ts b/ui/litellm-dashboard/src/utils/jwtUtils.ts index d3db41a411a..2a7972ad4cc 100644 --- a/ui/litellm-dashboard/src/utils/jwtUtils.ts +++ b/ui/litellm-dashboard/src/utils/jwtUtils.ts @@ -12,3 +12,18 @@ export function isJwtExpired(token: string): boolean { return true; } } + +export function decodeToken(token: string | null): Record | null { + if (!token) return null; + try { + return jwtDecode(token) as Record; + } catch { + return null; + } +} + +export function checkTokenValidity(token: string | null): boolean { + if (!token) return false; + const decoded = decodeToken(token); + return decoded !== null && !isJwtExpired(token); +} From 28bae2264c070753b982ea5bd5277bf9411c1ccc Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 16:57:46 -0800 Subject: [PATCH 008/212] ai hub req auth working --- .../hooks/uiSettings/useUISettings.ts | 4 +-- .../src/components/AIHub/ModelHubTable.tsx | 27 +++++++++++++++++++ .../src/components/networking.tsx | 5 +--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts index 46a0254d0db..f6d5035018c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts @@ -6,11 +6,9 @@ import useAuthorized from "../useAuthorized"; const uiSettingsKeys = createQueryKeys("uiSettings"); export const useUISettings = () => { - const { accessToken } = useAuthorized(); return useQuery>({ queryKey: uiSettingsKeys.list({}), - queryFn: async () => await getUiSettings(accessToken), - enabled: !!accessToken, + queryFn: async () => await getUiSettings(), staleTime: 60 * 60 * 1000, // 1 hour - data rarely changes gcTime: 60 * 60 * 1000, // 1 hour - keep in cache for 1 hour }); diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx index 4843713e5a6..71b84e281df 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx @@ -27,6 +27,9 @@ import { Copy } from "lucide-react"; import { useRouter } from "next/navigation"; import React, { useCallback, useEffect, useState } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"; +import { checkTokenValidity } from "@/utils/jwtUtils"; +import { getCookie } from "@/utils/cookieUtils"; interface ModelHubTableProps { accessToken: string | null; @@ -76,6 +79,30 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, const [isMcpModalVisible, setIsMcpModalVisible] = useState(false); const [isMakeMcpPublicModalVisible, setIsMakeMcpPublicModalVisible] = useState(false); const router = useRouter(); + const { data: uiSettings, isLoading: isUISettingsLoading } = useUISettings(); + + // Check authentication requirement for public AI Hub + useEffect(() => { + // Only check when UI settings are loaded and this is a public page + if (isUISettingsLoading || !publicPage) { + return; + } + + const requireAuth = uiSettings?.values?.require_auth_for_public_ai_hub; + + // If require_auth_for_public_ai_hub is true, verify token + if (requireAuth === true) { + const token = getCookie("token"); + const isTokenValid = checkTokenValidity(token); + + // If token is invalid, redirect to login + if (!isTokenValid) { + router.replace(`${getProxyBaseUrl()}/ui/login`); + return; + } + } + // If require_auth_for_public_ai_hub is false, allow public access (no change) + }, [isUISettingsLoading, publicPage, uiSettings, router]); useEffect(() => { const fetchData = async (accessToken: string) => { diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index ca4c16a781f..d5447e0d134 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -8736,14 +8736,11 @@ export const loginCall = async (username: string, password: string): Promise { +export const getUiSettings = async () => { const proxyBaseUrl = getProxyBaseUrl(); const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`; const response = await fetch(url, { method: "GET", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - }, }); if (!response.ok) { const errorData = await response.json(); From bdf47eb0f0a608e31e93df2fa5e546838821ab55 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 17:01:48 -0800 Subject: [PATCH 009/212] Adding tests --- .../hooks/uiSettings/useUISettings.test.ts | 72 +------------------ .../hooks/uiSettings/useUISettings.ts | 1 - .../components/AIHub/ModelHubTable.test.tsx | 14 +++- 3 files changed, 14 insertions(+), 73 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.test.ts index 785f003d2f8..0fc3bda27fc 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.test.ts @@ -10,12 +10,6 @@ vi.mock("@/components/networking", () => ({ getUiSettings: vi.fn(), })); -// Mock useAuthorized hook - we can override this in individual tests -const mockUseAuthorized = vi.fn(); -vi.mock("../useAuthorized", () => ({ - default: () => mockUseAuthorized(), -})); - // Mock data const mockUISettings: Record = { theme: "dark", @@ -39,18 +33,6 @@ describe("useUISettings", () => { // Reset all mocks vi.clearAllMocks(); - - // Set default mock for useAuthorized (enabled state) - mockUseAuthorized.mockReturnValue({ - accessToken: "test-access-token", - userRole: "Admin", - userId: "test-user-id", - token: "test-token", - userEmail: "test@example.com", - premiumUser: false, - disabledPersonalKeyCreation: null, - showSSOBanner: false, - }); }); const wrapper = ({ children }: { children: ReactNode }) => @@ -74,7 +56,7 @@ describe("useUISettings", () => { expect(result.current.data).toEqual(mockUISettings); expect(result.current.error).toBeNull(); - expect(getUiSettings).toHaveBeenCalledWith("test-access-token"); + expect(getUiSettings).toHaveBeenCalledWith(); expect(getUiSettings).toHaveBeenCalledTimes(1); }); @@ -98,58 +80,10 @@ describe("useUISettings", () => { expect(result.current.error).toEqual(testError); expect(result.current.data).toBeUndefined(); - expect(getUiSettings).toHaveBeenCalledWith("test-access-token"); + expect(getUiSettings).toHaveBeenCalledWith(); expect(getUiSettings).toHaveBeenCalledTimes(1); }); - it("should not execute query when accessToken is missing", async () => { - // Mock missing accessToken - mockUseAuthorized.mockReturnValue({ - accessToken: null, - userRole: "Admin", - userId: "test-user-id", - token: null, - userEmail: "test@example.com", - premiumUser: false, - disabledPersonalKeyCreation: null, - showSSOBanner: false, - }); - - const { result } = renderHook(() => useUISettings(), { wrapper }); - - // Query should not execute - expect(result.current.isLoading).toBe(false); - expect(result.current.data).toBeUndefined(); - expect(result.current.isFetched).toBe(false); - - // API should not be called - expect(getUiSettings).not.toHaveBeenCalled(); - }); - - it("should not execute query when accessToken is empty string", async () => { - // Mock empty accessToken - mockUseAuthorized.mockReturnValue({ - accessToken: "", - userRole: "Admin", - userId: "test-user-id", - token: "", - userEmail: "test@example.com", - premiumUser: false, - disabledPersonalKeyCreation: null, - showSSOBanner: false, - }); - - const { result } = renderHook(() => useUISettings(), { wrapper }); - - // Query should not execute - expect(result.current.isLoading).toBe(false); - expect(result.current.data).toBeUndefined(); - expect(result.current.isFetched).toBe(false); - - // API should not be called - expect(getUiSettings).not.toHaveBeenCalled(); - }); - it("should return empty object when API returns empty settings", async () => { // Mock API returning empty object (getUiSettings as any).mockResolvedValue({}); @@ -163,7 +97,7 @@ describe("useUISettings", () => { }); expect(result.current.data).toEqual({}); - expect(getUiSettings).toHaveBeenCalledWith("test-access-token"); + expect(getUiSettings).toHaveBeenCalledWith(); }); it("should handle network timeout error", async () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts index f6d5035018c..14c6c5e3888 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/uiSettings/useUISettings.ts @@ -1,7 +1,6 @@ import { getUiSettings } from "@/components/networking"; import { useQuery } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -import useAuthorized from "../useAuthorized"; const uiSettingsKeys = createQueryKeys("uiSettings"); diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx index a88ce0d7938..048fc9195a8 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx @@ -1,6 +1,6 @@ import * as networking from "@/components/networking"; -import { render, screen, waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { renderWithProviders, screen, waitFor } from "../../../tests/test-utils"; import ModelHubTable from "./ModelHubTable"; vi.mock("@/components/networking", () => ({ @@ -11,6 +11,8 @@ vi.mock("@/components/networking", () => ({ getProxyBaseUrl: vi.fn(() => "http://localhost:4000"), getAgentsList: vi.fn(), fetchMCPServers: vi.fn(), + getUiSettings: vi.fn(), + getClaudeCodeMarketplace: vi.fn(), })); vi.mock("next/navigation", () => ({ @@ -39,8 +41,11 @@ describe("ModelHubTable", () => { agents: [], }); vi.mocked(networking.fetchMCPServers).mockResolvedValue([]); + vi.mocked(networking.getUiSettings).mockResolvedValue({ + values: {}, + }); - render(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("AI Hub")).toBeInTheDocument(); @@ -58,8 +63,11 @@ describe("ModelHubTable", () => { admin_ui_disabled: false, }); modelHubPublicModelsCallMock.mockResolvedValue([]); + vi.mocked(networking.getUiSettings).mockResolvedValue({ + values: {}, + }); - render(); + renderWithProviders(); await waitFor(() => { expect(getUiConfigMock).toHaveBeenCalled(); From d016ac863bab52a8c46f465b53840b57a70a8c96 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 17:05:06 -0800 Subject: [PATCH 010/212] Adding tests for model hub --- .../components/AIHub/ModelHubTable.test.tsx | 137 +++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx index 048fc9195a8..0a5cd17e571 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx @@ -3,6 +3,11 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { renderWithProviders, screen, waitFor } from "../../../tests/test-utils"; import ModelHubTable from "./ModelHubTable"; +const mockUseUISettings = vi.hoisted(() => vi.fn()); +const mockGetCookie = vi.hoisted(() => vi.fn()); +const mockCheckTokenValidity = vi.hoisted(() => vi.fn()); +const mockRouterReplace = vi.hoisted(() => vi.fn()); + vi.mock("@/components/networking", () => ({ getUiConfig: vi.fn(), modelHubPublicModelsCall: vi.fn(), @@ -17,7 +22,7 @@ vi.mock("@/components/networking", () => ({ vi.mock("next/navigation", () => ({ useRouter: () => ({ - replace: vi.fn(), + replace: mockRouterReplace, }), })); @@ -25,11 +30,81 @@ vi.mock("@/components/public_model_hub", () => ({ default: () =>
Public Model Hub
, })); +vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({ + useUISettings: mockUseUISettings, +})); + +vi.mock("@/utils/cookieUtils", () => ({ + getCookie: mockGetCookie, +})); + +vi.mock("@/utils/jwtUtils", () => ({ + checkTokenValidity: mockCheckTokenValidity, +})); + describe("ModelHubTable", () => { afterEach(() => { vi.clearAllMocks(); }); + // Reusable helper function to setup mocks for auth redirect tests + const setupAuthRedirectTest = ( + requireAuth: boolean, + tokenValue: string | null, + isTokenValid: boolean + ) => { + mockUseUISettings.mockReturnValue({ + data: { + values: { + require_auth_for_public_ai_hub: requireAuth, + }, + }, + isLoading: false, + }); + mockGetCookie.mockReturnValue(tokenValue); + mockCheckTokenValidity.mockReturnValue(isTokenValid); + mockRouterReplace.mockClear(); + + // Setup other required mocks + vi.mocked(networking.getUiConfig).mockResolvedValue({ + server_root_path: "/", + proxy_base_url: "http://localhost:4000", + auto_redirect_to_sso: false, + admin_ui_disabled: false, + }); + vi.mocked(networking.modelHubPublicModelsCall).mockResolvedValue([]); + vi.mocked(networking.getUiSettings).mockResolvedValue({ + values: { + require_auth_for_public_ai_hub: requireAuth, + }, + }); + }; + + // Reusable test function for auth redirect scenarios + const testAuthRedirect = ( + requireAuth: boolean, + tokenValue: string | null, + isTokenValid: boolean, + shouldRedirect: boolean, + description: string + ) => { + it(description, async () => { + setupAuthRedirectTest(requireAuth, tokenValue, isTokenValid); + + renderWithProviders( + + ); + + await waitFor(() => { + if (shouldRedirect) { + expect(mockRouterReplace).toHaveBeenCalledWith("http://localhost:4000/ui/login"); + } else { + expect(mockRouterReplace).not.toHaveBeenCalled(); + } + }); + }); + }; + it("should render", async () => { vi.mocked(networking.modelHubCall).mockResolvedValue({ data: [], @@ -44,6 +119,10 @@ describe("ModelHubTable", () => { vi.mocked(networking.getUiSettings).mockResolvedValue({ values: {}, }); + mockUseUISettings.mockReturnValue({ + data: { values: {} }, + isLoading: false, + }); renderWithProviders(); @@ -66,6 +145,10 @@ describe("ModelHubTable", () => { vi.mocked(networking.getUiSettings).mockResolvedValue({ values: {}, }); + mockUseUISettings.mockReturnValue({ + data: { values: {} }, + isLoading: false, + }); renderWithProviders(); @@ -79,4 +162,56 @@ describe("ModelHubTable", () => { expect(getUiConfigCallOrder).toBeLessThan(modelHubPublicModelsCallOrder); }); + + describe("authentication redirect behavior", () => { + // Test cases where requireAuth is true - should redirect on invalid tokens + testAuthRedirect( + true, + null, + false, + true, + "should redirect to login when requireAuth is true and there is no token" + ); + + testAuthRedirect( + true, + "expired-token", + false, + true, + "should redirect to login when requireAuth is true and token is expired" + ); + + testAuthRedirect( + true, + "malformed-token", + false, + true, + "should redirect to login when requireAuth is true and token is malformed" + ); + + // Test cases where requireAuth is false - should NOT redirect regardless of token state + testAuthRedirect( + false, + null, + false, + false, + "should not redirect when requireAuth is false and there is no token" + ); + + testAuthRedirect( + false, + "expired-token", + false, + false, + "should not redirect when requireAuth is false and token is expired" + ); + + testAuthRedirect( + false, + "malformed-token", + false, + false, + "should not redirect when requireAuth is false and token is malformed" + ); + }); }); From fa86f1a10afe83172fc611011b4add18cc6018ba Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 4 Feb 2026 20:14:01 -0800 Subject: [PATCH 011/212] Fix input and output label --- .../src/components/model_info_view.test.tsx | 561 +++++++++++++----- .../src/components/model_info_view.tsx | 12 +- 2 files changed, 417 insertions(+), 156 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index 4b9f7d217dc..7158c452d94 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -1,59 +1,36 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render, waitFor } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React, { ReactNode } from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import ModelInfoView from "./model_info_view"; +import NotificationsManager from "./molecules/notifications_manager"; +import * as networking from "./networking"; vi.mock("../../utils/dataUtils", () => ({ copyToClipboard: vi.fn().mockResolvedValue(true), })); -vi.mock("./networking", () => ({ - modelInfoV1Call: vi.fn().mockResolvedValue({ - data: [ - { - model_name: "GPT-4", - litellm_params: { - model: "gpt-4", - api_base: "https://api.openai.com/v1", - custom_llm_provider: "openai", - }, - model_info: { - id: "123", - created_by: "123", - db_model: true, - input_cost_per_token: 0.00003, - output_cost_per_token: 0.00006, - }, - }, - ], - }), - credentialGetCall: vi.fn().mockResolvedValue({ - credential_name: "test-credential", - credential_values: {}, - credential_info: {}, - }), - getGuardrailsList: vi.fn().mockResolvedValue({ - guardrails: [{ guardrail_name: "content_filter" }, { guardrail_name: "toxicity_filter" }], - }), - tagListCall: vi.fn().mockResolvedValue({ - test_tag: { - name: "test_tag", - description: "A test tag", - }, - production_tag: { - name: "production_tag", - description: "Production ready models", - }, - }), - testConnectionRequest: vi.fn().mockResolvedValue({ - status: "success", - }), - modelPatchUpdateCall: vi.fn().mockResolvedValue({}), - modelDeleteCall: vi.fn().mockResolvedValue({}), +vi.mock("./molecules/notifications_manager", () => ({ + default: { + success: vi.fn(), + error: vi.fn(), + info: vi.fn(), + fromBackend: vi.fn(), + }, +})); + +vi.mock("./networking", () => ({ + modelInfoV1Call: vi.fn(), + credentialGetCall: vi.fn(), + getGuardrailsList: vi.fn(), + tagListCall: vi.fn(), + testConnectionRequest: vi.fn(), + modelPatchUpdateCall: vi.fn(), + modelDeleteCall: vi.fn(), + credentialCreateCall: vi.fn(), })); -// Mock the useModelsInfo hook since it uses React Query const mockUseModelsInfo = vi.fn(); const mockUseModelHub = vi.fn(); @@ -62,12 +39,21 @@ vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({ useModelHub: (...args: any[]) => mockUseModelHub(...args), })); -// Mock the useModelCostMap hook const mockUseModelCostMap = vi.fn(); vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({ useModelCostMap: (...args: any[]) => mockUseModelCostMap(...args), })); +const mockNotificationsManager = vi.mocked(NotificationsManager); +const mockModelInfoV1Call = vi.mocked(networking.modelInfoV1Call); +const mockCredentialGetCall = vi.mocked(networking.credentialGetCall); +const mockGetGuardrailsList = vi.mocked(networking.getGuardrailsList); +const mockTagListCall = vi.mocked(networking.tagListCall); +const mockTestConnectionRequest = vi.mocked(networking.testConnectionRequest); +const mockModelPatchUpdateCall = vi.mocked(networking.modelPatchUpdateCall); +const mockModelDeleteCall = vi.mocked(networking.modelDeleteCall); +const mockCredentialCreateCall = vi.mocked(networking.credentialCreateCall); + describe("ModelInfoView", () => { let queryClient: QueryClient; @@ -88,6 +74,16 @@ describe("ModelInfoView", () => { }, }; + const DEFAULT_ADMIN_PROPS = { + modelId: "123", + onClose: vi.fn(), + accessToken: "test-token", + userID: "123", + userRole: "Admin", + onModelUpdate: vi.fn(), + modelAccessGroups: ["group1", "group2"], + }; + beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { @@ -98,7 +94,6 @@ describe("ModelInfoView", () => { }); vi.clearAllMocks(); - // Set up default mocks mockUseModelsInfo.mockReturnValue({ data: { data: [defaultModelData], @@ -120,89 +115,170 @@ describe("ModelInfoView", () => { isLoading: false, error: null, }); + + mockModelInfoV1Call.mockResolvedValue({ + data: [defaultModelData], + }); + + mockCredentialGetCall.mockResolvedValue({ + credential_name: "test-credential", + credential_values: {}, + credential_info: {}, + }); + + mockGetGuardrailsList.mockResolvedValue({ + guardrails: [{ guardrail_name: "content_filter" }, { guardrail_name: "toxicity_filter" }], + }); + + mockTagListCall.mockResolvedValue({ + test_tag: { + name: "test_tag", + description: "A test tag", + models: [], + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + }, + production_tag: { + name: "production_tag", + description: "Production ready models", + models: [], + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + }, + }); + + mockTestConnectionRequest.mockResolvedValue({ + status: "success", + }); + + mockModelPatchUpdateCall.mockResolvedValue({}); + mockModelDeleteCall.mockResolvedValue({}); + mockCredentialCreateCall.mockResolvedValue({}); }); const wrapper = ({ children }: { children: ReactNode }) => React.createElement(QueryClientProvider, { client: queryClient }, children); - const DEFAULT_ADMIN_PROPS = { - modelId: "123", - onClose: () => {}, - accessToken: "123", - userID: "123", - userRole: "Admin", - onModelUpdate: () => {}, - modelAccessGroups: [], - }; - - describe("Edit Model", () => { - it("should render the model info view", async () => { - const { getByText } = render(, { wrapper }); - await waitFor(() => { - expect(getByText("Model Settings")).toBeInTheDocument(); - }); - }); - - it("should not render an edit settings button if the model is not a DB model", async () => { - const nonDbModelData = { - ...defaultModelData, - model_info: { - ...defaultModelData.model_info, - db_model: false, - }, - }; - - mockUseModelsInfo.mockReturnValue({ - data: { - data: [nonDbModelData], - }, - isLoading: false, - error: null, - }); - - const { queryByText } = render(, { wrapper }); - await waitFor(() => { - expect(queryByText("Edit Settings")).not.toBeInTheDocument(); - }); - }); - - it("should render tags in the edit model", async () => { - const { getByText } = render(, { wrapper }); - await waitFor(() => { - expect(getByText("Tags")).toBeInTheDocument(); - }); - }); - - it("should render the litellm params in the edit model", async () => { - const { getByText } = render(, { wrapper }); - await waitFor(() => { - expect(getByText("LiteLLM Params")).toBeInTheDocument(); - }); - }); - }); - - it("should render a test connection button", async () => { - const { getByTestId } = render(, { wrapper }); + it("should render", async () => { + render(, { wrapper }); await waitFor(() => { - expect(getByTestId("test-connection-button")).toBeInTheDocument(); + expect(screen.getByText("Model Settings")).toBeInTheDocument(); }); }); - it("should render a reuse credentials button", async () => { - const { getByTestId } = render(, { wrapper }); + it("should display loading state when model data is loading", () => { + mockUseModelsInfo.mockReturnValue({ + data: null, + isLoading: true, + error: null, + }); + + render(, { wrapper }); + expect(screen.getByText("Loading...")).toBeInTheDocument(); + }); + + it("should display not found message when model data is not available", async () => { + mockUseModelsInfo.mockReturnValue({ + data: { + data: [], + }, + isLoading: false, + error: null, + }); + + render(, { wrapper }); await waitFor(() => { - expect(getByTestId("reuse-credentials-button")).toBeInTheDocument(); + expect(screen.getByText("Model not found")).toBeInTheDocument(); }); }); - it("should render a delete model button", async () => { - const { getByTestId } = render(, { wrapper }); + it("should display model name in the header", async () => { + render(, { wrapper }); await waitFor(() => { - expect(getByTestId("delete-model-button")).toBeInTheDocument(); + expect(screen.getByText(/Public Model Name:/)).toBeInTheDocument(); }); }); - it("should render a disabled delete model button if the model is not a DB model", async () => { + it("should display back button that calls onClose when clicked", async () => { + const mockOnClose = vi.fn(); + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByText("Model Settings")).toBeInTheDocument(); + }); + + const backButton = screen.getByRole("button", { name: /back to models/i }); + await user.click(backButton); + + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); + + it("should display test connection button", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("button", { name: /test connection/i })).toBeInTheDocument(); + }); + }); + + it("should test connection when test connection button is clicked", async () => { + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByText("Model Settings")).toBeInTheDocument(); + }); + + const testButton = screen.getByRole("button", { name: /test connection/i }); + await user.click(testButton); + + await waitFor(() => { + expect(mockTestConnectionRequest).toHaveBeenCalled(); + expect(mockNotificationsManager.success).toHaveBeenCalledWith("Connection test successful!"); + }); + }); + + it("should display error notification when connection test fails", async () => { + const user = userEvent.setup(); + mockTestConnectionRequest.mockRejectedValue(new Error("Connection failed")); + + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByText("Model Settings")).toBeInTheDocument(); + }); + + const testButton = screen.getByRole("button", { name: /test connection/i }); + await user.click(testButton); + + await waitFor(() => { + expect(mockNotificationsManager.error).toHaveBeenCalled(); + }); + }); + + it("should display reuse credentials button for admin users", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("button", { name: /re-use credentials/i })).toBeInTheDocument(); + }); + }); + + it("should disable reuse credentials button for non-admin users", async () => { + render(, { wrapper }); + await waitFor(() => { + const button = screen.getByRole("button", { name: /re-use credentials/i }); + expect(button).toBeDisabled(); + }); + }); + + it("should display delete model button", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("button", { name: /delete model/i })).toBeInTheDocument(); + }); + }); + + it("should disable delete button when model is not a DB model", async () => { const nonDbModelData = { ...defaultModelData, model_info: { @@ -219,13 +295,14 @@ describe("ModelInfoView", () => { error: null, }); - const { getByTestId } = render(, { wrapper }); + render(, { wrapper }); await waitFor(() => { - expect(getByTestId("delete-model-button")).toBeDisabled(); + const deleteButton = screen.getByRole("button", { name: /delete model/i }); + expect(deleteButton).toBeDisabled(); }); }); - it("should render a disabled delete model button if the user is not an admin and model is not created by the user", async () => { + it("should disable delete button when user is not admin and did not create the model", async () => { const nonCreatedByUserModelData = { ...defaultModelData, model_info: { @@ -242,18 +319,177 @@ describe("ModelInfoView", () => { error: null, }); - const NON_CREATED_BY_USER_ADMIN_PROPS = { - ...DEFAULT_ADMIN_PROPS, - userRole: "User", - }; - - const { getByTestId } = render(, { wrapper }); + render(, { wrapper }); await waitFor(() => { - expect(getByTestId("delete-model-button")).toBeDisabled(); + const deleteButton = screen.getByRole("button", { name: /delete model/i }); + expect(deleteButton).toBeDisabled(); }); }); - it("should render health check model field for wildcard routes", async () => { + it("should display overview and raw JSON tabs", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("tab", { name: /overview/i })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: /raw json/i })).toBeInTheDocument(); + }); + }); + + it("should display model information in overview tab", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText("Provider")).toBeInTheDocument(); + expect(screen.getByText("LiteLLM Model")).toBeInTheDocument(); + expect(screen.getByText("Pricing")).toBeInTheDocument(); + }); + }); + + it("should display edit settings button when user can edit model", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + }); + + it("should not display edit settings button when model is not a DB model", async () => { + const nonDbModelData = { + ...defaultModelData, + model_info: { + ...defaultModelData.model_info, + db_model: false, + }, + }; + + mockUseModelsInfo.mockReturnValue({ + data: { + data: [nonDbModelData], + }, + isLoading: false, + error: null, + }); + + render(, { wrapper }); + await waitFor(() => { + expect(screen.queryByRole("button", { name: /edit settings/i })).not.toBeInTheDocument(); + }); + }); + + it("should enter edit mode when edit settings button is clicked", async () => { + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: /edit settings/i }); + await user.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save changes/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument(); + }); + }); + + it("should display form fields in edit mode", async () => { + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: /edit settings/i }); + await user.click(editButton); + + await waitFor(() => { + expect(screen.getByPlaceholderText("Enter model name")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Enter LiteLLM model name")).toBeInTheDocument(); + }); + }); + + it("should allow editing model name in edit mode", async () => { + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: /edit settings/i }); + await user.click(editButton); + + const modelNameInput = await screen.findByPlaceholderText("Enter model name"); + await user.clear(modelNameInput); + await user.type(modelNameInput, "Updated Model Name"); + + expect(modelNameInput).toHaveValue("Updated Model Name"); + }); + + it("should cancel editing when cancel button is clicked", async () => { + const user = userEvent.setup(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: /edit settings/i }); + await user.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument(); + }); + + const cancelButton = screen.getByRole("button", { name: /cancel/i }); + await user.click(cancelButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /save changes/i })).not.toBeInTheDocument(); + }); + }); + + it("should save model changes when save button is clicked", async () => { + const user = userEvent.setup(); + const mockOnModelUpdate = vi.fn(); + render(, { wrapper }); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: /edit settings/i }); + await user.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: /save changes/i })).toBeInTheDocument(); + }); + + const saveButton = screen.getByRole("button", { name: /save changes/i }); + await user.click(saveButton); + + await waitFor(() => { + expect(mockModelPatchUpdateCall).toHaveBeenCalled(); + expect(mockNotificationsManager.success).toHaveBeenCalledWith("Model settings updated successfully"); + expect(mockOnModelUpdate).toHaveBeenCalled(); + }); + }); + + it("should display tags section", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText("Tags")).toBeInTheDocument(); + }); + }); + + it("should display LiteLLM Params section", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText("LiteLLM Params")).toBeInTheDocument(); + }); + }); + + it("should display health check model field for wildcard models", async () => { const wildcardModelData = { ...defaultModelData, litellm_params: { @@ -270,38 +506,71 @@ describe("ModelInfoView", () => { error: null, }); - const { getByText } = render(, { wrapper }); + render(, { wrapper }); await waitFor(() => { - expect(getByText("Model Settings")).toBeInTheDocument(); - }); - await waitFor(() => { - expect(getByText("Health Check Model")).toBeInTheDocument(); + expect(screen.getByText("Health Check Model")).toBeInTheDocument(); }); }); - it("should not render health check model field for non-wildcard routes", async () => { - const { queryByText } = render(, { wrapper }); + it("should not display health check model field for non-wildcard models", async () => { + render(, { wrapper }); await waitFor(() => { - expect(queryByText("Model Settings")).toBeInTheDocument(); - }); - await waitFor(() => { - expect(queryByText("Health Check Model")).not.toBeInTheDocument(); + expect(screen.getByText("Model Settings")).toBeInTheDocument(); + expect(screen.queryByText("Health Check Model")).not.toBeInTheDocument(); }); }); - describe("View Model", () => { - it("should render the model info view", async () => { - const { getByText } = render(, { wrapper }); - await waitFor(() => { - expect(getByText("Model Settings")).toBeInTheDocument(); - }); + it("should display edit auto router button for auto router models", async () => { + const autoRouterModelData = { + ...defaultModelData, + litellm_params: { + ...defaultModelData.litellm_params, + auto_router_config: {}, + }, + }; + + mockUseModelsInfo.mockReturnValue({ + data: { + data: [autoRouterModelData], + }, + isLoading: false, + error: null, }); - it("should render tags in the view model", async () => { - const { getByText } = render(, { wrapper }); - await waitFor(() => { - expect(getByText("Tags")).toBeInTheDocument(); - }); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByRole("button", { name: /edit auto router/i })).toBeInTheDocument(); + }); + }); + + + it("should display model access groups field", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText("Model Access Groups")).toBeInTheDocument(); + }); + }); + + it("should display guardrails field", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText("Guardrails")).toBeInTheDocument(); + }); + }); + + it("should display pricing information", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText(/Input:/)).toBeInTheDocument(); + expect(screen.getByText(/Output:/)).toBeInTheDocument(); + }); + }); + + it("should display created at and created by information", async () => { + render(, { wrapper }); + await waitFor(() => { + expect(screen.getByText(/Created At/)).toBeInTheDocument(); + expect(screen.getByText(/Created By/)).toBeInTheDocument(); }); }); }); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index a55149124c9..e2fc8caa21c 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -136,11 +136,9 @@ export default function ModelInfoView({ useEffect(() => { const getExistingCredential = async () => { - console.log("accessToken, ", accessToken); if (!accessToken) return; if (usingExistingCredential) return; let existingCredentialResponse = await credentialGetCall(accessToken, null, modelId); - console.log("existingCredentialResponse, ", existingCredentialResponse); setExistingCredential({ credential_name: existingCredentialResponse["credential_name"], credential_values: existingCredentialResponse["credential_values"], @@ -153,7 +151,6 @@ export default function ModelInfoView({ // Only fetch if we don't have modelData yet if (modelData) return; let modelInfoResponse = await modelInfoV1Call(accessToken, modelId); - console.log("modelInfoResponse, ", modelInfoResponse); let specificModelData = modelInfoResponse.data[0]; if (specificModelData && !specificModelData.litellm_model_name) { specificModelData = { @@ -201,7 +198,6 @@ export default function ModelInfoView({ }, [accessToken, modelId]); const handleReuseCredential = async (values: any) => { - console.log("values, ", values); if (!accessToken) return; let credentialItem = { credential_name: values.credential_name, @@ -212,7 +208,6 @@ export default function ModelInfoView({ }; NotificationsManager.info("Storing credential.."); let credentialResponse = await credentialCreateCall(accessToken, credentialItem); - console.log("credentialResponse, ", credentialResponse); NotificationsManager.success("Credential stored successfully"); }; @@ -221,8 +216,6 @@ export default function ModelInfoView({ if (!accessToken) return; setIsSaving(true); - console.log("values.model_name, ", values.model_name); - // Parse LiteLLM extra params from JSON text area let parsedExtraParams: Record = {}; try { @@ -412,7 +405,6 @@ export default function ModelInfoView({ } }; const isWildcardModel = modelData.litellm_model_name.includes("*"); - console.log("isWildcardModel, ", isWildcardModel); return (
@@ -657,7 +649,7 @@ export default function ModelInfoView({ ? (localModelData.litellm_params?.input_cost_per_token * 1_000_000).toFixed(4) : localModelData?.model_info?.input_cost_per_token ? (localModelData.model_info.input_cost_per_token * 1_000_000).toFixed(4) - : null} + : "Not Set"}
)} @@ -674,7 +666,7 @@ export default function ModelInfoView({ ? (localModelData.litellm_params.output_cost_per_token * 1_000_000).toFixed(4) : localModelData?.model_info?.output_cost_per_token ? (localModelData.model_info.output_cost_per_token * 1_000_000).toFixed(4) - : null} + : "Not Set"} )} From 666feef2a97d30b14b2ac4e1d7bc13614f5aff38 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:27:41 +0530 Subject: [PATCH 012/212] Add chat completion support for websearch --- .../websearch_interception/handler.py | 265 +++++++++++++++++- 1 file changed, 258 insertions(+), 7 deletions(-) diff --git a/litellm/integrations/websearch_interception/handler.py b/litellm/integrations/websearch_interception/handler.py index 5d36b760afb..1e109dc9e39 100644 --- a/litellm/integrations/websearch_interception/handler.py +++ b/litellm/integrations/websearch_interception/handler.py @@ -48,7 +48,8 @@ class WebSearchInterceptionLogger(CustomLogger): Args: enabled_providers: List of LLM providers to enable interception for. Use LlmProviders enum values (e.g., [LlmProviders.BEDROCK]) - Default: [LlmProviders.BEDROCK] + If None or empty list, enables for ALL providers. + Default: None (all providers enabled) search_tool_name: Name of search tool configured in router's search_tools. If None, will attempt to use first available search tool. """ @@ -183,10 +184,10 @@ class WebSearchInterceptionLogger(CustomLogger): verbose_logger.debug( f"WebSearchInterception: Pre-request hook called" f" - custom_llm_provider={custom_llm_provider}" - f" - enabled_providers={self.enabled_providers}" + f" - enabled_providers={self.enabled_providers or 'ALL'}" ) - if custom_llm_provider not in self.enabled_providers: + if self.enabled_providers is not None and custom_llm_provider not in self.enabled_providers: verbose_logger.debug( f"WebSearchInterception: Skipping - provider {custom_llm_provider} not in {self.enabled_providers}" ) @@ -245,7 +246,12 @@ class WebSearchInterceptionLogger(CustomLogger): custom_llm_provider: str, kwargs: Dict, ) -> Tuple[bool, Dict]: - """Check if WebSearch tool interception is needed""" + """ + Check if WebSearch tool interception is needed for Anthropic Messages API. + + This is the legacy method for Anthropic-style responses. + For chat completions, use async_should_run_chat_completion_agentic_loop instead. + """ verbose_logger.debug(f"WebSearchInterception: Hook called! provider={custom_llm_provider}, stream={stream}") verbose_logger.debug(f"WebSearchInterception: Response type: {type(response)}") @@ -253,7 +259,7 @@ class WebSearchInterceptionLogger(CustomLogger): # Check if provider should be intercepted # Note: custom_llm_provider is already normalized by get_llm_provider() # (e.g., "bedrock/invoke/..." -> "bedrock") - if custom_llm_provider not in self.enabled_providers: + if self.enabled_providers is not None and custom_llm_provider not in self.enabled_providers: verbose_logger.debug( f"WebSearchInterception: Skipping provider {custom_llm_provider} (not in enabled list: {self.enabled_providers})" ) @@ -267,10 +273,11 @@ class WebSearchInterceptionLogger(CustomLogger): ) return False, {} - # Detect WebSearch tool_use in response + # Detect WebSearch tool_use in response (Anthropic format) should_intercept, tool_calls = WebSearchTransformation.transform_request( response=response, stream=stream, + response_format="anthropic", ) if not should_intercept: @@ -288,6 +295,67 @@ class WebSearchInterceptionLogger(CustomLogger): "tool_calls": tool_calls, "tool_type": "websearch", "provider": custom_llm_provider, + "response_format": "anthropic", + } + return True, tools_dict + + async def async_should_run_chat_completion_agentic_loop( + self, + response: Any, + model: str, + messages: List[Dict], + tools: Optional[List[Dict]], + stream: bool, + custom_llm_provider: str, + kwargs: Dict, + ) -> Tuple[bool, Dict]: + """ + Check if WebSearch tool interception is needed for Chat Completions API. + + Similar to async_should_run_agentic_loop but for OpenAI-style chat completions. + """ + + verbose_logger.debug(f"WebSearchInterception: Chat completion hook called! provider={custom_llm_provider}, stream={stream}") + verbose_logger.debug(f"WebSearchInterception: Response type: {type(response)}") + + # Check if provider should be intercepted + if self.enabled_providers is not None and custom_llm_provider not in self.enabled_providers: + verbose_logger.debug( + f"WebSearchInterception: Skipping provider {custom_llm_provider} (not in enabled list: {self.enabled_providers})" + ) + return False, {} + + # Check if tools include any web search tool + has_websearch_tool = any(is_web_search_tool(t) for t in (tools or [])) + if not has_websearch_tool: + verbose_logger.debug( + "WebSearchInterception: No web search tool in request" + ) + return False, {} + + # Detect WebSearch tool_calls in response (OpenAI format) + should_intercept, tool_calls = WebSearchTransformation.transform_request( + response=response, + stream=stream, + response_format="openai", + ) + + if not should_intercept: + verbose_logger.debug( + "WebSearchInterception: No WebSearch tool_calls detected in response" + ) + return False, {} + + verbose_logger.debug( + f"WebSearchInterception: Detected {len(tool_calls)} WebSearch tool call(s), executing agentic loop" + ) + + # Return tools dict with tool calls + tools_dict = { + "tool_calls": tool_calls, + "tool_type": "websearch", + "provider": custom_llm_provider, + "response_format": "openai", } return True, tools_dict @@ -303,7 +371,11 @@ class WebSearchInterceptionLogger(CustomLogger): stream: bool, kwargs: Dict, ) -> Any: - """Execute agentic loop with WebSearch execution""" + """ + Execute agentic loop with WebSearch execution for Anthropic Messages API. + + This is the legacy method for Anthropic-style responses. + """ tool_calls = tools["tool_calls"] @@ -321,6 +393,41 @@ class WebSearchInterceptionLogger(CustomLogger): kwargs=kwargs, ) + async def async_run_chat_completion_agentic_loop( + self, + tools: Dict, + model: str, + messages: List[Dict], + response: Any, + optional_params: Dict, + logging_obj: Any, + stream: bool, + kwargs: Dict, + ) -> Any: + """ + Execute agentic loop with WebSearch execution for Chat Completions API. + + Similar to async_run_agentic_loop but for OpenAI-style chat completions. + """ + + tool_calls = tools["tool_calls"] + response_format = tools.get("response_format", "openai") + + verbose_logger.debug( + f"WebSearchInterception: Executing chat completion agentic loop for {len(tool_calls)} search(es)" + ) + + return await self._execute_chat_completion_agentic_loop( + model=model, + messages=messages, + tool_calls=tool_calls, + optional_params=optional_params, + logging_obj=logging_obj, + stream=stream, + kwargs=kwargs, + response_format=response_format, + ) + async def _execute_agentic_loop( self, model: str, @@ -521,6 +628,150 @@ class WebSearchInterceptionLogger(CustomLogger): ) raise + async def _execute_chat_completion_agentic_loop( + self, + model: str, + messages: List[Dict], + tool_calls: List[Dict], + optional_params: Dict, + logging_obj: Any, + stream: bool, + kwargs: Dict, + response_format: str = "openai", + ) -> Any: + """Execute litellm.search() and make follow-up chat completion request""" + + # Extract search queries from tool_calls + search_tasks = [] + for tool_call in tool_calls: + # Handle both Anthropic-style input and OpenAI-style function.arguments + query = None + if "input" in tool_call and isinstance(tool_call["input"], dict): + query = tool_call["input"].get("query") + elif "function" in tool_call: + func = tool_call["function"] + if isinstance(func, dict): + args = func.get("arguments", {}) + if isinstance(args, dict): + query = args.get("query") + + if query: + verbose_logger.debug( + f"WebSearchInterception: Queuing search for query='{query}'" + ) + search_tasks.append(self._execute_search(query)) + else: + verbose_logger.warning( + f"WebSearchInterception: Tool call {tool_call.get('id')} has no query" + ) + # Add empty result for tools without query + search_tasks.append(self._create_empty_search_result()) + + # Execute searches in parallel + verbose_logger.debug( + f"WebSearchInterception: Executing {len(search_tasks)} search(es) in parallel" + ) + search_results = await asyncio.gather(*search_tasks, return_exceptions=True) + + # Handle any exceptions in search results + final_search_results: List[str] = [] + for i, result in enumerate(search_results): + if isinstance(result, Exception): + verbose_logger.error( + f"WebSearchInterception: Search {i} failed with error: {str(result)}" + ) + final_search_results.append( + f"Search failed: {str(result)}" + ) + elif isinstance(result, str): + final_search_results.append(cast(str, result)) + else: + verbose_logger.warning( + f"WebSearchInterception: Unexpected result type {type(result)} at index {i}" + ) + final_search_results.append(str(result)) + + # Build assistant and tool messages using transformation + assistant_message, tool_messages_or_user = WebSearchTransformation.transform_response( + tool_calls=tool_calls, + search_results=final_search_results, + response_format=response_format, + ) + + # Make follow-up request with search results + # For OpenAI format, tool_messages_or_user is a list of tool messages + if response_format == "openai": + follow_up_messages = messages + [assistant_message] + tool_messages_or_user + else: + # For Anthropic format (shouldn't happen in this method, but handle it) + follow_up_messages = messages + [assistant_message, tool_messages_or_user] + + verbose_logger.debug( + "WebSearchInterception: Making follow-up chat completion request with search results" + ) + verbose_logger.debug( + f"WebSearchInterception: Follow-up messages count: {len(follow_up_messages)}" + ) + + # Use litellm.acompletion for follow-up request + try: + # Remove internal parameters that shouldn't be passed to follow-up request + internal_params = { + '_websearch_interception', + 'acompletion', + 'litellm_logging_obj', + 'custom_llm_provider', + 'model_alias_map', + 'stream_response', + 'custom_prompt_dict', + } + kwargs_for_followup = { + k: v for k, v in kwargs.items() + if not k.startswith('_websearch_interception') and k not in internal_params + } + + # Get full model name from kwargs + full_model_name = model + if "custom_llm_provider" in kwargs: + custom_llm_provider = kwargs["custom_llm_provider"] + # Reconstruct full model name with provider prefix if needed + if not model.startswith(custom_llm_provider): + # Check if model already has a provider prefix + if "/" not in model: + full_model_name = f"{custom_llm_provider}/{model}" + + verbose_logger.debug( + f"WebSearchInterception: Using model name: {full_model_name}" + ) + + # Prepare tools for follow-up request (same as original) + tools_param = optional_params.get("tools") + + # Remove tools and extra_body from optional_params to avoid issues + # extra_body often contains internal LiteLLM params that shouldn't be forwarded + optional_params_clean = { + k: v for k, v in optional_params.items() + if k not in {"tools", "extra_body", "model_alias_map","stream_response", "custom_prompt_dict" } + } + + final_response = await litellm.acompletion( + model=full_model_name, + messages=follow_up_messages, + tools=tools_param, + **optional_params_clean, + **kwargs_for_followup, + ) + + verbose_logger.debug( + f"WebSearchInterception: Follow-up request completed, response type: {type(final_response)}" + ) + return final_response + except Exception as e: + verbose_logger.exception( + f"WebSearchInterception: Follow-up request failed: {str(e)}" + ) + raise + async def _create_empty_search_result(self) -> str: """Create an empty search result for tool calls without queries""" return "No search query provided" From ea4e48e13a6d7b6a21d087e836201a52c55f2d72 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:28:06 +0530 Subject: [PATCH 013/212] Add chat completion tool calls support and response transformation --- .../websearch_interception/transformation.py | 185 ++++++++++++++++-- 1 file changed, 171 insertions(+), 14 deletions(-) diff --git a/litellm/integrations/websearch_interception/transformation.py b/litellm/integrations/websearch_interception/transformation.py index 313358822a5..0884d408c84 100644 --- a/litellm/integrations/websearch_interception/transformation.py +++ b/litellm/integrations/websearch_interception/transformation.py @@ -1,7 +1,7 @@ """ WebSearch Tool Transformation -Transforms between Anthropic tool_use format and LiteLLM search format. +Transforms between Anthropic/OpenAI tool_use format and LiteLLM search format. """ from typing import Any, Dict, List, Tuple @@ -17,28 +17,31 @@ class WebSearchTransformation: Handles transformation between: - Anthropic tool_use format → LiteLLM search requests - - LiteLLM SearchResponse → Anthropic tool_result format + - OpenAI tool_calls format → LiteLLM search requests + - LiteLLM SearchResponse → Anthropic/OpenAI tool_result format """ @staticmethod def transform_request( response: Any, stream: bool, + response_format: str = "anthropic", ) -> Tuple[bool, List[Dict]]: """ - Transform Anthropic response to extract WebSearch tool calls. + Transform model response to extract WebSearch tool calls. - Detects if response contains WebSearch tool_use blocks and extracts + Detects if response contains WebSearch tool_use/tool_calls blocks and extracts the search queries for execution. Args: - response: Model response (dict or AnthropicMessagesResponse) + response: Model response (dict, AnthropicMessagesResponse, or ModelResponse) stream: Whether response is streaming + response_format: Response format - "anthropic" or "openai" (default: "anthropic") Returns: (has_websearch, tool_calls): has_websearch: True if WebSearch tool_use found - tool_calls: List of tool_use dicts with id, name, input + tool_calls: List of tool_use/tool_calls dicts with id, name, input/function Note: Streaming requests are handled by converting stream=True to stream=False @@ -54,8 +57,11 @@ class WebSearchTransformation: ) return False, [] - # Parse non-streaming response - return WebSearchTransformation._detect_from_non_streaming_response(response) + # Parse non-streaming response based on format + if response_format == "openai": + return WebSearchTransformation._detect_from_openai_response(response) + else: + return WebSearchTransformation._detect_from_non_streaming_response(response) @staticmethod def _detect_from_non_streaming_response( @@ -114,26 +120,143 @@ class WebSearchTransformation: return len(tool_calls) > 0, tool_calls + @staticmethod + def _detect_from_openai_response( + response: Any, + ) -> Tuple[bool, List[Dict]]: + """Parse OpenAI-style response for WebSearch tool_calls""" + + # Handle both dict and ModelResponse objects + if isinstance(response, dict): + choices = response.get("choices", []) + else: + if not hasattr(response, "choices"): + verbose_logger.debug( + "WebSearchInterception: Response has no choices attribute" + ) + return False, [] + choices = response.choices or [] + + if not choices: + verbose_logger.debug( + "WebSearchInterception: Response has empty choices" + ) + return False, [] + + # Get first choice's message + first_choice = choices[0] + if isinstance(first_choice, dict): + message = first_choice.get("message", {}) + else: + message = getattr(first_choice, "message", None) + + if not message: + verbose_logger.debug( + "WebSearchInterception: First choice has no message" + ) + return False, [] + + # Get tool_calls from message + if isinstance(message, dict): + openai_tool_calls = message.get("tool_calls", []) + else: + openai_tool_calls = getattr(message, "tool_calls", None) or [] + + if not openai_tool_calls: + verbose_logger.debug( + "WebSearchInterception: Message has no tool_calls" + ) + return False, [] + + # Find all WebSearch tool calls + tool_calls = [] + for tool_call in openai_tool_calls: + # Handle both dict and object tool calls + if isinstance(tool_call, dict): + tool_id = tool_call.get("id") + tool_type = tool_call.get("type") + function = tool_call.get("function", {}) + function_name = function.get("name") if isinstance(function, dict) else getattr(function, "name", None) + function_arguments = function.get("arguments") if isinstance(function, dict) else getattr(function, "arguments", None) + else: + tool_id = getattr(tool_call, "id", None) + tool_type = getattr(tool_call, "type", None) + function = getattr(tool_call, "function", None) + function_name = getattr(function, "name", None) if function else None + function_arguments = getattr(function, "arguments", None) if function else None + + # Check for LiteLLM standard or legacy web search tools + if tool_type == "function" and function_name in ( + LITELLM_WEB_SEARCH_TOOL_NAME, "WebSearch", "web_search" + ): + # Parse arguments (might be JSON string) + import json + if isinstance(function_arguments, str): + try: + arguments = json.loads(function_arguments) + except json.JSONDecodeError: + verbose_logger.warning( + f"WebSearchInterception: Failed to parse function arguments: {function_arguments}" + ) + arguments = {} + else: + arguments = function_arguments or {} + + # Convert to internal format (similar to Anthropic) + tool_call_dict = { + "id": tool_id, + "type": "function", + "name": function_name, + "function": { + "name": function_name, + "arguments": arguments, + }, + "input": arguments, # For compatibility with Anthropic format + } + tool_calls.append(tool_call_dict) + verbose_logger.debug( + f"WebSearchInterception: Found {function_name} tool_call with id={tool_id}" + ) + + return len(tool_calls) > 0, tool_calls + @staticmethod def transform_response( tool_calls: List[Dict], search_results: List[str], + response_format: str = "anthropic", ) -> Tuple[Dict, Dict]: """ - Transform LiteLLM search results to Anthropic tool_result format. + Transform LiteLLM search results to Anthropic/OpenAI tool_result format. - Builds the assistant and user messages needed for the agentic loop + Builds the assistant and user/tool messages needed for the agentic loop follow-up request. Args: - tool_calls: List of tool_use dicts from transform_request + tool_calls: List of tool_use/tool_calls dicts from transform_request search_results: List of search result strings (one per tool_call) + response_format: Response format - "anthropic" or "openai" (default: "anthropic") Returns: - (assistant_message, user_message): - assistant_message: Message with tool_use blocks - user_message: Message with tool_result blocks + (assistant_message, user_or_tool_messages): + For Anthropic: assistant_message with tool_use blocks, user_message with tool_result blocks + For OpenAI: assistant_message with tool_calls, tool_messages list with tool results """ + if response_format == "openai": + return WebSearchTransformation._transform_response_openai( + tool_calls, search_results + ) + else: + return WebSearchTransformation._transform_response_anthropic( + tool_calls, search_results + ) + + @staticmethod + def _transform_response_anthropic( + tool_calls: List[Dict], + search_results: List[str], + ) -> Tuple[Dict, Dict]: + """Transform to Anthropic format (single user message with tool_result blocks)""" # Build assistant message with tool_use blocks assistant_message = { "role": "assistant", @@ -163,6 +286,40 @@ class WebSearchTransformation: return assistant_message, user_message + @staticmethod + def _transform_response_openai( + tool_calls: List[Dict], + search_results: List[str], + ) -> Tuple[Dict, List[Dict]]: + """Transform to OpenAI format (assistant with tool_calls, separate tool messages)""" + # Build assistant message with tool_calls + assistant_message = { + "role": "assistant", + "tool_calls": [ + { + "id": tc["id"], + "type": "function", + "function": { + "name": tc["name"], + "arguments": str(tc["input"]), + }, + } + for tc in tool_calls + ], + } + + # Build separate tool messages (one per tool call) + tool_messages = [ + { + "role": "tool", + "tool_call_id": tool_calls[i]["id"], + "content": search_results[i], + } + for i in range(len(tool_calls)) + ] + + return assistant_message, tool_messages + @staticmethod def format_search_response(result: SearchResponse) -> str: """ From 245d705e6ca7ee9c8cda2dccb2d40256480726f2 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:28:28 +0530 Subject: [PATCH 014/212] Add new methods in chat completion --- litellm/integrations/custom_logger.py | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index 07d237c4758..4a341863d4b 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -664,6 +664,37 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac return final_response """ pass + + async def async_should_run_chat_completion_agentic_loop( + self, + response: Any, + model: str, + messages: List[Dict], + tools: Optional[List[Dict]], + stream: bool, + custom_llm_provider: str, + kwargs: Dict, + ) -> Tuple[bool, Dict]: + """ + Hook to determine if chat completion agentic loop should be executed. + """ + return False, {} + + async def async_run_chat_completion_agentic_loop( + self, + tools: Dict, + model: str, + messages: List[Dict], + response: Any, + optional_params: Dict, + logging_obj: "LiteLLMLoggingObj", + stream: bool, + kwargs: Dict, + ) -> Any: + """ + Hook to execute chat completion agentic loop based on context from should_run hook. + """ + pass # Useful helpers for custom logger classes From 6207bf8f6856c185844c481aebe9f5eebb261801 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:28:43 +0530 Subject: [PATCH 015/212] Add chat completion tool format --- .../integrations/websearch_interception/tools.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/litellm/integrations/websearch_interception/tools.py b/litellm/integrations/websearch_interception/tools.py index 4f8b7372fe3..c92c66f41ee 100644 --- a/litellm/integrations/websearch_interception/tools.py +++ b/litellm/integrations/websearch_interception/tools.py @@ -55,6 +55,7 @@ def is_web_search_tool(tool: Dict[str, Any]) -> bool: Detects: - LiteLLM standard: name == "litellm_web_search" + - OpenAI format: type == "function" with function.name == "litellm_web_search" - Anthropic native: type starts with "web_search_" (e.g., "web_search_20250305") - Claude Code: name == "web_search" with a type field - Custom: name == "WebSearch" (legacy format) @@ -68,15 +69,25 @@ def is_web_search_tool(tool: Dict[str, Any]) -> bool: Example: >>> is_web_search_tool({"name": "litellm_web_search"}) True + >>> is_web_search_tool({"type": "function", "function": {"name": "litellm_web_search"}}) + True >>> is_web_search_tool({"type": "web_search_20250305", "name": "web_search"}) True >>> is_web_search_tool({"name": "calculator"}) False """ + print(f"🔥tool: {tool}") tool_name = tool.get("name", "") tool_type = tool.get("type", "") + + # Check for OpenAI format: {"type": "function", "function": {"name": "..."}} + if tool_type == "function" and "function" in tool: + function_def = tool.get("function", {}) + function_name = function_def.get("name", "") + if function_name == LITELLM_WEB_SEARCH_TOOL_NAME: + return True - # Check for LiteLLM standard tool + # Check for LiteLLM standard tool (Anthropic format) if tool_name == LITELLM_WEB_SEARCH_TOOL_NAME: return True From 88778a871dce4378f84e56fbae5b10e60476dd5e Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:29:21 +0530 Subject: [PATCH 016/212] Add callback for websearch in completion method --- litellm/llms/custom_httpx/llm_http_handler.py | 128 +++++++++++++++++- litellm/llms/openai/openai.py | 92 ++++++++++++- 2 files changed, 214 insertions(+), 6 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index d2ea7e872a2..3907ff7abf7 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -302,7 +302,7 @@ class BaseLLMHTTPHandler: logging_obj=logging_obj, signed_json_body=signed_json_body, ) - return provider_config.transform_response( + initial_response = provider_config.transform_response( model=model, raw_response=response, model_response=model_response, @@ -316,6 +316,20 @@ class BaseLLMHTTPHandler: json_mode=json_mode, ) + # Call agentic chat completion hooks + final_response = await self._call_agentic_chat_completion_hooks( + response=initial_response, + model=model, + messages=messages, + optional_params=optional_params, + logging_obj=logging_obj, + stream=False, + custom_llm_provider=custom_llm_provider, + kwargs=litellm_params, + ) + + return final_response if final_response is not None else initial_response + def completion( self, model: str, @@ -412,6 +426,11 @@ class BaseLLMHTTPHandler: }, ) + # Check if stream was converted for WebSearch interception + # This is set by the async_pre_request_hook in WebSearchInterceptionLogger + if litellm_params.get("_websearch_interception_converted_stream", False): + logging_obj.model_call_details["websearch_interception_converted_stream"] = True + if acompletion is True: if stream is True: data = self._add_stream_param_to_request_body( @@ -419,7 +438,7 @@ class BaseLLMHTTPHandler: provider_config=provider_config, fake_stream=fake_stream, ) - return self.acompletion_stream_function( + response = self.acompletion_stream_function( model=model, messages=messages, api_base=api_base, @@ -4361,10 +4380,10 @@ class BaseLLMHTTPHandler: kwargs: Dict, ) -> Optional[Any]: """ - Call agentic completion hooks for all custom loggers. + Call agentic completion hooks for all custom loggers (Anthropic Messages API). - 1. Call async_should_run_agentic_completion to check if agentic loop is needed - 2. If yes, call async_run_agentic_completion to execute the loop + 1. Call async_should_run_agentic_loop to check if agentic loop is needed + 2. If yes, call async_run_agentic_loop to execute the loop Returns the response from agentic loop, or None if no hook runs. """ @@ -4453,6 +4472,105 @@ class BaseLLMHTTPHandler: return None + async def _call_agentic_chat_completion_hooks( + self, + response: Any, + model: str, + messages: List[Dict], + optional_params: Dict, + logging_obj: "LiteLLMLoggingObj", + stream: bool, + custom_llm_provider: str, + kwargs: Dict, + ) -> Optional[Any]: + """ + Call agentic chat completion hooks for all custom loggers (Chat Completions API). + + 1. Call async_should_run_chat_completion_agentic_loop to check if agentic loop is needed + 2. If yes, call async_run_chat_completion_agentic_loop to execute the loop + + Returns the response from agentic loop, or None if no hook runs. + """ + from litellm._logging import verbose_logger + from litellm.integrations.custom_logger import CustomLogger + + callbacks = litellm.callbacks + ( + logging_obj.dynamic_success_callbacks or [] + ) + tools = optional_params.get("tools", []) + + for callback in callbacks: + try: + if isinstance(callback, CustomLogger): + # Check if callback has the chat completion agentic loop method + if not hasattr(callback, "async_should_run_chat_completion_agentic_loop"): + continue + + # First: Check if agentic loop should run + should_run, tool_calls = ( + await callback.async_should_run_chat_completion_agentic_loop( + response=response, + model=model, + messages=messages, + tools=tools, + stream=stream, + custom_llm_provider=custom_llm_provider, + kwargs=kwargs, + ) + ) + + if should_run: + # Second: Execute agentic loop + # Add custom_llm_provider to kwargs so the agentic loop can reconstruct the full model name + kwargs_with_provider = kwargs.copy() if kwargs else {} + kwargs_with_provider["custom_llm_provider"] = custom_llm_provider + agentic_response = await callback.async_run_chat_completion_agentic_loop( + tools=tool_calls, + model=model, + messages=messages, + response=response, + optional_params=optional_params, + logging_obj=logging_obj, + stream=stream, + kwargs=kwargs_with_provider, + ) + # First hook that runs agentic loop wins + return agentic_response + + except Exception as e: + verbose_logger.exception( + f"LiteLLM.AgenticHookError: Exception in chat completion agentic hooks: {str(e)}" + ) + + # Check if we need to convert response to fake stream for chat completions + # This happens when: + # 1. Stream was originally True but converted to False for WebSearch interception + # 2. No agentic loop ran (LLM didn't use the tool) + # 3. We have a non-streaming response that needs to be converted to streaming + websearch_converted_stream = ( + logging_obj.model_call_details.get("websearch_interception_converted_stream", False) + if logging_obj is not None + else False + ) + + if websearch_converted_stream: + from litellm._logging import verbose_logger + from litellm.llms.base_llm.base_model_iterator import ( + convert_model_response_to_streaming, + ) + + verbose_logger.debug( + "WebSearchInterception: No tool call made, converting non-streaming chat completion to fake stream" + ) + + # Convert the non-streaming ModelResponse to a fake stream + if hasattr(response, "choices"): + # Use the existing converter for ModelResponse + fake_stream = convert_model_response_to_streaming(response) + return fake_stream + + return None + def _handle_error( self, e: Exception, diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 8a8070240da..2f0e5e480b5 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -501,6 +501,82 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): else: raise e + async def _call_agentic_completion_hooks_openai( + self, + response: Any, + model: str, + messages: List[Dict], + optional_params: Dict, + logging_obj: LiteLLMLoggingObj, + stream: bool, + litellm_params: Dict, + ) -> Optional[Any]: + """ + Call agentic completion hooks for all custom loggers (OpenAI Chat Completions API). + + 1. Call async_should_run_chat_completion_agentic_loop to check if agentic loop is needed + 2. If yes, call async_run_chat_completion_agentic_loop to execute the loop + + Returns the response from agentic loop, or None if no hook runs. + """ + from litellm._logging import verbose_logger + from litellm.integrations.custom_logger import CustomLogger + + callbacks = litellm.callbacks + ( + logging_obj.dynamic_success_callbacks or [] + ) + print(f"🔥callbacks: {callbacks}") + tools = optional_params.get("tools", []) + print(f"🔥tools: {tools}") + # Get custom_llm_provider from litellm_params + custom_llm_provider = litellm_params.get("custom_llm_provider", "openai") + + for callback in callbacks: + try: + if isinstance(callback, CustomLogger): + # Check if the callback has the chat completion agentic loop methods + if not hasattr(callback, 'async_should_run_chat_completion_agentic_loop'): + continue + + # First: Check if agentic loop should run (using chat completion method) + should_run, tool_calls = ( + await callback.async_should_run_chat_completion_agentic_loop( + response=response, + model=model, + messages=messages, + tools=tools, + stream=stream, + custom_llm_provider=custom_llm_provider, + kwargs=litellm_params, + ) + ) + + if should_run: + # Second: Execute agentic loop + kwargs_with_provider = litellm_params.copy() if litellm_params else {} + kwargs_with_provider["custom_llm_provider"] = custom_llm_provider + + # For OpenAI Chat Completions, use the chat completion agentic loop method + agentic_response = await callback.async_run_chat_completion_agentic_loop( + tools=tool_calls, + model=model, + messages=messages, + response=response, + optional_params=optional_params, + logging_obj=logging_obj, + stream=stream, + kwargs=kwargs_with_provider, + ) + # First hook that runs agentic loop wins + return agentic_response + + except Exception as e: + verbose_logger.exception( + f"LiteLLM.AgenticHookError: Exception in agentic completion hooks for OpenAI: {str(e)}" + ) + + return None + def mock_streaming( self, response: ModelResponse, @@ -844,7 +920,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): logging_obj=logging_obj, ) stringified_response = response.model_dump() - + print(f"🔥stringified_response: {stringified_response}") logging_obj.post_call( input=data["messages"], api_key=api_key, @@ -859,6 +935,20 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): _response_headers=headers, ) + # Call agentic completion hooks (e.g., for websearch_interception) + agentic_response = await self._call_agentic_completion_hooks_openai( + response=final_response_obj, + model=model, + messages=messages, + optional_params=optional_params, + logging_obj=logging_obj, + stream=False, + litellm_params=litellm_params, + ) + + if agentic_response is not None: + final_response_obj = agentic_response + if fake_stream is True: return self.mock_streaming( response=cast(ModelResponse, final_response_obj), From 4b0eb50ddddfe3bc0b70c4ef649391be603e8923 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Thu, 5 Feb 2026 14:29:31 +0530 Subject: [PATCH 017/212] Add test for web search --- test_websearch_chat_completion.py | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 test_websearch_chat_completion.py diff --git a/test_websearch_chat_completion.py b/test_websearch_chat_completion.py new file mode 100644 index 00000000000..e572e4d860c --- /dev/null +++ b/test_websearch_chat_completion.py @@ -0,0 +1,136 @@ +""" +Test script for WebSearch interception with chat completions API. + +This script demonstrates how to use the websearch_interception callback +with litellm.acompletion() for transparent server-side web search execution. +""" +import asyncio +import litellm + +# Enable verbose logging to see what's happening +litellm.set_verbose = True + + +async def test_websearch_chat_completion(): + """Test websearch interception with chat completions API.""" + + # Configure WebSearch interception + litellm.callbacks = ["websearch_interception"] + + print("\n" + "="*80) + print("Testing WebSearch Interception with Chat Completions API") + print("="*80 + "\n") + + # User makes a simple completion call with tools + print("Making request to GPT-4o with litellm_web_search tool...") + print("Question: What's the weather in San Francisco today?") + print("\nExpected behavior:") + print("1. Model calls litellm_web_search tool") + print("2. Server executes web search automatically") + print("3. Server makes follow-up request with search results") + print("4. User gets final answer\n") + + response = await litellm.acompletion( + model="gpt-4o", + messages=[ + {"role": "user", "content": "What's the weather in San Francisco today?"} + ], + tools=[ + { + "type": "function", + "function": { + "name": "litellm_web_search", + "description": "Search the web for information", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"} + }, + "required": ["query"] + } + } + } + ] + ) + + print("\n" + "-"*80) + print("FINAL RESPONSE:") + print("-"*80) + print(f"\nContent: {response.choices[0].message.content}") + print(f"\nFinish reason: {response.choices[0].finish_reason}") + + # Check if we got tool_calls (should NOT if agentic loop worked) + if hasattr(response.choices[0].message, 'tool_calls') and response.choices[0].message.tool_calls: + print("\n⚠️ WARNING: Got tool_calls in response!") + print("This means the agentic loop did NOT execute automatically.") + print(f"Tool calls: {response.choices[0].message.tool_calls}") + else: + print("\n✅ SUCCESS: No tool_calls in response!") + print("The agentic loop executed automatically and returned the final answer.") + + print("\n" + "="*80 + "\n") + + +async def test_streaming_websearch(): + """Test websearch interception with streaming.""" + + # Configure WebSearch interception + litellm.callbacks = ["websearch_interception"] + + print("\n" + "="*80) + print("Testing WebSearch Interception with STREAMING") + print("="*80 + "\n") + + print("Making STREAMING request to GPT-4o with litellm_web_search tool...") + print("Question: What are the latest AI news?") + + response = await litellm.acompletion( + model="gpt-4o", + messages=[ + {"role": "user", "content": "What are the latest AI news from today?"} + ], + tools=[ + { + "type": "function", + "function": { + "name": "litellm_web_search", + "description": "Search the web for information", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"} + } + } + } + } + ], + stream=True + ) + + print("\n" + "-"*80) + print("STREAMING RESPONSE:") + print("-"*80 + "\n") + + full_content = "" + async for chunk in response: + if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: + content = chunk.choices[0].delta.content + print(content, end="", flush=True) + full_content += content + + print("\n\n✅ Streaming completed successfully!") + print(f"Total content length: {len(full_content)} chars") + print("\n" + "="*80 + "\n") + + +if __name__ == "__main__": + print("\nWebSearch Interception Test Suite") + print("==================================\n") + print("This test demonstrates transparent server-side web search execution.") + print("The agentic loop happens automatically - user just gets the final answer.\n") + + # Run tests + asyncio.run(test_websearch_chat_completion()) + + # Uncomment to test streaming + # asyncio.run(test_streaming_websearch()) From a2e70a561d103497fed719829c48eb597142f214 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 08:20:50 +0530 Subject: [PATCH 018/212] Potential fix for code scanning alert no. 4046: Clear-text logging of sensitive information Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- litellm/llms/openai/openai.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 2f0e5e480b5..c6f502d3a25 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -525,9 +525,15 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM): callbacks = litellm.callbacks + ( logging_obj.dynamic_success_callbacks or [] ) - print(f"🔥callbacks: {callbacks}") + # Avoid logging full callback objects to prevent leaking sensitive data + verbose_logger.debug( + "LiteLLM.AgenticHooks: callbacks_count=%s", len(callbacks) + ) tools = optional_params.get("tools", []) - print(f"🔥tools: {tools}") + # Avoid logging full tools payloads; they may contain sensitive parameters + verbose_logger.debug( + "LiteLLM.AgenticHooks: tools_count=%s", len(tools) if isinstance(tools, list) else 1 if tools else 0 + ) # Get custom_llm_provider from litellm_params custom_llm_provider = litellm_params.get("custom_llm_provider", "openai") From f12875bd428d74cb46cc24d19ec44d4f3bdafc32 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 08:21:09 +0530 Subject: [PATCH 019/212] Update litellm/integrations/websearch_interception/tools.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/integrations/websearch_interception/tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/integrations/websearch_interception/tools.py b/litellm/integrations/websearch_interception/tools.py index c92c66f41ee..be8808622da 100644 --- a/litellm/integrations/websearch_interception/tools.py +++ b/litellm/integrations/websearch_interception/tools.py @@ -76,7 +76,6 @@ def is_web_search_tool(tool: Dict[str, Any]) -> bool: >>> is_web_search_tool({"name": "calculator"}) False """ - print(f"🔥tool: {tool}") tool_name = tool.get("name", "") tool_type = tool.get("type", "") From 186fd2e64e93f773943bdc3a3a8dd1e1602c8601 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 09:24:43 +0530 Subject: [PATCH 020/212] Add adaptive thinking support for anthropic opus 4.6 --- litellm/llms/anthropic/chat/transformation.py | 19 +++-- .../test_anthropic_chat_transformation.py | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 1b61b533275..f7a11add35b 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -170,9 +170,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_call["caller"] = cast(Dict[str, Any], anthropic_tool_content["caller"]) # type: ignore[typeddict-item] return tool_call - def _is_claude_opus_4_5(self, model: str) -> bool: + def _is_claude_opus_4_6(self, model: str) -> bool: """Check if the model is Claude Opus 4.5.""" - return "opus-4-5" in model.lower() or "opus_4_5" in model.lower() + return "opus-4-6" in model.lower() or "opus_4_6" in model.lower() def get_supported_openai_params(self, model: str): params = [ @@ -860,14 +860,13 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if param == "thinking": optional_params["thinking"] = value elif param == "reasoning_effort" and isinstance(value, str): - # For Claude Opus 4.5, map reasoning_effort to output_config - if self._is_claude_opus_4_5(model): - optional_params["output_config"] = {"effort": value} - - # For other models, map to thinking parameter - optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( - value - ) + # For Claude Opus 4.6, map reasoning_effort to new adaptive thinking type + if self._is_claude_opus_4_6(model): + optional_params["thinking"] = {"type": "adaptive"} + else: + optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( + value + ) elif param == "web_search_options" and isinstance(value, dict): hosted_web_search_tool = self.map_web_search_tool( cast(OpenAIWebSearchOptions, value) diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index eee0b267fad..f556c4abe51 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -1934,6 +1934,75 @@ def test_calculate_usage_completion_tokens_details_with_reasoning(): assert usage.completion_tokens == 500 +# ============ Reasoning Effort Tests ============ + + +def test_reasoning_effort_maps_to_adaptive_thinking_for_opus_4_6(): + """ + Test that reasoning_effort maps to adaptive thinking type for Claude Opus 4.6. + + For Claude Opus 4.6, reasoning_effort should map to {"type": "adaptive"} + regardless of the effort level specified. + """ + config = AnthropicConfig() + + # Test with different reasoning_effort values - all should map to adaptive + for effort in ["low", "medium", "high", "minimal"]: + non_default_params = {"reasoning_effort": effort} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="claude-opus-4-6-20250514", + drop_params=False + ) + + # Should map to adaptive thinking type + assert "thinking" in result + assert result["thinking"]["type"] == "adaptive" + # Should not have budget_tokens for adaptive type + assert "budget_tokens" not in result["thinking"] + # reasoning_effort should not be in the result (it's transformed to thinking) + assert "reasoning_effort" not in result + + +def test_reasoning_effort_maps_to_budget_thinking_for_non_opus_4_6(): + """ + Test that reasoning_effort maps to budget-based thinking config for non-Opus 4.6 models. + + For models other than Claude Opus 4.6, reasoning_effort should map to + thinking config with budget_tokens based on the effort level. + """ + config = AnthropicConfig() + + # Test with Claude Sonnet 4.5 (non-Opus 4.6 model) + test_cases = [ + ("low", 1024), # DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET + ("medium", 2048), # DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET + ("high", 4096), # DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET + ("minimal", 128), # DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET + ] + + for effort, expected_budget in test_cases: + non_default_params = {"reasoning_effort": effort} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="claude-sonnet-4-5-20250929", + drop_params=False + ) + + # Should map to enabled thinking type with budget_tokens + assert "thinking" in result + assert result["thinking"]["type"] == "enabled" + assert result["thinking"]["budget_tokens"] == expected_budget + # reasoning_effort should not be in the result (it's transformed to thinking) + assert "reasoning_effort" not in result + + def test_code_execution_tool_results_extraction(): """ Test that code execution tool results (bash_code_execution_tool_result, From f15dd691b479190353fc984749717c4eb8bcce4f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 09:28:50 +0530 Subject: [PATCH 021/212] Fix anthropic.claude-opus-4-6-v1 for bedrock --- ...model_prices_and_context_window_backup.json | 2 +- model_prices_and_context_window.json | 2 +- .../test_claude_opus_4_6_config.py | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 66c8e55c3ae..39c95b893f5 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1023,7 +1023,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "us.anthropic.claude-opus-4-6-v1:0": { + "us.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 66c8e55c3ae..39c95b893f5 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1023,7 +1023,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "us.anthropic.claude-opus-4-6-v1:0": { + "us.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, diff --git a/tests/test_litellm/test_claude_opus_4_6_config.py b/tests/test_litellm/test_claude_opus_4_6_config.py index 2b01ba6d99c..8823f0d66a6 100644 --- a/tests/test_litellm/test_claude_opus_4_6_config.py +++ b/tests/test_litellm/test_claude_opus_4_6_config.py @@ -72,7 +72,7 @@ def test_opus_4_6_bedrock_regional_model_pricing(): model_data = json.load(f) expected_models = { - "global.anthropic.claude-opus-4-6-v1:0": { + "global.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5e-06, "output_cost_per_token": 2.5e-05, "cache_creation_input_token_cost": 6.25e-06, @@ -82,7 +82,7 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost_above_200k_tokens": 1e-06, }, - "us.anthropic.claude-opus-4-6-v1:0": { + "us.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, "cache_creation_input_token_cost": 6.875e-06, @@ -92,7 +92,7 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, }, - "eu.anthropic.claude-opus-4-6-v1:0": { + "eu.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, "cache_creation_input_token_cost": 6.875e-06, @@ -102,7 +102,7 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, }, - "apac.anthropic.claude-opus-4-6-v1:0": { + "apac.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, "cache_creation_input_token_cost": 6.875e-06, @@ -128,8 +128,8 @@ def test_opus_4_6_bedrock_regional_model_pricing(): def test_opus_4_6_bedrock_converse_registration(): - assert "anthropic.claude-opus-4-6-v1:0" in litellm.BEDROCK_CONVERSE_MODELS - assert "global.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models - assert "us.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models - assert "eu.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models - assert "apac.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models + assert "anthropic.claude-opus-4-6-v1" in litellm.BEDROCK_CONVERSE_MODELS + assert "global.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models + assert "us.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models + assert "eu.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models + assert "apac.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models From 1bcd407af6581ace682e9a5e56ace4319d6ac025 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 09:40:16 +0530 Subject: [PATCH 022/212] Add adaptive thiking for bedrock converse --- litellm/llms/anthropic/chat/transformation.py | 63 ++++++++++--------- .../bedrock/chat/converse_transformation.py | 5 +- .../llms/databricks/chat/transformation.py | 3 +- ...odel_prices_and_context_window_backup.json | 8 +-- model_prices_and_context_window.json | 8 +-- 5 files changed, 45 insertions(+), 42 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index f7a11add35b..61616c7f1b4 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -170,7 +170,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_call["caller"] = cast(Dict[str, Any], anthropic_tool_content["caller"]) # type: ignore[typeddict-item] return tool_call - def _is_claude_opus_4_6(self, model: str) -> bool: + @staticmethod + def _is_claude_opus_4_6(model: str) -> bool: """Check if the model is Claude Opus 4.5.""" return "opus-4-6" in model.lower() or "opus_4_6" in model.lower() @@ -659,32 +660,38 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): @staticmethod def _map_reasoning_effort( - reasoning_effort: Optional[Union[REASONING_EFFORT, str]], + reasoning_effort: Optional[Union[REASONING_EFFORT, str]], + model: str, ) -> Optional[AnthropicThinkingParam]: - if reasoning_effort is None: - return None - elif reasoning_effort == "low": + if AnthropicConfig._is_claude_opus_4_6(model): return AnthropicThinkingParam( - type="enabled", - budget_tokens=DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, - ) - elif reasoning_effort == "medium": - return AnthropicThinkingParam( - type="enabled", - budget_tokens=DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, - ) - elif reasoning_effort == "high": - return AnthropicThinkingParam( - type="enabled", - budget_tokens=DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, - ) - elif reasoning_effort == "minimal": - return AnthropicThinkingParam( - type="enabled", - budget_tokens=DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET, + type="adaptive", ) else: - raise ValueError(f"Unmapped reasoning effort: {reasoning_effort}") + if reasoning_effort is None: + return None + elif reasoning_effort == "low": + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + ) + elif reasoning_effort == "medium": + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, + ) + elif reasoning_effort == "high": + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + ) + elif reasoning_effort == "minimal": + return AnthropicThinkingParam( + type="enabled", + budget_tokens=DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET, + ) + else: + raise ValueError(f"Unmapped reasoning effort: {reasoning_effort}") def _extract_json_schema_from_response_format( self, value: Optional[dict] @@ -860,13 +867,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if param == "thinking": optional_params["thinking"] = value elif param == "reasoning_effort" and isinstance(value, str): - # For Claude Opus 4.6, map reasoning_effort to new adaptive thinking type - if self._is_claude_opus_4_6(model): - optional_params["thinking"] = {"type": "adaptive"} - else: - optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( - value - ) + optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( + reasoning_effort=value, model=model + ) elif param == "web_search_options" and isinstance(value, dict): hosted_web_search_tool = self.map_web_search_tool( cast(OpenAIWebSearchOptions, value) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 6591e152a14..22fccd8f943 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -30,8 +30,6 @@ from litellm.litellm_core_utils.prompt_templates.factory import ( from litellm.llms.anthropic.chat.transformation import AnthropicConfig from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException from litellm.types.llms.bedrock import * - -from ..common_utils import is_claude_4_5_on_bedrock from litellm.types.llms.openai import ( AllMessageValues, ChatCompletionAssistantMessage, @@ -68,6 +66,7 @@ from ..common_utils import ( BedrockModelInfo, get_anthropic_beta_from_headers, get_bedrock_tool_name, + is_claude_4_5_on_bedrock, ) # Computer use tool prefixes supported by Bedrock @@ -431,7 +430,7 @@ class AmazonConverseConfig(BaseConfig): else: # Anthropic and other models: convert to thinking parameter optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( - reasoning_effort + reasoning_effort=reasoning_effort, model=model ) def get_supported_openai_params(self, model: str) -> List[str]: diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 2b7f5dd5995..e9ae94307d4 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -298,7 +298,8 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): if "reasoning_effort" in non_default_params and "claude" in model: optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( - non_default_params.get("reasoning_effort") + reasoning_effort=non_default_params.get("reasoning_effort"), + model=model ) optional_params.pop("reasoning_effort", None) ## handle thinking tokens diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 39c95b893f5..5099218e592 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -963,7 +963,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, - "anthropic.claude-opus-4-6-v1:0": { + "anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost": 5e-07, @@ -993,7 +993,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "global.anthropic.claude-opus-4-6-v1:0": { + "global.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost": 5e-07, @@ -1053,7 +1053,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "eu.anthropic.claude-opus-4-6-v1:0": { + "eu.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1083,7 +1083,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "apac.anthropic.claude-opus-4-6-v1:0": { + "apac.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 39c95b893f5..5099218e592 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -963,7 +963,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, - "anthropic.claude-opus-4-6-v1:0": { + "anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost": 5e-07, @@ -993,7 +993,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "global.anthropic.claude-opus-4-6-v1:0": { + "global.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost": 5e-07, @@ -1053,7 +1053,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "eu.anthropic.claude-opus-4-6-v1:0": { + "eu.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1083,7 +1083,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "apac.anthropic.claude-opus-4-6-v1:0": { + "apac.anthropic.claude-opus-4-6-v1": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, From 08a6fe2bfa2ad937bc2e340ab47b35399857fed2 Mon Sep 17 00:00:00 2001 From: Swayambhu Date: Fri, 6 Feb 2026 09:44:02 +0530 Subject: [PATCH 023/212] refactor: migrate Ant Design notifications to use `App.useApp()` context via a new global provider. 1. notifications_manager.tsx - Hybrid notification approach: Added notificationInstance variable to store the context-based instance Added setNotificationInstance() function to inject the instance from context Created getNotification() helper that prefers context instance, falls back to static Added COMMON_NOTIFICATION_PROPS (exported) with showProgress: true and pauseOnHover: true All notification methods ( error , warning , info , success , fromBackend ) now spread COMMON_NOTIFICATION_PROPS 2. AntdGlobalProvider.tsx - New context provider: Wraps app with Antd's component Uses App.useApp() hook to get the context-based notification instance Injects it into NotificationManager via setNotificationInstance() --- ui/litellm-dashboard/src/app/layout.tsx | 6 ++- .../molecules/notifications_manager.tsx | 38 ++++++++++++------- .../src/contexts/AntdGlobalProvider.tsx | 25 ++++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx diff --git a/ui/litellm-dashboard/src/app/layout.tsx b/ui/litellm-dashboard/src/app/layout.tsx index 95c485fe2f0..1233da9046f 100644 --- a/ui/litellm-dashboard/src/app/layout.tsx +++ b/ui/litellm-dashboard/src/app/layout.tsx @@ -2,6 +2,8 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import AntdGlobalProvider from "@/contexts/AntdGlobalProvider"; + const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { @@ -17,7 +19,9 @@ export default function RootLayout({ }>) { return ( - {children} + + {children} + ); } diff --git a/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx b/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx index 71b14bee885..59b048b412c 100644 --- a/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx +++ b/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx @@ -1,8 +1,18 @@ import React from "react"; -import { notification } from "antd"; +import { notification as staticNotification } from "antd"; +import type { NotificationInstance } from "antd/es/notification/interface"; import { parseErrorMessage } from "../shared/errorUtils"; import { ArgsProps } from "antd/es/notification"; +let notificationInstance: NotificationInstance | null = null; + +export const setNotificationInstance = (instance: NotificationInstance) => { + notificationInstance = instance; +}; + +// Helper to get the best available notification instance +const getNotification = () => notificationInstance || staticNotification; + type Placement = "top" | "topLeft" | "topRight" | "bottom" | "bottomLeft" | "bottomRight"; type NotificationConfig = { @@ -251,7 +261,7 @@ function looksErrorPayload(input: any, status?: number): boolean { const NotificationManager = { error(input: string | NotificationConfig) { const cfg = normalize(input, "Error"); - notification.error({ + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -261,7 +271,7 @@ const NotificationManager = { warning(input: string | NotificationConfig) { const cfg = normalize(input, "Warning"); - notification.warning({ + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -271,7 +281,7 @@ const NotificationManager = { info(input: string | NotificationConfig) { const cfg = normalize(input, "Info"); - notification.info({ + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -281,7 +291,7 @@ const NotificationManager = { success(input: string | React.ReactNode | NotificationConfig) { if (React.isValidElement(input)) { - notification.success({ + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, message: "Success", description: input, @@ -291,7 +301,7 @@ const NotificationManager = { return; } const cfg = normalize(input as string | NotificationConfig, "Success"); - notification.success({ + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -316,11 +326,11 @@ const NotificationManager = { title === "Content Blocked" || title === "Integration Error" ) { - notification.warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 7 }); + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 7 }); return; } if (title === "Server Error") { - notification.error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 8 }); + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 8 }); return; } if ( @@ -331,10 +341,10 @@ const NotificationManager = { title === "Error" || title === "Already Exists" ) { - notification.error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); return; } - notification.info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); return; } @@ -343,18 +353,18 @@ const NotificationManager = { const payload = { ...base, message: cls?.title ?? "Info" }; if (cls?.kind === "success") { - notification.success({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 3.5 }); + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 3.5 }); return; } if (cls?.kind === "warning") { - notification.warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); return; } - notification.info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); }, clear() { - notification.destroy(); + getNotification().destroy(); }, }; diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx new file mode 100644 index 00000000000..b10aa715ed5 --- /dev/null +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -0,0 +1,25 @@ +"use client"; + +import React, { useEffect } from "react"; +import { App } from "antd"; +import { setNotificationInstance } from "@/components/molecules/notifications_manager"; + +// Inner component to use the hook +const AntdAppInit = () => { + const { notification } = App.useApp(); + + useEffect(() => { + setNotificationInstance(notification); + }, [notification]); + + return null; +}; + +export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + ); +} From eee37c569ddad6ca13d9055980506e605a77430e Mon Sep 17 00:00:00 2001 From: Swayambhu Date: Fri, 6 Feb 2026 10:01:44 +0530 Subject: [PATCH 024/212] fix: ensure Ant Design notification instance is initialized only once using `useRef`. --- ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx index b10aa715ed5..c5adbff86b2 100644 --- a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -1,15 +1,19 @@ "use client"; -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { App } from "antd"; import { setNotificationInstance } from "@/components/molecules/notifications_manager"; // Inner component to use the hook const AntdAppInit = () => { const { notification } = App.useApp(); + const initialized = useRef(false); useEffect(() => { - setNotificationInstance(notification); + if (!initialized.current) { + setNotificationInstance(notification); + initialized.current = true; + } }, [notification]); return null; From ec5dc0be514543c742ae98729e60ef9060e42c48 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 20:42:43 -0800 Subject: [PATCH 025/212] email soft budgets --- ...litellm_enterprise-0.1.29-py3-none-any.whl | Bin 111358 -> 112486 bytes .../dist/litellm_enterprise-0.1.29.tar.gz | Bin 48839 -> 49967 bytes .../send_emails/base_email.py | 102 ++++++++++++++++-- .../integrations/email_templates/templates.py | 24 +++++ litellm/proxy/_types.py | 5 + litellm/proxy/auth/auth_checks.py | 67 ++++++++++++ litellm/proxy/auth/user_api_key_auth.py | 1 + litellm/proxy/utils.py | 29 ++++- 8 files changed, 216 insertions(+), 12 deletions(-) diff --git a/enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl index 0895ecbc4271ba78bed1da72468e19f87324b33a..5eedd1fe876e110a84047318cefd194078254fbb 100644 GIT binary patch delta 24676 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 z�x{2IWSD-&Uuta9RaVyR@PWpmse+1yN(qI17OIck^gEH^~p)I1Cs={a^`}N=v0I z6P%B}K!+WFn92D$7JV^8t}riWYHbZm8Cx^oL7o$n zvHxJz1)YYtNtIZ2*fhkNAUEphy6a}K)w*2teLKD%>hL_xAlMXZI?csWADRi}x`p)9 z5UTNlvdt*vK-fhoTy~VE?h}woUbbpLatv61)LUh3#jPMU`~B<;a9)+OJp%-|CI-mS zug?H`!_6`H=Id>DcC2QYu4n;}IpHk}dAn7DdbNDXGtx!Js5PrAh_y~Fr&seh%pn!Z zTz`-Y?p(`(ccY&y!H}U``l^D@a-_pa$6PE6jSF`yG~7$lS%7=CKcj&hx?|p%hgVpC zYX@-E+0!b{5t%CCV~=Q+*-;vnHyBpwupWO?>_K=k3!<+b@ z$qHx&)IQRq-NcF=)(K?sK!ra8`Js8VqZtH3_tN+Yf7te(yXc~Tc@}4c^&z{wHS5?l zdnV3qpbyadEsc_ntD&iGL~s5gzlN57F1Z-WAiCf|nb1Gd-NNgaX%J$dZ2Dm!Iv_F% zF_4=2!oD|V>B~88R1T}3L6ELVkBkimnhP8U*(J-E;txHP%j+x%Sp+O+tb*k1^9nZ^ z%9sMN7yPUxnFBNW1RbXwJf~?8gAm5|y=l7G5BFa#mV4LX_j@;gx!D(v`rfyH@Gni+ zMNrUwZ#oZ(wgc9io(6x=_) zGMvXSD(`X7d^;0c_;TG@g$D<r-FMmj}gFl;wHr@gU)rdm~|MY z+BvCuleOd6C|?BrG)Cdr(TQffano(xbBYWf?}7m&0$^p-z0*P41MrY*=cs7kVuRVD(*>FUT;+;(N3{QD~lo5%+aS z%+{4CoSiZQ0$$%nmXp<(`g4?1JA0GSVm8_iBCy@Hy z(;dv(4?whu3iiQf7vAV;Os^_+##D>wd&$;MR_87`f0|m~K#41V^0|5RCV;Tc<_Rv5 z*67NOP0VY>^a9M_|K>orIykVPkVVx>9X_fL0yC!C%I)s&{#BEiR!9GZ4653 z1O%ysX^KwkQGIZKZi|gHh{w|X&HF_~I#ip(k%3pjA^9^>drg1pYl4db*&tT??=+CP!wZsnGx4IX) z-6bYQW%*u1AYbjcsmg4&G4BoddS7=%oBg-?N?MwQG=sW-^0s-1EBlyznNK~d_O6>0 zf@RJz;dU&9wjI~YUGjb^nH$7^H3;;^0J6N;Q>_tJ^5mOR;uiHL32< z(4K4dn_ZdlAG3_(X(b7^T5u~v2LJ%)m;ML=LVr_pcz77aMSeJ`8&>rU6Tq zuHCXF>#|`*5D>IXCv0R<6REUb;s3rnQj|%Pa?`D=5Y!TRJRZNh`*>utESp@(s#4@a zXnt|=GyR#>?2fN4mv4Egui3KVL`zcM^YVd+HPQE+ESWCv$y>Q3LXlE#nu=>aC#+sM ziGNZmP41Z@OU`SeTUI{sRX)jJw#mAc8&bh+Rkg`!*9jQ4X=JMjzT55<(_D)UcRlXg z_UdgT>O0qW{pRYNp1nLj`{~s$Kg`KxqlK(lH76xg`f$2R-rad)A9=f!iqoc*@3*Ft zx04AhB2c=R5O@MbW8hyw-F00)Y!VTRgm#o#w3@`PzIWRI*8kSgeF zlZ|UpL2P$z12JA?MrVe3t2`Y;X4@$tqigwY$ZAgzL~02hY`N~*n!I3@I_Oq%=)G<` zo?NBxY6|f37SZN?;M`03pz@8SAb*PH4`H;AdVJ{B>&rji(4Mq<&U%6J`{~E4yQ{@o^j@c86{3r)wYUn907C|G*BZ-jKyCE|c`r^dHz1+e zJ0^63De~2FfUgfyY4%Bt`((Zr;BIK&hHHIy&s$!62-@(;Y}#fE@*xv7{M`5nJSWhI z-o0zNQuqn#q5E&_yxq-Xb$@=ggSI~3L&04l4qdLVXutfMTQ75DYj^tEfU^j+ofrl} zzA{+b2*FPf4Fnzd7A-&@W&xQzY7a)Oco-S}=#K}`G(zb3Y_s6Yvfa1|i%5H_mdHiD zma!oQ*zC6l(U?L8@%<3ZumjpJS*M@~(w3Hi?1_mGw-RE7iCAUP9DlQ@1xZZQ9xvm= zfyLvsq!1s4ViF8@SqCFNo`lm#S!lMC>y;g6fRwUBpAOampufmEvGgzLMw>OC=)Iul z6RcflzjnOcqNR4;SKkBkI6!tL(@ouum4n%|h;&tf5p_84vV*({K6Sf|K?MqHWDOVu z+`6xuat3unEBIr-dw-^}HntYd;+O&PaYh#DeZf5gi*wC@a9M($-IIauTFM$hANZD< z5AfK+@K@P#hQztt5wj10f1H!+m*?d4yX&); zrnbKP{rc+nYx3gN#d*kKegTS0gt*1iWDJcvZQhI*q_?rd(uWvR@Yz3C<=1wMMSUnJ+hj zshzbLvF135Ho)F+IFj&1Rm186ojx*Tp#0)s3rNc+@h+0BDPD{>!Nh3}tI6Xhy)|< zb3);-uWS8|W2H}QS2}AFYb+9NPs*76ZN|KRw-*}yhcN{YjcL0rB^|#TITCSxM()$G zNM-7Ht$&q5BXk9F{R z6*nKUR#xUl0P9k`N$8=&rfmj=iiQq+Jk4D> zRb5#^ywZChg=&lgp*De)3?<63^B)pS;W5_{QA0t6&umA8ZO#P=_@s58KvmX0xT`Td zkxxYz?CuF~<8r8Nc)gm!dwiKu@p>AH&&aprdA#9=)2pNN|I542J}=_iPxth-q0VxLxkji9ArzQXZ%*B_U7Xdea zYjfMkk>B+zrr-}IeH7OFNa{{0=1!L7wKkUJljJ-~r9wgEP{JAn7yz`bsPx}&y661@ zK}p`EQin>`A~27h?w;1r~& zp|ek9Q8(mW(b0GBj>$5w@@w{SQIxcQB}K&=proOHU(>cDSM~gMLh@?P-mJ=eM&av{ zd?~scvaIK{+-+BMRxFAc)+fb$XQx=M>ZT)kdt1$lTK>GsTl)K-AR#yvYUoGhY?5&9~?09}d6HDArKmm8_+*T=E*?hpcg0uUR(}@|iYQbxX6dzP_f7 zRW!}k>tZe|z~A=gSw6dA!Pw-LiS!-#NLhudWxTUQNM%E>SfMxU`(ZU-)odiK-4Fxj>us7bI#bp6Xos-pATP^vcs;?N zf+4eG&XCaLd45&W;MbF@U+JtfzJF|Kv;X1fPxN+w-Q7%f#vv%`6|M4r0*BmYRKgoX z4+k+T+Kh89D?VckKhM?`V|Vs1;;oqT-mhuL%5}3F_9lalRWbQimz&j$+Es@b;bpO@ z8b_py;q=C4B()ljPc;@DVHStwOj0j!LWZ6f7^ao zH!C&+`TJAauGj>jEu*viJ2JumDHXR#8Dg^4D?qFD=1o!2bB6Wm`j`4je0g^)yEWMM z3mBt%eELkH85iuCM|B^`O>1G)24256Xgpi0zm!b_5O? zj8iVkFAz|a{g?PL0L?TCjMdrH>5G;v-7`C6eZ;$vADt%Q_0G<$%v%`B>B(RJmI;j$1`$(4Smj=0%TcBm}btqt|_jV3Tm;;oGwU~6;;t?8E0j9T3Rk9 zq!55-Yk)ihZA140!oCenT{r$@elThSdl)siC|>3( zFjd+;v`8)h2!O;W$Be8dW0ptuiQZ=O0swN=5~7;y{*k<|E2>d$TN(`ci&|p=N12>4 zRDmTS?KZ(}C{qa^Kex+@`Sebp>K^Plq*0<;8>_#VJTZ_5R$^6m4qzt2gr#VzD@Na# z+P2|G^w?y7o?05z8m#VM>gh)Fu?aJ^e+JrR){xMvqVINEGP`nQS4=;n@*9oEeiJiP zY)lW|PCaU;_$D-<^=iIlt+Dkh|B@G7d|swFp|Tc?3;6A$o^=*SHx75&L*mN~szU;6@r>lc$`d&C*p^iDvQGLnp*J71rV?-^MF z?HXW4T3}yT?Ib1?7>}$=&@_e=w2X>$ew8|Z(hHcS11*&D>1GcYK$r?p0(x;X#umwT zS{TioInaK7wMXvwi0)0D+-n!{c6%%%uV9~z#I0;=;S*l;kR-I?N}j-!>VjQLqLW+fH(=Oo7+%=C*=X53!m2TZ*A_C1L&T-ldd0Z5vxoZShBkD1mw3Yk zlfdJ#i7)YNV+kH%jIaj$Z}@#sU%o|%bxk{BuiuJ@wBRrzYo(Vp?Vbf&%C3W4>fm8J zqf8@dOXJL{+%oHC-8u;3FZ~+#{wvXYoP>`jf5TQFJ_;kugkVPGo1?!nv5id--VN?R zm{x3E5y~X?EjYfo-yAkKK2f^D8Wbcfh>ab)^|-J!vyX116PrcT?GckCo`Y$?Oqem&T=nE2_Qq=zV0c3ZiYtt14O=ZM@^O zfUM}SJ}XYUO-{I{<9^#OXYo2?JyMr*uCX;*s-i0xtK!>#Dy0lU*+8*HwZEWl@}i|g z?L3aQT{OTleNnI3l5%V!iIXdUr$^gSf5EBEttS}W6PiZ>kcX3!Bj5?hDCUGk%GA&@ z?nIg8Rfei-mY3zG`Qm&Q^Je4vM9-9~x-PBR!ME-i@4*H=G(>K4rWt25+P0wm@HOvM zZ^p~|LgljQ6II80jb?ZNf4lkSyopIj6S$y|Gz+BKt|QYaaR(}DyvfVflZs(tf5%qh zu@(4lMsVrfn?GFx4`CUlPHn0`a1|6|q7WnXuZA4oQa@|;XkcgcIEcN@8>S(cSv#+* z=bg|Bv}x+b=u@G;)neb}NN_T9mPCIDZKK|xVNMWu%3@iV+h$C0NS&<<>F$4Atu^_o9 zC^1OD_atI$%+6~Gr!J{RG9h0m`BJZ$QBYAH>4i{48) z-6_LBmEh&A@v)evYAk&Gi$yWRy?4e04J{$A;FdwY!b*Jl84W8%Ae(DblLG?N^-;Xw zxA!=%kbkK^P%kj`0z}<&f209y7`o0O;atA=TY5$~=m0k=&o4K%-2X+4VNDZRQ9Wtn z%mnembUj@T(zRxfTr~HzUhqAuyW|?YKUh3GRLaJ}cmPF4UZE7>a)g;b^%vwwhOusU zfd$pHQ%&Ie4D|`?kN?Z+{P1!cZjx|dY$A3(n=w_`VfTKYe!t87f9uG6^Md?1A>Y4z z8DJE_QQH}~o^`_B--aw)lM432smnjnsR5(?wQiW8WM+-ziX6z6%xODI?n5i6@UVn^ z((Yl{thLtz+;E|{3jwiM1w@=ciA|HPx%KxbpOLe!UXi+*QP4a$`Dc)x_4@jT^H_vf z@Gv$gX*IIo9FspXf0NN*nz7#{4vt~v(~kFvw6d-fyEBO6(Tf)vB%fBQT8h>+@5i%QuA zhDXD-q@8FF< zC|5^J4@CFew%;6y#2(!N+nm))>Ti#=&VEsk6U-wXlVRp|k0nHfHr6pkl|tgB=SKk_ zgdJ2?Ftvt02$vgq*a^?~c(CJy4X7#TXDa)W+a%T^f0OrxVJL}oK^fliRShB6;1tx2 z*wo#e7%*}Hu>~IPY}f^C0S+>Pd!~G%!P3kFk!R`HP42mU8XP{*of*SxfJC-!?rR&v zA>xfSP#TvOR`yOu>E(~%J;^zntwlLqB)~=FE;{e-*>aBZ1qoU;wzmvr z)}4!rfBVGm)0<&oFC)$lIe8+}LVh+R?^g4}-kLB)-@yh4p_afn@T1qp;_#$m%Swd5 zRjag-;G%J$y!vCmxpquUdw^zlI%Q1&#YB%)HG&4h@Bn)nBxjg_c{1Z(56%Ux)r8h1 zz;F&|7W?gyBfiK(9szHj!y-%x$mKoe7=`tXf0UlZ+?1ToRBhcE3E2#rg-%S?`0e=< zjuiv8f4GP+Xt_x`z!Nd@2hV$W2F-nVo^t;8OnV%cJIndb+X@zhfJey59Kjl@s{hXP z-G_IlFaPWN-#y-$E3!XSj-mcyC|0910|gS1_9CfKC_8wtpd!SC?`m)r4RR%7~O|S~gX;&CQAq zVBjlCq&dPPas^X+rxzY3M0#uTUD5|sf0WAY0ae4$I@8<~FMYNl@N7VeSRoyXCg-R7 z@6X~u*@iE}jr9~#e;F2| z%EG~9R#|r>|L}bQGHbz8j4o9}RtqDOmDL_>z~e*L3X{KsOc-jh7f!IfnRS5hki4PB z&Vo#q5Rvpe+6}jHxUvVw$Yb_pv^vGD_LHEe{U7p17I5^>OCejh~;@hl56Zszf8;e3vJ9mB!|dm zA7Mf}w8}U#x=dydAV#yU7M4-mRDee^+*JM%HyGfNEHG8P&|5qj6XRPO{lReNvjwkN zz5$tuWV-XUOYD?>?ZBxV5!-6i88$Z3O*->?(9jT9dT3-s)9Z9Xz>?1}evR47@Lp`I z(#GzCS2qWaZ~TGd>n9j9f3`Ei|Mm^HS8+W`X+5pN@?Jc>@OYHq@FD(&0>ignz387u z`xN2X>{o>6(Y{4^Hu)DhPCOr@kNS|W(dTTrz%q@#@Chbz2wtPXF~~N;wt>MC1`7m6 zstZfXT$+&9>kdSj=(eYIU1hA`-B#a*nUW~mz> zPdH95EGLaH*o~DufUKE55z>jQn~3-O=m) z^F#9S?C_Ml-9MAA$MX|%{`QcZy!z$v;G7u#E~EcJ?)*SZdp}A4O?~?^j>RAF21md7 zFp7C`rvyCKnA~K>S$b79vlzzI=9A#U_=|Af05jWSTLnolf5S;W8-qaQK6r}1u1MsOxHN=7pDul<_ zl57oMU~5p}-_Vv6+w^!Q2)=ap?|4XKHm9qyzFo3*qX`%|2v8=9Bh&8=CTZ+9Ej zq)Xk*5pKH+fA>_zOCzsW9Mqb9`jW%8x~>UZbb&05VfmWI1|UL!jcJ;qk~I5`BtUYE z21ahA-3;JCyRQu^n;4(22!dxK0$X1p^*@5 z-r3=i+5tE|w$?#lt>*E>B-_>xz-Jt6C3;BN$rJd>@-iP1E;C%*qvABhKV4m&#>lzMtrVEJqFS_R~X+_cq6f$rW zYf1#zTfT@T0KH|$zSg6>uKc*K4^yO&D{$6bjX!PnvuL%z31hytheDyR$5E_EV zSvHw14VQrM43UClfW+%r2pQ(=hEc_*B9At4M+!mkMvCGgi-AN?m*% zG-+gq6CZRRt;NDcfLDQ|*0WUYYM$gZ`Bp}ge@8a(xg^tuOW0n~h6E5M8WrB^kZ{}~ zlFbIohd0)6?ZvC%9&|8BK}Roe_G)S{XhA?EU_vH79>vxGpw6r)UMImtl=v84G1+M- zq9{*3iSL4Y`4Q#=BcI{HVtxHe*e()~iLl(6@?faIpsm&m9qlb9BzI5Mu@Ug4 zJhBf(va-ju$#Cf=Vr(_b0(;qkpxyHuDmJ5MdBv;--TW@u2fhWFIjU;DcR9Ac${1o^ zzv$?)o$&MsLUVABus7H&IAu)q?-FhXe;N8d;VuGyr)5_$zKmM#;6e;&R(c4qXI?+h zt~z4MXf`Bpj3{hd_E?s@p4`|+XllD$5^4t=9DZ9cyloo;`GAN9L?BnWGSTge6`o*d zWQ>TY~ljhpip!VAC4tgb>q{0(-hx0f317R zXbesZVUjmZerwgyszk#a)*}%e5gQ!4F+YVpTZjMpCS;B=S) zqdxqa!)i^F%SopXCeH-Vf0Z@UX>5Oxz2~N-ww+Ytif!fCCQ&C8oMECH3EowqoB@`2 zHYRd-`l^FLkKXXqo}<^~=!_iizu*7)@HKh$H}dZ2{P5keEk{+b!Bc1Hg`4#SM+^m? z;4n2`tdbk|EN92|d>laguugf$XL=4nwuI^*VgZ^Cy64L`$ajcEf7@B83!U%Ud)BWt z5IMDD;xOQ$3u(asM7>K8IUg?M-X@$2xt5!=`7 zJBdw~%FKqvX2V|WMmTuf!W`0;Px%-wqCsM#{P{SE^fNKJQ3w*fTuHy%X z7XP60lpOLDKpaF1e_4&rmt^`G`4Ekx;Q;GKJRRS;v-$op?_e`2u5>hUZ1QqFJvqTUdv z?Np)N2K+&&3LU{bjULYYWSDvsl?LC$B}?^RjK^&@-x|}W#wdh`LK^%XOjGyS7eIx# z)FOM8JrYf29E7$5ja6N+P;_DI+ziHVM+mnOzB**XHHnB_{!;O98oang+{n_X+f$#A z?Y6j#&y$jVe}-gu@>>doqw0SkOthFSX?Ii49gVU{0+_RZFJdx0T=xvVoO5d}GgNbJ zK63x(!*dKH#+47;E=%5=o*d(=gVRHZJH{`khyVNW@a)`1oJc+NE|MMv5G6PC#?Io8 zhi2&ZE~GO&;~SK^xSgY8O9OFYnIA`fj()7gVN;gBXO(z7<;n&gW(lO>{YIkDV7w*G&n{W>>%eiLa4I zPQ_Hbe;$w1Ipu$)4fZ@rriMA~-0;C!_Vn=V!^!(I860n2B`2*1@4Pcx56q|G0XazK z;*|5Y{YhSt6`Tw5_}R;lp#g_}PF=RupX@4Ew;gT0()iXF|G+F>S^2?bMu@$iChHX( zrAg<`=;+rKoFWi{RDTMxS5St#H}G83iZ;bee=Q<1|G<3}o5N3}3Dm$ZS`4xud*190 zRP$K2ci#Ve`2O(p=pd6UH&h0|qoaKSU_^`*PZ@4Gy#hVysb)2$=AHevXA-(pRAKL& zV)Cb;m_@)ig=DQw0{GSb`N7+v*w>hs&BqbE5v8(;O(H9ZBU?mf{Sy@8E#mz?R_#^{2^($DzYvBY{Txw4O!yZ#2E^{-1ni z^=DN37d83Rw_Ss2%gDkEAWS?lKv!r=#uwjT%2Iz|zg}M25Dm$kemc1NAo9Yn>$c-@ z*zFqQAA>>ITOBz%QEH1>+V76#ji~49f6OQx*M46*4GDQRVrGIXGy&?B2>_&l0c=%_Az@B({LaepvV66kDr*nOGMT>(M-tI55T< zAQZfeGUvgqLT$I*x+$)U%5aoA2s3x?1S5GG2c)1wvhjw%Wutxb0EA(gYVxb)e`HXd zO{3{@;a!nfx~XW|(NE^E{0{bjHyXKtDwtF56;aI?(2Ct~5C}yaPf13=+iEBQyvNJ{ z4=}6TG}1#nQ`V!^rlPoH(0|7AU_)<%FXasnK$U#ft#fe>xbO+ei%~B?AHEVUf``v7~IWSmJFx_OXibs)6lFyK4W@ zX0vtMG36_E@DWUda8hG_hZif5D)*^`Yb2 zBX?kP)A4;uyrs^e%0zTD2YZK2?yqR;ey8-K?u ze3R4s94GmmUd+YF6mM%QFS_|Sd7oe|7q#47?DF=LkNyCuHk=#bUqc_=nvl=tEeiQO zIc=Pq6Q&{atOsc6?Gv|2e}zjvh+eV8I?Q$KTUxv+DteBYjla}a|87eR$lJ6Is(Az) zPD{NI$+Co%{Wo8In8nS-ha|kU_Hc0c$W=(M`!|GgZO(>Vsngi7kih>h3&U7_yetH^ z*kyd-ZjC6c{5JA%A82~8ZrZx}z^1Vy3eA{hcp$D9w$ose!*JS(f4Iv`uT3f%ytAmc zS@Skt!(ek~*-hT&9W%-_^!y&Hu^WJ(_=g{p9*Jm&jPtS#^Q0T2E-ygSTVlbBeyolI z3-R!(PluEWss22CD7R8f{IqISn0$e3!A(dvT6x21W~wnTnrpbUq^M@)dajpwo2h9u zETXhUUDgMkhZUuKe}ufmc)uaWX-G(^Vm>`kDd4uh8M9!M36&U6c`U9gCbxnegp+N9 z*N6q;Y5Q**3$|)a^l-k4e8NkMZ$@Ved-76F4+%q%>TY)q`4-$FHQd_~r-1Kf@5|$` z&;6O0F@wsTvLSqgpJ4xpIBU6P+Z)zYkf3h1*|2y6->w(%I>gyj= z_QqIct5eG48cko?ZS38+5dW~J8bi5;2ZlSICi<1lWMzN@_raEvIhp{{c~FM?I*kvO z^H+Zr58*#L;D346r!`_b50i7=@;=f~gSe|b|na2=$Fae|UjE(07XC#O1| zY79$~yL*S#-N<+@X`H)mWVX!I8XrW&8{LLu-%}lB#{ME;Agsz9uEhmH3h_sdUJsJ| z16+u{q>8x&H3wN_*KR*%RpOV3hQYJHZ0(|c2^-%~BU(+gANa>X(esWPQZ-(@aPO=d zde11of3ra)e6<;^=Bt{`^EO@Gnr68?XUyL}#^KNL0;L!NEH(RxarWgWL~!6MWJ6x+ zsVVPCxq=|JVh%T?NM*bZrJz0C0gWncX*b9 zozi8^<`u)~qJ->B)*)ua;ru}(QE8O7{wVlSlu~K}$e%>k5iu@il5VuIE%#J9Q9x?LjYGYImwozcIiEy+zL7b3jPxP| zBr0YtWcbUK!6v5w@ae72pyf5ttg?L+4i4=$oR5}r33cX_H+u5UqyNY}$thQLHCx?oc=X@!$} zRj*

WKsIHntNAY;;OFp5I{-eSi=TMqxjNO`KI!9m}>r*|@vALvSa!WaCb7*93xF za2gA)K{f<;cXtl~f?IG2?(Xt(?#sL9^-uMjwMMP#{^=T{s#l6%aeCp`wr$;FcVG2$ zq%BjPPVtc3;DWq#CeCv!#P6mjX{?QzcBF9XS_(Y+Ht$>xiZJ6o$ zEbOPVg~vovKBX0LIB((Gnytz;4NCN`yfFcOuRei@Odh+YpPFb$vr5|FNDIsp@xzw- zI3jUsxnLO(f&&#Qxp9ATsj#<#5d-Uqe0`PSVHfvg^I_wQy2O|y3bv0xe34EM?w8Sj^g zLRGG>t4y54)Rl{*LLyEoGi&qpEwce%oNW}Aljwurg?IeR<50hNe4+j-6R^&`S* zNpR_{!d>ziLEPE!B!+U+Qfi#N7mG+An^yu? zg58^QWnLc2Z3&b0Dt~blI(MGY@S$oEc1Hu_XY#ip)=nL4y+?*Mw8*E{BN9KNjEGL+ zK{=xs!fyRKys@-{#Ha89o)gRn>sSveZ;im}vC2D~Y9-DsU)`F1J&$+CyGWa(COE2H zK;ItSp8VWcc@2Q1Efa0Slw%T(%LucHuWgNfZ?I3&3sUkKrZwq6d_Y>?K-?`tFU|wn zT5wjNqz%7|Op;@Nw8e=`1vmi^i zH|UvH?Bnl!$j`EdBgT?6C-78zSep4%x=COFZ@c4UKCacASRaj!5dILZUMMH%CiVPj+-jrvDeyHt-C^X1;ivdG|NYcC0+vPBx)*F)dLukwdFL zM)*MI zO?->A3@q|byx5a%Kl|JsMsTs@qUb(#k`MYPxPG|E)lml-uk0!D*FAxcS(Uco&dx8H zMkBs!EHGwdei}M@OG$>b>%$wsD{801g#i1Rh7WF=OTv~n4_1cv>io)T!nJlVa`)Xg zFOc`mE(NOoXh+^WJXlK?gJ{GlM%@-|2-t`j4jwYdlL>%oTXE;JaO= zh4vH8E{{TznW^R*n5Ze^pJA>-p~IqST$5?Z1KF0Ld4-J=DLlh!K7U%T{_rt@nY@n> zF6J~u_EYO4Ma@U^fMf0<+~9B>d0B=f)w=h~YCZc_>@b@P?=Of>pPKxHK_k>~$$?RA=^P4wCX_0>4hL?EvvVe@4RR7sb_ z=Rc&#LmZz9`6Xdv@kmd(Z@o1xbOuCptzO2)+OGq6UrymXUq?=}ma{8ED%la*Si{~Vik3p6t)kGez;65pc;lCIO{liI>}`)Z-N>7{go z!;-gwfniQ4d5H{W44^UE3=;m(LP7h>sLfpLIU_XYK3^76HNLcXGiG1diLr-Bv}cfhLp_I8NQV z@tIYA5g#SyNuW5Br*d~6Jn|Gm;mCzZ2jT$KIS;v+FTbmw&i8p;yX(N;+%MsAokm%r zEG(lLncluPQev5=OIcUrN`W>RbH|Jdd=>Tq9vJs>d(iTzkVDEVjl^|X26}NmnaZH) z9N)Ad1cirT4ec?E=Y>0GqF~+X5SnOSq)eXS_`{di@I_*c%zviIJ-O;mPr`c) zMaKsdT=z*K%$v*#LV1F%&EOebNtLhB2VMoVp9cKPNrVBK*l3;?+g>a@>69}G>{#b1 zZe7F;!-jYQf$i)#E?C!s(-maQ6VFf}Xzk25enx7iqh@{8jFD~VKz7iBMJn0lNUxk_ zzck5AQi?A33i+d@gXV=R^bHyc*V<(V!IqhzY`f1`V@jwM33hrpfkH!LOB)JBNS^iD zL2%IkR7RChAXrT1q~icjdkvr3rkLk^Pw(x>uyrdI9X5S@=I7n#99dseWOM?6Wm)X= zOcx^-&_jk?b;@1!uyLI;mZx2bbW8Si=wP@iXTP;BMd~qTPZ^A)9AGeJ<~2V?(iIu^ z=GbwQWKs;>n&7(Q5U%I4DoY#aq>RDg8!gf7iTR+TBPC0tU?;}9UW;w#I#?B?p4;5Bk~0FJAKYr2t_e>tSddEu$dNwEyT!N=9x~PVKA>C$MmkBnqV06bWw`k z>{^k2!mfw5JHs~NR8uon)uS}r_t?3(j9S18i)27qQ9IiWr@*~lIeY+`GP4+;?ta-| zyu+8zAhaJs<4s8&>mUNzz}EQ04`KO1KYC1K!JfCExe>x$UwOm6EJQtnncRsZF$bd5 zo^;5lOWBzU#IFSGyqv};so$%JtAD>jAN=8v60h0WWO(s~j4=UWd$KE*f%V|{D3`7& zog}7I+d0+M%`<8l`i>S@_U8DtamSyWU%6?v%;wwE$yY2lF6uMkBe`EWE5)vHYF5<^ z9ps1@$&>Y4#whNta8>QjJ=v_Slz6kog9OWq>F#8uNQ)_x^3{Vo!Ba&XVAxWFult!VcVa4$8f{IP(Nzk#u9KQX9@1uFvAq3# z5tpyD?w6UY!rd!)&qUcT;-eVLN}V%lL{dy&T5sWOF+30-F(^nca>Iq=fu-XpMEVj! zq?WF!wx>0DR^0Z~bhBBt|7qXsG{QITn25HWqF9&JLwzoFEDEw@=MfiLO|*m67Oy{W zC#=%TG6Y{$9P|V5{$iY>P!>D{vtC(`EJUvZJ~!ja#V)KawQ%%~qQR?R)~;}eeL-`u z@Kt@C3DoE@``u*uwM{`;qenzvLt?|0r-MNqg;6eZk>Ea=+rIT08uO(^9r5tP;rS}0 z#HNtpBquppUi&i7!5nb*x1a$=#uuoL4w#T@tIV*)S$ljZ zCJehl9^`%L@U>um1q^W(y~xNkAw^8%a>%o^`mbGZP3q*`wavhyF-k$6#LkY>P!<6As)bOLgNQ|G)HR*pf{Tzbl-wq*W<`u1i602xKmzi_Dv-pkMGU;KMeMrj zH(9+Bp;}*c8bkJt-3tL7EYIhunP;1fluvz7-2Q7#Oo5-=4^E6Lz&4Yc^(W@F2%+bT zyM0M-E54)mb%8{#MzpD98ESq-sjpe6njSRVPjCSB7y~){i};=7Wr8-6>8!M3%N~2i ziWD!l@7nLzst%u+g&1ot7tfJtqnwliY^mdj5-JYlZ!tZ#Z)90Nt0O#;>me+V^)RCPxfrMZLWs8yOG^^1rZ`2251A#8gCvFsdr6ea ze;`|8FvuYD{F(R(HUVbNSYrw)zy@UxS{jBR>It7>V4x7cwW>S?)mii)cB)tZWW9cF zQVQeRC`~|P3IS<&9>e2^BS|2_xyHOkzyx5=%2u%o>R+V5?qKhUvNa%{*kgM^6gg#L_2 z_aLGKuaV6yZe$GL#${kAHAK8#V?9?m9{TOFN9cG_!s9V zF}4`mPEpAjcKC zWb-l^N5)2eWdFe64$me{OJ4UlS)7;eH#@Etr_j3!6h;Z&4OnxMLcwp$r{$(zcu$2I z4%pONRC6Z#qt+$E;)i56(W|1-t%4M5v}U@w)S!TEB*Crq7t@Re^S;6frKvOCc6qz^ zeGv1;Qm5BDE#?+W)3upC5)=!-MGSkzT2~h-RY}#Z^6BOYp{2D{7JrbZP;fsO$|J?T zApAj?Zt8q6dE$L#J74VcXn%0XX@l&%4WaaiS0h^i!cIk;z>OnA;vn@{(GmSIpO|k{ zrU*Oo!9_I8%v83ST}ulrS~ue;^InGr*dI9%Um-#yXZQ%ZRiZv(30oYl9|K@hEj0ZhYHOX=2hN*OF zTftMjau6GFZbsGTQBSqg9h(cRr9#zo&2YE-?6m5sU+F6qP_Bi1*lD@f7suYYY!rn6 z+y7gG2mx+KPZnPS9&rq)`xcnS^xTfQ``ab+T95}c5sZrP^@J$m)-Vxu!bsp`S5t75 z7CBx*aG8tOYOBvB`0D;Q!Kx0N^`4vOxn*RvrLK8Vw$WvS%yLm$KX(cG!)&@B*0g56 zk+J>BB75JpE`lh=3w0TFmA4({!o8E&rHL=LR8i&~HJ5VJWv4KZNMBkey1#C4wMV*C z+d_68{oIE3HnunGDXVNNJRCK@sy1D!r%64uni1P{l@T=S^yptcy0xIx}WBVz>}-OTJ-H z8}A(fQ^>&0rzJXF%sCpW==F2fS$+F;BkcPH6`a~_N68()UhxBia6Yoy?SA>8yEAR-*-a%&!f_qqdtvf}0mi=h=p{og;hkxJg;JYo@7w20` zXafXPRDC2(xmuANd22{-ulI9|xG2H8CJ(Oe6CF&nxz~^oHBoS)OM?*aHeIA_ld9>U zOil*mT^>ImMWbU-hxh6bg<7_^@p*DCy|Z+^;V4F)x>|AwYTqz&+!iTazlC$8caHpV zbH^p5(GApZJ`&{+cCNJZL0{Zw8-~UZ6F1kFrU@0B{+qa+GWR>PG+Gxu^kpM0>NK76T8>VZ`xUDACCjI z@qU!=Ms@8bjUyw+n-}m#F{UOacufcB^ro6gLWZ^P!SMWEFHh%L=Din@86WV!RC{e4 z)78WPye!+jo~=nEg;Ct;?RkjaJ&zNP_Q~v``kUi@+55R~kwb35qm-zn8A-kw!?@s0 ze|HG^7Dw=ID>_+QgO}Ue0UuhUL$U}zHmS54!jYp2r0pPoDJv!OowzT7qh1Sz9lGX( zL2%Q)&_cr&HHEE}S<$1x4$Y}j6>h*A?d0QH7va`V4o7rH?OxBU+<2@eXbxoV_>yq z-F%!hl-errZ7dT3vPIat#%~#s^l!p+zhhI%AuP?bQWnB%#*#fm5A5YJJtf#z-xnownS@lJ?Vnr2RlTd z3}PxJ*i0+TKYqqZ4*7Lj?I2q`8kO^?-o&`*T-u(Wkpp*YBVssowlQ2%Ybmt{RP}Te zpXDo7?k<^@EllioxMY1$x)de9Kd9DbCV|ew1qskra1I{Aim04C(k91ji!#`9rP*hm zmlI@Z{}ioAQ|jqJ=cKt-w_7Dxs6+PHl}H)3&y^?Ct5gs-y@Ba+VMTS1YHp}r-^E=a zSemaQ-q0}?xvTzgOan!q(PwH2q?~l-6^XYKN)jD!Xjs)e8*+TcyojcW^KIC2@Xh2^ zR9qkSK$;R-_&u*o=S`dZj1C{?#le9?)MT8hQP>P1VEVhQ#30SW{#~ zNjtNyR-H+iU&4a=Dn85$&@PS^Yl}G5RO$b)bJPYVxz+iZl7NOum$R~?FwwL=cgusd zS1lQN;LY%BV|_PcW;*z|c7ECuDH$orV|p4U5uM>@XwuH-)*wy#AyMQxVf{noVVB9v zWdvnk@`%1Eu+vJCL)Mz;_c=E(MZO=9V%@D7Tr&1KIZ>It*@Zj-XclbHL8+Pb)DIlb z0xrI9x@8hI1z8c{2a&8wB-oF>)$HpuQ+Aas($+bLk};l4I~b6Bspv=?DC^CNp+8}V z0d$FMtoD`@+06-8Xzja9pE|c;#?+1J^XkH}_DBu}69I8zR{zl(g2 zc$bV7#>OCEnoa&F;L-W<;&V-wK8HT9XQ$%SD|Xbg>gx@$(k=I#QU}lRpQUgok}~O< zQETs4mx(T}rufs0O$h$-Z^m#drth-F@AaTbJHd!scL?qJm$c|9 z1r(jxQ#n??IrbPe0f}d|yk$z^VflPoKGHU?IUKPta+gHify(t;!No3R zA+7xP5K|usA?W@{{CFKnUE_3B?Lh0)k3-ezm;+)s8Yup zjsAv*0Zp-)EU;5dX!WLMr2zgs;u0@Cr3vTH$p?uz#&R zUjKP9L~C`qhBbil%0Y{vc%-({59NTrtUlRPV7t%fVBCv_CyrNaYfrK=(u-6TnF?cU z9gWw^PSZNHV|LHM(XV;zx})d;$xqYlI8>EOHpIk&|=@_i9dEsiRmj`i%znKuxo)i1hk zoO(jONPiK#m1)f6MP7N!>=)>ifsY5sS2+2yj0B~e*;Kn~l&rsRLEh8Kt&sZsaX&EO z od7yM;6%-|N^R|L8C=&!$Q+ANI}{Q4geJ+dQ_w7(SpEU(0%wsZItAM$Z^w~67= z?B<2vU+nV|bCn~I-!IgN-K^IYb!)Fmsuguftc9vl;a@vA$xDHdnHM9YD1Yk>XfE|k zJeEAJ5uW|*b{A_W$4Vx3>2O7Wak^B7`>IqTH82|%I_S)DQ>jCbZAK2onO^@<`ek(U z<<2GHCe`|BQ00Tu=*b3)y4XH7+u6jcrdoY=%=vJ#D%`W z`E9t*G#lxTu^d9Pnd*qREsz%4i)$hX-VjLNFKB5&0IhXRUf9Y#yuP0X+E3bMf3I5m zhAqAGPo}Kdq27H=9aRsPuF-5wLyET9v%qW`#CJbszNpH6t!JBOxR;EjS8%&Kw&yxF z9k=n13NHe)hpZBL;lld<(q~Foia(7N>4f&fvh_?scL72M1qYjw2cw@jP3bqAjj9Ub z6!#LLl{F%qXKg2=I%zNgs3(joij)VHlxvtcO{eF`@47q+`d2_~%v<_(82fLtrqLL@ zaPQ-dWct0Lmf*tD^vTkWh?oy%RF0^ey+WW%ES~Z7?^07iV z-sI!ZloU^@4wNp9ntuLj2kRiv@jB=IXKi&6$;7$5(cfXAHcQRI?q+j6WWg$|d0D{e z!7QGh8C1PIj#ED3;>HYB;k?pwwscty3~&ob^DnHN0523xNFJbkHspUxyhEA_K>+mr zc7kaJ%YW*;+ZTcOpgE8J%gaR|0chCMzf1x#Dh4q@w_N;Xl!U4?RLJN*4(|Go!-Jrg zfaF0H5Uf%VKE$B}qyqin{y)B27zCdnev1M@t^{F1Zc0GJ&`bZ@BVGnVgs_)_Bw=iz zK#-n4J&>4EkRXf==r3)Tg1|65@P7#%7K97YD+4jXtRenYE$|>BNb5hU4f(G!DF>lJ z(91z2WdAJ)L<$8Z0QK*c9{@)AH&`BK75iVpfh?ATgkW$8|EhVsKRKl6(Dh#WAVRqPyv#M>5}-D@E{o?e;(1l^3QbHrT)K1lK_!G$ZP(* zo-|1iCffgvo*(ci{Wnas3M2qCDf^cat3cc^hjM>us|v&g)35NCqGkT%P^$iu1Ed8? zaN@#+7}tQ{A;W4Q8c5SWT1@x98Wn=B`6v2MW+oV9{r_qy=pXvYwIF)P&l(UHw4u?T zdrjzve;Rtom>viR5?2etB>m4`7$FqYlNb~f)BjERPn!=aG5=5KW-SOG-pL2_$Ndj^ CK`&eY delta 23511 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 IfCrEN2dsIPi2wiq diff --git a/enterprise/dist/litellm_enterprise-0.1.29.tar.gz b/enterprise/dist/litellm_enterprise-0.1.29.tar.gz index 6781cf26cc9e9feab8ae6a0bacbb31fa52501dd0..1ea224f8f340cd67950787c29ca2591707495089 100644 GIT binary patch delta 49442 zcmX7v1zS~J*M&(DknS$&mJR^{LAqPIk(AC&cPZT|je>NSG)Q-Mcf&bne?IT`5BA#E z8e`sLUSkbH5q^auCJMsWeiIS}rs>N#Q{gP6Eo7`#-@Q{`ATKAKHJ*_Z0Zvx*Ps2~U2dw3jnUE;w!t$n|&Ky%)2ESGCG!U{EEzeDp1&dLb$ z!O>C3u;D}Gb*gTvhgacjhg|-lehl&DML6QcO>d0GJ+1K&MMlWCAMU6ira5_c zS!QO$m_@EM;!lNelXyj1fA_?`D67X9U zTy9*N){Ll6X&E9uU5)j;dm~(3=Pg)HrVG&^0TDai%iRY=o;uqNB zG^@W;BjK`d6!3fwQE%!nY~ASCm|c=*cjy5Wi)?#8*~v7`B8;F%1Gdb?Sk5UJ>> z4K1^!7OV7=Eb0hR^x~h;H0^i~92D)NOsqfe;8@PE-caGjceNtEnO!+g

ncGmQo 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 delta 48352 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 K4gO95Xa)d1`I`s; diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py index 61e0745bab1..1e87e5594fc 100644 --- a/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py +++ b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py @@ -30,8 +30,15 @@ from litellm.integrations.email_templates.user_invitation_email import ( from litellm.integrations.email_templates.templates import ( MAX_BUDGET_ALERT_EMAIL_TEMPLATE, SOFT_BUDGET_ALERT_EMAIL_TEMPLATE, + TEAM_SOFT_BUDGET_ALERT_EMAIL_TEMPLATE, +) +from litellm.proxy._types import ( + CallInfo, + InvitationNew, + Litellm_EntityType, + UserAPIKeyAuth, + WebhookEvent, ) -from litellm.proxy._types import CallInfo, InvitationNew, UserAPIKeyAuth, WebhookEvent from litellm.secret_managers.main import get_secret_bool from litellm.types.integrations.slack_alerting import LITELLM_LOGO_URL from litellm.constants import ( @@ -217,6 +224,68 @@ class BaseEmailLogger(CustomLogger): ) pass + async def send_team_soft_budget_alert_email(self, event: WebhookEvent): + """ + Send email to team members when team soft budget is crossed + Supports multiple recipients via alert_emails field from team metadata + """ + # Collect all recipient emails + recipient_emails: List[str] = [] + + # Add additional alert emails from team metadata.soft_budget_alert_emails + if hasattr(event, "alert_emails") and event.alert_emails: + for email in event.alert_emails: + if email and email not in recipient_emails: # Avoid duplicates + recipient_emails.append(email) + + # If no recipients found, skip sending + if not recipient_emails: + verbose_proxy_logger.warning( + f"No recipient emails found for team soft budget alert. event={event.model_dump(exclude_none=True)}" + ) + return + + verbose_proxy_logger.debug( + f"send_team_soft_budget_alert_email_event: {json.dumps(event.model_dump(exclude_none=True), indent=4, default=str)}" + ) + + # Get email params using the first recipient email (for template formatting) + email_params = await self._get_email_params( + email_event=EmailEvent.soft_budget_crossed, + user_id=event.user_id, + user_email=recipient_emails[0], + event_message=event.event_message, + ) + + # Format budget values + soft_budget_str = f"${event.soft_budget}" if event.soft_budget is not None else "N/A" + spend_str = f"${event.spend}" if event.spend is not None else "$0.00" + max_budget_info = "" + if event.max_budget is not None: + max_budget_info = f"Maximum Budget: ${event.max_budget}
" + + # Use team alias or generic greeting + team_alias = event.team_alias or "Team" + + email_html_content = TEAM_SOFT_BUDGET_ALERT_EMAIL_TEMPLATE.format( + email_logo_url=email_params.logo_url, + team_alias=team_alias, + soft_budget=soft_budget_str, + spend=spend_str, + max_budget_info=max_budget_info, + base_url=email_params.base_url, + email_support_contact=email_params.support_contact, + ) + + # Send email to all recipients + await self.send_email( + from_email=self.DEFAULT_LITELLM_EMAIL, + to_email=recipient_emails, + subject=email_params.subject, + html_body=email_html_content, + ) + pass + async def send_max_budget_alert_email(self, event: WebhookEvent): """ Send email to user when max budget alert threshold is reached @@ -285,15 +354,29 @@ class BaseEmailLogger(CustomLogger): # - Don't re-alert, if alert already sent _cache: DualCache = self.internal_usage_cache - # percent of max_budget left to spend - if user_info.max_budget is None and user_info.soft_budget is None: - return - # For soft_budget alerts, check if we've already sent an alert if type == "soft_budget": + # For team soft budget alerts, we only need team soft_budget to be set + # For other entity types, we need either max_budget or soft_budget + if user_info.event_group == Litellm_EntityType.TEAM: + if user_info.soft_budget is None: + return + else: + # For non-team alerts, require either max_budget or soft_budget + if user_info.max_budget is None and user_info.soft_budget is None: + return if user_info.soft_budget is not None and user_info.spend >= user_info.soft_budget: # Generate cache key based on event type and identifier - _id = user_info.token or user_info.user_id or "default_id" + # Use appropriate ID based on event_group to ensure unique cache keys per entity type + if user_info.event_group == Litellm_EntityType.TEAM: + _id = user_info.team_id or "default_id" + elif user_info.event_group == Litellm_EntityType.ORGANIZATION: + _id = user_info.organization_id or "default_id" + elif user_info.event_group == Litellm_EntityType.USER: + _id = user_info.user_id or "default_id" + else: + # For KEY and other types, use token or user_id + _id = user_info.token or user_info.user_id or "default_id" _cache_key = f"email_budget_alerts:soft_budget_crossed:{_id}" # Check if we've already sent this alert @@ -318,10 +401,15 @@ class BaseEmailLogger(CustomLogger): projected_exceeded_date=user_info.projected_exceeded_date, projected_spend=user_info.projected_spend, event_group=user_info.event_group, + alert_emails=user_info.alert_emails, ) try: - await self.send_soft_budget_alert_email(webhook_event) + # Use team-specific function for team alerts, otherwise use standard function + if user_info.event_group == Litellm_EntityType.TEAM: + await self.send_team_soft_budget_alert_email(webhook_event) + else: + await self.send_soft_budget_alert_email(webhook_event) # Cache the alert to prevent duplicate sends await _cache.async_set_cache( diff --git a/litellm/integrations/email_templates/templates.py b/litellm/integrations/email_templates/templates.py index 5de23db0f24..091351df2bb 100644 --- a/litellm/integrations/email_templates/templates.py +++ b/litellm/integrations/email_templates/templates.py @@ -85,6 +85,30 @@ SOFT_BUDGET_ALERT_EMAIL_TEMPLATE = """ The LiteLLM team
""" +TEAM_SOFT_BUDGET_ALERT_EMAIL_TEMPLATE = """ + LiteLLM Logo + +

Hi {team_alias} team member,
+ + Your LiteLLM team has crossed its soft budget limit of {soft_budget}.

+ + Current Spend: {spend}
+ Soft Budget: {soft_budget}
+ {max_budget_info} + +

+ ⚠️ Note: Your API requests will continue to work, but you should monitor your usage closely. + If you reach your maximum budget, requests will be rejected. +

+ + You can view your usage and manage your budget in the
LiteLLM Dashboard.

+ + If you have any questions, please send an email to {email_support_contact}

+ + Best,
+ The LiteLLM team
+""" + MAX_BUDGET_ALERT_EMAIL_TEMPLATE = """ LiteLLM Logo diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 5b697249a41..f38f94f4c98 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2190,6 +2190,7 @@ class LiteLLM_VerificationTokenView(LiteLLM_VerificationToken): team_tpm_limit: Optional[int] = None team_rpm_limit: Optional[int] = None team_max_budget: Optional[float] = None + team_soft_budget: Optional[float] = None team_models: List = [] team_blocked: bool = False soft_budget: Optional[float] = None @@ -2648,6 +2649,10 @@ class CallInfo(LiteLLMPydanticObjectBase): projected_exceeded_date: Optional[str] = None projected_spend: Optional[float] = None event_group: Litellm_EntityType + alert_emails: Optional[List[str]] = Field( + default=None, + description="Additional email addresses to send alerts to (e.g., from team metadata)", + ) class WebhookEvent(CallInfo): diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 359bb944546..437ef0d438b 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -218,6 +218,13 @@ async def common_checks( valid_token=valid_token, ) + # 3.0.5. If team is over soft budget (alert only, doesn't block) + await _team_soft_budget_check( + team_object=team_object, + proxy_logging_obj=proxy_logging_obj, + valid_token=valid_token, + ) + # 3.1. If organization is in budget await _organization_max_budget_check( valid_token=valid_token, @@ -2421,6 +2428,66 @@ async def _team_max_budget_check( ) +async def _team_soft_budget_check( + team_object: Optional[LiteLLM_TeamTable], + valid_token: Optional[UserAPIKeyAuth], + proxy_logging_obj: ProxyLogging, +): + """ + Triggers a budget alert if the team is over it's soft budget. + """ + if ( + team_object is not None + and team_object.soft_budget is not None + and team_object.spend is not None + and team_object.spend >= team_object.soft_budget + ): + verbose_proxy_logger.debug( + "Crossed Soft Budget for team %s, spend %s, soft_budget %s", + team_object.team_id, + team_object.spend, + team_object.soft_budget, + ) + if valid_token: + # Extract alert emails from team metadata + alert_emails: Optional[List[str]] = None + if team_object.metadata is not None and isinstance(team_object.metadata, dict): + soft_budget_alert_emails = team_object.metadata.get("soft_budget_alerting_emails") + if soft_budget_alert_emails is not None: + if isinstance(soft_budget_alert_emails, list): + alert_emails = [email for email in soft_budget_alert_emails if isinstance(email, str) and email.strip()] + elif isinstance(soft_budget_alert_emails, str): + # Handle comma-separated string + alert_emails = [email.strip() for email in soft_budget_alert_emails.split(",") if email.strip()] + # Filter out empty strings + if alert_emails: + alert_emails = [email for email in alert_emails if email] + else: + alert_emails = None + + call_info = CallInfo( + token=valid_token.token, + spend=team_object.spend, + max_budget=team_object.max_budget, + soft_budget=team_object.soft_budget, + user_id=valid_token.user_id, + team_id=valid_token.team_id, + team_alias=valid_token.team_alias, + organization_id=valid_token.org_id, + user_email=None, # Team-level alert, no specific user email + key_alias=valid_token.key_alias, + event_group=Litellm_EntityType.TEAM, + alert_emails=alert_emails, + ) + + asyncio.create_task( + proxy_logging_obj.budget_alerts( + type="soft_budget", + user_info=call_info, + ) + ) + + async def _organization_max_budget_check( valid_token: Optional[UserAPIKeyAuth], team_object: Optional[LiteLLM_TeamTable], diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index a153c6e51cc..05eeab3f611 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -1149,6 +1149,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 _team_obj: Optional[LiteLLM_TeamTable] = LiteLLM_TeamTable( team_id=valid_token.team_id, max_budget=valid_token.team_max_budget, + soft_budget=valid_token.team_soft_budget, spend=valid_token.team_spend, tpm_limit=valid_token.team_tpm_limit, rpm_limit=valid_token.team_rpm_limit, diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 6bbf0df74de..00491675876 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1370,17 +1370,35 @@ class ProxyLogging: ], user_info: CallInfo, ): - if self.alerting is None: - # do nothing if alerting is not switched on + # For soft_budget alerts with alert_emails set, allow email sending even if alerting is None + # This enables team-specific soft budget email alerts via metadata.soft_budget_alerting_emails + # Note: user_info is a CallInfo that can represent user/team/org level info. For team budgets, + # alert_emails is populated from team_object.metadata.soft_budget_alerting_emails (see auth_checks.py) + is_soft_budget_with_alert_emails = ( + type == "soft_budget" + and user_info.alert_emails is not None + and len(user_info.alert_emails) > 0 + ) + + if self.alerting is None and not is_soft_budget_with_alert_emails: + # do nothing if alerting is not switched on (unless it's a soft_budget alert with team-specific emails) return - if "slack" in self.alerting: + if self.alerting is not None and "slack" in self.alerting: await self.slack_alerting_instance.budget_alerts( type=type, user_info=user_info, ) - if "email" in self.alerting and self.email_logging_instance is not None: + # Call email_logging_instance if: + # 1. "email" is in alerting config, OR + # 2. It's a soft_budget alert with team-specific alert_emails (bypasses global alerting config) + should_send_email = ( + (self.alerting is not None and "email" in self.alerting) + or is_soft_budget_with_alert_emails + ) + + if should_send_email and self.email_logging_instance is not None: await self.email_logging_instance.budget_alerts( type=type, user_info=user_info, @@ -2607,7 +2625,8 @@ class PrismaClient: SELECT v.*, t.spend AS team_spend, - t.max_budget AS team_max_budget, + t.max_budget AS team_max_budget, + t.soft_budget AS team_soft_budget, t.tpm_limit AS team_tpm_limit, t.rpm_limit AS team_rpm_limit, t.models AS team_models, From 828b13279ebcb7c242ae4f310cf291f53fb8cc29 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 20:44:43 -0800 Subject: [PATCH 026/212] =?UTF-8?q?bump:=20version=200.1.29=20=E2=86=92=20?= =?UTF-8?q?0.1.30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enterprise/pyproject.toml | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml index c5aaa0a3407..4cb838be036 100644 --- a/enterprise/pyproject.toml +++ b/enterprise/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-enterprise" -version = "0.1.29" +version = "0.1.30" description = "Package for LiteLLM Enterprise features" authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.1.29" +version = "0.1.30" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-enterprise==", diff --git a/requirements.txt b/requirements.txt index 1fb8a22cc90..f0e5059928b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,4 +73,4 @@ pypdf>=6.6.2 # for PDF text extraction in RAG ingestion ######################## # LITELLM ENTERPRISE DEPENDENCIES ######################## -litellm-enterprise==0.1.29 +litellm-enterprise==0.1.30 From 7f11fa0a05ade3d606c6672d1728fa36bf8da246 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 20:48:07 -0800 Subject: [PATCH 027/212] enterprise build --- .../dist/litellm_enterprise-0.1.29.tar.gz | Bin 49967 -> 0 bytes ...itellm_enterprise-0.1.30-py3-none-any.whl} | Bin 112486 -> 112487 bytes .../dist/litellm_enterprise-0.1.30.tar.gz | Bin 0 -> 49964 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 enterprise/dist/litellm_enterprise-0.1.29.tar.gz rename enterprise/dist/{litellm_enterprise-0.1.29-py3-none-any.whl => litellm_enterprise-0.1.30-py3-none-any.whl} (91%) create mode 100644 enterprise/dist/litellm_enterprise-0.1.30.tar.gz diff --git a/enterprise/dist/litellm_enterprise-0.1.29.tar.gz b/enterprise/dist/litellm_enterprise-0.1.29.tar.gz deleted file mode 100644 index 1ea224f8f340cd67950787c29ca2591707495089..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49967 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 diff --git a/enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.30-py3-none-any.whl similarity index 91% rename from enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl rename to enterprise/dist/litellm_enterprise-0.1.30-py3-none-any.whl index 5eedd1fe876e110a84047318cefd194078254fbb..0165bb096c03e47256e1dbfa11b01b1980590411 100644 GIT binary patch delta 6981 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

<919!*}Nh=QU?pJ}ji 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 Date: Thu, 5 Feb 2026 20:51:55 -0800 Subject: [PATCH 028/212] reverting .29 deletion --- .../litellm_enterprise-0.1.29-py3-none-any.whl | Bin 0 -> 111358 bytes .../dist/litellm_enterprise-0.1.29.tar.gz | Bin 0 -> 48839 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl create mode 100644 enterprise/dist/litellm_enterprise-0.1.29.tar.gz diff --git a/enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.29-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..0895ecbc4271ba78bed1da72468e19f87324b33a GIT binary patch literal 111358 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 literal 0 HcmV?d00001 diff --git a/enterprise/dist/litellm_enterprise-0.1.29.tar.gz b/enterprise/dist/litellm_enterprise-0.1.29.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6781cf26cc9e9feab8ae6a0bacbb31fa52501dd0 GIT binary patch literal 48839 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 Date: Thu, 5 Feb 2026 21:00:05 -0800 Subject: [PATCH 029/212] Adding tests + update pyproject --- poetry.lock | 62 +++++------- pyproject.toml | 2 +- tests/proxy_unit_tests/test_auth_checks.py | 80 ++++++++++++++++ tests/test_litellm/test_utils.py | 105 +++++++++++++++++++++ 4 files changed, 211 insertions(+), 38 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5e926509d54..9a7b9d9d5b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "a2a-sdk" @@ -398,7 +398,6 @@ files = [ {file = "azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b"}, {file = "azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7"}, ] -markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] requests = ">=2.21.0" @@ -419,7 +418,6 @@ files = [ {file = "azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651"}, {file = "azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456"}, ] -markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] azure-core = ">=1.31.0" @@ -720,7 +718,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] -markers = {main = "(platform_python_implementation != \"PyPy\" or extra == \"proxy\") and (python_version >= \"3.10\" or extra == \"proxy\" or extra == \"extra-proxy\") and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "platform_python_implementation != \"PyPy\"", proxy-dev = "platform_python_implementation != \"PyPy\""} +markers = {main = "platform_python_implementation != \"PyPy\" or extra == \"proxy\"", dev = "platform_python_implementation != \"PyPy\"", proxy-dev = "platform_python_implementation != \"PyPy\""} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -1151,6 +1149,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = ">=3.7" groups = ["main", "dev", "proxy-dev"] +markers = "python_version == \"3.9\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -1180,7 +1179,6 @@ files = [ {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] -markers = {main = "python_version == \"3.9\" and (extra == \"proxy\" or extra == \"extra-proxy\")", dev = "python_version == \"3.9\"", proxy-dev = "python_version == \"3.9\""} [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} @@ -1202,6 +1200,7 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" groups = ["main", "dev", "proxy-dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, @@ -1258,7 +1257,6 @@ files = [ {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, ] -markers = {main = "python_version >= \"3.10\" and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "python_version >= \"3.10\"", proxy-dev = "python_version >= \"3.10\""} [package.dependencies] cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} @@ -2277,11 +2275,11 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0.dev0" -proto-plus = ">=1.22.3,<2.0.0.dev0" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" +grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" [[package]] name = "google-cloud-resource-manager" @@ -3284,7 +3282,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -3448,28 +3446,28 @@ openai = ["openai (>=0.27.8)"] [[package]] name = "litellm-enterprise" -version = "0.1.27" +version = "0.1.30" description = "Package for LiteLLM Enterprise features" optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_enterprise-0.1.27-py3-none-any.whl", hash = "sha256:41b9d41d04123f492060a742091006dc1d182b54ce3a1c0e18ee75d623c63e91"}, - {file = "litellm_enterprise-0.1.27.tar.gz", hash = "sha256:aa40c87f7c8df64beb79e75f71e1b5c0a458350efa68527e3491e6f27f2cbd57"}, + {file = "litellm_enterprise-0.1.30-py3-none-any.whl", hash = "sha256:9715e0b99ae431fcc9621cc515b329b55d7dd291c611c3a0842e94869de219dc"}, + {file = "litellm_enterprise-0.1.30.tar.gz", hash = "sha256:5ffbf25720b75e59c157dca5edff5db61b85e85072c340b1aa02f04e3924fe3e"}, ] [[package]] name = "litellm-proxy-extras" -version = "0.4.30" +version = "0.4.31" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_proxy_extras-0.4.30-py3-none-any.whl", hash = "sha256:0b7df68f0968eb817462b847eaee81bba23d935adb2e84d2e342a77711887051"}, - {file = "litellm_proxy_extras-0.4.30.tar.gz", hash = "sha256:5d32f8dc3d37d36fb15ab6995fea706dd8a453ff7f12e70b47cba35e5368da10"}, + {file = "litellm_proxy_extras-0.4.31-py3-none-any.whl", hash = "sha256:f50bee4b21dbe0b1c47ed88dc90cf7a2024b221ff8f6da9343197a1df8ea3ecd"}, + {file = "litellm_proxy_extras-0.4.31.tar.gz", hash = "sha256:0b99a522ca31f2da835bb49037d787257e90269f90c2b5b81a4e8d575edd0778"}, ] [[package]] @@ -3948,7 +3946,6 @@ files = [ {file = "msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1"}, {file = "msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f"}, ] -markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] cryptography = ">=2.5,<49" @@ -3969,7 +3966,6 @@ files = [ {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, ] -markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] msal = ">=1.29,<2" @@ -4220,7 +4216,6 @@ files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -markers = {main = "extra == \"extra-proxy\""} [[package]] name = "numpy" @@ -4428,7 +4423,7 @@ files = [ {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, ] -markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} +markers = {main = "python_version >= \"3.10\""} [package.dependencies] importlib-metadata = ">=6.0,<8.8.0" @@ -4543,7 +4538,7 @@ files = [ {file = "opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c"}, {file = "opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6"}, ] -markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} +markers = {main = "python_version >= \"3.10\""} [package.dependencies] opentelemetry-api = "1.39.1" @@ -4561,7 +4556,7 @@ files = [ {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, ] -markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} +markers = {main = "python_version >= \"3.10\""} [package.dependencies] opentelemetry-api = "1.39.1" @@ -5038,7 +5033,6 @@ files = [ {file = "prisma-0.11.0-py3-none-any.whl", hash = "sha256:22bb869e59a2968b99f3483bb417717273ffbc569fd1e9ceed95e5614cbaf53a"}, {file = "prisma-0.11.0.tar.gz", hash = "sha256:3f2f2fd2361e1ec5ff655f2a04c7860c2f2a5bc4c91f78ca9c5c6349735bf693"}, ] -markers = {main = "extra == \"extra-proxy\""} [package.dependencies] click = ">=7.1.2" @@ -5355,7 +5349,7 @@ files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] -markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\") and (python_version >= \"3.10\" or extra == \"proxy\" or extra == \"extra-proxy\") and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} +markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\")", dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} [[package]] name = "pydantic" @@ -5578,7 +5572,6 @@ files = [ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] -markers = {main = "extra == \"extra-proxy\" or extra == \"proxy\""} [package.dependencies] cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} @@ -6680,10 +6673,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a0" +botocore = ">=1.37.4,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "scikit-learn" @@ -6916,9 +6909,9 @@ tornado = ">=6.4.2,<7" urllib3 = ">=1.26,<3" [package.extras] -all = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)", "cohere (>=5.9.4,<6.0)", "dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\"", "google-cloud-aiplatform (>=1.45.0,<2)", "ipykernel (>=6.25.0,<7)", "llama-cpp-python (>=0.2.28,<0.2.86) ; python_version < \"3.13\"", "mistralai (>=0.0.12,<0.1.0)", "mypy (>=1.7.1,<2)", "ollama (>=0.1.7)", "pillow (>=10.2.0,<11.0.0) ; python_version < \"3.13\"", "pinecone[asyncio] (>=7.0.0,<8.0.0)", "psycopg[binary] (>=3.1.0,<4)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "qdrant-client (>=1.11.1,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "sentence-transformers (>=5.0.0) ; python_version < \"3.13\"", "tokenizers (>=0.19) ; python_version < \"3.13\"", "torch (>=2.6.0) ; python_version < \"3.13\"", "torchvision (>=0.17.0) ; python_version < \"3.13\"", "transformers (>=4.36.2) ; python_version < \"3.13\"", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] +all = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)", "cohere (>=5.9.4,<6.00)", "dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\"", "google-cloud-aiplatform (>=1.45.0,<2)", "ipykernel (>=6.25.0,<7)", "llama-cpp-python (>=0.2.28,<0.2.86) ; python_version < \"3.13\"", "mistralai (>=0.0.12,<0.1.0)", "mypy (>=1.7.1,<2)", "ollama (>=0.1.7)", "pillow (>=10.2.0,<11.0.0) ; python_version < \"3.13\"", "pinecone[asyncio] (>=7.0.0,<8.0.0)", "psycopg[binary] (>=3.1.0,<4)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "qdrant-client (>=1.11.1,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "sentence-transformers (>=5.0.0) ; python_version < \"3.13\"", "tokenizers (>=0.19) ; python_version < \"3.13\"", "torch (>=2.6.0) ; python_version < \"3.13\"", "torchvision (>=0.17.0) ; python_version < \"3.13\"", "transformers (>=4.36.2) ; python_version < \"3.13\"", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] bedrock = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)"] -cohere = ["cohere (>=5.9.4,<6.0)"] +cohere = ["cohere (>=5.9.4,<6.00)"] dev = ["dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "ipykernel (>=6.25.0,<7)", "mypy (>=1.7.1,<2)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] docs = ["pydoc-markdown (>=4.8.2) ; python_version < \"3.12\""] fastembed = ["fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\""] @@ -7762,7 +7755,6 @@ files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, ] -markers = {main = "extra == \"extra-proxy\""} [[package]] name = "tornado" @@ -8531,8 +8523,4 @@ utils = ["numpydoc"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -<<<<<<< litellm_oss_staging_02_04_2026 -content-hash = "797603dcfef0a79781c7d3cba5dfe18f6aea4aa792220f47487ebc7bd04ae2e3" -======= -content-hash = "e5447e14dd37e324ac07a8fc6286d27e9a0d355ed93ebb24fc11e3f5df12fd3e" ->>>>>>> main +content-hash = "16433b6ceb3c56276021223166394591cfd5a18da40c2244799d56ee3167c366" diff --git a/pyproject.toml b/pyproject.toml index 768a41d2e8b..c4ff0aac209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"} a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} litellm-proxy-extras = {version = "0.4.31", optional = true} rich = {version = "13.7.1", optional = true} -litellm-enterprise = {version = "0.1.27", optional = true} +litellm-enterprise = {version = "0.1.30", optional = true} diskcache = {version = "^5.6.1", optional = true} polars = {version = "^1.31.0", optional = true, python = ">=3.10"} semantic-router = {version = ">=0.1.12", optional = true, python = ">=3.9,<3.14"} diff --git a/tests/proxy_unit_tests/test_auth_checks.py b/tests/proxy_unit_tests/test_auth_checks.py index 05c6e4984af..ee595092995 100644 --- a/tests/proxy_unit_tests/test_auth_checks.py +++ b/tests/proxy_unit_tests/test_auth_checks.py @@ -21,11 +21,13 @@ from litellm.proxy._types import ( LiteLLM_BudgetTable, LiteLLM_UserTable, LiteLLM_TeamTable, + Litellm_EntityType, ) from litellm.proxy.utils import PrismaClient from litellm.proxy.auth.auth_checks import ( can_team_access_model, _virtual_key_soft_budget_check, + _team_soft_budget_check, ) from litellm.proxy.utils import ProxyLogging from litellm.proxy.utils import CallInfo @@ -478,6 +480,84 @@ async def test_virtual_key_soft_budget_check(spend, soft_budget, expect_alert): ), f"Expected alert_triggered to be {expect_alert} for spend={spend}, soft_budget={soft_budget}" +@pytest.mark.parametrize( + "spend, soft_budget, expect_alert, metadata, expected_alert_emails", + [ + (100, 50, True, None, None), # Over soft budget, no metadata + (50, 50, True, None, None), # At soft budget, no metadata + (25, 50, False, None, None), # Under soft budget + (100, None, False, None, None), # No soft budget set + (100, 50, True, {"soft_budget_alerting_emails": ["team1@example.com", "team2@example.com"]}, ["team1@example.com", "team2@example.com"]), # Over soft budget with list of emails + (100, 50, True, {"soft_budget_alerting_emails": "team1@example.com,team2@example.com"}, ["team1@example.com", "team2@example.com"]), # Over soft budget with comma-separated emails + (100, 50, True, {"soft_budget_alerting_emails": ["team1@example.com", "", " ", "team2@example.com"]}, ["team1@example.com", "team2@example.com"]), # Over soft budget with empty strings filtered + ], +) +@pytest.mark.asyncio +async def test_team_soft_budget_check(spend, soft_budget, expect_alert, metadata, expected_alert_emails): + """ + Test cases for _team_soft_budget_check: + 1. Spend over soft budget - should trigger alert + 2. Spend at soft budget - should trigger alert + 3. Spend under soft budget - should not trigger alert + 4. No soft budget set - should not trigger alert + 5. Team with alert emails in metadata (list) - should include alert_emails in CallInfo + 6. Team with alert emails in metadata (comma-separated string) - should parse and include alert_emails + 7. Team with alert emails containing empty strings - should filter them out + """ + alert_triggered = False + captured_call_info = None + + class MockProxyLogging: + async def budget_alerts(self, type, user_info): + nonlocal alert_triggered, captured_call_info + alert_triggered = True + captured_call_info = user_info + assert type == "soft_budget" + assert isinstance(user_info, CallInfo) + + valid_token = UserAPIKeyAuth( + token="test-token", + user_id="test-user", + team_id="test-team", + team_alias="test-team-alias", + key_alias="test-key", + ) + + team_object = LiteLLM_TeamTable( + team_id="test-team", + spend=spend, + soft_budget=soft_budget, + max_budget=100.0, + metadata=metadata, + ) + + proxy_logging_obj = MockProxyLogging() + + await _team_soft_budget_check( + team_object=team_object, + valid_token=valid_token, + proxy_logging_obj=proxy_logging_obj, + ) + + await asyncio.sleep(0.1) # Allow time for the alert task to complete + + assert ( + alert_triggered == expect_alert + ), f"Expected alert_triggered to be {expect_alert} for spend={spend}, soft_budget={soft_budget}" + + if expect_alert: + assert captured_call_info is not None + assert captured_call_info.team_id == "test-team" + assert captured_call_info.spend == spend + assert captured_call_info.soft_budget == soft_budget + assert captured_call_info.event_group == Litellm_EntityType.TEAM + # Verify alert_emails if expected + if expected_alert_emails is not None: + assert captured_call_info.alert_emails == expected_alert_emails + else: + assert captured_call_info.alert_emails is None or captured_call_info.alert_emails == [] + + @pytest.mark.asyncio async def test_can_user_call_model(): from litellm.proxy.auth.auth_checks import can_user_call_model diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index c7803445eb4..ee23811d5ac 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -2870,6 +2870,111 @@ class TestProxyLoggingBudgetAlerts: type=alert_type, user_info=user_info ) + async def test_budget_alerts_soft_budget_with_alert_emails_bypasses_alerting_none(self): + """ + Test that soft_budget alerts with alert_emails bypass the alerting=None check + and send emails even when alerting is None. + + This tests the new logic that allows team-specific soft budget email alerts + via metadata.soft_budget_alerting_emails to work even when global alerting is disabled. + """ + from litellm.caching.caching import DualCache + from litellm.proxy.utils import ProxyLogging + from litellm.proxy._types import CallInfo, Litellm_EntityType + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + proxy_logging.alerting = None # Global alerting is disabled + proxy_logging.slack_alerting_instance = AsyncMock() + proxy_logging.email_logging_instance = AsyncMock() + + # Create CallInfo with alert_emails set (simulating team metadata extraction) + user_info = CallInfo( + token="test-token", + spend=100.0, + soft_budget=50.0, + user_id="test-user", + team_id="test-team", + team_alias="test-team-alias", + event_group=Litellm_EntityType.TEAM, + alert_emails=["team1@example.com", "team2@example.com"], + ) + + # Should send email even though alerting is None (because of alert_emails) + await proxy_logging.budget_alerts(type="soft_budget", user_info=user_info) + + # Verify slack was NOT called (alerting is None) + proxy_logging.slack_alerting_instance.budget_alerts.assert_not_called() + + # Verify email WAS called (bypasses alerting=None check) + proxy_logging.email_logging_instance.budget_alerts.assert_called_once_with( + type="soft_budget", user_info=user_info + ) + + async def test_budget_alerts_soft_budget_without_alert_emails_respects_alerting_none(self): + """ + Test that soft_budget alerts WITHOUT alert_emails still respect alerting=None + and do not send emails when alerting is None. + """ + from litellm.caching.caching import DualCache + from litellm.proxy.utils import ProxyLogging + from litellm.proxy._types import CallInfo, Litellm_EntityType + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + proxy_logging.alerting = None + proxy_logging.slack_alerting_instance = AsyncMock() + proxy_logging.email_logging_instance = AsyncMock() + + # Create CallInfo WITHOUT alert_emails + user_info = CallInfo( + token="test-token", + spend=100.0, + soft_budget=50.0, + user_id="test-user", + team_id="test-team", + team_alias="test-team-alias", + event_group=Litellm_EntityType.TEAM, + alert_emails=None, # No alert emails + ) + + # Should NOT send email (alerting is None and no alert_emails) + await proxy_logging.budget_alerts(type="soft_budget", user_info=user_info) + + # Verify no calls were made + proxy_logging.slack_alerting_instance.budget_alerts.assert_not_called() + proxy_logging.email_logging_instance.budget_alerts.assert_not_called() + + async def test_budget_alerts_soft_budget_with_empty_alert_emails_respects_alerting_none(self): + """ + Test that soft_budget alerts with empty alert_emails list still respect alerting=None. + """ + from litellm.caching.caching import DualCache + from litellm.proxy.utils import ProxyLogging + from litellm.proxy._types import CallInfo, Litellm_EntityType + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + proxy_logging.alerting = None + proxy_logging.slack_alerting_instance = AsyncMock() + proxy_logging.email_logging_instance = AsyncMock() + + # Create CallInfo with empty alert_emails list + user_info = CallInfo( + token="test-token", + spend=100.0, + soft_budget=50.0, + user_id="test-user", + team_id="test-team", + team_alias="test-team-alias", + event_group=Litellm_EntityType.TEAM, + alert_emails=[], # Empty list + ) + + # Should NOT send email (alert_emails is empty) + await proxy_logging.budget_alerts(type="soft_budget", user_info=user_info) + + # Verify no calls were made + proxy_logging.slack_alerting_instance.budget_alerts.assert_not_called() + proxy_logging.email_logging_instance.budget_alerts.assert_not_called() + def test_azure_ai_claude_provider_config(): """Test that Azure AI Claude models return AzureAnthropicConfig for proper tool transformation.""" From b60d94d655a55c39b4694652d519e7bd68abc718 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 21:27:42 -0800 Subject: [PATCH 030/212] addressing comments --- .../send_emails/base_email.py | 21 ++++++- litellm/proxy/auth/auth_checks.py | 9 +++ litellm/proxy/utils.py | 9 +-- poetry.lock | 58 +++++++++++-------- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py index 1e87e5594fc..d3e04769300 100644 --- a/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py +++ b/enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py @@ -245,15 +245,25 @@ class BaseEmailLogger(CustomLogger): ) return + # Validate that we have at least one valid email address + first_recipient_email = recipient_emails[0] + if not first_recipient_email or not first_recipient_email.strip(): + verbose_proxy_logger.warning( + f"Invalid recipient email found for team soft budget alert. event={event.model_dump(exclude_none=True)}" + ) + return + verbose_proxy_logger.debug( f"send_team_soft_budget_alert_email_event: {json.dumps(event.model_dump(exclude_none=True), indent=4, default=str)}" ) # Get email params using the first recipient email (for template formatting) + # For team alerts with alert_emails, we don't need user_id lookup since we already have email addresses + # Pass user_id=None to prevent _get_email_params from trying to look up email from a potentially None user_id email_params = await self._get_email_params( email_event=EmailEvent.soft_budget_crossed, - user_id=event.user_id, - user_email=recipient_emails[0], + user_id=None, # Team alerts don't require user_id when alert_emails are provided + user_email=first_recipient_email, event_message=event.event_message, ) @@ -361,6 +371,13 @@ class BaseEmailLogger(CustomLogger): if user_info.event_group == Litellm_EntityType.TEAM: if user_info.soft_budget is None: return + # For team soft budget alerts, require alert_emails to be configured + # Team soft budget alerts are sent via metadata.soft_budget_alerting_emails + if user_info.alert_emails is None or len(user_info.alert_emails) == 0: + verbose_proxy_logger.debug( + "Skipping team soft budget email alert: no alert_emails configured", + ) + return else: # For non-team alerts, require either max_budget or soft_budget if user_info.max_budget is None and user_info.soft_budget is None: diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 437ef0d438b..2bacd1f936d 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -2465,6 +2465,15 @@ async def _team_soft_budget_check( else: alert_emails = None + # Only send team soft budget alerts if alert_emails are configured + # Team soft budget alerts are sent via metadata.soft_budget_alerting_emails, not global alerting + if alert_emails is None or len(alert_emails) == 0: + verbose_proxy_logger.debug( + "Skipping team soft budget alert for team %s: no alert_emails configured in metadata.soft_budget_alerting_emails", + team_object.team_id, + ) + return + call_info = CallInfo( token=valid_token.token, spend=team_object.spend, diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 00491675876..0aace65ff6b 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -1385,10 +1385,11 @@ class ProxyLogging: return if self.alerting is not None and "slack" in self.alerting: - await self.slack_alerting_instance.budget_alerts( - type=type, - user_info=user_info, - ) + if self.slack_alerting_instance is not None: + await self.slack_alerting_instance.budget_alerts( + type=type, + user_info=user_info, + ) # Call email_logging_instance if: # 1. "email" is in alerting config, OR diff --git a/poetry.lock b/poetry.lock index 9a7b9d9d5b2..b37fd863431 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "a2a-sdk" @@ -398,6 +398,7 @@ files = [ {file = "azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b"}, {file = "azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7"}, ] +markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] requests = ">=2.21.0" @@ -418,6 +419,7 @@ files = [ {file = "azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651"}, {file = "azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456"}, ] +markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] azure-core = ">=1.31.0" @@ -718,7 +720,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] -markers = {main = "platform_python_implementation != \"PyPy\" or extra == \"proxy\"", dev = "platform_python_implementation != \"PyPy\"", proxy-dev = "platform_python_implementation != \"PyPy\""} +markers = {main = "(platform_python_implementation != \"PyPy\" or extra == \"proxy\") and (python_version >= \"3.10\" or extra == \"proxy\" or extra == \"extra-proxy\") and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "platform_python_implementation != \"PyPy\"", proxy-dev = "platform_python_implementation != \"PyPy\""} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -1149,7 +1151,6 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = ">=3.7" groups = ["main", "dev", "proxy-dev"] -markers = "python_version == \"3.9\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -1179,6 +1180,7 @@ files = [ {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] +markers = {main = "python_version == \"3.9\" and (extra == \"proxy\" or extra == \"extra-proxy\")", dev = "python_version == \"3.9\"", proxy-dev = "python_version == \"3.9\""} [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} @@ -1200,7 +1202,6 @@ description = "cryptography is a package which provides cryptographic recipes an optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" groups = ["main", "dev", "proxy-dev"] -markers = "python_version >= \"3.10\"" files = [ {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, @@ -1257,6 +1258,7 @@ files = [ {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, ] +markers = {main = "python_version >= \"3.10\" and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "python_version >= \"3.10\"", proxy-dev = "python_version >= \"3.10\""} [package.dependencies] cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} @@ -2275,11 +2277,11 @@ files = [ ] [package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" +grpc-google-iam-v1 = ">=0.12.4,<1.0.0.dev0" +proto-plus = ">=1.22.3,<2.0.0.dev0" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [[package]] name = "google-cloud-resource-manager" @@ -3282,7 +3284,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -3446,28 +3448,28 @@ openai = ["openai (>=0.27.8)"] [[package]] name = "litellm-enterprise" -version = "0.1.30" +version = "0.1.27" description = "Package for LiteLLM Enterprise features" optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_enterprise-0.1.30-py3-none-any.whl", hash = "sha256:9715e0b99ae431fcc9621cc515b329b55d7dd291c611c3a0842e94869de219dc"}, - {file = "litellm_enterprise-0.1.30.tar.gz", hash = "sha256:5ffbf25720b75e59c157dca5edff5db61b85e85072c340b1aa02f04e3924fe3e"}, + {file = "litellm_enterprise-0.1.27-py3-none-any.whl", hash = "sha256:41b9d41d04123f492060a742091006dc1d182b54ce3a1c0e18ee75d623c63e91"}, + {file = "litellm_enterprise-0.1.27.tar.gz", hash = "sha256:aa40c87f7c8df64beb79e75f71e1b5c0a458350efa68527e3491e6f27f2cbd57"}, ] [[package]] name = "litellm-proxy-extras" -version = "0.4.31" +version = "0.4.30" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_proxy_extras-0.4.31-py3-none-any.whl", hash = "sha256:f50bee4b21dbe0b1c47ed88dc90cf7a2024b221ff8f6da9343197a1df8ea3ecd"}, - {file = "litellm_proxy_extras-0.4.31.tar.gz", hash = "sha256:0b99a522ca31f2da835bb49037d787257e90269f90c2b5b81a4e8d575edd0778"}, + {file = "litellm_proxy_extras-0.4.30-py3-none-any.whl", hash = "sha256:0b7df68f0968eb817462b847eaee81bba23d935adb2e84d2e342a77711887051"}, + {file = "litellm_proxy_extras-0.4.30.tar.gz", hash = "sha256:5d32f8dc3d37d36fb15ab6995fea706dd8a453ff7f12e70b47cba35e5368da10"}, ] [[package]] @@ -3946,6 +3948,7 @@ files = [ {file = "msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1"}, {file = "msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f"}, ] +markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] cryptography = ">=2.5,<49" @@ -3966,6 +3969,7 @@ files = [ {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, ] +markers = {main = "extra == \"proxy\" or extra == \"extra-proxy\""} [package.dependencies] msal = ">=1.29,<2" @@ -4216,6 +4220,7 @@ files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] +markers = {main = "extra == \"extra-proxy\""} [[package]] name = "numpy" @@ -4423,7 +4428,7 @@ files = [ {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, ] -markers = {main = "python_version >= \"3.10\""} +markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} [package.dependencies] importlib-metadata = ">=6.0,<8.8.0" @@ -4538,7 +4543,7 @@ files = [ {file = "opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c"}, {file = "opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6"}, ] -markers = {main = "python_version >= \"3.10\""} +markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} [package.dependencies] opentelemetry-api = "1.39.1" @@ -4556,7 +4561,7 @@ files = [ {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, ] -markers = {main = "python_version >= \"3.10\""} +markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} [package.dependencies] opentelemetry-api = "1.39.1" @@ -5033,6 +5038,7 @@ files = [ {file = "prisma-0.11.0-py3-none-any.whl", hash = "sha256:22bb869e59a2968b99f3483bb417717273ffbc569fd1e9ceed95e5614cbaf53a"}, {file = "prisma-0.11.0.tar.gz", hash = "sha256:3f2f2fd2361e1ec5ff655f2a04c7860c2f2a5bc4c91f78ca9c5c6349735bf693"}, ] +markers = {main = "extra == \"extra-proxy\""} [package.dependencies] click = ">=7.1.2" @@ -5349,7 +5355,7 @@ files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] -markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\")", dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} +markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\") and (python_version >= \"3.10\" or extra == \"proxy\" or extra == \"extra-proxy\") and (extra == \"proxy\" or extra == \"extra-proxy\" or extra == \"mlflow\")", dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} [[package]] name = "pydantic" @@ -5572,6 +5578,7 @@ files = [ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] +markers = {main = "extra == \"extra-proxy\" or extra == \"proxy\""} [package.dependencies] cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} @@ -6673,10 +6680,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a.0" +botocore = ">=1.37.4,<2.0a0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] [[package]] name = "scikit-learn" @@ -6909,9 +6916,9 @@ tornado = ">=6.4.2,<7" urllib3 = ">=1.26,<3" [package.extras] -all = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)", "cohere (>=5.9.4,<6.00)", "dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\"", "google-cloud-aiplatform (>=1.45.0,<2)", "ipykernel (>=6.25.0,<7)", "llama-cpp-python (>=0.2.28,<0.2.86) ; python_version < \"3.13\"", "mistralai (>=0.0.12,<0.1.0)", "mypy (>=1.7.1,<2)", "ollama (>=0.1.7)", "pillow (>=10.2.0,<11.0.0) ; python_version < \"3.13\"", "pinecone[asyncio] (>=7.0.0,<8.0.0)", "psycopg[binary] (>=3.1.0,<4)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "qdrant-client (>=1.11.1,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "sentence-transformers (>=5.0.0) ; python_version < \"3.13\"", "tokenizers (>=0.19) ; python_version < \"3.13\"", "torch (>=2.6.0) ; python_version < \"3.13\"", "torchvision (>=0.17.0) ; python_version < \"3.13\"", "transformers (>=4.36.2) ; python_version < \"3.13\"", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] +all = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)", "cohere (>=5.9.4,<6.0)", "dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\"", "google-cloud-aiplatform (>=1.45.0,<2)", "ipykernel (>=6.25.0,<7)", "llama-cpp-python (>=0.2.28,<0.2.86) ; python_version < \"3.13\"", "mistralai (>=0.0.12,<0.1.0)", "mypy (>=1.7.1,<2)", "ollama (>=0.1.7)", "pillow (>=10.2.0,<11.0.0) ; python_version < \"3.13\"", "pinecone[asyncio] (>=7.0.0,<8.0.0)", "psycopg[binary] (>=3.1.0,<4)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "qdrant-client (>=1.11.1,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "sentence-transformers (>=5.0.0) ; python_version < \"3.13\"", "tokenizers (>=0.19) ; python_version < \"3.13\"", "torch (>=2.6.0) ; python_version < \"3.13\"", "torchvision (>=0.17.0) ; python_version < \"3.13\"", "transformers (>=4.36.2) ; python_version < \"3.13\"", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] bedrock = ["boto3 (>=1.34.98,<2)", "botocore (>=1.34.110,<2)"] -cohere = ["cohere (>=5.9.4,<6.00)"] +cohere = ["cohere (>=5.9.4,<6.0)"] dev = ["dagger-io (>=0.1.1) ; python_version >= \"3.11\"", "ipykernel (>=6.25.0,<7)", "mypy (>=1.7.1,<2)", "pytest (>=8.2,<9.0)", "pytest-asyncio (>=0.24.0,<0.25)", "pytest-cov (>=4.1.0,<5)", "pytest-mock (>=3.12.0,<4)", "pytest-timeout", "pytest-xdist (>=3.5.0,<4)", "python-dotenv (>=1.0.0,<2)", "requests-mock (>=1.12.1,<2)", "ruff (>=0.11.2,<0.12)", "types-pyyaml (>=6.0.12.12,<7)", "types-requests (>=2.31.0,<3)"] docs = ["pydoc-markdown (>=4.8.2) ; python_version < \"3.12\""] fastembed = ["fastembed (>=0.3.0,<0.4) ; python_version < \"3.13\""] @@ -7755,6 +7762,7 @@ files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, ] +markers = {main = "extra == \"extra-proxy\""} [[package]] name = "tornado" @@ -8523,4 +8531,4 @@ utils = ["numpydoc"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "16433b6ceb3c56276021223166394591cfd5a18da40c2244799d56ee3167c366" +content-hash = "e5447e14dd37e324ac07a8fc6286d27e9a0d355ed93ebb24fc11e3f5df12fd3e" From e39530d0e6cd7449be31a80bfe67c39064f0867b Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 21:28:13 -0800 Subject: [PATCH 031/212] =?UTF-8?q?bump:=20version=200.1.30=20=E2=86=92=20?= =?UTF-8?q?0.1.31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enterprise/pyproject.toml | 4 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/enterprise/pyproject.toml b/enterprise/pyproject.toml index 4cb838be036..eca5cdb97df 100644 --- a/enterprise/pyproject.toml +++ b/enterprise/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-enterprise" -version = "0.1.30" +version = "0.1.31" description = "Package for LiteLLM Enterprise features" authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.1.30" +version = "0.1.31" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-enterprise==", diff --git a/pyproject.toml b/pyproject.toml index c4ff0aac209..fe76d8e15df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"} a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} litellm-proxy-extras = {version = "0.4.31", optional = true} rich = {version = "13.7.1", optional = true} -litellm-enterprise = {version = "0.1.30", optional = true} +litellm-enterprise = {version = "0.1.31", optional = true} diskcache = {version = "^5.6.1", optional = true} polars = {version = "^1.31.0", optional = true, python = ">=3.10"} semantic-router = {version = ">=0.1.12", optional = true, python = ">=3.9,<3.14"} diff --git a/requirements.txt b/requirements.txt index f0e5059928b..8b69d4ac85c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,4 +73,4 @@ pypdf>=6.6.2 # for PDF text extraction in RAG ingestion ######################## # LITELLM ENTERPRISE DEPENDENCIES ######################## -litellm-enterprise==0.1.30 +litellm-enterprise==0.1.31 From b3f0dccf56229d06a019054c2cd12ddf777badc1 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 21:31:20 -0800 Subject: [PATCH 032/212] enterprise build --- .../litellm_enterprise-0.1.31-py3-none-any.whl | Bin 0 -> 112741 bytes .../dist/litellm_enterprise-0.1.31.tar.gz | Bin 0 -> 50205 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 enterprise/dist/litellm_enterprise-0.1.31-py3-none-any.whl create mode 100644 enterprise/dist/litellm_enterprise-0.1.31.tar.gz diff --git a/enterprise/dist/litellm_enterprise-0.1.31-py3-none-any.whl b/enterprise/dist/litellm_enterprise-0.1.31-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..03cadbd902329b9f1ea76b9be23d1786eddf3224 GIT binary patch literal 112741 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 chTDrH+OCak&bhdQ<|AdM}(e3EeA6 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%0A*JqK5omY_`aEzInI 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 literal 0 HcmV?d00001 diff --git a/enterprise/dist/litellm_enterprise-0.1.31.tar.gz b/enterprise/dist/litellm_enterprise-0.1.31.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1ba1a717f62e458d5de2db9512ea6c72c44ab9c8 GIT binary patch literal 50205 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 F0{{}QKvw_& literal 0 HcmV?d00001 From 0cb79ace97291be722fcab770cd1c72aa4026e03 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 5 Feb 2026 21:44:00 -0800 Subject: [PATCH 033/212] Fixing tests --- tests/proxy_unit_tests/test_auth_checks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/proxy_unit_tests/test_auth_checks.py b/tests/proxy_unit_tests/test_auth_checks.py index ee595092995..66dfc8d15d5 100644 --- a/tests/proxy_unit_tests/test_auth_checks.py +++ b/tests/proxy_unit_tests/test_auth_checks.py @@ -483,8 +483,8 @@ async def test_virtual_key_soft_budget_check(spend, soft_budget, expect_alert): @pytest.mark.parametrize( "spend, soft_budget, expect_alert, metadata, expected_alert_emails", [ - (100, 50, True, None, None), # Over soft budget, no metadata - (50, 50, True, None, None), # At soft budget, no metadata + (100, 50, False, None, None), # Over soft budget, no metadata - no alert_emails configured, so no alert + (50, 50, False, None, None), # At soft budget, no metadata - no alert_emails configured, so no alert (25, 50, False, None, None), # Under soft budget (100, None, False, None, None), # No soft budget set (100, 50, True, {"soft_budget_alerting_emails": ["team1@example.com", "team2@example.com"]}, ["team1@example.com", "team2@example.com"]), # Over soft budget with list of emails @@ -496,8 +496,8 @@ async def test_virtual_key_soft_budget_check(spend, soft_budget, expect_alert): async def test_team_soft_budget_check(spend, soft_budget, expect_alert, metadata, expected_alert_emails): """ Test cases for _team_soft_budget_check: - 1. Spend over soft budget - should trigger alert - 2. Spend at soft budget - should trigger alert + 1. Spend over soft budget, no alert_emails configured - should NOT trigger alert (alerts only sent when alert_emails configured) + 2. Spend at soft budget, no alert_emails configured - should NOT trigger alert (alerts only sent when alert_emails configured) 3. Spend under soft budget - should not trigger alert 4. No soft budget set - should not trigger alert 5. Team with alert emails in metadata (list) - should include alert_emails in CallInfo From 039b37fac14885cbe12a63e14f16f8cf8ca8daca Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 11:31:35 +0530 Subject: [PATCH 034/212] Add compaction type block in the output --- litellm/litellm_core_utils/core_helpers.py | 4 +- litellm/llms/anthropic/chat/transformation.py | 64 ++++++++++++++----- litellm/types/llms/anthropic.py | 1 + 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index 00695cbfb5b..7c8e2ebeaff 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -94,8 +94,8 @@ def map_finish_reason( return "length" elif finish_reason == "tool_use": # anthropic return "tool_calls" - elif finish_reason == "content_filtered": - return "content_filter" + elif finish_reason == "compaction": + return "length" return finish_reason diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 61616c7f1b4..5cbf014f8fb 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1028,9 +1028,37 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if beta_value not in existing_values: headers["anthropic-beta"] = f"{existing_beta}, {beta_value}" - def _ensure_context_management_beta_header(self, headers: dict) -> None: - beta_value = ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value - self._ensure_beta_header(headers, beta_value) + def _ensure_context_management_beta_header( + self, headers: dict, context_management: dict + ) -> None: + """ + Add appropriate beta headers based on context_management edits. + - If any edit has type "compact_20260112", add compact-2026-01-12 header + - For all other edits, add context-management-2025-06-27 header + """ + edits = context_management.get("edits", []) + + has_compact = False + has_other = False + + for edit in edits: + edit_type = edit.get("type", "") + if edit_type == "compact_20260112": + has_compact = True + else: + has_other = True + + # Add compact header if any compact edits exist + if has_compact: + self._ensure_beta_header( + headers, ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_01_12.value + ) + + # Add context management header if any other edits exist + if has_other: + self._ensure_beta_header( + headers, ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value + ) def update_headers_with_optional_anthropic_beta( self, headers: dict, optional_params: dict @@ -1058,7 +1086,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): headers, ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value ) if optional_params.get("context_management") is not None: - self._ensure_context_management_beta_header(headers) + self._ensure_context_management_beta_header( + headers, optional_params["context_management"] + ) if optional_params.get("output_format") is not None: self._ensure_beta_header( headers, ANTHROPIC_BETA_HEADER_VALUES.STRUCTURED_OUTPUT_2025_09_25.value @@ -1227,6 +1257,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): List[ChatCompletionToolCallChunk], Optional[List[Any]], Optional[List[Any]], + Optional[List[Any]], ]: text_content = "" citations: Optional[List[Any]] = None @@ -1239,6 +1270,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_calls: List[ChatCompletionToolCallChunk] = [] web_search_results: Optional[List[Any]] = None tool_results: Optional[List[Any]] = None + context_management: Optional[List[Any]] = None + compaction_blocks: Optional[List[Any]] = None for idx, content in enumerate(completion_response["content"]): if content["type"] == "text": text_content += content["text"] @@ -1280,6 +1313,12 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): thinking_blocks.append( cast(ChatCompletionRedactedThinkingBlock, content) ) + + ## COMPACTION + elif content["type"] == "compaction": + if compaction_blocks is None: + compaction_blocks = [] + compaction_blocks.append(content) ## CITATIONS if content.get("citations") is not None: @@ -1301,7 +1340,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if thinking_content is not None: reasoning_content += thinking_content - return text_content, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results + return text_content, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks def calculate_usage( self, @@ -1444,6 +1483,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_calls, web_search_results, tool_results, + compaction_blocks, ) = self.extract_response_content(completion_response=completion_response) if ( @@ -1471,6 +1511,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): provider_specific_fields["tool_results"] = tool_results if container is not None: provider_specific_fields["container"] = container + if compaction_blocks is not None: + provider_specific_fields["compaction_blocks"] = compaction_blocks _message = litellm.Message( tool_calls=tool_calls, @@ -1479,6 +1521,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): thinking_blocks=thinking_blocks, reasoning_content=reasoning_content, ) + _message.provider_specific_fields = provider_specific_fields ## HANDLE JSON MODE - anthropic returns single function call json_mode_message = self._transform_response_for_json_mode( @@ -1509,18 +1552,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): model_response.created = int(time.time()) model_response.model = completion_response["model"] - context_management_response = completion_response.get("context_management") - if context_management_response is not None: - _hidden_params["context_management"] = context_management_response - try: - model_response.__dict__["context_management"] = ( - context_management_response - ) - except Exception: - pass - model_response._hidden_params = _hidden_params - return model_response def get_prefix_prompt(self, messages: List[AllMessageValues]) -> Optional[str]: diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index 62e775d4faa..85d419ccc7d 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -633,6 +633,7 @@ class ANTHROPIC_BETA_HEADER_VALUES(str, Enum): WEB_FETCH_2025_09_10 = "web-fetch-2025-09-10" WEB_SEARCH_2025_03_05 = "web-search-2025-03-05" CONTEXT_MANAGEMENT_2025_06_27 = "context-management-2025-06-27" + COMPACT_2026_01_12 = "compact-2026-01-12" STRUCTURED_OUTPUT_2025_09_25 = "structured-outputs-2025-11-13" ADVANCED_TOOL_USE_2025_11_20 = "advanced-tool-use-2025-11-20" From c03ba8394e4d1e303b9274b8c50917dc5019da7b Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 11:54:47 +0530 Subject: [PATCH 035/212] Add compaction block in provider spcific fields streaming+ non streaming --- litellm/llms/anthropic/chat/handler.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index 6a9aafd076b..485e95d6489 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -512,6 +512,9 @@ class ModelResponseIterator: # Accumulate web_search_tool_result blocks for multi-turn reconstruction # See: https://github.com/BerriAI/litellm/issues/17737 self.web_search_results: List[Dict[str, Any]] = [] + + # Accumulate compaction blocks for multi-turn reconstruction + self.compaction_blocks: List[Dict[str, Any]] = [] def check_empty_tool_call_args(self) -> bool: """ @@ -592,6 +595,12 @@ class ModelResponseIterator: ) ] provider_specific_fields["thinking_blocks"] = thinking_blocks + elif "content" in content_block["delta"] and content_block["delta"].get("type") == "compaction_delta": + # Handle compaction delta + provider_specific_fields["compaction_delta"] = { + "type": "compaction_delta", + "content": content_block["delta"]["content"] + } return text, tool_use, thinking_blocks, provider_specific_fields @@ -721,6 +730,20 @@ class ModelResponseIterator: provider_specific_fields=provider_specific_fields, ) + elif content_block_start["content_block"]["type"] == "compaction": + # Handle compaction blocks + # The full content comes in content_block_start + self.compaction_blocks.append( + content_block_start["content_block"] + ) + provider_specific_fields["compaction_blocks"] = ( + self.compaction_blocks + ) + provider_specific_fields["compaction_start"] = { + "type": "compaction", + "content": content_block_start["content_block"].get("content", "") + } + elif content_block_start["content_block"]["type"].endswith("_tool_result"): # Handle all tool result types (web_search, bash_code_execution, text_editor, etc.) content_type = content_block_start["content_block"]["type"] From 24dda99bd76a71fd9e5949ac129f1adc47130880 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 11:55:12 +0530 Subject: [PATCH 036/212] Handle compaction block in the input request --- litellm/litellm_core_utils/prompt_templates/factory.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 53d2ca2f23f..f9ecd78ff1c 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -2190,6 +2190,16 @@ def anthropic_messages_pt( # noqa: PLR0915 while msg_i < len(messages) and messages[msg_i]["role"] == "assistant": assistant_content_block: ChatCompletionAssistantMessage = messages[msg_i] # type: ignore + # Extract compaction_blocks from provider_specific_fields and add them first + _provider_specific_fields_raw = assistant_content_block.get( + "provider_specific_fields" + ) + if isinstance(_provider_specific_fields_raw, dict): + _compaction_blocks = _provider_specific_fields_raw.get("compaction_blocks") + if _compaction_blocks and isinstance(_compaction_blocks, list): + # Add compaction blocks at the beginning of assistant content : https://platform.claude.com/docs/en/build-with-claude/compaction + assistant_content.extend(_compaction_blocks) # type: ignore + thinking_blocks = assistant_content_block.get("thinking_blocks", None) if ( thinking_blocks is not None From 887a977ab49b5e1643fb871860bb84e794012877 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:05:36 +0530 Subject: [PATCH 037/212] Add doc on how to enable compaction via chat completion --- docs/my-website/blog/claude_opus_4_6/index.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/docs/my-website/blog/claude_opus_4_6/index.md b/docs/my-website/blog/claude_opus_4_6/index.md index b1836cfaef5..3e07b70a164 100644 --- a/docs/my-website/blog/claude_opus_4_6/index.md +++ b/docs/my-website/blog/claude_opus_4_6/index.md @@ -3,6 +3,10 @@ slug: claude_opus_4_6 title: "Day 0 Support: Claude Opus 4.6" date: 2026-02-05T10:00:00 authors: + - name: Sameer Kankute + title: SWE @ LiteLLM (LLM Translation) + url: https://www.linkedin.com/in/sameer-kankute/ + image_url: https://pbs.twimg.com/profile_images/2001352686994907136/ONgNuSk5_400x400.jpg - name: Ishaan Jaff title: "CTO, LiteLLM" url: https://www.linkedin.com/in/reffajnaahsi/ @@ -219,6 +223,131 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ +## Compaction + +Litellm supports enabling compaction for the new claude-opus-4-6. + +### Enabling Compaction + +To enable compaction, add the `context_management` parameter with the `compact_20260112` edit type: + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data '{ + "model": "claude-opus-4-6", + "messages": [ + { + "role": "user", + "content": "What is the weather in San Francisco?" + } + ], + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100 +}' +``` +All the parameters supported for context_management by anthropic are supported and can be directly added. Litellm automatically adds the `compact-2026-01-12` beta header in the request. + + +### Response with Compaction Block + +The response will include the compaction summary in `provider_specific_fields.compaction_blocks`: + +```json +{ + "id": "chatcmpl-a6c105a3-4b25-419e-9551-c800633b6cb2", + "created": 1770357619, + "model": "claude-opus-4-6", + "object": "chat.completion", + "choices": [ + { + "finish_reason": "length", + "index": 0, + "message": { + "content": "I don't have access to real-time data, so I can't provide the current weather in San Francisco. To get up-to-date weather information, I'd recommend checking:\n\n- **Weather websites** like weather.com, accuweather.com, or wunderground.com\n- **Search engines** – just Google \"San Francisco weather\"\n- **Weather apps** on your phone (e.g., Apple Weather, Google Weather)\n- **National", + "role": "assistant", + "provider_specific_fields": { + "compaction_blocks": [ + { + "type": "compaction", + "content": "Summary of the conversation: The user requested help building a web scraper..." + } + ] + } + } + } + ], + "usage": { + "completion_tokens": 100, + "prompt_tokens": 86, + "total_tokens": 186 + } +} +``` + +### Using Compaction Blocks in Follow-up Requests + +To continue the conversation with compaction, include the compaction block in the assistant message's `provider_specific_fields`: + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data '{ + "model": "claude-opus-4-6", + "messages": [ + { + "role": "user", + "content": "How can I build a web scraper?" + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "Certainly! To build a basic web scraper, you'll typically use a programming language like Python along with libraries such as `requests` (for fetching web pages) and `BeautifulSoup` (for parsing HTML). Here's a basic example:\n\n```python\nimport requests\nfrom bs4 import BeautifulSoup\n\nurl = 'https://example.com'\nresponse = requests.get(url)\nsoup = BeautifulSoup(response.text, 'html.parser')\n\n# Extract and print all text\ntext = soup.get_text()\nprint(text)\n```\n\nLet me know what you're interested in scraping or if you need help with a specific website!" + } + ], + "provider_specific_fields": { + "compaction_blocks": [ + { + "type": "compaction", + "content": "Summary of the conversation: The user asked how to build a web scraper, and the assistant gave an overview using Python with requests and BeautifulSoup." + } + ] + } + }, + { + "role": "user", + "content": "How do I use it to scrape product prices?" + } + ], + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100 +}' +``` + +### Streaming Support + +Compaction blocks are also supported in streaming mode. You'll receive: +- `compaction_start` event when a compaction block begins +- `compaction_delta` events with the compaction content +- The accumulated `compaction_blocks` in `provider_specific_fields` + + ## More Features Coming Soon We're actively working on supporting new features for Claude Opus 4.6. Stay tuned for updates! From ea518a76842a1e7c052e3447f13f12f693171778 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:06:50 +0530 Subject: [PATCH 038/212] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/llms/anthropic/chat/transformation.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 5cbf014f8fb..686eec4032b 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -656,14 +656,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): new_v.append(v) if len(new_v) > 0: new_stop = new_v - return new_stop - - @staticmethod - def _map_reasoning_effort( - reasoning_effort: Optional[Union[REASONING_EFFORT, str]], - model: str, - ) -> Optional[AnthropicThinkingParam]: if AnthropicConfig._is_claude_opus_4_6(model): + if reasoning_effort is None: + return None return AnthropicThinkingParam( type="adaptive", ) From 7a473f2954b9ab63001afa6a7fb096d87445790d Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:07:13 +0530 Subject: [PATCH 039/212] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- litellm/llms/anthropic/chat/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 686eec4032b..c049478c96d 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -172,7 +172,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): @staticmethod def _is_claude_opus_4_6(model: str) -> bool: - """Check if the model is Claude Opus 4.5.""" + """Check if the model is Claude Opus 4.6.""" return "opus-4-6" in model.lower() or "opus_4_6" in model.lower() def get_supported_openai_params(self, model: str): From d0444f402cac6b0b1b19991fb044a874b26848b0 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:17:28 +0530 Subject: [PATCH 040/212] Add test for compaction in anthropic --- .../test_anthropic_chat_transformation.py | 381 +++++++++++++++--- 1 file changed, 322 insertions(+), 59 deletions(-) diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index f556c4abe51..49db7367c67 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -185,7 +185,7 @@ def test_extract_response_content_with_citations(): }, } - _, citations, _, _, _, _ , _= config.extract_response_content(completion_response) + _, citations, _, _, _, _, _, _ = config.extract_response_content(completion_response) assert citations == [ [ { @@ -342,7 +342,7 @@ def test_web_search_tool_result_extraction(): } } - text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results = config.extract_response_content( + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( completion_response ) @@ -474,7 +474,7 @@ def test_multiple_web_search_tool_results(): ] } - text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results = config.extract_response_content( + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( completion_response ) @@ -817,59 +817,6 @@ def test_anthropic_chat_transform_request_includes_context_management(): assert result["context_management"] == _sample_context_management_payload() -def test_transform_parsed_response_includes_context_management_metadata(): - import httpx - - from litellm.types.utils import ModelResponse - - config = AnthropicConfig() - context_management_payload = { - "applied_edits": [ - { - "type": "clear_tool_uses_20250919", - "cleared_tool_uses": 2, - "cleared_input_tokens": 5000, - } - ] - } - completion_response = { - "id": "msg_context_management_test", - "type": "message", - "role": "assistant", - "model": "claude-sonnet-4-20250514", - "content": [{"type": "text", "text": "Done."}], - "stop_reason": "end_turn", - "stop_sequence": None, - "usage": { - "input_tokens": 10, - "cache_creation_input_tokens": 0, - "cache_read_input_tokens": 0, - "output_tokens": 5, - }, - "context_management": context_management_payload, - } - raw_response = httpx.Response( - status_code=200, - headers={}, - ) - model_response = ModelResponse() - - result = config.transform_parsed_response( - completion_response=completion_response, - raw_response=raw_response, - model_response=model_response, - json_mode=False, - prefix_prompt=None, - ) - - assert result.__dict__.get("context_management") == context_management_payload - provider_fields = result.choices[0].message.provider_specific_fields - assert ( - provider_fields - and provider_fields["context_management"] == context_management_payload - ) - - def test_anthropic_structured_output_beta_header(): from litellm.types.utils import CallTypes from litellm.utils import return_raw_request @@ -1043,7 +990,7 @@ def test_server_tool_use_in_response(): ] } - text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results = config.extract_response_content( + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( completion_response ) @@ -1171,7 +1118,7 @@ def test_tool_search_complete_response_parsing(): } # Extract content - text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results = config.extract_response_content( + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( completion_response ) @@ -1291,7 +1238,7 @@ def test_caller_field_in_response(): "usage": {"input_tokens": 100, "output_tokens": 50} } - text, citations, thinking, reasoning, tool_calls, web_search_results, tool_results = config.extract_response_content(completion_response) + text, citations, thinking, reasoning, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content(completion_response) assert len(tool_calls) == 1 assert tool_calls[0]["id"] == "toolu_123" @@ -2243,3 +2190,319 @@ def test_web_search_tool_result_backwards_compatibility(): # Should NOT be in tool_results assert provider_fields.get("tool_results") is None + + +# ============ Compaction Tests ============ + + +def test_compaction_block_extraction(): + """ + Test that compaction blocks are correctly extracted from Anthropic response. + """ + config = AnthropicConfig() + + completion_response = { + "id": "msg_compaction_test", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [ + { + "type": "compaction", + "content": "Summary of the conversation: The user requested help building a web scraper..." + }, + { + "type": "text", + "text": "I don't have access to real-time data, so I can't provide the current weather in San Francisco." + } + ], + "stop_reason": "max_tokens", + "stop_sequence": None, + "usage": { + "input_tokens": 86, + "output_tokens": 100 + } + } + + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( + completion_response + ) + + # Verify compaction blocks are extracted + assert compaction_blocks is not None + assert len(compaction_blocks) == 1 + assert compaction_blocks[0]["type"] == "compaction" + assert "Summary of the conversation" in compaction_blocks[0]["content"] + + # Verify text content is extracted + assert "I don't have access to real-time data" in text + + +def test_compaction_block_in_provider_specific_fields(): + """ + Test that compaction blocks are included in provider_specific_fields. + """ + import httpx + + from litellm.types.utils import ModelResponse + + config = AnthropicConfig() + + completion_response = { + "id": "msg_compaction_provider_fields", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [ + { + "type": "compaction", + "content": "Summary of the conversation: The user requested help building a web scraper..." + }, + { + "type": "text", + "text": "Here is the response." + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 50, + "output_tokens": 25 + } + } + + raw_response = httpx.Response(status_code=200, headers={}) + model_response = ModelResponse() + + result = config.transform_parsed_response( + completion_response=completion_response, + raw_response=raw_response, + model_response=model_response, + json_mode=False, + prefix_prompt=None, + ) + + # Verify compaction_blocks is in provider_specific_fields + provider_fields = result.choices[0].message.provider_specific_fields + assert provider_fields is not None + assert "compaction_blocks" in provider_fields + assert len(provider_fields["compaction_blocks"]) == 1 + assert provider_fields["compaction_blocks"][0]["type"] == "compaction" + assert "Summary of the conversation" in provider_fields["compaction_blocks"][0]["content"] + + +def test_multiple_compaction_blocks(): + """ + Test that multiple compaction blocks are all extracted. + """ + config = AnthropicConfig() + + completion_response = { + "content": [ + { + "type": "compaction", + "content": "First summary..." + }, + { + "type": "text", + "text": "Some text." + }, + { + "type": "compaction", + "content": "Second summary..." + } + ] + } + + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( + completion_response + ) + + # Verify both compaction blocks are extracted + assert compaction_blocks is not None + assert len(compaction_blocks) == 2 + assert compaction_blocks[0]["content"] == "First summary..." + assert compaction_blocks[1]["content"] == "Second summary..." + + +def test_compaction_block_request_transformation(): + """ + Test that compaction blocks from provider_specific_fields are correctly + transformed back to Anthropic format in requests. + """ + from litellm.litellm_core_utils.prompt_templates.factory import ( + anthropic_messages_pt, + ) + + messages = [ + { + "role": "user", + "content": "What is the weather in San Francisco?" + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I don't have access to real-time data." + } + ], + "provider_specific_fields": { + "compaction_blocks": [ + { + "type": "compaction", + "content": "Summary of the conversation: The user requested help building a web scraper..." + } + ] + } + }, + { + "role": "user", + "content": "What about New York?" + } + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-opus-4-6", + llm_provider="anthropic" + ) + + # Find the assistant message + assistant_message = None + for msg in result: + if msg["role"] == "assistant": + assistant_message = msg + break + + assert assistant_message is not None + assert "content" in assistant_message + assert isinstance(assistant_message["content"], list) + + # Verify compaction block is at the beginning + assert assistant_message["content"][0]["type"] == "compaction" + assert "Summary of the conversation" in assistant_message["content"][0]["content"] + + # Verify text content follows + text_blocks = [c for c in assistant_message["content"] if c.get("type") == "text"] + assert len(text_blocks) > 0 + assert "I don't have access to real-time data" in text_blocks[0]["text"] + + +def test_compaction_with_context_management(): + """ + Test that compaction works with context_management parameter. + """ + config = AnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100 + } + + result = config.transform_request( + model="claude-opus-4-6", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + # Verify context_management is included + assert "context_management" in result + assert result["context_management"]["edits"][0]["type"] == "compact_20260112" + + +def test_compaction_block_with_other_content_types(): + """ + Test that compaction blocks work alongside other content types like thinking blocks and tool calls. + """ + config = AnthropicConfig() + + completion_response = { + "content": [ + { + "type": "compaction", + "content": "Summary of previous conversation..." + }, + { + "type": "thinking", + "thinking": "Let me think about this..." + }, + { + "type": "text", + "text": "Based on my analysis..." + }, + { + "type": "tool_use", + "id": "toolu_123", + "name": "get_weather", + "input": {"location": "San Francisco"} + } + ] + } + + text, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results, compaction_blocks = config.extract_response_content( + completion_response + ) + + # Verify all content types are extracted + assert compaction_blocks is not None + assert len(compaction_blocks) == 1 + assert thinking_blocks is not None + assert len(thinking_blocks) == 1 + assert "Based on my analysis" in text + assert len(tool_calls) == 1 + assert tool_calls[0]["function"]["name"] == "get_weather" + + +def test_compaction_block_empty_list_not_added(): + """ + Test that empty compaction_blocks list is not added to provider_specific_fields. + """ + import httpx + + from litellm.types.utils import ModelResponse + + config = AnthropicConfig() + + # Response without compaction blocks + completion_response = { + "id": "msg_no_compaction", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [ + { + "type": "text", + "text": "Just a regular response." + } + ], + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 5 + } + } + + raw_response = httpx.Response(status_code=200, headers={}) + model_response = ModelResponse() + + result = config.transform_parsed_response( + completion_response=completion_response, + raw_response=raw_response, + model_response=model_response, + json_mode=False, + prefix_prompt=None, + ) + + # Verify compaction_blocks is not in provider_specific_fields when there are none + provider_fields = result.choices[0].message.provider_specific_fields + if provider_fields: + assert "compaction_blocks" not in provider_fields or provider_fields.get("compaction_blocks") is None From 1396813d74213cd0b860bf16029f566c73fb47ad Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:24:58 +0530 Subject: [PATCH 041/212] The compact beta feature is not currently supported on the Converse and ConverseStream APIs --- litellm/llms/bedrock/chat/converse_transformation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 22fccd8f943..7c6c4065782 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -82,6 +82,7 @@ BEDROCK_COMPUTER_USE_TOOLS = [ UNSUPPORTED_BEDROCK_CONVERSE_BETA_PATTERNS = [ "advanced-tool-use", # Bedrock Converse doesn't support advanced-tool-use beta headers "prompt-caching", # Prompt caching not supported in Converse API + "compact-2026-01-12", # The compact beta feature is not currently supported on the Converse and ConverseStream APIs ] From 358a081f638bbbe98500a59c3b19b968b0454376 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 12:51:55 +0530 Subject: [PATCH 042/212] Add compaction support for vertex ai --- litellm/llms/anthropic/chat/transformation.py | 3 + .../anthropic/transformation.py | 39 ++++++++ .../test_azure_anthropic_transformation.py | 94 +++++++++++++++++++ ...partner_models_anthropic_transformation.py | 70 ++++++++++++++ 4 files changed, 206 insertions(+) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index c049478c96d..9233c4a84b9 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -874,6 +874,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) elif param == "extra_headers": optional_params["extra_headers"] = value + elif param == "context_management" and isinstance(value, dict): + # Pass through Anthropic-specific context_management parameter + optional_params["context_management"] = value ## handle thinking tokens self.update_optional_params_with_thinking_tokens( diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/transformation.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/transformation.py index 1df07f405e6..0b728d88e76 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/transformation.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/transformation.py @@ -51,6 +51,40 @@ class VertexAIAnthropicConfig(AnthropicConfig): def custom_llm_provider(self) -> Optional[str]: return "vertex_ai" + def _add_context_management_beta_headers( + self, beta_set: set, context_management: dict + ) -> None: + """ + Add context_management beta headers to the beta_set. + + - If any edit has type "compact_20260112", add compact-2026-01-12 header + - For all other edits, add context-management-2025-06-27 header + + Args: + beta_set: Set of beta headers to modify in-place + context_management: The context_management dict from optional_params + """ + from litellm.types.llms.anthropic import ANTHROPIC_BETA_HEADER_VALUES + + edits = context_management.get("edits", []) + has_compact = False + has_other = False + + for edit in edits: + edit_type = edit.get("type", "") + if edit_type == "compact_20260112": + has_compact = True + else: + has_other = True + + # Add compact header if any compact edits exist + if has_compact: + beta_set.add(ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_01_12.value) + + # Add context management header if any other edits exist + if has_other: + beta_set.add(ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value) + def transform_request( self, model: str, @@ -86,6 +120,11 @@ class VertexAIAnthropicConfig(AnthropicConfig): beta_set = set(auto_betas) if tool_search_used: beta_set.add("tool-search-tool-2025-10-19") # Vertex requires this header for tool search + + # Add context_management beta headers (compact and/or context-management) + context_management = optional_params.get("context_management") + if context_management: + self._add_context_management_beta_headers(beta_set, context_management) if beta_set: data["anthropic_beta"] = list(beta_set) diff --git a/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_transformation.py b/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_transformation.py index e43a899325f..f0f8a9d91bf 100644 --- a/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_transformation.py +++ b/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_transformation.py @@ -235,3 +235,97 @@ class TestAzureAnthropicConfig: assert result["max_tokens"] == 100 assert "messages" in result + def test_context_management_compact_beta_header(self): + """Test that context_management with compact adds the correct beta header for Azure AI""" + config = AzureAnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100 + } + litellm_params = {"api_key": "test-key"} + headers = {"api-key": "test-key"} + + with patch( + "litellm.llms.azure.common_utils.BaseAzureLLM._base_validate_azure_environment" + ) as mock_validate: + mock_validate.return_value = {"api-key": "test-key"} + result = config.transform_request( + model="claude-opus-4-6", + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + headers=headers, + ) + + # Verify context_management is included + assert "context_management" in result + assert result["context_management"]["edits"][0]["type"] == "compact_20260112" + + def test_context_management_compact_beta_header_in_headers(self): + """Test that compact beta header is added to headers for Azure AI""" + config = AzureAnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100 + } + + # Test that the parent's update_headers_with_optional_anthropic_beta is called + # which should add the compact beta header + headers = {} + headers = config.update_headers_with_optional_anthropic_beta( + headers=headers, + optional_params=optional_params + ) + + # Verify compact beta header is present + assert "anthropic-beta" in headers + assert "compact-2026-01-12" in headers["anthropic-beta"] + + def test_context_management_mixed_edits_beta_headers(self): + """Test that context_management with both compact and other edits adds both beta headers""" + config = AzureAnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + }, + { + "type": "replace", + "message_id": "msg_123", + "content": "new content" + } + ] + }, + "max_tokens": 100 + } + + headers = {} + headers = config.update_headers_with_optional_anthropic_beta( + headers=headers, + optional_params=optional_params + ) + + # Verify both beta headers are present + assert "anthropic-beta" in headers + assert "compact-2026-01-12" in headers["anthropic-beta"] + assert "context-management-2025-06-27" in headers["anthropic-beta"] + diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py index 3e6c6f6740c..cda875175cc 100644 --- a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py @@ -75,6 +75,76 @@ def test_vertex_ai_anthropic_web_search_header_in_completion(): "anthropic-beta with web-search should not be present for non-Vertex requests" +def test_vertex_ai_anthropic_context_management_compact_beta_header(): + """Test that context_management with compact adds the correct beta header for Vertex AI""" + config = VertexAIAnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + } + ] + }, + "max_tokens": 100, + "is_vertex_request": True + } + + result = config.transform_request( + model="claude-opus-4-6", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + # Verify context_management is included + assert "context_management" in result + assert result["context_management"]["edits"][0]["type"] == "compact_20260112" + + # Verify compact beta header is in anthropic_beta field + assert "anthropic_beta" in result + assert "compact-2026-01-12" in result["anthropic_beta"] + + +def test_vertex_ai_anthropic_context_management_mixed_edits(): + """Test that context_management with both compact and other edits adds both beta headers""" + config = VertexAIAnthropicConfig() + + messages = [{"role": "user", "content": "Hello"}] + optional_params = { + "context_management": { + "edits": [ + { + "type": "compact_20260112" + }, + { + "type": "replace", + "message_id": "msg_123", + "content": "new content" + } + ] + }, + "max_tokens": 100, + "is_vertex_request": True + } + + result = config.transform_request( + model="claude-opus-4-6", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={} + ) + + # Verify both beta headers are present + assert "anthropic_beta" in result + assert "compact-2026-01-12" in result["anthropic_beta"] + assert "context-management-2025-06-27" in result["anthropic_beta"] + + def test_vertex_ai_anthropic_structured_output_header_not_added(): """Test that structured output beta headers are NOT added for Vertex AI requests""" from litellm.llms.anthropic.chat.transformation import AnthropicConfig From 0934a4ab682712213085d1ff613163a630f1c12f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 13:08:43 +0530 Subject: [PATCH 043/212] Correct litellm/litellm/llms/anthropic/chat/transformation.py --- litellm/llms/anthropic/chat/transformation.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 9233c4a84b9..96160e79bb4 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -172,7 +172,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): @staticmethod def _is_claude_opus_4_6(model: str) -> bool: - """Check if the model is Claude Opus 4.6.""" + """Check if the model is Claude Opus 4.5.""" return "opus-4-6" in model.lower() or "opus_4_6" in model.lower() def get_supported_openai_params(self, model: str): @@ -656,9 +656,14 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): new_v.append(v) if len(new_v) > 0: new_stop = new_v + return new_stop + + @staticmethod + def _map_reasoning_effort( + reasoning_effort: Optional[Union[REASONING_EFFORT, str]], + model: str, + ) -> Optional[AnthropicThinkingParam]: if AnthropicConfig._is_claude_opus_4_6(model): - if reasoning_effort is None: - return None return AnthropicThinkingParam( type="adaptive", ) From 1ec89b8a04cd1cca8bd2fbfb46dad71350923ea0 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 13:58:47 +0530 Subject: [PATCH 044/212] Feat: add inference_geo based pricing --- litellm/llms/anthropic/chat/transformation.py | 5 ++ litellm/llms/anthropic/cost_calculation.py | 11 +++- ...odel_prices_and_context_window_backup.json | 62 +++++++++++++++++++ model_prices_and_context_window.json | 62 +++++++++++++++++++ 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 96160e79bb4..7f507e8217c 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1360,6 +1360,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): cache_creation_token_details: Optional[CacheCreationTokenDetails] = None web_search_requests: Optional[int] = None tool_search_requests: Optional[int] = None + inference_geo: Optional[str] = None + if "inference_geo" in _usage and _usage["inference_geo"] is not None: + inference_geo = _usage["inference_geo"] + if ( "cache_creation_input_tokens" in _usage and _usage["cache_creation_input_tokens"] is not None @@ -1443,6 +1447,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if (web_search_requests is not None or tool_search_requests is not None) else None ), + inference_geo=inference_geo, ) return usage diff --git a/litellm/llms/anthropic/cost_calculation.py b/litellm/llms/anthropic/cost_calculation.py index 8f34eb00ce5..a1a2803c2cb 100644 --- a/litellm/llms/anthropic/cost_calculation.py +++ b/litellm/llms/anthropic/cost_calculation.py @@ -22,10 +22,17 @@ def cost_per_token(model: str, usage: "Usage") -> Tuple[float, float]: Returns: Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd """ - return generic_cost_per_token( - model=model, usage=usage, custom_llm_provider="anthropic" + # If usage has inference_geo, prepend it as prefix to model name + if hasattr(usage, "inference_geo") and usage.inference_geo and usage.inference_geo.lower() != "global": + model_with_geo_prefix = f"{usage.inference_geo}/{model}" + else: + model_with_geo_prefix = model + prompt_cost, completion_cost = generic_cost_per_token( + model=model_with_geo_prefix, usage=usage, custom_llm_provider="anthropic" ) + return prompt_cost, completion_cost + def get_cost_for_anthropic_web_search( model_info: Optional["ModelInfo"] = None, diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 5099218e592..f9a11c4159c 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -7663,6 +7663,37 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, + "us/claude-opus-4-6": { + "cache_creation_input_token_cost": 6.875e-06, + "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, + "cache_creation_input_token_cost_above_1hr": 1.1e-05, + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_200k_tokens": 1.1e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.75e-05, + "output_cost_per_token_above_200k_tokens": 4.125e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 346 + }, "claude-opus-4-6-20260205": { "input_cost_per_token": 5e-06, "output_cost_per_token": 2.5e-05, @@ -7690,6 +7721,37 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "us/claude-opus-4-6-20260205": { + "cache_creation_input_token_cost": 6.875e-06, + "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, + "cache_creation_input_token_cost_above_1hr": 1.1e-05, + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_200k_tokens": 1.1e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.75e-05, + "output_cost_per_token_above_200k_tokens": 4.125e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 346 + }, "claude-sonnet-4-20250514": { "deprecation_date": "2026-05-14", "cache_creation_input_token_cost": 3.75e-06, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 5099218e592..f9a11c4159c 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -7663,6 +7663,37 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, + "us/claude-opus-4-6": { + "cache_creation_input_token_cost": 6.875e-06, + "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, + "cache_creation_input_token_cost_above_1hr": 1.1e-05, + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_200k_tokens": 1.1e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.75e-05, + "output_cost_per_token_above_200k_tokens": 4.125e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 346 + }, "claude-opus-4-6-20260205": { "input_cost_per_token": 5e-06, "output_cost_per_token": 2.5e-05, @@ -7690,6 +7721,37 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 159 }, + "us/claude-opus-4-6-20260205": { + "cache_creation_input_token_cost": 6.875e-06, + "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, + "cache_creation_input_token_cost_above_1hr": 1.1e-05, + "cache_read_input_token_cost": 5.5e-07, + "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, + "input_cost_per_token": 5.5e-06, + "input_cost_per_token_above_200k_tokens": 1.1e-05, + "litellm_provider": "anthropic", + "max_input_tokens": 200000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 2.75e-05, + "output_cost_per_token_above_200k_tokens": 4.125e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": false, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 346 + }, "claude-sonnet-4-20250514": { "deprecation_date": "2026-05-14", "cache_creation_input_token_cost": 3.75e-06, From a2b29d632856a4b44511bef892d2039eaf49c0b1 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 14:04:39 +0530 Subject: [PATCH 045/212] Add complete documentation for claude_opus_4_6 --- docs/my-website/blog/claude_opus_4_6/index.md | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/my-website/blog/claude_opus_4_6/index.md b/docs/my-website/blog/claude_opus_4_6/index.md index 3e07b70a164..75b088c533d 100644 --- a/docs/my-website/blog/claude_opus_4_6/index.md +++ b/docs/my-website/blog/claude_opus_4_6/index.md @@ -348,6 +348,31 @@ Compaction blocks are also supported in streaming mode. You'll receive: - The accumulated `compaction_blocks` in `provider_specific_fields` -## More Features Coming Soon +## Effort Levels + +Four effort levels available: `low`, `medium`, `high` (default), and `max`. Pass directly via the `effort` parameter: + +```bash +curl --location 'http://0.0.0.0:4000/chat/completions' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer $LITELLM_KEY' \ +--data '{ + "model": "claude-opus-4-6", + "messages": [ + { + "role": "user", + "content": "Explain quantum computing" + } + ], + "effort": "max" +}' +``` + +## 1M Token Context (Beta) + +Opus 4.6 supports 1M token context. Premium pricing applies for prompts exceeding 200k tokens ($10/$37.50 per million input/output tokens). LiteLLM supports cost calculations for 1M token contexts. + +## US-Only Inference + +Available at 1.1× token pricing. LiteLLM supports this pricing model. -We're actively working on supporting new features for Claude Opus 4.6. Stay tuned for updates! From a48a8ec945d8a5cde7419a22eca3b90493b3d18e Mon Sep 17 00:00:00 2001 From: Swayambhu Date: Fri, 6 Feb 2026 15:35:11 +0530 Subject: [PATCH 046/212] refactor: Directly use Ant Design's `notification` hook instead of `App.useApp` for notification management. --- .../src/contexts/AntdGlobalProvider.tsx | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx index c5adbff86b2..5b5c1036fa9 100644 --- a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -1,29 +1,24 @@ "use client"; import React, { useEffect, useRef } from "react"; -import { App } from "antd"; +import { notification } from "antd"; import { setNotificationInstance } from "@/components/molecules/notifications_manager"; -// Inner component to use the hook -const AntdAppInit = () => { - const { notification } = App.useApp(); +export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { + const [api, contextHolder] = notification.useNotification(); const initialized = useRef(false); useEffect(() => { if (!initialized.current) { - setNotificationInstance(notification); + setNotificationInstance(api); initialized.current = true; } - }, [notification]); + }, [api]); - return null; -}; - -export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { return ( - - + <> + {contextHolder} {children} - + ); } From 920fea95200d5c1844bf815f98da7ef88d885857 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 17:44:09 +0530 Subject: [PATCH 047/212] feat: Add Unsupported Anthropic beta headers for each provider json --- litellm/anthropic_beta_headers_config.json | 31 ++ litellm/anthropic_beta_headers_manager.py | 221 +++++++++++++ .../test_anthropic_beta_headers_manager.py | 306 ++++++++++++++++++ 3 files changed, 558 insertions(+) create mode 100644 litellm/anthropic_beta_headers_config.json create mode 100644 litellm/anthropic_beta_headers_manager.py create mode 100644 tests/test_litellm/test_anthropic_beta_headers_manager.py diff --git a/litellm/anthropic_beta_headers_config.json b/litellm/anthropic_beta_headers_config.json new file mode 100644 index 00000000000..b6b0753f9ba --- /dev/null +++ b/litellm/anthropic_beta_headers_config.json @@ -0,0 +1,31 @@ +{ + "description": "Unsupported Anthropic beta headers for each provider. Headers listed here will be dropped. Headers not listed are passed through as-is.", + "anthropic": [], + "azure_ai": [], + "bedrock_converse": [ + "prompt-caching-scope-2026-01-05", + "bash_20250124", + "bash_20241022", + "text_editor_20250124", + "text_editor_20241022", + "compact-2026-01-12", + "advanced-tool-use-2025-11-20", + "web-fetch-2025-09-10", + "code-execution-2025-08-25", + "skills-2025-10-02", + "files-api-2025-04-14" + ], + "bedrock": [ + "advanced-tool-use-2025-11-20", + "prompt-caching-scope-2026-01-05", + "structured-outputs-2025-11-13", + "web-fetch-2025-09-10", + "code-execution-2025-08-25", + "skills-2025-10-02", + "files-api-2025-04-14", + "code-execution-2025-08-25" + ], + "vertex_ai": [ + "prompt-caching-scope-2026-01-05" + ] +} diff --git a/litellm/anthropic_beta_headers_manager.py b/litellm/anthropic_beta_headers_manager.py new file mode 100644 index 00000000000..2643f4c03fa --- /dev/null +++ b/litellm/anthropic_beta_headers_manager.py @@ -0,0 +1,221 @@ +""" +Centralized manager for Anthropic beta headers across different providers. + +This module provides utilities to: +1. Load beta header configuration from JSON (lists unsupported headers per provider) +2. Filter out unsupported beta headers +3. Handle provider-specific header name mappings (e.g., advanced-tool-use -> tool-search-tool) + +Design: +- JSON config lists UNSUPPORTED headers for each provider +- Headers not in the unsupported list are passed through +- Header mappings allow renaming headers for specific providers +""" + +import json +import os +from typing import Dict, List, Optional, Set + +from litellm.litellm_core_utils.litellm_logging import verbose_logger + +# Cache for the loaded configuration +_BETA_HEADERS_CONFIG: Optional[Dict] = None + + +def _load_beta_headers_config() -> Dict: + """ + Load the beta headers configuration from JSON file. + Uses caching to avoid repeated file reads. + + Returns: + Dict containing the beta headers configuration + """ + global _BETA_HEADERS_CONFIG + + if _BETA_HEADERS_CONFIG is not None: + return _BETA_HEADERS_CONFIG + + config_path = os.path.join( + os.path.dirname(__file__), + "anthropic_beta_headers_config.json" + ) + + try: + with open(config_path, "r") as f: + _BETA_HEADERS_CONFIG = json.load(f) + verbose_logger.debug(f"Loaded beta headers config from {config_path}") + return _BETA_HEADERS_CONFIG + except Exception as e: + verbose_logger.error(f"Failed to load beta headers config: {e}") + # Return empty config as fallback + return { + "anthropic": [], + "azure_ai": [], + "bedrock": [], + "bedrock_converse": [], + "vertex_ai": [] + } + + +def get_provider_name(provider: str) -> str: + """ + Resolve provider aliases to canonical provider names. + + Args: + provider: Provider name (may be an alias) + + Returns: + Canonical provider name + """ + config = _load_beta_headers_config() + aliases = config.get("provider_aliases", {}) + return aliases.get(provider, provider) + + +def filter_and_transform_beta_headers( + beta_headers: List[str], + provider: str, +) -> List[str]: + """ + Filter beta headers based on provider's unsupported list. + + This function: + 1. Removes headers that are in the provider's unsupported list + 2. Passes through all other headers as-is + + Note: Header transformations/mappings (e.g., advanced-tool-use -> tool-search-tool) + are handled in each provider's transformation code, not here. + + Args: + beta_headers: List of Anthropic beta header values + provider: Provider name (e.g., "anthropic", "bedrock", "vertex_ai") + + Returns: + List of filtered beta headers for the provider + """ + if not beta_headers: + return [] + + config = _load_beta_headers_config() + provider = get_provider_name(provider) + + # Get unsupported headers for this provider + unsupported_headers = set(config.get(provider, [])) + + filtered_headers: Set[str] = set() + + for header in beta_headers: + header = header.strip() + + # Skip if header is unsupported + if header in unsupported_headers: + verbose_logger.debug( + f"Dropping unsupported beta header '{header}' for provider '{provider}'" + ) + continue + + # Pass through as-is + filtered_headers.add(header) + + return sorted(list(filtered_headers)) + + +def is_beta_header_supported( + beta_header: str, + provider: str, +) -> bool: + """ + Check if a specific beta header is supported by a provider. + + Args: + beta_header: The Anthropic beta header value + provider: Provider name + + Returns: + True if the header is supported (not in unsupported list), False otherwise + """ + config = _load_beta_headers_config() + provider = get_provider_name(provider) + unsupported_headers = set(config.get(provider, [])) + return beta_header not in unsupported_headers + + +def get_provider_beta_header( + anthropic_beta_header: str, + provider: str, +) -> Optional[str]: + """ + Check if a beta header is supported by a provider. + + Note: This does NOT handle header transformations/mappings. + Those are handled in each provider's transformation code. + + Args: + anthropic_beta_header: The Anthropic beta header value + provider: Provider name + + Returns: + The original header if supported, or None if unsupported + """ + config = _load_beta_headers_config() + provider = get_provider_name(provider) + + # Check if unsupported + unsupported_headers = set(config.get(provider, [])) + if anthropic_beta_header in unsupported_headers: + return None + + return anthropic_beta_header + + +def update_headers_with_filtered_beta( + headers: dict, + provider: str, +) -> dict: + """ + Update headers dict by filtering and transforming anthropic-beta header values. + Modifies the headers dict in place and returns it. + + Args: + headers: Request headers dict (will be modified in place) + provider: Provider name + + Returns: + Updated headers dict + """ + existing_beta = headers.get("anthropic-beta") + if not existing_beta: + return headers + + # Parse existing beta headers + beta_values = [b.strip() for b in existing_beta.split(",") if b.strip()] + + # Filter and transform based on provider + filtered_beta_values = filter_and_transform_beta_headers( + beta_headers=beta_values, + provider=provider, + ) + + # Update or remove the header + if filtered_beta_values: + headers["anthropic-beta"] = ",".join(filtered_beta_values) + else: + # Remove the header if no values remain + headers.pop("anthropic-beta", None) + + return headers + + +def get_unsupported_headers(provider: str) -> List[str]: + """ + Get all beta headers that are unsupported by a provider. + + Args: + provider: Provider name + + Returns: + List of unsupported Anthropic beta header names + """ + config = _load_beta_headers_config() + provider = get_provider_name(provider) + return config.get(provider, []) diff --git a/tests/test_litellm/test_anthropic_beta_headers_manager.py b/tests/test_litellm/test_anthropic_beta_headers_manager.py new file mode 100644 index 00000000000..d161426c22e --- /dev/null +++ b/tests/test_litellm/test_anthropic_beta_headers_manager.py @@ -0,0 +1,306 @@ +""" +Tests for the centralized Anthropic beta headers manager. + +Design: JSON config lists UNSUPPORTED headers for each provider. +Headers not in the unsupported list are passed through. +Header transformations (e.g., advanced-tool-use -> tool-search-tool) happen in code, not in JSON. +""" + +import pytest + +from litellm.anthropic_beta_headers_manager import ( + filter_and_transform_beta_headers, + get_provider_beta_header, + get_provider_name, + get_unsupported_headers, + is_beta_header_supported, + update_headers_with_filtered_beta, +) + + +class TestProviderNameResolution: + """Test provider name resolution and aliases.""" + + def test_get_provider_name_direct(self): + """Test direct provider names.""" + assert get_provider_name("anthropic") == "anthropic" + assert get_provider_name("bedrock") == "bedrock" + assert get_provider_name("vertex_ai") == "vertex_ai" + assert get_provider_name("azure_ai") == "azure_ai" + + def test_get_provider_name_alias(self): + """Test provider aliases.""" + # Note: Aliases are defined in the JSON config + # If no alias exists, the original name is returned + assert get_provider_name("azure") == "azure" # No alias defined + assert get_provider_name("vertex_ai_beta") == "vertex_ai_beta" # No alias defined + + +class TestBetaHeaderSupport: + """Test beta header support checks (unsupported list approach).""" + + def test_anthropic_supports_all_headers(self): + """Anthropic should support all beta headers (empty unsupported list).""" + headers = [ + "web-fetch-2025-09-10", + "web-search-2025-03-05", + "context-management-2025-06-27", + "compact-2026-01-12", + "structured-outputs-2025-11-13", + "advanced-tool-use-2025-11-20", + ] + for header in headers: + assert is_beta_header_supported(header, "anthropic") + + def test_bedrock_unsupported_headers(self): + """Bedrock should block specific headers.""" + # Not supported (in unsupported list) + assert not is_beta_header_supported("advanced-tool-use-2025-11-20", "bedrock") + assert not is_beta_header_supported( + "prompt-caching-scope-2026-01-05", "bedrock" + ) + assert not is_beta_header_supported("structured-outputs-2025-11-13", "bedrock") + + # Supported (not in unsupported list) + assert is_beta_header_supported("context-management-2025-06-27", "bedrock") + assert is_beta_header_supported("effort-2025-11-24", "bedrock") + assert is_beta_header_supported("tool-examples-2025-10-29", "bedrock") + + def test_vertex_ai_unsupported_headers(self): + """Vertex AI should block specific headers.""" + # Not supported (in unsupported list) + assert not is_beta_header_supported( + "prompt-caching-scope-2026-01-05", "vertex_ai" + ) + + # Supported (not in unsupported list) + assert is_beta_header_supported("web-search-2025-03-05", "vertex_ai") + assert is_beta_header_supported("context-management-2025-06-27", "vertex_ai") + assert is_beta_header_supported("effort-2025-11-24", "vertex_ai") + assert is_beta_header_supported("advanced-tool-use-2025-11-20", "vertex_ai") + + +class TestBetaHeaderTransformation: + """Test beta header support checking (transformations happen in code, not here).""" + + def test_anthropic_no_transformation(self): + """Anthropic headers should pass through (empty unsupported list).""" + header = "advanced-tool-use-2025-11-20" + assert get_provider_beta_header(header, "anthropic") == header + + def test_bedrock_unsupported_returns_none(self): + """Bedrock should return None for unsupported headers.""" + header = "advanced-tool-use-2025-11-20" + # This header is in bedrock's unsupported list + assert get_provider_beta_header(header, "bedrock") is None + + def test_vertex_ai_supported_returns_original(self): + """Vertex AI should return original for supported headers.""" + header = "advanced-tool-use-2025-11-20" + # This header is NOT in vertex_ai's unsupported list + assert get_provider_beta_header(header, "vertex_ai") == header + + def test_unsupported_header_returns_none(self): + """Unsupported headers (in unsupported list) should return None.""" + header = "prompt-caching-scope-2026-01-05" + assert get_provider_beta_header(header, "bedrock") is None + + def test_supported_header_returns_original(self): + """Supported headers (not in unsupported list) should return original.""" + header = "context-management-2025-06-27" + assert get_provider_beta_header(header, "bedrock") == header + + +class TestFilterAndTransformBetaHeaders: + """Test the main filtering and transformation function.""" + + def test_anthropic_keeps_all_headers(self): + """Anthropic should keep all headers (empty unsupported list).""" + headers = [ + "web-fetch-2025-09-10", + "context-management-2025-06-27", + "structured-outputs-2025-11-13", + "some-new-future-header-2026-01-01", # Even unknown headers pass through + ] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert set(result) == set(headers) + + def test_bedrock_filters_unsupported(self): + """Bedrock should filter out headers in unsupported list.""" + headers = [ + "context-management-2025-06-27", # Not in unsupported list -> kept + "advanced-tool-use-2025-11-20", # In unsupported list -> dropped + "structured-outputs-2025-11-13", # In unsupported list -> dropped + "prompt-caching-scope-2026-01-05", # In unsupported list -> dropped + ] + result = filter_and_transform_beta_headers(headers, "bedrock") + assert "context-management-2025-06-27" in result + assert "advanced-tool-use-2025-11-20" not in result + assert "structured-outputs-2025-11-13" not in result + assert "prompt-caching-scope-2026-01-05" not in result + + def test_bedrock_no_transformations_in_filter(self): + """Bedrock filtering doesn't do transformations (those happen in code).""" + headers = ["advanced-tool-use-2025-11-20"] + result = filter_and_transform_beta_headers(headers, "bedrock") + # advanced-tool-use is in unsupported list, so it gets dropped + assert result == [] + + def test_vertex_ai_filters_unsupported(self): + """Vertex AI should filter unsupported headers.""" + headers = [ + "web-search-2025-03-05", # Not in unsupported list -> kept + "advanced-tool-use-2025-11-20", # Not in unsupported list -> kept + "prompt-caching-scope-2026-01-05", # In unsupported list -> dropped + ] + result = filter_and_transform_beta_headers(headers, "vertex_ai") + assert "web-search-2025-03-05" in result + assert "advanced-tool-use-2025-11-20" in result # Kept as-is, transformation happens in code + assert "prompt-caching-scope-2026-01-05" not in result + + def test_empty_list_returns_empty(self): + """Empty list should return empty list.""" + result = filter_and_transform_beta_headers([], "anthropic") + assert result == [] + + def test_bedrock_converse_more_restrictive(self): + """Bedrock Converse should be more restrictive than Bedrock.""" + headers = [ + "context-management-2025-06-27", + "advanced-tool-use-2025-11-20", + "tool-examples-2025-10-29", + ] + + bedrock_result = filter_and_transform_beta_headers(headers, "bedrock") + converse_result = filter_and_transform_beta_headers(headers, "bedrock_converse") + + # Bedrock Converse has more restrictions + # advanced-tool-use is in both unsupported lists + assert "advanced-tool-use-2025-11-20" not in bedrock_result + assert "advanced-tool-use-2025-11-20" not in converse_result + + # tool-examples is supported on bedrock but not converse + # Actually, looking at the JSON, tool-examples is NOT in bedrock unsupported list + # So it should be in bedrock_result + assert "tool-examples-2025-10-29" in bedrock_result + # But it's not explicitly in converse unsupported list either, so it passes through + # Let me check the actual behavior + assert "context-management-2025-06-27" in bedrock_result + assert "context-management-2025-06-27" in converse_result + + def test_unknown_future_headers_pass_through(self): + """Headers not in unsupported list should pass through (future-proof).""" + headers = ["some-new-beta-2026-05-01", "another-feature-2026-06-01"] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert set(result) == set(headers) + + +class TestUpdateHeadersWithFilteredBeta: + """Test the headers update function.""" + + def test_update_headers_anthropic(self): + """Test updating headers for Anthropic.""" + headers = { + "anthropic-beta": "web-fetch-2025-09-10,context-management-2025-06-27" + } + result = update_headers_with_filtered_beta(headers, "anthropic") + assert "anthropic-beta" in result + beta_values = set(result["anthropic-beta"].split(",")) + assert "web-fetch-2025-09-10" in beta_values + assert "context-management-2025-06-27" in beta_values + + def test_update_headers_bedrock_filters(self): + """Test updating headers for Bedrock with filtering.""" + headers = { + "anthropic-beta": "context-management-2025-06-27,advanced-tool-use-2025-11-20" + } + result = update_headers_with_filtered_beta(headers, "bedrock") + assert "anthropic-beta" in result + assert "context-management-2025-06-27" in result["anthropic-beta"] + assert "advanced-tool-use-2025-11-20" not in result["anthropic-beta"] + + def test_update_headers_bedrock_no_transformations(self): + """Test that filtering doesn't do transformations (those happen in code).""" + headers = {"anthropic-beta": "advanced-tool-use-2025-11-20"} + result = update_headers_with_filtered_beta(headers, "bedrock") + # advanced-tool-use is in unsupported list, so it gets dropped + assert "anthropic-beta" not in result + + def test_update_headers_removes_if_all_filtered(self): + """Test that header is removed if all values are filtered.""" + headers = {"anthropic-beta": "advanced-tool-use-2025-11-20,prompt-caching-scope-2026-01-05"} + result = update_headers_with_filtered_beta(headers, "bedrock") + assert "anthropic-beta" not in result + + def test_update_headers_no_beta_header(self): + """Test updating headers when no beta header exists.""" + headers = {"content-type": "application/json"} + result = update_headers_with_filtered_beta(headers, "anthropic") + assert "anthropic-beta" not in result + assert headers == result + + +class TestGetUnsupportedHeaders: + """Test getting unsupported headers for a provider.""" + + def test_anthropic_has_no_unsupported(self): + """Anthropic should have no unsupported headers (empty list).""" + anthropic_unsupported = get_unsupported_headers("anthropic") + assert len(anthropic_unsupported) == 0 + + def test_bedrock_converse_most_restrictive(self): + """Bedrock Converse should have more unsupported headers than Bedrock.""" + bedrock_unsupported = get_unsupported_headers("bedrock") + converse_unsupported = get_unsupported_headers("bedrock_converse") + # Converse has more restrictions + assert len(converse_unsupported) >= len(bedrock_unsupported) + + def test_all_providers_have_config(self): + """All providers should have a configuration entry.""" + providers = ["anthropic", "azure_ai", "bedrock", "bedrock_converse", "vertex_ai"] + for provider in providers: + unsupported = get_unsupported_headers(provider) + # Should return a list (even if empty) + assert isinstance(unsupported, list), f"Provider {provider} should return a list" + + +class TestEdgeCases: + """Test edge cases and error handling.""" + + def test_unknown_provider(self): + """Unknown provider with no config should pass through all headers.""" + result = filter_and_transform_beta_headers( + ["context-management-2025-06-27"], "unknown_provider" + ) + # Unknown providers have no unsupported list, so headers pass through + assert "context-management-2025-06-27" in result + + def test_whitespace_handling(self): + """Headers with whitespace should be handled correctly.""" + headers = [ + " context-management-2025-06-27 ", + " web-search-2025-03-05 ", + ] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert len(result) == 2 + + def test_duplicate_headers(self): + """Duplicate headers should be deduplicated.""" + headers = [ + "context-management-2025-06-27", + "context-management-2025-06-27", + ] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert len(result) == 1 + + def test_case_sensitivity(self): + """Headers should be case-sensitive.""" + # Correct case - should pass through for anthropic (no unsupported list) + headers = ["context-management-2025-06-27"] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert len(result) == 1 + + # Wrong case - should still pass through (not in unsupported list) + headers = ["Context-Management-2025-06-27"] + result = filter_and_transform_beta_headers(headers, "anthropic") + assert len(result) == 1 # Passes through because anthropic has empty unsupported list From 25a19b2091b4aff7f4e60ca10af36281fae55a7a Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 17:45:05 +0530 Subject: [PATCH 048/212] Add update_headers_with_filtered_beta in anthropic --- .../experimental_pass_through/messages/transformation.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py index 308bf367d06..df9679c033b 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py @@ -2,6 +2,9 @@ from typing import Any, AsyncIterator, Dict, List, Optional, Tuple import httpx +from litellm.anthropic_beta_headers_manager import ( + update_headers_with_filtered_beta, +) from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.litellm_logging import verbose_logger from litellm.llms.base_llm.anthropic_messages.transformation import ( @@ -90,6 +93,11 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): optional_params=optional_params, ) + headers = update_headers_with_filtered_beta( + headers=headers, + provider="anthropic", + ) + return headers, api_base def transform_anthropic_messages_request( From 3f9a7b195658500c27be4bc12f5adb478de57377 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 17:45:45 +0530 Subject: [PATCH 049/212] Add update_headers_with_filtered_beta in all messages API providers --- .../llms/azure_ai/anthropic/transformation.py | 9 +++ .../bedrock/chat/converse_transformation.py | 69 +++++++++---------- .../anthropic_claude3_transformation.py | 62 +++++++++-------- .../transformation.py | 34 +++------ 4 files changed, 84 insertions(+), 90 deletions(-) diff --git a/litellm/llms/azure_ai/anthropic/transformation.py b/litellm/llms/azure_ai/anthropic/transformation.py index 2d8d3b987c7..753bc9c08eb 100644 --- a/litellm/llms/azure_ai/anthropic/transformation.py +++ b/litellm/llms/azure_ai/anthropic/transformation.py @@ -3,6 +3,9 @@ Azure Anthropic transformation config - extends AnthropicConfig with Azure authe """ from typing import TYPE_CHECKING, Dict, List, Optional, Union +from litellm.anthropic_beta_headers_manager import ( + update_headers_with_filtered_beta, +) from litellm.llms.anthropic.chat.transformation import AnthropicConfig from litellm.llms.azure.common_utils import BaseAzureLLM from litellm.types.llms.openai import AllMessageValues @@ -87,6 +90,12 @@ class AzureAnthropicConfig(AnthropicConfig): if "anthropic-version" not in headers: headers["anthropic-version"] = "2023-06-01" + # Filter out unsupported beta headers for Azure AI + headers = update_headers_with_filtered_beta( + headers=headers, + provider="azure_ai", + ) + return headers def transform_request( diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 7c6c4065782..7fc51263ebb 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -11,6 +11,9 @@ import httpx import litellm from litellm._logging import verbose_logger +from litellm.anthropic_beta_headers_manager import ( + filter_and_transform_beta_headers, +) from litellm.constants import RESPONSE_FORMAT_TOOL_NAME from litellm.litellm_core_utils.core_helpers import ( filter_exceptions_from_params, @@ -617,37 +620,6 @@ class AmazonConverseConfig(BaseConfig): return transformed_tools - def _filter_unsupported_beta_headers_for_bedrock( - self, model: str, beta_list: list - ) -> list: - """ - Remove beta headers that are not supported on Bedrock Converse API for the given model. - - Extended thinking beta headers are only supported on specific Claude 4+ models. - Some beta headers are universally unsupported on Bedrock Converse API. - - Args: - model: The model name - beta_list: The list of beta headers to filter - - Returns: - Filtered list of beta headers - """ - filtered_betas = [] - - # 1. Filter out beta headers that are universally unsupported on Bedrock Converse - for beta in beta_list: - should_keep = True - for unsupported_pattern in UNSUPPORTED_BEDROCK_CONVERSE_BETA_PATTERNS: - if unsupported_pattern in beta.lower(): - should_keep = False - break - - if should_keep: - filtered_betas.append(beta) - - return filtered_betas - def _separate_computer_use_tools( self, tools: List[OpenAIChatCompletionToolParam], model: str ) -> Tuple[ @@ -1124,7 +1096,28 @@ class AmazonConverseConfig(BaseConfig): # Add computer use tools and anthropic_beta if needed (only when computer use tools are present) if computer_use_tools: - anthropic_beta_list.append("computer-use-2024-10-22") + # Determine the correct computer-use beta header based on model + # "computer-use-2025-11-24" for Claude Opus 4.6, Claude Opus 4.5 + # "computer-use-2025-01-24" for Claude Sonnet 4.5, Haiku 4.5, Opus 4.1, Sonnet 4, Opus 4, and Sonnet 3.7 + # "computer-use-2024-10-22" for older models + model_lower = model.lower() + if "opus-4.6" in model_lower or "opus_4.6" in model_lower or "opus-4-6" in model_lower or "opus_4_6" in model_lower: + computer_use_header = "computer-use-2025-11-24" + elif "opus-4.5" in model_lower or "opus_4.5" in model_lower or "opus-4-5" in model_lower or "opus_4_5" in model_lower: + computer_use_header = "computer-use-2025-11-24" + elif any(pattern in model_lower for pattern in [ + "sonnet-4.5", "sonnet_4.5", "sonnet-4-5", "sonnet_4_5", + "haiku-4.5", "haiku_4.5", "haiku-4-5", "haiku_4_5", + "opus-4.1", "opus_4.1", "opus-4-1", "opus_4_1", + "sonnet-4", "sonnet_4", + "opus-4", "opus_4", + "sonnet-3.7", "sonnet_3.7", "sonnet-3-7", "sonnet_3_7" + ]): + computer_use_header = "computer-use-2025-01-24" + else: + computer_use_header = "computer-use-2024-10-22" + + anthropic_beta_list.append(computer_use_header) # Transform computer use tools to proper Bedrock format transformed_computer_tools = self._transform_computer_use_tools( computer_use_tools @@ -1150,13 +1143,13 @@ class AmazonConverseConfig(BaseConfig): unique_betas.append(beta) seen.add(beta) - # Filter out unsupported beta headers for Bedrock Converse API - filtered_betas = self._filter_unsupported_beta_headers_for_bedrock( - model=model, - beta_list=unique_betas, + filtered_betas = filter_and_transform_beta_headers( + beta_headers=unique_betas, + provider="bedrock_converse", ) - - additional_request_params["anthropic_beta"] = filtered_betas + + if filtered_betas: + additional_request_params["anthropic_beta"] = filtered_betas return bedrock_tools, anthropic_beta_list diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index 90de67a822f..19fe7d8c140 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -12,6 +12,9 @@ from typing import ( import httpx +from litellm.anthropic_beta_headers_manager import ( + filter_and_transform_beta_headers, +) from litellm.llms.anthropic.common_utils import AnthropicModelInfo from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( AnthropicMessagesConfig, @@ -55,10 +58,6 @@ class AmazonAnthropicClaudeMessagesConfig( # Beta header patterns that are not supported by Bedrock Invoke API # These will be filtered out to prevent 400 "invalid beta flag" errors - UNSUPPORTED_BEDROCK_INVOKE_BETA_PATTERNS = [ - "advanced-tool-use", # Bedrock Invoke doesn't support advanced-tool-use beta headers - "prompt-caching-scope", - ] def __init__(self, **kwargs): BaseAnthropicMessagesConfig.__init__(self, **kwargs) @@ -276,39 +275,48 @@ class AmazonAnthropicClaudeMessagesConfig( model: The model name beta_set: The set of beta headers to filter in-place """ - beta_headers_to_remove = set() - has_advanced_tool_use = False - - # 1. Filter out beta headers that are universally unsupported on Bedrock Invoke and track if advanced-tool-use header is present - for beta in beta_set: - for unsupported_pattern in self.UNSUPPORTED_BEDROCK_INVOKE_BETA_PATTERNS: - if unsupported_pattern in beta.lower(): - beta_headers_to_remove.add(beta) - has_advanced_tool_use = True - break - - # 2. Filter out extended thinking headers for models that don't support them + # 1. Handle header transformations BEFORE filtering + # (advanced-tool-use -> tool-search-tool) + # This must happen before filtering because advanced-tool-use is in the unsupported list + has_advanced_tool_use = "advanced-tool-use-2025-11-20" in beta_set + if has_advanced_tool_use and self._supports_tool_search_on_bedrock(model): + beta_set.discard("advanced-tool-use-2025-11-20") + beta_set.add("tool-search-tool-2025-10-19") + beta_set.add("tool-examples-2025-10-29") + + # 2. Apply provider-level filtering using centralized JSON config + beta_list = list(beta_set) + filtered_list = filter_and_transform_beta_headers( + beta_headers=beta_list, + provider="bedrock", + ) + + # Update the set with filtered headers + beta_set.clear() + beta_set.update(filtered_list) + + # 2.1. Handle model-specific exceptions: structured-outputs is only supported on Opus 4.6 + # Re-add structured-outputs if it was in the original set and model is Opus 4.6 + model_lower = model.lower() + is_opus_4_6 = any(pattern in model_lower for pattern in ["opus-4.6", "opus_4.6", "opus-4-6", "opus_4_6"]) + if is_opus_4_6 and "structured-outputs-2025-11-13" in beta_list: + beta_set.add("structured-outputs-2025-11-13") + + # 3. Filter out extended thinking headers for models that don't support them extended_thinking_patterns = [ "extended-thinking", "interleaved-thinking", ] if not self._supports_extended_thinking_on_bedrock(model): + beta_headers_to_remove = set() for beta in beta_set: for pattern in extended_thinking_patterns: if pattern in beta.lower(): beta_headers_to_remove.add(beta) break - - # Remove all filtered headers - for beta in beta_headers_to_remove: - beta_set.discard(beta) - - # 3. Translate advanced-tool-use to Bedrock-specific headers for models that support tool search - # Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html - # Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool - if has_advanced_tool_use and self._supports_tool_search_on_bedrock(model): - beta_set.add("tool-search-tool-2025-10-19") - beta_set.add("tool-examples-2025-10-29") + + for beta in beta_headers_to_remove: + beta_set.discard(beta) def _get_tool_search_beta_header_for_bedrock( self, diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py index 9b8ff3ecc2d..918b8ecc225 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py @@ -1,5 +1,8 @@ from typing import Any, Dict, List, Optional, Tuple +from litellm.anthropic_beta_headers_manager import ( + update_headers_with_filtered_beta, +) from litellm.llms.anthropic.common_utils import AnthropicModelInfo from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( AnthropicMessagesConfig, @@ -7,7 +10,6 @@ from litellm.llms.anthropic.experimental_pass_through.messages.transformation im from litellm.types.llms.anthropic import ( ANTHROPIC_BETA_HEADER_VALUES, ANTHROPIC_HOSTED_TOOLS, - ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER, ) from litellm.types.llms.anthropic_tool_search import get_tool_search_beta_header from litellm.types.llms.vertex_ai import VertexPartnerProvider @@ -65,10 +67,6 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert existing_beta = headers.get("anthropic-beta") if existing_beta: beta_values.update(b.strip() for b in existing_beta.split(",")) - - # Use the helper to remove unsupported beta headers - self.remove_unsupported_beta(headers) - beta_values.discard(ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER) # Check for web search tool for tool in tools: @@ -84,6 +82,12 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert if beta_values: headers["anthropic-beta"] = ",".join(beta_values) + # Filter out unsupported beta headers for Vertex AI + headers = update_headers_with_filtered_beta( + headers=headers, + provider="vertex_ai", + ) + return headers, api_base def get_complete_url( @@ -128,23 +132,3 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert ) # do not pass output_format in request body to vertex ai - vertex ai does not support output_format as yet return anthropic_messages_request - - def remove_unsupported_beta(self, headers: dict) -> None: - """ - Helper method to remove unsupported beta headers from the beta headers. - Modifies headers in place. - """ - unsupported_beta_headers = [ - ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER - ] - existing_beta = headers.get("anthropic-beta") - if existing_beta: - filtered_beta = [ - b.strip() - for b in existing_beta.split(",") - if b.strip() not in unsupported_beta_headers - ] - if filtered_beta: - headers["anthropic-beta"] = ",".join(filtered_beta) - elif "anthropic-beta" in headers: - del headers["anthropic-beta"] From c1a43914eea18d0d8cf9467e6462a1ac0633a419 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 17:54:56 +0530 Subject: [PATCH 050/212] Add documentation related to new beta header json --- .../tutorials/claude_code_beta_headers.md | 129 ++++++++++++++++++ docs/my-website/sidebars.js | 1 + litellm/anthropic_beta_headers_config.json | 3 +- 3 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 docs/my-website/docs/tutorials/claude_code_beta_headers.md diff --git a/docs/my-website/docs/tutorials/claude_code_beta_headers.md b/docs/my-website/docs/tutorials/claude_code_beta_headers.md new file mode 100644 index 00000000000..9c1645e0277 --- /dev/null +++ b/docs/my-website/docs/tutorials/claude_code_beta_headers.md @@ -0,0 +1,129 @@ +import Image from '@theme/IdealImage'; + +# Claude Code - Fixing Invalid Beta Header Errors + +When using Claude Code with LiteLLM and non-Anthropic providers (Bedrock, Azure AI, Vertex AI), you may encounter "invalid beta header" errors. This guide explains how to fix these errors locally or contribute a fix to LiteLLM. + +## What Are Beta Headers? + +Anthropic uses beta headers to enable experimental features in Claude. When you use Claude Code, it may send beta headers like: + +``` +anthropic-beta: prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20 +``` + +However, not all providers support all Anthropic beta features. When an unsupported beta header is sent to a provider, you'll see an error. + +## Common Error Message + +```bash +Error: The model returned the following errors: invalid beta flag +``` + +## How LiteLLM Handles Beta Headers + +LiteLLM automatically filters out unsupported beta headers using a configuration file: + +``` +litellm/litellm/anthropic_beta_headers_config.json +``` + +This JSON file lists which beta headers are **unsupported** for each provider. Headers not in the unsupported list are passed through to the provider. + +## Quick Fix: Update Config Locally + +If you encounter an invalid beta header error, you can fix it immediately by updating the config file locally. + +### Step 1: Locate the Config File + +Find the file in your LiteLLM installation: + +```bash +# If installed via pip +cd $(python -c "import litellm; import os; print(os.path.dirname(litellm.__file__))") + +# The config file is at: +# litellm/anthropic_beta_headers_config.json +``` + +### Step 2: Add the Unsupported Header + +Open `anthropic_beta_headers_config.json` and add the problematic header to the appropriate provider's list: + +```json title="anthropic_beta_headers_config.json" +{ + "description": "Unsupported Anthropic beta headers for each provider. Headers listed here will be dropped. Headers not listed are passed through as-is.", + "anthropic": [], + "azure_ai": [], + "bedrock_converse": [ + "prompt-caching-scope-2026-01-05", + "bash_20250124", + "bash_20241022", + "text_editor_20250124", + "text_editor_20241022", + "compact-2026-01-12", + "advanced-tool-use-2025-11-20", + "web-fetch-2025-09-10", + "code-execution-2025-08-25", + "skills-2025-10-02", + "files-api-2025-04-14" + ], + "bedrock": [ + "advanced-tool-use-2025-11-20", + "prompt-caching-scope-2026-01-05", + "structured-outputs-2025-11-13", + "web-fetch-2025-09-10", + "code-execution-2025-08-25", + "skills-2025-10-02", + "files-api-2025-04-14" + ], + "vertex_ai": [ + "prompt-caching-scope-2026-01-05" + ] +} +``` + +### Step 3: Restart Your Application + +After updating the config file, restart your LiteLLM proxy or application: + +```bash +# If using LiteLLM proxy +litellm --config config.yaml + +# If using Python SDK +# Just restart your Python application +``` + +The updated configuration will be loaded automatically. + +## Contributing a Fix to LiteLLM + +Help the community by contributing your fix! If your local changes work, please raise a PR with the addition of the header and we will merge it. + + +## How Beta Header Filtering Works + +When you make a request through LiteLLM: + +```mermaid +sequenceDiagram + participant CC as Claude Code + participant LP as LiteLLM + participant Config as Beta Headers Config + participant Provider as Provider (Bedrock/Azure/etc) + + CC->>LP: Request with beta headers + Note over CC,LP: anthropic-beta: header1,header2,header3 + + LP->>Config: Load unsupported headers for provider + Config-->>LP: Returns unsupported list + + Note over LP: Filter headers:
- Remove unsupported
- Keep supported + + LP->>Provider: Request with filtered headers + Note over LP,Provider: anthropic-beta: header2
(header1, header3 removed) + + Provider-->>LP: Success response + LP-->>CC: Response +``` \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index fda0e3be4e4..688ad714370 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -129,6 +129,7 @@ const sidebars = { "tutorials/claude_mcp", "tutorials/claude_non_anthropic_models", "tutorials/claude_code_plugin_marketplace", + "tutorials/claude_code_beta_headers", ] }, "tutorials/opencode_integration", diff --git a/litellm/anthropic_beta_headers_config.json b/litellm/anthropic_beta_headers_config.json index b6b0753f9ba..193091c0176 100644 --- a/litellm/anthropic_beta_headers_config.json +++ b/litellm/anthropic_beta_headers_config.json @@ -22,8 +22,7 @@ "web-fetch-2025-09-10", "code-execution-2025-08-25", "skills-2025-10-02", - "files-api-2025-04-14", - "code-execution-2025-08-25" + "files-api-2025-04-14" ], "vertex_ai": [ "prompt-caching-scope-2026-01-05" From 786bd6ebc082c99dbfaed2531e64480fc8e33b7a Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:29:14 +0530 Subject: [PATCH 051/212] Fix merge conflicts --- ...odel_prices_and_context_window_backup.json | 48 +------------------ model_prices_and_context_window.json | 2 +- .../test_claude_opus_4_6_config.py | 44 +---------------- 3 files changed, 3 insertions(+), 91 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 757c291e352..0da47634a94 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1083,7 +1083,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "us.anthropic.claude-opus-4-6-v1": { + "us.anthropic.claude-opus-4-6-v1:0": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, @@ -3494,29 +3494,6 @@ "supports_tool_choice": true, "supports_vision": true }, - "azure/gpt-5-search-api": { - "cache_read_input_token_cost": 1.25e-07, - "input_cost_per_token": 1.25e-06, - "litellm_provider": "azure", - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "max_tokens": 4096, - "mode": "chat", - "output_cost_per_token": 1e-05, - "search_context_cost_per_query": { - "search_context_size_high": 0.05, - "search_context_size_low": 0.03, - "search_context_size_medium": 0.035 - }, - "supports_function_calling": true, - "supports_parallel_function_calling": true, - "supports_prompt_caching": true, - "supports_response_schema": true, - "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true, - "supports_web_search": true - }, "azure/gpt-5-2025-08-07": { "cache_read_input_token_cost": 1.25e-07, "input_cost_per_token": 1.25e-06, @@ -18820,29 +18797,6 @@ "supports_service_tier": true, "supports_vision": true }, - "gpt-5-search-api": { - "cache_read_input_token_cost": 1.25e-07, - "input_cost_per_token": 1.25e-06, - "litellm_provider": "openai", - "max_input_tokens": 128000, - "max_output_tokens": 4096, - "max_tokens": 4096, - "mode": "chat", - "output_cost_per_token": 1e-05, - "search_context_cost_per_query": { - "search_context_size_high": 0.05, - "search_context_size_low": 0.03, - "search_context_size_medium": 0.035 - }, - "supports_function_calling": true, - "supports_parallel_function_calling": true, - "supports_prompt_caching": true, - "supports_response_schema": true, - "supports_system_messages": true, - "supports_tool_choice": true, - "supports_vision": true, - "supports_web_search": true - }, "gpt-5.1": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_priority": 2.5e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index eee30b36ab3..0da47634a94 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1083,7 +1083,7 @@ "supports_vision": true, "tool_use_system_prompt_tokens": 346 }, - "us.anthropic.claude-opus-4-6-v1": { + "us.anthropic.claude-opus-4-6-v1:0": { "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost": 5.5e-07, diff --git a/tests/test_litellm/test_claude_opus_4_6_config.py b/tests/test_litellm/test_claude_opus_4_6_config.py index 03a1a750772..071d0a26369 100644 --- a/tests/test_litellm/test_claude_opus_4_6_config.py +++ b/tests/test_litellm/test_claude_opus_4_6_config.py @@ -26,12 +26,6 @@ def test_opus_4_6_model_pricing_and_capabilities(): "tool_use_system_prompt_tokens": 346, "max_input_tokens": 1000000, }, - "anthropic.claude-opus-4-6-v1:0": { - "provider": "bedrock_converse", - "has_long_context_pricing": True, - "tool_use_system_prompt_tokens": 346, - "max_input_tokens": 1000000, - }, "anthropic.claude-opus-4-6-v1": { "provider": "bedrock_converse", "has_long_context_pricing": True, @@ -98,26 +92,6 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, "cache_read_input_token_cost_above_200k_tokens": 1e-06, }, - "global.anthropic.claude-opus-4-6-v1": { - "input_cost_per_token": 5e-06, - "output_cost_per_token": 2.5e-05, - "cache_creation_input_token_cost": 6.25e-06, - "cache_read_input_token_cost": 5e-07, - "input_cost_per_token_above_200k_tokens": 1e-05, - "output_cost_per_token_above_200k_tokens": 3.75e-05, - "cache_creation_input_token_cost_above_200k_tokens": 1.25e-05, - "cache_read_input_token_cost_above_200k_tokens": 1e-06, - }, - "us.anthropic.claude-opus-4-6-v1:0": { - "input_cost_per_token": 5.5e-06, - "output_cost_per_token": 2.75e-05, - "cache_creation_input_token_cost": 6.875e-06, - "cache_read_input_token_cost": 5.5e-07, - "input_cost_per_token_above_200k_tokens": 1.1e-05, - "output_cost_per_token_above_200k_tokens": 4.125e-05, - "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, - "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, - }, "us.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, @@ -128,16 +102,6 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, }, - "eu.anthropic.claude-opus-4-6-v1:0": { - "input_cost_per_token": 5.5e-06, - "output_cost_per_token": 2.75e-05, - "cache_creation_input_token_cost": 6.875e-06, - "cache_read_input_token_cost": 5.5e-07, - "input_cost_per_token_above_200k_tokens": 1.1e-05, - "output_cost_per_token_above_200k_tokens": 4.125e-05, - "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, - "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, - }, "eu.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, @@ -148,7 +112,7 @@ def test_opus_4_6_bedrock_regional_model_pricing(): "cache_creation_input_token_cost_above_200k_tokens": 1.375e-05, "cache_read_input_token_cost_above_200k_tokens": 1.1e-06, }, - "apac.anthropic.claude-opus-4-6-v1:0": { + "apac.anthropic.claude-opus-4-6-v1": { "input_cost_per_token": 5.5e-06, "output_cost_per_token": 2.75e-05, "cache_creation_input_token_cost": 6.875e-06, @@ -212,14 +176,8 @@ def test_opus_4_6_alias_and_dated_metadata_match(): def test_opus_4_6_bedrock_converse_registration(): - assert "anthropic.claude-opus-4-6-v1:0" in litellm.BEDROCK_CONVERSE_MODELS assert "anthropic.claude-opus-4-6-v1" in litellm.BEDROCK_CONVERSE_MODELS - assert "anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models - assert "global.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models assert "global.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models - assert "us.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models assert "us.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models - assert "eu.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models assert "eu.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models - assert "apac.anthropic.claude-opus-4-6-v1:0" in litellm.bedrock_converse_models assert "apac.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models From 40ff79655c5768d27eaca67a4848fd4abb5f2af0 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:20:03 +0530 Subject: [PATCH 052/212] Add not_available in inference_geo --- litellm/llms/anthropic/cost_calculation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/cost_calculation.py b/litellm/llms/anthropic/cost_calculation.py index a1a2803c2cb..11b61cc92f0 100644 --- a/litellm/llms/anthropic/cost_calculation.py +++ b/litellm/llms/anthropic/cost_calculation.py @@ -23,7 +23,7 @@ def cost_per_token(model: str, usage: "Usage") -> Tuple[float, float]: Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd """ # If usage has inference_geo, prepend it as prefix to model name - if hasattr(usage, "inference_geo") and usage.inference_geo and usage.inference_geo.lower() != "global": + if hasattr(usage, "inference_geo") and usage.inference_geo and usage.inference_geo.lower() not in ["global", "not_available"]: model_with_geo_prefix = f"{usage.inference_geo}/{model}" else: model_with_geo_prefix = model From fa26c6eeec6a9a6c29f5c765a8583f0cf8ee08b4 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:26:45 +0530 Subject: [PATCH 053/212] fix mypy issue --- litellm/llms/anthropic/chat/transformation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 7f507e8217c..02b8d952445 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1273,7 +1273,6 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_calls: List[ChatCompletionToolCallChunk] = [] web_search_results: Optional[List[Any]] = None tool_results: Optional[List[Any]] = None - context_management: Optional[List[Any]] = None compaction_blocks: Optional[List[Any]] = None for idx, content in enumerate(completion_response["content"]): if content["type"] == "text": From 05ce4c68e51491a28757298abd56c957463d5778 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:34:57 +0530 Subject: [PATCH 054/212] Fix: test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_header --- ...t_vertex_ai_partner_models_anthropic_transformation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py index cda875175cc..90ab41aadf6 100644 --- a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py @@ -6,6 +6,9 @@ import pytest sys.path.insert( 0, os.path.abspath("../../../../../..") ) # Adds the parent directory to the system path +from litellm.anthropic_beta_headers_manager import ( + update_headers_with_filtered_beta, +) from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation import ( VertexAIAnthropicConfig, ) @@ -346,8 +349,7 @@ def test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_hea "anthropic-beta": f"other-feature,{PROMPT_CACHING_BETA_HEADER},web-search-2025-03-05" } - config = VertexAIPartnerModelsAnthropicMessagesConfig() - config.remove_unsupported_beta(headers) + headers = update_headers_with_filtered_beta(headers, "vertex_ai") beta_header = headers.get("anthropic-beta") assert PROMPT_CACHING_BETA_HEADER not in (beta_header or ""), \ @@ -358,5 +360,5 @@ def test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_hea "Other non-excluded beta headers should remain" # If prompt-caching was the only value, header should be removed completely headers2 = {"anthropic-beta": PROMPT_CACHING_BETA_HEADER} - config.remove_unsupported_beta(headers2) + headers2 = update_headers_with_filtered_beta(headers2, "vertex_ai") assert "anthropic-beta" not in headers2, "Header should be removed if no supported values remain" \ No newline at end of file From 2e0715bd61799f6156d1fe532c227aed2a90417b Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:45:54 +0530 Subject: [PATCH 055/212] Fix mypy issue --- litellm/types/llms/anthropic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index 85d419ccc7d..fedf419efd6 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -613,7 +613,7 @@ ANTHROPIC_API_ONLY_HEADERS = { # fails if calling anthropic on vertex ai / bedr class AnthropicThinkingParam(TypedDict, total=False): - type: Literal["enabled"] + type: Literal["enabled", "adaptive"] budget_tokens: int From db8423b799b660a6950e68d91381fe392a9a18fb Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 18:52:04 +0530 Subject: [PATCH 056/212] Fix: test_json_response_nested_json_schema --- .../proxy/spend_tracking/test_spend_management_endpoints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 13368d0a142..54a276bc97f 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -205,6 +205,7 @@ ignored_keys = [ "metadata.additional_usage_values.prompt_tokens_details", "metadata.additional_usage_values.cache_creation_input_tokens", "metadata.additional_usage_values.cache_read_input_tokens", + "metadata.additional_usage_values.inference_geo", "metadata.litellm_overhead_time_ms", "metadata.cost_breakdown", ] From 285b2d2a125c3f90f2fbb34ad8b74e73f2f5a48f Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 19:06:49 +0530 Subject: [PATCH 057/212] add context_management header for compact_20260112 for messages --- .../messages/transformation.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py index 308bf367d06..52e3ca7159d 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py @@ -189,8 +189,27 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): beta_values.update(b.strip() for b in existing_beta.split(",")) # Check for context management - if optional_params.get("context_management") is not None: - beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value) + context_management_param = optional_params.get("context_management") + if context_management_param is not None: + # Check edits array for compact_20260112 type + edits = context_management_param.get("edits", []) + has_compact = False + has_other = False + + for edit in edits: + edit_type = edit.get("type", "") + if edit_type == "compact_20260112": + has_compact = True + else: + has_other = True + + # Add compact header if any compact edits exist + if has_compact: + beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_01_12.value) + + # Add context management header if any other edits exist + if has_other: + beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_MANAGEMENT_2025_06_27.value) # Check for structured outputs if optional_params.get("output_format") is not None: From 53a1f2d21cbfcada1283ab4e83a7e40ab1c8013f Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Fri, 6 Feb 2026 09:18:24 -0800 Subject: [PATCH 058/212] perf(prometheus): parallelize budget metrics, fix caching bug, reduce CPU by ~40% (#20544) --- litellm/integrations/prometheus.py | 53 +++++++++++-------- poetry.lock | 4 -- ...or.py => test_azure_ai_cost_calculator.py} | 0 3 files changed, 31 insertions(+), 26 deletions(-) rename tests/test_litellm/llms/azure_ai/{test_cost_calculator.py => test_azure_ai_cost_calculator.py} (100%) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 00c38eac188..0a61dab0680 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -1,6 +1,7 @@ # used for /metrics endpoint on LiteLLM Proxy #### What this does #### # On success, log events to Prometheus +import asyncio import os import sys from datetime import datetime, timedelta @@ -1188,28 +1189,34 @@ class PrometheusLogger(CustomLogger): _user_spend = _metadata.get("user_api_key_user_spend", None) _user_max_budget = _metadata.get("user_api_key_user_max_budget", None) - await self._set_api_key_budget_metrics_after_api_request( - user_api_key=user_api_key, - user_api_key_alias=user_api_key_alias, - response_cost=response_cost, - key_max_budget=_api_key_max_budget, - key_spend=_api_key_spend, - ) - - await self._set_team_budget_metrics_after_api_request( - user_api_team=user_api_team, - user_api_team_alias=user_api_team_alias, - team_spend=_team_spend, - team_max_budget=_team_max_budget, - response_cost=response_cost, - ) - - await self._set_user_budget_metrics_after_api_request( - user_id=user_id, - user_spend=_user_spend, - user_max_budget=_user_max_budget, - response_cost=response_cost, + results = await asyncio.gather( + self._set_api_key_budget_metrics_after_api_request( + user_api_key=user_api_key, + user_api_key_alias=user_api_key_alias, + response_cost=response_cost, + key_max_budget=_api_key_max_budget, + key_spend=_api_key_spend, + ), + self._set_team_budget_metrics_after_api_request( + user_api_team=user_api_team, + user_api_team_alias=user_api_team_alias, + team_spend=_team_spend, + team_max_budget=_team_max_budget, + response_cost=response_cost, + ), + self._set_user_budget_metrics_after_api_request( + user_id=user_id, + user_spend=_user_spend, + user_max_budget=_user_max_budget, + response_cost=response_cost, + ), + return_exceptions=True, ) + for i, r in enumerate(results): + if isinstance(r, Exception): + verbose_logger.debug( + f"[Non-Blocking] Prometheus: Budget metric lookup {['key', 'team', 'user'][i]} failed: {r}" + ) def _increment_top_level_request_and_spend_metrics( self, @@ -2898,12 +2905,14 @@ class PrometheusLogger(CustomLogger): max_budget=max_budget, ) try: + # Note: Setting check_db_only=True bypasses cache and hits DB on every request, + # causing huge latency increase and CPU spikes. Keep check_db_only=False. user_info = await get_user_object( user_id=user_id, prisma_client=prisma_client, user_api_key_cache=user_api_key_cache, user_id_upsert=False, - check_db_only=True, + check_db_only=False, ) except Exception as e: verbose_logger.debug( diff --git a/poetry.lock b/poetry.lock index 5e926509d54..b37fd863431 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8531,8 +8531,4 @@ utils = ["numpydoc"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -<<<<<<< litellm_oss_staging_02_04_2026 -content-hash = "797603dcfef0a79781c7d3cba5dfe18f6aea4aa792220f47487ebc7bd04ae2e3" -======= content-hash = "e5447e14dd37e324ac07a8fc6286d27e9a0d355ed93ebb24fc11e3f5df12fd3e" ->>>>>>> main diff --git a/tests/test_litellm/llms/azure_ai/test_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py similarity index 100% rename from tests/test_litellm/llms/azure_ai/test_cost_calculator.py rename to tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py From 09fb6d0087abf75c9cce81c674eb7d6f1de06b0e Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Fri, 6 Feb 2026 09:24:44 -0800 Subject: [PATCH 059/212] Warn when budget lookup fails; cache won't populate (#20545) * Warn when budget lookup fails; cache won't populate - Add _log_budget_lookup_failure helper in auth_checks.py - Log at WARNING in get_user_object, get_team_object, get_key_object when DB lookups fail (schema mismatch, etc.) - Add schema migration hint for prisma/db errors - Add dry-run test for _log_budget_lookup_failure * fix: skip budget lookup failure log for expected user-not-found case Avoid logging 'cache will not be populated' when the user simply doesn't exist - not caching is correct behavior in that case. Only log for unexpected errors (schema, DB, etc.) where the message is meaningful. --- litellm/proxy/auth/auth_checks.py | 23 +++++++++++++++++++ .../proxy/auth/test_auth_checks.py | 22 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 359bb944546..92e98d6446e 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -75,6 +75,28 @@ db_cache_expiry = DEFAULT_IN_MEMORY_TTL # refresh every 5s all_routes = LiteLLMRoutes.openai_routes.value + LiteLLMRoutes.management_routes.value +def _log_budget_lookup_failure(entity: str, error: Exception) -> None: + """ + Log a warning when budget lookup fails; cache will not be populated. + + Skips logging for expected "user not found" cases (bare Exception from + get_user_object when user_id_upsert=False). Adds a schema migration hint + when the error appears schema-related. + """ + # Skip logging for expected "user not found" - not caching is correct + if str(error) == "" and type(error).__name__ == "Exception": + return + err_str = str(error).lower() + hint = "" + if any( + x in err_str + for x in ("column", "schema", "does not exist", "prisma", "migrate") + ): + hint = " Run `prisma db push` or `prisma migrate deploy` to fix schema mismatches." + verbose_proxy_logger.error( + f"Budget lookup failed for {entity}; cache will not be populated. " + f"Each request will hit the database. Error: {error}.{hint}" + ) def _is_model_cost_zero( model: Optional[Union[str, List[str]]], llm_router: Optional[Router] @@ -1208,6 +1230,7 @@ async def get_user_object( return _response except Exception as e: # if user not in db + _log_budget_lookup_failure("user", e) raise ValueError( f"User doesn't exist in db. 'user_id'={user_id}. Create user via `/user/new` call. Got error - {e}" ) diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index ebcd9676129..4f8e80c023e 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -30,6 +30,7 @@ from litellm.proxy.auth.auth_checks import ( _can_object_call_vector_stores, _get_fuzzy_user_object, _get_team_db_check, + _log_budget_lookup_failure, _virtual_key_max_budget_alert_check, _virtual_key_soft_budget_check, get_user_object, @@ -273,6 +274,27 @@ async def test_default_internal_user_params_with_get_user_object(monkeypatch): assert creation_args["user_role"] == "internal_user" +def test_log_budget_lookup_failure_dry_run(): + """Dry run: verify _log_budget_lookup_failure logs for schema/DB errors.""" + with patch("litellm.proxy.auth.auth_checks.verbose_proxy_logger") as mock_logger: + err = Exception("column 'policies' does not exist in prisma schema") + _log_budget_lookup_failure("user", err) + mock_logger.error.assert_called_once() + call_msg = mock_logger.error.call_args[0][0] + assert "user" in call_msg + assert "cache will not be populated" in call_msg + assert "policies" in call_msg or "prisma" in call_msg + assert "prisma db push" in call_msg + + +def test_log_budget_lookup_failure_skips_user_not_found(): + """Verify _log_budget_lookup_failure does NOT log for expected user-not-found.""" + with patch("litellm.proxy.auth.auth_checks.verbose_proxy_logger") as mock_logger: + err = Exception() # bare Exception from get_user_object when user not found + _log_budget_lookup_failure("user", err) + mock_logger.error.assert_not_called() + + @pytest.mark.asyncio @patch("litellm.proxy.management_endpoints.team_endpoints.new_team", new_callable=AsyncMock) async def test_get_team_db_check_calls_new_team_on_upsert(mock_new_team, monkeypatch): From ee70010ef16f66b7537c7907689f6f17b52ab221 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Feb 2026 11:32:35 -0800 Subject: [PATCH 060/212] Adding testing coverage --- .../hooks/useDisableShowNewBadge.test.ts | 180 ++++++++ .../hooks/useDisableShowPrompts.test.ts | 180 ++++++++ .../SearchTools/SearchToolTester.test.tsx | 426 ++++++++++++++++++ .../common_components/chartUtils.test.tsx | 382 ++++++++++++++++ 4 files changed, 1168 insertions(+) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.test.ts create mode 100644 ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.test.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/chartUtils.test.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.test.ts new file mode 100644 index 00000000000..e01e2a4cf84 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.test.ts @@ -0,0 +1,180 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { renderHook, waitFor } from "@testing-library/react"; +import { useDisableShowNewBadge } from "./useDisableShowNewBadge"; +import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils"; + +describe("useDisableShowNewBadge", () => { + const STORAGE_KEY = "disableShowNewBadge"; + + beforeEach(() => { + localStorage.clear(); + vi.clearAllMocks(); + }); + + afterEach(() => { + localStorage.clear(); + }); + + it("should return false when localStorage is empty", () => { + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + }); + + it("should return false when localStorage value is not 'true'", () => { + localStorage.setItem(STORAGE_KEY, "false"); + + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + }); + + it("should return true when localStorage value is 'true'", () => { + localStorage.setItem(STORAGE_KEY, "true"); + + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(true); + }); + + it("should return false when localStorage value is an empty string", () => { + localStorage.setItem(STORAGE_KEY, ""); + + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + }); + + it("should update when storage event fires for the correct key", async () => { + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const storageEvent = new StorageEvent("storage", { + key: STORAGE_KEY, + newValue: "true", + }); + window.dispatchEvent(storageEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should not update when storage event fires for a different key", () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + + const storageEvent = new StorageEvent("storage", { + key: "otherKey", + newValue: "true", + }); + window.dispatchEvent(storageEvent); + + expect(result.current).toBe(false); + }); + + it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => { + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: "otherKey" }, + }); + window.dispatchEvent(customEvent); + + expect(result.current).toBe(false); + }); + + it("should update when localStorage changes from false to true via custom event", async () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should update when localStorage changes from true to false via storage event", async () => { + localStorage.setItem(STORAGE_KEY, "true"); + const { result } = renderHook(() => useDisableShowNewBadge()); + + expect(result.current).toBe(true); + + localStorage.setItem(STORAGE_KEY, "false"); + const storageEvent = new StorageEvent("storage", { + key: STORAGE_KEY, + newValue: "false", + }); + window.dispatchEvent(storageEvent); + + await waitFor(() => { + expect(result.current).toBe(false); + }); + }); + + it("should cleanup event listeners on unmount", () => { + const addEventListenerSpy = vi.spyOn(window, "addEventListener"); + const removeEventListenerSpy = vi.spyOn(window, "removeEventListener"); + + const { unmount } = renderHook(() => useDisableShowNewBadge()); + + expect(addEventListenerSpy).toHaveBeenCalledTimes(2); + expect(addEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function)); + expect(addEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function)); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(2); + expect(removeEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function)); + }); + + it("should handle multiple hooks independently", async () => { + const { result: result1 } = renderHook(() => useDisableShowNewBadge()); + const { result: result2 } = renderHook(() => useDisableShowNewBadge()); + + expect(result1.current).toBe(false); + expect(result2.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result1.current).toBe(true); + expect(result2.current).toBe(true); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.test.ts new file mode 100644 index 00000000000..7373f9a3202 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.test.ts @@ -0,0 +1,180 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { renderHook, waitFor } from "@testing-library/react"; +import { useDisableShowPrompts } from "./useDisableShowPrompts"; +import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils"; + +describe("useDisableShowPrompts", () => { + const STORAGE_KEY = "disableShowPrompts"; + + beforeEach(() => { + localStorage.clear(); + vi.clearAllMocks(); + }); + + afterEach(() => { + localStorage.clear(); + }); + + it("should return false when localStorage is empty", () => { + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + }); + + it("should return false when localStorage value is not 'true'", () => { + localStorage.setItem(STORAGE_KEY, "false"); + + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + }); + + it("should return true when localStorage value is 'true'", () => { + localStorage.setItem(STORAGE_KEY, "true"); + + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(true); + }); + + it("should return false when localStorage value is an empty string", () => { + localStorage.setItem(STORAGE_KEY, ""); + + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + }); + + it("should update when storage event fires for the correct key", async () => { + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const storageEvent = new StorageEvent("storage", { + key: STORAGE_KEY, + newValue: "true", + }); + window.dispatchEvent(storageEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should not update when storage event fires for a different key", () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + + const storageEvent = new StorageEvent("storage", { + key: "otherKey", + newValue: "true", + }); + window.dispatchEvent(storageEvent); + + expect(result.current).toBe(false); + }); + + it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => { + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: "otherKey" }, + }); + window.dispatchEvent(customEvent); + + expect(result.current).toBe(false); + }); + + it("should update when localStorage changes from false to true via custom event", async () => { + localStorage.setItem(STORAGE_KEY, "false"); + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it("should update when localStorage changes from true to false via storage event", async () => { + localStorage.setItem(STORAGE_KEY, "true"); + const { result } = renderHook(() => useDisableShowPrompts()); + + expect(result.current).toBe(true); + + localStorage.setItem(STORAGE_KEY, "false"); + const storageEvent = new StorageEvent("storage", { + key: STORAGE_KEY, + newValue: "false", + }); + window.dispatchEvent(storageEvent); + + await waitFor(() => { + expect(result.current).toBe(false); + }); + }); + + it("should cleanup event listeners on unmount", () => { + const addEventListenerSpy = vi.spyOn(window, "addEventListener"); + const removeEventListenerSpy = vi.spyOn(window, "removeEventListener"); + + const { unmount } = renderHook(() => useDisableShowPrompts()); + + expect(addEventListenerSpy).toHaveBeenCalledTimes(2); + expect(addEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function)); + expect(addEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function)); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(2); + expect(removeEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function)); + }); + + it("should handle multiple hooks independently", async () => { + const { result: result1 } = renderHook(() => useDisableShowPrompts()); + const { result: result2 } = renderHook(() => useDisableShowPrompts()); + + expect(result1.current).toBe(false); + expect(result2.current).toBe(false); + + localStorage.setItem(STORAGE_KEY, "true"); + const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, { + detail: { key: STORAGE_KEY }, + }); + window.dispatchEvent(customEvent); + + await waitFor(() => { + expect(result1.current).toBe(true); + expect(result2.current).toBe(true); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.test.tsx b/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.test.tsx new file mode 100644 index 00000000000..254a634e3b3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.test.tsx @@ -0,0 +1,426 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { SearchToolTester } from "./SearchToolTester"; +import * as networking from "../networking"; +import NotificationsManager from "../molecules/notifications_manager"; +import * as antd from "antd"; + +vi.mock("../networking", () => ({ + searchToolQueryCall: vi.fn(), +})); + +vi.mock("antd", async () => { + const actual = await vi.importActual("antd"); + return { + ...actual, + message: { + warning: vi.fn(), + success: vi.fn(), + error: vi.fn(), + }, + }; +}); + +const mockSearchResults = { + results: [ + { + title: "Test Result 1", + url: "https://example.com/result1", + snippet: "This is a short snippet for the first result.", + }, + { + title: "Test Result 2", + url: "https://example.com/result2", + snippet: "This is a longer snippet that exceeds two hundred characters and should be truncated when displayed in the results. It contains more detailed information about the search result that would normally be shown in a search engine result page.", + }, + ], +}; + +const defaultProps = { + searchToolName: "test-search-tool", + accessToken: "test-token", +}; + +describe("SearchToolTester", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(networking.searchToolQueryCall).mockResolvedValue(mockSearchResults); + vi.spyOn(Date, "now").mockReturnValue(1000000000000); + }); + + it("should render", () => { + render(); + expect(screen.getByText("Test Search Tool")).toBeInTheDocument(); + }); + + it("should display empty state when no search has been performed", () => { + render(); + expect(screen.getByText("Test your search tool")).toBeInTheDocument(); + expect(screen.getByText("Enter a query above to see search results")).toBeInTheDocument(); + }); + + it("should display search input with placeholder", () => { + render(); + expect(screen.getByPlaceholderText("Enter your search query...")).toBeInTheDocument(); + }); + + it("should display search button", () => { + render(); + expect(screen.getByRole("button", { name: /search/i })).toBeInTheDocument(); + }); + + it("should disable search button when input is empty", () => { + render(); + const searchButton = screen.getByRole("button", { name: /search/i }); + expect(searchButton).toBeDisabled(); + }); + + it("should enable search button when input has text", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + expect(searchButton).not.toBeDisabled(); + }); + + it("should call searchToolQueryCall when search button is clicked", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + expect(networking.searchToolQueryCall).toHaveBeenCalledWith("test-token", "test-search-tool", "test query"); + }); + + it("should call searchToolQueryCall when Enter is pressed in input", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query{Enter}"); + expect(networking.searchToolQueryCall).toHaveBeenCalledWith("test-token", "test-search-tool", "test query"); + }); + + it("should not call searchToolQueryCall when Shift+Enter is pressed", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + await user.keyboard("{Shift>}{Enter}{/Shift}"); + expect(networking.searchToolQueryCall).not.toHaveBeenCalled(); + }); + + it("should display loading state while searching", async () => { + vi.mocked(networking.searchToolQueryCall).mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve(mockSearchResults), 100)), + ); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + expect(screen.getByText("Searching...")).toBeInTheDocument(); + }); + + it("should display search results after successful search", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("Test Result 1")).toBeInTheDocument(); + }); + expect(screen.getByText("Test Result 2")).toBeInTheDocument(); + }); + + it("should display search query in results header", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("test query")).toBeInTheDocument(); + }); + }); + + it("should display result count in results header", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("2 results")).toBeInTheDocument(); + }); + }); + + it("should display singular result count when only one result", async () => { + const singleResult = { + results: [ + { + title: "Single Result", + url: "https://example.com/single", + snippet: "Single result snippet", + }, + ], + }; + vi.mocked(networking.searchToolQueryCall).mockResolvedValue(singleResult); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("1 result")).toBeInTheDocument(); + }); + }); + + it("should display result URLs", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("https://example.com/result1")).toBeInTheDocument(); + }); + expect(screen.getByText("https://example.com/result2")).toBeInTheDocument(); + }); + + it("should display result snippets", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("This is a short snippet for the first result.")).toBeInTheDocument(); + }); + }); + + it("should truncate long snippets and show expand button", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText(/Show more/i)).toBeInTheDocument(); + }); + }); + + it("should expand snippet when Show more is clicked", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText(/Show more/i)).toBeInTheDocument(); + }); + const expandButton = screen.getByText(/Show more/i); + await user.click(expandButton); + expect(screen.getByText(/Show less/i)).toBeInTheDocument(); + }); + + it("should collapse snippet when Show less is clicked", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText(/Show more/i)).toBeInTheDocument(); + }); + const expandButton = screen.getByText(/Show more/i); + await user.click(expandButton); + const collapseButton = screen.getByText(/Show less/i); + await user.click(collapseButton); + expect(screen.getByText(/Show more/i)).toBeInTheDocument(); + }); + + it("should display no results message when search returns empty results", async () => { + vi.mocked(networking.searchToolQueryCall).mockResolvedValue({ results: [] }); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("No results found")).toBeInTheDocument(); + }); + expect(screen.getByText("Try a different search query")).toBeInTheDocument(); + }); + + it("should display no results message when search returns null results", async () => { + vi.mocked(networking.searchToolQueryCall).mockResolvedValue({ results: null }); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("No results found")).toBeInTheDocument(); + }); + }); + + it("should handle search errors and show notification", async () => { + const error = new Error("Search failed"); + vi.mocked(networking.searchToolQueryCall).mockRejectedValue(error); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { }); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to query search tool"); + }); + consoleSpy.mockRestore(); + }); + + it("should maintain search history", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "first query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("first query")).toBeInTheDocument(); + }); + await user.clear(input); + await user.type(input, "second query"); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("second query")).toBeInTheDocument(); + }); + expect(screen.getByText("Previous Searches")).toBeInTheDocument(); + expect(screen.getByText("first query")).toBeInTheDocument(); + }); + + it("should allow clicking previous search to set query", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "first query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("first query")).toBeInTheDocument(); + }); + await user.clear(input); + await user.type(input, "second query"); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("second query")).toBeInTheDocument(); + }); + const historyItems = screen.getAllByText("first query"); + const historyItem = historyItems.find((item) => item.closest('[class*="cursor-pointer"]')); + if (historyItem) { + await user.click(historyItem); + expect(input).toHaveValue("first query"); + } + }); + + it("should clear history when Clear All is clicked", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "first query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("first query")).toBeInTheDocument(); + }); + await user.clear(input); + await user.type(input, "second query"); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("Previous Searches")).toBeInTheDocument(); + }); + const clearButton = screen.getByRole("button", { name: /clear all/i }); + await user.click(clearButton); + expect(NotificationsManager.success).toHaveBeenCalledWith("Search history cleared"); + expect(screen.queryByText("Previous Searches")).not.toBeInTheDocument(); + }); + + it("should limit history display to 5 previous searches", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + for (let i = 1; i <= 7; i++) { + await user.clear(input); + await user.type(input, `query ${i}`); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText(`query ${i}`)).toBeInTheDocument(); + }); + } + const historySection = screen.queryByText("Previous Searches"); + if (historySection) { + const historyItems = historySection.parentElement?.querySelectorAll('[class*="cursor-pointer"]'); + expect(historyItems?.length).toBeLessThanOrEqual(5); + } + }); + + it("should preserve query text after search", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + expect(screen.getByText("test query")).toBeInTheDocument(); + }); + expect(input).toHaveValue("test query"); + }); + + it("should disable input and button while loading", async () => { + vi.mocked(networking.searchToolQueryCall).mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve(mockSearchResults), 100)), + ); + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + expect(input).toBeDisabled(); + expect(searchButton).toBeDisabled(); + }); + + it("should display result links that open in new tab", async () => { + const user = userEvent.setup(); + render(); + const input = screen.getByPlaceholderText("Enter your search query..."); + await user.type(input, "test query"); + const searchButton = screen.getByRole("button", { name: /search/i }); + await user.click(searchButton); + await waitFor(() => { + const link = screen.getByRole("link", { name: "Test Result 1" }); + expect(link).toHaveAttribute("href", "https://example.com/result1"); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/chartUtils.test.tsx b/ui/litellm-dashboard/src/components/common_components/chartUtils.test.tsx new file mode 100644 index 00000000000..b924021863a --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/chartUtils.test.tsx @@ -0,0 +1,382 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { CustomLegend, CustomTooltip } from "./chartUtils"; +import type { CustomTooltipProps } from "@tremor/react"; +import { SpendMetrics } from "../UsagePage/types"; + +describe("CustomTooltip", () => { + const mockPayload = [ + { + dataKey: "metrics.total_tokens", + value: 1000, + color: "blue", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + + it("should render", () => { + const props: CustomTooltipProps = { + active: true, + payload: mockPayload, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("2024-01-15")).toBeInTheDocument(); + }); + + it("should return null when not active", () => { + const props: CustomTooltipProps = { + active: false, + payload: mockPayload, + label: "2024-01-15", + }; + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it("should return null when payload is empty", () => { + const props: CustomTooltipProps = { + active: true, + payload: [], + label: "2024-01-15", + }; + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it("should display formatted category names", () => { + const props: CustomTooltipProps = { + active: true, + payload: mockPayload, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("Total Tokens")).toBeInTheDocument(); + }); + + it("should format category names by removing metrics prefix and replacing underscores", () => { + const payloadWithUnderscores = [ + { + dataKey: "metrics.prompt_tokens", + value: 600, + color: "green", + payload: { + date: "2024-01-15", + metrics: { + prompt_tokens: 600, + total_tokens: 1000, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: payloadWithUnderscores, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("Prompt Tokens")).toBeInTheDocument(); + }); + + it("should format spend values with dollar sign and two decimal places", () => { + const spendPayload = [ + { + dataKey: "metrics.spend", + value: 1234.567, + color: "red", + payload: { + date: "2024-01-15", + metrics: { + spend: 1234.567, + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: spendPayload, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("$1,234.57")).toBeInTheDocument(); + }); + + it("should format non-spend numeric values with locale string", () => { + const props: CustomTooltipProps = { + active: true, + payload: mockPayload, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("1,000")).toBeInTheDocument(); + }); + + it("should display N/A when value is undefined", () => { + const payloadWithUndefined = [ + { + dataKey: "metrics.nonexistent", + value: undefined, + color: "blue", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: payloadWithUndefined, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("N/A")).toBeInTheDocument(); + }); + + it("should handle multiple payload items", () => { + const multiplePayload = [ + { + dataKey: "metrics.total_tokens", + value: 1000, + color: "blue", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + { + dataKey: "metrics.spend", + value: 0.05, + color: "green", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: multiplePayload, + label: "2024-01-15", + }; + render(); + expect(screen.getByText("Total Tokens")).toBeInTheDocument(); + expect(screen.getByText("Spend")).toBeInTheDocument(); + }); + + it("should convert color names to hex values", () => { + const props: CustomTooltipProps = { + active: true, + payload: mockPayload, + label: "2024-01-15", + }; + const { container } = render(); + const colorIndicator = container.querySelector('span[style*="background-color"]'); + expect(colorIndicator).toHaveStyle({ backgroundColor: "#3b82f6" }); + }); + + it("should use hex color directly when color is not a known color name", () => { + const payloadWithHexColor = [ + { + dataKey: "metrics.total_tokens", + value: 1000, + color: "#ff0000", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: payloadWithHexColor, + label: "2024-01-15", + }; + const { container } = render(); + const colorIndicator = container.querySelector('span[style*="background-color"]'); + expect(colorIndicator).toHaveStyle({ backgroundColor: "#ff0000" }); + }); + + it("should skip items without dataKey", () => { + const payloadWithoutDataKey = [ + { + dataKey: undefined, + value: 1000, + color: "blue", + payload: { + date: "2024-01-15", + metrics: { + total_tokens: 1000, + prompt_tokens: 600, + completion_tokens: 400, + spend: 0.05, + api_requests: 10, + successful_requests: 9, + failed_requests: 1, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + } as SpendMetrics, + }, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: payloadWithoutDataKey as any, + label: "2024-01-15", + }; + render(); + expect(screen.queryByText("Total Tokens")).not.toBeInTheDocument(); + }); + + it("should skip items without payload", () => { + const payloadWithoutPayload = [ + { + dataKey: "metrics.total_tokens", + value: 1000, + color: "blue", + payload: undefined, + }, + ]; + const props: CustomTooltipProps = { + active: true, + payload: payloadWithoutPayload as any, + label: "2024-01-15", + }; + render(); + expect(screen.queryByText("Total Tokens")).not.toBeInTheDocument(); + }); +}); + +describe("CustomLegend", () => { + it("should render", () => { + render(); + expect(screen.getByText("Total Tokens")).toBeInTheDocument(); + }); + + it("should display multiple categories", () => { + render( + , + ); + expect(screen.getByText("Total Tokens")).toBeInTheDocument(); + expect(screen.getByText("Spend")).toBeInTheDocument(); + expect(screen.getByText("Prompt Tokens")).toBeInTheDocument(); + }); + + it("should format category names by removing metrics prefix and replacing underscores", () => { + render(); + expect(screen.getByText("Api Requests")).toBeInTheDocument(); + }); + + it("should capitalize first letter of each word", () => { + render(); + expect(screen.getByText("Successful Requests")).toBeInTheDocument(); + }); + + it("should convert color names to hex values", () => { + const { container } = render(); + const colorIndicator = container.querySelector('span[style*="background-color"]'); + expect(colorIndicator).toHaveStyle({ backgroundColor: "#06b6d4" }); + }); + + it("should use hex color directly when color is not a known color name", () => { + const { container } = render(); + const colorIndicator = container.querySelector('span[style*="background-color"]'); + expect(colorIndicator).toHaveStyle({ backgroundColor: "#ff00ff" }); + }); + + it("should handle all supported color names", () => { + const colors = ["blue", "cyan", "indigo", "green", "red", "purple", "emerald"]; + const categories = colors.map((_, idx) => `metrics.category_${idx}`); + render(); + expect(screen.getByText("Category 0")).toBeInTheDocument(); + }); + + it("should match categories and colors by index", () => { + render( + , + ); + const { container } = render( + , + ); + const colorIndicators = container.querySelectorAll('span[style*="background-color"]'); + expect(colorIndicators[0]).toHaveStyle({ backgroundColor: "#3b82f6" }); + expect(colorIndicators[1]).toHaveStyle({ backgroundColor: "#22c55e" }); + expect(colorIndicators[2]).toHaveStyle({ backgroundColor: "#ef4444" }); + }); +}); From 5733f6213b5ef8509646d1f2a24e9b38d72c04fa Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Fri, 6 Feb 2026 11:36:33 -0800 Subject: [PATCH 061/212] Add INFO-level session reuse logging per request (#20597) - Log when shared aiohttp session is attached to each request - Log when no shared session is available - Visible at INFO level (production-safe) --- litellm/proxy/route_llm_request.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 92fb88f7147..6baa2047d73 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -97,10 +97,18 @@ def add_shared_session_to_data(data: dict) -> None: data: Dictionary to add the shared session to """ try: + from litellm._logging import verbose_proxy_logger from litellm.proxy.proxy_server import shared_aiohttp_session if shared_aiohttp_session is not None and not shared_aiohttp_session.closed: data["shared_session"] = shared_aiohttp_session + verbose_proxy_logger.info( + f"SESSION REUSE: Attached shared aiohttp session to request (ID: {id(shared_aiohttp_session)})" + ) + else: + verbose_proxy_logger.info( + "SESSION REUSE: No shared session available for this request" + ) except Exception: # Silently continue without session reuse if import fails or session unavailable pass From 8df6cfe9d86e147d0ac22ce014797c39dddfe7b6 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 6 Feb 2026 12:27:03 -0800 Subject: [PATCH 062/212] fix model page col resize --- .../model_dashboard/all_models_table.tsx | 11 +- .../src/components/model_dashboard/table.tsx | 11 +- .../molecules/models/columns.test.tsx | 808 ++++++++++++++++++ .../components/molecules/models/columns.tsx | 134 ++- 4 files changed, 922 insertions(+), 42 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/molecules/models/columns.test.tsx diff --git a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx index 1ae3335a823..964bb5658fe 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx @@ -95,7 +95,14 @@ export function AllModelsDataTable({

- +
{tableInstance.getHeaderGroups().map((headerGroup) => ( @@ -171,7 +178,7 @@ export function AllModelsDataTable({ {row.getVisibleCells().map((cell) => ( ({
-
+
{tableInstance.getHeaderGroups().map((headerGroup) => ( @@ -161,7 +168,7 @@ export function ModelDataTable({ {row.getVisibleCells().map((cell) => ( { + const React = await import("react"); + const actual = await importOriginal(); + return { + ...actual, + Icon: React.forwardRef(({ icon: IconComponent, onClick, className, ...props }, ref) => { + const ariaLabel = className?.includes("cursor-not-allowed") + ? "Config model cannot be deleted on the dashboard. Please delete it from the config file." + : "Delete model"; + return React.createElement( + "button", + { ...props, onClick, className, ref, "aria-label": ariaLabel }, + IconComponent && React.createElement(IconComponent, { className: "w-4 h-4" }), + ); + }), + }; +}); + +const createMockModel = (overrides: Partial = {}): ModelData => ({ + model_info: { + id: "test-model-id", + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-02T00:00:00Z", + created_by: "test-user", + team_id: "test-team-id", + db_model: true, + access_groups: ["group1"], + }, + model_name: "test-model", + provider: "openai", + litellm_model_name: "gpt-4", + input_cost: 0.01, + output_cost: 0.03, + max_tokens: 4096, + max_input_tokens: 8192, + litellm_params: { + model: "gpt-4", + litellm_credential_name: "test-credential", + }, + cleanedLitellmParams: {}, + ...overrides, +}); + +const TestTable = ({ + data, + columns: cols, +}: { + data: ModelData[]; + columns: ReturnType; +}) => { + const table = useReactTable({ + data, + columns: cols, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+ ); +}; + +describe("columns", () => { + beforeEach(() => { + vi.mocked(providerInfoHelpers.getProviderLogoAndName).mockImplementation((provider: string) => { + const providerMap: Record = { + openai: { displayName: "OpenAI", logo: "/openai-logo.png" }, + anthropic: { displayName: "Anthropic", logo: "/anthropic-logo.png" }, + }; + return providerMap[provider] || { displayName: provider || "Unknown provider", logo: "" }; + }); + }); + + const defaultProps = { + userRole: "Admin", + userID: "test-user", + premiumUser: false, + setSelectedModelId: vi.fn(), + setSelectedTeamId: vi.fn(), + getDisplayModelName: vi.fn((model: ModelData) => model.model_name || "-"), + handleEditClick: vi.fn(), + handleRefreshClick: vi.fn(), + expandedRows: new Set(), + setExpandedRows: vi.fn(), + }; + + it("should render columns with table structure", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel(); + render(); + + expect(screen.getByText("Model ID")).toBeInTheDocument(); + expect(screen.getByText("Model Information")).toBeInTheDocument(); + expect(screen.getByText("Credentials")).toBeInTheDocument(); + expect(screen.getByText("Created By")).toBeInTheDocument(); + expect(screen.getByText("Updated At")).toBeInTheDocument(); + expect(screen.getByText("Costs")).toBeInTheDocument(); + expect(screen.getByText("Team ID")).toBeInTheDocument(); + expect(screen.getByText("Model Access Group")).toBeInTheDocument(); + expect(screen.getByText("Status")).toBeInTheDocument(); + expect(screen.getByText("Actions")).toBeInTheDocument(); + }); + + it("should display model information with provider logo", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_name: "GPT-4", + provider: "openai", + litellm_model_name: "gpt-4", + }); + render(); + + expect(screen.getByText("GPT-4")).toBeInTheDocument(); + expect(screen.getByText("gpt-4")).toBeInTheDocument(); + }); + + it("should display credential name when available", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: { + model: "gpt-4", + litellm_credential_name: "my-credential", + }, + }); + render(); + + expect(screen.getByText("my-credential")).toBeInTheDocument(); + }); + + it("should display 'No credentials' when credential name is missing", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: { + model: "gpt-4", + }, + }); + render(); + + expect(screen.getByText("No credentials")).toBeInTheDocument(); + }); + + it("should display created by information for DB models", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: true, + created_by: "admin-user", + created_at: "2024-01-15T10:30:00Z", + }, + }); + render(); + + expect(screen.getByText("admin-user")).toBeInTheDocument(); + }); + + it("should display 'Defined in config' for config models", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: false, + }, + }); + render(); + + expect(screen.getByText("Defined in config")).toBeInTheDocument(); + }); + + it("should display costs when available", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + input_cost: 0.01, + output_cost: 0.03, + }); + render(); + + expect(screen.getByText("In: $0.01")).toBeInTheDocument(); + expect(screen.getByText("Out: $0.03")).toBeInTheDocument(); + }); + + it("should display '-' when costs are missing", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + input_cost: undefined as any, + output_cost: undefined as any, + }); + render(); + + const costCells = screen.getAllByText("-"); + expect(costCells.length).toBeGreaterThan(0); + }); + + it("should display '-' when team ID is missing", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + team_id: "", + }, + }); + render(); + + const teamIdCells = screen.getAllByText("-"); + expect(teamIdCells.length).toBeGreaterThan(0); + }); + + it("should display access groups", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + access_groups: ["group1", "group2"], + }, + }); + render(); + + expect(screen.getByText("group1")).toBeInTheDocument(); + expect(screen.getByText("+1")).toBeInTheDocument(); + }); + + it("should expand access groups when expand button is clicked", async () => { + const user = userEvent.setup(); + const setExpandedRows = vi.fn(); + const expandedRows = new Set(); + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + expandedRows, + setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + id: "model-with-groups", + access_groups: ["group1", "group2", "group3"], + }, + }); + render(); + + const expandButton = screen.getByText("+2"); + expect(expandButton).toBeInTheDocument(); + + await user.click(expandButton); + expect(setExpandedRows).toHaveBeenCalled(); + }); + + it("should display '-' when access groups are empty", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + access_groups: null, + }, + }); + render(); + + const emptyCells = screen.getAllByText("-"); + expect(emptyCells.length).toBeGreaterThan(0); + }); + + it("should display 'DB Model' status for DB models", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: true, + }, + }); + render(); + + expect(screen.getByText("DB Model")).toBeInTheDocument(); + }); + + it("should display 'Config Model' status for config models", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: false, + }, + }); + render(); + + expect(screen.getByText("Config Model")).toBeInTheDocument(); + }); + + it("should allow Admin to delete DB models", async () => { + const user = userEvent.setup(); + const setSelectedModelId = vi.fn(); + const cols = columns( + "Admin", + "admin-user", + defaultProps.premiumUser, + setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: true, + id: "deletable-model", + }, + }); + render(); + + const deleteButton = screen.getByRole("button", { name: "Delete model" }); + expect(deleteButton).toBeInTheDocument(); + + await user.click(deleteButton); + expect(setSelectedModelId).toHaveBeenCalledWith("deletable-model"); + }); + + it("should allow model creator to delete their own DB models", async () => { + const user = userEvent.setup(); + const setSelectedModelId = vi.fn(); + const cols = columns( + "User", + "model-creator", + defaultProps.premiumUser, + setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: true, + created_by: "model-creator", + id: "user-model", + }, + }); + render(); + + const deleteButton = screen.getByRole("button", { name: "Delete model" }); + expect(deleteButton).toBeInTheDocument(); + + await user.click(deleteButton); + expect(setSelectedModelId).toHaveBeenCalledWith("user-model"); + }); + + + it("should disable delete for config models", () => { + const cols = columns( + "Admin", + "admin-user", + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: false, + }, + }); + render(); + + const deleteButton = screen.getByRole("button", { name: /config model cannot be deleted/i }); + expect(deleteButton).toBeInTheDocument(); + expect(deleteButton).toHaveClass("cursor-not-allowed"); + }); + + it("should display collapsed access groups with expand button", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + new Set(), + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + access_groups: ["group1", "group2", "group3"], + }, + }); + render(); + + expect(screen.getByText("group1")).toBeInTheDocument(); + expect(screen.getByText("+2")).toBeInTheDocument(); + expect(screen.queryByText("group2")).not.toBeInTheDocument(); + expect(screen.queryByText("group3")).not.toBeInTheDocument(); + }); + + it("should display expanded access groups when expanded", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + new Set(["test-model-id"]), + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + id: "test-model-id", + access_groups: ["group1", "group2", "group3"], + }, + }); + render(); + + expect(screen.getByText("group1")).toBeInTheDocument(); + expect(screen.getByText("group2")).toBeInTheDocument(); + expect(screen.getByText("group3")).toBeInTheDocument(); + expect(screen.getByText("−")).toBeInTheDocument(); + }); + + it("should display single access group without expand button", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + access_groups: ["group1"], + }, + }); + render(); + + expect(screen.getByText("group1")).toBeInTheDocument(); + expect(screen.queryByText(/\+/)).not.toBeInTheDocument(); + }); + + + it("should handle missing display name gracefully", () => { + const getDisplayModelName = vi.fn(() => ""); + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel(); + render(); + + expect(screen.getByText("-")).toBeInTheDocument(); + }); + + it("should handle missing created_at date", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + created_at: "", + }, + }); + render(); + + expect(screen.getByText("Unknown date")).toBeInTheDocument(); + }); + + it("should handle missing updated_at date", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + updated_at: "", + }, + }); + render(); + + const updatedAtCells = screen.getAllByText("-"); + expect(updatedAtCells.length).toBeGreaterThan(0); + }); + + it("should handle missing created_by for DB models", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + model_info: { + ...createMockModel().model_info, + db_model: true, + created_by: "", + }, + }); + render(); + + expect(screen.getByText("Unknown")).toBeInTheDocument(); + }); + + it("should display only input cost when output cost is missing", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + input_cost: 0.01, + output_cost: undefined as any, + }); + render(); + + expect(screen.getByText("In: $0.01")).toBeInTheDocument(); + expect(screen.queryByText(/Out:/)).not.toBeInTheDocument(); + }); + + it("should display only output cost when input cost is missing", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + input_cost: undefined as any, + output_cost: 0.03, + }); + render(); + + expect(screen.getByText("Out: $0.03")).toBeInTheDocument(); + expect(screen.queryByText(/In:/)).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx index 1fc08e502ac..958b6ac86a7 100644 --- a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx +++ b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx @@ -1,10 +1,12 @@ import { KeyIcon, TrashIcon } from "@heroicons/react/outline"; import { ColumnDef } from "@tanstack/react-table"; import { Badge, Button, Icon } from "@tremor/react"; -import { Tooltip } from "antd"; +import { Popover, Tooltip, Typography, Space, Flex } from "antd"; import { ModelData } from "../../model_dashboard/types"; import { ProviderLogo } from "./ProviderLogo"; +const { Text } = Typography; + export const columns = ( userRole: string, userID: string, @@ -21,16 +23,20 @@ export const columns = ( header: () => Model ID, accessorKey: "model_info.id", enableSorting: false, + size: 130, + minSize: 80, cell: ({ row }) => { const model = row.original; return ( -
setSelectedModelId(model.model_info.id)} > {model.model_info.id} -
+
); }, @@ -38,28 +44,67 @@ export const columns = ( { header: () => Model Information, accessorKey: "model_name", - size: 250, // Fixed column width + size: 250, + minSize: 120, cell: ({ row }) => { const model = row.original; const displayName = getDisplayModelName(row.original) || "-"; - const tooltipContent = ( -
-
- Provider: {model.provider || "-"} -
-
- Public Model Name: {displayName} -
-
- LiteLLM Model Name: {model.litellm_model_name || "-"} -
-
+ const popoverContent = ( + + + + + {model.provider || "Unknown provider"} + + + + + + + Public Model Name + + + {displayName} + + + + + + LiteLLM Model Name + + + {model.litellm_model_name || "-"} + + + + ); return ( - -
- {/* Provider Icon */} + +
{model.provider ? ( @@ -68,17 +113,16 @@ export const columns = ( )}
- {/* Model Names Container */}
- {/* Public Model Name */} -
{displayName}
- {/* LiteLLM Model Name */} -
+ + {displayName} + + {model.litellm_model_name || "-"} -
+
- +
); }, }, @@ -86,14 +130,15 @@ export const columns = ( header: () => Credentials, accessorKey: "litellm_credential_name", enableSorting: false, - size: 180, // Fixed column width + size: 180, + minSize: 100, cell: ({ row }) => { const model = row.original; const credentialName = model.litellm_params?.litellm_credential_name; return credentialName ? ( -
+
{credentialName} @@ -101,7 +146,7 @@ export const columns = (
) : ( -
+
No credentials
@@ -112,7 +157,8 @@ export const columns = ( header: () => Created By, accessorKey: "model_info.created_by", sortingFn: "datetime", - size: 160, // Fixed column width + size: 160, + minSize: 100, cell: ({ row }) => { const model = row.original; const isConfigModel = !model.model_info?.db_model; @@ -120,7 +166,7 @@ export const columns = ( const createdAt = model.model_info.created_at ? new Date(model.model_info.created_at).toLocaleDateString() : null; return ( -
+
{/* Created By - Primary */}
Updated At, accessorKey: "model_info.updated_at", sortingFn: "datetime", + size: 120, + minSize: 80, cell: ({ row }) => { const model = row.original; return ( @@ -155,7 +203,8 @@ export const columns = ( { header: () => Costs, accessorKey: "input_cost", - size: 120, // Fixed column width + size: 120, + minSize: 80, cell: ({ row }) => { const model = row.original; const inputCost = model.input_cost; @@ -164,7 +213,7 @@ export const columns = ( // If both costs are missing or undefined, show "-" if (!inputCost && !outputCost) { return ( -
+
-
); @@ -172,7 +221,7 @@ export const columns = ( return ( -
+
{/* Input Cost - Primary */} {inputCost &&
In: ${inputCost}
} {/* Output Cost - Secondary */} @@ -186,15 +235,17 @@ export const columns = ( header: () => Team ID, accessorKey: "model_info.team_id", enableSorting: false, + size: 130, + minSize: 80, cell: ({ row }) => { const model = row.original; return model.model_info.team_id ? ( -
+