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 ("\n Perplexity 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 ("\n Enter 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