From f126f75995ba4b3ca28beabfce365a2172ff2cf5 Mon Sep 17 00:00:00 2001 From: Danial Khan <83788585+danialkhan02@users.noreply.github.com> Date: Wed, 7 Jan 2026 14:17:28 -0500 Subject: [PATCH] feat: pass server_tool_use and tool_search_tool_result blocks to anthropic (#18770) * feat: Allow message types of server_tool_use and tool_search_tool_result to reach anthropic * test: anthropic server tool use pass through testing --- .../prompt_templates/factory.py | 8 ++ ...llm_core_utils_prompt_templates_factory.py | 91 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 12570a02de..f359dbbb70 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -2137,6 +2137,14 @@ def anthropic_messages_pt( # noqa: PLR0915 assistant_content.append( cast(AnthropicMessagesTextParam, _cached_message) ) + # handle server_tool_use blocks (tool search, web search, etc.) + # Pass through as-is since these are Anthropic-native content types + elif m.get("type", "") == "server_tool_use": + assistant_content.append(m) # type: ignore + # handle tool_search_tool_result blocks + # Pass through as-is since these are Anthropic-native content types + elif m.get("type", "") == "tool_search_tool_result": + assistant_content.append(m) # type: ignore elif ( "content" in assistant_content_block and isinstance(assistant_content_block["content"], str) diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index c8fe6efeaa..4914ec0bfb 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -1137,3 +1137,94 @@ def test_bedrock_create_bedrock_block_different_document_formats(): assert f"DocumentPDFmessages_" in block["document"]["name"] assert block["document"]["name"].endswith(f"_{format_type}") assert block["document"]["format"] == format_type + + +def test_anthropic_messages_pt_server_tool_use_passthrough(): + """ + Test that anthropic_messages_pt passes through server_tool_use and + tool_search_tool_result blocks in assistant message content. + + These are Anthropic-native content types used for tool search functionality + that need to be preserved when reconstructing multi-turn conversations. + + Fixes: https://github.com/BerriAI/litellm/issues/XXXXX + """ + from litellm.litellm_core_utils.prompt_templates.factory import anthropic_messages_pt + + messages = [ + { + "role": "user", + "content": "I need help with time information." + }, + { + "role": "assistant", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_01ABC123", + "name": "tool_search_tool_regex", + "input": {"query": ".*time.*"} + }, + { + "type": "tool_search_tool_result", + "tool_use_id": "srvtoolu_01ABC123", + "content": { + "type": "tool_search_tool_search_result", + "tool_references": [ + {"type": "tool_reference", "tool_name": "get_time"} + ] + } + }, + { + "type": "text", + "text": "I found the time tool. How can I help you?" + } + ], + }, + { + "role": "user", + "content": "What's the time in New York?" + }, + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-sonnet-4-5-20250929", + llm_provider="anthropic", + ) + + # Verify we have 3 messages (user, assistant, user) + assert len(result) == 3 + + # Verify the assistant message content + assistant_msg = result[1] + assert assistant_msg["role"] == "assistant" + assert isinstance(assistant_msg["content"], list) + + # Find the different content block types + content_types = [block.get("type") for block in assistant_msg["content"]] + + # Verify server_tool_use block is preserved + assert "server_tool_use" in content_types + server_tool_use_block = next( + b for b in assistant_msg["content"] if b.get("type") == "server_tool_use" + ) + assert server_tool_use_block["id"] == "srvtoolu_01ABC123" + assert server_tool_use_block["name"] == "tool_search_tool_regex" + assert server_tool_use_block["input"] == {"query": ".*time.*"} + + # Verify tool_search_tool_result block is preserved + assert "tool_search_tool_result" in content_types + tool_result_block = next( + b for b in assistant_msg["content"] if b.get("type") == "tool_search_tool_result" + ) + assert tool_result_block["tool_use_id"] == "srvtoolu_01ABC123" + assert tool_result_block["content"]["type"] == "tool_search_tool_search_result" + assert tool_result_block["content"]["tool_references"][0]["tool_name"] == "get_time" + + # Verify text block is also preserved + assert "text" in content_types + text_block = next( + b for b in assistant_msg["content"] if b.get("type") == "text" + ) + assert text_block["text"] == "I found the time tool. How can I help you?"