mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2026-01-29 09:17:27 +00:00
feat: ReAct !
This commit is contained in:
parent
8c413949ac
commit
8bc82dbd09
@ -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": "是否启用图像生成功能",
|
||||
|
||||
@ -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<Object>} 调用结果
|
||||
*/
|
||||
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 上下文事件对象
|
||||
|
||||
256
lib/ai/contextBuilder.js
Normal file
256
lib/ai/contextBuilder.js
Normal file
@ -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<Object>} 基础上下文
|
||||
*/
|
||||
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<string>} 人格提示词
|
||||
*/
|
||||
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();
|
||||
413
lib/ai/reactEngine.js
Normal file
413
lib/ai/reactEngine.js
Normal file
@ -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<Object>} 执行结果
|
||||
*/
|
||||
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();
|
||||
@ -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<Array>} 处理后的消息数组
|
||||
*/
|
||||
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);
|
||||
|
||||
53
lib/ai/tools/baseTool.js
Normal file
53
lib/ai/tools/baseTool.js
Normal file
@ -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<Object>} 执行结果
|
||||
*/
|
||||
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;
|
||||
140
lib/ai/tools/contentTool.js
Normal file
140
lib/ai/tools/contentTool.js
Normal file
@ -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 };
|
||||
98
lib/ai/tools/controlTool.js
Normal file
98
lib/ai/tools/controlTool.js
Normal file
@ -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 };
|
||||
105
lib/ai/tools/memoryTool.js
Normal file
105
lib/ai/tools/memoryTool.js
Normal file
@ -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 };
|
||||
148
lib/ai/tools/messageTool.js
Normal file
148
lib/ai/tools/messageTool.js
Normal file
@ -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 };
|
||||
141
lib/ai/tools/retrievalTool.js
Normal file
141
lib/ai/tools/retrievalTool.js
Normal file
@ -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 };
|
||||
174
lib/ai/tools/toolCombinations.js
Normal file
174
lib/ai/tools/toolCombinations.js
Normal file
@ -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();
|
||||
84
lib/ai/tools/toolInitializer.js
Normal file
84
lib/ai/tools/toolInitializer.js
Normal file
@ -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;
|
||||
101
lib/ai/tools/toolRegistry.js
Normal file
101
lib/ai/tools/toolRegistry.js
Normal file
@ -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<Object>} 执行结果
|
||||
*/
|
||||
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;
|
||||
Loading…
x
Reference in New Issue
Block a user