mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-13 11:53:48 +00:00
feat:恢复mcp模块 动态agent
This commit is contained in:
@@ -1,50 +1,41 @@
|
||||
package org.ruoyi.service.chat.impl;
|
||||
|
||||
import dev.langchain4j.agentic.AgenticServices;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorAgent;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy;
|
||||
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.mcp.McpToolProvider;
|
||||
import dev.langchain4j.mcp.client.DefaultMcpClient;
|
||||
import dev.langchain4j.mcp.client.McpClient;
|
||||
import dev.langchain4j.mcp.client.transport.McpTransport;
|
||||
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.service.tool.ToolProvider;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.agent.ChartGenerationAgent;
|
||||
import org.ruoyi.agent.SqlAgent;
|
||||
import org.ruoyi.agent.WebSearchAgent;
|
||||
import org.ruoyi.agent.tool.ExecuteSqlQueryTool;
|
||||
import org.ruoyi.agent.tool.QueryAllTablesTool;
|
||||
import org.ruoyi.agent.tool.QueryTableSchemaTool;
|
||||
import org.ruoyi.agent.McpAgent;
|
||||
import org.ruoyi.common.chat.base.ThreadContext;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner;
|
||||
import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService;
|
||||
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
|
||||
import org.ruoyi.common.core.utils.ObjectUtils;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
||||
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
||||
import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -213,17 +204,6 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定会话的内存缓存(可选)
|
||||
* 在会话结束时调用,释放内存资源
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public static void clearChatMemory(Object sessionId) {
|
||||
memoryCache.remove(sessionId);
|
||||
log.debug("已清理会话 {} 的内存缓存", sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行聊天(钩子方法 - 子类必须实现)
|
||||
* 注意:messages 已包含完整的历史上下文和当前消息
|
||||
@@ -232,7 +212,8 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe
|
||||
* @param chatRequest 聊天请求
|
||||
* @param handler 响应处理器
|
||||
*/
|
||||
protected abstract void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List<ChatMessage> messagesWithMemory, StreamingChatResponseHandler handler);
|
||||
protected abstract void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest,
|
||||
List<ChatMessage> messagesWithMemory, StreamingChatResponseHandler handler);
|
||||
|
||||
/**
|
||||
* 创建标准的响应处理器
|
||||
@@ -302,103 +283,80 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建具体厂商的 StreamingChatModel
|
||||
* 子类必须实现此方法,返回对应厂商的模型实例
|
||||
*/
|
||||
protected abstract StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest);
|
||||
|
||||
|
||||
/**
|
||||
* 获取提供者名称(子类必须实现)
|
||||
*/
|
||||
public abstract String getProviderName();
|
||||
|
||||
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
||||
// 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器
|
||||
// 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取)
|
||||
// McpTransport transport = new StdioMcpTransport.Builder()
|
||||
// .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
|
||||
// "bing-cn-mcp"
|
||||
// ))
|
||||
// .logEvents(true)
|
||||
// .build();
|
||||
|
||||
// // 步骤2: 创建MCP客户端
|
||||
// McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||
// .transport(transport)
|
||||
// .build();
|
||||
|
||||
// // 步骤3: 配置工具提供者
|
||||
// ToolProvider toolProvider = McpToolProvider.builder()
|
||||
// .mcpClients(List.of(mcpClient))
|
||||
// .build();
|
||||
|
||||
|
||||
McpTransport transport1 = new StdioMcpTransport.Builder()
|
||||
.command(List.of("npx", "-y",
|
||||
"mcp-echarts"
|
||||
))
|
||||
.logEvents(true)
|
||||
.build();
|
||||
|
||||
// 步骤2: 创建MCP客户端
|
||||
McpClient mcpClient1 = new DefaultMcpClient.Builder()
|
||||
.transport(transport1)
|
||||
.build();
|
||||
|
||||
// 步骤3: 配置工具提供者
|
||||
ToolProvider toolProvider1 = McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient1))
|
||||
.build();
|
||||
|
||||
// 步骤4: 配置OpenAI模型
|
||||
// OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder()
|
||||
// .baseUrl(chatModelVo.getApiHost())
|
||||
// .apiKey(chatModelVo.getApiKey())
|
||||
// .modelName(chatModelVo.getModelName())
|
||||
// .build();
|
||||
|
||||
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
// .baseUrl(chatModelVo.getApiHost())
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class)
|
||||
.chatModel(
|
||||
qwenChatModel)
|
||||
.tools(
|
||||
SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取
|
||||
SpringUtils.getBean(QueryTableSchemaTool.class),
|
||||
SpringUtils.getBean(ExecuteSqlQueryTool.class)
|
||||
)
|
||||
.build();
|
||||
|
||||
// WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class)
|
||||
// .chatModel(PLANNER_MODEL)
|
||||
// .toolProvider(toolProvider)
|
||||
// .build();
|
||||
|
||||
ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class)
|
||||
.chatModel(
|
||||
qwenChatModel)
|
||||
.toolProvider(toolProvider1)
|
||||
.build();
|
||||
String res = sqlAgent.getData(userMessage);
|
||||
String res1 = chartGenerationAgent.generateChart(res);
|
||||
System.out.println(res1);
|
||||
System.out.println(res);
|
||||
SupervisorAgent supervisor = AgenticServices
|
||||
.supervisorBuilder()
|
||||
.chatModel(qwenChatModel)
|
||||
.subAgents(sqlAgent, chartGenerationAgent)
|
||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||
.build();
|
||||
|
||||
String invoke = supervisor.invoke(userMessage);
|
||||
System.out.println(invoke);
|
||||
return res1;
|
||||
log.info("执行Agent任务,消息: {}", userMessage);
|
||||
// 加载所有可用的 Agent,让 Supervisor 根据任务类型自动选择
|
||||
return doAgentWithAllAgents(userMessage, chatModelVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用单一 Agent 处理所有任务
|
||||
* 不使用 Supervisor 模式,而是使用 MCP Agent 来处理所有任务
|
||||
*
|
||||
* @param userMessage 用户消息
|
||||
* @param chatModelVo 聊天模型配置
|
||||
* @return Agent 响应结果
|
||||
*/
|
||||
protected String doAgentWithAllAgents(String userMessage, ChatModelVo chatModelVo) {
|
||||
|
||||
try {
|
||||
// 1. 加载 LLM 模型
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
// 2. 获取统一工具提供工厂
|
||||
ToolProviderFactory toolProviderFactory = SpringUtils.getBean(ToolProviderFactory.class);
|
||||
|
||||
// 3. 获取所有可用的工具
|
||||
|
||||
// 3.1 添加 BUILTIN 工具对象(包括 SQL 工具)
|
||||
List<Object> builtinTools = toolProviderFactory.getAllBuiltinToolObjects();
|
||||
|
||||
List<Object> allTools = new ArrayList<>(builtinTools);
|
||||
|
||||
log.debug("Loaded {} builtin tools (including SQL tools)", builtinTools.size());
|
||||
|
||||
log.debug("Total tools: {}", allTools.size());
|
||||
|
||||
// 4. 获取 MCP 工具提供者
|
||||
ToolProvider mcpToolProvider = toolProviderFactory.getAllEnabledMcpToolsProvider();
|
||||
|
||||
// 5. 创建 MCP Agent(包含所有工具)
|
||||
var agentBuilder = AgenticServices.agentBuilder(McpAgent.class).chatModel(qwenChatModel);
|
||||
|
||||
// 添加所有工具
|
||||
if (!allTools.isEmpty()) {
|
||||
agentBuilder.tools(allTools.toArray(new Object[0]));
|
||||
}
|
||||
|
||||
// 添加 MCP 工具
|
||||
if (mcpToolProvider != null) {
|
||||
agentBuilder.toolProvider(mcpToolProvider);
|
||||
}
|
||||
|
||||
McpAgent mcpAgent = agentBuilder.build();
|
||||
|
||||
// 6. 调用大模型LLM
|
||||
String result = mcpAgent.callMcpTool(userMessage);
|
||||
log.info("Agent 执行完成,结果长度: {}", result.length());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Agent 模式执行失败: {}", e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建流式聊天模型
|
||||
* 子类必须实现此方法,返回对应厂商的模型实例
|
||||
*/
|
||||
protected abstract StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
package org.ruoyi.service.chat.impl.provider;
|
||||
|
||||
import dev.langchain4j.agentic.AgenticServices;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorAgent;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy;
|
||||
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
||||
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import dev.langchain4j.service.tool.ToolProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.agent.McpAgent;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.enums.ChatModeType;
|
||||
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
||||
import org.ruoyi.service.chat.impl.AbstractStreamingChatService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* qianWenAI服务调用
|
||||
@@ -39,9 +30,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
||||
// 添加文档解析的前缀字段
|
||||
private static final String UPLOAD_FILE_API_PREFIX = "fileid";
|
||||
|
||||
// 用于线程安全的锁
|
||||
private final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) {
|
||||
return QwenStreamingChatModel.builder()
|
||||
@@ -91,69 +79,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
||||
}).orElse(messagesWithMemory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用MCP服务(智能体)
|
||||
* 使用统一的ToolProviderFactory获取所有已配置的工具(BUILTIN + MCP)
|
||||
*
|
||||
* @param userMessage 用户信息
|
||||
* @param chatModelVo 模型信息
|
||||
* @return 返回LLM信息
|
||||
*/
|
||||
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
||||
try {
|
||||
// 步骤1: 获取统一工具提供工厂
|
||||
ToolProviderFactory toolProviderFactory = SpringUtils.getBean(ToolProviderFactory.class);
|
||||
|
||||
// 步骤2: 获取 BUILTIN 工具对象
|
||||
List<Object> builtinTools = toolProviderFactory.getAllBuiltinToolObjects();
|
||||
|
||||
// 步骤3: 获取 MCP 工具提供者
|
||||
ToolProvider mcpToolProvider = toolProviderFactory.getAllEnabledMcpToolsProvider();
|
||||
|
||||
log.info("doAgent: BUILTIN tools count = {}, MCP tools enabled = {}",
|
||||
builtinTools.size(), mcpToolProvider != null);
|
||||
|
||||
// 步骤4: 加载LLM模型
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
// 步骤5: 创建MCP Agent,使用所有已配置的工具
|
||||
// 使用 .tools() 传入 BUILTIN 工具对象(Java 对象,带 @Tool 注解的方法)
|
||||
// 使用 .toolProvider() 传入 MCP 工具提供者(MCP 协议工具)
|
||||
var agentBuilder = AgenticServices.agentBuilder(McpAgent.class)
|
||||
.chatModel(qwenChatModel);
|
||||
|
||||
// 添加 BUILTIN 工具(如果有)
|
||||
if (!builtinTools.isEmpty()) {
|
||||
agentBuilder.tools(builtinTools.toArray(new Object[0]));
|
||||
log.debug("Added {} BUILTIN tools to agent", builtinTools.size());
|
||||
}
|
||||
|
||||
// 添加 MCP 工具(如果有)
|
||||
if (mcpToolProvider != null) {
|
||||
agentBuilder.toolProvider(mcpToolProvider);
|
||||
log.debug("Added MCP tool provider to agent");
|
||||
}
|
||||
|
||||
McpAgent mcpAgent = agentBuilder.build();
|
||||
|
||||
// 步骤6: 创建超级智能体协调MCP Agent
|
||||
SupervisorAgent supervisor = AgenticServices
|
||||
.supervisorBuilder()
|
||||
.chatModel(qwenChatModel)
|
||||
.subAgents(mcpAgent)
|
||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||
.build();
|
||||
|
||||
// 步骤7: 调用大模型LLM
|
||||
return supervisor.invoke(userMessage);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return ChatModeType.QIAN_WEN.getCode();
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.ruoyi.service.mcp;
|
||||
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.mcp.McpMarketBo;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
|
||||
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MCP 市场服务接口
|
||||
*
|
||||
* @author ruoyi team
|
||||
*/
|
||||
public interface IMcpMarketService {
|
||||
|
||||
/**
|
||||
* 分页查询市场列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 市场分页列表
|
||||
*/
|
||||
TableDataInfo<McpMarketVo> selectPageList(McpMarketBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询市场列表(不分页)
|
||||
*
|
||||
* @param keyword 关键词
|
||||
* @param status 状态
|
||||
* @return 市场列表结果
|
||||
*/
|
||||
McpMarketListResult listMarkets(String keyword, String status);
|
||||
|
||||
/**
|
||||
* 查询市场列表(用于导出)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 市场列表
|
||||
*/
|
||||
List<McpMarketVo> queryList(McpMarketBo bo);
|
||||
|
||||
/**
|
||||
* 根据ID查询市场
|
||||
*
|
||||
* @param id 市场ID
|
||||
* @return 市场信息
|
||||
*/
|
||||
McpMarketVo selectById(Long id);
|
||||
|
||||
/**
|
||||
* 新增市场
|
||||
*
|
||||
* @param bo 市场信息
|
||||
* @return 新增后的市场ID
|
||||
*/
|
||||
String insert(McpMarketBo bo);
|
||||
|
||||
/**
|
||||
* 更新市场
|
||||
*
|
||||
* @param bo 市场信息
|
||||
* @return 结果
|
||||
*/
|
||||
String update(McpMarketBo bo);
|
||||
|
||||
/**
|
||||
* 删除市场
|
||||
*
|
||||
* @param ids 市场 ID 列表
|
||||
*/
|
||||
void deleteByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 更新市场状态
|
||||
*
|
||||
* @param id 市场 ID
|
||||
* @param status 状态
|
||||
*/
|
||||
void updateStatus(Long id, String status);
|
||||
|
||||
/**
|
||||
* 获取市场工具列表
|
||||
*
|
||||
* @param marketId 市场 ID
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 工具列表结果
|
||||
*/
|
||||
McpMarketToolListResult getMarketTools(Long marketId, int page, int size);
|
||||
|
||||
/**
|
||||
* 刷新市场工具列表
|
||||
*
|
||||
* @param marketId 市场 ID
|
||||
* @return 刷新结果
|
||||
*/
|
||||
McpMarketRefreshResult refreshMarketTools(Long marketId);
|
||||
|
||||
/**
|
||||
* 加载工具到本地
|
||||
*
|
||||
* @param toolId 市场工具 ID
|
||||
*/
|
||||
void loadToolToLocal(Long toolId);
|
||||
|
||||
/**
|
||||
* 批量加载工具到本地
|
||||
*
|
||||
* @param toolIds 工具 ID 列表
|
||||
* @return 成功加载的数量
|
||||
*/
|
||||
int batchLoadTools(List<Long> toolIds);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.ruoyi.service.mcp;
|
||||
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.mcp.McpToolBo;
|
||||
import org.ruoyi.domain.dto.mcp.McpToolListResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
|
||||
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MCP 工具服务接口
|
||||
*
|
||||
* @author ruoyi team
|
||||
*/
|
||||
public interface IMcpToolService {
|
||||
|
||||
/**
|
||||
* 分页查询工具列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 工具分页列表
|
||||
*/
|
||||
TableDataInfo<McpToolVo> selectPageList(McpToolBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询工具列表(不分页)
|
||||
*
|
||||
* @param keyword 关键词
|
||||
* @param type 类型
|
||||
* @param status 状态
|
||||
* @return 工具列表结果
|
||||
*/
|
||||
McpToolListResult listTools(String keyword, String type, String status);
|
||||
|
||||
/**
|
||||
* 查询工具列表(用于导出)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 工具列表
|
||||
*/
|
||||
List<McpToolVo> queryList(McpToolBo bo);
|
||||
|
||||
/**
|
||||
* 根据ID查询工具
|
||||
*
|
||||
* @param id 工具ID
|
||||
* @return 工具信息
|
||||
*/
|
||||
McpToolVo selectById(Long id);
|
||||
|
||||
/**
|
||||
* 新增工具
|
||||
*
|
||||
* @param bo 工具信息
|
||||
* @return 新增后的工具ID
|
||||
*/
|
||||
String insert(McpToolBo bo);
|
||||
|
||||
/**
|
||||
* 更新工具
|
||||
*
|
||||
* @param bo 工具信息
|
||||
* @return 结果
|
||||
*/
|
||||
String update(McpToolBo bo);
|
||||
|
||||
/**
|
||||
* 删除工具
|
||||
*
|
||||
* @param ids 工具 ID 列表
|
||||
*/
|
||||
void deleteByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 更新工具状态
|
||||
*
|
||||
* @param id 工具 ID
|
||||
* @param status 状态
|
||||
*/
|
||||
void updateStatus(Long id, String status);
|
||||
|
||||
/**
|
||||
* 测试工具连接
|
||||
*
|
||||
* @param id 工具 ID
|
||||
* @return 测试结果
|
||||
*/
|
||||
McpToolTestResult testTool(Long id);
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package org.ruoyi.service.mcp.impl;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.exception.ServiceException;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.mcp.McpMarketBo;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
|
||||
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||
import org.ruoyi.domain.entity.mcp.McpMarketTool;
|
||||
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||
import org.ruoyi.enums.McpToolStatus;
|
||||
import org.ruoyi.mapper.mcp.McpMarketMapper;
|
||||
import org.ruoyi.mapper.mcp.McpMarketToolMapper;
|
||||
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||
import org.ruoyi.service.mcp.IMcpMarketService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* MCP 市场服务实现
|
||||
*
|
||||
* @author ruoyi team
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class McpMarketServiceImpl implements IMcpMarketService {
|
||||
|
||||
private final McpMarketMapper baseMapper;
|
||||
private final McpMarketToolMapper mcpMarketToolMapper;
|
||||
private final McpToolMapper mcpToolMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public TableDataInfo<McpMarketVo> selectPageList(McpMarketBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<McpMarket> wrapper = buildQueryWrapper(bo);
|
||||
Page<McpMarketVo> page = baseMapper.selectVoPage(pageQuery.build(), wrapper);
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpMarketListResult listMarkets(String keyword, String status) {
|
||||
LambdaQueryWrapper<McpMarket> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.and(w -> w.like(McpMarket::getName, keyword)
|
||||
.or()
|
||||
.like(McpMarket::getDescription, keyword));
|
||||
}
|
||||
if (StringUtils.hasText(status)) {
|
||||
wrapper.eq(McpMarket::getStatus, status);
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(McpMarket::getUpdateTime);
|
||||
|
||||
List<McpMarket> list = baseMapper.selectList(wrapper);
|
||||
|
||||
return McpMarketListResult.of(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<McpMarketVo> queryList(McpMarketBo bo) {
|
||||
LambdaQueryWrapper<McpMarket> wrapper = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpMarketVo selectById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String insert(McpMarketBo bo) {
|
||||
McpMarket market = MapstructUtils.convert(bo, McpMarket.class);
|
||||
if (market.getStatus() == null) {
|
||||
market.setStatus(McpToolStatus.ENABLED.getValue());
|
||||
}
|
||||
baseMapper.insert(market);
|
||||
return String.valueOf(market.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String update(McpMarketBo bo) {
|
||||
McpMarket market = MapstructUtils.convert(bo, McpMarket.class);
|
||||
baseMapper.updateById(market);
|
||||
return String.valueOf(market.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteByIds(List<Long> ids) {
|
||||
for (Long id : ids) {
|
||||
// 先删除关联的市场工具
|
||||
LambdaQueryWrapper<McpMarketTool> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(McpMarketTool::getMarketId, id);
|
||||
mcpMarketToolMapper.delete(wrapper);
|
||||
}
|
||||
|
||||
// 删除市场
|
||||
baseMapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateStatus(Long id, String status) {
|
||||
McpMarket market = new McpMarket();
|
||||
market.setId(id);
|
||||
market.setStatus(status);
|
||||
baseMapper.updateById(market);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpMarketToolListResult getMarketTools(Long marketId, int page, int size) {
|
||||
LambdaQueryWrapper<McpMarketTool> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(McpMarketTool::getMarketId, marketId);
|
||||
wrapper.orderByDesc(McpMarketTool::getCreateTime);
|
||||
|
||||
Page<McpMarketTool> pageResult = mcpMarketToolMapper.selectPage(new Page<>(page, size), wrapper);
|
||||
|
||||
return McpMarketToolListResult.of(
|
||||
pageResult.getRecords(),
|
||||
pageResult.getTotal(),
|
||||
(int) pageResult.getCurrent(),
|
||||
(int) pageResult.getSize()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public McpMarketRefreshResult refreshMarketTools(Long marketId) {
|
||||
McpMarket market = baseMapper.selectById(marketId);
|
||||
if (market == null) {
|
||||
throw new ServiceException("市场不存在");
|
||||
}
|
||||
|
||||
int addedCount = 0;
|
||||
int updatedCount = 0;
|
||||
|
||||
try {
|
||||
// 从市场 URL 获取工具列表(使用hutool的HttpUtil)
|
||||
HttpResponse response = HttpRequest.get(market.getUrl())
|
||||
.timeout(30000) // 30秒超时
|
||||
.execute();
|
||||
String responseBody = response.body();
|
||||
JsonNode rootNode = objectMapper.readTree(responseBody);
|
||||
|
||||
// 假设响应格式为 { "data": [...] } 或直接是数组
|
||||
JsonNode toolsNode = rootNode.has("data") ? rootNode.get("data") : rootNode;
|
||||
|
||||
if (toolsNode.isArray()) {
|
||||
// 获取现有工具
|
||||
LambdaQueryWrapper<McpMarketTool> existingWrapper = new LambdaQueryWrapper<>();
|
||||
existingWrapper.eq(McpMarketTool::getMarketId, marketId);
|
||||
List<McpMarketTool> existingTools = mcpMarketToolMapper.selectList(existingWrapper);
|
||||
|
||||
// 创建现有工具的名称到ID映射
|
||||
Map<String, McpMarketTool> existingToolMap = existingTools.stream()
|
||||
.collect(Collectors.toMap(McpMarketTool::getToolName, t -> t));
|
||||
|
||||
// 处理新工具
|
||||
for (JsonNode toolNode : toolsNode) {
|
||||
String toolName = getTextValue(toolNode, "name", "title");
|
||||
McpMarketTool existingTool = existingToolMap.get(toolName);
|
||||
|
||||
if (existingTool != null) {
|
||||
// 更新现有工具
|
||||
existingTool.setToolDescription(getTextValue(toolNode, "description", "desc"));
|
||||
existingTool.setToolVersion(getTextValue(toolNode, "version"));
|
||||
existingTool.setToolMetadata(toolNode.toString());
|
||||
mcpMarketToolMapper.updateById(existingTool);
|
||||
updatedCount++;
|
||||
} else {
|
||||
// 插入新工具
|
||||
McpMarketTool tool = new McpMarketTool();
|
||||
tool.setMarketId(marketId);
|
||||
tool.setToolName(toolName);
|
||||
tool.setToolDescription(getTextValue(toolNode, "description", "desc"));
|
||||
tool.setToolVersion(getTextValue(toolNode, "version"));
|
||||
tool.setToolMetadata(toolNode.toString());
|
||||
tool.setIsLoaded(false);
|
||||
mcpMarketToolMapper.insert(tool);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Successfully refreshed market tools for market: {}, added: {}, updated: {}",
|
||||
market.getName(), addedCount, updatedCount);
|
||||
|
||||
return McpMarketRefreshResult.builder()
|
||||
.success(true)
|
||||
.message("刷新成功")
|
||||
.addedCount(addedCount)
|
||||
.updatedCount(updatedCount)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to refresh market tools for market {}: {}", marketId, e.getMessage());
|
||||
return McpMarketRefreshResult.builder()
|
||||
.success(false)
|
||||
.message("刷新市场工具列表失败: " + e.getMessage())
|
||||
.addedCount(0)
|
||||
.updatedCount(0)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JSON 节点获取文本值,尝试多个字段名
|
||||
*/
|
||||
private String getTextValue(JsonNode node, String... fieldNames) {
|
||||
for (String fieldName : fieldNames) {
|
||||
if (node.has(fieldName) && !node.get(fieldName).isNull()) {
|
||||
return node.get(fieldName).asText();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void loadToolToLocal(Long toolId) {
|
||||
McpMarketTool marketTool = mcpMarketToolMapper.selectById(toolId);
|
||||
if (marketTool == null) {
|
||||
throw new ServiceException("市场工具不存在");
|
||||
}
|
||||
|
||||
if (marketTool.getIsLoaded()) {
|
||||
throw new ServiceException("工具已加载到本地");
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析工具元数据
|
||||
JsonNode metadata = objectMapper.readTree(marketTool.getToolMetadata());
|
||||
|
||||
// 创建本地工具
|
||||
McpTool localTool = new McpTool();
|
||||
localTool.setName(marketTool.getToolName());
|
||||
localTool.setDescription(marketTool.getToolDescription());
|
||||
|
||||
// 根据元数据判断类型
|
||||
if (metadata.has("baseUrl") || metadata.has("url")) {
|
||||
localTool.setType("REMOTE");
|
||||
String baseUrl = metadata.has("baseUrl") ? metadata.get("baseUrl").asText() :
|
||||
metadata.has("url") ? metadata.get("url").asText() : null;
|
||||
localTool.setConfigJson(objectMapper.writeValueAsString(Map.of("baseUrl", baseUrl != null ? baseUrl : "")));
|
||||
} else {
|
||||
localTool.setType("LOCAL");
|
||||
// 构建本地工具配置
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
if (metadata.has("command")) {
|
||||
config.put("command", metadata.get("command").asText());
|
||||
}
|
||||
if (metadata.has("args") && metadata.get("args").isArray()) {
|
||||
config.put("args", objectMapper.convertValue(metadata.get("args"), List.class));
|
||||
}
|
||||
if (metadata.has("env") && metadata.get("env").isObject()) {
|
||||
config.put("env", objectMapper.convertValue(metadata.get("env"), Map.class));
|
||||
}
|
||||
// 如果有 npm 包名,使用 npx 启动
|
||||
if (metadata.has("package") || metadata.has("npmPackage")) {
|
||||
String packageName = metadata.has("package") ? metadata.get("package").asText() :
|
||||
metadata.get("npmPackage").asText();
|
||||
config.put("command", "npx");
|
||||
config.put("args", List.of("-y", packageName));
|
||||
}
|
||||
localTool.setConfigJson(objectMapper.writeValueAsString(config));
|
||||
}
|
||||
|
||||
localTool.setStatus(McpToolStatus.ENABLED.getValue());
|
||||
mcpToolMapper.insert(localTool);
|
||||
|
||||
// 更新市场工具状态
|
||||
marketTool.setIsLoaded(true);
|
||||
marketTool.setLocalToolId(localTool.getId());
|
||||
mcpMarketToolMapper.updateById(marketTool);
|
||||
|
||||
log.info("Successfully loaded tool {} to local", marketTool.getToolName());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load tool to local: {}", e.getMessage());
|
||||
throw new ServiceException("加载工具到本地失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int batchLoadTools(List<Long> toolIds) {
|
||||
int successCount = 0;
|
||||
for (Long toolId : toolIds) {
|
||||
try {
|
||||
loadToolToLocal(toolId);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to load tool {}: {}", toolId, e.getMessage());
|
||||
}
|
||||
}
|
||||
return successCount;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<McpMarket> buildQueryWrapper(McpMarketBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<McpMarket> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(StringUtils.hasText(bo.getStatus()), McpMarket::getStatus, bo.getStatus())
|
||||
.like(StringUtils.hasText(bo.getName()), McpMarket::getName, bo.getName())
|
||||
.like(StringUtils.hasText(bo.getDescription()), McpMarket::getDescription, bo.getDescription());
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package org.ruoyi.service.mcp.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.exception.ServiceException;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.mcp.McpToolBo;
|
||||
import org.ruoyi.domain.dto.mcp.McpToolListResult;
|
||||
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
|
||||
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||
import org.ruoyi.enums.McpToolStatus;
|
||||
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||
import org.ruoyi.service.mcp.IMcpToolService;
|
||||
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
|
||||
import org.ruoyi.mcp.service.core.LangChain4jMcpToolProviderService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP 工具服务实现
|
||||
*
|
||||
* @author ruoyi team
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class McpToolServiceImpl implements IMcpToolService {
|
||||
|
||||
private final McpToolMapper baseMapper;
|
||||
private final LangChain4jMcpToolProviderService langChain4jMcpToolProviderService;
|
||||
private final BuiltinToolRegistry builtinToolRegistry;
|
||||
|
||||
@Override
|
||||
public TableDataInfo<McpToolVo> selectPageList(McpToolBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<McpTool> wrapper = buildQueryWrapper(bo);
|
||||
Page<McpToolVo> page = baseMapper.selectVoPage(pageQuery.build(), wrapper);
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpToolListResult listTools(String keyword, String type, String status) {
|
||||
LambdaQueryWrapper<McpTool> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
wrapper.and(w -> w.like(McpTool::getName, keyword)
|
||||
.or()
|
||||
.like(McpTool::getDescription, keyword));
|
||||
}
|
||||
if (StringUtils.hasText(type)) {
|
||||
wrapper.eq(McpTool::getType, type);
|
||||
}
|
||||
if (StringUtils.hasText(status)) {
|
||||
wrapper.eq(McpTool::getStatus, status);
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(McpTool::getUpdateTime);
|
||||
|
||||
List<McpTool> list = baseMapper.selectList(wrapper);
|
||||
|
||||
return McpToolListResult.of(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<McpToolVo> queryList(McpToolBo bo) {
|
||||
LambdaQueryWrapper<McpTool> wrapper = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpToolVo selectById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String insert(McpToolBo bo) {
|
||||
McpTool tool = MapstructUtils.convert(bo, McpTool.class);
|
||||
if (tool.getStatus() == null) {
|
||||
tool.setStatus(McpToolStatus.ENABLED.getValue());
|
||||
}
|
||||
if (tool.getType() == null) {
|
||||
tool.setType("LOCAL");
|
||||
}
|
||||
baseMapper.insert(tool);
|
||||
return String.valueOf(tool.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String update(McpToolBo bo) {
|
||||
McpTool existingTool = baseMapper.selectById(bo.getId());
|
||||
if (existingTool != null && BuiltinToolRegistry.TYPE_BUILTIN.equals(existingTool.getType())) {
|
||||
throw new ServiceException("内置工具不允许编辑");
|
||||
}
|
||||
|
||||
McpTool tool = MapstructUtils.convert(bo, McpTool.class);
|
||||
baseMapper.updateById(tool);
|
||||
|
||||
// 如果工具正在使用中,需要刷新连接
|
||||
langChain4jMcpToolProviderService.refreshClient(bo.getId());
|
||||
|
||||
return String.valueOf(tool.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteByIds(List<Long> ids) {
|
||||
// 过滤掉内置工具
|
||||
List<Long> deletableIds = ids.stream()
|
||||
.filter(id -> {
|
||||
McpTool tool = baseMapper.selectById(id);
|
||||
return tool == null || !BuiltinToolRegistry.TYPE_BUILTIN.equals(tool.getType());
|
||||
})
|
||||
.toList();
|
||||
|
||||
if (deletableIds.isEmpty()) {
|
||||
throw new ServiceException("所选工具均为内置工具,不允许删除");
|
||||
}
|
||||
|
||||
// 刷新连接(LangChain4j会自动处理)
|
||||
deletableIds.forEach(id -> langChain4jMcpToolProviderService.refreshClient(id));
|
||||
baseMapper.deleteBatchIds(deletableIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateStatus(Long id, String status) {
|
||||
McpTool tool = new McpTool();
|
||||
tool.setId(id);
|
||||
tool.setStatus(status);
|
||||
baseMapper.updateById(tool);
|
||||
|
||||
// 刷新连接
|
||||
langChain4jMcpToolProviderService.refreshClient(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public McpToolTestResult testTool(Long id) {
|
||||
McpTool tool = baseMapper.selectById(id);
|
||||
if (tool == null) {
|
||||
return McpToolTestResult.fail("工具不存在");
|
||||
}
|
||||
|
||||
// 根据工具类型选择不同的测试逻辑
|
||||
if (BuiltinToolRegistry.TYPE_BUILTIN.equals(tool.getType())) {
|
||||
// 内置工具 - 直接验证是否在注册表中
|
||||
return testBuiltinTool(tool);
|
||||
} else {
|
||||
// MCP 工具 (LOCAL/REMOTE) - 测试连接
|
||||
return testMcpTool(tool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试内置工具
|
||||
* 内置工具不需要网络连接,只需验证是否在注册表中
|
||||
*
|
||||
* @param tool 工具信息
|
||||
* @return 测试结果
|
||||
*/
|
||||
private McpToolTestResult testBuiltinTool(McpTool tool) {
|
||||
try {
|
||||
boolean isRegistered = builtinToolRegistry.hasTool(tool.getName());
|
||||
if (isRegistered) {
|
||||
return McpToolTestResult.success(
|
||||
String.format("内置工具 [%s] 已注册,可正常使用", tool.getName()),
|
||||
1,
|
||||
List.of(tool.getName())
|
||||
);
|
||||
} else {
|
||||
return McpToolTestResult.fail(
|
||||
String.format("内置工具 [%s] 未在注册表中找到,请检查工具名称是否正确", tool.getName())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("测试内置工具失败: {} - {}", tool.getName(), e.getMessage());
|
||||
return McpToolTestResult.fail("测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试MCP工具连接
|
||||
*
|
||||
* @param tool 工具信息
|
||||
* @return 测试结果
|
||||
*/
|
||||
private McpToolTestResult testMcpTool(McpTool tool) {
|
||||
try {
|
||||
boolean isHealthy = langChain4jMcpToolProviderService.checkToolHealth(tool.getId());
|
||||
if (isHealthy) {
|
||||
return McpToolTestResult.success(
|
||||
String.format("MCP工具 [%s] 连接测试成功", tool.getName()),
|
||||
1,
|
||||
List.of(tool.getName())
|
||||
);
|
||||
} else {
|
||||
return McpToolTestResult.fail(
|
||||
String.format("MCP工具 [%s] 连接测试失败", tool.getName())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("测试MCP工具失败: {} - {}", tool.getName(), e.getMessage());
|
||||
return McpToolTestResult.fail("测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<McpTool> buildQueryWrapper(McpToolBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<McpTool> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(StringUtils.hasText(bo.getType()), McpTool::getType, bo.getType())
|
||||
.eq(StringUtils.hasText(bo.getStatus()), McpTool::getStatus, bo.getStatus())
|
||||
.like(StringUtils.hasText(bo.getName()), McpTool::getName, bo.getName())
|
||||
.like(StringUtils.hasText(bo.getDescription()), McpTool::getDescription, bo.getDescription());
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user