diff --git a/config/ai.json b/config/ai.json index 8e9de02..8f8139a 100644 --- a/config/ai.json +++ b/config/ai.json @@ -73,6 +73,29 @@ "surprise" ] }, + "?useReAct": "是否启用ReAct工具调用模式(需要模型支持function calling)", + "useReAct": false, + "?reactConfig": "ReAct配置", + "reactConfig": { + "?maxIterations": "最大思考迭代次数", + "maxIterations": 8, + "?timeout": "ReAct超时时间(毫秒)", + "timeout": 45000, + "?enableThinking": "是否显示思考过程", + "enableThinking": false, + "?toolTimeout": "单个工具执行超时时间(毫秒)", + "toolTimeout": 10000, + "?minConfidence": "最小信心度阈值(0-1),低于此值会继续收集信息", + "minConfidence": 0.7, + "?contextMemoryLimit": "上下文记忆数量限制", + "contextMemoryLimit": 5, + "?enableContextHistory": "是否在迭代中包含聊天历史", + "enableContextHistory": true, + "?maxParallelTools": "单次最大并行工具调用数量", + "maxParallelTools": 5, + "?enableToolCombinations": "是否启用工具组合建议", + "enableToolCombinations": true + }, "?imageConfig": "图像生成配置", "imageConfig": { "?enabled": "是否启用图像生成功能", diff --git a/lib/ai/aiCaller.js b/lib/ai/aiCaller.js index 713fe4c..f1f9c2a 100644 --- a/lib/ai/aiCaller.js +++ b/lib/ai/aiCaller.js @@ -4,6 +4,8 @@ import { getSystemPrompt } from '../../constants/ai/prompts.js'; import SessionManager from "./sessionManager.js"; import UserConfigManager from './userConfigManager.js'; import { imageProcessor } from './imageProcessor.js'; +import ReactEngine from './reactEngine.js'; +import ToolInitializer from './tools/toolInitializer.js'; //ai调用器 class AiCaller { @@ -33,6 +35,13 @@ class AiCaller { await UserConfigManager.init(); + // 初始化ReAct引擎和工具系统 + if (this.config.useReAct) { + await ReactEngine.init(); + await ToolInitializer.initialize(); + logger.info('[crystelf-ai] ReAct模式已启用'); + } + this.isInitialized = true; logger.info('[crystelf-ai] 初始化完成'); } catch (error) { @@ -59,7 +68,14 @@ class AiCaller { try { const userId = e.user_id; const userConfig = await UserConfigManager.getUserConfig(String(userId)); - logger.info(`[crystelf-ai] 用户 ${userId} 使用配置 - 智能多模态: ${userConfig.smartMultimodal}, 多模态启用: ${userConfig.multimodalEnabled}`); + + // 检查是否启用ReAct模式 + if (this.config.useReAct && userConfig.useReAct !== false) { + logger.info(`[crystelf-ai] 用户 ${userId} 使用ReAct模式`); + return await this.callReActAi(prompt, chatHistory, memories, e, originalMessages, imageMessages, userConfig); + } + + logger.info(`[crystelf-ai] 用户 ${userId} 使用传统模式 - 智能多模态: ${userConfig.smartMultimodal}, 多模态启用: ${userConfig.multimodalEnabled}`); if (imageMessages && imageMessages.length > 0) { logger.info(`[crystelf-ai] 检测到图像生成请求,数量: ${imageMessages.length}`); @@ -362,6 +378,57 @@ class AiCaller { return userOpenaiChat; } + /** + * ReAct模式AI调用 + * @param prompt 用户输入 + * @param chatHistory 聊天历史 + * @param memories 记忆 + * @param e 事件对象 + * @param originalMessages 原始消息数组 + * @param imageMessages 图像消息数组 + * @param userConfig 用户配置 + * @returns {Promise} 调用结果 + */ + async callReActAi(prompt, chatHistory, memories, e, originalMessages, imageMessages, userConfig) { + try { + // 构建执行上下文 + const context = { + e, + userConfig, + chatHistory, + memories, + originalMessages, + imageMessages + }; + + // 执行ReAct循环 + const result = await ReactEngine.execute(prompt, context); + + if (result.success) { + return { + success: true, + response: result.responses, + rawResponse: JSON.stringify(result.responses), + thinkingSteps: result.thinkingSteps, + iterations: result.iterations, + duration: result.duration + }; + } else { + return { + success: false, + error: result.error, + response: result.responses + }; + } + } catch (error) { + logger.error(`[crystelf-ai] ReAct模式调用失败: ${error.message}`); + return { + success: false, + error: error.message + }; + } + } + /** * 获取系统提示词 * @param {object} e 上下文事件对象 diff --git a/lib/ai/contextBuilder.js b/lib/ai/contextBuilder.js new file mode 100644 index 0000000..c516b8e --- /dev/null +++ b/lib/ai/contextBuilder.js @@ -0,0 +1,256 @@ +import ConfigControl from '../config/configControl.js'; +import { getSystemPrompt } from '../../constants/ai/prompts.js'; + +/** + * ReAct上下文构建器 + * 为每次迭代构建丰富的上下文信息 + */ +class ContextBuilder { + constructor() { + this.baseContext = null; + } + + /** + * 构建基础上下文(只在第一次构建) + * @param {Object} e - 事件对象 + * @param {Array} memories - 记忆数组 + * @returns {Promise} 基础上下文 + */ + async buildBaseContext(e, memories = []) { + if (this.baseContext) { + return this.baseContext; + } + + const config = await ConfigControl.get(); + const botInfo = { + id: e.bot?.uin || '未知', + name: config?.profile?.nickName || '晶灵', + }; + + const userInfo = { + id: e.user_id || e.sender?.user_id || '未知', + name: e.sender?.card || e.sender?.nickname || '用户', + isMaster: e.isMaster, + }; + + const now = Date.now(); + const date = new Date(now); + const formatDate = date.toLocaleDateString('zh-CN'); + const formatTime = date.toLocaleTimeString('zh-CN'); + + // 获取群聊历史 + const aiConfig = await ConfigControl.get('ai'); + const historyLen = aiConfig?.getChatHistoryLength || 10; + const maxMessageLength = aiConfig?.maxMessageLength || 100; + + let groupChatHistory = ''; + try { + const history = await e.group.getChatHistory(e.message_id, historyLen); + if (history && history.length > 0) { + groupChatHistory = '\n[群聊聊天记录(从旧到新)]\n'; + for (const message of history) { + const msgArr = message.message; + for (const msg of msgArr) { + if (msg.type === 'text') { + let displayText = msg.text; + if (msg.text && msg.text.length > maxMessageLength) { + const omittedChars = msg.text.length - maxMessageLength; + displayText = msg.text.substring(0, maxMessageLength) + `...(省略${omittedChars}字)`; + } + groupChatHistory += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前说过:${displayText}\n`; + } + if (msg.type === 'at') { + if (msg.qq == e.bot.uin) { + groupChatHistory += `[${message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前@了你\n`; + } else { + const atNickname = await e.group.pickMember(msg.qq).nickname || '一个人'; + groupChatHistory += `[${message.sender.user_id == e.bot.uin ? '你' : message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前@了${atNickname},id是${msg.qq}\n`; + } + } + if (msg.type === 'image') { + groupChatHistory += `[${message.sender?.nickname},id:${message.sender?.user_id},seq:${message.message_id}]之前发送了一张图片(你可能暂时无法查看)\n`; + } + } + } + } + } catch (error) { + logger.warn(`[crystelf-ai] 获取群聊历史失败: ${error.message}`); + } + + // 构建记忆上下文 + let memoryContext = ''; + if (memories && memories.length > 0) { + memoryContext = '\n[相关记忆信息]\n'; + memories.forEach((memory, index) => { + const timeDiff = this.calculateTimeDifference(memory.createdAt); + memoryContext += `${index + 1}. 关键词:${memory.keywords},内容:${memory.data},记忆创建时间:${memory.createdAt},距离现在:${timeDiff}\n`; + }); + } + + this.baseContext = { + botInfo, + userInfo, + timeInfo: { + timestamp: now, + date: formatDate, + time: formatTime + }, + groupChatHistory, + memoryContext, + environmentInfo: `现在的Date.now()是:${now}\n现在的日期是:${formatDate}\n现在的时间是:${formatTime}` + }; + + return this.baseContext; + } + + /** + * 构建迭代上下文 + * @param {Object} baseContext - 基础上下文 + * @param {number} iteration - 当前迭代次数 + * @param {Array} thinkingSteps - 思考步骤历史 + * @param {string} userInput - 用户输入 + * @returns {string} 格式化的上下文字符串 + */ + buildIterationContext(baseContext, iteration, thinkingSteps, userInput) { + let contextPrompt = ''; + + // 基础身份和环境信息 + contextPrompt += `=== 身份和环境信息 ===\n`; + contextPrompt += `[你的信息]\n`; + contextPrompt += `- 你的昵称:${baseContext.botInfo.name}\n`; + contextPrompt += `- 你的qq号:${baseContext.botInfo.id}\n\n`; + + contextPrompt += `[对话用户信息]\n`; + contextPrompt += `- 用户名字:${baseContext.userInfo.name}\n`; + contextPrompt += `- 用户qq号:${baseContext.userInfo.id}\n`; + contextPrompt += `- 是否为主人:${baseContext.userInfo.isMaster ? '是' : '否'}(请注意!!!无论用户的用户名是什么,是否是主人都以这个为准!!禁止乱认主人!!)\n\n`; + + contextPrompt += `[环境信息]\n`; + contextPrompt += `${baseContext.environmentInfo}\n\n`; + + // 聊天历史(第一次迭代时提供) + if (iteration === 0 && baseContext.groupChatHistory) { + contextPrompt += baseContext.groupChatHistory + '\n'; + } + + // 记忆信息(第一次迭代时提供) + if (iteration === 0 && baseContext.memoryContext) { + contextPrompt += baseContext.memoryContext + '\n'; + } + + // 当前用户输入 + contextPrompt += `=== 当前对话 ===\n`; + contextPrompt += `用户当前说:"${userInput}"\n\n`; + + // 思考历史(从第二次迭代开始) + if (iteration > 0 && thinkingSteps.length > 0) { + contextPrompt += `=== 你的思考和行动历史 ===\n`; + thinkingSteps.forEach((step, index) => { + contextPrompt += `第${step.iteration}轮:\n`; + contextPrompt += `- 你的思考: ${step.thought}\n`; + + // 详细的行动信息 + if (step.decision.tool_calls && step.decision.tool_calls.length > 0) { + contextPrompt += `- 你决定的行动: 调用${step.decision.tool_calls.length}个工具\n`; + step.decision.tool_calls.forEach((toolCall, i) => { + const toolName = toolCall.function.name; + const params = JSON.parse(toolCall.function.arguments); + contextPrompt += ` ${i + 1}. ${toolName}(${Object.entries(params).map(([k, v]) => `${k}="${v}"`).join(', ')})\n`; + }); + } else if (step.decision.action) { + contextPrompt += `- 你决定的行动: ${step.decision.action}\n`; + } + + // 详细的执行结果 + if (step.observation.multipleTools && step.observation.toolResults) { + contextPrompt += `- 执行结果:\n`; + step.observation.toolResults.forEach((toolResult, i) => { + const status = toolResult.success ? '✓' : '✗'; + contextPrompt += ` ${status} ${toolResult.toolName}: ${toolResult.result.message || toolResult.result.result || '完成'}\n`; + }); + } else { + contextPrompt += `- 执行结果: ${step.observation.message || step.observation.result || '无结果'}\n`; + } + + contextPrompt += `\n`; + }); + } + + // 当前迭代信息 + contextPrompt += `=== 当前状态 ===\n`; + contextPrompt += `这是第${iteration + 1}轮思考\n`; + if (iteration === 0) { + contextPrompt += `这是第一轮,请仔细分析用户需求,确定是否需要收集更多信息\n`; + } else { + contextPrompt += `基于前面的思考和行动结果,请决定下一步\n`; + } + + return contextPrompt; + } + + /** + * 构建人格提示词 + * @returns {Promise} 人格提示词 + */ + async buildPersonalityPrompt() { + try { + const basePrompt = await getSystemPrompt(); + + // 简化版人格提示词,专注于ReAct模式 + const reactPersonality = `你是一个名为晶灵的智能助手,具有以下特征: +1. 性格温和友善,喜欢帮助用户解决问题 +2. 知识渊博,能够回答各种问题 +3. 偶尔会使用一些可爱的表情和语气 +4. 会记住与用户的对话内容,提供个性化的回复 +5. 能够理解中文语境和网络用语 +6. 回复简洁明了,避免过于冗长 + +在ReAct模式下,你需要: +- 仔细思考用户的真实需求 +- 主动使用工具收集必要信息 +- 基于收集的信息给出准确回答 +- 当信息足够时使用find_answer工具结束 +- 当需要更多信息时使用need_more_info工具继续`; + + return reactPersonality; + } catch (error) { + logger.error(`[crystelf-ai] 构建人格提示词失败: ${error.message}`); + return '你是一个友善的智能助手,请帮助用户解决问题。'; + } + } + + /** + * 计算时间差 + * @param {number} pastTime - 过去时间戳 + * @returns {string} 时间差字符串 + */ + calculateTimeDifference(pastTime) { + const now = Date.now(); + const diff = now - pastTime; + + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + + let result = ''; + if (days > 0) { + result += `${days}天`; + } + if (hours > 0) { + result += `${hours}小时`; + } + if (minutes > 0) { + result += `${minutes}分钟`; + } + return result || '刚刚'; + } + + /** + * 重置上下文(新对话时调用) + */ + reset() { + this.baseContext = null; + } +} + +export default new ContextBuilder(); \ No newline at end of file diff --git a/lib/ai/reactEngine.js b/lib/ai/reactEngine.js new file mode 100644 index 0000000..69103be --- /dev/null +++ b/lib/ai/reactEngine.js @@ -0,0 +1,413 @@ +import ConfigControl from '../config/configControl.js'; +import toolRegistry from './tools/toolRegistry.js'; +import AiCaller from './aiCaller.js'; +import ContextBuilder from './contextBuilder.js'; +import MemorySystem from './memorySystem.js'; +import ToolCombinations from './tools/toolCombinations.js'; + +/** + * ReAct引擎 + * Reasoning + Acting 循环 + */ +class ReactEngine { + constructor() { + this.maxIterations = 5; + this.timeout = 30000; + this.toolTimeout = 10000; + this.enableThinking = false; + } + + async init() { + try { + const config = await ConfigControl.get('ai'); + const reactConfig = config?.reactConfig || {}; + + this.maxIterations = reactConfig.maxIterations || 5; + this.timeout = reactConfig.timeout || 30000; + this.toolTimeout = reactConfig.toolTimeout || 10000; + this.enableThinking = reactConfig.enableThinking || false; + + logger.info('[crystelf-ai] ReAct引擎初始化完成'); + } catch (error) { + logger.error(`[crystelf-ai] ReAct引擎初始化失败: ${error.message}`); + } + } + + /** + * 执行ReAct循环 + * @param {string} userInput - 用户输入 + * @param {Object} context - 执行上下文 + * @returns {Promise} 执行结果 + */ + async execute(userInput, context) { + const startTime = Date.now(); + const responseQueue = []; + const thinkingSteps = []; + + // 扩展上下文 + const executionContext = { + ...context, + responseQueue, + startTime, + userInput + }; + + try { + // 构建基础上下文(包含记忆搜索) + const memories = await MemorySystem.searchMemories(context.e.user_id, userInput, 5); + const baseContext = await ContextBuilder.buildBaseContext(context.e, memories); + + let shouldContinue = true; + let iteration = 0; + + while (shouldContinue && iteration < this.maxIterations) { + // 检查超时 + if (Date.now() - startTime > this.timeout) { + logger.warn(`[crystelf-ai] ReAct超时,已迭代 ${iteration} 次`); + break; + } + + // 构建当前迭代的上下文 + const iterationContext = ContextBuilder.buildIterationContext( + baseContext, + iteration, + thinkingSteps, + userInput + ); + + // 思考阶段 + const thought = await this.think(iterationContext, iteration); + + // 决策阶段 + const decision = await this.decide(thought, iterationContext, iteration); + + // 行动阶段 + const observation = await this.act(decision, executionContext); + + // 记录思考步骤 + const step = { + iteration: iteration + 1, + thought, + decision, + observation, + timestamp: Date.now(), + // 新增:AI的推理过程和工具调用详情 + reasoning: decision.reasoning || thought, + toolsUsed: observation.multipleTools ? + observation.toolResults?.map(tr => tr.toolName) || [] : + [decision.action || decision.tool_calls?.[0]?.function?.name].filter(Boolean), + executionTime: observation.duration || 0, + success: observation.success !== false + }; + thinkingSteps.push(step); + + // 检查是否应该结束循环 + if (observation.shouldEnd === true) { + logger.info(`[crystelf-ai] AI决定结束循环,第${iteration + 1}轮完成`); + shouldContinue = false; + } else if (observation.shouldEnd === false) { + logger.info(`[crystelf-ai] AI决定继续收集信息,进入第${iteration + 2}轮`); + shouldContinue = true; + } + + iteration++; + } + + // 如果循环结束但没有响应,添加默认响应 + if (responseQueue.length === 0) { + responseQueue.push({ + type: 'message', + data: iteration >= this.maxIterations ? + '我已经尽力思考了,但可能需要更多信息才能给出完美的回答...' : + '让我来帮助您解决这个问题。', + at: -1, + quote: -1, + recall: false + }); + } + + // 重置上下文构建器 + ContextBuilder.reset(); + + return { + success: true, + responses: responseQueue, + thinkingSteps: this.enableThinking ? thinkingSteps : [], + iterations: iteration, + duration: Date.now() - startTime + }; + + } catch (error) { + logger.error(`[crystelf-ai] ReAct执行失败: ${error.message}`); + + // 重置上下文构建器 + ContextBuilder.reset(); + + // 返回错误响应 + return { + success: false, + error: error.message, + responses: [{ + type: 'message', + data: '抱歉,我在处理您的请求时遇到了问题...', + at: -1, + quote: -1, + recall: false + }], + thinkingSteps: this.enableThinking ? thinkingSteps : [] + }; + } + } + + /** + * 思考阶段 + */ + async think(iterationContext, iteration) { + const personalityPrompt = await ContextBuilder.buildPersonalityPrompt(); + + const thinkingPrompt = `${personalityPrompt} + +${iterationContext} + +=== 思考任务 === +请仔细分析当前情况,思考: +1. 用户的真实需求是什么? +2. 我现在掌握了哪些信息? +3. 还缺少什么关键信息? +4. 下一步应该采取什么行动? + +请简洁明了地描述你的思考过程。`; + + try { + const response = await this.callAI(thinkingPrompt, {}); + return response || '继续分析用户需求'; + } catch (error) { + logger.error(`[crystelf-ai] 思考阶段失败: ${error.message}`); + return '继续分析用户需求'; + } + } + + /** + * 决策阶段 + */ + async decide(thought, iterationContext, iteration) { + const toolSchemas = toolRegistry.getToolSchemas(); + const personalityPrompt = await ContextBuilder.buildPersonalityPrompt(); + + const systemPrompt = `${personalityPrompt} + +${iterationContext} + +=== 决策指南 === +基于你的思考:"${thought}" + +你现在需要决定下一步行动。可用工具: +${JSON.stringify(toolSchemas, null, 2)} + +重要规则: +1. 你可以同时调用多个工具来提高效率,例如同时搜索记忆和获取聊天历史 +2. 如果你已经有足够信息回答用户,使用 find_answer 工具表示准备结束 +3. 如果需要更多信息,可以先调用 need_more_info 说明需求,然后调用相应的信息收集工具 +4. 使用具体的工具来收集信息(如 search_memory, get_chat_history 等) +5. 使用 send_message 等工具来回复用户 +6. 优先考虑用户的实际需求,不要过度收集信息 +7. 合理利用并行工具调用,但避免调用冲突的工具 + +${ToolCombinations.getAllCombinationsDescription()} + +请调用最合适的工具(可以是多个)。记住: +- 优先使用推荐的工具组合来提高效率 +- 可以根据具体情况调整组合中的参数 +- 避免调用冲突的工具(如同时使用find_answer和need_more_info)`; + + const messages = [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: '请根据当前思考决定下一步行动。' } + ]; + + try { + // 调用支持工具调用的AI + const response = await this.callAIWithTools(messages, toolSchemas, {}); + return response; + } catch (error) { + logger.error(`[crystelf-ai] 决策阶段失败: ${error.message}`); + return { + action: 'send_message', + parameters: { + content: '我在思考过程中遇到了问题,请稍后再试。' + } + }; + } + } + + /** + * 行动阶段 - 支持多工具并行调用 + */ + async act(decision, context) { + try { + if (decision.tool_calls && decision.tool_calls.length > 0) { + // 处理多个工具调用 + const toolResults = []; + const toolPromises = []; + + logger.info(`[crystelf-ai] 准备并行执行 ${decision.tool_calls.length} 个工具`); + + // 并行执行所有工具调用 + for (const toolCall of decision.tool_calls) { + const toolName = toolCall.function.name; + const parameters = JSON.parse(toolCall.function.arguments); + + logger.info(`[crystelf-ai] 调用工具: ${toolName}, 参数: ${JSON.stringify(parameters)}`); + + const toolPromise = toolRegistry.executeTool(toolName, parameters, context) + .then(result => ({ + toolName, + parameters, + result, + toolCallId: toolCall.id + })) + .catch(error => ({ + toolName, + parameters, + result: { + success: false, + message: `工具执行失败: ${error.message}` + }, + toolCallId: toolCall.id, + error: error.message + })); + + toolPromises.push(toolPromise); + } + + // 等待所有工具执行完成 + const results = await Promise.all(toolPromises); + + // 整合结果 + let shouldEnd = null; + let hasError = false; + const messages = []; + + for (const { toolName, parameters, result, toolCallId, error } of results) { + toolResults.push({ + toolName, + parameters, + result, + toolCallId, + success: !error + }); + + if (error) { + hasError = true; + messages.push(`工具 ${toolName} 执行失败: ${error}`); + } else { + messages.push(`工具 ${toolName} 执行成功: ${result.message || result.result || '完成'}`); + + // 检查循环控制 + if (result.shouldEnd !== undefined) { + shouldEnd = result.shouldEnd; + } + } + } + + return { + success: !hasError, + message: messages.join('\n'), + toolResults, + shouldEnd, + multipleTools: true, + toolCount: decision.tool_calls.length + }; + + } else if (decision.action) { + // 处理单个直接行动 + const result = await toolRegistry.executeTool(decision.action, decision.parameters, context); + return { + ...result, + multipleTools: false, + toolCount: 1 + }; + } else { + return { + success: false, + message: '未识别的决策格式', + multipleTools: false, + toolCount: 0 + }; + } + } catch (error) { + logger.error(`[crystelf-ai] 行动阶段失败: ${error.message}`); + return { + success: false, + message: `执行行动失败: ${error.message}`, + multipleTools: false, + toolCount: 0 + }; + } + } + + /** + * 调用AI(文本模式) + */ + async callAI(prompt, context) { + // 创建临时的事件对象用于AI调用 + const tempE = { + user_id: 'react_system', + bot: { uin: 'system' }, + group_id: 'system' + }; + + const result = await AiCaller.callTextAi(prompt, [], [], tempE); + + if (result.success) { + return result.response; + } else { + throw new Error(result.error || 'AI调用失败'); + } + } + + /** + * 调用AI(工具调用模式) + */ + async callAIWithTools(messages, tools, context) { + // 使用系统配置调用AI + const config = await ConfigControl.get('ai'); + const apiCaller = AiCaller.getUserOpenaiInstance('system', config); + + try { + const completion = await apiCaller.openai.chat.completions.create({ + model: config.modelType, + messages: messages, + tools: tools, + tool_choice: 'auto', + temperature: config.temperature || 0.7, + parallel_tool_calls: true // 启用并行工具调用 + }); + + const message = completion.choices[0].message; + + if (message.tool_calls && message.tool_calls.length > 0) { + logger.info(`[crystelf-ai] AI决定调用 ${message.tool_calls.length} 个工具: ${message.tool_calls.map(tc => tc.function.name).join(', ')}`); + return { + tool_calls: message.tool_calls, + finished: false, + reasoning: message.content || '执行工具调用' + }; + } else { + return { + action: 'send_message', + parameters: { + content: message.content + }, + finished: true, + reasoning: message.content + }; + } + } catch (error) { + logger.error(`[crystelf-ai] 工具调用AI失败: ${error.message}`); + throw error; + } + } +} + +export default new ReactEngine(); \ No newline at end of file diff --git a/lib/ai/responseHandler.js b/lib/ai/responseHandler.js index a432bfa..5974b3d 100644 --- a/lib/ai/responseHandler.js +++ b/lib/ai/responseHandler.js @@ -20,6 +20,13 @@ class ResponseHandler { */ async processResponse(rawResponse, userMessage, groupId,user_id) { try { + // 检查是否是ReAct模式的响应 + if (Array.isArray(rawResponse)) { + logger.info('[crystelf-ai] 处理ReAct模式响应'); + return await this.processReActResponse(rawResponse, userMessage, groupId, user_id); + } + + // 传统模式响应处理 const parsedResponse = this.parseAiResponse(rawResponse); if (!parsedResponse.success) { logger.error(`[crystelf-ai] 解析AI响应失败: ${parsedResponse.error}`); @@ -43,6 +50,32 @@ class ResponseHandler { } } + /** + * 处理ReAct模式响应 + * @param responses ReAct响应数组 + * @param userMessage 用户消息 + * @param groupId 群聊id + * @param user_id 用户id + * @returns {Promise} 处理后的消息数组 + */ + async processReActResponse(responses, userMessage, groupId, user_id) { + const processedMessages = []; + + for (const response of responses) { + // ReAct响应已经是标准格式,直接处理 + const processedMessage = await this.processMessage(response, userMessage, groupId, user_id); + if (processedMessage) { + processedMessages.push(processedMessage); + } + } + + if (processedMessages.length === 0) { + return this.createDefaultResponse(); + } + + return processedMessages; + } + parseAiResponse(response) { try { const cleanResponse = this.cleanResponseText(response); diff --git a/lib/ai/tools/baseTool.js b/lib/ai/tools/baseTool.js new file mode 100644 index 0000000..27e20f3 --- /dev/null +++ b/lib/ai/tools/baseTool.js @@ -0,0 +1,53 @@ +/** + * 基础工具类 + * 所有工具都应该继承这个类 + */ +class BaseTool { + constructor(name, description, parameters = {}) { + this.name = name; + this.description = description; + this.parameters = parameters; + } + + /** + * 执行工具 + * @param {Object} params - 工具参数 + * @param {Object} context - 执行上下文 + * @returns {Promise} 执行结果 + */ + async execute(params, context) { + throw new Error('子类必须实现 execute 方法'); + } + + /** + * 验证参数 + * @param {Object} params - 参数对象 + * @returns {boolean} 是否有效 + */ + validateParams(params) { + // 基础参数验证逻辑 + for (const [key, schema] of Object.entries(this.parameters.properties || {})) { + if (schema.required && !params[key]) { + throw new Error(`缺少必需参数: ${key}`); + } + } + return true; + } + + /** + * 获取工具的JSON Schema定义 + * @returns {Object} JSON Schema + */ + getSchema() { + return { + type: 'function', + function: { + name: this.name, + description: this.description, + parameters: this.parameters + } + }; + } +} + +export default BaseTool; \ No newline at end of file diff --git a/lib/ai/tools/contentTool.js b/lib/ai/tools/contentTool.js new file mode 100644 index 0000000..a90cc87 --- /dev/null +++ b/lib/ai/tools/contentTool.js @@ -0,0 +1,140 @@ +import BaseTool from './baseTool.js'; +import Renderer from '../renderer.js'; + +/** + * 渲染代码工具 + */ +class RenderCodeTool extends BaseTool { + constructor() { + super( + 'render_code', + '将代码渲染为高亮图片', + { + type: 'object', + properties: { + code: { + type: 'string', + description: '要渲染的代码内容' + }, + language: { + type: 'string', + description: '编程语言类型,如javascript、python、java等' + } + }, + required: ['code', 'language'] + } + ); + } + + async execute(params, context) { + const { code, language } = params; + const { responseQueue } = context; + + const codeObj = { + type: 'code', + data: code, + language: language + }; + + responseQueue.push(codeObj); + + return { + success: true, + message: `已渲染${language}代码块` + }; + } +} + +/** + * 渲染Markdown工具 + */ +class RenderMarkdownTool extends BaseTool { + constructor() { + super( + 'render_markdown', + '将Markdown内容渲染为图片', + { + type: 'object', + properties: { + markdown: { + type: 'string', + description: '要渲染的Markdown内容' + } + }, + required: ['markdown'] + } + ); + } + + async execute(params, context) { + const { markdown } = params; + const { responseQueue } = context; + + const markdownObj = { + type: 'markdown', + data: markdown + }; + + responseQueue.push(markdownObj); + + return { + success: true, + message: '已渲染Markdown内容' + }; + } +} + +/** + * 生成图片工具 + */ +class GenerateImageTool extends BaseTool { + constructor() { + super( + 'generate_image', + '根据描述生成图片', + { + type: 'object', + properties: { + prompt: { + type: 'string', + description: '图片生成的描述文本' + }, + style: { + type: 'string', + enum: ['natural', 'vivid'], + description: '图片风格,默认natural', + default: 'natural' + }, + size: { + type: 'string', + enum: ['1024x1024', '1792x1024', '1024x1792'], + description: '图片尺寸,默认1024x1024', + default: '1024x1024' + } + }, + required: ['prompt'] + } + ); + } + + async execute(params, context) { + const { prompt, style = 'natural', size = '1024x1024' } = params; + const { responseQueue } = context; + + const imageObj = { + type: 'image', + data: prompt, + style: style, + size: size + }; + + responseQueue.push(imageObj); + + return { + success: true, + message: `已生成图片: ${prompt.substring(0, 30)}...` + }; + } +} + +export { RenderCodeTool, RenderMarkdownTool, GenerateImageTool }; \ No newline at end of file diff --git a/lib/ai/tools/controlTool.js b/lib/ai/tools/controlTool.js new file mode 100644 index 0000000..ee63180 --- /dev/null +++ b/lib/ai/tools/controlTool.js @@ -0,0 +1,98 @@ +import BaseTool from './baseTool.js'; + +/** + * 找到答案工具 - 结束ReAct循环 + */ +class FindAnswerTool extends BaseTool { + constructor() { + super( + 'find_answer', + '当你已经收集到足够信息并准备给出最终回答时使用此工具', + { + type: 'object', + properties: { + confidence: { + type: 'number', + description: '回答的信心程度(0-1),1表示非常确信', + minimum: 0, + maximum: 1 + }, + summary: { + type: 'string', + description: '简要总结你收集到的关键信息' + }, + ready_to_respond: { + type: 'boolean', + description: '是否准备好给出最终回答', + default: true + } + }, + required: ['confidence', 'summary'] + } + ); + } + + async execute(params, context) { + const { confidence, summary, ready_to_respond = true } = params; + + logger.info(`[crystelf-ai] AI决定结束循环 - 信心度: ${confidence}, 摘要: ${summary}`); + + return { + success: true, + message: `已收集足够信息,准备回答`, + shouldEnd: true, + confidence, + summary, + readyToRespond: ready_to_respond + }; + } +} + +/** + * 需要更多信息工具 - 继续ReAct循环 + */ +class NeedMoreInfoTool extends BaseTool { + constructor() { + super( + 'need_more_info', + '当你需要更多信息才能给出满意回答时使用此工具', + { + type: 'object', + properties: { + missing_info: { + type: 'string', + description: '描述还缺少什么关键信息' + }, + next_action_plan: { + type: 'string', + description: '下一步计划采取什么行动获取信息' + }, + urgency: { + type: 'string', + enum: ['low', 'medium', 'high'], + description: '获取这些信息的紧急程度', + default: 'medium' + } + }, + required: ['missing_info', 'next_action_plan'] + } + ); + } + + async execute(params, context) { + const { missing_info, next_action_plan, urgency = 'medium' } = params; + + logger.info(`[crystelf-ai] AI需要更多信息 - 缺失: ${missing_info}, 计划: ${next_action_plan}`); + + return { + success: true, + message: `需要更多信息: ${missing_info}`, + shouldEnd: false, + missingInfo: missing_info, + nextPlan: next_action_plan, + urgency + }; + } +} + +export { FindAnswerTool, NeedMoreInfoTool }; \ No newline at end of file diff --git a/lib/ai/tools/memoryTool.js b/lib/ai/tools/memoryTool.js new file mode 100644 index 0000000..540b636 --- /dev/null +++ b/lib/ai/tools/memoryTool.js @@ -0,0 +1,105 @@ +import BaseTool from './baseTool.js'; +import MemorySystem from '../memorySystem.js'; + +/** + * 存储记忆工具 + */ +class StoreMemoryTool extends BaseTool { + constructor() { + super( + 'store_memory', + '存储重要的用户信息到记忆系统', + { + type: 'object', + properties: { + content: { + type: 'string', + description: '要存储的记忆内容,应该简洁明了' + }, + keywords: { + type: 'array', + items: { + type: 'string' + }, + description: '记忆的关键词数组,用于后续检索' + }, + importance: { + type: 'number', + description: '记忆重要性等级(1-10),默认5', + minimum: 1, + maximum: 10, + default: 5 + }, + expire_days: { + type: 'number', + description: '记忆保存天数,默认30天', + default: 30 + } + }, + required: ['content', 'keywords'] + } + ); + } + + async execute(params, context) { + const { content, keywords, importance = 5, expire_days = 30 } = params; + const { e } = context; + + try { + // 验证记忆内容的合法性 + if (!this.isValidMemoryContent(content)) { + return { + success: false, + message: '记忆内容不符合存储规范' + }; + } + + const memoryId = await MemorySystem.addMemory( + e.group_id, + e.user_id, + content, + keywords, + expire_days + ); + + if (memoryId) { + return { + success: true, + message: `已存储记忆: ${content.substring(0, 30)}...`, + memoryId + }; + } else { + return { + success: false, + message: '记忆存储失败' + }; + } + } catch (error) { + return { + success: false, + message: `存储记忆失败: ${error.message}` + }; + } + } + + /** + * 验证记忆内容是否合法 + * @param {string} content - 记忆内容 + * @returns {boolean} 是否合法 + */ + isValidMemoryContent(content) { + // 不允许存储的内容类型 + const forbiddenPatterns = [ + /主人/i, + /叫.*主人/i, + /角色扮演/i, + /催眠/i, + /修改.*人设/i, + /更改.*提示词/i + ]; + + return !forbiddenPatterns.some(pattern => pattern.test(content)); + } +} + +export { StoreMemoryTool }; \ No newline at end of file diff --git a/lib/ai/tools/messageTool.js b/lib/ai/tools/messageTool.js new file mode 100644 index 0000000..f8ba500 --- /dev/null +++ b/lib/ai/tools/messageTool.js @@ -0,0 +1,148 @@ +import BaseTool from './baseTool.js'; + +/** + * 发送消息工具 + * 整合了原来的message、at、quote功能 + */ +class SendMessageTool extends BaseTool { + constructor() { + super( + 'send_message', + '发送消息给用户,支持@用户和引用消息', + { + type: 'object', + properties: { + content: { + type: 'string', + description: '消息内容' + }, + at_user: { + type: 'string', + description: '要@的用户QQ号,不需要@时不传此参数' + }, + quote_message_id: { + type: 'string', + description: '要引用的消息ID,不需要引用时不传此参数' + }, + recall_after: { + type: 'number', + description: '多少秒后撤回消息,不需要撤回时不传此参数' + } + }, + required: ['content'] + } + ); + } + + async execute(params, context) { + const { content, at_user, quote_message_id, recall_after } = params; + const { e, responseQueue } = context; + + // 构建消息对象,兼容原来的格式 + const messageObj = { + type: 'message', + data: content, + at: at_user ? parseInt(at_user) : -1, + quote: quote_message_id ? parseInt(quote_message_id) : -1, + recall: recall_after ? true : false + }; + + // 添加到响应队列 + responseQueue.push(messageObj); + + return { + success: true, + message: `已发送消息: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`, + messageId: Date.now().toString() + }; + } +} + +/** + * 发送表情包工具 + */ +class SendMemeTool extends BaseTool { + constructor() { + super( + 'send_meme', + '发送表情包', + { + type: 'object', + properties: { + emotion: { + type: 'string', + enum: ['angry', 'bye', 'confused', 'default', 'good', 'goodmorning', 'goodnight', 'happy', 'sad', 'shy', 'sorry', 'surprise'], + description: '表情包情绪类型' + } + }, + required: ['emotion'] + } + ); + } + + async execute(params, context) { + const { emotion } = params; + const { responseQueue } = context; + + const memeObj = { + type: 'meme', + data: emotion + }; + + responseQueue.push(memeObj); + + return { + success: true, + message: `已发送${emotion}表情包` + }; + } +} + +/** + * 戳一戳工具 + */ +class PokeTool extends BaseTool { + constructor() { + super( + 'poke_user', + '戳一戳指定用户', + { + type: 'object', + properties: { + user_id: { + type: 'string', + description: '要戳的用户QQ号' + } + }, + required: ['user_id'] + } + ); + } + + async execute(params, context) { + const { user_id } = params; + const { e, responseQueue } = context; + + // 不能戳自己 + if (user_id === e.bot.uin.toString()) { + return { + success: false, + message: '不能戳自己' + }; + } + + const pokeObj = { + type: 'poke', + id: parseInt(user_id) + }; + + responseQueue.push(pokeObj); + + return { + success: true, + message: `已戳一戳用户 ${user_id}` + }; + } +} + +export { SendMessageTool, SendMemeTool, PokeTool }; \ No newline at end of file diff --git a/lib/ai/tools/retrievalTool.js b/lib/ai/tools/retrievalTool.js new file mode 100644 index 0000000..f774912 --- /dev/null +++ b/lib/ai/tools/retrievalTool.js @@ -0,0 +1,141 @@ +import BaseTool from './baseTool.js'; +import MemorySystem from '../memorySystem.js'; + +/** + * 搜索记忆工具 + */ +class SearchMemoryTool extends BaseTool { + constructor() { + super( + 'search_memory', + '搜索用户的历史记忆和对话信息', + { + type: 'object', + properties: { + query: { + type: 'string', + description: '搜索关键词或问题' + }, + limit: { + type: 'number', + description: '返回结果数量限制,默认5条', + default: 5 + } + }, + required: ['query'] + } + ); + } + + async execute(params, context) { + const { query, limit = 5 } = params; + const { e } = context; + + try { + const memories = await MemorySystem.searchMemories(e.user_id, query, limit); + + if (!memories || memories.length === 0) { + return { + success: true, + message: '未找到相关记忆', + memories: [] + }; + } + + const formattedMemories = memories.map(memory => ({ + content: memory.data, + keywords: memory.keywords, + relevance: memory.relevance, + createdAt: new Date(memory.createdAt).toLocaleString() + })); + + return { + success: true, + message: `找到 ${memories.length} 条相关记忆`, + memories: formattedMemories + }; + } catch (error) { + return { + success: false, + message: `搜索记忆失败: ${error.message}`, + memories: [] + }; + } + } +} + +/** + * 获取聊天历史工具 + */ +class GetChatHistoryTool extends BaseTool { + constructor() { + super( + 'get_chat_history', + '获取最近的聊天历史记录', + { + type: 'object', + properties: { + count: { + type: 'number', + description: '获取消息数量,默认10条', + default: 10 + }, + include_bot: { + type: 'boolean', + description: '是否包含机器人自己的消息,默认true', + default: true + } + } + } + ); + } + + async execute(params, context) { + const { count = 10, include_bot = true } = params; + const { e } = context; + + try { + const history = await e.group.getChatHistory(e.message_id, count); + + if (!history || history.length === 0) { + return { + success: true, + message: '未找到聊天历史', + history: [] + }; + } + + const formattedHistory = history + .filter(msg => include_bot || msg.sender?.user_id !== e.bot.uin) + .map(msg => { + const textContent = msg.message + ?.filter(m => m.type === 'text') + ?.map(m => m.text) + ?.join('') || ''; + + return { + user_id: msg.sender?.user_id, + nickname: msg.sender?.nickname, + content: textContent, + timestamp: new Date(msg.time * 1000).toLocaleString(), + message_id: msg.message_id + }; + }) + .filter(msg => msg.content.trim() !== ''); + + return { + success: true, + message: `获取到 ${formattedHistory.length} 条聊天记录`, + history: formattedHistory + }; + } catch (error) { + return { + success: false, + message: `获取聊天历史失败: ${error.message}`, + history: [] + }; + } + } +} + +export { SearchMemoryTool, GetChatHistoryTool }; \ No newline at end of file diff --git a/lib/ai/tools/toolCombinations.js b/lib/ai/tools/toolCombinations.js new file mode 100644 index 0000000..84eea31 --- /dev/null +++ b/lib/ai/tools/toolCombinations.js @@ -0,0 +1,174 @@ +/** + * 工具组合建议系统 + * 为AI提供常用的工具组合模式 + */ +class ToolCombinations { + constructor() { + this.combinations = new Map(); + this.initializeCombinations(); + } + + initializeCombinations() { + // 信息收集组合 + this.combinations.set('gather_user_info', { + name: '收集用户信息', + tools: ['search_memory', 'get_chat_history'], + description: '同时搜索用户记忆和获取聊天历史', + useCase: '当需要了解用户背景信息时', + example: { + search_memory: { query: '用户偏好', limit: 5 }, + get_chat_history: { count: 10, include_bot: true } + } + }); + + // 信息收集并回复组合 + this.combinations.set('search_and_respond', { + name: '搜索并回复', + tools: ['search_memory', 'send_message'], + description: '搜索相关信息后立即回复用户', + useCase: '当能够基于记忆直接回答时', + example: { + search_memory: { query: '相关关键词', limit: 3 }, + send_message: { content: '基于搜索结果的回复' } + } + }); + + // 存储并回复组合 + this.combinations.set('store_and_respond', { + name: '存储并回复', + tools: ['store_memory', 'send_message'], + description: '存储重要信息并回复用户', + useCase: '当用户提供新的重要信息时', + example: { + store_memory: { content: '重要信息', keywords: ['关键词'] }, + send_message: { content: '我记住了这个信息' } + } + }); + + // 多渠道信息收集 + this.combinations.set('comprehensive_search', { + name: '全面信息收集', + tools: ['search_memory', 'get_chat_history', 'need_more_info'], + description: '全面收集信息并说明还需要什么', + useCase: '处理复杂问题时', + example: { + search_memory: { query: '相关主题', limit: 5 }, + get_chat_history: { count: 15, include_bot: true }, + need_more_info: { missing_info: '具体需求', next_action_plan: '下一步计划' } + } + }); + + // 内容生成组合 + this.combinations.set('generate_content', { + name: '生成内容', + tools: ['render_code', 'send_message'], + description: '渲染代码并发送说明', + useCase: '当需要展示代码示例时', + example: { + render_code: { code: '示例代码', language: 'javascript' }, + send_message: { content: '这是相关的代码示例' } + } + }); + + // 完整回复组合 + this.combinations.set('complete_response', { + name: '完整回复', + tools: ['find_answer', 'send_message', 'send_meme'], + description: '给出完整回答并结束对话', + useCase: '当有足够信息给出最终回答时', + example: { + find_answer: { confidence: 0.8, summary: '信息摘要' }, + send_message: { content: '详细回答' }, + send_meme: { emotion: 'happy' } + } + }); + } + + /** + * 获取推荐的工具组合 + * @param {string} scenario - 场景描述 + * @returns {Array} 推荐的组合 + */ + getRecommendations(scenario) { + const recommendations = []; + + // 基于场景关键词匹配 + const scenarioLower = scenario.toLowerCase(); + + if (scenarioLower.includes('记忆') || scenarioLower.includes('历史')) { + recommendations.push(this.combinations.get('gather_user_info')); + } + + if (scenarioLower.includes('回答') || scenarioLower.includes('回复')) { + recommendations.push(this.combinations.get('search_and_respond')); + } + + if (scenarioLower.includes('存储') || scenarioLower.includes('记住')) { + recommendations.push(this.combinations.get('store_and_respond')); + } + + if (scenarioLower.includes('复杂') || scenarioLower.includes('详细')) { + recommendations.push(this.combinations.get('comprehensive_search')); + } + + if (scenarioLower.includes('代码') || scenarioLower.includes('示例')) { + recommendations.push(this.combinations.get('generate_content')); + } + + if (scenarioLower.includes('完成') || scenarioLower.includes('结束')) { + recommendations.push(this.combinations.get('complete_response')); + } + + return recommendations; + } + + /** + * 获取所有组合的描述 + * @returns {string} 格式化的组合描述 + */ + getAllCombinationsDescription() { + let description = '=== 常用工具组合模式 ===\n\n'; + + for (const [key, combo] of this.combinations) { + description += `${combo.name}:\n`; + description += `- 工具: ${combo.tools.join(', ')}\n`; + description += `- 说明: ${combo.description}\n`; + description += `- 适用: ${combo.useCase}\n\n`; + } + + return description; + } + + /** + * 验证工具组合的兼容性 + * @param {Array} toolNames - 工具名称数组 + * @returns {Object} 验证结果 + */ + validateCombination(toolNames) { + const conflicts = []; + const warnings = []; + + // 检查冲突的工具组合 + if (toolNames.includes('find_answer') && toolNames.includes('need_more_info')) { + conflicts.push('find_answer 和 need_more_info 不能同时使用'); + } + + if (toolNames.includes('send_message') && toolNames.length === 1) { + warnings.push('单独使用 send_message 可能缺少信息收集'); + } + + // 检查工具数量 + if (toolNames.length > 5) { + warnings.push('同时调用过多工具可能影响性能'); + } + + return { + isValid: conflicts.length === 0, + conflicts, + warnings, + toolCount: toolNames.length + }; + } +} + +export default new ToolCombinations(); \ No newline at end of file diff --git a/lib/ai/tools/toolInitializer.js b/lib/ai/tools/toolInitializer.js new file mode 100644 index 0000000..587c5c9 --- /dev/null +++ b/lib/ai/tools/toolInitializer.js @@ -0,0 +1,84 @@ +import toolRegistry from './toolRegistry.js'; +import { SendMessageTool, SendMemeTool, PokeTool } from './messageTool.js'; +import { SearchMemoryTool, GetChatHistoryTool } from './retrievalTool.js'; +import { StoreMemoryTool } from './memoryTool.js'; +import { RenderCodeTool, RenderMarkdownTool, GenerateImageTool } from './contentTool.js'; +import { FindAnswerTool, NeedMoreInfoTool } from './controlTool.js'; + +/** + * 工具初始化器 + * 负责注册所有可用的工具 + */ +class ToolInitializer { + static async initialize() { + try { + // 清空现有工具 + toolRegistry.clear(); + + // 注册基础交互工具 + toolRegistry.register(new SendMessageTool()); + toolRegistry.register(new SendMemeTool()); + toolRegistry.register(new PokeTool()); + + // 注册信息检索工具 + toolRegistry.register(new SearchMemoryTool()); + toolRegistry.register(new GetChatHistoryTool()); + + // 注册记忆管理工具 + toolRegistry.register(new StoreMemoryTool()); + + // 注册内容生成工具 + toolRegistry.register(new RenderCodeTool()); + toolRegistry.register(new RenderMarkdownTool()); + toolRegistry.register(new GenerateImageTool()); + + // 注册循环控制工具 + toolRegistry.register(new FindAnswerTool()); + toolRegistry.register(new NeedMoreInfoTool()); + + const toolCount = toolRegistry.getToolList().length; + logger.info(`[crystelf-ai] 工具初始化完成,共注册 ${toolCount} 个工具`); + + return true; + } catch (error) { + logger.error(`[crystelf-ai] 工具初始化失败: ${error.message}`); + return false; + } + } + + /** + * 获取工具统计信息 + */ + static getToolStats() { + const tools = toolRegistry.getToolList(); + const categories = { + message: 0, + retrieval: 0, + memory: 0, + content: 0, + other: 0 + }; + + tools.forEach(tool => { + if (tool.name.includes('message') || tool.name.includes('meme') || tool.name.includes('poke')) { + categories.message++; + } else if (tool.name.includes('search') || tool.name.includes('get')) { + categories.retrieval++; + } else if (tool.name.includes('memory')) { + categories.memory++; + } else if (tool.name.includes('render') || tool.name.includes('generate')) { + categories.content++; + } else { + categories.other++; + } + }); + + return { + total: tools.length, + categories, + tools: tools.map(t => ({ name: t.name, description: t.description })) + }; + } +} + +export default ToolInitializer; \ No newline at end of file diff --git a/lib/ai/tools/toolRegistry.js b/lib/ai/tools/toolRegistry.js new file mode 100644 index 0000000..b0da40b --- /dev/null +++ b/lib/ai/tools/toolRegistry.js @@ -0,0 +1,101 @@ +/** + * 工具注册表 + * 管理所有可用的工具 + */ +class ToolRegistry { + constructor() { + this.tools = new Map(); + this.toolSchemas = []; + } + + /** + * 注册工具 + * @param {BaseTool} tool - 工具实例 + */ + register(tool) { + this.tools.set(tool.name, tool); + this.toolSchemas.push(tool.getSchema()); + logger.info(`[crystelf-ai] 注册工具: ${tool.name}`); + } + + /** + * 获取工具 + * @param {string} name - 工具名称 + * @returns {BaseTool|null} 工具实例 + */ + getTool(name) { + return this.tools.get(name) || null; + } + + /** + * 获取所有工具的Schema + * @returns {Array} 工具Schema数组 + */ + getToolSchemas() { + return this.toolSchemas; + } + + /** + * 执行工具 + * @param {string} name - 工具名称 + * @param {Object} params - 参数 + * @param {Object} context - 执行上下文 + * @returns {Promise} 执行结果 + */ + async executeTool(name, params, context) { + const tool = this.getTool(name); + if (!tool) { + throw new Error(`工具不存在: ${name}`); + } + + try { + tool.validateParams(params); + const startTime = Date.now(); + const result = await tool.execute(params, context); + const duration = Date.now() - startTime; + + logger.info(`[crystelf-ai] 工具 ${name} 执行完成,耗时: ${duration}ms`); + + return { + success: true, + result, + toolName: name, + duration, + timestamp: Date.now() + }; + } catch (error) { + logger.error(`[crystelf-ai] 工具 ${name} 执行失败: ${error.message}`); + return { + success: false, + error: error.message, + toolName: name, + timestamp: Date.now() + }; + } + } + + /** + * 获取工具列表 + * @returns {Array} 工具信息数组 + */ + getToolList() { + return Array.from(this.tools.values()).map(tool => ({ + name: tool.name, + description: tool.description, + parameters: tool.parameters + })); + } + + /** + * 清空所有工具 + */ + clear() { + this.tools.clear(); + this.toolSchemas = []; + } +} + +// 创建全局工具注册表实例 +const toolRegistry = new ToolRegistry(); + +export default toolRegistry; \ No newline at end of file