Build an AI Research Agent with Perplexity — 2026 Guide
Build an AI Research Agent with Perplexity — 2026 Guide
Why This Matters
Perplexity AI has become the gold standard for citation-grounded AI search. Its Deep Research mode, released in early 2026, can analyze hundreds of sources per query. By building your own research agent on top of the Perplexity API, you automate what would take hours: competitive analysis, market research, literature reviews, and daily intelligence briefs.
This tutorial builds a production-ready research agent using Perplexity API v2, Python, and a modular pipeline architecture.
Prerequisites
- Perplexity Pro subscription ($20/mo) or Enterprise API access
- Python 3.11+ installed
- API key from perplexity.ai/settings/api
- Any LLM API key (optional, for synthesis) — OpenAI or Anthropic
- Basic Python knowledge — we provide the full code
Verify your environment:
python3 --version
# Expected: Python 3.11.0 or higher
pip3 --version
# Expected: pip 23.0 or higher
Step-by-Step
Step 1: Set Up Your Project
Create the project structure:
mkdir perplexity-research-agent
cd perplexity-research-agent
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install httpx pydantic rich python-dotenv
Create a .env file:
PERPLEXITY_API_KEY=pplx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # optional for synthesis
Our agent uses httpx for async HTTP, pydantic for typed data models, rich for formatted console output, and python-dotenv for environment variable loading.
Step 2: Build the Core Perplexity Client
The Perplexity API (v2) provides a chat/completions endpoint with search capabilities:
# client.py
import os
import json
from typing import Optional
import httpx
from pydantic import BaseModel
from dotenv import load_dotenv
load_dotenv()
class PerplexityConfig(BaseModel):
api_key: str = os.getenv("PERPLEXITY_API_KEY", "")
model: str = "sonar-deep-research" # 2026 best model for deep research
max_tokens: int = 8192
class ResearchQuery(BaseModel):
topic: str
depth: str = "standard" # standard, deep, comprehensive
max_sources: int = 20
focus: str = "balanced" # balanced, recent, authoritative
class PerplexityClient:
def __init__(self, config: Optional[PerplexityConfig] = None):
self.config = config or PerplexityConfig()
self.base_url = "https://api.perplexity.ai"
self.headers = {
"Authorization": f"Bearer {self.config.api_key}",
"Content-Type": "application/json",
}
async def research(self, query: ResearchQuery) -> dict:
"""Run a research query through Perplexity's deep research."""
system_prompt = self._build_system_prompt(query)
payload = {
"model": self.config.model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Research: {query.topic}"},
],
"max_tokens": self.config.max_tokens,
"temperature": 0.3, # Lower for factual accuracy
"search_recency": "month" if query.focus == "recent" else "all",
"search_domain_filter": None,
}
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(
f"{self.base_url}/chat/completions",
headers=self.headers,
json=payload,
)
response.raise_for_status()
data = response.json()
return {
"content": data["choices"][0]["message"]["content"],
"citations": data.get("citations", []),
"model": data["model"],
"usage": data.get("usage", {}),
}
def _build_system_prompt(self, query: ResearchQuery) -> str:
depth_instructions = {
"standard": "Provide a concise overview with 3-5 key points.",
"deep": "Analyze thoroughly. Include statistics, competing viewpoints, and future projections.",
"comprehensive": "Write an exhaustive analysis covering all dimensions: technical, market, social, and regulatory.",
}
return f"""You are a professional research analyst. Research the given topic thoroughly.
{depth_instructions.get(query.depth, depth_instructions['standard'])}
Requirements:
- Base every claim on cited sources
- Include specific data points and statistics
- Highlight conflicting viewpoints if they exist
- Focus on 2025-2026 information
- Aim for {query.max_sources} sources minimum
- Format citations as [1], [2], etc."""
Step 3: Build the Report Generator
Transform raw research into structured reports:
# reporter.py
import json
from pathlib import Path
from datetime import datetime
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
console = Console()
class ResearchReport:
def __init__(self, topic: str, raw_data: dict):
self.topic = topic
self.content = raw_data["content"]
self.citations = raw_data.get("citations", [])
self.timestamp = datetime.now().isoformat()
def to_markdown(self) -> str:
"""Convert research to a formatted markdown report."""
lines = [
f"# Research Report: {self.topic}",
f"**Generated**: {self.timestamp}",
f"**Sources**: {len(self.citations)}",
"",
"---",
"",
self.content,
"",
"---",
"",
"## Sources",
]
for i, citation in enumerate(self.citations, 1):
if isinstance(citation, str):
lines.append(f"{i}. {citation}")
elif isinstance(citation, dict):
url = citation.get("url", citation.get("link", "Unknown"))
title = citation.get("title", "Untitled")
lines.append(f"{i}. [{title}]({url})")
return "\n".join(lines)
def save(self, output_dir: str = "reports") -> Path:
"""Save report to disk."""
path = Path(output_dir)
path.mkdir(exist_ok=True)
slug = self.topic.lower().replace(" ", "-")[:40]
filename = path / f"{slug}-{datetime.now().strftime('%Y%m%d')}.md"
filename.write_text(self.to_markdown(), encoding="utf-8")
console.print(Panel(f"Report saved: {filename}", title="✅ Complete"))
return filename
Step 4: Build the Agent Orchestrator
Create the main agent that handles multi-topic research:
# agent.py
import asyncio
from typing import List
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from client import PerplexityClient, ResearchQuery
from reporter import ResearchReport
console = Console()
class ResearchAgent:
def __init__(self):
self.client = PerplexityClient()
async def research_single(self, topic: str, depth: str = "deep") -> ResearchReport:
"""Research a single topic and return a report."""
query = ResearchQuery(topic=topic, depth=depth)
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
) as progress:
task = progress.add_task(f"[cyan]Researching: {topic[:40]}...", total=None)
result = await self.client.research(query)
progress.update(task, completed=True)
return ResearchReport(topic, result)
async def research_batch(self, topics: List[str], depth: str = "standard"):
"""Research multiple topics concurrently."""
tasks = [self.research_single(topic, depth) for topic in topics]
results = await asyncio.gather(*tasks)
console.print(f"\n[bold green]✓ {len(results)} reports generated[/]")
for report in results:
report.save()
return results
async def research_with_synthesis(self, main_topic: str, subtopics: List[str]):
"""Research a main topic plus subtopics, then synthesize."""
all_topics = [main_topic] + subtopics
reports = await self.research_batch(all_topics, "deep")
# Use Perplexity to synthesize across all reports
synthesis_query = f"Synthesize the following research into a unified analysis of '{main_topic}':\n\n"
for r in reports:
synthesis_query += f"\n--- {r.topic} ---\n{r.content[:2000]}\n"
query = ResearchQuery(topic=synthesis_query, depth="comprehensive")
synthesis = await self.client.research(query)
syn_report = ResearchReport(f"{main_topic} - Synthesis", synthesis)
syn_report.save()
return reports, syn_report
async def main():
agent = ResearchAgent()
console.print("[bold]🔬 Perplexity Research Agent[/]")
console.print("=" * 50)
# Single topic
report = await agent.research_single("Latest developments in AI agents 2026", "deep")
report.save()
# Multi-topic batch
topics = [
"OpenAI o3 model capabilities 2026",
"Anthropic Claude 4 pricing changes",
"Google Gemini 2 Ultra benchmarks",
]
await agent.research_batch(topics)
# Synthesis workflow
subtopics = ["Market size", "Key players", "Technology trends"]
await agent.research_with_synthesis("AI coding assistants 2026", subtopics)
if __name__ == "__main__":
asyncio.run(main())
Step 5: Add Scheduled Research (Daily Briefing)
Set up a cron-based daily briefing agent:
# daily_brief.py
import asyncio
import json
from pathlib import Path
from agent import ResearchAgent
DAILY_TOPICS = {
"AI Industry": "Major AI announcements and product launches today",
"Open Source": "New open source AI model releases",
"Funding": "AI startup funding rounds and acquisitions",
"Regulation": "AI regulatory developments globally",
}
async def daily_briefing():
agent = ResearchAgent()
topics = [f"{k}: {v}" for k, v in DAILY_TOPICS.items()]
console.print("[bold]📰 Daily AI Briefing[/]")
reports = await agent.research_batch(topics, "standard")
# Generate a single digest
digest = ["# Daily AI Briefing\n"]
for report in reports:
digest.append(f"## {report.topic}\n")
digest.append(report.content[:500])
digest.append("")
digest_path = Path("briefings")
digest_path.mkdir(exist_ok=True)
(digest_path / f"briefing-{__import__('datetime').datetime.now().strftime('%Y%m%d')}.md").write_text("\n".join(digest))
if __name__ == "__main__":
asyncio.run(daily_briefing())
Schedule it with cron:
# Run daily briefing at 8 AM
crontab -e
0 8 * * * cd /path/to/perplexity-research-agent && source .venv/bin/activate && python daily_brief.py
Step 6: Deploy as an API Service
Make your agent accessible via web API:
pip install fastapi uvicorn
# api.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from agent import ResearchAgent
app = FastAPI(title="Perplexity Research Agent API")
agent = ResearchAgent()
class ResearchRequest(BaseModel):
topic: str
depth: str = "deep"
class ResearchResponse(BaseModel):
topic: str
content: str
sources: int
@app.post("/research", response_model=ResearchResponse)
async def research_endpoint(request: ResearchRequest):
try:
report = await agent.research_single(request.topic, request.depth)
return ResearchResponse(
topic=report.topic,
content=report.content[:5000],
sources=len(report.citations),
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
return {"status": "ok", "agent": "perplexity-research-agent"}
# Run with: uvicorn api:app --reload --port 8000
Tips & Best Practices
- Temperature tuning: Keep temperature at 0.2-0.3 for factual research, 0.7 for creative analysis
- Citation verification: Always verify citations from the response — Perplexity returns URLs you can check
- Rate limits: Perplexity Pro allows ~100 requests/day. Enterprise plans offer higher limits
- Cost optimization: Use
sonar-smallfor quick lookups,sonar-deep-researchonly for in-depth reports - Error recovery: Wrap API calls in try/except with exponential backoff for 429 rate limit errors
Community Reviews & Ratings
Perplexity AI has earned strong reviews across platforms in 2026:
G2: 4.7/5 from 2,800+ reviews. Users praise the accuracy of citations and depth of research. “Perplexity Deep Research saved me 5+ hours per week on competitive analysis,” reports a senior analyst at a Fortune 500 company.
Product Hunt: Perplexity Pro was Product Hunt’s #1 AI Product of 2024 and remains a top-10 AI tool in 2026. Deep Research mode launched with a 4.9/5 rating from 1,200+ upvotes.
Capterra: 4.6/5 from 890 reviews. Enterprise users highlight the API reliability and citation accuracy. “The ability to get grounded, cited research in seconds is game-changing,” writes a Director of Research at a consulting firm.
Reddit r/PerplexityAI: Active community of 85K+ members sharing search techniques, API tips, and comparison benchmarks. The consensus: Deep Research is best-in-class for academic and market research.
“Perplexity isn’t just a search engine — it’s a research partner that cites its sources.” — PCMag Editors’ Choice 2025
Common Mistakes
- Ignoring citations — The biggest value of Perplexity is citation transparency. Always extract and display them.
- Overloading queries — One broad query is less effective than 3-5 specific sub-queries that you aggregate.
- No freshness filter — Default search shows results from all time. For tech topics, always set
search_recencyto “month” or “week”. - Missing error handling — API calls fail for various reasons. Always implement retry logic.
- Not validating input topics — Vague queries produce vague results. Pre-process topics to make them specific and scoped.
FAQ
Q: How is Perplexity different from Google Search API? Perplexity returns LLM-generated analysis with inline citations, not raw search results. It’s better for understanding and synthesis, worse for raw URL discovery.
Q: What’s the cost per research query?
With sonar-deep-research at Pro tier (~$20/mo), each query costs about 2-5 cents in API credits. Enterprise plans have usage-based pricing at ~$0.003 per 1K tokens.
Q: Can I limit results to specific domains?
Yes. The API supports search_domain_filter to restrict results to, e.g., ["arxiv.org", "nature.com"] for scientific research.
Q: How many sources does deep research use? Perplexity Deep Research mode analyzes 50-300+ sources per query, depending on topic breadth and depth setting.
Q: Can the agent run locally without internet? No — the agent requires Perplexity API access. However, you can cache results locally and re-synthesize offline.
Q: What Python version works best?
Python 3.11+ with asyncio is recommended for the async concurrency pattern shown above.