From 765b129ab49badf60f08cab8a19ffcbb0f389ed7 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:23:59 -0700 Subject: [PATCH] fix(responses): keep a dict tool call's thought signature on the streamed function_call done item --- .../streaming_iterator.py | 7 +++++- .../test_litellm_completion_responses.py | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/litellm/responses/litellm_completion_transformation/streaming_iterator.py b/litellm/responses/litellm_completion_transformation/streaming_iterator.py index 0721968441c..394f221e0d0 100644 --- a/litellm/responses/litellm_completion_transformation/streaming_iterator.py +++ b/litellm/responses/litellm_completion_transformation/streaming_iterator.py @@ -93,7 +93,12 @@ def _tool_call_objects(tool_calls: object) -> tuple[object, ...]: def _tool_call_provider_specific_fields(tool_call: object) -> Mapping[str, object]: - fields: Final[object] = getattr(tool_call, "provider_specific_fields", None) + mapping: Final = as_str_mapping(tool_call) + fields: Final[object] = ( + mapping.get("provider_specific_fields") + if mapping is not None + else getattr(tool_call, "provider_specific_fields", None) + ) return {"provider_specific_fields": fields} if fields else {} diff --git a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py index 7a5254b64f4..a6a1335fdc4 100644 --- a/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/unit/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -4746,6 +4746,29 @@ class TestEnsureOutputItemContentPartAdded: assert done.item.name == "spawn_agent" assert done.item.namespace == "collaboration" + def test_streaming_final_only_dict_tool_call_keeps_its_provider_specific_fields(self): + from unittest.mock import MagicMock + + iterator = self._make_iterator() + iterator.tool_call_provider_fields = True + provider_specific_fields = {"thought_signature": "sig-" + "x" * 512} + message = MagicMock() + message.tool_calls = [ + { + "id": "call_1", + "function": {"name": "get_weather", "arguments": '{"city":"Paris"}'}, + "provider_specific_fields": provider_specific_fields, + } + ] + complete_response = MagicMock() + complete_response.choices = [MagicMock(message=message)] + + iterator._queue_final_tool_call_done_events(complete_response) + + done = iterator._pending_tool_events[-1] + assert done.type == "response.output_item.done" + assert done.item.provider_specific_fields == provider_specific_fields + def test_streaming_top_level_function_collision_stays_unnamespaced(self): iterator = self._make_iterator() iterator.responses_api_request = {