AI 工具与上下文管理
@ventostack/ai 提供了 AI 应用开发的基础设施,包括工具注册与执行、权限沙箱、审批流和对话上下文管理。它不直接封装 LLM 提供商 API,而是专注于 AI 工具的安全调用与生命周期管理。
createToolRegistry() 创建工具注册表,负责工具的注册、参数校验、超时执行和 JSON Schema 导出:
import { createToolRegistry } from "@ventostack/ai";
const registry = createToolRegistry();
// 注册工具registry.register({ name: "get_weather", description: "获取指定城市的当前天气", parameters: [ { name: "city", type: "string", description: "城市名称", required: true }, { name: "unit", type: "string", description: "温度单位", required: false }, ], handler: async (params) => { const { city, unit = "celsius" } = params; // 调用天气 API return { city, temperature: 25, unit }; }, requiresApproval: false, riskLevel: "low", timeout: 10_000,});
// 列出所有工具const tools = registry.list();
// 校验参数const validation = registry.validateParams("get_weather", { city: "北京" });if (!validation.valid) { console.error(validation.errors);}
// 执行工具const result = await registry.execute("get_weather", { city: "北京", unit: "celsius" });console.log(result.success, result.result, result.duration);
// 导出 JSON Schema(用于 Function Calling)const schemas = registry.toJSONSchema();createSandbox() 创建权限沙箱,控制工具执行、网络访问和文件访问权限:
import { createSandbox } from "@ventostack/ai";
const sandbox = createSandbox({ allowedTools: ["get_weather", "search_document"], // 允许执行的工具名称列表,为空或不设置时拒绝所有工具(必须显式配置白名单) allowedHosts: ["api.weather.com", "api.example.com"], allowNetworkAccess: true, allowFileRead: true, allowFileWrite: false, workingDirectory: "/app/data", maxExecutionTime: 30_000, maxMemory: 100 * 1024 * 1024,});
// 检查权限if (sandbox.canExecute("get_weather")) { // 允许执行}
if (sandbox.canAccessURL("https://api.weather.com/v1/current")) { // 允许访问该 URL}
if (sandbox.canAccessPath("/app/data/config.json", "read")) { // 允许读取该文件}
// 包装执行(带超时控制)const result = await sandbox.wrapExecution("get_weather", async () => { return await fetchWeather();});安全默认:
allowedTools为空数组或未设置时,canExecute()对所有工具返回 false。必须显式配置白名单才能允许工具执行。
createApprovalManager() 创建审批管理器,用于敏感工具调用的人工审批:
import { createApprovalManager } from "@ventostack/ai";
const approval = createApprovalManager({ defaultTTL: 3_600_000, // 默认有效期 1 小时 onRequest: (req) => { console.log(`新审批请求: ${req.toolName} (${req.id})`); }, onReview: (req) => { console.log(`审批完成: ${req.status}`); },});
// 提交审批请求const request = await approval.request( "delete_database", { table: "users", where: "id = '123'" }, "system",);
// 查询待审批列表const pending = approval.listPending();
// 批准请求(如果请求不存在或已处理,返回 null)const approved = approval.approve(request.id, "admin", "确认执行删除操作");if (!approved) { console.error("审批请求不存在或已过期");}
// 拒绝请求(如果请求不存在或已处理,返回 null)const rejected = approval.reject(request.id, "admin", "风险过高,拒绝执行");if (!rejected) { console.error("审批请求不存在或已过期");}
// 获取状态(不存在返回 null)const status = approval.getStatus(request.id);if (!status) { console.error("审批请求不存在");}
// 清理过期请求const cleaned = approval.cleanup();createContextManager() 创建对话上下文管理器,维护多轮对话的消息历史:
import { createContextManager } from "@ventostack/ai";
const contextManager = createContextManager();
// 创建新对话const ctx = contextManager.create("你是一个专业的技术支持助手");console.log(ctx.conversationId);
// 添加用户消息contextManager.addMessage(ctx.conversationId, "user", "如何创建一个路由?");
// 添加助手回复contextManager.addMessage(ctx.conversationId, "assistant", "使用 createRouter() 创建路由实例...");
// 获取历史消息const history = contextManager.getHistory(ctx.conversationId, 10);
// 设置元数据contextManager.setMetadata(ctx.conversationId, "userId", "user_123");
// 截断消息(只保留最近 20 条)const removed = contextManager.truncate(ctx.conversationId, 20);
// 销毁对话contextManager.destroy(ctx.conversationId);/** 工具定义 */interface ToolDefinition { name: string; description: string; parameters: ToolParameter[]; handler: (params: Record<string, unknown>) => Promise<unknown>; requiresApproval?: boolean; riskLevel?: "low" | "medium" | "high" | "critical"; timeout?: number;}
/** 工具执行结果 */interface ToolExecutionResult { toolName: string; success: boolean; result?: unknown; error?: string; duration: number; timestamp: number;}
/** 工具注册表 */interface ToolRegistry { register(tool: ToolDefinition): void; unregister(name: string): boolean; get(name: string): ToolDefinition | undefined; list(): ToolDefinition[]; execute(name: string, params: Record<string, unknown>): Promise<ToolExecutionResult>; validateParams(name: string, params: Record<string, unknown>): { valid: boolean; errors: string[] }; toJSONSchema(): Array<{ name: string; description: string; parameters: object }>;}
/** 沙箱权限配置 */interface SandboxPermissions { allowedTools?: string[]; allowedHosts?: string[]; maxExecutionTime?: number; maxMemory?: number; allowFileRead?: boolean; allowFileWrite?: boolean; allowNetworkAccess?: boolean; workingDirectory?: string;}
/** 沙箱实例 */interface Sandbox { canExecute(toolName: string): boolean; canAccessURL(url: string): boolean; canAccessPath(filePath: string, mode: "read" | "write"): boolean; wrapExecution<T>(toolName: string, fn: () => Promise<T>): Promise<T>; getPermissions(): SandboxPermissions;}
/** 审批请求 */interface ApprovalRequest { id: string; toolName: string; params: Record<string, unknown>; requestedBy: string; requestedAt: number; status: "pending" | "approved" | "rejected" | "expired"; reviewedBy?: string; reviewedAt?: number; reason?: string; expiresAt: number;}
/** 审批管理器 */interface ApprovalManager { request(toolName: string, params: Record<string, unknown>, requestedBy: string): Promise<ApprovalRequest>; approve(id: string, reviewedBy: string, reason?: string): ApprovalRequest | null; reject(id: string, reviewedBy: string, reason?: string): ApprovalRequest | null; getStatus(id: string): ApprovalRequest | null; listPending(): ApprovalRequest[]; cleanup(): number;}
/** 对话消息 */interface ConversationMessage { role: "system" | "user" | "assistant" | "tool"; content: string; toolCallId?: string; timestamp: number;}
/** 上下文管理器 */interface ContextManager { create(systemPrompt?: string): ConversationContext; get(conversationId: string): ConversationContext | null; addMessage(conversationId: string, role: "user" | "assistant" | "tool", content: string, toolCallId?: string): ConversationMessage | null; getHistory(conversationId: string, limit?: number): ConversationMessage[]; setMetadata(conversationId: string, key: string, value: unknown): boolean; destroy(conversationId: string): boolean; listActive(): string[]; truncate(conversationId: string, maxMessages: number): number;}