Claude-code-plugins-plus perplexity-migration-deep-dive
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/perplexity-pack/skills/perplexity-migration-deep-dive" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-perplexity-migration-deep-dive && rm -rf "$T"
manifest:
plugins/saas-packs/perplexity-pack/skills/perplexity-migration-deep-dive/SKILL.mdsource content
Perplexity Migration Deep Dive
Current State
!
npm list openai 2>/dev/null | grep openai || echo 'N/A'
!grep -rn "google.*search\|bing.*api\|serpapi\|pplx-7b\|pplx-70b" --include="*.ts" --include="*.py" . 2>/dev/null | head -5 || echo 'No legacy search APIs found'
Overview
Migrate from traditional search APIs (Google Custom Search, Bing, SerpAPI) or legacy LLMs to Perplexity Sonar. Key advantage: Perplexity combines search + LLM summarization in a single API call, replacing a multi-step pipeline.
Migration Comparison
| Feature | Google CSE / Bing | Perplexity Sonar |
|---|---|---|
| Returns | Raw search results (links + snippets) | Synthesized answer + citations |
| Answer generation | Requires separate LLM call | Built-in |
| Citation handling | Manual extraction | Automatic array |
| Cost structure | Per-search ($5/1K queries) | Per-token + per-request |
| Recency filter | Date range parameters | |
| Domain filter | Site restriction | |
Instructions
Step 1: Assess Current Integration
set -euo pipefail # Find existing search API usage grep -rn "googleapis.*customsearch\|bing.*search\|serpapi\|serper\|tavily" \ --include="*.ts" --include="*.py" --include="*.js" \ . 2>/dev/null || echo "No search APIs found" # Count integration points grep -rln "search.*api\|customsearch\|bing.*web" \ --include="*.ts" --include="*.py" --include="*.js" \ . 2>/dev/null | wc -l
Step 2: Build Adapter Layer
// src/search/adapter.ts export interface SearchResult { answer: string; citations: string[]; rawResults?: Array<{ title: string; url: string; snippet: string }>; } export interface SearchAdapter { search(query: string, opts?: { recency?: string; domains?: string[] }): Promise<SearchResult>; } // Legacy adapter (existing Google/Bing implementation) class GoogleSearchAdapter implements SearchAdapter { async search(query: string): Promise<SearchResult> { // Existing Google CSE code const results = await googleCustomSearch(query); return { answer: "", // No built-in answer generation citations: results.items.map((i: any) => i.link), rawResults: results.items.map((i: any) => ({ title: i.title, url: i.link, snippet: i.snippet, })), }; } } // New Perplexity adapter class PerplexitySearchAdapter implements SearchAdapter { private client: OpenAI; constructor() { this.client = new OpenAI({ apiKey: process.env.PERPLEXITY_API_KEY!, baseURL: "https://api.perplexity.ai", }); } async search(query: string, opts?: { recency?: string; domains?: string[] }): Promise<SearchResult> { const response = await this.client.chat.completions.create({ model: "sonar", messages: [{ role: "user", content: query }], ...(opts?.recency && { search_recency_filter: opts.recency }), ...(opts?.domains && { search_domain_filter: opts.domains }), } as any); return { answer: response.choices[0].message.content || "", citations: (response as any).citations || [], rawResults: (response as any).search_results || [], }; } }
Step 3: Feature Flag Traffic Split
// src/search/factory.ts function getSearchAdapter(): SearchAdapter { const perplexityPercent = parseInt(process.env.PERPLEXITY_TRAFFIC_PERCENT || "0"); if (Math.random() * 100 < perplexityPercent) { return new PerplexitySearchAdapter(); } return new GoogleSearchAdapter(); } // Migration schedule: // Week 1: PERPLEXITY_TRAFFIC_PERCENT=10 (canary) // Week 2: PERPLEXITY_TRAFFIC_PERCENT=50 (half traffic) // Week 3: PERPLEXITY_TRAFFIC_PERCENT=100 (full migration) // Week 4: Remove Google adapter code
Step 4: Validate Migration Quality
// Compare results between old and new adapter async function compareSearchResults(query: string): Promise<{ perplexity: SearchResult; google: SearchResult; citationOverlap: number; }> { const [perplexity, google] = await Promise.all([ new PerplexitySearchAdapter().search(query), new GoogleSearchAdapter().search(query), ]); // Check citation overlap (shared domains) const pplxDomains = new Set(perplexity.citations.map((u) => new URL(u).hostname)); const googleDomains = new Set(google.citations.map((u) => new URL(u).hostname)); const overlap = [...pplxDomains].filter((d) => googleDomains.has(d)).length; return { perplexity, google, citationOverlap: overlap / Math.max(pplxDomains.size, 1), }; }
Step 5: Simplify Post-Migration
// Before migration: 3-step pipeline // 1. Google Custom Search API → raw results // 2. Send results to LLM for summarization // 3. Extract citations manually // After migration: 1-step async function search(query: string): Promise<{ answer: string; sources: string[] }> { const client = new OpenAI({ apiKey: process.env.PERPLEXITY_API_KEY!, baseURL: "https://api.perplexity.ai", }); const response = await client.chat.completions.create({ model: "sonar", messages: [{ role: "user", content: query }], }); return { answer: response.choices[0].message.content || "", sources: (response as any).citations || [], }; }
Rollback Plan
set -euo pipefail # Instant rollback: set traffic to 0% # kubectl set env deployment/search-app PERPLEXITY_TRAFFIC_PERCENT=0 # The adapter layer keeps both implementations live until decommissioned
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Citation format differs | Google returns titles, Perplexity returns URLs | Normalize in adapter |
| No raw results | Perplexity returns synthesized answer | Use field if available |
| Higher latency | Perplexity does search + synthesis | Expected; cache to compensate |
| Cost increase | Perplexity uses more tokens | Route simple queries to sonar, limit max_tokens |
Output
- Adapter layer abstracting search implementations
- Feature-flagged traffic split for gradual migration
- Quality comparison between old and new search
- Simplified single-API architecture post-migration
Resources
Next Steps
For advanced troubleshooting, see
perplexity-advanced-troubleshooting.