ai-sdk-guardrails
Version:
Input and output guardrails middleware for Vercel AI SDK.
1,448 lines (1,445 loc) • 46.2 kB
JavaScript
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
};