diff --git a/litellm/llms/oci/chat/cohere.py b/litellm/llms/oci/chat/cohere.py index 62cb71f2a52..f13a371c85c 100644 --- a/litellm/llms/oci/chat/cohere.py +++ b/litellm/llms/oci/chat/cohere.py @@ -254,11 +254,14 @@ def handle_cohere_stream_chunk(dict_chunk: dict) -> ModelResponseStream: typed_chunk.index = 0 # OCI Cohere's terminal SSE event re-sends the full assembled response in - # `text` alongside a populated `chatHistory`. Emitting that text would - # concatenate the whole response onto the already-streamed deltas. - # `chatHistory` is the correct discriminator: `finishReason` is a weaker - # signal that could in principle appear on a non-consolidated chunk. - is_terminal_consolidation = typed_chunk.chatHistory is not None + # `text` alongside a populated `chatHistory` and a non-null `finishReason`. + # Emitting that text would concatenate the whole response onto the + # already-streamed deltas. We require both signals to be present so that a + # future API change which adds `chatHistory` to intermediate chunks (or a + # rare early-populated case) doesn't silently drop legitimate token deltas. + 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 "") finish_reason = typed_chunk.finishReason diff --git a/litellm/llms/oci/chat/generic.py b/litellm/llms/oci/chat/generic.py index 7938e8f3254..bf44ee9b82f 100644 --- a/litellm/llms/oci/chat/generic.py +++ b/litellm/llms/oci/chat/generic.py @@ -327,7 +327,7 @@ def handle_generic_response( reasoning_tokens: Optional[int] = None if ( oci_usage.completionTokensDetails - and oci_usage.completionTokensDetails.reasoningTokens + and oci_usage.completionTokensDetails.reasoningTokens is not None ): reasoning_tokens = oci_usage.completionTokensDetails.reasoningTokens model_response.usage = Usage( # type: ignore[attr-defined] diff --git a/litellm/llms/oci/chat/transformation.py b/litellm/llms/oci/chat/transformation.py index e03773397ff..d34a34855d1 100644 --- a/litellm/llms/oci/chat/transformation.py +++ b/litellm/llms/oci/chat/transformation.py @@ -195,11 +195,15 @@ class OCIChatConfig(BaseConfig): # - tool_choice is unsupported # - stop sequences key is "stopSequences" not "stop" # - n (numGenerations) is GENERIC-only + # The unsupported keys are kept in the map with value ``False`` so + # ``map_openai_params`` either drops them (under drop_params) or raises + # a clear error, rather than silently passing them through. self.openai_to_oci_cohere_param_map = { k: ("stopSequences" if k == "stop" else v) for k, v in self.openai_to_oci_generic_param_map.items() - if k not in ("tool_choice", "max_retries", "n") } + self.openai_to_oci_cohere_param_map["tool_choice"] = False + self.openai_to_oci_cohere_param_map["n"] = False # OCI Cohere models are not reasoning models; mark reasoning_effort # explicitly unsupported so callers either get a clear error or have # the param dropped under drop_params, rather than silently passing @@ -316,7 +320,12 @@ class OCIChatConfig(BaseConfig): litellm_params: dict, stream: Optional[bool] = None, ) -> str: - base = get_oci_base_url(optional_params, api_base or litellm.api_base) + # If the caller provides a full endpoint URL, use it as-is. + # Otherwise construct the standard OCI GenAI chat endpoint from the region. + resolved_base = api_base or litellm.api_base + if resolved_base: + return resolved_base.rstrip("/") + base = get_oci_base_url(optional_params, None) return f"{base}/{OCI_API_VERSION}/actions/chat" def _get_optional_params(