Skip to content

Commit b1438a0

Browse files
authored
Merge pull request #118 from blorm-network:perplexity
perplexity connector
2 parents 52d86b0 + fa3afa5 commit b1438a0

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

src/connection_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from src.connections.ethereum_connection import EthereumConnection
2121
from src.connections.together_connection import TogetherAIConnection
2222
from src.connections.evm_connection import EVMConnection
23+
from src.connections.perplexity_connection import PerplexityConnection
2324

2425
logger = logging.getLogger("connection_manager")
2526

@@ -70,6 +71,8 @@ def _class_name_to_type(class_name: str) -> Type[BaseConnection]:
7071
return TogetherAIConnection
7172
elif class_name == "evm":
7273
return EVMConnection
74+
elif class_name == "perplexity":
75+
return PerplexityConnection
7376
return None
7477

7578
def _register_connection(self, config_dic: Dict[str, Any]) -> None:
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import logging
2+
import os
3+
from typing import Dict, Any
4+
from dotenv import load_dotenv, set_key
5+
from openai import OpenAI
6+
from src.connections.base_connection import BaseConnection, Action, ActionParameter
7+
8+
logger = logging.getLogger("connections.perplexity_connection")
9+
10+
11+
class PerplexityConnectionError(Exception):
12+
"""Base exception for Perplexity connection errors"""
13+
pass
14+
15+
16+
class PerplexityAPIError(PerplexityConnectionError):
17+
"""Raised when Perplexity API returns an error"""
18+
pass
19+
20+
21+
class PerplexityConfigurationError(PerplexityConnectionError):
22+
"""Raised when there's an issue with Perplexity configuration"""
23+
pass
24+
25+
26+
class PerplexityConnection(BaseConnection):
27+
def __init__(self, config: Dict[str, Any]):
28+
super().__init__(config)
29+
self._client = None
30+
self.base_url = "https://api.perplexity.ai"
31+
32+
@property
33+
def is_llm_provider(self) -> bool:
34+
return False # This is a search provider, not an LLM
35+
36+
def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
37+
"""Validate Perplexity configuration from JSON"""
38+
required_fields = ["model"]
39+
missing_fields = [field for field in required_fields if field not in config]
40+
41+
if missing_fields:
42+
raise ValueError(f"Missing required configuration fields: {', '.join(missing_fields)}")
43+
44+
if not isinstance(config["model"], str):
45+
raise ValueError("model must be a string")
46+
47+
return config
48+
49+
def _get_client(self) -> OpenAI:
50+
"""Get or create Perplexity client"""
51+
if not self._client:
52+
api_key = os.getenv("PERPLEXITY_API_KEY")
53+
if not api_key:
54+
raise PerplexityConfigurationError("Perplexity API key not found in environment")
55+
self._client = OpenAI(
56+
api_key=api_key,
57+
base_url=self.base_url
58+
)
59+
return self._client
60+
61+
def register_actions(self) -> None:
62+
"""Register available Perplexity actions"""
63+
self.actions = {
64+
"search": Action(
65+
name="search",
66+
parameters=[
67+
ActionParameter("query", True, str, "The search query to process"),
68+
ActionParameter("model", False, str, "Model to use for search (defaults to sonar-reasoning-pro)")
69+
],
70+
description="Perform a search query using Perplexity's Sonar API"
71+
)
72+
}
73+
74+
def configure(self) -> bool:
75+
"""Setup Perplexity API configuration"""
76+
logger.info("\n🔍 PERPLEXITY API SETUP")
77+
78+
if self.is_configured():
79+
logger.info("\nPerplexity API is already configured.")
80+
response = input("Do you want to reconfigure? (y/n): ")
81+
if response.lower() != 'y':
82+
return True
83+
84+
logger.info("\n📝 To get your Perplexity API credentials:")
85+
logger.info("1. Go to https://www.perplexity.ai/settings")
86+
logger.info("2. Generate a new API key")
87+
88+
api_key = input("\nEnter your Perplexity API key: ")
89+
90+
try:
91+
if not os.path.exists('.env'):
92+
with open('.env', 'w') as f:
93+
f.write('')
94+
95+
set_key('.env', 'PERPLEXITY_API_KEY', api_key)
96+
97+
# Test the configuration
98+
client = self._get_client()
99+
self.search("test") # Simple test query
100+
101+
logger.info("\n✅ Perplexity API configuration successfully saved!")
102+
return True
103+
104+
except Exception as e:
105+
logger.error(f"Configuration failed: {e}")
106+
return False
107+
108+
def is_configured(self, verbose = False) -> bool:
109+
"""Check if Perplexity API key is configured and valid"""
110+
try:
111+
load_dotenv()
112+
api_key = os.getenv('PERPLEXITY_API_KEY')
113+
if not api_key:
114+
return False
115+
116+
client = self._get_client()
117+
self.search("test") # Quick test query
118+
return True
119+
120+
except Exception as e:
121+
if verbose:
122+
logger.debug(f"Configuration check failed: {e}")
123+
return False
124+
125+
def search(self, query: str, model: str = None, **kwargs) -> str:
126+
"""Perform a search query using Perplexity"""
127+
try:
128+
client = self._get_client()
129+
130+
# Use configured model if none provided
131+
if not model:
132+
model = self.config.get("model", "sonar-reasoning-pro")
133+
134+
messages = [
135+
{
136+
"role": "system",
137+
"content": "You are a search assistant. Please provide detailed and accurate information based on the search query."
138+
},
139+
{
140+
"role": "user",
141+
"content": query
142+
}
143+
]
144+
145+
completion = client.chat.completions.create(
146+
model=model,
147+
messages=messages
148+
)
149+
150+
return completion.choices[0].message.content
151+
152+
except Exception as e:
153+
raise PerplexityAPIError(f"Search failed: {e}")
154+
155+
def perform_action(self, action_name: str, kwargs) -> Any:
156+
"""Execute a Perplexity action with validation"""
157+
if action_name not in self.actions:
158+
raise KeyError(f"Unknown action: {action_name}")
159+
160+
action = self.actions[action_name]
161+
errors = action.validate_params(kwargs)
162+
if errors:
163+
raise ValueError(f"Invalid parameters: {', '.join(errors)}")
164+
165+
# Call the appropriate method based on action name
166+
method_name = action_name.replace('-', '_')
167+
method = getattr(self, method_name)
168+
return method(**kwargs)

0 commit comments

Comments
 (0)