fix(ollama_chat): set finish_reason to tool_calls when tool calls arrive before the final chunk - #35782
Conversation
…ive before the final chunk Ollama streams tool_calls in chunks with done=false, then sends a final done chunk with no tool_calls and done_reason=stop. chunk_parser only overrode finish_reason when the final chunk itself carried tool_calls, so streamed tool calls ended with finish_reason=stop and spec-strict clients never executed them. Track whether any chunk in the stream carried tool_calls on the iterator and use that when the done chunk arrives Fixes BerriAI#35663
Greptile SummaryThis PR tracks whether an Ollama chat stream emitted tool calls and uses that state to correct the terminal finish reason
Confidence Score: 4/5The token-limit termination case after a streamed tool call should be fixed before merging The new unconditional override converts done_reason="length" to "tool_calls" after any tool-call delta, hiding truncation and potentially presenting incomplete arguments as executable Files Needing Attention: litellm/llms/ollama/chat/transformation.py, tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py
|
| Filename | Overview |
|---|---|
| litellm/llms/ollama/chat/transformation.py | Corrects split-chunk tool-call termination, but also masks token-limit termination after a tool-call delta |
| tests/test_litellm/llms/ollama/test_ollama_chat_transformation.py | Covers the reported stop case but omits the existing length termination combined with an earlier tool call |
Reviews (1): Last reviewed commit: "fix(ollama_chat): set finish_reason to t..." | Re-trigger Greptile
| # https://github.com/BerriAI/litellm/issues/35663 | ||
| if self.emitted_tool_calls: |
There was a problem hiding this comment.
Tool calls mask length termination
When Ollama emits a tool-call delta and then terminates with done_reason="length", this branch replaces the truncation signal with finish_reason="tool_calls", causing clients to treat potentially incomplete tool-call arguments as executable.
Rule Used: What: avoid backwards-incompatible changes without... (source)
Knowledge Base Used: LLM Provider Adapters
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2163837e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self.emitted_tool_calls: | ||
| finish_reason = "tool_calls" |
There was a problem hiding this comment.
Preserve length finish reasons after tool-call chunks
When Ollama ends a stream with done_reason="length" after a prior tool-call chunk, this unconditional override changes the terminal finish_reason from length to tool_calls. That masks max-token truncation, so strict clients may execute a tool call whose arguments were cut off instead of treating the response as incomplete; the existing streaming finalizer only upgrades stop to tool_calls for this reason. Please gate this override to stop-like endings while preserving non-stop reasons such as length.
Useful? React with 👍 / 👎.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
TLDR
Problem this solves:
How it solves it:
Relevant issues
Fixes #35663
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Environment for both runs: local proxy started with
python -m litellm.proxy.proxy_cli --model ollama_chat/llama3.2 --host 127.0.0.1 --port 4000, ollama 0.15.6 serving llama3.2 locally, real streaming LLM calls, no mocksBefore, at cb8c734 (litellm_internal_staging without this fix): the stream carries the tool call delta but the final chunk reports finish_reason "stop" (SSE trimmed to the two relevant chunks)
After, at d216383 (this PR): same request, the tool call delta is unchanged and the final chunk now reports finish_reason "tool_calls"
Same fix observed through /v1/messages, where the finish reason surfaces as stop_reason. Request:
Before, at cb8c734, the tool_use block streams but the message ends as end_turn
After, at d216383, the same request ends with stop_reason "tool_use"
/v1/responses was also checked with the equivalent streamed request at both commits; the full event sequences are identical before and after, because the Responses emulation derives its function_call output items from the tool call deltas rather than from finish_reason, so that endpoint's output is unchanged by this PR
Type
🐛 Bug Fix
Changes
OllamaChatCompletionResponseIterator.chunk_parseronly overrode finish_reason to "tool_calls" when the tool calls appeared on the same chunk asdone: true. Ollama emits tool calls in a chunk withdone: false, then a separate final chunk with an empty message anddone_reason: "stop", so streamed tool calls ended with finish_reason "stop" and clients that gate tool execution on the finish reason discarded them. The non-streaming path already sets finish_reason correctly for this caseThe iterator now sets an
emitted_tool_callsflag when any chunk carries tool calls, and the done branch checks that flag instead of only the current chunk. The flag is per-stream state on the iterator instance, following the existingstarted_reasoning_contentpattern in the same class. Behavior for tool calls arriving on the done chunk itself is unchanged since the flag is set earlier in that same call, anddone_reason: "length"handling is untouched when no tool calls were streamedAdded a regression test that feeds the iterator a tool call chunk followed by a bare done chunk and asserts the final chunk reports finish_reason "tool_calls"; it fails at cb8c734 and passes with this change. Related but distinct: #35711 tracks tool call streaming for the
ollama/generate endpoint, which this PR does not touchFinal Attestation