Merge pull request #23500 from BerriAI/litellm_litellm-mypy-errors-28de

[Fix] MyPy Errors
This commit is contained in:
yuneng-jiang
2026-03-12 22:46:10 -07:00
committed by GitHub
25 changed files with 139 additions and 59 deletions
+1 -1
View File
@@ -1897,7 +1897,7 @@ if TYPE_CHECKING:
supports_reasoning: Callable[..., bool]
acreate: Callable[..., Any]
get_max_tokens: Callable[..., int]
get_model_info: Callable[..., _ModelInfoType]
get_model_info: Callable[..., _ModelInfoType] # type: ignore[no-redef]
register_prompt_template: Callable[..., None]
validate_environment: Callable[..., dict]
check_valid_key: Callable[..., bool]
@@ -2283,7 +2283,7 @@ def sanitize_messages_for_tool_calling(
for idx, msg in enumerate(sanitized_messages):
role = msg.get("role")
tcid = msg.get("tool_call_id") if role in ["tool", "function"] else None
if tcid:
if tcid and isinstance(tcid, str):
if tcid in seen_in_block:
# Mark the earlier occurrence for removal (keep latest)
duplicates_to_remove.add(seen_in_block[tcid])
@@ -2581,13 +2581,11 @@ def anthropic_messages_pt( # noqa: PLR0915
# Build the text block if content is a non-empty string
text_element = None
if (
isinstance(assistant_content_block.get("content"), str)
and assistant_content_block["content"]
):
_acb_content = assistant_content_block.get("content")
if isinstance(_acb_content, str) and _acb_content:
_anthropic_text_content_element = AnthropicMessagesTextParam(
type="text",
text=assistant_content_block["content"],
text=_acb_content,
)
_content_element = add_cache_control_to_content(
anthropic_content_element=_anthropic_text_content_element,
@@ -2682,9 +2680,10 @@ def anthropic_messages_pt( # noqa: PLR0915
_content_is_list = "content" in assistant_content_block and isinstance(
assistant_content_block["content"], list
)
_content_list = assistant_content_block.get("content") if _content_is_list else None
_list_has_thinking = False
if _content_is_list:
for _item in assistant_content_block["content"]:
if _content_is_list and _content_list is not None:
for _item in _content_list:
if isinstance(_item, dict) and _item.get("type") in (
"thinking",
"redacted_thinking",
@@ -2696,8 +2695,10 @@ def anthropic_messages_pt( # noqa: PLR0915
thinking_blocks is not None and not _list_has_thinking
): # IMPORTANT: ADD THIS FIRST, ELSE ANTHROPIC WILL RAISE AN ERROR
assistant_content.extend(thinking_blocks)
if _content_is_list:
for m in assistant_content_block["content"]:
if _content_is_list and _content_list is not None:
for m in _content_list:
if not isinstance(m, dict):
continue
# handle thinking blocks
thinking_block = cast(str, m.get("thinking", ""))
text_block = cast(str, m.get("text", ""))
@@ -14,7 +14,7 @@ Anthropic Files API endpoints:
import calendar
import time
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, cast
import httpx
from openai.types.file_deleted import FileDeleted
@@ -79,7 +79,7 @@ class AnthropicFilesConfig(BaseFilesConfig):
return AnthropicError(
status_code=status_code,
message=error_message,
headers=headers,
headers=cast(httpx.Headers, headers) if isinstance(headers, dict) else headers,
)
def validate_environment(
@@ -230,8 +230,8 @@ class BlackForestLabsImageEditConfig(BaseImageEditConfig):
def transform_image_edit_request(
self,
model: str,
prompt: str,
image: FileTypes,
prompt: Optional[str],
image: Optional[FileTypes],
image_edit_optional_request_params: Dict,
litellm_params: GenericLiteLLMParams,
headers: dict,
@@ -259,7 +259,12 @@ class BlackForestLabsImageGenerationConfig(BaseImageGenerationConfig):
raw_response: httpx.Response,
model_response: ImageResponse,
logging_obj: LiteLLMLoggingObj,
**kwargs,
request_data: dict,
optional_params: dict,
litellm_params: dict,
encoding: Any,
api_key: Optional[str] = None,
json_mode: Optional[bool] = None,
) -> ImageResponse:
"""
Transform Black Forest Labs response to OpenAI-compatible ImageResponse.
+1 -1
View File
@@ -5,7 +5,7 @@ Documentation: https://api-dashboard.search.brave.com/app/documentation/web-sear
from __future__ import annotations
from datetime import datetime, timezone
from dateutil import parser
from dateutil import parser # type: ignore[import-untyped]
from typing import Dict, List, Literal, Optional, TypedDict, Union
import httpx
import re
+18 -13
View File
@@ -3036,10 +3036,11 @@ class BaseLLMHTTPHandler:
elif isinstance(transformed_request, dict) and "file" in transformed_request:
# Handle multipart form-data uploads (e.g., Anthropic Files API)
# The dict contains tuples suitable for httpx's `files` parameter
file_request = cast(Dict[str, Any], transformed_request)
upload_response = sync_httpx_client.post(
url=api_base,
headers=headers,
files=transformed_request,
files=file_request,
timeout=timeout,
)
else:
@@ -4870,10 +4871,12 @@ class BaseLLMHTTPHandler:
timeout=timeout,
)
except Exception as e:
raise self._handle_error(
e=e,
provider_config=provider_config,
)
if provider_config is not None:
raise self._handle_error(
e=e,
provider_config=provider_config,
)
raise
async def async_realtime_calls_handler(
self,
@@ -4954,10 +4957,12 @@ class BaseLLMHTTPHandler:
timeout=timeout,
)
except Exception as e:
raise self._handle_error(
e=e,
provider_config=provider_config,
)
if provider_config is not None:
raise self._handle_error(
e=e,
provider_config=provider_config,
)
raise
async def async_responses_websocket(
self,
@@ -8026,7 +8031,7 @@ class BaseLLMHTTPHandler:
url = api_base
params = {}
params: Dict[str, Any] = {}
if after is not None:
params["after"] = after
if before is not None:
@@ -8108,7 +8113,7 @@ class BaseLLMHTTPHandler:
url = api_base
params = {}
params: Dict[str, Any] = {}
if after is not None:
params["after"] = after
if before is not None:
@@ -8170,7 +8175,7 @@ class BaseLLMHTTPHandler:
url = f"{api_base}/{vector_store_id}"
request_body = dict(vector_store_update_optional_params)
request_body: Dict[str, Any] = dict(vector_store_update_optional_params)
# Clean metadata to only include string values (OpenAI requirement)
if "metadata" in request_body and request_body["metadata"] is not None:
@@ -8253,7 +8258,7 @@ class BaseLLMHTTPHandler:
url = f"{api_base}/{vector_store_id}"
request_body = dict(vector_store_update_optional_params)
request_body: Dict[str, Any] = dict(vector_store_update_optional_params)
# Clean metadata to only include string values (OpenAI requirement)
if "metadata" in request_body and request_body["metadata"] is not None:
@@ -63,16 +63,19 @@ class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
def _ensure_message_type(
self, input: Union[str, ResponseInputParam]
) -> Union[str, List[Dict[str, Any]]]:
) -> Union[str, ResponseInputParam]:
"""Ensure list input items have type='message' (required by Perplexity)."""
if isinstance(input, str):
return input
if isinstance(input, list):
result = []
result: List[Any] = []
for item in input:
if isinstance(item, dict) and "type" not in item:
item = {**item, "type": "message"}
result.append(item)
new_item = dict(item) # convert to plain dict to avoid TypedDict checking
new_item["type"] = "message"
result.append(new_item)
else:
result.append(item)
return result
return input
@@ -153,6 +153,7 @@ class GoogleBatchEmbeddings(VertexLLM):
is_multimodal = _is_multimodal_input(input)
use_embed_content = is_multimodal or (custom_llm_provider == "vertex_ai")
mode: Literal["embedding", "batch_embedding"]
if use_embed_content:
mode = "embedding"
else:
@@ -200,6 +201,7 @@ class GoogleBatchEmbeddings(VertexLLM):
)
### TRANSFORMATION (sync path) ###
request_data: Any
if use_embed_content:
resolved_files = {}
if api_key:
@@ -190,7 +190,7 @@ class VertexAILlama3StreamingHandler(OpenAIChatCompletionStreamingHandler):
],
)
# Modify current chunk to be the first chunk with role but no finish_reason
result.choices[0].finish_reason = None
result.choices[0].finish_reason = None # type: ignore[assignment]
delta.role = "assistant"
# Ensure content is empty string for first chunk, not None
if delta.content is None:
+1 -1
View File
@@ -7700,7 +7700,7 @@ async def acount_tokens(
local_count = litellm.token_counter(
model=model,
messages=fallback_messages,
tools=tools,
tools=tools, # type: ignore[arg-type]
)
return TokenCountResponse(
+3 -3
View File
@@ -142,9 +142,9 @@ def decrypt_credentials(
"aws_session_token",
]
for field in secret_fields:
value = credentials.get(field)
if value is not None:
credentials[field] = decrypt_value_helper(
value = credentials.get(field) # type: ignore[literal-required]
if value is not None and isinstance(value, str):
credentials[field] = decrypt_value_helper( # type: ignore[literal-required]
value=value,
key=field,
exception_type="debug",
+25 -3
View File
@@ -530,6 +530,8 @@ class ProxyBaseLLMRequestProcessing:
"aresponses",
"_arealtime",
"_aresponses_websocket",
"acreate_realtime_client_secret",
"arealtime_calls",
"aget_responses",
"adelete_responses",
"acancel_responses",
@@ -553,6 +555,10 @@ class ProxyBaseLLMRequestProcessing:
"allm_passthrough_route",
"avector_store_search",
"avector_store_create",
"avector_store_retrieve",
"avector_store_list",
"avector_store_update",
"avector_store_delete",
"avector_store_file_create",
"avector_store_file_list",
"avector_store_file_retrieve",
@@ -774,20 +780,36 @@ class ProxyBaseLLMRequestProcessing:
"aembedding",
"aresponses",
"_arealtime",
"_aresponses_websocket",
"acreate_realtime_client_secret",
"arealtime_calls",
"aget_responses",
"adelete_responses",
"acancel_responses",
"acompact_responses",
"acreate_batch",
"aretrieve_batch",
"alist_batches",
"acancel_batch",
"afile_content",
"afile_retrieve",
"afile_delete",
"atext_completion",
"aimage_edit",
"acreate_fine_tuning_job",
"acancel_fine_tuning_job",
"alist_fine_tuning_jobs",
"aretrieve_fine_tuning_job",
"alist_input_items",
"aimage_edit",
"agenerate_content",
"agenerate_content_stream",
"allm_passthrough_route",
"avector_store_search",
"avector_store_create",
"avector_store_retrieve",
"avector_store_list",
"avector_store_update",
"avector_store_delete",
"avector_store_file_create",
"avector_store_file_list",
"avector_store_file_retrieve",
@@ -815,8 +837,8 @@ class ProxyBaseLLMRequestProcessing:
"aget_interaction",
"adelete_interaction",
"acancel_interaction",
"acancel_batch",
"afile_delete",
"asend_message",
"call_mcp_tool",
"acreate_eval",
"alist_evals",
"aget_eval",
@@ -315,6 +315,7 @@ class PanwPrismaAirsHandler(CustomGuardrail):
panw_metadata["litellm_trace_id"] = metadata["litellm_trace_id"]
# Build contents: tool_event takes priority, else prompt/response text
contents: List[Dict[str, Any]]
if tool_event is not None:
contents = [{"tool_event": tool_event}]
else:
@@ -1485,7 +1486,7 @@ class PanwPrismaAirsHandler(CustomGuardrail):
detail = (
e.detail if isinstance(e.detail, dict) else {"message": str(e.detail)}
)
error_obj = dict(detail.get("error", detail))
error_obj: Dict[str, Any] = dict(detail.get("error", detail)) # type: ignore[arg-type]
error_obj["code"] = e.status_code
yield f"data: {json.dumps({'error': error_obj})}\n\n"
except Exception as e:
@@ -106,9 +106,9 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
if (self.output_parse_pii or self.apply_to_output) and not logging_only:
current_hook = self.event_hook
if isinstance(current_hook, str) and current_hook != "post_call":
self.event_hook = [current_hook, "post_call"]
self.event_hook = cast(List[GuardrailEventHooks], [current_hook, "post_call"])
elif isinstance(current_hook, list) and "post_call" not in current_hook:
self.event_hook = current_hook + ["post_call"]
self.event_hook = cast(List[GuardrailEventHooks], current_hook + ["post_call"])
self.pii_entities_config: Dict[Union[PiiEntityType, str], PiiAction] = (
pii_entities_config or {}
)
@@ -908,7 +908,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
if self.apply_to_output is True:
if self._is_anthropic_message_response(response):
return await self._process_anthropic_response_for_pii(
response=response, request_data=data, mode="mask"
response=cast(dict, response), request_data=data, mode="mask"
)
return await self._mask_output_response(
response=response, request_data=data
@@ -927,7 +927,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail):
)
elif self._is_anthropic_message_response(response):
await self._process_anthropic_response_for_pii(
response=response, request_data=data, mode="unmask"
response=cast(dict, response), request_data=data, mode="unmask"
)
return response
@@ -259,6 +259,7 @@ class SemanticToolFilterHook(CustomLogger):
user_api_key_dict: "UserAPIKeyAuth",
response: Any,
request_headers: Optional[Dict[str, str]] = None,
litellm_call_info: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, str]]:
"""Add semantic filter stats and tool names to response headers."""
from litellm.constants import MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH
@@ -1776,11 +1776,13 @@ async def _validate_mcp_servers_for_key_update(
user_api_key_cache=user_api_key_cache,
check_db_only=True,
)
object_permission_dict = (
data.object_permission.model_dump()
if hasattr(data.object_permission, "model_dump")
else data.object_permission
)
object_permission_dict: Optional[dict] = None
if data.object_permission is not None:
object_permission_dict = (
data.object_permission.model_dump()
if hasattr(data.object_permission, "model_dump")
else dict(data.object_permission) # type: ignore[arg-type]
)
await validate_key_mcp_servers_against_team(
object_permission=object_permission_dict,
team_obj=effective_team_obj,
+15
View File
@@ -173,8 +173,21 @@ async def route_request( # noqa: PLR0915 - Complex routing function, refactorin
"agenerate_content",
"agenerate_content_stream",
"allm_passthrough_route",
"acreate_batch",
"aretrieve_batch",
"alist_batches",
"afile_content",
"afile_retrieve",
"acreate_fine_tuning_job",
"acancel_fine_tuning_job",
"alist_fine_tuning_jobs",
"aretrieve_fine_tuning_job",
"avector_store_search",
"avector_store_create",
"avector_store_retrieve",
"avector_store_list",
"avector_store_update",
"avector_store_delete",
"avector_store_file_create",
"avector_store_file_list",
"avector_store_file_retrieve",
@@ -207,6 +220,8 @@ async def route_request( # noqa: PLR0915 - Complex routing function, refactorin
"aget_interaction",
"adelete_interaction",
"acancel_interaction",
"asend_message",
"call_mcp_tool",
"acancel_batch",
"afile_delete",
"acreate_eval",
@@ -386,7 +386,7 @@ async def vector_store_list(
version,
)
data = {}
data: dict = {}
if after is not None:
data["after"] = after
if before is not None:
@@ -410,11 +410,12 @@ class LiteLLMCompletionResponsesConfig:
else getattr(new_msg, "role", None)
)
if new_role == "assistant":
new_tcs = (
_raw_tcs = (
new_msg.get("tool_calls")
if isinstance(new_msg, dict)
else getattr(new_msg, "tool_calls", None)
) or []
)
new_tcs: list = _raw_tcs if isinstance(_raw_tcs, list) else []
for tc in new_tcs:
LiteLLMCompletionResponsesConfig._add_tool_call_to_assistant(
last_msg, tc
+3 -3
View File
@@ -19,11 +19,11 @@ if TYPE_CHECKING:
GenerateContentRequestParametersDict = _genai_types._GenerateContentParametersDict
ToolConfigDict = _genai_types.ToolConfigDict
class GenerateContentRequestDict(GenerateContentRequestParametersDict): # type: ignore[misc]
class GenerateContentRequestDict(GenerateContentRequestParametersDict): # type: ignore[misc, valid-type]
generationConfig: Optional[Any]
tools: Optional[ToolConfigDict] # type: ignore[assignment]
tools: Optional[ToolConfigDict] # type: ignore[assignment, valid-type]
class GenerateContentResponse(GoogleGenAIGenerateContentResponse, BaseLiteLLMOpenAIResponseObject): # type: ignore[misc]
class GenerateContentResponse(GoogleGenAIGenerateContentResponse, BaseLiteLLMOpenAIResponseObject): # type: ignore[misc, valid-type]
_hidden_params: dict = {}
pass
+8
View File
@@ -1052,6 +1052,14 @@ OpenAIImageGenerationOptionalParams = Literal[
"size",
"style",
"user",
"seed",
"safety_tolerance",
"prompt_upsampling",
"raw",
"num_images",
"image_url",
"image_prompt_strength",
"aspect_ratio",
]
OpenAIImageEditOptionalParams = Literal[
+1 -1
View File
@@ -1663,7 +1663,7 @@ class StreamingChoices(OpenAIObject):
if finish_reason:
self.finish_reason = map_finish_reason(finish_reason)
else:
self.finish_reason = None
self.finish_reason = None # type: ignore[assignment]
self.index = index
if delta is not None:
if isinstance(delta, Delta):
+2
View File
@@ -8141,6 +8141,8 @@ class ProviderConfigManager:
raise ValueError(f"Provider {provider.value} not found")
return create_config_class(provider_config)()
return None
@staticmethod
def get_provider_embedding_config(
model: str,
@@ -282,6 +282,10 @@ class TestBlackForestLabsImageGenerationTransformation:
raw_response=mock_response,
model_response=model_response,
logging_obj=self.logging_obj,
request_data={},
optional_params={},
litellm_params={},
encoding=None,
)
assert len(result.data) == 1
@@ -306,6 +310,10 @@ class TestBlackForestLabsImageGenerationTransformation:
raw_response=mock_response,
model_response=model_response,
logging_obj=self.logging_obj,
request_data={},
optional_params={},
litellm_params={},
encoding=None,
)
assert len(result.data) == 2
@@ -329,6 +337,10 @@ class TestBlackForestLabsImageGenerationTransformation:
raw_response=mock_response,
model_response=model_response,
logging_obj=self.logging_obj,
request_data={},
optional_params={},
litellm_params={},
encoding=None,
)
def test_get_error_class(self):