Claude-code-plugins-plus-skills perplexity-policy-guardrails
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-policy-guardrails" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-perplexity-policy-guardrails && rm -rf "$T"
manifest:
plugins/saas-packs/perplexity-pack/skills/perplexity-policy-guardrails/SKILL.mdsource content
Perplexity Policy Guardrails
Overview
Policy enforcement for Perplexity Sonar API. Since Perplexity performs live web searches, guardrails must address: query content moderation (what users can search for), citation reliability (filtering low-quality sources), cost control (model selection + token limits), and responsible AI usage.
Policy Pipeline
User Query │ ▼ Query Moderation (block harmful queries) │ ▼ PII Sanitization (strip personal data) │ ▼ Quota Check (daily limit by user tier) │ ▼ Model Selection (enforce tier-appropriate model) │ ▼ Perplexity API Call │ ▼ Citation Quality Scoring (filter low-trust sources) │ ▼ Response to User
Prerequisites
- Perplexity API configured
- Content moderation policy defined
- User tier system in place
- Redis for quota tracking (optional: in-memory for simple apps)
Instructions
Step 1: Query Content Moderation
const BLOCKED_PATTERNS = [ /\b(write|generate|create)\s+(malware|virus|exploit|ransomware)\b/i, /\b(personal|private)\s+(address|phone|ssn)\s+of\s+\w+/i, /\b(bypass|circumvent|hack)\s+(security|firewall|authentication)\b/i, /\b(how to|tutorial)\s+(stalk|dox|harass)\b/i, ]; const MAX_QUERY_LENGTH = 2000; class PolicyError extends Error { constructor(public code: string, message: string) { super(message); this.name = "PolicyError"; } } function moderateQuery(query: string): string { if (query.length > MAX_QUERY_LENGTH) { throw new PolicyError("QUERY_TOO_LONG", `Query exceeds ${MAX_QUERY_LENGTH} characters`); } for (const pattern of BLOCKED_PATTERNS) { if (pattern.test(query)) { throw new PolicyError("CONTENT_BLOCKED", "Query blocked by content policy"); } } return query; }
Step 2: Model Selection Policy
interface ModelPolicy { model: string; maxTokens: number; costPerRequest: number; } const MODEL_POLICIES: Record<string, ModelPolicy> = { free: { model: "sonar", maxTokens: 256, costPerRequest: 0.005 }, basic: { model: "sonar", maxTokens: 1024, costPerRequest: 0.005 }, pro: { model: "sonar-pro", maxTokens: 2048, costPerRequest: 0.02 }, enterprise: { model: "sonar-pro", maxTokens: 4096, costPerRequest: 0.02 }, }; function enforceModelPolicy( userTier: string, requestedModel?: string ): ModelPolicy { const policy = MODEL_POLICIES[userTier] || MODEL_POLICIES.free; // Prevent free users from using expensive models if (requestedModel === "sonar-pro" && !["pro", "enterprise"].includes(userTier)) { console.warn(`User tier ${userTier} not allowed sonar-pro, using sonar`); return MODEL_POLICIES.free; } return requestedModel ? { ...policy, model: requestedModel } : policy; }
Step 3: Per-User Usage Quotas
class UsageQuota { private usage: Map<string, { count: number; resetAt: number }> = new Map(); private readonly limits: Record<string, number> = { free: 50, basic: 200, pro: 1000, enterprise: 5000, }; check(userId: string, tier: string = "free"): void { const key = `${userId}:${new Date().toISOString().slice(0, 10)}`; const entry = this.usage.get(key) || { count: 0, resetAt: this.endOfDay() }; // Reset if past end of day if (Date.now() > entry.resetAt) { entry.count = 0; entry.resetAt = this.endOfDay(); } const limit = this.limits[tier] || this.limits.free; if (entry.count >= limit) { throw new PolicyError( "QUOTA_EXCEEDED", `Daily quota exceeded (${entry.count}/${limit}). Resets at midnight UTC.` ); } entry.count++; this.usage.set(key, entry); } getUsage(userId: string): { used: number; limit: number; remaining: number } { const key = `${userId}:${new Date().toISOString().slice(0, 10)}`; const entry = this.usage.get(key); const used = entry?.count || 0; return { used, limit: 50, remaining: Math.max(0, 50 - used) }; } private endOfDay(): number { const d = new Date(); d.setUTCHours(23, 59, 59, 999); return d.getTime(); } }
Step 4: Citation Quality Scoring
const TRUSTED_TLDS = new Set(["gov", "edu", "org"]); const HIGH_QUALITY_DOMAINS = new Set([ "nature.com", "science.org", "arxiv.org", "wikipedia.org", "nih.gov", "cdc.gov", "who.int", ]); const LOW_QUALITY_DOMAINS = new Set([ "reddit.com", "quora.com", "medium.com", "yahoo.com", ]); interface CitationQuality { url: string; trust: "high" | "medium" | "low"; domain: string; } function scoreCitations(citations: string[]): { scored: CitationQuality[]; highTrustPercent: number; } { const scored = citations.map((url) => { const domain = new URL(url).hostname; const tld = domain.split(".").pop() || ""; let trust: "high" | "medium" | "low" = "medium"; if (TRUSTED_TLDS.has(tld) || HIGH_QUALITY_DOMAINS.has(domain)) { trust = "high"; } else if (LOW_QUALITY_DOMAINS.has(domain)) { trust = "low"; } return { url, trust, domain }; }); const highTrust = scored.filter((s) => s.trust === "high").length; return { scored, highTrustPercent: citations.length > 0 ? highTrust / citations.length : 0, }; }
Step 5: Full Policy Pipeline
const quota = new UsageQuota(); async function policiedSearch( query: string, userId: string, userTier: string = "free", requestedModel?: string ) { // 1. Content moderation const moderated = moderateQuery(query); // 2. PII sanitization const { clean } = sanitizeQuery(moderated); // 3. Quota check quota.check(userId, userTier); // 4. Model policy const policy = enforceModelPolicy(userTier, requestedModel); // 5. API call const response = await perplexity.chat.completions.create({ model: policy.model, messages: [{ role: "user", content: clean }], max_tokens: policy.maxTokens, }); // 6. Citation quality const citations = (response as any).citations || []; const quality = scoreCitations(citations); return { answer: response.choices[0].message.content, citations: quality.scored, citationQuality: quality.highTrustPercent, model: response.model, tokens: response.usage?.total_tokens, }; }
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Query blocked | Content moderation triggered | Review patterns, adjust if false positive |
| Quota exceeded | User hit daily limit | Upgrade tier or wait for reset |
| Model downgraded | User tier restricts access | Inform user of tier limitations |
| Low citation quality | All sources from forums | Add for trusted sources |
Output
- Query content moderation with blocked patterns
- Model selection enforced by user tier
- Per-user daily quotas
- Citation quality scoring and filtering
- Full policy pipeline combining all layers
Resources
Next Steps
For architecture patterns, see
perplexity-architecture-variants.