ai-sdk-guardrails
Version:
Input and output guardrails middleware for Vercel AI SDK.
816 lines (811 loc) • 27.9 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/errors.ts
var errors_exports = {};
__export(errors_exports, {
GuardrailConfigurationError: () => GuardrailConfigurationError,
GuardrailExecutionError: () => GuardrailExecutionError,
GuardrailTimeoutError: () => GuardrailTimeoutError,
GuardrailValidationError: () => GuardrailValidationError,
GuardrailsError: () => GuardrailsError,
InputBlockedError: () => InputBlockedError,
MiddlewareError: () => MiddlewareError,
OutputBlockedError: () => OutputBlockedError,
extractErrorInfo: () => extractErrorInfo,
isGuardrailsError: () => isGuardrailsError
});
function isGuardrailsError(error) {
return error instanceof GuardrailsError;
}
function extractErrorInfo(error) {
if (isGuardrailsError(error)) {
return {
name: error.name,
message: error.message,
code: error.code,
metadata: error.metadata
};
}
if (error instanceof Error) {
return {
name: error.name,
message: error.message
};
}
return {
name: "UnknownError",
message: String(error)
};
}
var GuardrailsError, GuardrailValidationError, GuardrailExecutionError, GuardrailTimeoutError, GuardrailConfigurationError, InputBlockedError, OutputBlockedError, MiddlewareError;
var init_errors = __esm({
"src/errors.ts"() {
"use strict";
GuardrailsError = class extends Error {
timestamp;
metadata;
constructor(message, metadata = {}) {
super(message);
this.timestamp = /* @__PURE__ */ new Date();
this.metadata = metadata;
Object.setPrototypeOf(this, new.target.prototype);
}
/**
* Convert the error to a serializable object for logging/reporting
*/
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
timestamp: this.timestamp.toISOString(),
metadata: this.metadata,
stack: this.stack
};
}
/**
* Check if this error is of a specific type
*/
is(errorClass) {
return this instanceof errorClass;
}
};
GuardrailValidationError = class extends GuardrailsError {
name = "GuardrailValidationError";
code = "GUARDRAIL_VALIDATION_FAILED";
guardrailName;
validationErrors;
constructor(guardrailName, validationErrors, metadata = {}) {
const message = `Guardrail "${guardrailName}" validation failed: ${validationErrors.map((e) => e.message).join(", ")}`;
super(message, { ...metadata, guardrailName, validationErrors });
this.guardrailName = guardrailName;
this.validationErrors = validationErrors;
}
};
GuardrailExecutionError = class extends GuardrailsError {
name = "GuardrailExecutionError";
code = "GUARDRAIL_EXECUTION_FAILED";
guardrailName;
originalError;
constructor(guardrailName, originalError, metadata = {}) {
const message = originalError ? `Guardrail "${guardrailName}" execution failed: ${originalError.message}` : `Guardrail "${guardrailName}" execution failed`;
super(message, {
...metadata,
guardrailName,
originalError: originalError?.message
});
this.guardrailName = guardrailName;
this.originalError = originalError;
}
};
GuardrailTimeoutError = class extends GuardrailsError {
name = "GuardrailTimeoutError";
code = "GUARDRAIL_TIMEOUT";
guardrailName;
timeoutMs;
constructor(guardrailName, timeoutMs, metadata = {}) {
const message = `Guardrail "${guardrailName}" timed out after ${timeoutMs}ms`;
super(message, { ...metadata, guardrailName, timeoutMs });
this.guardrailName = guardrailName;
this.timeoutMs = timeoutMs;
}
};
GuardrailConfigurationError = class extends GuardrailsError {
name = "GuardrailConfigurationError";
code = "GUARDRAIL_CONFIG_INVALID";
configPath;
configErrors;
constructor(configErrors, configPath, metadata = {}) {
const message = `Guardrail configuration error${configPath ? ` in ${configPath}` : ""}: ${configErrors.join(", ")}`;
super(message, { ...metadata, configPath, configErrors });
this.configPath = configPath;
this.configErrors = configErrors;
}
};
InputBlockedError = class extends GuardrailsError {
name = "InputBlockedError";
code = "INPUT_BLOCKED";
blockedGuardrails;
constructor(blockedGuardrails, metadata = {}) {
const guardrailNames = blockedGuardrails.map((g) => g.name).join(", ");
const message = `Input blocked by guardrail${blockedGuardrails.length > 1 ? "s" : ""}: ${guardrailNames}`;
super(message, { ...metadata, blockedGuardrails });
this.blockedGuardrails = blockedGuardrails;
}
};
OutputBlockedError = class extends GuardrailsError {
name = "OutputBlockedError";
code = "OUTPUT_BLOCKED";
blockedGuardrails;
constructor(blockedGuardrails, metadata = {}) {
const guardrailNames = blockedGuardrails.map((g) => g.name).join(", ");
const message = `Output blocked by guardrail${blockedGuardrails.length > 1 ? "s" : ""}: ${guardrailNames}`;
super(message, { ...metadata, blockedGuardrails });
this.blockedGuardrails = blockedGuardrails;
}
};
MiddlewareError = class extends GuardrailsError {
name = "MiddlewareError";
code = "MIDDLEWARE_ERROR";
middlewareType;
phase;
originalError;
constructor(middlewareType, phase, originalError, metadata = {}) {
const message = originalError ? `${middlewareType} middleware ${phase} error: ${originalError.message}` : `${middlewareType} middleware ${phase} error`;
super(message, {
...metadata,
middlewareType,
phase,
originalError: originalError?.message
});
this.middlewareType = middlewareType;
this.phase = phase;
this.originalError = originalError;
}
};
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
GuardrailConfigurationError: () => GuardrailConfigurationError,
GuardrailExecutionError: () => GuardrailExecutionError,
GuardrailTimeoutError: () => GuardrailTimeoutError,
GuardrailValidationError: () => GuardrailValidationError,
GuardrailsError: () => GuardrailsError,
InputBlockedError: () => InputBlockedError,
MiddlewareError: () => MiddlewareError,
OutputBlockedError: () => OutputBlockedError,
createInputGuardrail: () => createInputGuardrail,
createInputGuardrailsMiddleware: () => createInputGuardrailsMiddleware,
createOutputGuardrail: () => createOutputGuardrail,
createOutputGuardrailsMiddleware: () => createOutputGuardrailsMiddleware,
defineInputGuardrail: () => defineInputGuardrail,
defineOutputGuardrail: () => defineOutputGuardrail,
executeInputGuardrails: () => executeInputGuardrails,
executeOutputGuardrails: () => executeOutputGuardrails,
extractErrorInfo: () => extractErrorInfo,
isGuardrailsError: () => isGuardrailsError,
wrapWithGuardrails: () => wrapWithGuardrails,
wrapWithInputGuardrails: () => wrapWithInputGuardrails,
wrapWithOutputGuardrails: () => wrapWithOutputGuardrails
});
module.exports = __toCommonJS(index_exports);
// src/core.ts
init_errors();
function createInputGuardrail(name, description, execute) {
return { name, description, execute };
}
function createOutputGuardrail(name, execute) {
return { name, execute };
}
// src/guardrails.ts
var import_ai = require("ai");
function defineInputGuardrail(guardrail) {
const enhanced = {
enabled: true,
priority: "medium",
version: "1.0.0",
tags: [],
...guardrail,
execute: async (params) => {
const startTime = Date.now();
const originalExecute = guardrail.execute;
try {
const result = await originalExecute(params);
const executionTime = Date.now() - startTime;
return {
...result,
context: {
guardrailName: guardrail.name,
guardrailVersion: guardrail.version,
executedAt: /* @__PURE__ */ new Date(),
executionTimeMs: executionTime,
...result.context
}
};
} catch (error) {
const executionTime = Date.now() - startTime;
return {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
context: {
guardrailName: guardrail.name,
guardrailVersion: guardrail.version,
executedAt: /* @__PURE__ */ new Date(),
executionTimeMs: executionTime
},
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
}
}
};
return enhanced;
}
async function executeInputGuardrails(guardrails, params, options = {}) {
const {
parallel = true,
timeout = 3e4,
// 30 seconds
continueOnFailure = true,
logLevel = "info"
} = options;
const enabledGuardrails = guardrails.filter((g) => g.enabled !== false).sort((a, b) => {
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
return (priorityOrder[b.priority || "medium"] || 2) - (priorityOrder[a.priority || "medium"] || 2);
});
const results = [];
const executeWithTimeout = async (guardrail) => {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(async () => {
const { GuardrailTimeoutError: GuardrailTimeoutError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
reject(new GuardrailTimeoutError2(guardrail.name, timeout));
}, timeout);
});
const executionPromise = guardrail.execute(params);
return Promise.race([executionPromise, timeoutPromise]);
};
if (parallel) {
const promises = enabledGuardrails.map(async (guardrail) => {
try {
const result = await executeWithTimeout(guardrail);
if (result.tripwireTriggered && logLevel !== "none") {
console.log(
`Input guardrail "${guardrail.name}" triggered: ${result.message}`
);
}
return result;
} catch (error) {
if (logLevel !== "none") {
console.error(
`Error executing input guardrail "${guardrail.name}":`,
error
);
}
return {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
}
});
results.push(...await Promise.all(promises));
} else {
for (const guardrail of enabledGuardrails) {
try {
const result = await executeWithTimeout(guardrail);
results.push(result);
if (result.tripwireTriggered) {
if (logLevel !== "none") {
console.log(
`Input guardrail "${guardrail.name}" triggered: ${result.message}`
);
}
if (!continueOnFailure) {
break;
}
}
} catch (error) {
if (logLevel !== "none") {
console.error(
`Error executing input guardrail "${guardrail.name}":`,
error
);
}
const errorResult = {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
results.push(errorResult);
if (!continueOnFailure) {
break;
}
}
}
}
return results;
}
function defineOutputGuardrail(guardrail) {
const enhanced = {
enabled: true,
priority: "medium",
version: "1.0.0",
tags: [],
...guardrail,
execute: async (params) => {
const startTime = Date.now();
const originalExecute = guardrail.execute;
try {
const result = await originalExecute(params);
const executionTime = Date.now() - startTime;
return {
...result,
context: {
guardrailName: guardrail.name,
guardrailVersion: guardrail.version,
executedAt: /* @__PURE__ */ new Date(),
executionTimeMs: executionTime,
...result.context
}
};
} catch (error) {
const executionTime = Date.now() - startTime;
return {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
context: {
guardrailName: guardrail.name,
guardrailVersion: guardrail.version,
executedAt: /* @__PURE__ */ new Date(),
executionTimeMs: executionTime
},
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
}
}
};
return enhanced;
}
async function executeOutputGuardrails(guardrails, params, options = {}) {
const {
parallel = true,
timeout = 3e4,
// 30 seconds
continueOnFailure = true,
logLevel = "info"
} = options;
const enabledGuardrails = guardrails.filter((g) => g.enabled !== false).sort((a, b) => {
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
return (priorityOrder[b.priority || "medium"] || 2) - (priorityOrder[a.priority || "medium"] || 2);
});
const results = [];
const executeWithTimeout = async (guardrail) => {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(async () => {
const { GuardrailTimeoutError: GuardrailTimeoutError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
reject(new GuardrailTimeoutError2(guardrail.name, timeout));
}, timeout);
});
const executionPromise = guardrail.execute(params);
return Promise.race([executionPromise, timeoutPromise]);
};
if (parallel) {
const promises = enabledGuardrails.map(async (guardrail) => {
try {
const result = await executeWithTimeout(guardrail);
if (result.tripwireTriggered && logLevel !== "none") {
console.log(
`Output guardrail "${guardrail.name}" triggered: ${result.message}`
);
}
return result;
} catch (error) {
if (logLevel !== "none") {
console.error(
`Error executing output guardrail "${guardrail.name}":`,
error
);
}
return {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
}
});
results.push(...await Promise.all(promises));
} else {
for (const guardrail of enabledGuardrails) {
try {
const result = await executeWithTimeout(guardrail);
results.push(result);
if (result.tripwireTriggered) {
if (logLevel !== "none") {
console.log(
`Output guardrail "${guardrail.name}" triggered: ${result.message}`
);
}
if (!continueOnFailure) {
break;
}
}
} catch (error) {
if (logLevel !== "none") {
console.error(
`Error executing output guardrail "${guardrail.name}":`,
error
);
}
const errorResult = {
tripwireTriggered: true,
message: `Guardrail execution failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "critical",
metadata: {
error: error instanceof Error ? error.message : String(error)
}
};
results.push(errorResult);
if (!continueOnFailure) {
break;
}
}
}
}
return results;
}
function extractTextFromContent(content) {
return content.filter((part) => part.type === "text").map((part) => part.text || "").join("");
}
function wrapWithInputGuardrails(model, guardrails, options) {
const middleware = createInputGuardrailsMiddleware({
inputGuardrails: guardrails,
...options
});
return (0, import_ai.wrapLanguageModel)({
model,
middleware
});
}
function wrapWithOutputGuardrails(model, guardrails, options) {
const middleware = createOutputGuardrailsMiddleware({
outputGuardrails: guardrails,
...options
});
return (0, import_ai.wrapLanguageModel)({
model,
middleware
});
}
function wrapWithGuardrails(model, config) {
const {
inputGuardrails = [],
outputGuardrails = [],
throwOnBlocked,
executionOptions,
onInputBlocked,
onOutputBlocked
} = config;
const middlewares = [];
if (inputGuardrails.length > 0) {
middlewares.push(
createInputGuardrailsMiddleware({
inputGuardrails,
throwOnBlocked,
executionOptions,
onInputBlocked
})
);
}
if (outputGuardrails.length > 0) {
middlewares.push(
createOutputGuardrailsMiddleware({
outputGuardrails,
throwOnBlocked,
executionOptions,
onOutputBlocked
})
);
}
if (middlewares.length === 0) {
return model;
}
return (0, import_ai.wrapLanguageModel)({
model,
middleware: middlewares
});
}
function createInputGuardrailsMiddleware(config) {
const {
inputGuardrails,
executionOptions = {},
onInputBlocked,
throwOnBlocked = false
} = config;
return {
transformParams: async ({
params
}) => {
const enhancedParams = {
...params,
guardrailsBlocked: void 0
};
const promptMessages = Array.isArray(enhancedParams.prompt) ? enhancedParams.prompt : [];
const systemMessage = promptMessages.find((msg) => msg.role === "system");
const system = systemMessage && Array.isArray(systemMessage.content) ? extractTextFromContent(systemMessage.content) : "";
const messages = promptMessages.filter((msg) => msg.role !== "system").map((msg) => ({
role: msg.role,
content: msg.content && Array.isArray(msg.content) ? extractTextFromContent(msg.content) : ""
}));
const prompt = messages.length === 1 && messages[0]?.role === "user" ? messages[0].content : messages.map((m) => m.content).join(" ");
const guardrailContext = {
prompt,
messages,
system,
maxOutputTokens: enhancedParams.maxOutputTokens,
temperature: enhancedParams.temperature
};
const inputResults = await executeInputGuardrails(
inputGuardrails,
guardrailContext,
executionOptions
);
const blockedResults = inputResults.filter((r) => r.tripwireTriggered);
if (blockedResults.length > 0) {
if (onInputBlocked) {
onInputBlocked(blockedResults, guardrailContext);
}
if (throwOnBlocked) {
const { InputBlockedError: InputBlockedError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
const blockedGuardrails = blockedResults.map((r) => ({
name: r.context?.guardrailName || "unknown",
message: r.message || "Blocked",
severity: r.severity || "medium"
}));
throw new InputBlockedError2(blockedGuardrails);
}
enhancedParams.guardrailsBlocked = blockedResults;
}
return enhancedParams;
},
wrapGenerate: async ({
doGenerate,
params
}) => {
const paramsWithGuardrails = params;
if (paramsWithGuardrails.guardrailsBlocked) {
const blockedResults = paramsWithGuardrails.guardrailsBlocked;
const blockedMessage = blockedResults.map((r) => r.message).join(", ");
return {
content: [
{ type: "text", text: `[Input blocked: ${blockedMessage}]` }
],
finishReason: "other",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
warnings: []
};
}
return doGenerate();
},
wrapStream: async ({
doStream,
params
}) => {
const paramsWithGuardrails = params;
if (paramsWithGuardrails.guardrailsBlocked) {
const blockedResults = paramsWithGuardrails.guardrailsBlocked;
const blockedMessage = blockedResults.map((r) => r.message).join(", ");
const stream = new ReadableStream({
start(controller) {
controller.enqueue({
type: "text-delta",
id: "1",
delta: `[Input blocked: ${blockedMessage}]`
});
controller.enqueue({
type: "finish",
finishReason: "other",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
});
controller.close();
}
});
return { stream };
}
return doStream();
}
};
}
function createOutputGuardrailsMiddleware(config) {
const {
outputGuardrails,
executionOptions = {},
onOutputBlocked,
throwOnBlocked = false
} = config;
return {
wrapGenerate: async ({
doGenerate,
params
}) => {
const result = await doGenerate();
const promptMessages = Array.isArray(params.prompt) ? params.prompt : [];
const systemMessage = promptMessages.find((msg) => msg.role === "system");
const system = systemMessage && Array.isArray(systemMessage.content) ? extractTextFromContent(systemMessage.content) : "";
const messages = promptMessages.filter((msg) => msg.role !== "system").map((msg) => ({
role: msg.role,
content: msg.content && Array.isArray(msg.content) ? extractTextFromContent(msg.content) : ""
}));
const prompt = messages.length === 1 && messages[0]?.role === "user" ? messages[0].content : messages.map((m) => m.content).join(" ");
const guardrailContext = {
prompt,
messages,
system,
maxOutputTokens: params.maxOutputTokens,
temperature: params.temperature
};
const outputContext = {
input: guardrailContext,
result
};
const outputResults = await executeOutputGuardrails(
outputGuardrails,
outputContext,
executionOptions
);
const blockedResults = outputResults.filter((r) => r.tripwireTriggered);
if (blockedResults.length > 0) {
if (onOutputBlocked) {
onOutputBlocked(blockedResults, guardrailContext, result);
}
if (throwOnBlocked) {
const { OutputBlockedError: OutputBlockedError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
const blockedGuardrails = blockedResults.map((r) => ({
name: r.context?.guardrailName || "unknown",
message: r.message || "Blocked",
severity: r.severity || "medium"
}));
throw new OutputBlockedError2(blockedGuardrails);
}
}
return result;
},
wrapStream: async ({
doStream,
params
}) => {
const streamResult = await doStream();
let accumulatedText = "";
const blockedChunks = [];
const transformStream = new TransformStream({
transform(chunk) {
if (chunk.type === "text-delta") {
accumulatedText += chunk.delta || "";
}
blockedChunks.push(chunk);
},
async flush(controller) {
const promptMessages = Array.isArray(params.prompt) ? params.prompt : [];
const systemMessage = promptMessages.find(
(msg) => msg.role === "system"
);
const system = systemMessage && Array.isArray(systemMessage.content) ? extractTextFromContent(systemMessage.content) : "";
const messages = promptMessages.filter((msg) => msg.role !== "system").map((msg) => ({
role: msg.role,
content: msg.content && Array.isArray(msg.content) ? extractTextFromContent(msg.content) : ""
}));
const prompt = messages.length === 1 && messages[0]?.role === "user" ? messages[0].content : messages.map((m) => m.content).join(" ");
const guardrailContext = {
prompt,
messages,
system,
maxOutputTokens: params.maxOutputTokens,
temperature: params.temperature
};
const outputContext = {
input: guardrailContext,
result: { text: accumulatedText }
};
const outputResults = await executeOutputGuardrails(
outputGuardrails,
outputContext,
executionOptions
);
const blockedResults = outputResults.filter(
(r) => r.tripwireTriggered
);
if (blockedResults.length > 0) {
if (onOutputBlocked) {
onOutputBlocked(blockedResults, guardrailContext, {
text: accumulatedText
});
}
if (throwOnBlocked) {
controller.error(
new Error(
`Output guardrails blocked response: ${blockedResults.map((r) => r.message).join(", ")}`
)
);
return;
}
controller.enqueue({
type: "text-delta",
id: "1",
delta: "[Output blocked by guardrails]"
});
controller.enqueue({
type: "finish",
finishReason: "error",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
});
} else {
for (const chunk of blockedChunks) {
controller.enqueue(chunk);
}
}
}
});
return {
stream: streamResult.stream.pipeThrough(transformStream)
};
}
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
GuardrailConfigurationError,
GuardrailExecutionError,
GuardrailTimeoutError,
GuardrailValidationError,
GuardrailsError,
InputBlockedError,
MiddlewareError,
OutputBlockedError,
createInputGuardrail,
createInputGuardrailsMiddleware,
createOutputGuardrail,
createOutputGuardrailsMiddleware,
defineInputGuardrail,
defineOutputGuardrail,
executeInputGuardrails,
executeOutputGuardrails,
extractErrorInfo,
isGuardrailsError,
wrapWithGuardrails,
wrapWithInputGuardrails,
wrapWithOutputGuardrails
});