UNPKG

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
"use strict"; 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