mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-21 04:24:51 +00:00
[Feat] Add reasoning_effort support for perplexity models (#11562)
* fix: add reasoning_effort for pplx * docs pplx reasoning * [tests] add mock tests for pplx reasoning (#11564) * Add tests for Perplexity reasoning models and effort parameter * tests perplexity reasoning effort --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * test pplx reasoning effort --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor Agent
parent
9b87fde633
commit
cd8ec4556f
@@ -18,6 +18,7 @@ Supported Providers:
|
||||
- XAI (`xai/`)
|
||||
- Google AI Studio (`google/`)
|
||||
- Vertex AI (`vertex_ai/`)
|
||||
- Perplexity (`perplexity/`)
|
||||
|
||||
LiteLLM will standardize the `reasoning_content` in the response and `thinking_blocks` in the assistant message.
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ Translate from OpenAI's `/v1/chat/completions` to Perplexity's `/v1/chat/complet
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
|
||||
|
||||
class PerplexityChatConfig(OpenAIGPTConfig):
|
||||
@property
|
||||
def custom_llm_provider(self) -> Optional[str]:
|
||||
return "perplexity"
|
||||
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: Optional[str], api_key: Optional[str]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
@@ -29,7 +35,7 @@ class PerplexityChatConfig(OpenAIGPTConfig):
|
||||
|
||||
Eg. Perplexity does not support tools, tool_choice, function_call, functions, etc.
|
||||
"""
|
||||
return [
|
||||
base_openai_params = [
|
||||
"frequency_penalty",
|
||||
"max_tokens",
|
||||
"max_completion_tokens",
|
||||
@@ -41,3 +47,12 @@ class PerplexityChatConfig(OpenAIGPTConfig):
|
||||
"max_retries",
|
||||
"extra_headers",
|
||||
]
|
||||
|
||||
try:
|
||||
if litellm.supports_reasoning(
|
||||
model=model, custom_llm_provider=self.custom_llm_provider
|
||||
):
|
||||
base_openai_params.append("reasoning_effort")
|
||||
except Exception as e:
|
||||
verbose_logger.debug(f"Error checking if model supports reasoning: {e}")
|
||||
return base_openai_params
|
||||
|
||||
@@ -439,6 +439,23 @@
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gpt-4o-audio-preview-2025-06-03": {
|
||||
"max_tokens": 16384,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 16384,
|
||||
"input_cost_per_token": 2.5e-06,
|
||||
"input_cost_per_audio_token": 4.0e-5,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"output_cost_per_audio_token": 8.0e-5,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_audio_input": true,
|
||||
"supports_audio_output": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gpt-4o-mini-audio-preview": {
|
||||
"max_tokens": 16384,
|
||||
"max_input_tokens": 128000,
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
import litellm
|
||||
from litellm import completion
|
||||
from litellm.utils import get_optional_params
|
||||
|
||||
|
||||
class TestPerplexityReasoning:
|
||||
"""
|
||||
Test suite for Perplexity Sonar reasoning models with reasoning_effort parameter
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,reasoning_effort",
|
||||
[
|
||||
("perplexity/sonar-reasoning", "low"),
|
||||
("perplexity/sonar-reasoning", "medium"),
|
||||
("perplexity/sonar-reasoning", "high"),
|
||||
("perplexity/sonar-reasoning-pro", "low"),
|
||||
("perplexity/sonar-reasoning-pro", "medium"),
|
||||
("perplexity/sonar-reasoning-pro", "high"),
|
||||
]
|
||||
)
|
||||
def test_perplexity_reasoning_effort_parameter_mapping(self, model, reasoning_effort):
|
||||
"""
|
||||
Test that reasoning_effort parameter is correctly mapped for Perplexity Sonar reasoning models
|
||||
"""
|
||||
# Set up local model cost map
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
# Get provider and optional params
|
||||
_, provider, _, _ = litellm.get_llm_provider(model=model)
|
||||
|
||||
optional_params = get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider=provider,
|
||||
reasoning_effort=reasoning_effort,
|
||||
)
|
||||
|
||||
# Verify that reasoning_effort is preserved in optional_params for Perplexity
|
||||
assert "reasoning_effort" in optional_params
|
||||
assert optional_params["reasoning_effort"] == reasoning_effort
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"perplexity/sonar-reasoning",
|
||||
"perplexity/sonar-reasoning-pro",
|
||||
]
|
||||
)
|
||||
def test_perplexity_reasoning_effort_mock_completion(self, model):
|
||||
"""
|
||||
Test that reasoning_effort is correctly passed in actual completion call (mocked)
|
||||
"""
|
||||
from openai import OpenAI
|
||||
from openai.types.chat.chat_completion import ChatCompletion
|
||||
|
||||
litellm.set_verbose = True
|
||||
|
||||
# Mock successful response with reasoning content
|
||||
response_object = {
|
||||
"id": "cmpl-test",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": model.split("/")[1],
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "This is a test response from the reasoning model.",
|
||||
"reasoning_content": "Let me think about this step by step...",
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 9,
|
||||
"completion_tokens": 20,
|
||||
"total_tokens": 29,
|
||||
"completion_tokens_details": {
|
||||
"reasoning_tokens": 15
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
pydantic_obj = ChatCompletion(**response_object)
|
||||
|
||||
def _return_pydantic_obj(*args, **kwargs):
|
||||
new_response = MagicMock()
|
||||
new_response.headers = {"content-type": "application/json"}
|
||||
new_response.parse.return_value = pydantic_obj
|
||||
return new_response
|
||||
|
||||
openai_client = OpenAI(api_key="fake-api-key")
|
||||
|
||||
with patch.object(
|
||||
openai_client.chat.completions.with_raw_response, "create", side_effect=_return_pydantic_obj
|
||||
) as mock_client:
|
||||
|
||||
response = completion(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "Hello, please think about this carefully."}],
|
||||
reasoning_effort="high",
|
||||
client=openai_client,
|
||||
)
|
||||
|
||||
# Verify the call was made
|
||||
assert mock_client.called
|
||||
|
||||
# Get the request data from the mock call
|
||||
call_args = mock_client.call_args
|
||||
request_data = call_args.kwargs
|
||||
|
||||
# Verify reasoning_effort was included in the request
|
||||
assert "reasoning_effort" in request_data
|
||||
assert request_data["reasoning_effort"] == "high"
|
||||
|
||||
# Verify response structure
|
||||
assert response.choices[0].message.content is not None
|
||||
assert response.choices[0].message.content == "This is a test response from the reasoning model."
|
||||
|
||||
def test_perplexity_reasoning_models_support_reasoning(self):
|
||||
"""
|
||||
Test that Perplexity Sonar reasoning models are correctly identified as supporting reasoning
|
||||
"""
|
||||
from litellm.utils import supports_reasoning
|
||||
|
||||
# Set up local model cost map
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
reasoning_models = [
|
||||
"perplexity/sonar-reasoning",
|
||||
"perplexity/sonar-reasoning-pro",
|
||||
]
|
||||
|
||||
for model in reasoning_models:
|
||||
assert supports_reasoning(model, None), f"{model} should support reasoning"
|
||||
|
||||
def test_perplexity_non_reasoning_models_dont_support_reasoning(self):
|
||||
"""
|
||||
Test that non-reasoning Perplexity models don't support reasoning
|
||||
"""
|
||||
from litellm.utils import supports_reasoning
|
||||
|
||||
# Set up local model cost map
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
non_reasoning_models = [
|
||||
"perplexity/sonar",
|
||||
"perplexity/sonar-pro",
|
||||
"perplexity/llama-3.1-sonar-large-128k-chat",
|
||||
"perplexity/mistral-7b-instruct",
|
||||
]
|
||||
|
||||
for model in non_reasoning_models:
|
||||
# These models should not support reasoning (should return False or raise exception)
|
||||
try:
|
||||
result = supports_reasoning(model, None)
|
||||
# If it doesn't raise an exception, it should return False
|
||||
assert result is False, f"{model} should not support reasoning"
|
||||
except Exception:
|
||||
# If it raises an exception, that's also acceptable behavior
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,expected_api_base",
|
||||
[
|
||||
("perplexity/sonar-reasoning", "https://api.perplexity.ai"),
|
||||
("perplexity/sonar-reasoning-pro", "https://api.perplexity.ai"),
|
||||
]
|
||||
)
|
||||
def test_perplexity_reasoning_api_base_configuration(self, model, expected_api_base):
|
||||
"""
|
||||
Test that Perplexity reasoning models use the correct API base
|
||||
"""
|
||||
from litellm.llms.perplexity.chat.transformation import PerplexityChatConfig
|
||||
|
||||
config = PerplexityChatConfig()
|
||||
api_base, _ = config._get_openai_compatible_provider_info(
|
||||
api_base=None, api_key="test-key"
|
||||
)
|
||||
|
||||
assert api_base == expected_api_base
|
||||
|
||||
def test_perplexity_reasoning_effort_in_supported_params(self):
|
||||
"""
|
||||
Test that reasoning_effort is in the list of supported parameters for Perplexity
|
||||
"""
|
||||
from litellm.llms.perplexity.chat.transformation import PerplexityChatConfig
|
||||
|
||||
config = PerplexityChatConfig()
|
||||
supported_params = config.get_supported_openai_params(model="perplexity/sonar-reasoning")
|
||||
|
||||
assert "reasoning_effort" in supported_params
|
||||
Reference in New Issue
Block a user