diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/GetNameInfo.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/GetNameInfo.java deleted file mode 100644 index 92b7e3c4..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/GetNameInfo.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.ruoyi.agent; - -import dev.langchain4j.agentic.Agent; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; - -/** - * User Name Retrieval Agent - * A simple assistant that retrieves user names using the get_name tool. - */ -public interface GetNameInfo { - - @SystemMessage(""" - You are a user identity assistant. You MUST always use tools to get information. - - MANDATORY REQUIREMENTS: - - You MUST call the get_user_name_by_id tool for ANY question about names or identity - - NEVER respond without calling the get_user_name_by_id tool first - - Return ONLY the exact string returned by the get_user_name_by_id tool - - Do not make up names like "John Doe" or any other default names - - Do not use your knowledge to answer - ALWAYS use the tool - - Your workflow: - 1. Extract userId from the query (if mentioned), or use "1" as default - 2. ALWAYS call the get_user_name_by_id tool with the userId parameter - 3. Return the exact result as plain text with no additions - - CRITICAL: If you don't call the get_user_name_by_id tool, your response is wrong. - """) - @UserMessage(""" - Get the user name using the get_user_name_by_id tool. Query: {{query}} - - IMPORTANT: Return only the exact result from the tool. - """) - @Agent("User identity assistant that returns user name from get_name tool") - String search(@V("query") String query); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java deleted file mode 100644 index 7bef6b4a..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.ruoyi.agent; - -import dev.langchain4j.agentic.Agent; -import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.UserMessage; -import dev.langchain4j.service.V; - -public interface McpAgent extends Agent { - - /** - * 系统提示词:通用工具调用智能体 - * 不限定具体工具类型,让 LangChain4j 自动传递工具描述给 LLM - */ - @SystemMessage(""" - 你是一个AI助手,可以通过调用各种工具来帮助用户完成不同的任务。 - - 【工具使用规则】 - 1. 根据用户的请求,判断需要使用哪些工具 - 2. 仔细阅读每个工具的描述,确保理解工具的功能和参数要求 - 3. 使用正确的参数调用工具 - 4. 如果工具执行失败,向用户友好地说明错误原因,并尝试提供替代方案 - 5. 对于复杂任务,可以分步骤使用多个工具完成 - 6. 将工具执行结果以清晰易懂的方式呈现给用户 - - 【响应格式】 - - 直接回答用户的问题 - - 如果使用了工具,说明使用了什么工具以及结果 - - 如果遇到错误,提供友好的错误信息和解决建议 - """) - - @UserMessage(""" - {{query}} - """) - - @Agent("通用工具调用智能体") - /** - * 智能体对外调用入口 - * @param query 用户的自然语言请求 - * @return 处理结果 - */ - String callMcpTool(@V("query") String query); -} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java deleted file mode 100644 index ad0b06ca..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/AgentChatHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -//package org.ruoyi.service.chat.handler; -// -//import dev.langchain4j.agentic.AgenticServices; -//import dev.langchain4j.community.model.dashscope.QwenChatModel; -//import dev.langchain4j.service.tool.ToolProvider; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.ruoyi.agent.McpAgent; -//import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -//import org.ruoyi.common.chat.entity.chat.ChatContext; -//import org.ruoyi.common.chat.service.chatMessage.IChatMessageService; -//import org.ruoyi.common.sse.utils.SseMessageUtils; -//import org.ruoyi.mcp.service.core.ToolProviderFactory; -//import org.springframework.core.annotation.Order; -//import org.springframework.stereotype.Component; -//import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -// -//import java.util.ArrayList; -//import java.util.List; -// -///** -// * Agent 深度思考处理器 -// *

-// * 处理 enableThinking=true 的场景,使用 Agent 进行深度思考和工具调用 -// * -// * @author ageerle@163.com -// * @date 2025/12/13 -// */ -//@Slf4j -//@Component -//@Order(3) -//@RequiredArgsConstructor -//public class AgentChatHandler implements ChatHandler { -// -// private final ToolProviderFactory toolProviderFactory; -// -// @Override -// public boolean supports(ChatContext context) { -// Boolean enableThinking = context.getChatRequest().getEnableThinking(); -// return enableThinking != null && enableThinking; -// } -// -// @Override -// public SseEmitter handle(ChatContext context) { -// log.info("处理 Agent 深度思考,用户: {}", context.getUserId()); -// -// Long userId = context.getUserId(); -// String tokenValue = context.getTokenValue(); -// ChatModelVo chatModelVo = context.getChatModelVo(); -// -// try { -// // 1. 保存用户消息 -// String content = extractUserContent(context); -//// saveChatMessage(context.getChatRequest(), userId, content, -//// RoleType.USER.getName(), chatModelVo); -// -// // 2. 执行 Agent 任务 -// String result = doAgent(content, chatModelVo); -// -// // 3. 发送结果并保存 -// SseMessageUtils.sendMessage(userId, result); -// SseMessageUtils.completeConnection(userId, tokenValue); -// -//// saveChatMessage(context.getChatRequest(), userId, result, -//// RoleType.ASSISTANT.getName(), chatModelVo); -// // todo 保存消息 -// } catch (Exception e) { -// log.error("Agent 执行失败: {}", e.getMessage(), e); -// SseMessageUtils.sendMessage(userId, "Agent 执行失败:" + e.getMessage()); -// SseMessageUtils.completeConnection(userId, tokenValue); -// } -// -// return context.getEmitter(); -// } -// -// /** -// * 执行 Agent 任务 -// */ -// private String doAgent(String userMessage, ChatModelVo chatModelVo) { -// log.info("执行 Agent 任务,消息: {}", userMessage); -// -// try { -// // 1. 加载 LLM 模型 -// QwenChatModel qwenChatModel = QwenChatModel.builder() -// .apiKey(chatModelVo.getApiKey()) -// .modelName(chatModelVo.getModelName()) -// .build(); -// -// // 2. 获取内置工具 -// List builtinTools = toolProviderFactory.getAllBuiltinToolObjects(); -// List allTools = new ArrayList<>(builtinTools); -// log.debug("加载 {} 个内置工具", builtinTools.size()); -// -// // 3. 获取 MCP 工具提供者 -// ToolProvider mcpToolProvider = toolProviderFactory.getAllEnabledMcpToolsProvider(); -// -// // 4. 创建 MCP Agent -// var agentBuilder = AgenticServices.agentBuilder(McpAgent.class) -// .chatModel(qwenChatModel); -// -// if (!allTools.isEmpty()) { -// agentBuilder.tools(allTools.toArray(new Object[0])); -// } -// if (mcpToolProvider != null) { -// agentBuilder.toolProvider(mcpToolProvider); -// } -// -// McpAgent mcpAgent = agentBuilder.build(); -// -// // 5. 调用 Agent -// String result = mcpAgent.callMcpTool(userMessage); -// log.info("Agent 执行完成,结果长度: {}", result.length()); -// return result; -// -// } catch (Exception e) { -// log.error("Agent 模式执行失败: {}", e.getMessage(), e); -// return "Agent 执行失败: " + e.getMessage(); -// } -// } -// -// /** -// * 提取用户消息内容 -// */ -// private String extractUserContent(ChatContext context) { -// var messages = context.getChatRequest().getMessages(); -// if (messages != null && !messages.isEmpty()) { -// return messages.get(0).getContent(); -// } -// return ""; -// } -// -//} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/ResumeChatHandler.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/ResumeChatHandler.java deleted file mode 100644 index 3b017c1f..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/ResumeChatHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -//package org.ruoyi.service.chat.handler; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner; -//import org.ruoyi.common.chat.entity.chat.ChatContext; -//import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; -//import org.ruoyi.common.core.utils.ObjectUtils; -//import org.springframework.core.annotation.Order; -//import org.springframework.stereotype.Component; -//import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -// -///** -// * 人机交互恢复处理器 -// *

-// * 处理 isResume=true 的场景,恢复工作流的人机交互 -// * -// * @author ageerle@163.com -// * @date 2025/12/13 -// */ -//@Slf4j -//@Component -//@Order(1) -//@RequiredArgsConstructor -//public class ResumeChatHandler implements ChatHandler { -// -// private final IWorkFlowStarterService workFlowStarterService; -// -// @Override -// public boolean supports(ChatContext context) { -// Boolean isResume = context.getChatRequest().getIsResume(); -// return isResume != null && isResume; -// } -// -// @Override -// public SseEmitter handle(ChatContext context) { -// log.info("处理人机交互恢复,用户: {}", context.getUserId()); -// -// ReSumeRunner reSumeRunner = context.getChatRequest().getReSumeRunner(); -// if (ObjectUtils.isEmpty(reSumeRunner)) { -// log.warn("人机交互恢复参数为空"); -// return context.getEmitter(); -// } -// -// workFlowStarterService.resumeFlow( -// reSumeRunner.getRuntimeUuid(), -// reSumeRunner.getFeedbackContent(), -// context.getEmitter() -// ); -// -// return context.getEmitter(); -// } -//} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/WorkflowChatHandler.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/WorkflowChatHandler.java deleted file mode 100644 index 322ffc06..00000000 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/handler/WorkflowChatHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -//package org.ruoyi.service.chat.handler; -// -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import org.ruoyi.common.chat.base.ThreadContext; -//import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner; -//import org.ruoyi.common.chat.entity.chat.ChatContext; -//import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; -//import org.ruoyi.common.core.utils.ObjectUtils; -//import org.springframework.core.annotation.Order; -//import org.springframework.stereotype.Component; -//import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -// -///** -// * 工作流对话处理器 -// *

-// * 处理 enableWorkFlow=true 的场景,启动工作流对话 -// * -// * @author ageerle@163.com -// * @date 2025/12/13 -// */ -//@Slf4j -//@Component -//@Order(2) -//@RequiredArgsConstructor -//public class WorkflowChatHandler implements ChatHandler { -// -// private final IWorkFlowStarterService workFlowStarterService; -// -// @Override -// public boolean supports(ChatContext context) { -// Boolean enableWorkFlow = context.getChatRequest().getEnableWorkFlow(); -// return enableWorkFlow != null && enableWorkFlow; -// } -// -// @Override -// public SseEmitter handle(ChatContext context) { -// log.info("处理工作流对话,用户: {}, 会话: {}", -// context.getUserId(), context.getChatRequest().getSessionId()); -// -// WorkFlowRunner runner = context.getChatRequest().getWorkFlowRunner(); -// if (ObjectUtils.isEmpty(runner)) { -// log.warn("工作流参数为空"); -// return context.getEmitter(); -// } -// -// return workFlowStarterService.streaming( -// ThreadContext.getCurrentUser(), -// runner.getUuid(), -// runner.getInputs(), -// context.getChatRequest().getSessionId() -// ); -// } -//} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java index 9c7b32d1..ba1c5ac8 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java @@ -1,25 +1,48 @@ package org.ruoyi.service.chat.impl; import cn.dev33.satoken.stp.StpUtil; +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.*; +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.RequiredArgsConstructor; 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.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.enums.RoleType; import org.ruoyi.common.chat.service.chat.IChatModelService; import org.ruoyi.common.chat.service.chat.IChatService; +import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService; +import org.ruoyi.common.core.utils.ObjectUtils; import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.common.sse.core.SseEmitterManager; import org.ruoyi.common.sse.utils.SseMessageUtils; import org.ruoyi.domain.bo.vector.QueryVectorBo; import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo; import org.ruoyi.factory.ChatServiceFactory; +import org.ruoyi.mcp.service.core.ToolProviderFactory; import org.ruoyi.service.chat.AbstractChatService; import org.ruoyi.service.chat.IChatMessageService; import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore; @@ -62,12 +85,18 @@ public class ChatServiceFacade implements IChatService { private final IChatMessageService chatMessageService; + private final IWorkFlowStarterService workFlowStarterService; + + private final ToolProviderFactory toolProviderFactory; + /** * 内存实例缓存,避免同一会话重复创建 * Key: sessionId, Value: MessageWindowChatMemory实例 */ private static final Map memoryCache = new ConcurrentHashMap<>(); + + /** * 统一聊天入口 - SSE流式响应 * @@ -76,6 +105,11 @@ public class ChatServiceFacade implements IChatService { */ public SseEmitter sseChat(ChatRequest chatRequest) { + // 4. 具体的服务实现 + Long userId = LoginHelper.getUserId(); + String tokenValue = StpUtil.getTokenValue(); + SseEmitter emitter = sseEmitterManager.connect(userId, tokenValue); + // 1. 根据模型名称查询完整配置 ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); if (chatModelVo == null) { @@ -85,14 +119,17 @@ public class ChatServiceFacade implements IChatService { // 2. 构建上下文消息列表 List contextMessages = buildContextMessages(chatRequest); - // 3. 路由服务提供商 + // 3. 处理特殊聊天模式(工作流、人机交互恢复、思考模式) + SseEmitter specialResult = handleSpecialChatModes(chatRequest, contextMessages, chatModelVo, emitter); + if (specialResult != null) { + return specialResult; + } + + // 4. 路由服务提供商 String providerCode = chatModelVo.getProviderCode(); log.info("路由到服务提供商: {}, 模型: {}", providerCode, chatRequest.getModel()); AbstractChatService chatService = chatServiceFactory.getOriginalService(providerCode); - // 4. 具体的服务实现 - Long userId = LoginHelper.getUserId(); - String tokenValue = StpUtil.getTokenValue(); - SseEmitter emitter = sseEmitterManager.connect(userId, tokenValue); + StreamingChatResponseHandler handler = createResponseHandler(userId, tokenValue,chatRequest); @@ -105,6 +142,131 @@ public class ChatServiceFacade implements IChatService { return emitter; } + /** + * 处理特殊聊天模式(工作流、人机交互恢复、思考模式) + * + * @param chatRequest 聊天请求 + * @param contextMessages 上下文消息列表(可能被修改) + * @param chatModelVo 聊天模型配置 + * @param emitter SSE发射器 + * @return 如果需要提前返回则返回SseEmitter,否则返回null + */ + private SseEmitter handleSpecialChatModes(ChatRequest chatRequest, List contextMessages, + ChatModelVo chatModelVo, SseEmitter emitter) { + // 处理工作流对话 + if (chatRequest.getEnableWorkFlow()) { + log.info("处理工作流对话,会话: {}", chatRequest.getSessionId()); + + WorkFlowRunner runner = chatRequest.getWorkFlowRunner(); + if (ObjectUtils.isEmpty(runner)) { + log.warn("工作流参数为空"); + } + + return workFlowStarterService.streaming( + ThreadContext.getCurrentUser(), + runner.getUuid(), + runner.getInputs(), + chatRequest.getSessionId() + ); + } + + // 处理人机交互恢复 + if (chatRequest.getIsResume()) { + log.info("处理人机交互恢复"); + + ReSumeRunner reSumeRunner = chatRequest.getReSumeRunner(); + if (ObjectUtils.isEmpty(reSumeRunner)) { + log.warn("人机交互恢复参数为空"); + return emitter; + } + + workFlowStarterService.resumeFlow( + reSumeRunner.getRuntimeUuid(), + reSumeRunner.getFeedbackContent(), + emitter + ); + + return emitter; + } + + // 处理思考模式 + if (chatRequest.getEnableThinking()) { + handleThinkingMode(chatRequest, contextMessages, chatModelVo); + } + + return null; + } + + /** + * 处理思考模式 + * + * @param chatRequest 聊天请求 + * @param contextMessages 上下文消息列表 + * @param chatModelVo 聊天模型配置 + */ + private void handleThinkingMode(ChatRequest chatRequest, List contextMessages, ChatModelVo chatModelVo) { + // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 + McpTransport transport = new StdioMcpTransport.Builder() + .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", "bing-cn-mcp")) + .logEvents(true) + .build(); + + McpClient mcpClient = new DefaultMcpClient.Builder() + .transport(transport) + .build(); + + ToolProvider toolProvider = McpToolProvider.builder() + .mcpClients(List.of(mcpClient)) + .build(); + + // 配置echarts MCP + McpTransport transport1 = new StdioMcpTransport.Builder() + .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", "mcp-echarts")) + .logEvents(true) + .build(); + + McpClient mcpClient1 = new DefaultMcpClient.Builder() + .transport(transport1) + .build(); + + ToolProvider toolProvider1 = McpToolProvider.builder() + .mcpClients(List.of(mcpClient1)) + .build(); + + // 配置模型 + OpenAiChatModel plannerModel = OpenAiChatModel.builder() + .baseUrl(chatModelVo.getApiHost()) + .apiKey(chatModelVo.getApiKey()) + .modelName(chatModelVo.getModelName()) + .build(); + + // 构建各Agent + SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) + .chatModel(plannerModel) + .tools(new QueryAllTablesTool(), new QueryTableSchemaTool(), new ExecuteSqlQueryTool()) + .build(); + + WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) + .chatModel(plannerModel) + .toolProvider(toolProvider) + .build(); + + ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) + .chatModel(plannerModel) + .toolProvider(toolProvider1) + .build(); + + // 构建监督者Agent + SupervisorAgent supervisor = AgenticServices.supervisorBuilder() + .chatModel(plannerModel) + .subAgents(sqlAgent, chartGenerationAgent) + .responseStrategy(SupervisorResponseStrategy.LAST) + .build(); + + String invoke = supervisor.invoke(chatRequest.getContent()); + contextMessages.add(AiMessage.from(invoke)); + } + /** * 支持外部 handler 的对话接口(跨模块调用) * 同时发送到 SSE 和外部 handler