mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-13 20:53:42 +08:00
Compare commits
5 Commits
1d1b72c6e2
...
ace7e961a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ace7e961a4 | ||
|
|
5dc21653c0 | ||
|
|
d0d4b2229f | ||
|
|
07c9727345 | ||
|
|
689adf079d |
@@ -16,7 +16,7 @@ public class RuoYiAIApplication {
|
|||||||
SpringApplication application = new SpringApplication(RuoYiAIApplication.class);
|
SpringApplication application = new SpringApplication(RuoYiAIApplication.class);
|
||||||
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||||
application.run(args);
|
application.run(args);
|
||||||
System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙");
|
System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -73,6 +73,9 @@ public abstract class AbstractStreamingChatService implements IChatService {
|
|||||||
*/
|
*/
|
||||||
private static final Map<Object, MessageWindowChatMemory> memoryCache = new ConcurrentHashMap<>();
|
private static final Map<Object, MessageWindowChatMemory> memoryCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义聊天流程骨架
|
* 定义聊天流程骨架
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
package org.ruoyi.service.chat.impl.provider;
|
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.AgenticServices;
|
||||||
import dev.langchain4j.agentic.supervisor.SupervisorAgent;
|
import dev.langchain4j.agentic.supervisor.SupervisorAgent;
|
||||||
import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy;
|
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.DefaultMcpClient;
|
||||||
import dev.langchain4j.mcp.client.McpClient;
|
import dev.langchain4j.mcp.client.McpClient;
|
||||||
import dev.langchain4j.mcp.client.transport.McpTransport;
|
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.StreamingChatModel;
|
||||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||||
import dev.langchain4j.service.tool.ToolProvider;
|
import dev.langchain4j.service.tool.ToolProvider;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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服务调用
|
* qianWenAI服务调用
|
||||||
@@ -41,13 +41,6 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
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";
|
private static final String UPLOAD_FILE_API_PREFIX = "fileid";
|
||||||
@@ -108,49 +101,45 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
|||||||
* @return 返回LLM信息
|
* @return 返回LLM信息
|
||||||
*/
|
*/
|
||||||
protected String doAgent(String userMessage,ChatModelVo chatModelVo) {
|
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<ToolSpecification> toolSpecifications = mcpClient.listTools();
|
|
||||||
System.out.println(toolSpecifications);
|
|
||||||
|
|
||||||
// 步骤3:将mcp对象包装
|
|
||||||
ToolProvider toolProvider = McpToolProvider.builder()
|
|
||||||
.mcpClients(List.of(mcpClient))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// 步骤4:加载LLM模型对话
|
// 步骤4:加载LLM模型对话
|
||||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||||
.baseUrl(QWEN_API_HOST)
|
|
||||||
.apiKey(chatModelVo.getApiKey())
|
.apiKey(chatModelVo.getApiKey())
|
||||||
.modelName(chatModelVo.getModelName())
|
.modelName(chatModelVo.getModelName())
|
||||||
.build();
|
.build();
|
||||||
|
McpTransport echart = new StdioMcpTransport.Builder()
|
||||||
// 步骤5:将MCP对象由智能体Agent管控
|
.command(List.of("uv",
|
||||||
McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class)
|
"--directory",
|
||||||
.chatModel(qwenChatModel)
|
"/Users/zhangmingming/data/coder/LLM/MCP/cicd-pipeline-example/",
|
||||||
.toolProvider(toolProvider)
|
"run",
|
||||||
|
"text2sql-mcp"
|
||||||
|
))
|
||||||
|
.logEvents(true)
|
||||||
.build();
|
.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对象由超级智能体管控
|
// 步骤6:将所有MCP对象由超级智能体管控
|
||||||
SupervisorAgent supervisor = AgenticServices
|
SupervisorAgent supervisor = AgenticServices
|
||||||
.supervisorBuilder()
|
.supervisorBuilder()
|
||||||
.chatModel(qwenChatModel)
|
.chatModel(qwenChatModel)
|
||||||
.subAgents(mcpAgent)
|
.subAgents(chartGenerationAgent)
|
||||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user