File size: 14,223 Bytes
c922f8b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
"""
Perplexity API tool for the GAIA agent.
This module provides a tool for querying the Perplexity API with advanced models:
- sonar-reasoning: Balanced model for general reasoning and search tasks
- sonar-reasoning-pro: Enhanced version with stronger analytical capabilities
- sonar-deep-research: Specialized model for in-depth research and complex queries
The tool uses a fixed temperature of 0.1 for maximum accuracy and allows selecting
the appropriate model for different types of tasks.
The tool handles API responses and errors, and extracts content and citations from responses.
Models Reference:
- sonar-reasoning: Balanced accuracy and speed for general queries
- sonar-reasoning-pro: Higher accuracy, deeper analysis, suitable for complex reasoning tasks
- sonar-deep-research: Maximum depth and research capabilities, ideal for scholarly research
"""
import os
import json
import logging
import urllib.request
import urllib.error
from typing import Dict, Any, List, Optional, Union
logger = logging.getLogger("gaia_agent.tools.perplexity")
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class PerplexityTool:
"""Tool for querying the Perplexity API."""
# Valid models supported by the Perplexity API
VALID_MODELS = ["sonar", "sonar-reasoning", "sonar-reasoning-pro", "sonar-deep-research"]
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
Initialize the Perplexity API tool.
Args:
config: Optional configuration dictionary that may include:
- api_key: Perplexity API key (otherwise read from environment)
- model: Model to use (default: "sonar-reasoning")
- temperature: Temperature setting (default: 0.1)
- timeout: Timeout in seconds (default: 30)
"""
self.config = config or {}
self.api_key = self.config.get("api_key") or os.getenv("PERPLEXITY_API_KEY")
if not self.api_key:
logger.warning("Perplexity API key not found in config or environment variables")
self.api_url = "https://api.perplexity.ai"
# Get model from config, environment variable, or use default
model = self.config.get("model") or os.getenv("PERPLEXITY_MODEL", "sonar-reasoning")
if model not in self.VALID_MODELS:
logger.warning(f"Invalid model '{model}'. Using default 'sonar-reasoning' instead.")
model = "sonar-reasoning"
self.model = model
# Set temperature to 0.1 for maximum accuracy unless otherwise specified
self.temperature = self.config.get("temperature", 0.1)
self.timeout = self.config.get("timeout", 30)
self.endpoint = f"{self.api_url}/chat/completions"
logger.info(f"Initialized Perplexity tool with model: {self.model}")
def query(self,
question: str,
max_tokens: int = 500,
system_prompt: Optional[str] = None,
model: Optional[str] = None) -> Dict[str, Any]:
"""
Send a query to the Perplexity API.
Args:
question: The question to ask
max_tokens: Maximum number of tokens in the response
system_prompt: Optional system prompt
model: Override the model for this specific query
Returns:
Dictionary containing the query results
Raises:
Exception: If an error occurs during the query
"""
if not self.api_key:
raise Exception("Perplexity API key not available")
try:
# Determine which model to use (either the override or the default)
use_model = model if model else self.model
# Validate the model
if use_model not in self.VALID_MODELS:
logger.warning(f"Invalid model specified: '{use_model}'. Using '{self.model}' instead.")
use_model = self.model
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": question})
# Model parameter must be included in the request body
payload = {
"model": use_model,
"messages": messages,
"max_tokens": max_tokens,
"temperature": self.temperature
}
logger.info(f"Querying Perplexity API with model: {use_model}")
payload_bytes = json.dumps(payload).encode('utf-8')
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
req = urllib.request.Request(
self.endpoint,
data=payload_bytes,
headers=headers,
method="POST"
)
with urllib.request.urlopen(req, timeout=self.timeout) as response:
response_data = response.read().decode('utf-8')
api_response = json.loads(response_data)
content = self._extract_content(api_response)
citations = self._extract_citations(api_response)
usage = self._get_usage(api_response)
result = {
"query": question,
"content": content,
"citations": citations,
"usage": usage,
"model": use_model, # Use the actual model used for the query
"raw_response": api_response
}
return result
except urllib.error.HTTPError as e:
logger.error(f"HTTP Error {e.code} when querying Perplexity API: {e.reason}")
try:
error_data = e.read().decode('utf-8')
logger.error(f"Error details: {error_data}")
except:
logger.error("Could not read error details")
raise Exception(f"Perplexity API HTTP error: {e.code} {e.reason}")
except urllib.error.URLError as e:
logger.error(f"URL Error when querying Perplexity API: {e.reason}")
raise Exception(f"Perplexity API connection error: {e.reason}")
except Exception as e:
logger.error(f"Error querying Perplexity API: {str(e)}")
raise Exception(f"Perplexity API error: {str(e)}")
def search(self,
query: str,
max_tokens: int = 500,
model: Optional[str] = None,
depth: str = "standard") -> Dict[str, Any]:
"""
Search the web using the Perplexity API.
Args:
query: The search query
max_tokens: Maximum number of tokens in the response
model: Optional model override (uses instance model by default)
depth: Research depth - "standard", "deep", or "comprehensive"
Returns:
Dictionary containing the search results
Raises:
Exception: If an error occurs during the search
"""
# Select the appropriate model based on depth if not explicitly specified
if not model:
if depth == "deep":
model = "sonar-reasoning-pro"
elif depth == "comprehensive":
model = "sonar-deep-research"
# Adjust the system prompt based on the requested depth
if depth == "standard":
system_prompt = """You are a helpful web search assistant.
Search the web for accurate and up-to-date information to answer the user's query.
Provide a comprehensive answer with relevant facts and details.
Include citations to your sources."""
elif depth == "deep":
system_prompt = """You are an advanced web search and analysis assistant.
Perform a deep search for accurate, detailed, and up-to-date information to answer the user's query.
Analyze the information critically, identifying key patterns, insights, and connections.
Provide a thorough answer with comprehensive analysis.
Include citations to your sources and note the reliability of each source."""
elif depth == "comprehensive":
system_prompt = """You are an expert research assistant conducting comprehensive analysis.
Perform exhaustive research on the user's query, finding detailed information from diverse sources.
Critically analyze all information, evaluating source credibility and identifying consensus vs. disagreements.
Synthesize findings into a comprehensive research summary addressing all aspects of the query.
Include thorough citations to your sources, noting the authority and reliability of each source.
Identify any gaps in available information or areas where further research would be beneficial."""
else:
# Default to standard if depth is not recognized
system_prompt = """You are a helpful web search assistant.
Search the web for accurate and up-to-date information to answer the user's query.
Provide a comprehensive answer with relevant facts and details.
Include citations to your sources."""
logger.info(f"Performing {depth} search with model: {model or self.model}")
return self.query(query, max_tokens, system_prompt, model)
def _extract_content(self, response: Dict[str, Any]) -> Optional[str]:
"""
Extract content from a Perplexity API response.
Args:
response: The API response dictionary
Returns:
The extracted content, or None if not found
"""
if "choices" in response and len(response["choices"]) > 0:
if "message" in response["choices"][0]:
return response["choices"][0]["message"].get("content")
return None
def _extract_citations(self, response: Dict[str, Any]) -> List[str]:
"""
Extract citations from a Perplexity API response.
Args:
response: The API response dictionary
Returns:
List of citation URLs
"""
return response.get("citations", [])
def _get_usage(self, response: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract usage information from a Perplexity API response.
Args:
response: The API response dictionary
Returns:
Dictionary containing usage information
"""
return response.get("usage", {})
def create_perplexity_tool(model: str = "sonar-reasoning") -> PerplexityTool:
"""
Create a Perplexity tool instance with the specified model.
Args:
model: The Perplexity model to use. Options:
- "sonar-reasoning": Balanced model for general reasoning and search (default)
- "sonar-reasoning-pro": Enhanced model with stronger analytical capabilities
- "sonar-deep-research": Specialized model for in-depth research and complex queries
Returns:
A configured PerplexityTool instance
"""
config = {"model": model}
return PerplexityTool(config)
def create_perplexity_tool_pro() -> PerplexityTool:
"""
Create a Perplexity tool instance using the sonar-reasoning-pro model.
This model provides enhanced analysis capabilities for complex reasoning tasks.
Returns:
A PerplexityTool instance configured with the sonar-reasoning-pro model
"""
return create_perplexity_tool("sonar-reasoning-pro")
def create_perplexity_tool_research() -> PerplexityTool:
"""
Create a Perplexity tool instance using the sonar-deep-research model.
This model provides maximum depth and research capabilities, ideal for
scholarly research and complex investigative queries.
Returns:
A PerplexityTool instance configured with the sonar-deep-research model
"""
return create_perplexity_tool("sonar-deep-research")
# Additional specialized search methods
def deep_search(query: str, max_tokens: int = 750) -> Dict[str, Any]:
"""
Perform a deep search using the sonar-reasoning-pro model.
This is a convenience function for performing a search with enhanced depth
and analysis. It uses the sonar-reasoning-pro model which offers improved
analytical capabilities compared to the standard model.
Args:
query: The search query
max_tokens: Maximum number of tokens in the response (default: 750)
Returns:
Dictionary containing the search results with enhanced analysis
"""
tool = create_perplexity_tool_pro()
return tool.search(query, max_tokens, depth="deep")
def comprehensive_research(query: str, max_tokens: int = 1000) -> Dict[str, Any]:
"""
Perform comprehensive research using the sonar-deep-research model.
This is a convenience function for performing exhaustive research on complex topics.
It uses the sonar-deep-research model which provides the maximum depth and
research capabilities available.
Args:
query: The research query
max_tokens: Maximum number of tokens in the response (default: 1000)
Returns:
Dictionary containing thorough research results with comprehensive analysis
"""
tool = create_perplexity_tool_research()
return tool.search(query, max_tokens, depth="comprehensive") |