Add reasoning effort mapping

This commit is contained in:
Sameer Kankute 2025-12-15 22:52:11 +05:30
parent 4d821c9011
commit b4a41b6777
6 changed files with 118 additions and 28 deletions

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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:

View file

@ -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