diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java index 41870120..679f4166 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java @@ -16,7 +16,7 @@ public class RuoYiAIApplication { SpringApplication application = new SpringApplication(RuoYiAIApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); - System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙"); + System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙"); } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java new file mode 100644 index 00000000..436220fe --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/EchartsAgent.java @@ -0,0 +1,89 @@ +package org.ruoyi.agent; + +import dev.langchain4j.agentic.Agent; +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; + +/** + * Text2SQL and Echarts Chart Generation Agent + * An intelligent assistant that converts natural language queries into SQL, + * executes database queries, and generates Echarts visualizations. + */ +public interface EchartsAgent { + + @SystemMessage(""" + You are a data visualization assistant that generates Echarts chart configurations. + + CRITICAL OUTPUT REQUIREMENTS: + - Return Echarts JSON wrapped in markdown code block + - Use this exact format: ```json\n{...}\n``` + - The JSON inside must be valid Echarts configuration + - Frontend expects markdown format for proper parsing + + Your workflow: + 1. Use MCP tools to query the database and get data + 2. The MCP tool returns data in this structure: + {"data": [{"dict_type": "value1", "count": 10}, {"dict_type": "value2", "count": 20}, ...]} + 3. Transform this data into Echarts configuration + 4. Return ONLY the Echarts JSON + + Data transformation rules: + - Extract array elements into xAxis categories and series data + - For the example above: xAxis.data = ["value1", "value2"], series.data = [10, 20] + - Choose chart type based on request: bar (default), line, pie, etc. + + Expected output format (bar chart example): + ```json + { + "title": { + "text": "Dict Type Distribution", + "left": "center" + }, + "tooltip": { + "trigger": "axis" + }, + "xAxis": { + "type": "category", + "data": ["type1", "type2", "type3"] + }, + "yAxis": { + "type": "value" + }, + "series": [ + { + "name": "数量", + "type": "bar", + "data": [10, 20, 15] + } + ] + } + ``` + + For pie charts: + { + "title": {"text": "Chart Title", "left": "center"}, + "tooltip": {"trigger": "item"}, + "series": [{ + "type": "pie", + "radius": "50%", + "data": [ + {"value": 10, "name": "Category1"}, + {"value": 20, "name": "Category2"} + ] + }] + } + + REMEMBER: + - Always wrap JSON in ```json and ``` markers + - Use proper formatting with indentation + - This is the expected format for frontend parsing + """) + @UserMessage(""" + Generate an Echarts chart for: {{query}} + + IMPORTANT: Return the Echarts configuration JSON wrapped in markdown code block (```json...```). + """) + @Agent("Data visualization assistant that returns Echarts JSON configurations for frontend rendering") + String search(@V("query") String query); +} 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 new file mode 100644 index 00000000..92b7e3c4 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/GetNameInfo.java @@ -0,0 +1,38 @@ +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/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index c671e679..9664c9ae 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -73,6 +73,9 @@ public abstract class AbstractStreamingChatService implements IChatService { */ private static final Map memoryCache = new ConcurrentHashMap<>(); + + + /** * 定义聊天流程骨架 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java index ab583520..a8966dee 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java @@ -1,6 +1,18 @@ package org.ruoyi.service.chat.impl.provider; -import dev.langchain4j.agent.tool.ToolSpecification; +import java.util.ArrayList; +import java.util.List; + +import org.ruoyi.agent.EchartsAgent; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.ruoyi.config.McpSseConfig; +import org.ruoyi.enums.ChatModeType; +import org.ruoyi.service.chat.impl.AbstractStreamingChatService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; @@ -13,23 +25,11 @@ 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.http.StreamableHttpMcpTransport; +import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport; 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.config.McpSseConfig; -import org.ruoyi.enums.ChatModeType; -import org.ruoyi.service.chat.impl.AbstractStreamingChatService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; - -import java.util.ArrayList; -import java.util.List; /** * qianWenAI服务调用 @@ -41,13 +41,6 @@ import java.util.List; @Slf4j public class QianWenChatServiceImpl extends AbstractStreamingChatService { - @Autowired - private McpSseConfig mcpSseConfig; - - /** - * 千问开发者默认地址 - */ - private static final String QWEN_API_HOST = "https://dashscope.aliyuncs.com/api/v1"; // 添加文档解析的前缀字段 private static final String UPLOAD_FILE_API_PREFIX = "fileid"; @@ -108,49 +101,45 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { * @return 返回LLM信息 */ protected String doAgent(String userMessage,ChatModelVo chatModelVo) { - // 判断是否开启MCP服务 - if (!mcpSseConfig.isEnabled()) { - return ""; - } - - // 步骤1:根据SSE对外暴露端点连接 - McpTransport httpMcpTransport = new StreamableHttpMcpTransport.Builder(). - url(mcpSseConfig.getUrl()). - logRequests(true). - build(); - - // 步骤2:开启客户端连接 - McpClient mcpClient = new DefaultMcpClient.Builder() - .transport(httpMcpTransport) - .build(); - - // 获取所有mcp工具 - List toolSpecifications = mcpClient.listTools(); - System.out.println(toolSpecifications); - - // 步骤3:将mcp对象包装 - ToolProvider toolProvider = McpToolProvider.builder() - .mcpClients(List.of(mcpClient)) - .build(); - + // 步骤4:加载LLM模型对话 QwenChatModel qwenChatModel = QwenChatModel.builder() - .baseUrl(QWEN_API_HOST) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .build(); - - // 步骤5:将MCP对象由智能体Agent管控 - McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class) - .chatModel(qwenChatModel) - .toolProvider(toolProvider) + McpTransport echart = new StdioMcpTransport.Builder() + .command(List.of("uv", + "--directory", + "/Users/zhangmingming/data/coder/LLM/MCP/cicd-pipeline-example/", + "run", + "text2sql-mcp" + )) + .logEvents(true) .build(); + // 步骤2: 创建MCP客户端 + McpClient echartClient = new DefaultMcpClient.Builder() + .transport(echart) + .build(); + + // 步骤3: 配置工具提供者 + ToolProvider echartToolProvider = McpToolProvider.builder() + .mcpClients(List.of( + echartClient)) + .build(); + + EchartsAgent chartGenerationAgent = AgenticServices.agentBuilder( + EchartsAgent.class) + .chatModel(qwenChatModel) + .toolProvider(echartToolProvider) + .build(); + + // 步骤6:将所有MCP对象由超级智能体管控 SupervisorAgent supervisor = AgenticServices .supervisorBuilder() .chatModel(qwenChatModel) - .subAgents(mcpAgent) + .subAgents(chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) .build();