This commit is contained in:
devin-ai-integration[bot] 2026-08-27 18:18:27 -05:00 committed by GitHub
commit 2c7e32679c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 1 deletions

View file

@ -467,7 +467,11 @@ class OllamaChatCompletionResponseIterator(BaseModelResponseIterator):
tool_calls: Final = chunk["message"].get("tool_calls")
if tool_calls is not None:
for tool_call in tool_calls:
function_args = tool_call.get("function").get("arguments")
function = tool_call.get("function") or {}
function_index = function.get("index")
if function_index is not None:
tool_call["index"] = function_index
function_args = 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:

View file

@ -616,6 +616,91 @@ class TestOllamaFinishReasonLength:
), f"Expected 'stop' for natural finish, got '{result.choices[0].finish_reason}'"
class TestOllamaStreamingToolCallIndex:
"""Streaming: parallel tool calls must preserve Ollama's per-call function.index."""
def test_parallel_tool_calls_preserve_distinct_index(self):
"""
Ollama streams one tool call per chunk with the correct index nested at
function.index (0, then 1). Previously LiteLLM dropped it and every delta
was emitted with index=0, so clients that accumulate arguments by index
merged parallel calls into one malformed tool call.
"""
iterator = OllamaChatCompletionResponseIterator(
streaming_response=iter([]),
sync_stream=True,
)
first_chunk = {
"model": "qwen3:14b",
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"function": {
"index": 0,
"name": "read_file",
"arguments": {"path": "a.rs"},
}
}
],
},
"done": False,
}
second_chunk = {
"model": "qwen3:14b",
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"function": {
"index": 1,
"name": "read_file",
"arguments": {"path": "b.rs"},
}
}
],
},
"done": False,
}
first_result = iterator.chunk_parser(first_chunk)
second_result = iterator.chunk_parser(second_chunk)
assert first_result.choices[0].delta.tool_calls[0].index == 0
assert second_result.choices[0].delta.tool_calls[0].index == 1
def test_missing_function_index_falls_back_to_positional(self):
"""When Ollama omits function.index, Delta still assigns a positional index."""
iterator = OllamaChatCompletionResponseIterator(
streaming_response=iter([]),
sync_stream=True,
)
chunk = {
"model": "qwen3:14b",
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"function": {
"name": "read_file",
"arguments": {"path": "a.rs"},
}
}
],
},
"done": False,
}
result = iterator.chunk_parser(chunk)
assert result.choices[0].delta.tool_calls[0].index == 0
class TestOllamaReasoningContentStreaming:
"""Test that reasoning_content is properly extracted from all thinking chunks."""