From 2c2cef28d5f3018a5c055ba7d385fde7c4169955 Mon Sep 17 00:00:00 2001 From: Apeksha_2022 <70309060+Apeksha9389@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:31:57 +0530 Subject: [PATCH 1/2] perf(bedrock): drop the redundant deep copy of Converse request params --- litellm/llms/bedrock/chat/converse_transformation.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index cfbb9956988..85b1ec7f709 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -24,7 +24,6 @@ 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 ( @@ -1645,10 +1644,13 @@ class AmazonConverseConfig(BaseConfig): # ``_handle_reasoning_effort_parameter`` so it does not linger on the # caller's ``optional_params`` after the transformation returns. anthropic_output_config_already_normalized = bool(optional_params.pop("_output_config_normalized", False)) - # 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: Final = filter_exceptions_from_params(optional_params) - inference_params = safe_deep_copy(cleaned_params) + # Filter out exception objects (they should not be stored in optional_params; this is a + # defensive fix). The filter rebuilds every dict and list, so its result is already + # independent of the caller's params: deep-copying it again only doubles the allocation, + # which for large tool lists triggers long garbage-collection pauses on the event loop. + inference_params: dict = filter_exceptions_from_params( # pyright: ignore[reportMissingTypeArgument] # mutable-ok: popped and split below, untyped like the deep copy it replaces + optional_params + ) supported_converse_params: Final = list(AmazonConverseConfig.__annotations__.keys()) + ["top_k"] supported_tool_call_params: Final = ["tools", "tool_choice"] supported_config_params: Final = list(self.get_config_blocks().keys()) From bbf5d36a3263a404c274fb9e9a1e6854c2089e82 Mon Sep 17 00:00:00 2001 From: Apeksha_2022 <70309060+Apeksha9389@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:32:40 +0530 Subject: [PATCH 2/2] test(bedrock): cover copying Converse params only once --- .../chat/test_converse_transformation.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/unit/llms/bedrock/chat/test_converse_transformation.py b/tests/unit/llms/bedrock/chat/test_converse_transformation.py index 6c996a7ce75..a58f3cdfe08 100644 --- a/tests/unit/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/unit/llms/bedrock/chat/test_converse_transformation.py @@ -415,6 +415,48 @@ def test_bedrock_empty_text_block_is_removed_from_converse_request() -> None: assert request["messages"] == [{"role": "user", "content": [{"text": "Hey, how's it going?"}]}] +class _DeepCopyCountingStr(str): + deep_copies = 0 + + def __deepcopy__(self, memo: dict) -> "_DeepCopyCountingStr": + type(self).deep_copies += 1 + return self + + +def test_bedrock_converse_request_copies_params_once() -> None: + description: Final = _DeepCopyCountingStr("City to look up") + tools: Final = [ + { + "type": "function", + "function": { + "name": "get_weather", + "parameters": { + "type": "object", + "properties": {"city": {"type": "string", "description": description}}, + "required": ["city"], + }, + }, + } + ] + snapshot: Final = copy.deepcopy(tools) + _DeepCopyCountingStr.deep_copies = 0 + + request: Final = AmazonConverseConfig()._transform_request( + model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[{"role": "user", "content": "Weather in Paris?"}], + optional_params={"maxTokens": 10, "tools": tools}, + litellm_params={}, + headers={}, + ) + + # one copy, by the tool converter that edits the schema; none for the params as a whole + assert _DeepCopyCountingStr.deep_copies == 1 + tool_spec: Final = request["toolConfig"]["tools"][0]["toolSpec"] + assert tool_spec["name"] == "get_weather" + assert tool_spec["inputSchema"]["json"]["properties"]["city"]["description"] == "City to look up" + assert tools == snapshot + + @pytest.mark.asyncio async def test_bedrock_max_completion_tokens_become_converse_max_tokens() -> None: requests: Final[list[httpx.Request]] = []