From fcfe856e1011e681f4aa3a96cc0087e4dda10469 Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzaxirr@users.noreply.github.com> Date: Wed, 1 Oct 2025 07:14:35 +0530 Subject: [PATCH] Add support for GPT 5 codex models (#14841) * Add support for GPT 5 codex models * lint * fixes --- .../llms/openai/chat/gpt_5_transformation.py | 14 ++- ...odel_prices_and_context_window_backup.json | 75 +++++++++++++ model_prices_and_context_window.json | 28 +++++ .../chat/test_azure_gpt5_transformation.py | 57 +++++++++- .../llms/openai/test_gpt5_transformation.py | 101 ++++++++++++++++++ 5 files changed, 271 insertions(+), 4 deletions(-) diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index 3902304a3b..fa357c1bd2 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -8,18 +8,24 @@ from .gpt_transformation import OpenAIGPTConfig class OpenAIGPT5Config(OpenAIGPTConfig): - """Configuration for gpt-5 models. + """Configuration for gpt-5 models including GPT-5-Codex variants. Handles OpenAI API quirks for the gpt-5 series like: - Mapping ``max_tokens`` -> ``max_completion_tokens``. - Dropping unsupported ``temperature`` values when requested. + - Support for GPT-5-Codex models optimized for code generation. """ @classmethod def is_model_gpt_5_model(cls, model: str) -> bool: return "gpt-5" in model + @classmethod + def is_model_gpt_5_codex_model(cls, model: str) -> bool: + """Check if the model is specifically a GPT-5 Codex variant.""" + return "gpt-5-codex" in model + def get_supported_openai_params(self, model: str) -> list: from litellm.utils import supports_tool_choice @@ -38,7 +44,9 @@ class OpenAIGPT5Config(OpenAIGPTConfig): ] return [ - param for param in base_gpt_series_params if param not in non_supported_params + param + for param in base_gpt_series_params + if param not in non_supported_params ] def map_openai_params( @@ -67,7 +75,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig): else: raise litellm.utils.UnsupportedParamsError( message=( - "gpt-5 models don't support temperature={}. Only temperature=1 is supported. To drop unsupported params set `litellm.drop_params = True`" + "gpt-5 models (including gpt-5-codex) don't support temperature={}. Only temperature=1 is supported. To drop unsupported params set `litellm.drop_params = True`" ).format(temperature_value), status_code=400, ) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 44e11863bb..87fdfc7188 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -12727,6 +12727,81 @@ "supports_tool_choice": true, "supports_vision": true }, + "gpt-5-codex": { + "cache_read_input_token_cost": 1.25e-07, + "cache_read_input_token_cost_flex": 6.25e-08, + "cache_read_input_token_cost_priority": 2.5e-07, + "input_cost_per_token": 1.25e-06, + "input_cost_per_token_flex": 6.25e-07, + "input_cost_per_token_priority": 2.5e-06, + "litellm_provider": "openai", + "max_input_tokens": 400000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "metadata": { + "notes": "GPT-5-Codex pricing placeholder - needs to be updated with actual OpenAI pricing" + }, + "mode": "chat", + "output_cost_per_token": 1e-05, + "output_cost_per_token_flex": 5e-06, + "output_cost_per_token_priority": 2e-05, + "supported_endpoints": [ + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, + "gpt-5-codex-latest": { + "cache_read_input_token_cost": 1.25e-07, + "cache_read_input_token_cost_flex": 6.25e-08, + "cache_read_input_token_cost_priority": 2.5e-07, + "input_cost_per_token": 1.25e-06, + "input_cost_per_token_flex": 6.25e-07, + "input_cost_per_token_priority": 2.5e-06, + "litellm_provider": "openai", + "max_input_tokens": 400000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "metadata": { + "notes": "GPT-5-Codex-Latest pricing placeholder - needs to be updated with actual OpenAI pricing" + }, + "mode": "chat", + "output_cost_per_token": 1e-05, + "output_cost_per_token_flex": 5e-06, + "output_cost_per_token_priority": 2e-05, + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/batch", + "/v1/responses" + ], + "supported_modalities": [ + "text" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, "gpt-5-2025-08-07": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_flex": 6.25e-08, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 44e11863bb..877df1bb78 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -12727,6 +12727,34 @@ "supports_tool_choice": true, "supports_vision": true }, + "gpt-5-codex": { + "cache_read_input_token_cost": 1.25e-07, + "input_cost_per_token": 1.25e-06, + "litellm_provider": "openai", + "max_input_tokens": 400000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "chat", + "output_cost_per_token": 1e-05, + "supported_endpoints": [ + "/v1/responses" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text" + ], + "supports_function_calling": true, + "supports_native_streaming": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true + }, "gpt-5-2025-08-07": { "cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost_flex": 6.25e-08, diff --git a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py index 81d64d7057..2ef2020b09 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py @@ -11,7 +11,9 @@ def config() -> AzureOpenAIGPT5Config: def test_azure_gpt5_supports_reasoning_effort(config: AzureOpenAIGPT5Config): assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5") - assert "reasoning_effort" in config.get_supported_openai_params(model="gpt5_series/my-deployment") + assert "reasoning_effort" in config.get_supported_openai_params( + model="gpt5_series/my-deployment" + ) def test_azure_gpt5_maps_max_tokens(config: AzureOpenAIGPT5Config): @@ -46,3 +48,56 @@ def test_azure_gpt5_series_transform_request(config: AzureOpenAIGPT5Config): headers={}, ) assert request["model"] == "gpt-5" + + +# GPT-5-Codex specific tests for Azure +def test_azure_gpt5_codex_model_detection(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5-Codex models are correctly detected.""" + assert config.is_model_gpt_5_model("gpt-5-codex") + assert config.is_model_gpt_5_model("gpt5_series/gpt-5-codex") + + +def test_azure_gpt5_codex_supports_reasoning_effort(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5-Codex supports reasoning_effort parameter.""" + assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5-codex") + assert "reasoning_effort" in config.get_supported_openai_params( + model="gpt5_series/gpt-5-codex" + ) + + +def test_azure_gpt5_codex_maps_max_tokens(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5-Codex correctly maps max_tokens to max_completion_tokens.""" + params = config.map_openai_params( + non_default_params={"max_tokens": 150}, + optional_params={}, + model="gpt-5-codex", + drop_params=False, + api_version="2024-05-01-preview", + ) + assert params["max_completion_tokens"] == 150 + assert "max_tokens" not in params + + +def test_azure_gpt5_codex_temperature_error(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5-Codex raises error for unsupported temperature.""" + with pytest.raises(litellm.utils.UnsupportedParamsError): + config.map_openai_params( + non_default_params={"temperature": 0.8}, + optional_params={}, + model="gpt-5-codex", + drop_params=False, + api_version="2024-05-01-preview", + ) + + +def test_azure_gpt5_codex_series_transform_request(config: AzureOpenAIGPT5Config): + """Test that Azure GPT-5-Codex series routing works correctly.""" + request = config.transform_request( + model="gpt5_series/gpt-5-codex", + messages=[], + optional_params={}, + litellm_params={}, + headers={}, + ) + assert request["model"] == "gpt-5-codex" + diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index 3e6a6a2346..876eb8b29f 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -2,16 +2,24 @@ import pytest import litellm from litellm.llms.openai.openai import OpenAIConfig +from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config @pytest.fixture() def config() -> OpenAIConfig: return OpenAIConfig() + +@pytest.fixture() +def gpt5_config() -> OpenAIGPT5Config: + return OpenAIGPT5Config() + + def test_gpt5_supports_reasoning_effort(config: OpenAIConfig): assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5") assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5-mini") + def test_gpt5_maps_max_tokens(config: OpenAIConfig): params = config.map_openai_params( non_default_params={"max_tokens": 10}, @@ -52,3 +60,96 @@ def test_gpt5_unsupported_params_drop(config: OpenAIConfig): drop_params=True, ) assert "top_p" not in params + + +# GPT-5-Codex specific tests +def test_gpt5_codex_model_detection(gpt5_config: OpenAIGPT5Config): + """Test that GPT-5-Codex models are correctly detected as GPT-5 models.""" + assert gpt5_config.is_model_gpt_5_model("gpt-5-codex") + assert gpt5_config.is_model_gpt_5_codex_model("gpt-5-codex") + + # Regular GPT-5 models should not be detected as codex + assert not gpt5_config.is_model_gpt_5_codex_model("gpt-5") + assert not gpt5_config.is_model_gpt_5_codex_model("gpt-5-mini") + + +def test_gpt5_codex_supports_reasoning_effort(config: OpenAIConfig): + """Test that GPT-5-Codex supports reasoning_effort parameter.""" + assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5-codex") + + +def test_gpt5_codex_maps_max_tokens(config: OpenAIConfig): + """Test that GPT-5-Codex correctly maps max_tokens to max_completion_tokens.""" + params = config.map_openai_params( + non_default_params={"max_tokens": 100}, + optional_params={}, + model="gpt-5-codex", + drop_params=False, + ) + assert params["max_completion_tokens"] == 100 + assert "max_tokens" not in params + + +def test_gpt5_codex_temperature_drop(config: OpenAIConfig): + """Test that GPT-5-Codex drops unsupported temperature values when drop_params=True.""" + params = config.map_openai_params( + non_default_params={"temperature": 0.7}, + optional_params={}, + model="gpt-5-codex", + drop_params=True, + ) + assert "temperature" not in params + + +def test_gpt5_codex_temperature_error(config: OpenAIConfig): + """Test that GPT-5-Codex raises error for unsupported temperature when drop_params=False.""" + with pytest.raises( + litellm.utils.UnsupportedParamsError, + match="gpt-5 models \\(including gpt-5-codex\\)", + ): + config.map_openai_params( + non_default_params={"temperature": 0.7}, + optional_params={}, + model="gpt-5-codex", + drop_params=False, + ) + + + +def test_gpt5_codex_temperature_one_allowed(config: OpenAIConfig): + """Test that GPT-5-Codex allows temperature=1.""" + params = config.map_openai_params( + non_default_params={"temperature": 1.0}, + optional_params={}, + model="gpt-5-codex", + drop_params=False, + ) + assert params["temperature"] == 1.0 + + +def test_gpt5_codex_unsupported_params_drop(config: OpenAIConfig): + """Test that GPT-5-Codex drops unsupported parameters.""" + unsupported_params = [ + "top_p", + "presence_penalty", + "frequency_penalty", + "logprobs", + "top_logprobs", + ] + + for param in unsupported_params: + assert param not in config.get_supported_openai_params(model="gpt-5-codex") + + +def test_gpt5_codex_supports_tool_choice(gpt5_config: OpenAIGPT5Config): + """Test that GPT-5-Codex supports tool_choice parameter.""" + supported_params = gpt5_config.get_supported_openai_params(model="gpt-5-codex") + assert "tool_choice" in supported_params + + +def test_gpt5_codex_supports_function_calling(config: OpenAIConfig): + """Test that GPT-5-Codex supports function calling parameters.""" + supported_params = config.get_supported_openai_params(model="gpt-5-codex") + assert "functions" in supported_params + assert "function_call" in supported_params + assert "tools" in supported_params