fix(perplexity): remove models from shared TypedDict and restore error handling

- Remove Perplexity-specific `models` field from ResponsesAPIOptionalRequestParams
  TypedDict to avoid polluting the shared OpenAI type. The param is still passed
  through via get_supported_openai_params + map_openai_params at runtime.
- Add transform_response_api_response override to catch Perplexity's HTTP 200
  with status:"failed" and raise BaseLLMException instead of silently succeeding.
This commit is contained in:
Chesars
2026-03-10 19:51:23 -03:00
parent 90a77c6466
commit a2781f0db3
3 changed files with 127 additions and 3 deletions
@@ -4,15 +4,20 @@ Perplexity Responses API — OpenAI-compatible.
The only provider quirks:
- cost returned as dict → handled by ResponseAPIUsage.parse_cost validator
- preset models (preset/pro-search) → handled by transform_responses_api_request
- HTTP 200 with status:"failed" → raised as exception in transform_response_api_response
Ref: https://docs.perplexity.ai/api-reference/responses-post
"""
from typing import Dict, Optional, Union
import httpx
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import ResponseInputParam
from litellm.types.llms.openai import ResponseInputParam, ResponsesAPIResponse
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders
@@ -78,6 +83,34 @@ class PerplexityResponsesConfig(OpenAIResponsesAPIConfig):
headers=headers,
)
def transform_response_api_response(
self,
model: str,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> ResponsesAPIResponse:
"""Check for Perplexity's status:'failed' on HTTP 200 before delegating to base."""
try:
raw_response_json = raw_response.json()
except Exception:
raw_response_json = None
if (
isinstance(raw_response_json, dict)
and raw_response_json.get("status") == "failed"
):
error = raw_response_json.get("error", {})
raise BaseLLMException(
status_code=raw_response.status_code,
message=error.get("message", "Unknown Perplexity error"),
)
return super().transform_response_api_response(
model=model,
raw_response=raw_response,
logging_obj=logging_obj,
)
def supports_native_websocket(self) -> bool:
"""Perplexity does not support native WebSocket for Responses API"""
return False
-2
View File
@@ -1155,8 +1155,6 @@ class ResponsesAPIOptionalRequestParams(TypedDict, total=False):
] # Number of partial images to generate (1-3) for streaming image generation
context_management: Optional[List[ContextManagementEntry]]
"""Context management configuration. E.g. [{\"type\": \"compaction\", \"compact_threshold\": 200000}] for server-side compaction (minimum 1000)."""
models: Optional[List[str]]
"""Model fallback chain (e.g. Perplexity). Models are tried in order until one succeeds."""
class ResponsesAPIRequestParams(ResponsesAPIOptionalRequestParams, total=False):
@@ -7,11 +7,17 @@ transformations for the Agent API (Responses API).
Source: litellm/llms/perplexity/responses/transformation.py
"""
import json
import os
import sys
import httpx
import pytest
sys.path.insert(0, os.path.abspath("../../../../.."))
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.llms.perplexity.responses.transformation import PerplexityResponsesConfig
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
from litellm.types.utils import LlmProviders
@@ -359,3 +365,90 @@ class TestPerplexityResponsesTransformation:
assert config is not None
assert isinstance(config, PerplexityResponsesConfig)
assert config.custom_llm_provider == LlmProviders.PERPLEXITY
def test_failed_status_raises_exception(self):
"""Perplexity HTTP 200 with status:'failed' must raise BaseLLMException"""
config = PerplexityResponsesConfig()
failed_body = {
"status": "failed",
"error": {"message": "Model quota exceeded"},
}
raw_response = httpx.Response(
status_code=200,
json=failed_body,
request=httpx.Request("POST", "https://api.perplexity.ai/v1/responses"),
)
logging_obj = LiteLLMLoggingObj(
model="perplexity/openai/gpt-5.2",
messages=[],
stream=False,
call_type="responses",
start_time=None,
litellm_call_id="test",
function_id="test",
)
with pytest.raises(BaseLLMException) as exc_info:
config.transform_response_api_response(
model="perplexity/openai/gpt-5.2",
raw_response=raw_response,
logging_obj=logging_obj,
)
assert "Model quota exceeded" in str(exc_info.value.message)
def test_successful_response_passes_through(self):
"""Normal completed response delegates to base OpenAI handler"""
config = PerplexityResponsesConfig()
success_body = {
"id": "resp_123",
"object": "response",
"created_at": 1700000000,
"status": "completed",
"model": "openai/gpt-5.2",
"output": [
{
"type": "message",
"id": "msg_123",
"role": "assistant",
"status": "completed",
"content": [
{"type": "output_text", "text": "Hello!", "annotations": []}
],
}
],
"usage": {
"input_tokens": 10,
"output_tokens": 5,
"total_tokens": 15,
},
}
raw_response = httpx.Response(
status_code=200,
json=success_body,
request=httpx.Request("POST", "https://api.perplexity.ai/v1/responses"),
)
logging_obj = LiteLLMLoggingObj(
model="perplexity/openai/gpt-5.2",
messages=[],
stream=False,
call_type="responses",
start_time=None,
litellm_call_id="test",
function_id="test",
)
response = config.transform_response_api_response(
model="perplexity/openai/gpt-5.2",
raw_response=raw_response,
logging_obj=logging_obj,
)
assert response.id == "resp_123"
assert response.status == "completed"