import type { ComparisonRequest, ComparisonResult } from "../types"; import type { SearchResult } from "./tavily"; import { generateComparisonWithResearch } from "./openai"; const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions"; const SYSTEM_PROMPT = `You are a research synthesis engine. Given web search results for multiple items, produce a structured JSON comparison. You MUST respond with valid JSON matching this exact structure: { "items": [ { "name": "item name", "description": "brief overview", "overallScore": 7.5, "dimensions": { "Dimension Name": { "score": 8, "summary": "brief assessment", "details": "detailed analysis", "pros": ["pro 1"], "cons": ["con 1"] } }, "pros": ["overall pro 1"], "cons": ["overall con 1"], "sources": [{ "title": "source", "url": "https://...", "snippet": "excerpt" }] } ], "dimensions": ["Dimension 1", "Dimension 2"], "summary": "comparison summary", "recommendation": "clear recommendation" }`; export async function synthesizeResearch( request: ComparisonRequest, searchResults: Record ): Promise { const apiKey = process.env.PERPLEXITY_API_KEY; if (!apiKey) { return generateComparisonWithResearch(request, searchResults); } const allResults = Object.values(searchResults).flat(); if (allResults.length === 0) { return generateComparisonWithResearch(request, searchResults); } let researchContext = "Search results for each item:\n\n"; for (const [itemName, results] of Object.entries(searchResults)) { if (results.length === 0) continue; researchContext += `=== ${itemName} ===\n`; for (const r of results) { researchContext += `- ${r.title}: ${r.content}\n Source: ${r.url}\n`; } researchContext += "\n"; } const userPrompt = `Compare: ${request.items.join(", ")} ${request.query ? `Focus: ${request.query}` : ""} ${request.dimensions?.length ? `Dimensions: ${request.dimensions.join(", ")}` : ""} ${researchContext}`; try { const response = await fetch(PERPLEXITY_API_URL, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "sonar", messages: [ { role: "system", content: SYSTEM_PROMPT }, { role: "user", content: userPrompt }, ], temperature: 0.3, }), }); if (!response.ok) { console.error( `Perplexity API error: ${response.status} ${response.statusText}` ); return generateComparisonWithResearch(request, searchResults); } const data = await response.json(); const content = data.choices?.[0]?.message?.content; if (!content) { console.error("Empty response from Perplexity"); return generateComparisonWithResearch(request, searchResults); } const parsed: unknown = JSON.parse(content); if ( !parsed || typeof parsed !== "object" || !Array.isArray((parsed as Record).items) || !Array.isArray((parsed as Record).dimensions) ) { console.error("Invalid structure from Perplexity, falling back to OpenAI"); return generateComparisonWithResearch(request, searchResults); } return parsed as ComparisonResult; } catch (error) { console.error( "Perplexity synthesis failed, falling back to OpenAI:", error instanceof Error ? error.message : error ); return generateComparisonWithResearch(request, searchResults); } }