openguardrails
Version:
An LLM-based context-aware AI guardrail capable of understanding conversation context for security, safety and data leakage detection.
572 lines • 23.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GuardrailResponseHelper = exports.OpenGuardrails = void 0;
/**
* OpenGuardrails Client
*/
const axios_1 = __importDefault(require("axios"));
const fs = __importStar(require("fs"));
const exceptions_1 = require("./exceptions");
/**
* OpenGuardrails Client - Context-aware AI Guardrails based on LLM
*
* This client provides a simple interface for interacting with the OpenGuardrails API.
* The guardrails use context-aware technology to understand the conversation context and perform security checks.
*
* @example
*
* ```typescript
* const client = new OpenGuardrails({ apiKey: "your-api-key" });
*
* // Detect user input
* const result = await client.checkPrompt("User question");
*
* // Detect output content (based on context)
* const result = await client.checkResponseCtx("User question", "Assistant answer");
*
* // Detect conversation context
* const messages = [
* { role: "user", content: "Question" },
* { role: "assistant", content: "Answer" }
* ];
* const result = await client.checkConversation(messages);
* console.log(result.overall_risk_level); // "high_risk/medium_risk/low_risk/no_risk"s
* console.log(result.suggest_action); // "pass/reject/replace"
* ```
*/
class OpenGuardrails {
constructor(config) {
const { apiKey, baseUrl = 'https://api.openguardrails.com/v1', timeout = 30000, maxRetries = 3 } = config;
this.maxRetries = maxRetries;
this.client = axios_1.default.create({
baseURL: baseUrl,
timeout,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'User-Agent': 'openguardrails-nodejs/3.0.0'
}
});
}
/**
* Create a default response with no risk
*/
createSafeResponse() {
return {
id: 'guardrails-safe-default',
result: {
compliance: {
risk_level: 'no_risk',
categories: []
},
security: {
risk_level: 'no_risk',
categories: []
}
},
overall_risk_level: 'no_risk',
suggest_action: 'pass',
suggest_answer: undefined
};
}
/**
* 检测用户输入的安全性
*
* @param content The user input content to be detected
* @param userId 可选,租户AI应用的用户ID,用于用户级别的风险控制和审计追踪
* @returns 检测结果,格式为:
* ```
* {
* "id": "guardrails-xxx",
* "result": {
* "compliance": {
* "risk_level": "high_risk/medium_risk/low_risk/no_risk",
* "categories": ["violent crime", "sensitive political topics"]
* },
* "security": {
* "risk_level": "high_risk/medium_risk/low_risk/no_risk",
* "categories": ["prompt injection"]
* }
* },
* "overall_risk_level": "high_risk/medium_risk/low_risk/no_risk",
* "suggest_action": "pass/reject/replace",
* "suggest_answer": "Suggested answer content"
* }
* ```
*
* @throws {ValidationError} Invalid input parameters
* @throws {AuthenticationError} Authentication failed
* @throws {RateLimitError} Exceeded rate limit
* @throws {OpenGuardrailsError} Other API errors
*
* @example
* ```typescript
* const result = await client.checkPrompt("I want to learn programming");
* console.log(result.overall_risk_level); // "no_risk"
* console.log(result.suggest_action); // "pass"
* console.log(result.result.compliance.risk_level); // "no_risk"
*
* // Pass user ID for tracking
* const result = await client.checkPrompt("我想学习编程", "user-123");
* ```
*/
async checkPrompt(content, userId) {
// If content is an empty string, return a safe response
if (!content || !content.trim()) {
return this.createSafeResponse();
}
const requestData = {
input: content.trim()
};
if (userId) {
requestData.xxai_app_user_id = userId;
}
return this.makeRequest('POST', '/guardrails/input', requestData);
}
/**
* Detect conversation context security - context-aware detection
*
* This is the core function of the guardrails, which can understand the complete conversation context for security detection.
* Instead of detecting each message separately, it analyzes the security of the entire conversation.
*
* @param messages Conversation message list, containing the complete conversation between user and assistant, each message contains role('user' or 'assistant') and content
* @param model The model name used
* @param userId Optional, the user ID of the tenant AI application, used for user-level risk control and audit tracking
* @returns The detection result based on the conversation context, the format is the same as checkPrompt
*
* @example
* ```typescript
* // Detect the security of the conversation between user and assistant
* const messages = [
* { role: "user", content: "User question" },
* { role: "assistant", content: "Assistant answer" }
* ];
* const result = await client.checkConversation(messages);
* console.log(result.overall_risk_level); // "no_risk"
* console.log(result.suggest_action); // The suggestion based on the conversation context
*
* // Pass user ID for tracking
* const result = await client.checkConversation(messages, 'OpenGuardrails-Text', "user-123");
* ```
*/
async checkConversation(messages, model = 'OpenGuardrails-Text', userId) {
if (!messages || messages.length === 0) {
throw new exceptions_1.ValidationError('Messages cannot be empty');
}
// Validate message format
const validatedMessages = [];
let allEmpty = true; // Mark whether all content is empty
for (const msg of messages) {
if (typeof msg !== 'object' || !msg.role || typeof msg.content !== 'string') {
throw new exceptions_1.ValidationError("Each message must have 'role' and 'content' fields");
}
if (!['user', 'system', 'assistant'].includes(msg.role)) {
throw new exceptions_1.ValidationError('role must be one of: user, system, assistant');
}
const content = msg.content;
// Check if there is non-empty content
if (content && content.trim()) {
allEmpty = false;
// Only add non-empty messages to validatedMessages
validatedMessages.push({ role: msg.role, content });
}
}
// If all messages' content are empty, return a safe response
if (allEmpty) {
return this.createSafeResponse();
}
// Ensure at least one message
if (validatedMessages.length === 0) {
return this.createSafeResponse();
}
const requestData = {
model,
messages: validatedMessages
};
if (userId) {
if (!requestData.extra_body) {
requestData.extra_body = {};
}
requestData.extra_body.xxai_app_user_id = userId;
}
return this.makeRequest('POST', '/guardrails', requestData);
}
/**
* Detect the security of user input and model output - context-aware detection
*
* This is the core function of the guardrails, which can understand the complete conversation context for security detection.
* The guardrails will detect whether the model output is safe and compliant based on the context of the user question.
*
* @param prompt The user input text content, used to make the guardrails understand the context semantic
* @param response The model output text content, the actual detection object
* @param userId Optional, the user ID of the tenant AI application, used for user-level risk control and audit tracking
* @returns The detection result based on the context, the format is the same as checkPrompt
*
* @throws {ValidationError} Invalid input parameters
* @throws {AuthenticationError} Authentication failed
* @throws {RateLimitError} Exceeded rate limit
* @throws {OpenGuardrailsError} Other API errors
*
* @example
* ```typescript
* const result = await client.checkResponseCtx(
* "Cooking",
* "I can teach you how to cook simple home-cooked meals"
* );
* console.log(result.overall_risk_level); // "no_risk"
* console.log(result.suggest_action); // "pass"
*
* // 传递用户ID用于追踪
* const result = await client.checkResponseCtx(
* "Cooking",
* "I can teach you how to cook simple home-cooked meals",
* "user-123"
* );
* ```
*/
async checkResponseCtx(prompt, response, userId) {
// If prompt or response is an empty string, return a safe response
if ((!prompt || !prompt.trim()) && (!response || !response.trim())) {
return this.createSafeResponse();
}
const requestData = {
input: prompt ? prompt.trim() : '',
output: response ? response.trim() : ''
};
if (userId) {
requestData.xxai_app_user_id = userId;
}
return this.makeRequest('POST', '/guardrails/output', requestData);
}
/**
* Encode the image to base64 format
*/
async encodeBase64FromPath(imagePath) {
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
// Get the image from the URL
const response = await axios_1.default.get(imagePath, { responseType: 'arraybuffer' });
return Buffer.from(response.data).toString('base64');
}
else {
// Read the image from the local file
const imageBuffer = await fs.promises.readFile(imagePath);
return imageBuffer.toString('base64');
}
}
/**
* Detect the security of text prompt and image - multi-modal detection
*
* Combine the text semantic and image content for security detection.
*
* @param prompt Text prompt (can be empty)
* @param image The local path or HTTP(S) link of the image file (cannot be empty)
* @param model The model name used, default is the multi-modal model
* @param userId Optional, the user ID of the tenant AI application, used for user-level risk control and audit tracking
* @returns The detection result
*
* @throws {ValidationError} Invalid input parameters
* @throws {AuthenticationError} Authentication failed
* @throws {RateLimitError} Exceeded rate limit
* @throws {OpenGuardrailsError} Other API errors
*
* @example
* ```typescript
* // Detect the local image
* const result = await client.checkPromptImage("Is this image safe?", "/path/to/image.jpg");
* // Detect the network image
* const result = await client.checkPromptImage("", "https://example.com/image.jpg");
* console.log(result.overall_risk_level);
*
* // Pass user ID for tracking
* const result = await client.checkPromptImage("这个图片安全吗?", "/path/to/image.jpg", 'OpenGuardrails-VL', "user-123");
* ```
*/
async checkPromptImage(prompt, image, model = 'OpenGuardrails-VL', userId) {
if (!image) {
throw new exceptions_1.ValidationError('Image path cannot be empty');
}
// Encode the image
let imageBase64;
try {
imageBase64 = await this.encodeBase64FromPath(image);
}
catch (error) {
if (error.code === 'ENOENT') {
throw new exceptions_1.ValidationError(`Image file not found: ${image}`);
}
throw new exceptions_1.OpenGuardrailsError(`Failed to encode image: ${error.message}`);
}
// Build the message content
const content = [];
if (prompt && prompt.trim()) {
content.push({ type: 'text', text: prompt.trim() });
}
content.push({
type: 'image_url',
image_url: { url: `data:image/jpeg;base64,${imageBase64}` }
});
const messages = [
{
role: 'user',
content: content
}
];
const requestData = {
model,
messages
};
if (userId) {
if (!requestData.extra_body) {
requestData.extra_body = {};
}
requestData.extra_body.xxai_app_user_id = userId;
}
return this.makeRequest('POST', '/guardrails', requestData);
}
/**
* Detect the security of text prompt and multiple images - multi-modal detection
*
* Combine the text semantic and multiple image content for security detection.
*
* @param prompt Text prompt (can be empty)
* @param images The local path or HTTP(S) link list of the image file (cannot be empty)
* @param model The model name used, default is the multi-modal model
* @param userId Optional, the user ID of the tenant AI application, used for user-level risk control and audit tracking
* @returns The detection result
*
* @throws {ValidationError} Invalid input parameters
* @throws {AuthenticationError} Authentication failed
* @throws {RateLimitError} Exceeded rate limit
* @throws {OpenGuardrailsError} Other API errors
*
* @example
* ```typescript
* const images = ["/path/to/image1.jpg", "https://example.com/image2.jpg"];
* const result = await client.checkPromptImages("Are these images safe?", images);
* console.log(result.overall_risk_level);
*
* // Pass user ID for tracking
* const result = await client.checkPromptImages("这些图片安全吗?", images, 'OpenGuardrails-VL', "user-123");
* ```
*/
async checkPromptImages(prompt, images, model = 'OpenGuardrails-VL', userId) {
if (!images || images.length === 0) {
throw new exceptions_1.ValidationError('Images list cannot be empty');
}
// Build the message content
const content = [];
if (prompt && prompt.trim()) {
content.push({ type: 'text', text: prompt.trim() });
}
// Encode all images
for (const imagePath of images) {
try {
const imageBase64 = await this.encodeBase64FromPath(imagePath);
content.push({
type: 'image_url',
image_url: { url: `data:image/jpeg;base64,${imageBase64}` }
});
}
catch (error) {
if (error.code === 'ENOENT') {
throw new exceptions_1.ValidationError(`Image file not found: ${imagePath}`);
}
throw new exceptions_1.OpenGuardrailsError(`Failed to encode image ${imagePath}: ${error.message}`);
}
}
const messages = [
{
role: 'user',
content: content
}
];
const requestData = {
model,
messages
};
if (userId) {
if (!requestData.extra_body) {
requestData.extra_body = {};
}
requestData.extra_body.xxai_app_user_id = userId;
}
return this.makeRequest('POST', '/guardrails', requestData);
}
/**
* Check the health status of the API service
*/
async healthCheck() {
return this.makeRequest('GET', '/guardrails/health');
}
/**
* Get the list of available models
*/
async getModels() {
return this.makeRequest('GET', '/guardrails/models');
}
/**
* Send HTTP request
*/
async makeRequest(method, endpoint, data) {
var _a;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
let response;
if (method === 'GET') {
response = await this.client.get(endpoint);
}
else if (method === 'POST') {
response = await this.client.post(endpoint, data);
}
else {
throw new exceptions_1.OpenGuardrailsError(`Unsupported HTTP method: ${method}`);
}
// If it is a guardrails detection request, return the structured response
if (['/guardrails', '/guardrails/input', '/guardrails/output'].includes(endpoint) && typeof response.data === 'object') {
return response.data;
}
return response.data;
}
catch (error) {
if (axios_1.default.isAxiosError(error)) {
const axiosError = error;
if (axiosError.response) {
const status = axiosError.response.status;
if (status === 401) {
throw new exceptions_1.AuthenticationError('Invalid API key');
}
else if (status === 422) {
const errorDetail = ((_a = axiosError.response.data) === null || _a === void 0 ? void 0 : _a.detail) || 'Validation error';
throw new exceptions_1.ValidationError(`Validation error: ${errorDetail}`);
}
else if (status === 429) {
if (attempt < this.maxRetries) {
// Exponential backoff retry
const waitTime = (2 ** attempt) * 1000 + 1000;
await this.sleep(waitTime);
continue;
}
throw new exceptions_1.RateLimitError('Rate limit exceeded');
}
else {
let errorMsg = axiosError.response.data;
try {
const errorData = axiosError.response.data;
errorMsg = (errorData === null || errorData === void 0 ? void 0 : errorData.detail) || errorMsg;
}
catch (_b) { }
throw new exceptions_1.OpenGuardrailsError(`API request failed with status ${status}: ${errorMsg}`);
}
}
else if (axiosError.code === 'ECONNABORTED') {
if (attempt < this.maxRetries) {
await this.sleep(1000);
continue;
}
throw new exceptions_1.OpenGuardrailsError('Request timeout');
}
else {
if (attempt < this.maxRetries) {
await this.sleep(1000);
continue;
}
throw new exceptions_1.NetworkError('Connection error');
}
}
else {
// These errors do not need to be retried
if (error instanceof exceptions_1.AuthenticationError ||
error instanceof exceptions_1.ValidationError ||
error instanceof exceptions_1.RateLimitError) {
throw error;
}
if (attempt < this.maxRetries) {
await this.sleep(1000);
continue;
}
throw new exceptions_1.OpenGuardrailsError(`Unexpected error: ${error}`);
}
}
}
}
/**
* Sleep function
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
exports.OpenGuardrails = OpenGuardrails;
/**
* Extended methods for GuardrailResponse
*/
class GuardrailResponseHelper {
/**
* Check if the content is safe
*/
static isSafe(response) {
return response.suggest_action === 'pass';
}
/**
* Check if the content is blocked
*/
static isBlocked(response) {
return response.suggest_action === 'reject';
}
/**
* Check if there is a substitute
*/
static hasSubstitute(response) {
return response.suggest_action === 'replace' || response.suggest_action === 'reject';
}
/**
* Get all risk categories
*/
static getAllCategories(response) {
const categories = [];
categories.push(...response.result.compliance.categories);
categories.push(...response.result.security.categories);
if (response.result.data) {
categories.push(...response.result.data.categories);
}
return Array.from(new Set(categories)); // Remove duplicates
}
}
exports.GuardrailResponseHelper = GuardrailResponseHelper;
//# sourceMappingURL=client.js.map