mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(ollama_chat): preserve non-stop finish reasons when tool calls were streamed
Only upgrade a "stop" ending to "tool_calls". Ollama can terminate a stream with done_reason="length" after a tool call has already been emitted, and reporting that as "tool_calls" hides the truncation, so a client may execute a tool call whose arguments were cut off. Matches the existing guard in litellm's streaming finalizer, which only upgrades a "stop" ending for the same reason.
This commit is contained in:
parent
2f55eb664b
commit
a2f49c3fc4
2 changed files with 51 additions and 2 deletions
|
|
@ -510,10 +510,12 @@ class OllamaChatCompletionResponseIterator(BaseModelResponseIterator):
|
|||
|
||||
if chunk["done"] is True:
|
||||
finish_reason = chunk.get("done_reason") or "stop"
|
||||
# Override finish_reason when tool_calls were emitted in any chunk of the stream
|
||||
# Override finish_reason when tool_calls were emitted in any chunk of the
|
||||
# stream. Only upgrade a "stop" ending - other terminal reasons such as
|
||||
# "length" mean the tool call may be truncated, so don't report it as complete.
|
||||
# Fixes: https://github.com/BerriAI/litellm/issues/18922
|
||||
# https://github.com/BerriAI/litellm/issues/35663
|
||||
if self.emitted_tool_calls:
|
||||
if self.emitted_tool_calls and finish_reason == "stop":
|
||||
finish_reason = "tool_calls"
|
||||
choices = [
|
||||
StreamingChoices(
|
||||
|
|
|
|||
|
|
@ -527,6 +527,53 @@ class TestOllamaToolCalling:
|
|||
result.choices[0].finish_reason == "tool_calls"
|
||||
), f"Expected 'tool_calls' when tool calls were streamed in an earlier chunk, got '{result.choices[0].finish_reason}'"
|
||||
|
||||
def test_finish_reason_length_survives_earlier_tool_call_chunk(self):
|
||||
"""Streaming: a truncated stream keeps finish_reason='length' after a tool call.
|
||||
|
||||
Ollama emits tool_calls in an earlier chunk and can then terminate with
|
||||
done_reason='length' once num_predict is reached. The tool call arguments may be
|
||||
cut off, so the terminal reason must survive rather than be upgraded to
|
||||
'tool_calls'. Mirrors the guard in litellm's own streaming finalizer, which only
|
||||
upgrades a 'stop' ending.
|
||||
"""
|
||||
iterator = OllamaChatCompletionResponseIterator(
|
||||
streaming_response=iter([]),
|
||||
sync_stream=True,
|
||||
)
|
||||
|
||||
tool_call_chunk = {
|
||||
"model": "qwen3:14b",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": {"location": "San Francisco"},
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
"done": False,
|
||||
}
|
||||
|
||||
done_chunk = {
|
||||
"model": "qwen3:14b",
|
||||
"message": {"role": "assistant", "content": ""},
|
||||
"done": True,
|
||||
"done_reason": "length",
|
||||
"prompt_eval_count": 100,
|
||||
"eval_count": 51,
|
||||
}
|
||||
|
||||
iterator.chunk_parser(tool_call_chunk)
|
||||
result = iterator.chunk_parser(done_chunk)
|
||||
|
||||
assert (
|
||||
result.choices[0].finish_reason == "length"
|
||||
), f"Expected 'length' to survive truncation after a tool call chunk, got '{result.choices[0].finish_reason}'"
|
||||
|
||||
|
||||
class TestOllamaFinishReasonLength:
|
||||
"""Tests for done_reason 'length' → finish_reason 'length' mapping.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue