UNPKG

@voyager-0x/agent-mcp

Version:

Voyager MCP Agent - A powerful Model Context Protocol agent

174 lines (173 loc) 6.44 kB
import { ChatManager } from "./chat.js"; import { util } from "./util.js"; import { Session } from "./session.js"; import { log } from "./log.js"; import { mcpModulesManager } from "./module.js"; class BaseAgent { /** * 取消当前聊天 */ cancelChat() { this.session.emit("cancel"); } constructor() { this.session = new Session(); this.chatManager = new ChatManager({ session: this.session, }); } async parseJson(jsonString) { try { return JSON.parse(jsonString); } catch (error) { const chatClient = await this.chatManager.createFixJsonChat(); let content = await chatClient.chat(jsonString); // 处理大模型推理的think标签内容 content = util.removeThinkConetnt(content); log.info("修复后的JSON内容", content); return util.parseJson(content); } } } export class McpAgent extends BaseAgent { constructor() { super(); this.isChatting = false; } async chat(message) { this.isChatting = true; try { await this.chatToCallFunction(message); } catch (error) { if (error.name === "AbortError") { return; // 聊天被取消,直接返回 } // 只有非取消错误才记录日志并重新抛出 throw error; } finally { this.isChatting = false; } } async chatToCallFunction(usermessage) { let chatClient = this.chatClient; if (!chatClient) { chatClient = this.chatClient = await this.chatManager.createMcpChat(); } // 多次重试原因说明: // 受限于大模型本身的能力,部分模型的推理结果可能存在错误,需要多次尝试 // 如可能建议使用更好的模型,这样减少模型推理的错误,降低Token消耗 const exec = async (usermessage, count) => { const countMax = 3; // 最大重试次数 try { const content = await chatClient.chat(usermessage); // 处理工具调用 const toolResult = await this.processToolOrResourceCall(content); if (toolResult === null) { this.session.emit("finish"); } else { await this.chatToCallFunction(typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)); } return content; } catch (error) { if (error.name === "AbortError") { // 聊天被取消,直接抛出错误 throw error; } if (count < countMax) { log.warn(`重试第 ${count + 1} 次: `, error); // 生成一段新的用户消息,将错误信息告知大模型,重新尝试 usermessage = `请重新尝试,之前的错误信息是:${JSON.stringify(error)}`; return exec(usermessage, count + 1); } else { throw error; } } }; await exec(usermessage, 0); this.session.add(chatClient.getMessages()); } async processToolOrResourceCall(content) { // 处理大模型推理的think标签内容 content = util.removeThinkConetnt(content); // 判定是否是工具调用 if (content.includes("<use_mcp_resource>")) { return this.handlerReadResource(content); } else if (content.includes("<use_mcp_tool>")) { return this.handlerCallTool(content); } else { return null; } } async handlerReadResource(content) { // 解析 <use_mcp_resource> 标签的内容 const resourceCallRegex = /<use_mcp_resource>([\s\S]*?)<\/use_mcp_resource>/; const exec = resourceCallRegex.exec(content); const resourceCallJsonString = exec ? exec[1].trim() : ""; log.info("读取资源", resourceCallJsonString); if (!resourceCallJsonString) { return null; } const resourceCall = await this.parseJson(resourceCallJsonString); const module = mcpModulesManager.getMcpModule({ uuid: resourceCall.moduleId, }); if (!module) { log.error(`Module with uuid ${resourceCall.moduleId} not found`); return null; } try { const result = await module.client.readResource({ uri: resourceCall.uri, }); log.info("资源读取返回结果", result); return result; } catch (error) { log.error(`Failed to read resource ${resourceCall.uri} from module ${resourceCall.moduleId}:`, error); return `Error reading resource: ${error.message || error}`; } } async handlerCallTool(content) { const toolCallRegex = /<use_mcp_tool>([\s\S]*?)<\/use_mcp_tool>/; const exec = toolCallRegex.exec(content); const toolCallContent = exec ? exec[1].trim() : ""; log.info("工具调用内容", toolCallContent); if (!toolCallContent) { return null; } const toolCall = await this.parseJson(toolCallContent); if (!toolCall || !toolCall.moduleId || !toolCall.tool) { log.error("Invalid tool call format."); return null; } const module = mcpModulesManager.getMcpModule({ uuid: toolCall.moduleId, }); if (!module) { log.error(`Module with uuid ${toolCall.moduleId} not found`); return null; } try { const toolResult = await module.client.callTool({ name: toolCall.tool, arguments: toolCall.arguments || {}, }); log.info("工具调用返回结果", toolResult); return toolResult; } catch (error) { log.error(`Failed to call tool ${toolCall.tool} from module ${toolCall.moduleId}:`, error); return `Error calling tool: ${error?.message || error}`; } } }