perplexity-haystack 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/CHANGELOG.md +7 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/PKG-INFO +1 -1
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/embedders/perplexity/document_embedder.py +8 -6
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/embedders/perplexity/text_embedder.py +4 -4
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/generators/perplexity/chat/chat_generator.py +16 -19
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_chat_generator.py +72 -9
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_document_embedder.py +2 -1
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_text_embedder.py +2 -1
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/.gitignore +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/LICENSE.txt +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/README.md +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/pydoc/config_docusaurus.yml +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/pyproject.toml +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/embedders/perplexity/__init__.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/embedders/perplexity/embedding_encoding.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/generators/perplexity/__init__.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/generators/perplexity/chat/__init__.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/generators/py.typed +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/websearch/perplexity/__init__.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/websearch/perplexity/perplexity_websearch.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/src/haystack_integrations/components/websearch/py.typed +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/__init__.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_embedding_encoding.py +0 -0
- {perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_websearch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perplexity-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Haystack integration for Perplexity Web Search
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/perplexity#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -142,9 +142,9 @@ class PerplexityDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
|
142
142
|
max_retries=max_retries,
|
|
143
143
|
http_client_kwargs=_http_client_kwargs_with_attribution(http_client_kwargs),
|
|
144
144
|
)
|
|
145
|
-
self.http_client_kwargs
|
|
146
|
-
|
|
147
|
-
self.
|
|
145
|
+
# self.http_client_kwargs keeps the attribution header so that haystack-ai >= 3.0 builds the clients
|
|
146
|
+
# with it at warm-up; the user-provided value is preserved for serialization
|
|
147
|
+
self._http_client_kwargs = http_client_kwargs
|
|
148
148
|
|
|
149
149
|
def _decode_response_embeddings(self, response: CreateEmbeddingResponse) -> list[list[float]]:
|
|
150
150
|
return [_decode_embedding(str(el.embedding), self.encoding_format) for el in response.data]
|
|
@@ -168,7 +168,8 @@ class PerplexityDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
try:
|
|
171
|
-
|
|
171
|
+
# with haystack-ai >= 3.0 the client is Optional and built by the warm_up call in run
|
|
172
|
+
response = self.client.embeddings.create(**args) # type: ignore[union-attr]
|
|
172
173
|
except APIError as exc:
|
|
173
174
|
ids = ", ".join(b[0] for b in batch)
|
|
174
175
|
msg = "Failed embedding of documents {ids} caused by {exc}"
|
|
@@ -212,7 +213,8 @@ class PerplexityDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
try:
|
|
215
|
-
|
|
216
|
+
# with haystack-ai >= 3.0 the client is Optional and built by the warm_up_async call in run_async
|
|
217
|
+
response = await self.async_client.embeddings.create(**args) # type: ignore[union-attr]
|
|
216
218
|
except APIError as exc:
|
|
217
219
|
ids = ", ".join(b[0] for b in batch)
|
|
218
220
|
msg = "Failed embedding of documents {ids} caused by {exc}"
|
|
@@ -255,7 +257,7 @@ class PerplexityDocumentEmbedder(OpenAIDocumentEmbedder):
|
|
|
255
257
|
encoding_format=self.encoding_format,
|
|
256
258
|
timeout=self.timeout,
|
|
257
259
|
max_retries=self.max_retries,
|
|
258
|
-
http_client_kwargs=self.
|
|
260
|
+
http_client_kwargs=self._http_client_kwargs,
|
|
259
261
|
)
|
|
260
262
|
|
|
261
263
|
@classmethod
|
|
@@ -114,9 +114,9 @@ class PerplexityTextEmbedder(OpenAITextEmbedder):
|
|
|
114
114
|
max_retries=max_retries,
|
|
115
115
|
http_client_kwargs=_http_client_kwargs_with_attribution(http_client_kwargs),
|
|
116
116
|
)
|
|
117
|
-
self.http_client_kwargs
|
|
118
|
-
|
|
119
|
-
self.
|
|
117
|
+
# self.http_client_kwargs keeps the attribution header so that haystack-ai >= 3.0 builds the clients
|
|
118
|
+
# with it at warm-up; the user-provided value is preserved for serialization
|
|
119
|
+
self._http_client_kwargs = http_client_kwargs
|
|
120
120
|
|
|
121
121
|
def _prepare_input(self, text: str) -> dict[str, Any]:
|
|
122
122
|
kwargs = OpenAITextEmbedder._prepare_input(self, text=text)
|
|
@@ -147,7 +147,7 @@ class PerplexityTextEmbedder(OpenAITextEmbedder):
|
|
|
147
147
|
encoding_format=self.encoding_format,
|
|
148
148
|
timeout=self.timeout,
|
|
149
149
|
max_retries=self.max_retries,
|
|
150
|
-
http_client_kwargs=self.
|
|
150
|
+
http_client_kwargs=self._http_client_kwargs,
|
|
151
151
|
)
|
|
152
152
|
|
|
153
153
|
@classmethod
|
|
@@ -25,20 +25,15 @@ def _attribution_header() -> str:
|
|
|
25
25
|
return f"{_INTEGRATION_SLUG}/{version}"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if with_options is not None:
|
|
38
|
-
return with_options(default_headers=headers)
|
|
39
|
-
|
|
40
|
-
client._custom_headers = {**getattr(client, "_custom_headers", {}), **headers}
|
|
41
|
-
return client
|
|
28
|
+
def _http_client_kwargs_with_headers(
|
|
29
|
+
http_client_kwargs: dict[str, Any] | None,
|
|
30
|
+
extra_headers: dict[str, Any] | None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
kwargs = dict(http_client_kwargs or {})
|
|
33
|
+
headers = {**kwargs.get("headers", {}), **(extra_headers or {})}
|
|
34
|
+
headers["X-Pplx-Integration"] = _attribution_header()
|
|
35
|
+
kwargs["headers"] = headers
|
|
36
|
+
return kwargs
|
|
42
37
|
|
|
43
38
|
|
|
44
39
|
@component
|
|
@@ -133,12 +128,12 @@ class PerplexityChatGenerator(OpenAIResponsesChatGenerator):
|
|
|
133
128
|
max_retries=max_retries,
|
|
134
129
|
tools=tools,
|
|
135
130
|
tools_strict=tools_strict,
|
|
136
|
-
http_client_kwargs=http_client_kwargs,
|
|
131
|
+
http_client_kwargs=_http_client_kwargs_with_headers(http_client_kwargs, extra_headers),
|
|
137
132
|
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
self.
|
|
133
|
+
# self.http_client_kwargs carries the attribution (and extra) headers so that the parent bakes them into
|
|
134
|
+
# the httpx client whether it is built eagerly (haystack-ai 2.x) or at warm-up (haystack-ai >= 3.0);
|
|
135
|
+
# the user-provided value is preserved for serialization
|
|
136
|
+
self._http_client_kwargs = http_client_kwargs
|
|
142
137
|
|
|
143
138
|
def to_dict(self) -> dict[str, Any]:
|
|
144
139
|
"""
|
|
@@ -150,6 +145,8 @@ class PerplexityChatGenerator(OpenAIResponsesChatGenerator):
|
|
|
150
145
|
data = super(PerplexityChatGenerator, self).to_dict() # noqa: UP008
|
|
151
146
|
data["type"] = generate_qualified_class_name(type(self))
|
|
152
147
|
data["init_parameters"]["extra_headers"] = self.extra_headers
|
|
148
|
+
# serialize the user-provided value, not the internal one enriched with attribution headers
|
|
149
|
+
data["init_parameters"]["http_client_kwargs"] = self._http_client_kwargs
|
|
153
150
|
return data
|
|
154
151
|
|
|
155
152
|
@classmethod
|
{perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_chat_generator.py
RENAMED
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from unittest.mock import patch
|
|
8
8
|
|
|
9
|
+
import httpx
|
|
9
10
|
import pytest
|
|
10
11
|
from haystack.components.generators.utils import print_streaming_chunk
|
|
11
12
|
from haystack.dataclasses import ChatMessage
|
|
@@ -37,6 +38,27 @@ def _make_response() -> Response:
|
|
|
37
38
|
return Response.construct(**response_data)
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
def _make_transport(captured: list[httpx.Request]) -> httpx.MockTransport:
|
|
42
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
43
|
+
captured.append(request)
|
|
44
|
+
return httpx.Response(
|
|
45
|
+
200,
|
|
46
|
+
json={
|
|
47
|
+
"id": "resp_test",
|
|
48
|
+
"created_at": datetime.now(tz=timezone.utc).timestamp(),
|
|
49
|
+
"model": "openai/gpt-5.4",
|
|
50
|
+
"object": "response",
|
|
51
|
+
"output": [],
|
|
52
|
+
"parallel_tool_calls": True,
|
|
53
|
+
"tool_choice": "auto",
|
|
54
|
+
"tools": [],
|
|
55
|
+
},
|
|
56
|
+
headers={"Content-Type": "application/json"},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return httpx.MockTransport(handler)
|
|
60
|
+
|
|
61
|
+
|
|
40
62
|
@pytest.fixture
|
|
41
63
|
def chat_messages() -> list[ChatMessage]:
|
|
42
64
|
return [
|
|
@@ -63,7 +85,7 @@ class TestPerplexityChatGenerator:
|
|
|
63
85
|
|
|
64
86
|
component = PerplexityChatGenerator()
|
|
65
87
|
|
|
66
|
-
assert component.
|
|
88
|
+
assert component.api_key.resolve_value() == "test-api-key"
|
|
67
89
|
assert component.model == "openai/gpt-5.4"
|
|
68
90
|
assert component.api_base_url == "https://api.perplexity.ai/v1"
|
|
69
91
|
assert component.streaming_callback is None
|
|
@@ -84,7 +106,7 @@ class TestPerplexityChatGenerator:
|
|
|
84
106
|
http_client_kwargs={"proxy": "http://localhost:8080"},
|
|
85
107
|
)
|
|
86
108
|
|
|
87
|
-
assert component.
|
|
109
|
+
assert component.api_key.resolve_value() == "test-api-key"
|
|
88
110
|
assert component.model == "anthropic/claude-sonnet-4-6"
|
|
89
111
|
assert component.streaming_callback is print_streaming_chunk
|
|
90
112
|
assert component.generation_kwargs == {
|
|
@@ -96,7 +118,16 @@ class TestPerplexityChatGenerator:
|
|
|
96
118
|
assert component.extra_headers == {"test-header": "test-value"}
|
|
97
119
|
assert component.timeout == 10
|
|
98
120
|
assert component.max_retries == 2
|
|
99
|
-
|
|
121
|
+
|
|
122
|
+
assert component._http_client_kwargs == {"proxy": "http://localhost:8080"}
|
|
123
|
+
assert component.http_client_kwargs["headers"]["test-header"] == "test-value"
|
|
124
|
+
assert component.http_client_kwargs["headers"]["X-Pplx-Integration"].startswith("haystack/")
|
|
125
|
+
|
|
126
|
+
def test_warm_up(self, monkeypatch):
|
|
127
|
+
monkeypatch.setenv("PERPLEXITY_API_KEY", "test-api-key")
|
|
128
|
+
component = PerplexityChatGenerator()
|
|
129
|
+
component.warm_up() # with haystack-ai >= 3.0 the client is created during warm-up
|
|
130
|
+
assert component.client.api_key == "test-api-key"
|
|
100
131
|
|
|
101
132
|
def test_to_dict_default_round_trip(self, monkeypatch):
|
|
102
133
|
monkeypatch.setenv("PERPLEXITY_API_KEY", "test-api-key")
|
|
@@ -177,7 +208,7 @@ class TestPerplexityChatGenerator:
|
|
|
177
208
|
assert deserialized.extra_headers == {"test-header": "test-value"}
|
|
178
209
|
assert deserialized.timeout == 10
|
|
179
210
|
assert deserialized.max_retries == 2
|
|
180
|
-
assert deserialized.
|
|
211
|
+
assert deserialized._http_client_kwargs == {"proxy": "http://localhost:8080"}
|
|
181
212
|
|
|
182
213
|
def test_run_uses_responses_create(self, chat_messages):
|
|
183
214
|
component = PerplexityChatGenerator(api_key=Secret.from_token("test-api-key"))
|
|
@@ -193,16 +224,48 @@ class TestPerplexityChatGenerator:
|
|
|
193
224
|
assert "messages" not in call_kwargs
|
|
194
225
|
assert call_kwargs["stream"] is False
|
|
195
226
|
|
|
196
|
-
def
|
|
227
|
+
def test_http_client_kwargs_with_headers_merges_extra_and_attribution(self):
|
|
228
|
+
kwargs = chat_generator_module._http_client_kwargs_with_headers(
|
|
229
|
+
{"headers": {"existing-header": "existing-value"}},
|
|
230
|
+
{"test-header": "test-value"},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
assert kwargs["headers"]["existing-header"] == "existing-value"
|
|
234
|
+
assert kwargs["headers"]["test-header"] == "test-value"
|
|
235
|
+
assert kwargs["headers"]["X-Pplx-Integration"].startswith("haystack/")
|
|
236
|
+
|
|
237
|
+
def test_run_sends_attribution_header(self, chat_messages):
|
|
238
|
+
captured: list[httpx.Request] = []
|
|
197
239
|
component = PerplexityChatGenerator(
|
|
198
240
|
api_key=Secret.from_token("test-api-key"),
|
|
199
241
|
extra_headers={"test-header": "test-value"},
|
|
242
|
+
http_client_kwargs={"transport": _make_transport(captured)},
|
|
200
243
|
)
|
|
201
244
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
assert
|
|
205
|
-
|
|
245
|
+
component.run(chat_messages)
|
|
246
|
+
|
|
247
|
+
assert len(captured) == 1
|
|
248
|
+
request = captured[0]
|
|
249
|
+
assert request.headers["Authorization"] == "Bearer test-api-key"
|
|
250
|
+
assert request.headers["X-Pplx-Integration"].startswith("haystack/")
|
|
251
|
+
assert request.headers["test-header"] == "test-value"
|
|
252
|
+
|
|
253
|
+
@pytest.mark.asyncio
|
|
254
|
+
async def test_run_async_sends_attribution_header(self, chat_messages):
|
|
255
|
+
captured: list[httpx.Request] = []
|
|
256
|
+
component = PerplexityChatGenerator(
|
|
257
|
+
api_key=Secret.from_token("test-api-key"),
|
|
258
|
+
extra_headers={"test-header": "test-value"},
|
|
259
|
+
http_client_kwargs={"transport": _make_transport(captured)},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
await component.run_async(chat_messages)
|
|
263
|
+
|
|
264
|
+
assert len(captured) == 1
|
|
265
|
+
request = captured[0]
|
|
266
|
+
assert request.headers["Authorization"] == "Bearer test-api-key"
|
|
267
|
+
assert request.headers["X-Pplx-Integration"].startswith("haystack/")
|
|
268
|
+
assert request.headers["test-header"] == "test-value"
|
|
206
269
|
|
|
207
270
|
|
|
208
271
|
@pytest.mark.skipif(
|
{perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_document_embedder.py
RENAMED
|
@@ -236,7 +236,8 @@ class TestPerplexityDocumentEmbedder:
|
|
|
236
236
|
assert component.encoding_format == "base64_binary"
|
|
237
237
|
assert component.timeout is None
|
|
238
238
|
assert component.max_retries is None
|
|
239
|
-
|
|
239
|
+
# the user-provided value; component.http_client_kwargs carries the attribution header
|
|
240
|
+
assert component._http_client_kwargs is None
|
|
240
241
|
|
|
241
242
|
def test_run_sends_attribution_header(self):
|
|
242
243
|
captured: list[httpx.Request] = []
|
{perplexity_haystack-0.1.1 → perplexity_haystack-0.2.0}/tests/test_perplexity_text_embedder.py
RENAMED
|
@@ -179,7 +179,8 @@ class TestPerplexityTextEmbedder:
|
|
|
179
179
|
assert component.encoding_format == "base64_binary"
|
|
180
180
|
assert component.timeout is None
|
|
181
181
|
assert component.max_retries is None
|
|
182
|
-
|
|
182
|
+
# the user-provided value; component.http_client_kwargs carries the attribution header
|
|
183
|
+
assert component._http_client_kwargs is None
|
|
183
184
|
|
|
184
185
|
def test_run_sends_attribution_header(self):
|
|
185
186
|
captured: list[httpx.Request] = []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|