fix(oci): also report finish_reason="tool_calls" on streaming terminal chunks

Extends the finish_reason fix to the streaming handlers (per review): the
terminal SSE chunk normalized OCI's "COMPLETE" to "stop" even when tool calls
were emitted. handle_cohere_stream_chunk now reports "tool_calls" when tool
calls are present on the chunk or were emitted earlier (prior_tool_calls_emitted,
since the terminal chunk suppresses already-streamed tool calls), and
handle_generic_stream_chunk reports "tool_calls" when the chunk carries them.
This commit is contained in:
ly-wang19 2026-06-22 12:56:48 +08:00
parent b3d3444bac
commit 2dd7b2bb1a
No known key found for this signature in database
3 changed files with 61 additions and 0 deletions

View file

@ -375,6 +375,13 @@ def handle_cohere_stream_chunk(
]
finish_reason = _normalize_oci_finish_reason(typed_chunk.finishReason)
# OpenAI semantics: the terminal chunk must report finish_reason="tool_calls"
# when tool calls were emitted — on this chunk or earlier in the stream. OCI
# Cohere's terminal finishReason is "COMPLETE" (normalized to "stop"), and the
# terminal chunk suppresses already-emitted tool calls, so rely on
# prior_tool_calls_emitted as well.
if finish_reason is not None and (tool_calls or prior_tool_calls_emitted):
finish_reason = "tool_calls"
return ModelResponseStream(
choices=[

View file

@ -463,6 +463,9 @@ def handle_generic_stream_chunk(dict_chunk: dict) -> ModelResponseStream:
finish_reason: Optional[str] = _normalize_oci_finish_reason(
typed_chunk.finishReason
)
# OpenAI semantics: a chunk carrying tool calls reports finish_reason="tool_calls".
if tool_calls:
finish_reason = "tool_calls"
return ModelResponseStream(
choices=[

View file

@ -3,6 +3,8 @@ import datetime
import httpx
from litellm import ModelResponse
from litellm.llms.oci.chat.cohere import handle_cohere_stream_chunk
from litellm.llms.oci.chat.generic import handle_generic_stream_chunk
from litellm.llms.oci.chat.transformation import OCIChatConfig
@ -94,3 +96,52 @@ def test_generic_tool_calls_report_finish_reason_tool_calls():
result = _transform("meta.llama-3.3-70b-instruct", _generic_body_with_tool_calls())
assert result.choices[0].message.tool_calls is not None
assert result.choices[0].finish_reason == "tool_calls"
# --- streaming handlers (terminal chunk finish_reason) ---
def _cohere_terminal_chunk() -> dict:
return {
"apiFormat": "COHERE",
"text": "All done.",
"finishReason": "COMPLETE",
"chatHistory": [{"role": "CHATBOT", "message": "All done."}],
"index": 0,
}
def test_cohere_stream_terminal_chunk_reports_tool_calls_when_emitted():
# On the terminal chunk, OCI Cohere reports finishReason="COMPLETE" and
# suppresses already-streamed tool calls; finish_reason must still be
# "tool_calls" when tool calls were emitted earlier in the stream.
result = handle_cohere_stream_chunk(
_cohere_terminal_chunk(), prior_tool_calls_emitted=True
)
assert result.choices[0].finish_reason == "tool_calls"
def test_cohere_stream_terminal_chunk_reports_stop_without_tool_calls():
result = handle_cohere_stream_chunk(
_cohere_terminal_chunk(), prior_tool_calls_emitted=False
)
assert result.choices[0].finish_reason == "stop"
def test_generic_stream_chunk_with_tool_calls_reports_tool_calls():
chunk = {
"finishReason": "STOP",
"message": {
"toolCalls": [
{
"id": "call_0",
"type": "FUNCTION",
"name": "get_weather",
"arguments": "{}",
}
]
},
"index": 0,
}
result = handle_generic_stream_chunk(chunk)
assert result.choices[0].finish_reason == "tool_calls"