From 1c6cd61311ecde561ec33a59ef38fe2e21aa1ed4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 21 May 2026 06:05:48 +0000 Subject: [PATCH] fix(oci): polish stream chunk handling and signed body default - cohere stream terminal consolidation now emits content=None instead of "" - drop redundant index truthiness check (None is already replaced with 0) - accept both "TOOL_CALL" and "TOOL_CALLS" finish reasons in cohere - signed_json_body defaults to None and uses explicit None check, so an explicitly empty bytes body wouldn't be silently re-serialized Co-authored-by: Yassin Kortam --- litellm/llms/oci/chat/cohere.py | 10 ++++++---- litellm/llms/oci/chat/generic.py | 2 +- litellm/llms/oci/chat/transformation.py | 16 ++++++++++++---- .../llms/oci/test_oci_coverage_boost.py | 8 ++++---- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/litellm/llms/oci/chat/cohere.py b/litellm/llms/oci/chat/cohere.py index 4134a065ccf..dc611c9906c 100644 --- a/litellm/llms/oci/chat/cohere.py +++ b/litellm/llms/oci/chat/cohere.py @@ -214,7 +214,7 @@ def handle_cohere_response( finish_reason = "stop" elif oci_finish_reason == "MAX_TOKENS": finish_reason = "length" - elif oci_finish_reason == "TOOL_CALL": + elif oci_finish_reason in ("TOOL_CALL", "TOOL_CALLS"): finish_reason = "tool_calls" elif oci_finish_reason is not None: # OCI Cohere can emit error/cancel finish reasons (e.g. ``ERROR``, @@ -291,7 +291,9 @@ def handle_cohere_stream_chunk(dict_chunk: dict) -> ModelResponseStream: is_terminal_consolidation = ( typed_chunk.chatHistory is not None and typed_chunk.finishReason is not None ) - text = "" if is_terminal_consolidation else (typed_chunk.text or "") + text: Optional[str] = ( + None if is_terminal_consolidation else (typed_chunk.text or "") + ) # Tool calls on the terminal consolidation chunk (whether from # `typed_chunk.toolCalls` or from `chatHistory`) restate what was already @@ -326,7 +328,7 @@ def handle_cohere_stream_chunk(dict_chunk: dict) -> ModelResponseStream: finish_reason = "stop" elif finish_reason == "MAX_TOKENS": finish_reason = "length" - elif finish_reason == "TOOL_CALL": + elif finish_reason in ("TOOL_CALL", "TOOL_CALLS"): finish_reason = "tool_calls" elif finish_reason is not None: # OCI Cohere can emit error/cancel finish reasons (e.g. ``ERROR``, @@ -338,7 +340,7 @@ def handle_cohere_stream_chunk(dict_chunk: dict) -> ModelResponseStream: return ModelResponseStream( choices=[ StreamingChoices( - index=typed_chunk.index if typed_chunk.index else 0, + index=typed_chunk.index, delta=Delta( content=text, tool_calls=tool_calls, diff --git a/litellm/llms/oci/chat/generic.py b/litellm/llms/oci/chat/generic.py index ced9bd5ff81..8ded6c7a546 100644 --- a/litellm/llms/oci/chat/generic.py +++ b/litellm/llms/oci/chat/generic.py @@ -432,7 +432,7 @@ def handle_generic_stream_chunk(dict_chunk: dict) -> ModelResponseStream: return ModelResponseStream( choices=[ StreamingChoices( - index=typed_chunk.index if typed_chunk.index else 0, + index=typed_chunk.index, delta=Delta( content=text, tool_calls=( diff --git a/litellm/llms/oci/chat/transformation.py b/litellm/llms/oci/chat/transformation.py index eb4367f0f5a..a06c0ebdd3f 100644 --- a/litellm/llms/oci/chat/transformation.py +++ b/litellm/llms/oci/chat/transformation.py @@ -565,7 +565,7 @@ class OCIChatConfig(BaseConfig): messages: list, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, json_mode: Optional[bool] = None, - signed_json_body: bytes = b"", + signed_json_body: Optional[bytes] = None, ) -> "OCIStreamWrapper": if "stream" in data: del data["stream"] @@ -576,7 +576,11 @@ class OCIChatConfig(BaseConfig): response = client.post( api_base, headers=headers, - data=signed_json_body or json.dumps(data), + data=( + signed_json_body + if signed_json_body is not None + else json.dumps(data) + ), stream=True, logging_obj=logging_obj, timeout=STREAMING_TIMEOUT, @@ -606,7 +610,7 @@ class OCIChatConfig(BaseConfig): messages: list, client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, json_mode: Optional[bool] = None, - signed_json_body: bytes = b"", + signed_json_body: Optional[bytes] = None, ) -> "OCIStreamWrapper": if "stream" in data: del data["stream"] @@ -617,7 +621,11 @@ class OCIChatConfig(BaseConfig): response = await client.post( api_base, headers=headers, - data=signed_json_body or json.dumps(data), + data=( + signed_json_body + if signed_json_body is not None + else json.dumps(data) + ), stream=True, logging_obj=logging_obj, timeout=STREAMING_TIMEOUT, diff --git a/tests/test_litellm/llms/oci/test_oci_coverage_boost.py b/tests/test_litellm/llms/oci/test_oci_coverage_boost.py index 575122bf70e..be3de91ba3d 100644 --- a/tests/test_litellm/llms/oci/test_oci_coverage_boost.py +++ b/tests/test_litellm/llms/oci/test_oci_coverage_boost.py @@ -614,7 +614,7 @@ def test_handle_cohere_stream_chunk_complete(): } result = handle_cohere_stream_chunk(chunk) assert result.choices[0].finish_reason == "stop" - assert result.choices[0].delta.content == "" + assert result.choices[0].delta.content is None def test_handle_cohere_stream_chunk_max_tokens(): @@ -626,7 +626,7 @@ def test_handle_cohere_stream_chunk_max_tokens(): } result = handle_cohere_stream_chunk(chunk) assert result.choices[0].finish_reason == "length" - assert result.choices[0].delta.content == "" + assert result.choices[0].delta.content is None def test_handle_cohere_stream_chunk_tool_call(): @@ -638,7 +638,7 @@ def test_handle_cohere_stream_chunk_tool_call(): } result = handle_cohere_stream_chunk(chunk) assert result.choices[0].finish_reason == "tool_calls" - assert result.choices[0].delta.content == "" + assert result.choices[0].delta.content is None def test_handle_cohere_stream_chunk_terminal_drops_full_response_text(): @@ -658,7 +658,7 @@ def test_handle_cohere_stream_chunk_terminal_drops_full_response_text(): ], } result = handle_cohere_stream_chunk(chunk) - assert result.choices[0].delta.content == "" + assert result.choices[0].delta.content is None def test_handle_cohere_stream_chunk_incremental_passes_text_through():