From 314516a9b091a25bb9562d23aeddee1eb40b376b Mon Sep 17 00:00:00 2001 From: Grainne Ni She McKnight Date: Tue, 3 Nov 2026 12:03:26 +0100 Subject: [PATCH] fix: preserve Anthropic tool references in Responses tool outputs --- .../transformation.py | 6 ++ ...test_function_call_output_normalization.py | 56 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 3e605e926f7..ca8a94186e6 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -431,6 +431,12 @@ def tool_output_to_chat_content( text_acc.append(txt) if txt: normalized_blocks.append({"type": "text", "text": txt}) + elif part_type == "tool_reference": + match cast(Mapping[str, object], part): # cast-ok: dict checked above + case {"tool_name": str(tool_name)} if tool_name: + normalized_blocks.append({"type": "tool_reference", "tool_name": tool_name}) + case _: + continue elif (media := decode_responses_media(part)) is not None: normalized_blocks.append(media) diff --git a/tests/unit/responses/litellm_completion_transformation/test_function_call_output_normalization.py b/tests/unit/responses/litellm_completion_transformation/test_function_call_output_normalization.py index fce06598d29..72f9b7d3a6e 100644 --- a/tests/unit/responses/litellm_completion_transformation/test_function_call_output_normalization.py +++ b/tests/unit/responses/litellm_completion_transformation/test_function_call_output_normalization.py @@ -7,15 +7,18 @@ we normalize it to text/image blocks or a string. """ import json +from collections.abc import Mapping from copy import deepcopy -from typing import Final +from typing import Final, cast import pytest from litellm.exceptions import BadRequestError +from litellm.litellm_core_utils.prompt_templates.factory import convert_to_anthropic_tool_result from litellm.responses.litellm_completion_transformation.transformation import ( LiteLLMCompletionResponsesConfig, ) +from litellm.types.llms.openai import ChatCompletionToolMessage, ToolMessageContentPart def _normalize_media_content(kind: str, parts: list[dict[str, object]]) -> object: @@ -188,3 +191,54 @@ def test_computer_screenshot_output_remains_structured_media() -> None: "image_url": {"url": "data:image/png;base64,AAAA", "detail": "auto"}, } ] + + +@pytest.mark.parametrize( + ("output", "expected_content"), + [ + ( + [{"type": "tool_reference", "tool_name": "get_weather"}], + [{"type": "tool_reference", "tool_name": "get_weather"}], + ), + ( + [ + {"type": "input_text", "text": "Loaded tools"}, + {"type": "tool_reference", "tool_name": "get_weather"}, + {"type": "input_text", "text": " and "}, + {"type": "tool_reference", "tool_name": "get_forecast"}, + ], + [ + {"type": "text", "text": "Loaded tools"}, + {"type": "tool_reference", "tool_name": "get_weather"}, + {"type": "text", "text": " and "}, + {"type": "tool_reference", "tool_name": "get_forecast"}, + ], + ), + ( + [ + {"type": "tool_reference"}, + {"type": "tool_reference", "tool_name": None}, + {"type": "tool_reference", "tool_name": ""}, + {"type": "tool_reference", "tool_name": "get_weather"}, + ], + [{"type": "tool_reference", "tool_name": "get_weather"}], + ), + ], + ids=["reference-only", "interleaved-text-and-references", "malformed-references-omitted"], +) +def test_function_call_output_preserves_tool_references_for_anthropic( + output: list[Mapping[str, object]], expected_content: list[ToolMessageContentPart] +) -> None: + messages: Final = ( + LiteLLMCompletionResponsesConfig._transform_responses_api_tool_call_output_to_chat_completion_message( + {"type": "function_call_output", "call_id": "call_load_tools", "output": output} + ) + ) + + assert messages == [{"role": "tool", "tool_call_id": "call_load_tools", "content": expected_content}] + message: Final = cast(ChatCompletionToolMessage, messages[0]) # cast-ok: complete tool message asserted above + assert convert_to_anthropic_tool_result(message) == { + "type": "tool_result", + "tool_use_id": "call_load_tools", + "content": expected_content, + }