diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 23c1965f68e..63ae42a776c 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -1951,6 +1951,7 @@ class AmazonConverseConfig(BaseConfig): message: Message, tools: Optional[List[ToolBlock]] = None, initial_finish_reason: Optional[str] = None, + enable_text_tool_call_parser: bool = False, ) -> Tuple[Message, Optional[str]]: """ Apply tool call transformation to a message. @@ -1979,6 +1980,8 @@ class AmazonConverseConfig(BaseConfig): message.content = None returned_finish_reason = "tool_calls" except Exception: + if not enable_text_tool_call_parser: + return message, returned_finish_reason if message.content is not None: tool_call = self._text_content_tool_call_transformation( message.content, tools @@ -2301,6 +2304,9 @@ class AmazonConverseConfig(BaseConfig): message=_message, tools=text_tool_call_tools, initial_finish_reason=initial_finish_reason, + enable_text_tool_call_parser=optional_params.get( + "enable_text_tool_call_parser", False + ), ) model_response.choices = [ litellm.Choices( diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index 609e49bcbd8..5a2d7ebdac9 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -259,7 +259,10 @@ def test_apply_tool_call_transformation_parses_function_parameter_text(): transformed_message, finish_reason = ( config.apply_tool_call_transformation_if_needed( - message, _read_file_tool(), initial_finish_reason="stop" + message, + _read_file_tool(), + initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -283,7 +286,10 @@ def test_apply_tool_call_transformation_parses_tool_use_xml_text(): transformed_message, finish_reason = ( config.apply_tool_call_transformation_if_needed( - message, _read_file_tool(), initial_finish_reason="stop" + message, + _read_file_tool(), + initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -304,7 +310,10 @@ def test_apply_tool_call_transformation_parses_bare_tool_name_json_text(): transformed_message, finish_reason = ( config.apply_tool_call_transformation_if_needed( - message, _read_file_tool(), initial_finish_reason="stop" + message, + _read_file_tool(), + initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -319,7 +328,10 @@ def test_apply_tool_call_transformation_parses_bare_zero_arg_tool_call(): transformed_message, finish_reason = ( config.apply_tool_call_transformation_if_needed( - message, _read_file_tool(), initial_finish_reason="stop" + message, + _read_file_tool(), + initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -345,7 +357,10 @@ def test_apply_tool_call_transformation_parses_tool_call_json_tag_text(): transformed_message, finish_reason = ( config.apply_tool_call_transformation_if_needed( - message, _read_file_tool(), initial_finish_reason="stop" + message, + _read_file_tool(), + initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -369,6 +384,7 @@ def test_apply_tool_call_transformation_ignores_text_for_unknown_tool_name(): } ], initial_finish_reason="stop", + enable_text_tool_call_parser=True, ) ) @@ -377,7 +393,24 @@ def test_apply_tool_call_transformation_ignores_text_for_unknown_tool_name(): assert transformed_message.tool_calls is None -def test_transform_response_uses_bedrock_tool_config_for_text_tool_calls(): +def test_apply_tool_call_transformation_does_not_promote_text_by_default(): + from litellm.types.utils import Message + + config = AmazonConverseConfig() + original_content = "\nread_file\n{}" + message = Message(role="assistant", content=original_content) + + transformed_message, finish_reason = ( + config.apply_tool_call_transformation_if_needed( + message, _read_file_tool(), initial_finish_reason="stop" + ) + ) + + assert finish_reason == "stop" + assert transformed_message.content == original_content + assert transformed_message.tool_calls is None + +def test_transform_response_does_not_promote_text_tool_calls_by_default(): import httpx config = AmazonConverseConfig() @@ -432,6 +465,67 @@ def test_transform_response_uses_bedrock_tool_config_for_text_tool_calls(): encoding=None, ) + choice = model_response.choices[0] + assert choice.finish_reason == "stop" + assert choice.message.content is not None + assert choice.message.tool_calls is None + + +def test_transform_response_uses_opt_in_text_tool_call_parser(): + import httpx + + config = AmazonConverseConfig() + raw_response = httpx.Response( + status_code=200, + json={ + "output": { + "message": { + "role": "assistant", + "content": [ + { + "text": ( + "\n\n" + 'read_file\n' + 'C:\\Projects\\redaigo\\scripts\\run_etf_v13.py\n' + '0\n' + '3000\n' + "" + ) + } + ], + } + }, + "stopReason": "end_turn", + "usage": {"inputTokens": 1, "outputTokens": 1, "totalTokens": 2}, + }, + ) + + model_response = config._transform_response( + model="bedrock/converse/claude-opus-4.6", + response=raw_response, + model_response=ModelResponse(), + stream=False, + logging_obj=None, + optional_params={"enable_text_tool_call_parser": True}, + api_key=None, + data=json.dumps( + { + "toolConfig": { + "tools": [ + { + "toolSpec": { + "name": "read_file", + "inputSchema": {"json": {}}, + } + } + ] + } + } + ), + messages=[], + encoding=None, + ) + choice = model_response.choices[0] _assert_read_file_tool_call(choice.message, choice.finish_reason)