From dbb0a5f97c320780ec1674c5d1724b42843aca77 Mon Sep 17 00:00:00 2001 From: saleh alghusson Date: Thu, 27 Aug 2026 15:53:11 -0400 Subject: [PATCH 1/4] feat(perplexity): add integration attribution header --- litellm/llms/perplexity/chat/transformation.py | 15 +++++++++++++++ .../chat/test_perplexity_chat_transformation.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/litellm/llms/perplexity/chat/transformation.py b/litellm/llms/perplexity/chat/transformation.py index bf33103b480..2aa894eeed5 100644 --- a/litellm/llms/perplexity/chat/transformation.py +++ b/litellm/llms/perplexity/chat/transformation.py @@ -27,6 +27,21 @@ class PerplexityChatConfig(OpenAIGPTConfig): dynamic_api_key = api_key or get_secret_str("PERPLEXITYAI_API_KEY") or get_secret_str("PERPLEXITY_API_KEY") return api_base, dynamic_api_key + def validate_environment( + self, + headers: dict, + model: str, + messages: list[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: str | None = None, + api_base: str | None = None, + ) -> dict: + headers.setdefault("X-Pplx-Integration", "litellm") + return super().validate_environment( + headers, model, messages, optional_params, litellm_params, api_key, api_base + ) + def get_supported_openai_params(self, model: str) -> list: """ Perplexity supports a subset of OpenAI params diff --git a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py index 29d185b686d..b679aa330fb 100644 --- a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py +++ b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py @@ -19,6 +19,21 @@ from litellm.types.utils import Usage class TestPerplexityChatTransformation: """Test suite for Perplexity chat transformation functionality.""" + @pytest.mark.parametrize( + ("headers", "expected"), + [({}, "litellm"), ({"X-Pplx-Integration": "custom"}, "custom")], + ) + def test_integration_header_default_is_caller_overridable(self, headers, expected): + result = PerplexityChatConfig().validate_environment( + headers=headers, + model="sonar", + messages=[], + optional_params={}, + litellm_params={}, + ) + + assert result["X-Pplx-Integration"] == expected + def test_enhance_usage_with_citation_tokens(self): """Test extraction of citation tokens from API response.""" config = PerplexityChatConfig() From 9cc14a6c492efa5fbbcdc89438842b786afe5908 Mon Sep 17 00:00:00 2001 From: Saleh Alghusson Date: Thu, 27 Aug 2026 16:46:03 -0400 Subject: [PATCH 2/4] Satisfy type discipline for Perplexity headers --- litellm/llms/perplexity/chat/transformation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/litellm/llms/perplexity/chat/transformation.py b/litellm/llms/perplexity/chat/transformation.py index 2aa894eeed5..c29f35dece9 100644 --- a/litellm/llms/perplexity/chat/transformation.py +++ b/litellm/llms/perplexity/chat/transformation.py @@ -29,14 +29,14 @@ class PerplexityChatConfig(OpenAIGPTConfig): def validate_environment( self, - headers: dict, + headers: dict, # mutable-ok: matches the base chat transform signature model: str, - messages: list[AllMessageValues], - optional_params: dict, - litellm_params: dict, + messages: list[AllMessageValues], # mutable-ok: matches the base chat transform signature + optional_params: dict, # mutable-ok: matches the base chat transform signature + litellm_params: dict, # mutable-ok: matches the base chat transform signature api_key: str | None = None, api_base: str | None = None, - ) -> dict: + ) -> dict: # mutable-ok: matches the base chat transform signature headers.setdefault("X-Pplx-Integration", "litellm") return super().validate_environment( headers, model, messages, optional_params, litellm_params, api_key, api_base From 9dbb06ca297dd3e9fcf1cf961f8f3ec2c47b712a Mon Sep 17 00:00:00 2001 From: Saleh Alghusson Date: Thu, 27 Aug 2026 18:26:53 -0400 Subject: [PATCH 3/4] fix: preserve case-insensitive Perplexity header overrides --- litellm/llms/perplexity/chat/transformation.py | 3 ++- .../chat/test_perplexity_chat_transformation.py | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/litellm/llms/perplexity/chat/transformation.py b/litellm/llms/perplexity/chat/transformation.py index c29f35dece9..085c7644566 100644 --- a/litellm/llms/perplexity/chat/transformation.py +++ b/litellm/llms/perplexity/chat/transformation.py @@ -37,7 +37,8 @@ class PerplexityChatConfig(OpenAIGPTConfig): api_key: str | None = None, api_base: str | None = None, ) -> dict: # mutable-ok: matches the base chat transform signature - headers.setdefault("X-Pplx-Integration", "litellm") + if not any(name.lower() == "x-pplx-integration" for name in headers): + headers["X-Pplx-Integration"] = "litellm" return super().validate_environment( headers, model, messages, optional_params, litellm_params, api_key, api_base ) diff --git a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py index b679aa330fb..7966f60d683 100644 --- a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py +++ b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py @@ -5,12 +5,10 @@ Tests the response transformation to extract citation tokens and search queries from Perplexity API responses. """ -from unittest.mock import Mock - +import httpx import pytest # Add the project root to Python path - from litellm import ModelResponse from litellm.llms.perplexity.chat.transformation import PerplexityChatConfig from litellm.types.utils import Usage @@ -21,18 +19,19 @@ class TestPerplexityChatTransformation: @pytest.mark.parametrize( ("headers", "expected"), - [({}, "litellm"), ({"X-Pplx-Integration": "custom"}, "custom")], + [({}, "litellm"), ({"x-pplx-integration": "custom"}, "custom")], ) def test_integration_header_default_is_caller_overridable(self, headers, expected): - result = PerplexityChatConfig().validate_environment( + headers = PerplexityChatConfig().validate_environment( headers=headers, model="sonar", messages=[], optional_params={}, litellm_params={}, ) + request = httpx.Request("POST", "https://api.perplexity.ai/chat/completions", headers=headers) - assert result["X-Pplx-Integration"] == expected + assert request.headers.get_list("x-pplx-integration") == [expected] def test_enhance_usage_with_citation_tokens(self): """Test extraction of citation tokens from API response.""" From b4419f2a2100b53ebc8dec2eb561610a00bda1d0 Mon Sep 17 00:00:00 2001 From: Saleh Alghusson Date: Fri, 28 Aug 2026 12:15:32 -0400 Subject: [PATCH 4/4] Address Perplexity attribution test review --- .../chat/test_perplexity_chat_transformation.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py index 7966f60d683..5e835e44917 100644 --- a/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py +++ b/tests/test_litellm/llms/perplexity/chat/test_perplexity_chat_transformation.py @@ -21,15 +21,21 @@ class TestPerplexityChatTransformation: ("headers", "expected"), [({}, "litellm"), ({"x-pplx-integration": "custom"}, "custom")], ) - def test_integration_header_default_is_caller_overridable(self, headers, expected): - headers = PerplexityChatConfig().validate_environment( + def test_integration_header_default_is_caller_overridable( + self, headers: dict[str, str], expected: str + ): + validated_headers = PerplexityChatConfig().validate_environment( headers=headers, model="sonar", messages=[], optional_params={}, litellm_params={}, ) - request = httpx.Request("POST", "https://api.perplexity.ai/chat/completions", headers=headers) + request = httpx.Request( + "POST", + "https://api.perplexity.ai/chat/completions", + headers=validated_headers, + ) assert request.headers.get_list("x-pplx-integration") == [expected]