mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
Merge f92ef4da3a into 1af7a403c6
This commit is contained in:
commit
c0f1bd986f
2 changed files with 101 additions and 2 deletions
|
|
@ -468,7 +468,7 @@ class OllamaChatCompletionResponseIterator(BaseModelResponseIterator):
|
|||
|
||||
# process tool calls - if complete function arg - add id to tool call
|
||||
tool_calls: Final = chunk["message"].get("tool_calls")
|
||||
if tool_calls is not None:
|
||||
if tool_calls:
|
||||
self.seen_tool_calls = True
|
||||
for tool_call in tool_calls:
|
||||
function_args = tool_call.get("function").get("arguments")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from typing import cast
|
||||
from typing import Final, cast
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -478,6 +478,105 @@ class TestOllamaToolCalling:
|
|||
assert result.choices[0].finish_reason == "stop"
|
||||
assert result.choices[0].message.tool_calls is None
|
||||
|
||||
def test_tool_calls_streaming(self):
|
||||
"""Streaming: native Ollama tool_calls must surface with string arguments, and the
|
||||
terminal chunk must report finish_reason='tool_calls'.
|
||||
|
||||
Ollama /api/chat streams the tool call in a non-final chunk (done=False) and then
|
||||
sends a SEPARATE terminal chunk (done=True, done_reason='stop') that carries no
|
||||
tool_calls. finish_reason must still be upgraded to 'tool_calls' on that terminal
|
||||
chunk so clients execute the tool. Regression guard for
|
||||
https://github.com/BerriAI/litellm/issues/24091 on the streaming path; the
|
||||
non-streaming path is covered by test_finish_reason_tool_calls_non_streaming.
|
||||
"""
|
||||
iterator = OllamaChatCompletionResponseIterator(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
|
||||
tool_call_chunk = {
|
||||
"model": "qwen3:4b",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": {"location": "Beijing", "date": "tomorrow"},
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
"done": False,
|
||||
}
|
||||
|
||||
tool_result = iterator.chunk_parser(tool_call_chunk)
|
||||
|
||||
assert tool_result.choices[0].finish_reason is None
|
||||
|
||||
tool_calls = tool_result.choices[0].delta.tool_calls
|
||||
assert tool_calls is not None
|
||||
assert len(tool_calls) == 1
|
||||
|
||||
tool_call = tool_calls[0]
|
||||
assert tool_call.index == 0
|
||||
assert tool_call.id is not None and tool_call.id != ""
|
||||
assert tool_call.type == "function"
|
||||
assert tool_call.function.name == "get_weather"
|
||||
assert isinstance(tool_call.function.arguments, str)
|
||||
assert json.loads(tool_call.function.arguments) == {
|
||||
"location": "Beijing",
|
||||
"date": "tomorrow",
|
||||
}
|
||||
|
||||
done_chunk = {
|
||||
"model": "qwen3:4b",
|
||||
"message": {"role": "assistant", "content": ""},
|
||||
"done": True,
|
||||
"done_reason": "stop",
|
||||
"prompt_eval_count": 100,
|
||||
"eval_count": 50,
|
||||
}
|
||||
|
||||
done_result = iterator.chunk_parser(done_chunk)
|
||||
|
||||
assert done_result.choices[0].finish_reason == "tool_calls"
|
||||
|
||||
@pytest.mark.parametrize("done_reason", ["stop", "length"])
|
||||
@pytest.mark.parametrize("empty_calls_in_terminal_chunk", [False, True])
|
||||
def test_empty_tool_calls_preserve_finish_reason(
|
||||
self, done_reason: str, empty_calls_in_terminal_chunk: bool
|
||||
) -> None:
|
||||
iterator: Final = OllamaChatCompletionResponseIterator(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
content_result: Final = iterator.chunk_parser(
|
||||
{
|
||||
"model": "qwen3:4b",
|
||||
"message": {"role": "assistant", "content": "Hello", "tool_calls": []},
|
||||
"done": False,
|
||||
}
|
||||
)
|
||||
done_result: Final = iterator.chunk_parser(
|
||||
{
|
||||
"model": "qwen3:4b",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
**({"tool_calls": []} if empty_calls_in_terminal_chunk else {}),
|
||||
},
|
||||
"done": True,
|
||||
"done_reason": done_reason,
|
||||
}
|
||||
)
|
||||
|
||||
assert content_result.choices[0].finish_reason is None
|
||||
assert not content_result.choices[0].delta.tool_calls
|
||||
assert done_result.choices[0].finish_reason == done_reason
|
||||
assert not done_result.choices[0].delta.tool_calls
|
||||
|
||||
|
||||
class TestOllamaFinishReasonLength:
|
||||
"""Tests for done_reason 'length' → finish_reason 'length' mapping.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue