From perplexity-pack
Migrates from Google Custom Search, Bing API, SerpAPI, or legacy LLMs to Perplexity Sonar using strangler fig pattern. Assesses integrations, builds adapters for incremental replacement.
How this skill is triggered — by the user, by Claude, or both
Slash command
/perplexity-pack:perplexity-migration-deep-diveThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
!`npm list openai 2>/dev/null | grep openai || echo 'N/A'`
!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'
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.
| 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 citations array |
| Cost structure | Per-search ($5/1K queries) | Per-token + per-request |
| Recency filter | Date range parameters | search_recency_filter |
| Domain filter | Site restriction | search_domain_filter |
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
// 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 || [],
};
}
}
// 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
// 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),
};
}
// 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 || [],
};
}
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
| Issue | Cause | Solution |
|---|---|---|
| Citation format differs | Google returns titles, Perplexity returns URLs | Normalize in adapter |
| No raw results | Perplexity returns synthesized answer | Use search_results 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 |
For advanced troubleshooting, see perplexity-advanced-troubleshooting.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin perplexity-packMigrates Perplexity AI from deprecated pplx-api/legacy Sonar models to sonar/sonar-pro. Scans JS/TS/Python code for old models/URLs and applies migration maps.
Migrates Node.js search pipelines from Google, Bing, Tavily, Serper to Exa neural search. Installs exa-js SDK, compares APIs, and provides TypeScript adapter for unified interface.
Guides migrating search from Elasticsearch, Typesense, or Meilisearch to Algolia with data sync, TypeScript adapters, replaceAllObjects swaps, and strangler fig traffic shifting.