| name | perplexity-data-handling |
| description | Implement Perplexity query sanitization, citation validation, result caching,
and conversation context management for search workflows.
Trigger with phrases like "perplexity data", "perplexity PII",
"perplexity citations", "perplexity cache", "perplexity context".
|
| allowed-tools | Read, Write, Edit |
| version | 1.12.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","perplexity","compliance"] |
| compatibility | Designed for Claude Code |
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();
}
perplexity...({
: ,
: [{ : , : 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 = <>();
citations.( {
normalized = c..()[].(, );
(seen.(normalized)) ;
seen.(normalized);
;
});
}
(): {
rendered = answer;
( c citations.( c.)) {
rendered = rendered.(, );
}
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,
research: 4 * 3600_000,
factual: 24 * 3600_000,
default: 1 * 3600_000,
};
const resultCache = new LRUCache<string, CachedResult>({ max: 500 });
function detectQueryType(query: string): keyof typeof CACHE_TTL {
if (.(query)) ;
(.(query)) ;
(.(query)) ;
;
}
() {
hash = ()
.()
.();
cached = resultCache.(hash);
(cached) { ...cached, : };
response = (query);
rawCitations = (response ). || [];
citations = ((rawCitations));
queryType = (query);
: = {
: response.[].. || ,
citations,
: .(),
: response.,
};
resultCache.(hash, entry, { : [queryType] });
{ ...entry, : };
}
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();
}
(): [] {
[....];
}
() {
(.. > .) {
systemIdx = .[]. === ? : ;
..(systemIdx, );
}
(.() > . && .. > ) {
systemIdx = .[]. === ? : ;
..(systemIdx, );
}
}
(): {
..(
sum + .((m.). / ),
);
}
() {
system = ..( m. === );
. = 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.