diff --git a/litellm/llms/ollama/chat/transformation.py b/litellm/llms/ollama/chat/transformation.py index 644ab187f15..59cd4c821e4 100644 --- a/litellm/llms/ollama/chat/transformation.py +++ b/litellm/llms/ollama/chat/transformation.py @@ -481,7 +481,7 @@ class OllamaChatCompletionResponseIterator(BaseModelResponseIterator): function_args = tool_call.get("function").get("arguments") if function_args is not None and len(function_args) > 0: is_function_call_complete = self._is_function_call_complete(function_args) - if is_function_call_complete: + if is_function_call_complete and "id" not in tool_call: tool_call["id"] = str(uuid.uuid4()) # PROCESS REASONING CONTENT diff --git a/tests/unit/llms/ollama/test_ollama_chat_transformation.py b/tests/unit/llms/ollama/test_ollama_chat_transformation.py index 1a0bbe171a9..3f80c84bfd6 100644 --- a/tests/unit/llms/ollama/test_ollama_chat_transformation.py +++ b/tests/unit/llms/ollama/test_ollama_chat_transformation.py @@ -947,6 +947,68 @@ class TestOllamaToolCallTransformation: assert "tool_call_id" in tool_msg, "tool_call_id must be forwarded to Ollama" assert tool_msg["tool_call_id"] == "call_abc123" + def test_streaming_preserves_existing_tool_call_id(self): + """Streaming: if a tool_call in a chunk already has an id, OllamaChatCompletionResponseIterator + must preserve that id instead of generating a new uuid. + """ + iterator = OllamaChatCompletionResponseIterator( + streaming_response=iter([]), + sync_stream=True, + ) + + tool_chunk = { + "model": "qwen3:8b", + "message": { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_pre_existing_id_123", + "function": { + "name": "get_weather", + "arguments": {"city": "San Francisco"}, + }, + } + ], + }, + "done": False, + } + + result = iterator.chunk_parser(tool_chunk) + assert result.choices[0].delta.tool_calls is not None + assert result.choices[0].delta.tool_calls[0]["id"] == "call_pre_existing_id_123" + + def test_streaming_generates_id_when_missing(self): + """Streaming: if a tool_call in a chunk lacks an id, OllamaChatCompletionResponseIterator + generates an id when function arguments are complete. + """ + iterator = OllamaChatCompletionResponseIterator( + streaming_response=iter([]), + sync_stream=True, + ) + + tool_chunk = { + "model": "qwen3:8b", + "message": { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "function": { + "name": "get_weather", + "arguments": {"city": "San Francisco"}, + }, + } + ], + }, + "done": False, + } + + result = iterator.chunk_parser(tool_chunk) + assert result.choices[0].delta.tool_calls is not None + assert "id" in result.choices[0].delta.tool_calls[0] + assert result.choices[0].delta.tool_calls[0]["id"] is not None + class TestOllamaStreamingUsage: @staticmethod