| """ |
| Perplexity API Client — Thin async wrapper for deep financial research queries. |
| Uses the Sonar model for fast, web-grounded responses. |
| """ |
| import logging |
| import os |
|
|
| import httpx |
|
|
| logger = logging.getLogger(__name__) |
|
|
| PERPLEXITY_BASE_URL = "https://api.perplexity.ai" |
| DEFAULT_MODEL = "sonar" |
|
|
| RESEARCH_SYSTEM_PROMPT = """You are a financial research analyst. Provide concise, data-backed answers to investment and market questions. Focus on: |
| - Current market data and trends |
| - Specific numbers, dates, and percentages |
| - Impact on the user's portfolio holdings |
| - Actionable insights |
| |
| Keep responses under 200 words. Use bullet points for clarity. Include source attribution where possible.""" |
|
|
|
|
| async def research(query: str, context: str = "") -> str: |
| """ |
| Perform deep financial research via Perplexity API. |
| Returns a structured research summary. |
| |
| Args: |
| query: The research question |
| context: Optional context about user's portfolio |
| """ |
| api_key = os.getenv("PERPLEXITY_API_KEY", "").strip() |
| if not api_key: |
| return "Perplexity API key not configured. Unable to perform deep research." |
|
|
| messages = [ |
| {"role": "system", "content": RESEARCH_SYSTEM_PROMPT}, |
| ] |
| if context: |
| messages.append({"role": "user", "content": f"Context about my portfolio: {context}"}) |
| messages.append({"role": "assistant", "content": "Understood. I'll factor in your portfolio context."}) |
|
|
| messages.append({"role": "user", "content": query}) |
|
|
| try: |
| async with httpx.AsyncClient(timeout=30) as client: |
| resp = await client.post( |
| f"{PERPLEXITY_BASE_URL}/chat/completions", |
| json={ |
| "model": DEFAULT_MODEL, |
| "messages": messages, |
| "temperature": 0.1, |
| "max_tokens": 1000, |
| }, |
| headers={ |
| "Authorization": f"Bearer {api_key}", |
| "Content-Type": "application/json", |
| }, |
| ) |
| resp.raise_for_status() |
| data = resp.json() |
| content = data["choices"][0]["message"]["content"] |
| logger.info(f"Perplexity research completed for: {query[:60]}...") |
| return content |
|
|
| except httpx.HTTPStatusError as e: |
| logger.error(f"Perplexity API error: {e.response.status_code} — {e.response.text[:200]}") |
| return f"Research API error (status {e.response.status_code}). Please try again." |
| except Exception as e: |
| logger.error(f"Perplexity client error: {e}") |
| return f"Unable to perform research: {str(e)}" |
|
|