diff --git a/litellm/llms/oci/chat/transformation.py b/litellm/llms/oci/chat/transformation.py index 256d0a74623..03c760433cb 100644 --- a/litellm/llms/oci/chat/transformation.py +++ b/litellm/llms/oci/chat/transformation.py @@ -73,7 +73,6 @@ from litellm.types.utils import ( ModelResponse, ModelResponseStream, ) -from litellm.utils import supports_reasoning if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj @@ -96,16 +95,15 @@ def _model_uses_max_completion_tokens(model: str) -> bool: and accept ``maxCompletionTokens`` everywhere, so route the whole vendor prefix to it rather than chasing each new release in ``model_prices_and_context_window.json``. The ``openai.gpt-oss-*`` open - weights are served by OCI's own stack and keep ``maxTokens``. Any other - vendor falls back to the catalog's ``supports_reasoning`` flag. + weights are served by OCI's own stack and keep ``maxTokens``, as does every + other vendor whatever its catalog reasoning flag: OCI's Gemini models ignore + ``maxCompletionTokens`` and fall back to a default of about 4K output tokens. """ if not model: return False name: Final = model[4:] if model.lower().startswith("oci/") else model lowered: Final = name.lower() - if lowered.startswith("openai."): - return not lowered.startswith("openai.gpt-oss") - return supports_reasoning(model=name, custom_llm_provider="oci") + return lowered.startswith("openai.") and not lowered.startswith("openai.gpt-oss") def _iter_sse_events(stream: Iterator[str]) -> Iterator[str]: @@ -572,8 +570,7 @@ class OCIChatConfig(BaseConfig): # OpenAI reasoning models on OCI (e.g. GPT-5 family) reject "maxTokens" # and require "maxCompletionTokens" per OCI's /20231130/Chat schema. - # Driven by the supports_reasoning flag in the model catalog. Cohere's - # endpoint uses "maxTokens" regardless, so the override is GENERIC-only. + # Cohere's endpoint uses "maxTokens" regardless, so the override is GENERIC-only. max_tokens_key: Final = ( "maxCompletionTokens" if vendor != OCIVendors.COHERE and model and _model_uses_max_completion_tokens(model) diff --git a/tests/unit/llms/oci/chat/test_oci_generic_chat.py b/tests/unit/llms/oci/chat/test_oci_generic_chat.py index 9ec5ab9aed4..d576a2b03d2 100644 --- a/tests/unit/llms/oci/chat/test_oci_generic_chat.py +++ b/tests/unit/llms/oci/chat/test_oci_generic_chat.py @@ -8,6 +8,7 @@ from unittest.mock import MagicMock import httpx from litellm import ModelResponse +from litellm.constants import DEFAULT_OCI_CHAT_MAX_TOKENS from litellm.llms.oci.chat.generic import ( adapt_messages_to_generic_oci_standard, adapt_messages_to_generic_oci_standard_content_message, @@ -369,6 +370,27 @@ def _register_oci_gpt5_in_catalog(): litellm.model_cost.pop(key, None) +@pytest.fixture +def _flag_oci_gemini_as_reasoning_in_catalog(): + """Mark OCI's Gemini models as reasoning models, as the bundled catalog has done since #41902, + whichever map ``litellm.model_cost`` was populated from.""" + import litellm + + keys = ("oci/google.gemini-2.5-pro", "oci/google.gemini-2.5-flash") + originals = {key: litellm.model_cost.get(key) for key in keys} + for key in keys: + litellm.model_cost[key] = { + **(originals[key] or {"litellm_provider": "oci", "mode": "chat"}), + "supports_reasoning": True, + } + yield + for key, original in originals.items(): + if original is None: + litellm.model_cost.pop(key, None) + else: + litellm.model_cost[key] = original + + class TestGpt5MaxCompletionTokens: def test_helper_detects_gpt5_family(self, _register_oci_gpt5_in_catalog): assert _model_uses_max_completion_tokens("openai.gpt-5") is True @@ -441,6 +463,18 @@ class TestGpt5MaxCompletionTokens: assert out.get("maxTokens") == 64 assert "maxCompletionTokens" not in out + @pytest.mark.parametrize("model", ["google.gemini-2.5-pro", "oci/google.gemini-2.5-flash"]) + @pytest.mark.parametrize(("params", "expected"), [({"max_tokens": 64}, 64), ({}, DEFAULT_OCI_CHAT_MAX_TOKENS)]) + def test_gemini_reasoning_models_keep_max_tokens( + self, model, params, expected, _flag_oci_gemini_as_reasoning_in_catalog + ): + """Regression: OCI's Gemini models ignore maxCompletionTokens and fall back to a default of + about 4K output tokens (verified against live OCI on 2026-09-28), so a reasoning flag in the + catalog must not move the limit off maxTokens.""" + out = OCIChatConfig()._get_optional_params(OCIVendors.GENERIC, params, model=model) + assert out.get("maxTokens") == expected + assert "maxCompletionTokens" not in out + def test_cohere_reasoning_model_keeps_max_tokens(self): cfg = OCIChatConfig() out = cfg._get_optional_params(