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.
@@ -0,0 +1,4 @@
1
+ from .driver import PerplexityDriver
2
+ from .perplexity import PerplexityAgent
3
+
4
+ __all__ = ["PerplexityAgent", "PerplexityDriver"]
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ hyperforge_perplexity