feat(perplexity): update Responses API integration to match Agent API

- Rename "Agentic Research API" to "Agent API"
Expand
- supported Responses API parameters
- Fix function
tool handling to pass custom function tools through unchanged instead of heuristically mapping them.
 -Update model registry with current Perplexity
models and presets
- Add Function Calling and Structured Outputs documentation sections.
- Unit tests for transformation logic.
This commit is contained in:
Kesku 2026-02-19 03:14:03 +00:00 committed by Shin
parent 7ae157198b
commit 5899e909fd
5 changed files with 620 additions and 56 deletions

View file

@ -120,7 +120,7 @@ All models listed here https://docs.perplexity.ai/docs/model-cards are supported
## Agentic Research API (Responses API)
## Agent API (Responses API)
Requires v1.72.6+
@ -196,7 +196,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/openai/gpt-4o",
model="perplexity/openai/gpt-5.2",
input="Explain quantum computing in simple terms",
custom_llm_provider="perplexity",
max_output_tokens=500,
@ -215,7 +215,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/anthropic/claude-3-5-sonnet-20241022",
model="perplexity/anthropic/claude-sonnet-4-5",
input="Write a short story about a robot learning to paint",
custom_llm_provider="perplexity",
max_output_tokens=500,
@ -234,7 +234,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/google/gemini-2.0-flash-exp",
model="perplexity/google/gemini-2.5-flash",
input="Explain the concept of neural networks",
custom_llm_provider="perplexity",
max_output_tokens=500,
@ -253,7 +253,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/xai/grok-2-1212",
model="perplexity/xai/grok-4-1-fast-non-reasoning",
input="What makes a good AI assistant?",
custom_llm_provider="perplexity",
max_output_tokens=500,
@ -276,7 +276,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/openai/gpt-4o",
model="perplexity/openai/gpt-5.2",
input="What's the weather in San Francisco today?",
custom_llm_provider="perplexity",
tools=[{"type": "web_search"}],
@ -286,6 +286,78 @@ response = responses(
print(response.output)
```
### Function Calling
The Agent API supports custom function tools. Pass function tools through unchanged:
```python
from litellm import responses
import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/openai/gpt-5.2",
input="What's the weather in San Francisco?",
custom_llm_provider="perplexity",
tools=[
{"type": "web_search"},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
},
},
},
],
instructions="Use tools when appropriate.",
)
print(response.output)
```
### Structured Outputs
Request JSON schema structured outputs via the `text` parameter:
```python
from litellm import responses
import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/preset/pro-search",
input="Extract key facts about the Eiffel Tower",
custom_llm_provider="perplexity",
text={
"format": {
"type": "json_schema",
"name": "facts",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"height_meters": {"type": "number"},
"year_built": {"type": "integer"},
},
"required": ["name", "height_meters", "year_built"],
},
"strict": True,
}
},
)
print(response.output)
```
### Reasoning Effort (Responses API)
@ -319,7 +391,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/anthropic/claude-3-5-sonnet-20241022",
model="perplexity/anthropic/claude-sonnet-4-5",
input=[
{"type": "message", "role": "system", "content": "You are a helpful assistant."},
{"type": "message", "role": "user", "content": "What are the latest AI developments?"},
@ -343,7 +415,7 @@ import os
os.environ['PERPLEXITY_API_KEY'] = ""
response = responses(
model="perplexity/openai/gpt-4o",
model="perplexity/openai/gpt-5.2",
input="Tell me a story about space exploration",
custom_llm_provider="perplexity",
stream=True,
@ -360,23 +432,28 @@ for chunk in response:
| Provider | Model Name | Function Call |
|----------|------------|---------------|
| OpenAI | gpt-4o | `responses(model="perplexity/openai/gpt-4o", ...)` |
| OpenAI | gpt-4o-mini | `responses(model="perplexity/openai/gpt-4o-mini", ...)` |
| OpenAI | gpt-5.2 | `responses(model="perplexity/openai/gpt-5.2", ...)` |
| Anthropic | claude-3-5-sonnet-20241022 | `responses(model="perplexity/anthropic/claude-3-5-sonnet-20241022", ...)` |
| Anthropic | claude-3-5-haiku-20241022 | `responses(model="perplexity/anthropic/claude-3-5-haiku-20241022", ...)` |
| Google | gemini-2.0-flash-exp | `responses(model="perplexity/google/gemini-2.0-flash-exp", ...)` |
| Google | gemini-2.0-flash-thinking-exp | `responses(model="perplexity/google/gemini-2.0-flash-thinking-exp", ...)` |
| xAI | grok-2-1212 | `responses(model="perplexity/xai/grok-2-1212", ...)` |
| xAI | grok-2-vision-1212 | `responses(model="perplexity/xai/grok-2-vision-1212", ...)` |
| OpenAI | gpt-5.1 | `responses(model="perplexity/openai/gpt-5.1", ...)` |
| OpenAI | gpt-5-mini | `responses(model="perplexity/openai/gpt-5-mini", ...)` |
| Anthropic | claude-opus-4-6 | `responses(model="perplexity/anthropic/claude-opus-4-6", ...)` |
| Anthropic | claude-opus-4-5 | `responses(model="perplexity/anthropic/claude-opus-4-5", ...)` |
| Anthropic | claude-sonnet-4-5 | `responses(model="perplexity/anthropic/claude-sonnet-4-5", ...)` |
| Anthropic | claude-haiku-4-5 | `responses(model="perplexity/anthropic/claude-haiku-4-5", ...)` |
| Google | gemini-3-pro-preview | `responses(model="perplexity/google/gemini-3-pro-preview", ...)` |
| Google | gemini-3-flash-preview | `responses(model="perplexity/google/gemini-3-flash-preview", ...)` |
| Google | gemini-2.5-pro | `responses(model="perplexity/google/gemini-2.5-pro", ...)` |
| Google | gemini-2.5-flash | `responses(model="perplexity/google/gemini-2.5-flash", ...)` |
| xAI | grok-4-1-fast-non-reasoning | `responses(model="perplexity/xai/grok-4-1-fast-non-reasoning", ...)` |
| Perplexity | sonar | `responses(model="perplexity/perplexity/sonar", ...)` |
### Available Presets
| Preset Name | Function Call |
|----------------|--------------------------------------------------------|
| fast-search | `responses(model="perplexity/preset/fast-search", ...)`|
| pro-search | `responses(model="perplexity/preset/pro-search", ...)` |
| deep-research | `responses(model="perplexity/preset/deep-research", ...)`|
| Preset Name | Function Call |
|-------------|---------------|
| fast-search | `responses(model="perplexity/preset/fast-search", ...)` |
| pro-search | `responses(model="perplexity/preset/pro-search", ...)` |
| deep-research | `responses(model="perplexity/preset/deep-research", ...)` |
| advanced-deep-research | `responses(model="perplexity/preset/advanced-deep-research", ...)` |
### Complete Example
@ -388,7 +465,7 @@ os.environ['PERPLEXITY_API_KEY'] = ""
# Comprehensive example with multiple features
response = responses(
model="perplexity/openai/gpt-4o",
model="perplexity/openai/gpt-5.2",
input="Research the latest developments in quantum computing and provide sources",
custom_llm_provider="perplexity",
tools=[

View file

@ -1,5 +1,5 @@
"""
Perplexity Agentic Research API (Responses API) module
Perplexity Agent API (Responses API) module
"""
from .transformation import PerplexityResponsesConfig

View file

@ -1,5 +1,5 @@
"""
Transformation logic for Perplexity Agentic Research API (Responses API)
Transformation logic for Perplexity Agent API (Responses API)
This module handles the translation between OpenAI's Responses API format
and Perplexity's Responses API format, which supports:
@ -32,10 +32,10 @@ from litellm.types.utils import LlmProviders
class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
"""
Configuration for Perplexity Agentic Research API (Responses API)
Configuration for Perplexity Agent API (Responses API)
Reference: https://docs.perplexity.ai/agentic-research/quickstart
Reference: https://docs.perplexity.ai/docs/agent-api/overview
"""
@property
@ -47,6 +47,7 @@ class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
Perplexity Responses API supports a different set of parameters
Ref: https://docs.perplexity.ai/api-reference/responses-post
Params aligned with response-echo fields and Open Responses spec.
"""
return [
"max_output_tokens",
@ -58,6 +59,23 @@ class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
"preset",
"instructions",
"models", # Model fallback support
"tool_choice",
"parallel_tool_calls",
"max_tool_calls",
"text",
"previous_response_id",
"store",
"background",
"truncation",
"metadata",
"safety_identifier",
"user",
"stream_options",
"top_logprobs",
"prompt_cache_key",
"frequency_penalty",
"presence_penalty",
"service_tier",
]
def validate_environment(
@ -142,35 +160,75 @@ class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
tools_list = [dict(tool) if hasattr(tool, '__dict__') else tool for tool in tools] # type: ignore
mapped_params["tools"] = self._transform_tools(tools_list) # type: ignore
# Tool control
if response_api_optional_params.get("tool_choice"):
mapped_params["tool_choice"] = response_api_optional_params["tool_choice"]
if response_api_optional_params.get("parallel_tool_calls") is not None:
mapped_params["parallel_tool_calls"] = response_api_optional_params["parallel_tool_calls"]
if response_api_optional_params.get("max_tool_calls"):
mapped_params["max_tool_calls"] = response_api_optional_params["max_tool_calls"]
# Structured outputs
text_param = response_api_optional_params.get("text")
if text_param:
mapped_params["text"] = text_param
# Conversation continuity
if response_api_optional_params.get("previous_response_id"):
mapped_params["previous_response_id"] = response_api_optional_params["previous_response_id"]
# Storage and lifecycle
if response_api_optional_params.get("store") is not None:
mapped_params["store"] = response_api_optional_params["store"]
if response_api_optional_params.get("background") is not None:
mapped_params["background"] = response_api_optional_params["background"]
if response_api_optional_params.get("truncation"):
mapped_params["truncation"] = response_api_optional_params["truncation"]
# Metadata
if response_api_optional_params.get("metadata"):
mapped_params["metadata"] = response_api_optional_params["metadata"]
if response_api_optional_params.get("safety_identifier"):
mapped_params["safety_identifier"] = response_api_optional_params["safety_identifier"]
if response_api_optional_params.get("user"):
mapped_params["user"] = response_api_optional_params["user"]
# Additional
if response_api_optional_params.get("top_logprobs") is not None:
mapped_params["top_logprobs"] = response_api_optional_params["top_logprobs"]
if response_api_optional_params.get("prompt_cache_key"):
mapped_params["prompt_cache_key"] = response_api_optional_params["prompt_cache_key"]
if response_api_optional_params.get("frequency_penalty") is not None:
mapped_params["frequency_penalty"] = response_api_optional_params["frequency_penalty"]
if response_api_optional_params.get("presence_penalty") is not None:
mapped_params["presence_penalty"] = response_api_optional_params["presence_penalty"]
if response_api_optional_params.get("service_tier"):
mapped_params["service_tier"] = response_api_optional_params["service_tier"]
return mapped_params
def _transform_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Transform tools to Perplexity format
Transform tools to Perplexity format.
Perplexity supports:
Perplexity supports (per public OpenAPI spec):
- web_search: Performs web searches
- fetch_url: Fetches content from URLs
- function: Function Calling
"""
perplexity_tools = []
for tool in tools:
if isinstance(tool, dict):
tool_type = tool.get("type")
tool_type = tool.get("type", "")
# Direct Perplexity tool format
if tool_type in ["web_search", "fetch_url"]:
perplexity_tools.append(tool)
# OpenAI function format - try to map to Perplexity tools
# Function tools: Perplexity supports them natively
elif tool_type == "function":
function = tool.get("function", {})
function_name = function.get("name", "")
if function_name == "web_search" or "search" in function_name.lower():
perplexity_tools.append({"type": "web_search"})
elif function_name == "fetch_url" or "fetch" in function_name.lower():
perplexity_tools.append({"type": "fetch_url"})
perplexity_tools.append(tool)
return perplexity_tools

View file

@ -26642,65 +26642,124 @@
"supports_function_calling": true,
"supports_tool_choice": true
},
"perplexity/preset/fast-search": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_preset": true,
"supports_function_calling": true
},
"perplexity/preset/pro-search": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_preset": true
"supports_preset": true,
"supports_function_calling": true
},
"perplexity/openai/gpt-4o": {
"perplexity/preset/deep-research": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_preset": true,
"supports_function_calling": true
},
"perplexity/openai/gpt-4o-mini": {
"perplexity/preset/advanced-deep-research": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_preset": true,
"supports_function_calling": true
},
"perplexity/openai/gpt-5.2": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": true
"supports_reasoning": true,
"supports_function_calling": true
},
"perplexity/anthropic/claude-3-5-sonnet-20241022": {
"perplexity/openai/gpt-5.1": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/anthropic/claude-3-5-haiku-20241022": {
"perplexity/openai/gpt-5-mini": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-2.0-flash-exp": {
"perplexity/anthropic/claude-opus-4-6": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-2.0-flash-thinking-exp": {
"perplexity/anthropic/claude-opus-4-5": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": true
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/xai/grok-2-1212": {
"perplexity/anthropic/claude-sonnet-4-5": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/xai/grok-2-vision-1212": {
"perplexity/anthropic/claude-haiku-4-5": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-3-pro-preview": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-3-flash-preview": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-2.5-pro": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/google/gemini-2.5-flash": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/xai/grok-4-1-fast-non-reasoning": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"perplexity/perplexity/sonar": {
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
"supports_reasoning": false,
"supports_function_calling": true
},
"publicai/aisingapore/Qwen-SEA-LION-v4-32B-IT": {
"input_cost_per_token": 0.0,

View file

@ -0,0 +1,370 @@
"""
Tests for Perplexity Responses API transformation
Tests the PerplexityResponsesConfig class that handles Perplexity-specific
transformations for the Agent API (Responses API).
Source: litellm/llms/perplexity/responses/transformation.py
"""
import os
import sys
sys.path.insert(0, os.path.abspath("../../../../.."))
import pytest
from litellm.llms.perplexity.responses.transformation import PerplexityResponsesConfig
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager
class TestPerplexityResponsesTransformation:
"""Test Perplexity Responses API configuration and transformations"""
def test_function_tool_passthrough(self):
"""Function tools with name/description/parameters are preserved"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(
tools=[
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
},
},
}
]
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert "tools" in result
assert len(result["tools"]) == 1
assert result["tools"][0]["type"] == "function"
assert result["tools"][0]["function"]["name"] == "get_weather"
assert result["tools"][0]["function"]["description"] == "Get the current weather"
assert "parameters" in result["tools"][0]["function"]
def test_web_search_tool_passthrough(self):
"""web_search tools are passed through unchanged"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(tools=[{"type": "web_search"}])
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert "tools" in result
assert len(result["tools"]) == 1
assert result["tools"][0]["type"] == "web_search"
def test_fetch_url_tool_passthrough(self):
"""fetch_url tools are passed through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(tools=[{"type": "fetch_url"}])
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert "tools" in result
assert len(result["tools"]) == 1
assert result["tools"][0]["type"] == "fetch_url"
def test_mixed_tools_function_and_web_search(self):
"""Mixed function and web_search tools are transformed correctly"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(
tools=[
{"type": "web_search"},
{
"type": "function",
"function": {
"name": "custom_tool",
"description": "A custom tool",
"parameters": {"type": "object"},
},
},
]
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert len(result["tools"]) == 2
assert result["tools"][0]["type"] == "web_search"
assert result["tools"][1]["type"] == "function"
assert result["tools"][1]["function"]["name"] == "custom_tool"
def test_tool_choice_mapping(self):
"""tool_choice passes through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(tool_choice="required", temperature=0.7)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("tool_choice") == "required"
def test_parallel_tool_calls(self):
"""parallel_tool_calls passes through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(parallel_tool_calls=True, temperature=0.7)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("parallel_tool_calls") is True
def test_max_tool_calls_mapping(self):
"""max_tool_calls passes through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(max_tool_calls=5, temperature=0.7)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("max_tool_calls") == 5
def test_text_passthrough(self):
"""text param passes through as-is (Perplexity accepts Open Responses format directly)"""
config = PerplexityResponsesConfig()
text_value = {
"format": {
"type": "json_schema",
"name": "weather_response",
"schema": {"type": "object", "properties": {"temp": {"type": "number"}}},
"strict": True,
}
}
params = ResponsesAPIOptionalRequestParams(
text=text_value,
temperature=0.7,
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert "text" in result
assert result["text"] == text_value
assert "response_format" not in result
def test_previous_response_id(self):
"""previous_response_id passes through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(
previous_response_id="resp_abc123",
temperature=0.7,
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("previous_response_id") == "resp_abc123"
def test_store_background_truncation(self):
"""Lifecycle params pass through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(
store=True,
background=False,
truncation="auto",
temperature=0.7,
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("store") is True
assert result.get("background") is False
assert result.get("truncation") == "auto"
def test_metadata_safety_identifier_user(self):
"""Metadata params pass through"""
config = PerplexityResponsesConfig()
params = ResponsesAPIOptionalRequestParams(
metadata={"request_id": "req_123"},
safety_identifier="safety_123",
user="user_456",
temperature=0.7,
)
result = config.map_openai_params(
response_api_optional_params=params,
model="perplexity/openai/gpt-5.2",
drop_params=False,
)
assert result.get("metadata") == {"request_id": "req_123"}
assert result.get("safety_identifier") == "safety_123"
assert result.get("user") == "user_456"
def test_all_supported_params_declared(self):
"""get_supported_openai_params returns complete list"""
config = PerplexityResponsesConfig()
supported = config.get_supported_openai_params("perplexity/openai/gpt-5.2")
expected = [
"max_output_tokens",
"stream",
"temperature",
"top_p",
"tools",
"reasoning",
"preset",
"instructions",
"models",
"tool_choice",
"parallel_tool_calls",
"max_tool_calls",
"text",
"previous_response_id",
"store",
"background",
"truncation",
"metadata",
"safety_identifier",
"user",
"stream_options",
"top_logprobs",
"prompt_cache_key",
"frequency_penalty",
"presence_penalty",
"service_tier",
]
for param in expected:
assert param in supported, f"Missing supported param: {param}"
def test_cost_transformation(self):
"""Perplexity cost dict to OpenAI float"""
config = PerplexityResponsesConfig()
usage_data = {
"input_tokens": 100,
"output_tokens": 200,
"total_tokens": 300,
"cost": {
"currency": "USD",
"input_cost": 0.0001,
"output_cost": 0.0002,
"total_cost": 0.0003,
},
}
result = config._transform_usage(usage_data)
assert result["input_tokens"] == 100
assert result["output_tokens"] == 200
assert result["total_tokens"] == 300
assert result["cost"] == 0.0003
def test_cost_transformation_float_passthrough(self):
"""Cost already float passes through"""
config = PerplexityResponsesConfig()
usage_data = {
"input_tokens": 100,
"output_tokens": 200,
"total_tokens": 300,
"cost": 0.0005,
}
result = config._transform_usage(usage_data)
assert result["cost"] == 0.0005
def test_preset_handling(self):
"""Preset model names work"""
config = PerplexityResponsesConfig()
data = config.transform_responses_api_request(
model="preset/pro-search",
input="What is AI?",
response_api_optional_request_params={"temperature": 0.7},
litellm_params={},
headers={},
)
assert data["preset"] == "pro-search"
assert data["input"] == "What is AI?"
assert "temperature" in data
def test_get_complete_url(self):
"""Correct endpoint URL"""
config = PerplexityResponsesConfig()
url = config.get_complete_url(api_base=None, litellm_params={})
assert url == "https://api.perplexity.ai/v1/responses"
custom_url = config.get_complete_url(
api_base="https://custom.perplexity.ai",
litellm_params={},
)
assert custom_url == "https://custom.perplexity.ai/v1/responses"
url_with_slash = config.get_complete_url(
api_base="https://api.perplexity.ai/",
litellm_params={},
)
assert url_with_slash == "https://api.perplexity.ai/v1/responses"
def test_perplexity_provider_config_registration(self):
"""Test that Perplexity provider returns PerplexityResponsesConfig"""
config = ProviderConfigManager.get_provider_responses_api_config(
model="perplexity/openai/gpt-5.2",
provider=LlmProviders.PERPLEXITY,
)
assert config is not None
assert isinstance(config, PerplexityResponsesConfig)
assert config.custom_llm_provider == LlmProviders.PERPLEXITY