perplexity-data-handling
Guides on sanitizing user queries and managing data retention for Perplexity AI integrations.
Install
mkdir -p .claude/skills/perplexity-data-handling && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/6012" && unzip -o skill.zip -d .claude/skills/perplexity-data-handling && rm skill.zipInstalls to .claude/skills/perplexity-data-handling
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Implement Perplexity query sanitization, citation validation, resultKey capabilities
- →Sanitize user queries to redact PII before sending to Perplexity API
- →Validate and deduplicate citation URLs from Perplexity responses
- →Implement result caching with freshness policies based on query type
- →Manage conversation context with automatic trimming for token limits
- →Detect and warn about PII redaction in queries
- →Render answers with linked and validated citations
How it works
This skill processes data flowing through the Perplexity Sonar API by sanitizing queries for PII, validating citations, caching results, and managing conversation context.
Inputs & outputs
When to use perplexity-data-handling
- →Sanitizing user queries
- →Redacting PII from search prompts
- →Validating citation URLs
About this skill
Perplexity Data Handling
Overview
Manage data flowing through Perplexity Sonar API. Critical concern: queries are sent to Perplexity for web search, so any PII in queries is exposed to external infrastructure. Responses contain citations (third-party URLs) that must be validated before displaying to users.
Data Flow
User Input → Query Sanitization → Perplexity API → Response Parsing
│
┌─────────────┼──────────────┐
│ │ │
Answer Text Citations Search Results
│ │ │
Format & Validate & Store for
Display Deduplicate Analytics
Prerequisites
- Perplexity API key configured
- Understanding of PII regulations (GDPR/CCPA)
- Cache storage (Redis or in-memory)
Instructions
Step 1: Query Sanitization
function sanitizeQuery(query: string): { clean: string; redacted: boolean } {
let clean = query;
let redacted = false;
const patterns: Array<[RegExp, string]> = [
[/\b[\w.+-]+@[\w-]+\.[\w.]+\b/g, "[email]"],
[/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, "[phone]"],
[/\b\d{3}-\d{2}-\d{4}\b/g, "[ssn]"],
[/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, "[card]"],
[/\b(pplx-|sk-|pk_|sk_live_)\w{20,}\b/g, "[token]"],
[/\b(user|customer|account)\s*#?\s*\d+\b/gi, "[id]"],
];
for (const [pattern, replacement] of patterns) {
if (pattern.test(clean)) {
clean = clean.replace(pattern, replacement);
redacted = true;
}
}
return { clean, redacted };
}
async function safeSearch(rawQuery: string) {
const { clean, redacted } = sanitizeQuery(rawQuery);
if (redacted) {
console.warn("[Data] PII redacted from Perplexity query");
}
return perplexity.chat.completions.create({
model: "sonar",
messages: [{ role: "user", content: clean }],
});
}
Step 2: Citation Validation
interface ValidatedCitation {
url: string;
domain: string;
valid: boolean;
index: number;
}
function validateCitations(citations: string[]): ValidatedCitation[] {
return citations.map((url, i) => {
try {
const parsed = new URL(url);
return {
url: url.replace(/[.,;:]+$/, ""),
domain: parsed.hostname,
valid: ["http:", "https:"].includes(parsed.protocol),
index: i + 1,
};
} catch {
return { url, domain: "unknown", valid: false, index: i + 1 };
}
});
}
function deduplicateCitations(citations: ValidatedCitation[]): ValidatedCitation[] {
const seen = new Set<string>();
return citations.filter((c) => {
const normalized = c.url.split("?")[0].replace(/\/$/, "");
if (seen.has(normalized)) return false;
seen.add(normalized);
return true;
});
}
// Replace [1] markers with linked citations
function renderCitations(answer: string, citations: ValidatedCitation[]): string {
let rendered = answer;
for (const c of citations.filter((c) => c.valid)) {
rendered = rendered.replaceAll(`[${c.index}]`, `${c.index}`);
}
return rendered;
}
Step 3: Result Caching with Freshness Policy
import { LRUCache } from "lru-cache";
import { createHash } from "crypto";
interface CachedResult {
answer: string;
citations: ValidatedCitation[];
cachedAt: number;
model: string;
}
const CACHE_TTL: Record<string, number> = {
news: 30 * 60_000, // 30 min for breaking/current events
research: 4 * 3600_000, // 4 hours for research topics
factual: 24 * 3600_000, // 24 hours for stable facts
default: 1 * 3600_000, // 1 hour default
};
const resultCache = new LRUCache<string, CachedResult>({ max: 500 });
function detectQueryType(query: string): keyof typeof CACHE_TTL {
if (/\b(latest|today|breaking|recent|this week)\b/i.test(query)) return "news";
if (/\b(research|study|paper|analysis|compare)\b/i.test(query)) return "research";
if (/\b(what is|define|how does|who is)\b/i.test(query)) return "factual";
return "default";
}
async function cachedSearch(query: string, model = "sonar") {
const hash = createHash("sha256")
.update(`${model}:${query.toLowerCase().trim()}`)
.digest("hex");
const cached = resultCache.get(hash);
if (cached) return { ...cached, fromCache: true };
const response = await safeSearch(query);
const rawCitations = (response as any).citations || [];
const citations = deduplicateCitations(validateCitations(rawCitations));
const queryType = detectQueryType(query);
const entry: CachedResult = {
answer: response.choices[0].message.content || "",
citations,
cachedAt: Date.now(),
model: response.model,
};
resultCache.set(hash, entry, { ttl: CACHE_TTL[queryType] });
return { ...entry, fromCache: false };
}
Step 4: Conversation Context Management
import OpenAI from "openai";
type Message = OpenAI.ChatCompletionMessageParam;
class SearchContext {
private messages: Message[] = [];
private readonly maxMessages = 10;
private readonly maxEstimatedTokens = 8000;
constructor(systemPrompt?: string) {
if (systemPrompt) {
this.messages.push({ role: "system", content: systemPrompt });
}
}
addUserMessage(content: string) {
this.messages.push({ role: "user", content });
this.trim();
}
addAssistantMessage(content: string) {
this.messages.push({ role: "assistant", content });
this.trim();
}
getMessages(): Message[] {
return [...this.messages];
}
private trim() {
// Keep system prompt + last N messages
while (this.messages.length > this.maxMessages) {
const systemIdx = this.messages[0].role === "system" ? 1 : 0;
this.messages.splice(systemIdx, 1);
}
// Trim if estimated tokens too high
while (this.estimateTokens() > this.maxEstimatedTokens && this.messages.length > 2) {
const systemIdx = this.messages[0].role === "system" ? 1 : 0;
this.messages.splice(systemIdx, 1);
}
}
private estimateTokens(): number {
return this.messages.reduce(
(sum, m) => sum + Math.ceil(String(m.content).length / 4),
0
);
}
clear() {
const system = this.messages.find((m) => m.role === "system");
this.messages = system ? [system] : [];
}
}
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| PII in search query | User entered personal data | Apply sanitizeQuery before API call |
| Broken citation URLs | Source page moved/deleted | Validate URLs, filter invalid ones |
| Stale cached results | TTL too long for news | Use query-type-aware TTL |
| Context overflow | Too many conversation turns | Automatic trimming in SearchContext |
| Duplicate citations | Same source cited multiple times | Deduplicate by normalized URL |
Output
- Query sanitization stripping PII before API calls
- Citation validation and deduplication
- Cache with query-type-aware TTL
- Conversation context with automatic trimming
Resources
Next Steps
For access control, see perplexity-enterprise-rbac.
Prerequisites
Limitations
- →PII in search query if sanitizeQuery is not applied
- →Broken citation URLs if validation is skipped
- →Stale cached results if TTL is too long for query type
How it compares
This workflow specifically addresses PII handling, citation validation, and context management for Perplexity API integrations, which is more specialized than a generic search implementation.
Compared to similar skills
perplexity-data-handling side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| perplexity-data-handling (this skill) | 1 | 25d | No flags | Intermediate |
| juicebox-prod-checklist | 1 | 25d | Caution | Beginner |
| juicebox-security-basics | 1 | 25d | Review | Intermediate |
| perplexity-security-basics | 1 | 25d | Caution | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
juicebox-prod-checklist
jeremylongshore
Execute Juicebox production deployment checklist. Use when preparing for production launch, validating deployment readiness, or performing pre-launch reviews. Trigger with phrases like "juicebox production", "deploy juicebox prod", "juicebox launch checklist", "juicebox go-live".
juicebox-security-basics
jeremylongshore
Apply Juicebox security best practices. Use when securing API keys, implementing access controls, or auditing Juicebox integration security. Trigger with phrases like "juicebox security", "secure juicebox", "juicebox API key security", "juicebox access control".
perplexity-security-basics
jeremylongshore
Apply Perplexity security best practices for secrets and access control. Use when securing API keys, implementing least privilege access, or auditing Perplexity security configuration. Trigger with phrases like "perplexity security", "perplexity secrets", "secure perplexity", "perplexity API key security".
vercel-rate-limits
jeremylongshore
Implement Vercel rate limiting, backoff, and idempotency patterns. Use when handling rate limit errors, implementing retry logic, or optimizing API request throughput for Vercel. Trigger with phrases like "vercel rate limit", "vercel throttling", "vercel 429", "vercel retry", "vercel backoff".
solana-dev
solanabr
Unified skill hub for Solana development. Routes to external submodule skills (solana-foundation, sendai, solana-game, trailofbits, cloudflare, qedgen, colosseum) and local skills. Progressive disclosure — read only what you need.
data
salazarsebas
Querying Stellar chain data via Stellar RPC (preferred) and Horizon (legacy). Covers RPC JSON-RPC methods, Horizon REST endpoints, streaming, pagination, historical queries, Hubble/Galexie for deep history, and the RPC/Horizon migration story. Use when reading balances, transactions, operations, led