33# SPDX-License-Identifier: Apache-2.0
44
55import importlib .metadata
6- from typing import Any
7-
8- from haystack import component , default_to_dict , logging
9- from haystack .components .generators .chat import OpenAIChatGenerator
10- from haystack .dataclasses import ChatMessage , StreamingCallbackT
11- from haystack .tools import (
12- ToolsType ,
13- _check_duplicate_tool_names ,
14- flatten_tools_or_toolsets ,
15- serialize_tools_or_toolset ,
16- )
17- from haystack .utils import serialize_callable
18- from haystack .utils .auth import Secret
6+ from typing import Any , ClassVar
197
20- logger = logging .getLogger (__name__ )
8+ from haystack import component , default_from_dict
9+ from haystack .components .generators .chat import OpenAIResponsesChatGenerator
10+ from haystack .dataclasses import StreamingCallbackT
11+ from haystack .tools import ToolsType , deserialize_tools_or_toolset_inplace
12+ from haystack .utils import deserialize_callable
13+ from haystack .utils .auth import Secret
2114
2215_INTEGRATION_SLUG = "haystack"
2316_PACKAGE_NAME = "perplexity-haystack"
17+ _PERPLEXITY_COMPONENT_PATH = "haystack_integrations.components.generators.perplexity.PerplexityChatGenerator"
18+ _PERPLEXITY_INTERNAL_COMPONENT_PATH = (
19+ "haystack_integrations.components.generators.perplexity.chat.chat_generator.PerplexityChatGenerator"
20+ )
2421
2522
2623def _attribution_header () -> str :
@@ -31,31 +28,38 @@ def _attribution_header() -> str:
3128 return f"{ _INTEGRATION_SLUG } /{ version } "
3229
3330
34- @component
35- class PerplexityChatGenerator (OpenAIChatGenerator ):
36- """
37- Enables text generation using the Perplexity Agent API.
31+ def _perplexity_headers (extra_headers : dict [str , Any ] | None = None ) -> dict [str , Any ]:
32+ return {
33+ ** (extra_headers or {}),
34+ "X-Pplx-Integration" : _attribution_header (),
35+ }
36+
37+
38+ def _with_default_headers (client : Any , headers : dict [str , Any ]) -> Any :
39+ with_options = getattr (client , "with_options" , None )
40+ if with_options is not None :
41+ return with_options (default_headers = headers )
3842
39- For supported models, see [Perplexity docs](https://docs.perplexity.ai/).
43+ client ._custom_headers = {** getattr (client , "_custom_headers" , {}), ** headers }
44+ return client
4045
41- Users can pass any text generation parameters valid for the Perplexity chat completion API
42- directly to this component using the `generation_kwargs` parameter in `__init__` or the `generation_kwargs`
43- parameter in `run` method.
4446
45- Key Features and Compatibility:
46- - **Primary Compatibility**: Designed to work seamlessly with the Perplexity chat completion endpoint.
47- - **Streaming Support**: Supports streaming responses from the Perplexity chat completion endpoint.
48- - **Customizability**: Supports all parameters supported by the Perplexity chat completion endpoint .
47+ @ component
48+ class PerplexityChatGenerator ( OpenAIResponsesChatGenerator ):
49+ """
50+ Completes chats using Perplexity models .
4951
50- This component uses the ChatMessage format for structuring both input and output,
51- ensuring coherent and contextually relevant responses in chat-based text generation scenarios.
52- Details on the ChatMessage format can be found in the
53- [Haystack docs](https://docs.haystack.deepset.ai/docs/chatmessage)
52+ Powered by the Perplexity Agent API (`POST /v1/agent`, OpenAI Responses-compatible).
53+ See the [Perplexity Agent API quickstart](https://docs.perplexity.ai/docs/agent-api/quickstart)
54+ for details.
5455
55- Usage example:
56+ It uses the [ChatMessage](https://docs.haystack.deepset.ai/docs/chatmessage) format in input and output.
57+ You can customize generation by passing Perplexity Agent API parameters through `generation_kwargs`.
58+
59+ ### Usage example
5660 ```python
57- from haystack_integrations.components.generators.perplexity import PerplexityChatGenerator
5861 from haystack.dataclasses import ChatMessage
62+ from haystack_integrations.components.generators.perplexity import PerplexityChatGenerator
5963
6064 messages = [ChatMessage.from_user("What's Natural Language Processing?")]
6165
@@ -65,62 +69,79 @@ class PerplexityChatGenerator(OpenAIChatGenerator):
6569 ```
6670 """
6771
72+ SUPPORTED_MODELS : ClassVar [list [str ]] = [
73+ "openai/gpt-5.5" ,
74+ "openai/gpt-5.4" ,
75+ "openai/gpt-4o" ,
76+ "anthropic/claude-sonnet-4-6" ,
77+ "xai/grok-4-1" ,
78+ "google/gemini-3-flash-preview" ,
79+ ]
80+ """A non-exhaustive list of Agent API models supported by this component.
81+ See https://docs.perplexity.ai/docs/agent-api/models for the full and current list."""
82+
6883 def __init__ (
6984 self ,
7085 * ,
7186 api_key : Secret = Secret .from_env_var ("PERPLEXITY_API_KEY" ),
72- model : str = "sonar-pro" ,
87+ model : str = "openai/gpt-5.4" ,
88+ api_base_url : str | None = "https://api.perplexity.ai/v1" ,
7389 streaming_callback : StreamingCallbackT | None = None ,
74- api_base_url : str | None = "https://api.perplexity.ai" ,
90+ organization : str | None = None ,
7591 generation_kwargs : dict [str , Any ] | None = None ,
76- tools : ToolsType | None = None ,
92+ tools : ToolsType | list [dict [str , Any ]] | None = None ,
93+ tools_strict : bool = False ,
7794 timeout : float | None = None ,
7895 extra_headers : dict [str , Any ] | None = None ,
7996 max_retries : int | None = None ,
8097 http_client_kwargs : dict [str , Any ] | None = None ,
8198 ) -> None :
8299 """
83- Creates an instance of PerplexityChatGenerator.
100+ Initialize the PerplexityChatGenerator component .
84101
85102 :param api_key:
86103 The Perplexity API key.
87104 :param model:
88- The name of the Perplexity chat completion model to use.
89- :param streaming_callback:
90- A callback function that is called when a new token is received from the stream.
91- The callback function accepts StreamingChunk as an argument.
105+ The Perplexity Agent API model to use.
92106 :param api_base_url:
93107 The Perplexity API base URL.
108+ :param streaming_callback:
109+ A callback function called when a new token is received from the stream.
110+ :param organization:
111+ Organization ID forwarded to the OpenAI-compatible client.
94112 :param generation_kwargs:
95- Other parameters to use for the model. These parameters are all sent directly to
96- the Perplexity endpoint.
113+ Additional parameters sent directly to the Perplexity Agent API.
97114 :param tools:
98- A list of tools or a Toolset for which the model can prepare calls. This parameter can accept either a
99- list of `Tool` objects or a `Toolset` instance.
115+ A list of Haystack tools, a Toolset, or OpenAI-compatible tool definitions.
116+ :param tools_strict:
117+ Whether to enable strict schema adherence for Haystack tool calls.
100118 :param timeout:
101- The timeout for the Perplexity API call .
119+ Timeout for Perplexity API calls .
102120 :param extra_headers:
103121 Additional HTTP headers to include in requests to the Perplexity API.
104122 :param max_retries:
105123 Maximum number of retries to contact Perplexity after an internal error.
106- If not set, it defaults to either the `OPENAI_MAX_RETRIES` environment variable, or set to 5.
107124 :param http_client_kwargs:
108- A dictionary of keyword arguments to configure a custom `httpx.Client`or `httpx.AsyncClient`.
109- For more information, see the [HTTPX documentation](https://www.python-httpx.org/api/#client).
110-
125+ A dictionary of keyword arguments to configure a custom `httpx.Client` or `httpx.AsyncClient`.
111126 """
127+ self .extra_headers = extra_headers
112128 super (PerplexityChatGenerator , self ).__init__ ( # noqa: UP008
113129 api_key = api_key ,
114130 model = model ,
115131 streaming_callback = streaming_callback ,
116132 api_base_url = api_base_url ,
133+ organization = organization ,
117134 generation_kwargs = generation_kwargs ,
118- tools = tools ,
119135 timeout = timeout ,
120136 max_retries = max_retries ,
137+ tools = tools ,
138+ tools_strict = tools_strict ,
121139 http_client_kwargs = http_client_kwargs ,
122140 )
123- self .extra_headers = extra_headers
141+
142+ default_headers = _perplexity_headers (extra_headers )
143+ self .client = _with_default_headers (self .client , default_headers )
144+ self .async_client = _with_default_headers (self .async_client , default_headers )
124145
125146 def to_dict (self ) -> dict [str , Any ]:
126147 """
@@ -129,90 +150,30 @@ def to_dict(self) -> dict[str, Any]:
129150 :returns:
130151 The serialized component as a dictionary.
131152 """
132- callback_name = serialize_callable (self .streaming_callback ) if self .streaming_callback else None
133-
134- return default_to_dict (
135- self ,
136- model = self .model ,
137- streaming_callback = callback_name ,
138- api_base_url = self .api_base_url ,
139- generation_kwargs = self .generation_kwargs ,
140- api_key = self .api_key .to_dict (),
141- tools = serialize_tools_or_toolset (self .tools ),
142- extra_headers = self .extra_headers ,
143- timeout = self .timeout ,
144- max_retries = self .max_retries ,
145- http_client_kwargs = self .http_client_kwargs ,
146- )
153+ data = super (PerplexityChatGenerator , self ).to_dict () # noqa: UP008
154+ data ["type" ] = _PERPLEXITY_COMPONENT_PATH
155+ data ["init_parameters" ]["extra_headers" ] = self .extra_headers
156+ return data
147157
148- def _prepare_api_call (
149- self ,
150- * ,
151- messages : list [ChatMessage ],
152- streaming_callback : StreamingCallbackT | None = None ,
153- generation_kwargs : dict [str , Any ] | None = None ,
154- tools : ToolsType | None = None ,
155- tools_strict : bool | None = None ,
156- ) -> dict [str , Any ]:
157- # update generation kwargs by merging with the generation kwargs passed to the run method
158- generation_kwargs = {** self .generation_kwargs , ** (generation_kwargs or {})}
159- extra_headers = {
160- ** (self .extra_headers or {}),
161- "X-Pplx-Integration" : _attribution_header (),
162- }
163-
164- is_streaming = streaming_callback is not None
165- num_responses = generation_kwargs .pop ("n" , 1 )
166-
167- if is_streaming and num_responses > 1 :
168- msg = "Cannot stream multiple responses, please set n=1."
169- raise ValueError (msg )
170- response_format = generation_kwargs .pop ("response_format" , None )
171-
172- # adapt ChatMessage(s) to the format expected by the OpenAI API
173- openai_formatted_messages = [message .to_openai_dict_format () for message in messages ]
174-
175- flattened_tools = flatten_tools_or_toolsets (tools or self .tools )
176- tools_strict = tools_strict if tools_strict is not None else self .tools_strict
177- _check_duplicate_tool_names (flattened_tools )
178-
179- openai_tools = {}
180- if flattened_tools :
181- tool_definitions = []
182- for t in flattened_tools :
183- function_spec = {** t .tool_spec }
184- if tools_strict :
185- function_spec ["strict" ] = True
186- function_spec ["parameters" ]["additionalProperties" ] = False
187- tool_definitions .append ({"type" : "function" , "function" : function_spec })
188- openai_tools = {"tools" : tool_definitions }
189-
190- base_args = {
191- "model" : self .model ,
192- "messages" : openai_formatted_messages ,
193- "n" : num_responses ,
194- ** openai_tools ,
195- "extra_headers" : {** extra_headers },
196- "extra_body" : {** generation_kwargs },
197- }
198-
199- if response_format and not is_streaming :
200- # for structured outputs without streaming, we use openai's parse endpoint
201- # Note: `stream` cannot be passed to chat.completions.parse
202- # we pass a key `openai_endpoint` as a hint to the run method to use the parse endpoint
203- # this key will be removed before the API call is made
204- return {
205- ** base_args ,
206- "response_format" : response_format ,
207- "openai_endpoint" : "parse" ,
208- }
209-
210- # for structured outputs with streaming, we use openai's create endpoint
211- # we pass a key `openai_endpoint` as a hint to the run method to use the create endpoint
212- # this key will be removed before the API call is made
213- final_args = {** base_args , "stream" : is_streaming , "openai_endpoint" : "create" }
214-
215- # We only set the response_format parameter if it's not None since None is not a valid value in the API.
216- if response_format :
217- final_args ["response_format" ] = response_format
218- return final_args
158+ @classmethod
159+ def from_dict (cls , data : dict [str , Any ]) -> "PerplexityChatGenerator" :
160+ """
161+ Deserialize this component from a dictionary.
162+
163+ :param data: The dictionary representation of this component.
164+ :returns:
165+ The deserialized component instance.
166+ """
167+ tools = data ["init_parameters" ].get ("tools" )
168+ if tools and (
169+ (isinstance (tools , dict ) and tools .get ("type" ) == "haystack.tools.toolset.Toolset" )
170+ or (isinstance (tools , list ) and tools [0 ].get ("type" ) == "haystack.tools.tool.Tool" )
171+ ):
172+ deserialize_tools_or_toolset_inplace (data ["init_parameters" ], key = "tools" )
173+
174+ serialized_callback_handler = data .get ("init_parameters" , {}).get ("streaming_callback" )
175+ if serialized_callback_handler :
176+ data ["init_parameters" ]["streaming_callback" ] = deserialize_callable (serialized_callback_handler )
177+
178+ data ["type" ] = _PERPLEXITY_INTERNAL_COMPONENT_PATH
179+ return default_from_dict (cls , data )
0 commit comments