mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-01 16:21:12 +00:00
[Fix] CI/CD - Fix Bedrock tool calling test failures with non-serializable objects and internal parameters (#17930)
* fix(bedrock): filter non-serializable objects from request params - Enhanced filter_exceptions_from_params() to filter callable objects (functions) and Logging objects - Applied filtering in Bedrock's _prepare_request_params() before deepcopy - Applied filtering to additional_request_params before JSON serialization - Prevents TypeError during deepcopy (APIConnectionError objects) and JSON serialization (functions, Logging objects) - Fixes test_bedrock_tool_calling test failures Root cause: MCP-related functions (handle_chat_completion_with_mcp, completion_callable) and litellm_logging_obj were incorrectly added to optional_params via add_provider_specific_params_to_optional_params(), which then ended up in additional_request_params. These objects should be in litellm_params, not optional_params. * fix(bedrock): filter internal MCP parameters from API requests Filter out LiteLLM internal/MCP-related parameters (skip_mcp_handler, mcp_handler_context, _skip_mcp_handler) from additional_request_params before sending to Bedrock API to prevent 'extraneous key' errors. - Added filter_internal_params() helper function in core_helpers.py - Applied filtering in Bedrock's _prepare_request_params() method - Fixes test_bedrock_completion.py::test_bedrock_tool_calling * fix: mypy type error * fix: add filter_exceptions_from_params to recursive function ignore list - Add filter_exceptions_from_params to IGNORE_FUNCTIONS in recursive_detector.py - Function is safe: has max_depth parameter (default 20) to prevent infinite recursion
This commit is contained in:
@@ -300,4 +300,103 @@ def safe_deep_copy(data):
|
||||
data["litellm_metadata"][
|
||||
"litellm_parent_otel_span"
|
||||
] = litellm_parent_otel_span
|
||||
return new_data
|
||||
return new_data
|
||||
|
||||
|
||||
def filter_exceptions_from_params(data: Any, max_depth: int = 20) -> Any:
|
||||
"""
|
||||
Recursively filter out Exception objects and callable objects from dicts/lists.
|
||||
|
||||
This is a defensive utility to prevent deepcopy failures when exception objects
|
||||
are accidentally stored in parameter dictionaries (e.g., optional_params).
|
||||
Also filters callable objects (functions) to prevent JSON serialization errors.
|
||||
Exceptions and callables should not be stored in params - this function removes them.
|
||||
|
||||
Args:
|
||||
data: The data structure to filter (dict, list, or any other type)
|
||||
max_depth: Maximum recursion depth to prevent infinite loops
|
||||
|
||||
Returns:
|
||||
Filtered data structure with Exception and callable objects removed, or None if the
|
||||
entire input was an Exception or callable
|
||||
"""
|
||||
if max_depth <= 0:
|
||||
return data
|
||||
|
||||
# Skip exception objects
|
||||
if isinstance(data, Exception):
|
||||
return None
|
||||
# Skip callable objects (functions, methods, lambdas) but not classes (type objects)
|
||||
if callable(data) and not isinstance(data, type):
|
||||
return None
|
||||
# Skip known non-serializable object types (Logging, etc.)
|
||||
obj_type_name = type(data).__name__
|
||||
if obj_type_name in ["Logging", "LiteLLMLoggingObj"]:
|
||||
return None
|
||||
|
||||
if isinstance(data, dict):
|
||||
result: dict[str, Any] = {}
|
||||
for k, v in data.items():
|
||||
# Skip exception and callable values
|
||||
if isinstance(v, Exception) or (callable(v) and not isinstance(v, type)):
|
||||
continue
|
||||
try:
|
||||
filtered = filter_exceptions_from_params(v, max_depth - 1)
|
||||
if filtered is not None:
|
||||
result[k] = filtered
|
||||
except Exception:
|
||||
# Skip values that cause errors during filtering
|
||||
continue
|
||||
return result
|
||||
elif isinstance(data, list):
|
||||
result_list: list[Any] = []
|
||||
for item in data:
|
||||
# Skip exception and callable items
|
||||
if isinstance(item, Exception) or (callable(item) and not isinstance(item, type)):
|
||||
continue
|
||||
try:
|
||||
filtered = filter_exceptions_from_params(item, max_depth - 1)
|
||||
if filtered is not None:
|
||||
result_list.append(filtered)
|
||||
except Exception:
|
||||
# Skip items that cause errors during filtering
|
||||
continue
|
||||
return result_list
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def filter_internal_params(data: dict, additional_internal_params: Optional[set] = None) -> dict:
|
||||
"""
|
||||
Filter out LiteLLM internal parameters that shouldn't be sent to provider APIs.
|
||||
|
||||
This removes internal/MCP-related parameters that are used by LiteLLM internally
|
||||
but should not be included in API requests to providers.
|
||||
|
||||
Args:
|
||||
data: Dictionary of parameters to filter
|
||||
additional_internal_params: Optional set of additional internal parameter names to filter
|
||||
|
||||
Returns:
|
||||
Filtered dictionary with internal parameters removed
|
||||
"""
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
# Known internal parameters that should never be sent to provider APIs
|
||||
internal_params = {
|
||||
"skip_mcp_handler",
|
||||
"mcp_handler_context",
|
||||
"_skip_mcp_handler",
|
||||
}
|
||||
|
||||
# Add any additional internal params if provided
|
||||
if additional_internal_params:
|
||||
internal_params.update(additional_internal_params)
|
||||
|
||||
# Filter out internal parameters
|
||||
return {
|
||||
k: v
|
||||
for k, v in data.items()
|
||||
if k not in internal_params
|
||||
}
|
||||
@@ -12,7 +12,12 @@ import httpx
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import RESPONSE_FORMAT_TOOL_NAME
|
||||
from litellm.litellm_core_utils.core_helpers import map_finish_reason
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
filter_exceptions_from_params,
|
||||
filter_internal_params,
|
||||
map_finish_reason,
|
||||
safe_deep_copy,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
_parse_content_for_reasoning,
|
||||
@@ -879,7 +884,10 @@ class AmazonConverseConfig(BaseConfig):
|
||||
self, optional_params: dict, model: str
|
||||
) -> Tuple[dict, dict, dict]:
|
||||
"""Prepare and separate request parameters."""
|
||||
inference_params = copy.deepcopy(optional_params)
|
||||
# Filter out exception objects before deepcopy to prevent deepcopy failures
|
||||
# Exceptions should not be stored in optional_params (this is a defensive fix)
|
||||
cleaned_params = filter_exceptions_from_params(optional_params)
|
||||
inference_params = safe_deep_copy(cleaned_params)
|
||||
supported_converse_params = list(
|
||||
AmazonConverseConfig.__annotations__.keys()
|
||||
) + ["top_k"]
|
||||
@@ -904,11 +912,20 @@ class AmazonConverseConfig(BaseConfig):
|
||||
inference_params = {
|
||||
k: v for k, v in inference_params.items() if k in total_supported_params
|
||||
}
|
||||
|
||||
|
||||
# Only set the topK value in for models that support it
|
||||
additional_request_params.update(
|
||||
self._handle_top_k_value(model, inference_params)
|
||||
)
|
||||
|
||||
# Filter out internal/MCP-related parameters that shouldn't be sent to the API
|
||||
# These are LiteLLM internal parameters, not API parameters
|
||||
additional_request_params = filter_internal_params(additional_request_params)
|
||||
|
||||
# Filter out non-serializable objects (exceptions, callables, logging objects, etc.)
|
||||
# from additional_request_params to prevent JSON serialization errors
|
||||
# This filters: Exception objects, callable objects (functions), Logging objects, etc.
|
||||
additional_request_params = filter_exceptions_from_params(additional_request_params)
|
||||
|
||||
return inference_params, additional_request_params, request_metadata
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ IGNORE_FUNCTIONS = [
|
||||
"_collect_argument_paths", # max depth set.
|
||||
"_split_text", # max depth set.
|
||||
"_delete_nested_value_custom", # max depth set (bounded by number of path segments).
|
||||
"filter_exceptions_from_params", # max depth set (default 20) to prevent infinite recursion.
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user