UNPKG

ai-sdk-guardrails

Version:

Input and output guardrails middleware for Vercel AI SDK.

1,448 lines (1,445 loc) 46.2 kB
import { createOutputGuardrail } from "./chunk-F7POYYOU.js"; // src/guardrails/output.ts var EMPTY_CONTENT = { text: "", object: null, usage: void 0, finishReason: void 0, generationTimeMs: void 0, reasoningText: void 0 }; function emptyContent() { return { ...EMPTY_CONTENT }; } function mapUsage(usage) { if (!usage) { return void 0; } const pickNumber = (keys) => { for (const key of keys) { const value = usage[key]; if (typeof value === "number") { return value; } if (value && typeof value === "object" && "total" in value && typeof value.total === "number") { return value.total; } } return void 0; }; const totalTokens = pickNumber(["totalTokens"]); const promptTokens = pickNumber(["inputTokens", "promptTokens"]); const completionTokens = pickNumber(["outputTokens", "completionTokens"]); const computedTotal = totalTokens ?? (promptTokens !== void 0 && completionTokens !== void 0 ? promptTokens + completionTokens : void 0); if (promptTokens === void 0 && completionTokens === void 0 && computedTotal === void 0) { return void 0; } return { promptTokens, completionTokens, totalTokens: computedTotal }; } function extractGenerationTime(result) { return result.experimental_providerMetadata?.generationTimeMs ?? result.providerMetadata?.generationTimeMs ?? 0; } function extractReasoningText(result) { return result.reasoningText || result.experimental_providerMetadata?.reasoningText || void 0; } function createContent(partial) { return { ...EMPTY_CONTENT, ...partial }; } function extractContent(result) { const contentArray = result.content; if ("content" in result && Array.isArray(contentArray) && contentArray.length > 0) { const typedResult = result; const textContent = typedResult.content.filter((item) => item.type === "text" && item.text).map((item) => item.text).join(""); const objectValue = typedResult.output ?? typedResult.object ?? null; return createContent({ text: textContent || "", object: objectValue, usage: mapUsage(typedResult.usage), finishReason: typedResult.finishReason, generationTimeMs: extractGenerationTime(typedResult), reasoningText: extractReasoningText(typedResult) }); } if ("output" in result && result.output !== null && result.output !== void 0) { const outputResult = result; return createContent({ text: outputResult.text || "", object: outputResult.output, usage: mapUsage(outputResult.usage), finishReason: outputResult.finishReason, generationTimeMs: extractGenerationTime(outputResult), reasoningText: extractReasoningText(outputResult) }); } if ("object" in result && result.object !== null && result.object !== void 0) { const objectResult = result; return createContent({ text: objectResult.text || "", object: objectResult.object, usage: mapUsage(objectResult.usage), finishReason: objectResult.finishReason, generationTimeMs: extractGenerationTime(objectResult), reasoningText: extractReasoningText(objectResult) }); } if ("text" in result && typeof result.text === "string") { const textResult = result; return createContent({ text: textResult.text || "", object: null, usage: mapUsage(textResult.usage), finishReason: textResult.finishReason, generationTimeMs: extractGenerationTime(textResult), reasoningText: extractReasoningText(textResult) }); } if ("textStream" in result || "objectStream" in result || "embeddings" in result || "then" in result) { return emptyContent(); } return emptyContent(); } function stringifyContent(text, object, accumulatedText) { if (accumulatedText !== void 0) { return accumulatedText; } if (text !== void 0 && text !== null && text.length > 0) { return text; } if (object !== null && object !== void 0) { return JSON.stringify(object); } return ""; } function normalizeUsage(usage) { if (!usage) { return void 0; } const pickNumber = (keys) => { for (const key of keys) { const value = usage[key]; if (typeof value === "number") { return value; } if (value && typeof value === "object" && "total" in value && typeof value.total === "number") { return value.total; } } return void 0; }; const totalTokens = pickNumber(["totalTokens", "total_tokens"]); const promptTokens = pickNumber([ "inputTokens", "promptTokens", "input_tokens", "prompt_tokens" ]); const completionTokens = pickNumber([ "outputTokens", "completionTokens", "output_tokens", "completion_tokens" ]); const computedTotal = totalTokens ?? (promptTokens !== void 0 && completionTokens !== void 0 ? promptTokens + completionTokens : void 0); if (promptTokens === void 0 && completionTokens === void 0 && computedTotal === void 0) { return void 0; } return { promptTokens, completionTokens, totalTokens: computedTotal }; } var outputLengthLimit = (maxLength) => createOutputGuardrail( "output-length-limit", (context, accumulatedText) => { const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result); const content = stringifyContent(text, object, accumulatedText); const normalizedUsage = normalizeUsage(usage); return { tripwireTriggered: content.length > maxLength, message: `Output length ${content.length} exceeds limit of ${maxLength}`, severity: "medium", metadata: { contentLength: content.length, maxLength, hasObject: !!object, usage: normalizedUsage, finishReason, generationTimeMs, tokensPerMs: normalizedUsage?.totalTokens && generationTimeMs ? normalizedUsage.totalTokens / generationTimeMs : void 0 }, info: { guardrailName: "output-length-limit", contentLength: content.length, maxLength, hasObject: !!object } }; } ); var minLengthRequirement = (minLength) => createOutputGuardrail( "output-min-length", (context, accumulatedText) => { const { text, object } = extractContent(context.result); const content = accumulatedText || text || (object ? JSON.stringify(object) : ""); if (content.length < minLength) { return { tripwireTriggered: true, message: `Output too short: ${content.length} characters (min: ${minLength})`, severity: "medium", metadata: { currentLength: content.length, minLength, deficit: minLength - content.length, hasObject: !!object }, info: { guardrailName: "output-min-length", currentLength: content.length, minLength, deficit: minLength - content.length, hasObject: !!object } }; } return { tripwireTriggered: false, info: { guardrailName: "output-min-length", currentLength: content.length, minLength } }; } ); var sensitiveDataFilter = () => createOutputGuardrail( "sensitive-data-filter", (context, accumulatedText) => { const { text, object } = extractContent(context.result); const content = accumulatedText || text || (object ? JSON.stringify(object) : ""); const sensitivePatterns = [ { name: "SSN", regex: /\b\d{3}-\d{2}-\d{4}\b/, severity: "high" }, { name: "API Key", regex: /(?:api[_-]?key|apikey|api_token)[\s:=]*['"]*([a-zA-Z0-9]{32,})/i, severity: "critical" }, { name: "Credit Card", regex: /\b(?:\d{4}[-\s]?){3}\d{4}\b/, severity: "high" }, { name: "Email", regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, severity: "medium" }, { name: "Phone", regex: /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/, severity: "medium" }, { name: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/, severity: "critical" }, { name: "Private Key", regex: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/, severity: "critical" } ]; const detected = sensitivePatterns.filter((p) => p.regex.test(content)); if (detected.length > 0) { const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 }; let highestSeverity = detected[0]?.severity || "medium"; for (const pattern of detected) { if (severityOrder[pattern.severity] > severityOrder[highestSeverity]) { highestSeverity = pattern.severity; } } return { tripwireTriggered: true, message: `Sensitive data detected: ${detected.map((p) => p.name).join(", ")}`, severity: highestSeverity, metadata: { detectedTypes: detected.map((p) => ({ type: p.name, severity: p.severity })), count: detected.length, contentLength: content.length }, info: { guardrailName: "sensitive-data-filter", detectedTypes: detected.map((p) => p.name), count: detected.length, contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "sensitive-data-filter" } }; } ); var blockedContent = (words) => createOutputGuardrail( "blocked-content", (context, accumulatedText) => { const { text, object } = extractContent(context.result); const content = stringifyContent(text, object, accumulatedText); const lowerContent = content.toLowerCase(); const blockedWord = words.find( (word) => lowerContent.includes(word.toLowerCase()) ); return { tripwireTriggered: !!blockedWord, message: blockedWord ? `Blocked content detected: ${blockedWord}` : void 0, severity: "high", metadata: { blockedWord, allWords: words, contentLength: content.length }, info: { guardrailName: "blocked-content", blockedWord, allWords: words, contentLength: content.length } }; } ); var jsonValidation = () => createOutputGuardrail( "json-validation", (context, accumulatedText) => { const { text, object } = extractContent(context.result); const content = stringifyContent(text, object, accumulatedText); if (object) { return { tripwireTriggered: false, info: { guardrailName: "json-validation", hasObject: true } }; } const trimmed = content.trim(); if (!trimmed.startsWith("{") && !trimmed.startsWith("[") && !trimmed.startsWith('"')) { return { tripwireTriggered: true, message: "Output is not valid JSON - does not start with valid JSON character", severity: "medium", metadata: { error: "Invalid JSON prefix", textLength: content.length, firstChar: trimmed.charAt(0) }, info: { guardrailName: "json-validation", error: "Invalid JSON prefix", textLength: content.length, firstChar: trimmed.charAt(0) } }; } try { JSON.parse(content); return { tripwireTriggered: false, info: { guardrailName: "json-validation" } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { tripwireTriggered: true, message: `Output is not valid JSON: ${errorMessage}`, severity: "medium", metadata: { error: errorMessage, textLength: content.length, validationErrors: [errorMessage] }, info: { guardrailName: "json-validation", error: errorMessage, textLength: content.length, validationErrors: [errorMessage] } }; } } ); var confidenceThreshold = (minConfidence) => createOutputGuardrail( "confidence-threshold", (context) => { const { text, object, usage, finishReason, generationTimeMs, reasoningText } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const hasUncertainty = content.toLowerCase().includes("i think") || content.toLowerCase().includes("maybe") || content.toLowerCase().includes("probably") || content.toLowerCase().includes("uncertain") || content.toLowerCase().includes("not sure"); const finishReasonPenalty = finishReason === "length" ? 0.2 : 0; const baseConfidence = hasUncertainty ? 0.5 : 0.9; const confidence = Math.max(0, baseConfidence - finishReasonPenalty); return { tripwireTriggered: confidence < minConfidence, message: `Output confidence ${confidence} below threshold ${minConfidence}`, severity: "medium", metadata: { confidence, minConfidence, hasUncertainty, textLength: content.length, usage, finishReason, generationTimeMs, finishReasonPenalty, reasoningText }, info: { guardrailName: "confidence-threshold", confidence, minConfidence, hasUncertainty, textLength: content.length } }; } ); var toxicityFilter = (threshold = 0.7) => createOutputGuardrail( "toxicity-filter", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const toxicWords = ["toxic", "harmful", "offensive", "inappropriate"]; const detectedWords = toxicWords.filter( (word) => content.toLowerCase().includes(word) ); const toxicityScore = detectedWords.length * 0.3; return { tripwireTriggered: toxicityScore > threshold, message: `Content toxicity score ${toxicityScore} exceeds threshold ${threshold}`, severity: "high", metadata: { toxicityScore, threshold, detectedWords, contentLength: content.length }, info: { guardrailName: "toxicity-filter", toxicityScore, threshold, detectedWords, contentLength: content.length } }; } ); var customValidation = (name, validator, message) => { return createOutputGuardrail(name, (context) => { const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result); const validatorInput = { text, object, usage, finishReason, generationTimeMs }; const blocked = validator(validatorInput); return { tripwireTriggered: blocked, message: blocked ? message : void 0, severity: "medium", metadata: { validatorName: name, hasText: !!text, hasObject: !!object, usage, finishReason, generationTimeMs }, info: { guardrailName: name, validatorName: name, hasText: !!text, hasObject: !!object } }; }); }; var schemaValidation = (schema) => createOutputGuardrail( "schema-validation", (context) => { const { object, usage, finishReason, generationTimeMs } = extractContent( context.result ); if (!object) { return { tripwireTriggered: true, message: "No object to validate", severity: "medium", metadata: { hasObject: false, usage, finishReason, generationTimeMs }, info: { guardrailName: "schema-validation", hasObject: false } }; } try { schema.parse(object); return { tripwireTriggered: false, metadata: { hasObject: true, validationPassed: true, usage, finishReason, generationTimeMs }, info: { guardrailName: "schema-validation", hasObject: true, validationPassed: true } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { tripwireTriggered: true, message: `Schema validation failed: ${errorMessage}`, severity: "high", metadata: { hasObject: true, validationPassed: false, error: errorMessage, usage, finishReason, generationTimeMs }, info: { guardrailName: "schema-validation", hasObject: true, validationPassed: false, error: errorMessage } }; } } ); var tokenUsageLimit = (maxTokens) => createOutputGuardrail( "token-usage-limit", (context) => { const { text, object, usage, generationTimeMs } = extractContent( context.result ); const totalTokens = usage?.totalTokens || 0; const content = text || (object ? JSON.stringify(object) : ""); return { tripwireTriggered: totalTokens > maxTokens, message: `Token usage ${totalTokens} exceeds limit of ${maxTokens}`, severity: "medium", metadata: { totalTokens, maxTokens, inputTokens: usage?.inputTokens || usage?.promptTokens, outputTokens: usage?.outputTokens || usage?.completionTokens, contentLength: content.length, generationTimeMs, tokensPerMs: totalTokens && generationTimeMs ? totalTokens / generationTimeMs : void 0 }, info: { guardrailName: "token-usage-limit", totalTokens, maxTokens, contentLength: content.length } }; } ); var performanceMonitor = (maxGenerationTimeMs) => createOutputGuardrail( "performance-monitor", (context) => { const { text, object, usage, generationTimeMs } = extractContent( context.result ); const actualGenerationTimeMs = generationTimeMs || 0; const content = text || (object ? JSON.stringify(object) : ""); return { tripwireTriggered: actualGenerationTimeMs > maxGenerationTimeMs, message: `Generation time ${actualGenerationTimeMs}ms exceeds limit of ${maxGenerationTimeMs}ms`, severity: "low", metadata: { generationTimeMs: actualGenerationTimeMs, maxGenerationTimeMs, contentLength: content.length, usage, tokensPerMs: usage?.totalTokens && actualGenerationTimeMs ? usage.totalTokens / actualGenerationTimeMs : void 0, charactersPerMs: actualGenerationTimeMs ? content.length / actualGenerationTimeMs : void 0 }, info: { guardrailName: "performance-monitor", generationTimeMs: actualGenerationTimeMs, maxGenerationTimeMs, contentLength: content.length } }; } ); var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value); var hallucinationDetector = (confidenceThreshold2 = 0.7) => createOutputGuardrail( "hallucination-detector", (context, accumulatedText) => { const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result); const content = accumulatedText || (object && isObject(object) ? JSON.stringify(object) : text || ""); const uncertaintyIndicators = [ "i think", "i believe", "probably", "likely", "might be", "could be", "not sure", "uncertain", "possibly", "perhaps", "maybe", "seems like", "appears to be", "my understanding is", "if i recall correctly" ]; const factualClaims = [ "according to", "studies show", "research indicates", "data suggests", "statistics show", "proven fact", "scientific evidence", "documented", "confirmed by", "established that" ]; const uncertaintyCount = uncertaintyIndicators.filter( (indicator) => content.toLowerCase().includes(indicator) ).length; const factualClaimCount = factualClaims.filter( (claim) => content.toLowerCase().includes(claim) ).length; const hallucinationScore = uncertaintyCount * 0.3 + factualClaimCount * 0.2; const isHallucination = hallucinationScore > confidenceThreshold2; return { tripwireTriggered: isHallucination, message: isHallucination ? `Potential hallucination detected (score: ${hallucinationScore})` : void 0, severity: hallucinationScore > 0.8 ? "high" : "medium", metadata: { hallucinationScore, confidenceThreshold: confidenceThreshold2, uncertaintyCount, factualClaimCount, contentLength: content.length, usage, finishReason, generationTimeMs }, suggestion: "Please verify factual claims and consider requesting sources", info: { guardrailName: "hallucination-detector", hallucinationScore, confidenceThreshold: confidenceThreshold2, uncertaintyCount, factualClaimCount, contentLength: content.length } }; } ); var biasDetector = () => createOutputGuardrail("bias-detector", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const lowerContent = content.toLowerCase(); const biasPatterns = { gender: [ "men are better at", "women are better at", "typical male", "typical female", "boys will be boys", "women should", "men should", "ladies", "gentlemen" ], racial: [ "people of that race", "those people", "their culture", "natural talent", "genetic predisposition", "inherent ability", "cultural background" ], age: [ "young people today", "older people can't", "millennials are", "boomers are", "too old to", "too young to" ], socioeconomic: [ "poor people are", "rich people are", "welfare recipients", "privileged class", "working class", "upper class" ] }; const detectedBias = []; const matches = []; for (const [category, patterns] of Object.entries(biasPatterns)) { const found = patterns.filter( (pattern) => lowerContent.includes(pattern) ); if (found.length > 0) { detectedBias.push(category); matches.push(...found); } } return { tripwireTriggered: detectedBias.length > 0, message: detectedBias.length > 0 ? `Potential bias detected in categories: ${detectedBias.join(", ")}` : void 0, severity: "medium", metadata: { biasCategories: detectedBias, biasPatterns: matches, contentLength: content.length }, suggestion: "Consider reviewing content for potential bias and using more inclusive language", info: { guardrailName: "bias-detector", biasCategories: detectedBias, biasPatterns: matches, contentLength: content.length } }; }); var factualAccuracyChecker = (requireSources = false) => createOutputGuardrail( "factual-accuracy-checker", (context) => { const { text, object, generationTimeMs } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const factualClaims = [ "according to", "studies show", "research indicates", "data suggests", "statistics show", "proven fact", "scientific evidence", "documented", "confirmed by", "established that", "published in", "survey found" ]; const sourceCitations = [ "source:", "reference:", "citation:", "published in", "journal of", "university of", "institute of", "doi:", "isbn:", "url:", "http" ]; const factualClaimCount = factualClaims.filter( (claim) => content.toLowerCase().includes(claim) ).length; const sourceCitationCount = sourceCitations.filter( (source) => content.toLowerCase().includes(source) ).length; const hasUnfoundedClaims = requireSources && factualClaimCount > 0 && sourceCitationCount === 0; return { tripwireTriggered: hasUnfoundedClaims, message: hasUnfoundedClaims ? `Factual claims detected without sources (${factualClaimCount} claims, ${sourceCitationCount} sources)` : void 0, severity: "medium", metadata: { factualClaimCount, sourceCitationCount, requireSources, contentLength: content.length, generationTimeMs }, suggestion: "Please provide sources for factual claims or clarify that claims are general knowledge", info: { guardrailName: "factual-accuracy-checker", factualClaimCount, sourceCitationCount, requireSources, contentLength: content.length } }; } ); var privacyLeakageDetector = () => createOutputGuardrail( "privacy-leakage-detector", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const privacyPatterns = { personal: /\b(john|jane|smith|doe|password|secret|private|confidential)\b/gi, contact: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, phone: /\b\d{3}-\d{3}-\d{4}\b/g, financial: /\b(credit card|ssn|social security|bank account|routing number)\b/gi, location: /\b(address|street|apartment|zip code|postal code)\b/gi }; const detectedLeakage = []; const matches = []; for (const [category, pattern] of Object.entries(privacyPatterns)) { const found = content.match(pattern); if (found) { detectedLeakage.push(category); matches.push(...found); } } return { tripwireTriggered: detectedLeakage.length > 0, message: detectedLeakage.length > 0 ? `Potential privacy leakage detected: ${detectedLeakage.join(", ")}` : void 0, severity: "critical", metadata: { leakageCategories: detectedLeakage, matchCount: matches.length, contentLength: content.length }, suggestion: "Review output for any personal or sensitive information that should be removed", info: { guardrailName: "privacy-leakage-detector", leakageCategories: detectedLeakage, matchCount: matches.length, contentLength: content.length } }; } ); var contentConsistencyChecker = (referenceContent) => createOutputGuardrail( "content-consistency-checker", (context) => { if (!referenceContent) { return { tripwireTriggered: false, info: { guardrailName: "content-consistency-checker" } }; } const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const contentWords = content.toLowerCase().split(/\s+/); const referenceWords = referenceContent.toLowerCase().split(/\s+/); const commonWords = contentWords.filter( (word) => referenceWords.includes(word) ); const consistencyScore = commonWords.length / Math.max(contentWords.length, referenceWords.length); const isInconsistent = consistencyScore < 0.3; return { tripwireTriggered: isInconsistent, message: isInconsistent ? `Content consistency score too low: ${consistencyScore.toFixed(2)}` : void 0, severity: "medium", metadata: { consistencyScore, contentLength: content.length, referenceLength: referenceContent.length, commonWordCount: commonWords.length }, suggestion: "Ensure output maintains consistency with reference content", info: { guardrailName: "content-consistency-checker", consistencyScore, contentLength: content.length, referenceLength: referenceContent.length, commonWordCount: commonWords.length } }; } ); var complianceChecker = (regulations = []) => createOutputGuardrail( "compliance-checker", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const compliancePatterns = { gdpr: ["personal data", "data processing", "consent", "data subject"], hipaa: ["patient", "medical", "health information", "protected health"], pci: ["credit card", "payment", "cardholder", "card number"], sox: ["financial", "audit", "internal controls", "financial reporting"], coppa: ["children", "under 13", "parental consent", "minor"] }; const violations = []; for (const regulation of regulations) { const patterns = compliancePatterns[regulation.toLowerCase()]; if (patterns) { const found = patterns.some( (pattern) => content.toLowerCase().includes(pattern) ); if (found) { violations.push(regulation.toUpperCase()); } } } return { tripwireTriggered: violations.length > 0, message: violations.length > 0 ? `Potential compliance violations detected: ${violations.join(", ")}` : void 0, severity: "high", metadata: { violations, regulations, contentLength: content.length, environment: context.input?.environment }, suggestion: "Review output for compliance with applicable regulations", info: { guardrailName: "compliance-checker", violations, regulations, contentLength: content.length, environment: context.input?.environment } }; } ); var secretRedaction = createOutputGuardrail( "secret-redaction", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const secretPatterns = [ // API Keys (various formats) - more specific patterns to reduce false positives { name: "API Key", pattern: /(?:api[_-]?key|apikey|access[_-]?key)\s*[:=]\s*['"]?([a-zA-Z0-9]{20,})['"]?/gi }, // AWS Access Keys { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g }, // AWS Secret Keys { name: "AWS Secret Key", pattern: /[A-Za-z0-9/+=]{40}/g }, // AWS ARNs { name: "AWS ARN", pattern: /arn:aws:[a-zA-Z0-9-]+:[a-zA-Z0-9-]*:[0-9]*:[a-zA-Z0-9-_/.:*]+/g }, // JWT Tokens { name: "JWT Token", pattern: /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g }, // Bearer Tokens { name: "Bearer Token", pattern: /Bearer\s+[a-zA-Z0-9_.-]+/gi }, // GitHub Personal Access Tokens { name: "GitHub Token", pattern: /ghp_[a-zA-Z0-9]{36}/g }, // Google API Keys { name: "Google API Key", pattern: /AIza[0-9A-Za-z_-]{35}/g }, // PEM Certificate/Key blocks { name: "PEM Certificate/Key", pattern: /-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/g }, // SSH Private Keys { name: "SSH Private Key", pattern: /-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----[\s\S]*?-----END (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----/g }, // Database Connection Strings { name: "Database Connection String", pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^@\s]+:[^@\s]+@[^/\s]+/gi }, // Environment variables with secrets (more specific to reduce false positives) { name: "Environment Secret", pattern: /(?:token|secret|password|key)\s*[:=]\s*['"]?([a-zA-Z0-9_.-]{16,})['"]?/gi } ]; const detectedSecrets = []; for (const { name, pattern } of secretPatterns) { if (pattern.global) { pattern.lastIndex = 0; } let match; while ((match = pattern.exec(content)) !== null) { const maskedSecret = match[0].length > 20 ? match[0].slice(0, 8) + "..." + match[0].slice(-4) : match[0].slice(0, 4) + "..."; detectedSecrets.push({ type: name, pattern: maskedSecret, position: match.index }); if (match[0].length === 0) { pattern.lastIndex++; } } } if (detectedSecrets.length > 0) { return { tripwireTriggered: true, message: `Output contains ${detectedSecrets.length} potential secret(s): ${detectedSecrets.map((s) => s.type).join(", ")}`, severity: "critical", metadata: { secretsDetected: detectedSecrets.length, secretTypes: detectedSecrets.map((s) => s.type), contentLength: content.length }, suggestion: "Remove sensitive information before sharing output", info: { guardrailName: "secret-redaction", secretsDetected: detectedSecrets.length, secretTypes: detectedSecrets.map((s) => s.type), contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "secret-redaction" } }; } ); var unsafeContentDetector = createOutputGuardrail( "unsafe-content-detector", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const unsafePatterns = [ { category: "Violence", patterns: [ /\b(kill|murder|assassinate|torture|bomb|weapon|gun|knife|explosive)\b/gi, /\b(harm|hurt|injure|attack|assault|fight)\s+(someone|people|person)/gi, /\b(violence|violent|aggression|aggressive)\b/gi ] }, { category: "Hate Speech", patterns: [ /\b(hate|racist|sexist|homophobic|transphobic|xenophobic)\b/gi, /\b(nazi|fascist|supremacist|terrorism|terrorist)\b/gi, /\b(discrimination|prejudice|bigotry)\b/gi ] }, { category: "Self-Harm", patterns: [ /\b(suicide|self-harm|self-hurt|cut myself|end my life)\b/gi, /\b(want to die|kill myself|harm myself)\b/gi, /\b(suicidal|depression|self-destruction)\b/gi ] }, { category: "Illegal Activities", patterns: [ /\b(illegal drugs|drug dealing|money laundering|fraud|scam)\b/gi, /\b(hack|crack|pirate|steal|burglary|theft)\b/gi, /\b(counterfeit|forgery|blackmail|extortion)\b/gi ] }, { category: "Adult Content", patterns: [ /\b(pornography|explicit sexual|adult content|nsfw)\b/gi, /\b(sexual explicit|graphic sexual|sexual imagery)\b/gi ] }, { category: "Personal Information", patterns: [ /\b\d{3}-\d{2}-\d{4}\b/g, // SSN format /\b\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b/g, // Credit card format /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g // Email ] } ]; const detectedIssues = []; for (const { category, patterns } of unsafePatterns) { let totalMatches = 0; for (const pattern of patterns) { const matches = content.match(pattern); if (matches) { totalMatches += matches.length; } } if (totalMatches > 0) { detectedIssues.push({ category, matches: totalMatches }); } } if (detectedIssues.length > 0) { return { tripwireTriggered: true, message: `Unsafe content detected: ${detectedIssues.map((i) => `${i.category} (${i.matches})`).join(", ")}`, severity: "high", metadata: { categoriesDetected: detectedIssues.length, issues: detectedIssues, contentLength: content.length }, suggestion: "Review and modify content to remove potentially harmful material", info: { guardrailName: "unsafe-content-detector", categoriesDetected: detectedIssues.length, issues: detectedIssues, contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "unsafe-content-detector" } }; } ); var costQuotaRails = (options) => createOutputGuardrail( "cost-quota-rails", (context) => { const { text, object, usage } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const totalTokens = usage?.totalTokens || 0; const estimatedCost = options.tokenCostPer1K ? totalTokens / 1e3 * options.tokenCostPer1K : 0; const issues = []; if (options.maxTokensPerRequest && totalTokens > options.maxTokensPerRequest) { issues.push( `Token usage (${totalTokens}) exceeds limit (${options.maxTokensPerRequest})` ); } if (options.maxCostPerRequest && estimatedCost > options.maxCostPerRequest) { issues.push( `Estimated cost ($${estimatedCost.toFixed(4)}) exceeds limit ($${options.maxCostPerRequest})` ); } if (issues.length > 0) { return { tripwireTriggered: true, message: `Cost/quota limits exceeded: ${issues.join(", ")}`, severity: "high", metadata: { totalTokens, estimatedCost, maxTokensPerRequest: options.maxTokensPerRequest, maxCostPerRequest: options.maxCostPerRequest, tokenCostPer1K: options.tokenCostPer1K, contentLength: content.length }, suggestion: "Consider reducing request size or adjusting quota limits", info: { guardrailName: "cost-quota-rails", totalTokens, estimatedCost, maxTokensPerRequest: options.maxTokensPerRequest, maxCostPerRequest: options.maxCostPerRequest, contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "cost-quota-rails" } }; } ); var enhancedHallucinationDetector = (options) => createOutputGuardrail( "enhanced-hallucination-detector", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const { requireCitations = false, citationFormats = ["[", "(", "doi:", "url:", "source:", "ref:"], factCheckPatterns = [ "according to", "studies show", "research indicates", "data suggests", "statistics show", "proven fact", "scientific evidence", "documented", "confirmed by", "published in" ], confidenceThreshold: confidenceThreshold2 = 0.7, schemaConstraints } = options; const issues = []; let hallucinationScore = 0; const factualClaims = factCheckPatterns.filter( (pattern) => content.toLowerCase().includes(pattern.toLowerCase()) ); const citations = citationFormats.filter( (format) => content.toLowerCase().includes(format.toLowerCase()) ); if (requireCitations && factualClaims.length > 0 && citations.length === 0) { issues.push( `${factualClaims.length} factual claims detected without citations` ); hallucinationScore += 0.4; } const uncertaintyIndicators = [ "i think", "i believe", "probably", "likely", "might be", "could be", "not sure", "uncertain", "possibly", "perhaps", "maybe", "seems like", "appears to be", "if i recall correctly" ]; const uncertaintyCount = uncertaintyIndicators.filter( (indicator) => content.toLowerCase().includes(indicator) ).length; if (uncertaintyCount > 0 && factualClaims.length > 0) { issues.push(`Uncertainty expressions combined with factual claims`); hallucinationScore += uncertaintyCount * 0.1; } if (schemaConstraints && object) { const obj = object; if (schemaConstraints.requiredFields) { const missingFields = schemaConstraints.requiredFields.filter( (field) => !Object.hasOwn(obj, field) || obj[field] === null || obj[field] === void 0 ); if (missingFields.length > 0) { issues.push(`Missing required fields: ${missingFields.join(", ")}`); hallucinationScore += 0.3; } } if (schemaConstraints.allowedValues) { for (const [field, allowedValues] of Object.entries( schemaConstraints.allowedValues )) { if (!Object.hasOwn(obj, field) || allowedValues.includes(String(obj[field]))) { continue; } issues.push(`Invalid value for field '${field}': ${obj[field]}`); hallucinationScore += 0.2; } } } const contradictionPatterns = [ ["always", "never"], ["all", "none"], ["definitely", "maybe"], ["certain", "uncertain"], ["true", "false"] ]; for (const [pos, neg] of contradictionPatterns) { if (!(pos && neg && content.toLowerCase().includes(pos) && content.toLowerCase().includes(neg))) { continue; } issues.push(`Potential contradiction detected: ${pos}/${neg}`); hallucinationScore += 0.15; } const isHallucination = hallucinationScore > confidenceThreshold2; if (isHallucination || issues.length > 0) { return { tripwireTriggered: isHallucination, message: `Potential hallucination detected (score: ${hallucinationScore.toFixed(2)}): ${issues.join("; ")}`, severity: hallucinationScore > 0.8 ? "high" : "medium", metadata: { hallucinationScore, confidenceThreshold: confidenceThreshold2, issues, factualClaimsCount: factualClaims.length, citationsCount: citations.length, uncertaintyCount, contentLength: content.length, requireCitations }, suggestion: "Verify factual claims with reliable sources and add citations if making specific claims", info: { guardrailName: "enhanced-hallucination-detector", hallucinationScore, confidenceThreshold: confidenceThreshold2, issues, factualClaimsCount: factualClaims.length, citationsCount: citations.length, uncertaintyCount, contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "enhanced-hallucination-detector" } }; } ); var retryAfterIntegration = (options) => createOutputGuardrail( "retry-after-integration", (context) => { const { usage, generationTimeMs } = extractContent(context.result); const { maxRetryDelayMs = 6e4, // 1 minute max defaultBackoffMs = 1e3, // 1 second default jitterFactor = 0.1, trackRateLimits = true } = options; const rateLimitIndicators = { hasRetryAfter: false, retryAfterMs: 0, rateLimitExceeded: false, requestsRemaining: null, resetTime: null }; if (generationTimeMs && generationTimeMs > 5e3) { rateLimitIndicators.rateLimitExceeded = true; } const totalTokens = usage?.totalTokens || 0; if (totalTokens > 1e4) { rateLimitIndicators.rateLimitExceeded = true; } let recommendedBackoffMs = defaultBackoffMs; if (rateLimitIndicators.hasRetryAfter) { recommendedBackoffMs = Math.min( rateLimitIndicators.retryAfterMs, maxRetryDelayMs ); } else if (rateLimitIndicators.rateLimitExceeded) { recommendedBackoffMs = Math.min( defaultBackoffMs * 2 + Math.random() * jitterFactor * defaultBackoffMs, maxRetryDelayMs ); } const jitter = Math.random() * jitterFactor * recommendedBackoffMs; const finalBackoffMs = Math.round(recommendedBackoffMs + jitter); if (rateLimitIndicators.hasRetryAfter || rateLimitIndicators.rateLimitExceeded) { return { tripwireTriggered: true, message: `Rate limiting detected, recommended backoff: ${finalBackoffMs}ms`, severity: "medium", metadata: { ...rateLimitIndicators, recommendedBackoffMs: finalBackoffMs, originalBackoffMs: recommendedBackoffMs, jitterMs: jitter, maxRetryDelayMs, totalTokens, generationTimeMs, trackRateLimits }, suggestion: `Wait ${finalBackoffMs}ms before making the next request to respect rate limits`, info: { guardrailName: "retry-after-integration", hasRetryAfter: rateLimitIndicators.hasRetryAfter, rateLimitExceeded: rateLimitIndicators.rateLimitExceeded, recommendedBackoffMs: finalBackoffMs, totalTokens } }; } return { tripwireTriggered: false, metadata: { backoffCalculated: finalBackoffMs, rateLimitTracking: trackRateLimits, generationTimeMs, totalTokens }, info: { guardrailName: "retry-after-integration", backoffCalculated: finalBackoffMs, rateLimitTracking: trackRateLimits } }; } ); export { extractContent, stringifyContent, normalizeUsage, outputLengthLimit, minLengthRequirement, sensitiveDataFilter, blockedContent, jsonValidation, confidenceThreshold, toxicityFilter, customValidation, schemaValidation, tokenUsageLimit, performanceMonitor, hallucinationDetector, biasDetector, factualAccuracyChecker, privacyLeakageDetector, contentConsistencyChecker, complianceChecker, secretRedaction, unsafeContentDetector, costQuotaRails, enhancedHallucinationDetector, retryAfterIntegration };