hyperforge-perplexity 1.0.0.post22__py3-none-any.whl
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.
- hyperforge_perplexity/__init__.py +4 -0
- hyperforge_perplexity/config.py +64 -0
- hyperforge_perplexity/driver.py +27 -0
- hyperforge_perplexity/perplexity.py +197 -0
- hyperforge_perplexity/py.typed +0 -0
- hyperforge_perplexity-1.0.0.post22.dist-info/METADATA +21 -0
- hyperforge_perplexity-1.0.0.post22.dist-info/RECORD +9 -0
- hyperforge_perplexity-1.0.0.post22.dist-info/WHEEL +5 -0
- hyperforge_perplexity-1.0.0.post22.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import ClassVar, List, Literal, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
from hyperforge.context.config import ContextAgentConfig
|
|
4
|
+
from hyperforge.driver import DriverConfig, EncryptedPayload
|
|
5
|
+
from hyperforge.utils import WidgetType
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
from pydantic.config import ConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PerplexityInnerConfig(EncryptedPayload):
|
|
11
|
+
encrypted_fields: ClassVar[list[str]] = ["key"]
|
|
12
|
+
key: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PerplexityDriverConfig(DriverConfig[PerplexityInnerConfig]):
|
|
16
|
+
model_config = ConfigDict(title="Perplexity")
|
|
17
|
+
provider: Literal["perplexity"]
|
|
18
|
+
config: PerplexityInnerConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PerplexityAgentConfig(ContextAgentConfig):
|
|
22
|
+
model_config = ConfigDict(title="Perplexity Answer")
|
|
23
|
+
module: Literal["perplexity"] = "perplexity"
|
|
24
|
+
published_functions: Optional[Tuple[str, ...]] = Field(
|
|
25
|
+
default=("internet_search",),
|
|
26
|
+
title="Published functions",
|
|
27
|
+
description="List of functions published by this agent to be used by other agents in the chain",
|
|
28
|
+
json_schema_extra={
|
|
29
|
+
"widget": WidgetType.NOT_SHOWN,
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
domain: List[str] = Field(
|
|
33
|
+
default_factory=list,
|
|
34
|
+
title="Domain Filter",
|
|
35
|
+
description="Domains to restrict the Perplexity search to.",
|
|
36
|
+
)
|
|
37
|
+
search_context_size: Literal["low", "medium", "high"] = Field(
|
|
38
|
+
default="low",
|
|
39
|
+
title="Search Context Size",
|
|
40
|
+
description="Determines how much search context Perplexity retrieves for the model. "
|
|
41
|
+
"Options are: `low` (minimizes context for cost savings but less comprehensive answers), "
|
|
42
|
+
"`medium` (balanced approach suitable for most queries), "
|
|
43
|
+
"and `high` (maximizes context for comprehensive answers but at higher cost).",
|
|
44
|
+
)
|
|
45
|
+
related_questions: bool = Field(
|
|
46
|
+
default=False,
|
|
47
|
+
title="Generate Related Questions",
|
|
48
|
+
description="Determines if Perplexity should return related questions to the original. "
|
|
49
|
+
"They will be stored in the Agentic Memory as future questions",
|
|
50
|
+
)
|
|
51
|
+
images: bool = Field(
|
|
52
|
+
default=False,
|
|
53
|
+
title="Return Images",
|
|
54
|
+
description="Determines whether Perplexity search results should include images.",
|
|
55
|
+
)
|
|
56
|
+
prompt: Optional[str] = Field(
|
|
57
|
+
None,
|
|
58
|
+
title="Custom Prompt",
|
|
59
|
+
description="Custom prompt to use for the Perplexity agent.",
|
|
60
|
+
json_schema_extra={
|
|
61
|
+
"widget": WidgetType.EXPANDABLE_TEXTAREA,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
source: str = "perplexity"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from hyperforge.configure import driver
|
|
2
|
+
from hyperforge.driver import Driver
|
|
3
|
+
from perplexity import AsyncPerplexity
|
|
4
|
+
|
|
5
|
+
from hyperforge_perplexity.config import PerplexityDriverConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@driver(
|
|
9
|
+
id="perplexity",
|
|
10
|
+
title="Perplexity Driver",
|
|
11
|
+
description="Driver for interacting with the Perplexity API.",
|
|
12
|
+
config_schema=PerplexityDriverConfig,
|
|
13
|
+
)
|
|
14
|
+
class PerplexityDriver(Driver):
|
|
15
|
+
client: AsyncPerplexity
|
|
16
|
+
api_key: str
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
async def init(cls, driver: PerplexityDriverConfig):
|
|
20
|
+
client = AsyncPerplexity(api_key=driver.config.key)
|
|
21
|
+
|
|
22
|
+
return cls(
|
|
23
|
+
api_key=driver.config.key,
|
|
24
|
+
client=client,
|
|
25
|
+
name=driver.name,
|
|
26
|
+
provider=driver.provider,
|
|
27
|
+
)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from base64 import b64encode
|
|
2
|
+
from time import time
|
|
3
|
+
from typing import Any, ClassVar, Dict, List, Optional, cast
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from hyperforge.agent import Agent
|
|
7
|
+
from hyperforge.configure import agent
|
|
8
|
+
from hyperforge.context.agent import ContextAgent
|
|
9
|
+
from hyperforge.definition import FunctionDefinition
|
|
10
|
+
from hyperforge.manager import Manager
|
|
11
|
+
from hyperforge.memory import Chunk, Context, QuestionMemory
|
|
12
|
+
from hyperforge.utils.http import safe_http_client
|
|
13
|
+
from nuclia.lib.nua_responses import Image
|
|
14
|
+
from perplexity.types import ChatMessageInput
|
|
15
|
+
from perplexity.types.shared.chat_message_output import ChatMessageOutput
|
|
16
|
+
from perplexity.types.shared_params.web_search_options import WebSearchOptions
|
|
17
|
+
|
|
18
|
+
from hyperforge_perplexity.config import PerplexityAgentConfig
|
|
19
|
+
from hyperforge_perplexity.driver import PerplexityDriver
|
|
20
|
+
|
|
21
|
+
SYSTEM_PROMPT = "Be precise and concise"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@agent(
|
|
25
|
+
id="perplexity",
|
|
26
|
+
agent_type="context",
|
|
27
|
+
title="Perplexity Answers",
|
|
28
|
+
description="Use Perplexity to get information from the internet.",
|
|
29
|
+
config_schema=PerplexityAgentConfig,
|
|
30
|
+
)
|
|
31
|
+
class PerplexityAgent(ContextAgent, Agent[PerplexityAgentConfig]):
|
|
32
|
+
driver: Optional[PerplexityDriver] = None
|
|
33
|
+
__published_functions__: ClassVar[Dict[str, FunctionDefinition]] = {
|
|
34
|
+
"internet_search": FunctionDefinition(
|
|
35
|
+
name="internet_search",
|
|
36
|
+
description="Performs an internet search using Perplexity Search to get context to answer questions.",
|
|
37
|
+
parameters={
|
|
38
|
+
"question": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "The question to search for on the internet.",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def build_messages(self, question: str) -> list:
|
|
47
|
+
"""Builds the messages list for the chat completion."""
|
|
48
|
+
system_prompt = (
|
|
49
|
+
self.config.prompt if self.config.prompt is not None else SYSTEM_PROMPT
|
|
50
|
+
)
|
|
51
|
+
return [
|
|
52
|
+
ChatMessageInput(role="system", content=system_prompt),
|
|
53
|
+
ChatMessageInput(role="user", content=question),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
async def internet_search(
|
|
57
|
+
self,
|
|
58
|
+
question: str,
|
|
59
|
+
memory: QuestionMemory,
|
|
60
|
+
manager: Manager,
|
|
61
|
+
question_uuid: Optional[str] = None,
|
|
62
|
+
) -> Context:
|
|
63
|
+
messages = self.build_messages(question)
|
|
64
|
+
|
|
65
|
+
t0 = time()
|
|
66
|
+
|
|
67
|
+
if self.driver is None:
|
|
68
|
+
self.driver: Optional[PerplexityDriver] = cast(
|
|
69
|
+
Optional[PerplexityDriver], manager.drivers.get(self.config.source)
|
|
70
|
+
)
|
|
71
|
+
if self.driver is None:
|
|
72
|
+
raise Exception("Perplexity driver does not exist")
|
|
73
|
+
|
|
74
|
+
web_search_options = WebSearchOptions(
|
|
75
|
+
search_context_size=self.config.search_context_size,
|
|
76
|
+
)
|
|
77
|
+
response = await self.driver.client.chat.completions.create(
|
|
78
|
+
messages=messages,
|
|
79
|
+
model="sonar-pro",
|
|
80
|
+
return_images=self.config.images,
|
|
81
|
+
return_related_questions=self.config.related_questions,
|
|
82
|
+
search_domain_filter=self.config.domain,
|
|
83
|
+
web_search_options=web_search_options,
|
|
84
|
+
)
|
|
85
|
+
context = Context(
|
|
86
|
+
agent_id=self.agent_id,
|
|
87
|
+
original_question_uuid=memory.original_question_uuid,
|
|
88
|
+
actual_question_uuid=question_uuid,
|
|
89
|
+
question=question,
|
|
90
|
+
title=self.config.title
|
|
91
|
+
if self.config.title
|
|
92
|
+
else "Internet search with Perplexity",
|
|
93
|
+
source="perplexity",
|
|
94
|
+
agent=self.config.module,
|
|
95
|
+
)
|
|
96
|
+
text = None
|
|
97
|
+
for choice in response.choices:
|
|
98
|
+
if (
|
|
99
|
+
choice.message is not None
|
|
100
|
+
and choice.message.content is not None
|
|
101
|
+
and isinstance(choice.message, ChatMessageOutput)
|
|
102
|
+
):
|
|
103
|
+
text = (
|
|
104
|
+
choice.message.content
|
|
105
|
+
if isinstance(choice.message.content, str)
|
|
106
|
+
else str(choice.message.content)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
hasattr(response, "images")
|
|
111
|
+
and response.images is not None
|
|
112
|
+
and isinstance(response.images, list)
|
|
113
|
+
):
|
|
114
|
+
for image in response.images:
|
|
115
|
+
image_dict = cast(Dict[str, Any], image)
|
|
116
|
+
async with safe_http_client() as session:
|
|
117
|
+
resp = await session.get(image_dict["image_url"])
|
|
118
|
+
content_type = resp.headers.get("Content-Type")
|
|
119
|
+
if content_type:
|
|
120
|
+
# Remove parameters like "; charset=utf-8"
|
|
121
|
+
mime = content_type.split(";", 1)[0].strip()
|
|
122
|
+
if mime:
|
|
123
|
+
context.images[image_dict["origin_url"]] = Image(
|
|
124
|
+
content_type=mime,
|
|
125
|
+
b64encoded=b64encode(resp.content).decode(),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if text is not None:
|
|
129
|
+
chunk = Chunk(
|
|
130
|
+
chunk_id=uuid4().hex,
|
|
131
|
+
text=text,
|
|
132
|
+
labels=[],
|
|
133
|
+
url=response.citations, # type: ignore
|
|
134
|
+
origin_agent=self.config.module,
|
|
135
|
+
)
|
|
136
|
+
context.chunks.append(chunk)
|
|
137
|
+
|
|
138
|
+
# for chunk in response add to context
|
|
139
|
+
input_nuclia_tokens = 0
|
|
140
|
+
context.summary = text if text is not None else ""
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
self.config.related_questions
|
|
144
|
+
and hasattr(response, "related_questions")
|
|
145
|
+
and response.related_questions is not None
|
|
146
|
+
):
|
|
147
|
+
questions: List[str] = cast(List[str], response.related_questions)
|
|
148
|
+
memory.add_future_questions(questions)
|
|
149
|
+
|
|
150
|
+
# TODO: Report nuclia tokens correctly
|
|
151
|
+
if response.usage is not None and response.usage.total_tokens is not None:
|
|
152
|
+
input_nuclia_tokens += response.usage.total_tokens
|
|
153
|
+
|
|
154
|
+
await memory.add_step(
|
|
155
|
+
step_module=self.config.module,
|
|
156
|
+
step_title=self.step_title("Search results"),
|
|
157
|
+
step_reason="",
|
|
158
|
+
step_agent_path=f"/context/{self.config.id or self.agent_id}",
|
|
159
|
+
step_value=text if text is not None else "",
|
|
160
|
+
timeit=time() - t0,
|
|
161
|
+
input_nuclia_tokens=input_nuclia_tokens,
|
|
162
|
+
output_nuclia_tokens=0,
|
|
163
|
+
)
|
|
164
|
+
return context
|
|
165
|
+
|
|
166
|
+
async def _get_question_context(
|
|
167
|
+
self,
|
|
168
|
+
memory: QuestionMemory,
|
|
169
|
+
manager: Manager,
|
|
170
|
+
question_uuid: str,
|
|
171
|
+
question: str,
|
|
172
|
+
flow_id: str,
|
|
173
|
+
extra_context: Optional[Dict[str, Any]] = None,
|
|
174
|
+
) -> List[tuple[str, str]]:
|
|
175
|
+
if self.driver is None:
|
|
176
|
+
self.driver: Optional[PerplexityDriver] = cast(
|
|
177
|
+
Optional[PerplexityDriver], manager.drivers.get(self.config.source)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if self.driver is None:
|
|
181
|
+
raise Exception("Perplexity driver does not exist")
|
|
182
|
+
|
|
183
|
+
context = await self.internet_search(
|
|
184
|
+
question,
|
|
185
|
+
memory,
|
|
186
|
+
manager,
|
|
187
|
+
question_uuid=question_uuid,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
missing = await self.save_ctx_and_return_missing(
|
|
191
|
+
context=context,
|
|
192
|
+
question=question,
|
|
193
|
+
memory=memory,
|
|
194
|
+
manager=manager,
|
|
195
|
+
flow_id=flow_id,
|
|
196
|
+
)
|
|
197
|
+
return [missing] if missing is not None else []
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge_perplexity
|
|
3
|
+
Version: 1.0.0.post22
|
|
4
|
+
Summary: Perplexity Hyperforge agent
|
|
5
|
+
Author-email: Nuclia <nucliadb@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://progress.com
|
|
8
|
+
Project-URL: Repository, https://github.com/nuclia/hyperforge
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: <4,>=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: hyperforge
|
|
18
|
+
Requires-Dist: httpx
|
|
19
|
+
Requires-Dist: perplexityai>=0.35.1
|
|
20
|
+
|
|
21
|
+
# Perplexity Hyperforge agents
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
hyperforge_perplexity/__init__.py,sha256=1NLnfxhRQBKSXNarJGUtJUqbdxC6sYZaoqMZxOohsF0,128
|
|
2
|
+
hyperforge_perplexity/config.py,sha256=ooOknxzeWTVOIyOpnmHUnkn8cwbFxAkPh2U72R3p0Ic,2459
|
|
3
|
+
hyperforge_perplexity/driver.py,sha256=bHQry9SYskDbgKHi_OMwaKAPKVXe1ySWQJjSJI-vRbA,732
|
|
4
|
+
hyperforge_perplexity/perplexity.py,sha256=BqdtAkzCwfrqyjPQ9wtYJH9CuV8_weR-Kr46KksjHxM,7220
|
|
5
|
+
hyperforge_perplexity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
hyperforge_perplexity-1.0.0.post22.dist-info/METADATA,sha256=zICmj1ff2p3V0sTOZQ60M245dN0JiL5M_PVMqE8_UPc,796
|
|
7
|
+
hyperforge_perplexity-1.0.0.post22.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
hyperforge_perplexity-1.0.0.post22.dist-info/top_level.txt,sha256=930QZHtq_vfVXK6qol86FhpLVYJRUmtsQoDf58OZKyQ,22
|
|
9
|
+
hyperforge_perplexity-1.0.0.post22.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyperforge_perplexity
|