diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 4e6f4a2a51a..9b217367ee2 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -229,15 +229,15 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): Gemini 3 models include: - gemini-3-pro-preview - gemini-3-flash - - skyhawk (Gemini 3 Flash checkpoint) + - fiercefalcon (Gemini 3 Flash checkpoint) - Any future Gemini 3.x models """ # Check for Gemini 3 models if "gemini-3" in model: return True - # Check for skyhawk (Gemini 3 Flash checkpoint) - if "skyhawk" in model.lower(): + # Check for fiercefalcon (Gemini 3 Flash checkpoint) # TODO: remove + if "fiercefalcon" in model.lower(): # TODO : Remove this once we have the official name of the model return True return False @@ -632,20 +632,20 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): Returns: GeminiThinkingConfig with thinkingLevel and includeThoughts """ - # Check if this is skyhawk/gemini-3-flash which supports MINIMAL thinking level - is_skyhawk = model and ( - "skyhawk" in model.lower() or "gemini-3-flash" in model.lower() + # Check if this is gemini-3-flash which supports MINIMAL thinking level + is_fiercefalcon= model and ( + "fiercefalcon" in model.lower() or "gemini-3-flash" in model.lower() ) if reasoning_effort == "minimal": - if is_skyhawk: + if is_fiercefalcon: return {"thinkingLevel": "minimal", "includeThoughts": True} else: return {"thinkingLevel": "low", "includeThoughts": True} elif reasoning_effort == "low": return {"thinkingLevel": "low", "includeThoughts": True} elif reasoning_effort == "medium": - # For skyhawk, medium maps to "medium", otherwise "high" - if is_skyhawk: + # For fiercefalcon, medium maps to "medium", otherwise "high" + if is_fiercefalcon: return {"thinkingLevel": "medium", "includeThoughts": True} else: return { @@ -655,14 +655,14 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): elif reasoning_effort == "high": return {"thinkingLevel": "high", "includeThoughts": True} elif reasoning_effort == "disable": - # Gemini 3 cannot fully disable thinking, so we use "minimal" for skyhawk, "low" for others - if is_skyhawk: + # Gemini 3 cannot fully disable thinking, so we use "minimal" for fiercefalcon, "low" for others + if is_fiercefalcon: return {"thinkingLevel": "minimal", "includeThoughts": False} else: return {"thinkingLevel": "low", "includeThoughts": False} elif reasoning_effort == "none": - # For skyhawk, use "minimal" instead of "low" - if is_skyhawk: + # For fiercefalcon, use "minimal" instead of "low" + if is_fiercefalcon: return {"thinkingLevel": "minimal", "includeThoughts": False} else: return {"thinkingLevel": "low", "includeThoughts": False} @@ -935,10 +935,10 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "thinkingLevel" not in thinking_config and "thinkingBudget" not in thinking_config ): - # For skyhawk, default to "minimal" to match Gemini 2.5 Flash behavior + # For fiercefalcon, default to "minimal" to match Gemini 2.5 Flash behavior # For other Gemini 3 models, default to "low" - is_skyhawk = "skyhawk" in model.lower() or "gemini-3-flash" in model.lower() - thinking_config["thinkingLevel"] = "minimal" if is_skyhawk else "low" + is_fiercefalcon = "fiercefalcon" in model.lower() or "gemini-3-flash" in model.lower() + thinking_config["thinkingLevel"] = "minimal" if is_fiercefalcon else "low" optional_params["thinkingConfig"] = thinking_config return optional_params diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index ff99f1ea728..f7a33070c9a 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -14416,7 +14416,7 @@ "supports_web_search": true, "tpm": 800000 }, - "gemini/skyhawk": { + "gemini/fiercefalcon": { "cache_read_input_token_cost": 3e-08, "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 3e-07, @@ -14463,7 +14463,7 @@ "supports_web_search": true, "tpm": 800000 }, - "skyhawk": { + "fiercefalcon": { "cache_read_input_token_cost": 3e-08, "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 3e-07, diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 9359c20c67e..cc1079ca6b8 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -86,6 +86,7 @@ class LiteLLMCompletionResponsesConfig: "metadata", "parallel_tool_calls", "previous_response_id", + "reasoning", "stream", "temperature", "text", @@ -122,6 +123,17 @@ class LiteLLMCompletionResponsesConfig: text_param ) + # Extract reasoning_effort from reasoning parameter + reasoning_effort = None + reasoning_param = responses_api_request.get("reasoning") + if reasoning_param: + if isinstance(reasoning_param, dict): + # reasoning can be {"effort": "low|medium|high"} + reasoning_effort = reasoning_param.get("effort") + elif isinstance(reasoning_param, str): + # reasoning could be a string directly + reasoning_effort = reasoning_param + litellm_completion_request: dict = { "messages": LiteLLMCompletionResponsesConfig.transform_responses_api_input_to_messages( input=input, @@ -140,6 +152,7 @@ class LiteLLMCompletionResponsesConfig: "service_tier": kwargs.get("service_tier"), "web_search_options": web_search_options, "response_format": response_format, + "reasoning_effort": reasoning_effort, # litellm specific params "custom_llm_provider": custom_llm_provider, "extra_headers": extra_headers, @@ -161,7 +174,6 @@ class LiteLLMCompletionResponsesConfig: litellm_completion_request = { k: v for k, v in litellm_completion_request.items() if v is not None } - return litellm_completion_request @staticmethod diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index ff99f1ea728..f7a33070c9a 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -14416,7 +14416,7 @@ "supports_web_search": true, "tpm": 800000 }, - "gemini/skyhawk": { + "gemini/fiercefalcon": { "cache_read_input_token_cost": 3e-08, "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 3e-07, @@ -14463,7 +14463,7 @@ "supports_web_search": true, "tpm": 800000 }, - "skyhawk": { + "fiercefalcon": { "cache_read_input_token_cost": 3e-08, "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 3e-07, diff --git a/proxy_server_config.yaml b/proxy_server_config.yaml index df3a08a143b..ee2852cc841 100644 --- a/proxy_server_config.yaml +++ b/proxy_server_config.yaml @@ -227,4 +227,4 @@ general_settings: # settings for using redis caching # REDIS_HOST: redis-16337.c322.us-east-1-2.ec2.cloud.redislabs.com # REDIS_PORT: "16337" - # REDIS_PASSWORD: + # REDIS_PASSWORD: \ No newline at end of file diff --git a/tests/test_litellm/google_genai/test_google_genai_transformation.py b/tests/test_litellm/google_genai/test_google_genai_transformation.py index 3c71ac45c5c..e932bf2c78f 100644 --- a/tests/test_litellm/google_genai/test_google_genai_transformation.py +++ b/tests/test_litellm/google_genai/test_google_genai_transformation.py @@ -12,6 +12,9 @@ sys.path.insert( import pytest from litellm.llms.gemini.google_genai.transformation import GoogleGenAIConfig +from litellm.responses.litellm_completion_transformation.transformation import ( + LiteLLMCompletionResponsesConfig, +) def test_map_generate_content_optional_params_response_json_schema_camelcase(): @@ -30,7 +33,7 @@ def test_map_generate_content_optional_params_response_json_schema_camelcase(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # responseJsonSchema should be in the result (camelCase format for Google GenAI API) @@ -56,7 +59,7 @@ def test_map_generate_content_optional_params_response_schema_snakecase(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # response_schema should be converted to responseJsonSchema (camelCase) @@ -79,7 +82,7 @@ def test_map_generate_content_optional_params_thinking_config_camelcase(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # thinkingConfig should be in the result (camelCase format for Google GenAI API) @@ -103,7 +106,7 @@ def test_map_generate_content_optional_params_thinking_config_snakecase(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # thinking_config should be converted to thinkingConfig (camelCase) @@ -135,7 +138,7 @@ def test_map_generate_content_optional_params_mixed_formats(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # All parameters should be converted to camelCase @@ -162,10 +165,85 @@ def test_map_generate_content_optional_params_response_mime_type(): result = config.map_generate_content_optional_params( generate_content_config_dict=generate_content_config_dict, - model="gemini/skyhawk" + model="gemini/fiercefalcon" ) # responseMimeType should be passed through (it's already camelCase) assert "responseMimeType" in result or "response_mime_type" in result assert "responseJsonSchema" in result + +def test_responses_api_reasoning_dict_format(): + """Test that reasoning parameter with dict format is mapped to reasoning_effort""" + from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams + + responses_api_request: ResponsesAPIOptionalRequestParams = { + "reasoning": {"effort": "high"}, + "temperature": 1.0, + } + + result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="gemini/2.5-pro", + input="Hello, what is the capital of France?", + responses_api_request=responses_api_request, + ) + + # reasoning_effort should be extracted from reasoning dict + assert "reasoning_effort" in result + assert result["reasoning_effort"] == "high" + + +def test_responses_api_reasoning_string_format(): + """Test that reasoning parameter with string format is mapped to reasoning_effort""" + from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams + + responses_api_request: ResponsesAPIOptionalRequestParams = { + "reasoning": "medium", # Could be a string directly + "temperature": 1.0, + } + + result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="gemini/2.5-pro", + input="Hello, what is the capital of France?", + responses_api_request=responses_api_request, + ) + + # reasoning_effort should be extracted from reasoning string + assert "reasoning_effort" in result + assert result["reasoning_effort"] == "medium" + + +def test_responses_api_reasoning_low_effort(): + """Test that low reasoning effort is correctly mapped""" + from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams + + responses_api_request: ResponsesAPIOptionalRequestParams = { + "reasoning": {"effort": "low"}, + } + + result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="gemini/2.5-pro", + input="Test", + responses_api_request=responses_api_request, + ) + + assert "reasoning_effort" in result + assert result["reasoning_effort"] == "low" + + +def test_responses_api_no_reasoning(): + """Test that no reasoning_effort is included when reasoning is not provided""" + from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams + + responses_api_request: ResponsesAPIOptionalRequestParams = { + "temperature": 1.0, + } + + result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request( + model="gemini/2.5-pro", + input="Test", + responses_api_request=responses_api_request, + ) + + # reasoning_effort should not be in result if not provided (filtered out as None) + assert "reasoning_effort" not in result or result.get("reasoning_effort") is None