From 735ace429bdfe9f4f4289755302cf75b09ff1c02 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:05:26 -0700 Subject: [PATCH] fix(anthropic): send number and boolean tool results as JSON instead of an empty string Snowflake sent json.dumps of a non-string tool result before it moved onto the shared transform, which then sent an empty string for a number or boolean, so Claude lost the answer. Mapping results were already serialized; numbers and booleans now take the same path, and None stays empty as it was on Anthropic --- litellm/litellm_core_utils/prompt_templates/factory.py | 2 +- .../test_litellm_core_utils_prompt_templates_factory.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 8c4afddc944..4d4366e0dfe 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1935,7 +1935,7 @@ def convert_to_anthropic_tool_result( ) = "" if isinstance(message["content"], str): anthropic_content = message["content"] - elif isinstance(message["content"], Mapping): + elif isinstance(message["content"], (Mapping, int, float)): anthropic_content = json.dumps(message["content"]) elif isinstance(message["content"], list): content_list: Final = message["content"] diff --git a/tests/unit/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/unit/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index ef45fd03f19..c7134c18676 100644 --- a/tests/unit/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/unit/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -3191,17 +3191,16 @@ def test_convert_to_anthropic_tool_result_image_with_cache_control(): assert result["content"][1]["cache_control"]["type"] == "ephemeral" -def test_convert_to_anthropic_tool_result_serializes_mapping_content(): +@pytest.mark.parametrize("tool_output", [{"result_key": "result_value"}, 42, 0, 3.5, True, False]) +def test_convert_to_anthropic_tool_result_serializes_non_string_content(tool_output): from litellm.litellm_core_utils.prompt_templates.factory import ( convert_to_anthropic_tool_result, ) - result = convert_to_anthropic_tool_result( - {"role": "tool", "tool_call_id": "call_dict", "content": {"result_key": "result_value"}} - ) + result = convert_to_anthropic_tool_result({"role": "tool", "tool_call_id": "call_value", "content": tool_output}) assert result["type"] == "tool_result" - assert json.loads(result["content"]) == {"result_key": "result_value"} + assert json.loads(result["content"]) == tool_output def test_convert_to_anthropic_tool_result_image_without_cache_control():