From d4b8fd4a54b781150c4e45a2c7bdf48e23cf2ca8 Mon Sep 17 00:00:00 2001 From: lioZ129 <1907814837@qq.com> Date: Thu, 26 Mar 2026 15:46:22 +0800 Subject: [PATCH] fix CI and add monkeypatch test --- litellm/caching/caching.py | 2 +- .../prompt_templates/factory.py | 54 ++++++++-------- litellm/llms/gigachat/file_handler.py | 2 +- .../proxy/management_helpers/audit_logs.py | 14 +++-- .../hpc_ai/test_hpc_ai_chat_transformation.py | 61 +++++++++++++++++++ 5 files changed, 97 insertions(+), 36 deletions(-) diff --git a/litellm/caching/caching.py b/litellm/caching/caching.py index 406a4f8c98a..69cffc6044a 100644 --- a/litellm/caching/caching.py +++ b/litellm/caching/caching.py @@ -411,7 +411,7 @@ class Cache: Returns: str: The hashed cache key. """ - hash_object = hashlib.sha256(cache_key.encode()) + hash_object = hashlib.sha256(cache_key.encode(), usedforsecurity=False) # Hexadecimal representation of the hash hash_hex = hash_object.hexdigest() verbose_logger.debug("Hashed cache key (SHA-256): %s", hash_hex) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index d29ca1649ff..c722a58ad9d 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1393,10 +1393,10 @@ def convert_to_gemini_tool_call_invoke( if tool_calls is not None: for idx, tool in enumerate(tool_calls): if "function" in tool: - gemini_function_call: Optional[ - VertexFunctionCall - ] = _gemini_tool_call_invoke_helper( - function_call_params=tool["function"] + gemini_function_call: Optional[VertexFunctionCall] = ( + _gemini_tool_call_invoke_helper( + function_call_params=tool["function"] + ) ) if gemini_function_call is not None: part_dict: VertexPartType = { @@ -1574,9 +1574,7 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915 file_data = ( file_content.get("file_data", "") if isinstance(file_content, dict) - else file_content - if isinstance(file_content, str) - else "" + else file_content if isinstance(file_content, str) else "" ) if file_data: @@ -2081,9 +2079,9 @@ def _sanitize_empty_text_content( if isinstance(content, str): if not content or not content.strip(): message = cast(AllMessageValues, dict(message)) # Make a copy - message[ - "content" - ] = "[System: Empty message content sanitised to satisfy protocol]" + message["content"] = ( + "[System: Empty message content sanitised to satisfy protocol]" + ) verbose_logger.debug( f"_sanitize_empty_text_content: Replaced empty text content in {message.get('role')} message" ) @@ -2423,9 +2421,9 @@ def anthropic_messages_pt( # noqa: PLR0915 # Convert ChatCompletionImageUrlObject to dict if needed image_url_value = m["image_url"] if isinstance(image_url_value, str): - image_url_input: Union[ - str, dict[str, Any] - ] = image_url_value + image_url_input: Union[str, dict[str, Any]] = ( + image_url_value + ) else: # ChatCompletionImageUrlObject or dict case - convert to dict image_url_input = { @@ -2452,9 +2450,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_element[ - "cache_control" - ] = _content_element["cache_control"] + _anthropic_content_element["cache_control"] = ( + _content_element["cache_control"] + ) user_content.append(_anthropic_content_element) elif m.get("type", "") == "text": m = cast(ChatCompletionTextObject, m) @@ -2514,9 +2512,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_content_text_element[ - "cache_control" - ] = _content_element["cache_control"] + _anthropic_content_text_element["cache_control"] = ( + _content_element["cache_control"] + ) user_content.append(_anthropic_content_text_element) @@ -2649,9 +2647,9 @@ def anthropic_messages_pt( # noqa: PLR0915 original_content_element=dict(assistant_content_block), ) if "cache_control" in _content_element: - _anthropic_text_content_element[ - "cache_control" - ] = _content_element["cache_control"] + _anthropic_text_content_element["cache_control"] = ( + _content_element["cache_control"] + ) text_element = _anthropic_text_content_element # Interleave: each thinking block precedes its server tool group. @@ -2811,9 +2809,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ) if "cache_control" in _content_element: - _anthropic_text_content_element[ - "cache_control" - ] = _content_element["cache_control"] + _anthropic_text_content_element["cache_control"] = ( + _content_element["cache_control"] + ) assistant_content.append(_anthropic_text_content_element) @@ -3725,7 +3723,7 @@ class BedrockImageProcessor: sample = normalized[:HASH_SAMPLE_BYTES] # --- Compute deterministic hash (sample + total length) --- - hasher = hashlib.sha256() + hasher = hashlib.sha256(usedforsecurity=False) hasher.update(sample) hasher.update( str(len(normalized)).encode("utf-8") @@ -5255,9 +5253,7 @@ def default_response_schema_prompt(response_schema: dict) -> str: prompt_str = """Use this JSON schema: ```json {} - ```""".format( - response_schema - ) + ```""".format(response_schema) return prompt_str diff --git a/litellm/llms/gigachat/file_handler.py b/litellm/llms/gigachat/file_handler.py index 200428a747a..8e77798e4d1 100644 --- a/litellm/llms/gigachat/file_handler.py +++ b/litellm/llms/gigachat/file_handler.py @@ -29,7 +29,7 @@ _file_cache: Dict[str, str] = {} def _get_url_hash(url: str) -> str: """Generate hash for URL to use as cache key.""" - return hashlib.sha256(url.encode()).hexdigest() + return hashlib.sha256(url.encode(), usedforsecurity=False).hexdigest() def _parse_data_url(data_url: str) -> Optional[Tuple[bytes, str, str]]: diff --git a/litellm/proxy/management_helpers/audit_logs.py b/litellm/proxy/management_helpers/audit_logs.py index b9020222f1f..7599e11bdef 100644 --- a/litellm/proxy/management_helpers/audit_logs.py +++ b/litellm/proxy/management_helpers/audit_logs.py @@ -51,7 +51,11 @@ def _build_audit_log_payload( if request_data.updated_at is not None: updated_at = request_data.updated_at.isoformat() - table_name_str: str = request_data.table_name.value if isinstance(request_data.table_name, LitellmTableNames) else str(request_data.table_name) + table_name_str: str = ( + request_data.table_name.value + if isinstance(request_data.table_name, LitellmTableNames) + else str(request_data.table_name) + ) return StandardAuditLogPayload( id=request_data.id, @@ -89,7 +93,9 @@ async def _dispatch_audit_log_to_callbacks( for callback in litellm.audit_log_callbacks: try: - resolved: Optional[CustomLogger] = callback if isinstance(callback, CustomLogger) else None + resolved: Optional[CustomLogger] = ( + callback if isinstance(callback, CustomLogger) else None + ) if isinstance(callback, str): resolved = _resolve_audit_log_callback(callback) if resolved is None: @@ -138,9 +144,7 @@ async def create_object_audit_log( return _changed_by = ( - litellm_changed_by - or user_api_key_dict.user_id - or litellm_proxy_admin_name + litellm_changed_by or user_api_key_dict.user_id or litellm_proxy_admin_name ) await create_audit_log_for_update( diff --git a/tests/test_litellm/llms/hpc_ai/test_hpc_ai_chat_transformation.py b/tests/test_litellm/llms/hpc_ai/test_hpc_ai_chat_transformation.py index 5226d1b1717..56f929454b5 100644 --- a/tests/test_litellm/llms/hpc_ai/test_hpc_ai_chat_transformation.py +++ b/tests/test_litellm/llms/hpc_ai/test_hpc_ai_chat_transformation.py @@ -4,6 +4,7 @@ Unit tests for HPC-AI OpenAI-compatible configuration. import os import sys +from typing import Any, Optional sys.path.insert(0, os.path.abspath("../../../../..")) @@ -15,6 +16,66 @@ from litellm.llms.hpc_ai.chat.transformation import HpcAiConfig class TestHpcAiConfig: + def test_get_openai_compatible_provider_info_default_base_when_env_unset( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + def fake_get_secret_str( + secret_name: str, default_value: Optional[Any] = None + ) -> Optional[str]: + return None + + monkeypatch.setattr( + "litellm.llms.hpc_ai.chat.transformation.get_secret_str", + fake_get_secret_str, + ) + config = HpcAiConfig() + api_base, api_key = config._get_openai_compatible_provider_info(None, None) + assert api_base == "https://api.hpc-ai.com/inference/v1" + assert api_key is None + + def test_get_openai_compatible_provider_info_api_key_from_env( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + def fake_get_secret_str( + secret_name: str, default_value: Optional[Any] = None + ) -> Optional[str]: + if secret_name == "HPC_AI_API_KEY": + return "env-hpc-ai-key" + return None + + monkeypatch.setattr( + "litellm.llms.hpc_ai.chat.transformation.get_secret_str", + fake_get_secret_str, + ) + config = HpcAiConfig() + api_base, api_key = config._get_openai_compatible_provider_info(None, None) + assert api_base == "https://api.hpc-ai.com/inference/v1" + assert api_key == "env-hpc-ai-key" + + def test_get_openai_compatible_provider_info_explicit_overrides_env( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + def fake_get_secret_str( + secret_name: str, default_value: Optional[Any] = None + ) -> Optional[str]: + if secret_name == "HPC_AI_API_BASE": + return "https://from-env.example/v1" + if secret_name == "HPC_AI_API_KEY": + return "from-env-key" + return None + + monkeypatch.setattr( + "litellm.llms.hpc_ai.chat.transformation.get_secret_str", + fake_get_secret_str, + ) + config = HpcAiConfig() + api_base, api_key = config._get_openai_compatible_provider_info( + "https://explicit.example/v1", + "explicit-key", + ) + assert api_base == "https://explicit.example/v1" + assert api_key == "explicit-key" + def test_validate_environment_sets_auth_header(self): config = HpcAiConfig() headers = {}