修改数据库读取工具

This commit is contained in:
zhang
2026-02-24 16:07:18 +08:00
parent f25ebdf9ec
commit 26bcfbba8a
9 changed files with 214 additions and 211 deletions

View File

@@ -58,9 +58,16 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root username: root
password: 123456 password: Qzhang450000
agent:
url: jdbc:mysql://127.0.0.1:3306/yunding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# url: jdbc:mysql://localhost:3306/agent_db
username: root
password: Qzhang450000
driverClassName: com.mysql.cj.jdbc.Driver
hikari: hikari:
# 最大连接池数量 # 最大连接池数量
maxPoolSize: 20 maxPoolSize: 20
@@ -77,11 +84,7 @@ spring:
# 多久检查一次连接的活性 # 多久检查一次连接的活性
keepaliveTime: 30000 keepaliveTime: 30000
agent:
mysql:
url: jdbc:mysql://localhost:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
--- # 上传文件地址 --- # 上传文件地址
sys: sys:
@@ -265,3 +268,4 @@ justauth:
client-secret: 1f7d08**********5b7**********29e client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea redirect-uri: ${justauth.address}/social-callback?source=gitea
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task"

View File

@@ -1,44 +1,44 @@
package org.ruoyi.agent.config; // package org.ruoyi.agent.config;
import com.zaxxer.hikari.HikariConfig; // import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; // import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource; // import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; // import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; // import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; // import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; // import org.springframework.context.annotation.Configuration;
/** // /**
* Agent MySQL 数据源配置 // * Agent MySQL 数据源配置
* 为 Agent 配置独立的 MySQL 数据库连接池HikariCP // * 为 Agent 配置独立的 MySQL 数据库连接池HikariCP
* // *
* 仅在 agent.mysql.enabled=true 时启用 // * 仅在 agent.mysql.enabled=true 时启用
*/ // */
@Configuration // @Configuration
@EnableConfigurationProperties(AgentMysqlProperties.class) // @EnableConfigurationProperties(AgentMysqlProperties.class)
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") // @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
public class AgentMysqlConfig { // public class AgentMysqlConfig {
/** // /**
* 创建 Agent 专用的数据源 // * 创建 Agent 专用的数据源
* 与项目主数据源隔离,独立管理 // * 与项目主数据源隔离,独立管理
* // *
* @param properties Agent MySQL 配置属性 // * @param properties Agent MySQL 配置属性
* @return HikariCP 数据源 // * @return HikariCP 数据源
*/ // */
@Bean("agentDataSource") // @Bean("agentDataSource")
public DataSource agentDataSource(AgentMysqlProperties properties) { // public DataSource agentDataSource(AgentMysqlProperties properties) {
HikariConfig config = new HikariConfig(); // HikariConfig config = new HikariConfig();
config.setJdbcUrl(properties.getUrl()); // config.setJdbcUrl(properties.getUrl());
config.setUsername(properties.getUsername()); // config.setUsername(properties.getUsername());
config.setPassword(properties.getPassword()); // config.setPassword(properties.getPassword());
config.setDriverClassName("com.mysql.cj.jdbc.Driver"); // config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setMaximumPoolSize(properties.getMaxPoolSize()); // config.setMaximumPoolSize(properties.getMaxPoolSize());
config.setMinimumIdle(properties.getMinIdle()); // config.setMinimumIdle(properties.getMinIdle());
config.setConnectionTimeout(30000); // config.setConnectionTimeout(30000);
config.setIdleTimeout(600000); // config.setIdleTimeout(600000);
config.setMaxLifetime(1800000); // config.setMaxLifetime(1800000);
return new HikariDataSource(config); // return new HikariDataSource(config);
} // }
} // }

View File

@@ -16,7 +16,8 @@ public class TableStructure {
* 表名 * 表名
*/ */
private String tableName; private String tableName;
private String tableType; // 添加此字段BASE TABLE 或 VIEW
/** /**
* 表注释/说明 * 表注释/说明
*/ */

View File

@@ -1,19 +1,19 @@
package org.ruoyi.agent.manager; package org.ruoyi.agent.manager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/** /**
* 架构初始化器 * 架构初始化器
* 在应用启动完成后自动初始化表结构缓存 * 在应用启动完成后自动初始化表结构缓存
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") // @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
public class TableSchemaInitializer { public class TableSchemaInitializer {
@Autowired(required = false) @Autowired(required = false)

View File

@@ -1,16 +1,30 @@
package org.ruoyi.agent.manager; package org.ruoyi.agent.manager;
import lombok.extern.slf4j.Slf4j; import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.ruoyi.agent.domain.ColumnInfo; import org.ruoyi.agent.domain.ColumnInfo;
import org.ruoyi.agent.domain.TableStructure; import org.ruoyi.agent.domain.TableStructure;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.*; import com.baomidou.dynamic.datasource.annotation.DS;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j;
import java.util.stream.Collectors;
/** /**
* 表结构管理器 * 表结构管理器
@@ -24,12 +38,15 @@ import java.util.stream.Collectors;
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") @DS("agent")
public class TableSchemaManager { public class TableSchemaManager {
@Autowired(required = false) @Autowired(required = false)
private DataSource agentDataSource; private DataSource agentDataSource;
@Value("${AGENT_ALLOWED_TABLES}")
private String allowedTables;
/** /**
* 表结构缓存 (表名 -> 表结构) * 表结构缓存 (表名 -> 表结构)
* 使用 ConcurrentHashMap 支持高并发访问 * 使用 ConcurrentHashMap 支持高并发访问
@@ -55,12 +72,12 @@ public class TableSchemaManager {
if (initialized) { if (initialized) {
return; return;
} }
try { try {
log.info("Initializing database schema cache..."); log.info("Initializing database schema cache...");
loadAllowedTableSchemas(); loadAllowedTableSchemas();
initialized = true; initialized = true;
log.info("Schema cache initialized with {} tables", schemaCache.size()); log.info("Schema cache initialized with {} tables", schemaCache.size());
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to initialize schema cache", e); log.error("Failed to initialize schema cache", e);
} }
@@ -103,6 +120,7 @@ public class TableSchemaManager {
try (ResultSet tableRs = metaData.getTables(conn.getCatalog(), null, tableName, new String[]{"TABLE"})) { try (ResultSet tableRs = metaData.getTables(conn.getCatalog(), null, tableName, new String[]{"TABLE"})) {
if (tableRs.next()) { if (tableRs.next()) {
table.setTableComment(tableRs.getString("REMARKS")); table.setTableComment(tableRs.getString("REMARKS"));
table.setTableType(tableRs.getString("TABLE_TYPE"));
} }
} }
@@ -183,7 +201,6 @@ public class TableSchemaManager {
* 获取所有允许的表名 * 获取所有允许的表名
*/ */
public List<String> getAllowedTableNames() { public List<String> getAllowedTableNames() {
String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
if (allowedTables == null || allowedTables.trim().isEmpty()) { if (allowedTables == null || allowedTables.trim().isEmpty()) {
log.warn("AGENT_ALLOWED_TABLES not configured"); log.warn("AGENT_ALLOWED_TABLES not configured");
return new ArrayList<>(); return new ArrayList<>();
@@ -224,7 +241,7 @@ public class TableSchemaManager {
* 检查表是否在允许列表中 * 检查表是否在允许列表中
*/ */
private boolean isTableAllowed(String tableName) { private boolean isTableAllowed(String tableName) {
String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); // String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
if (allowedTables == null || allowedTables.trim().isEmpty()) { if (allowedTables == null || allowedTables.trim().isEmpty()) {
return false; return false;
} }

View File

@@ -1,12 +1,5 @@
package org.ruoyi.agent.tool; package org.ruoyi.agent.tool;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -16,17 +9,26 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
/** /**
* 执行 SQL 查询的 Tool * 执行 SQL 查询的 Tool
* 执行指定的 SELECT SQL 查询并返回结果 * 执行指定的 SELECT SQL 查询并返回结果
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
public class ExecuteSqlQueryTool { public class ExecuteSqlQueryTool {
@Autowired(required = false) @Autowired(required = false)
private DataSource agentDataSource; private DataSource dataSource;
/** /**
* 执行 SELECT SQL 查询 * 执行 SELECT SQL 查询
@@ -37,6 +39,8 @@ public class ExecuteSqlQueryTool {
*/ */
@Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user") @Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user")
public String executeSql(String sql) { public String executeSql(String sql) {
// 2. 手动推入数据源上下文
DynamicDataSourceContextHolder.push("agent");
if (sql == null || sql.trim().isEmpty()) { if (sql == null || sql.trim().isEmpty()) {
return "Error: SQL query cannot be empty"; return "Error: SQL query cannot be empty";
} }
@@ -48,11 +52,11 @@ public class ExecuteSqlQueryTool {
} }
try { try {
if (agentDataSource == null) { if (dataSource == null) {
return "Error: Database datasource not configured"; return "Error: Database datasource not configured";
} }
try (Connection connection = agentDataSource.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql); try (PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery()) { ResultSet resultSet = preparedStatement.executeQuery()) {
@@ -82,7 +86,12 @@ public class ExecuteSqlQueryTool {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Error executing SQL: {}", sql, e); log.error("Error executing SQL: {}", sql, e);
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
return "Error: " + e.getMessage(); return "Error: " + e.getMessage();
} finally {
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
} }
} }

View File

@@ -1,20 +1,14 @@
package org.ruoyi.agent.tool; package org.ruoyi.agent.tool;
import com.zaxxer.hikari.HikariConfig; import java.util.List;
import com.zaxxer.hikari.HikariDataSource;
import dev.langchain4j.agent.tool.Tool; import org.ruoyi.agent.domain.TableStructure;
import lombok.extern.slf4j.Slf4j; import org.ruoyi.agent.manager.TableSchemaManager;
import org.ruoyi.agent.config.AgentMysqlProperties;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sql.DataSource; import dev.langchain4j.agent.tool.Tool;
import java.sql.Connection; import lombok.extern.slf4j.Slf4j;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/** /**
* 查询数据库所有表的 Tool * 查询数据库所有表的 Tool
@@ -22,12 +16,11 @@ import java.util.List;
*/ */
@Slf4j @Slf4j
@Component @Component
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true")
public class QueryAllTablesTool { public class QueryAllTablesTool {
@Autowired(required = false)
private DataSource agentDataSource; @Autowired
private TableSchemaManager tableSchemaManager; // 注入管理器
/** /**
* 查询数据库中所有表 * 查询数据库中所有表
* 返回数据库中存在的所有表的列表 * 返回数据库中存在的所有表的列表
@@ -37,44 +30,36 @@ public class QueryAllTablesTool {
@Tool("Query all tables in the database and return table names and basic information") @Tool("Query all tables in the database and return table names and basic information")
public String queryAllTables() { public String queryAllTables() {
try { try {
if (agentDataSource == null) { // 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑)
return "Error: Database datasource not configured"; List<TableStructure> tableSchemas = tableSchemaManager.getAllowedTableSchemas();
}
if (tableSchemas == null || tableSchemas.isEmpty()) {
try (Connection connection = agentDataSource.getConnection()) { return "No tables found in database or cache is empty.";
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"});
List<String> tableNames = new ArrayList<>();
List<String> tableDetails = new ArrayList<>();
while (resultSet.next()) {
String tableName = resultSet.getString("TABLE_NAME");
String tableComment = resultSet.getString("REMARKS");
String tableType = resultSet.getString("TABLE_TYPE");
tableNames.add(tableName);
tableDetails.add(String.format("- %s (%s) - %s",
tableName, tableType, tableComment != null ? tableComment : "No comment"));
} }
resultSet.close();
// 2. 格式化结果
if (tableNames.isEmpty()) {
return "No tables found in database";
}
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
result.append("Found ").append(tableNames.size()).append(" tables:\n"); result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n");
for (String detail : tableDetails) {
result.append(detail).append("\n"); for (TableStructure schema : tableSchemas) {
String tableName = schema.getTableName();
String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE";
String tableComment = schema.getTableComment();
result.append(String.format("- %s (%s) - %s\n",
tableName,
tableType,
tableComment != null ? tableComment : "No comment"));
} }
log.info("Successfully queried {} tables", tableNames.size()); log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size());
return result.toString(); return result.toString();
} catch (Exception e) {
log.error("Error retrieving tables from cache", e);
return "Error: " + e.getMessage();
} }
} catch (Exception e) {
log.error("Error querying all tables", e);
return "Error: " + e.getMessage();
}
} }
} }

View File

@@ -1,83 +1,57 @@
package org.ruoyi.agent.tool; package org.ruoyi.agent.tool;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
/** import javax.sql.DataSource;
* 查询表建表详情的 Tool
* 根据表名查询该表的建表 SQL 语句 import org.springframework.beans.factory.annotation.Autowired;
*/ import org.springframework.stereotype.Component;
@Slf4j
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
@Component @Component
@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") @Slf4j
public class QueryTableSchemaTool { public class QueryTableSchemaTool {
@Autowired(required = false) @Autowired(required = false)
private DataSource agentDataSource; private DataSource dataSource;
/**
* 根据表名查询建表详情
* 返回指定表的 CREATE TABLE 语句
*
* @param tableName 表名
* @return 包含建表 SQL 的结果
*/
@Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name") @Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name")
public String queryTableSchema(String tableName) { public String queryTableSchema(String tableName) {
// 2. 手动推入数据源上下文
DynamicDataSourceContextHolder.push("agent");
if (tableName == null || tableName.trim().isEmpty()) { if (tableName == null || tableName.trim().isEmpty()) {
return "Error: Table name cannot be empty"; return "Error: Table name cannot be empty";
} }
// 验证表名有效性,防止 SQL 注入 if (!tableName.matches("^[a-zA-Z0-9_]+$")) {
if (!isValidIdentifier(tableName)) {
return "Error: Invalid table name format"; return "Error: Invalid table name format";
} }
try { String sql = "SHOW CREATE TABLE `" + tableName + "`";
if (agentDataSource == null) {
return "Error: Database datasource not configured"; try (Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("Create Table");
} }
try (Connection connection = agentDataSource.getConnection()) { return "Table not found: " + tableName;
String sql = "SHOW CREATE TABLE " + tableName;
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
String createTableSql = resultSet.getString("Create Table");
resultSet.close();
preparedStatement.close();
log.info("Successfully queried schema for table: {}", tableName);
return "CREATE TABLE DDL for " + tableName + ":\n\n" + createTableSql;
}
resultSet.close();
preparedStatement.close();
return "Error: Table not found or not accessible: " + tableName;
}
} catch (Exception e) { } catch (Exception e) {
log.error("Error querying table schema for table: {}", tableName, e); // 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
log.error("Error querying table schema: {}", tableName, e);
return "Error: " + e.getMessage(); return "Error: " + e.getMessage();
} finally {
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
} }
} }
/**
* 验证是否为有效的 SQL 标识符
*/
private boolean isValidIdentifier(String identifier) {
if (identifier == null || identifier.isEmpty()) {
return false;
}
return identifier.matches("^[a-zA-Z0-9_\\.]+$");
}
} }

View File

@@ -3,6 +3,7 @@ package org.ruoyi.service.chat.impl;
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;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.mcp.McpToolProvider; import dev.langchain4j.mcp.McpToolProvider;
@@ -325,26 +326,26 @@ public abstract class AbstractStreamingChatService implements IChatService {
protected String doAgent(String userMessage, ChatModelVo chatModelVo) { protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
// 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器
// 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取) // 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取)
McpTransport transport = new StdioMcpTransport.Builder() // McpTransport transport = new StdioMcpTransport.Builder()
.command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", // .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
"bing-cn-mcp" // "bing-cn-mcp"
)) // ))
.logEvents(true) // .logEvents(true)
.build(); // .build();
// 步骤2: 创建MCP客户端 // // 步骤2: 创建MCP客户端
McpClient mcpClient = new DefaultMcpClient.Builder() // McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport) // .transport(transport)
.build(); // .build();
// 步骤3: 配置工具提供者 // // 步骤3: 配置工具提供者
ToolProvider toolProvider = McpToolProvider.builder() // ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(List.of(mcpClient)) // .mcpClients(List.of(mcpClient))
.build(); // .build();
McpTransport transport1 = new StdioMcpTransport.Builder() McpTransport transport1 = new StdioMcpTransport.Builder()
.command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", .command(List.of("npx", "-y",
"mcp-echarts" "mcp-echarts"
)) ))
.logEvents(true) .logEvents(true)
@@ -361,40 +362,52 @@ public abstract class AbstractStreamingChatService implements IChatService {
.build(); .build();
// 步骤4: 配置OpenAI模型 // 步骤4: 配置OpenAI模型
OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() // OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder()
.baseUrl(chatModelVo.getApiHost()) // .baseUrl(chatModelVo.getApiHost())
// .apiKey(chatModelVo.getApiKey())
// .modelName(chatModelVo.getModelName())
// .build();
QwenChatModel qwenChatModel = QwenChatModel.builder()
// .baseUrl(chatModelVo.getApiHost())
.apiKey(chatModelVo.getApiKey()) .apiKey(chatModelVo.getApiKey())
.modelName(chatModelVo.getModelName()) .modelName(chatModelVo.getModelName())
.build(); .build();
SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class)
.chatModel(PLANNER_MODEL) .chatModel(
qwenChatModel)
.tools( .tools(
new QueryAllTablesTool(), SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取
new QueryTableSchemaTool(), SpringUtils.getBean(QueryTableSchemaTool.class),
new ExecuteSqlQueryTool() SpringUtils.getBean(ExecuteSqlQueryTool.class)
) )
.build(); .build();
WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) // WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class)
.chatModel(PLANNER_MODEL) // .chatModel(PLANNER_MODEL)
.toolProvider(toolProvider) // .toolProvider(toolProvider)
.build(); // .build();
ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class)
.chatModel(PLANNER_MODEL) .chatModel(
qwenChatModel)
.toolProvider(toolProvider1) .toolProvider(toolProvider1)
.build(); .build();
String res = sqlAgent.getData(userMessage);
String res1 = chartGenerationAgent.generateChart(res);
System.out.println(res1);
System.out.println(res);
SupervisorAgent supervisor = AgenticServices SupervisorAgent supervisor = AgenticServices
.supervisorBuilder() .supervisorBuilder()
.chatModel(PLANNER_MODEL) .chatModel(qwenChatModel)
.subAgents(sqlAgent, chartGenerationAgent) .subAgents(sqlAgent, chartGenerationAgent)
.responseStrategy(SupervisorResponseStrategy.LAST) .responseStrategy(SupervisorResponseStrategy.LAST)
.build(); .build();
String invoke = supervisor.invoke(userMessage); String invoke = supervisor.invoke(userMessage);
System.out.println(invoke); System.out.println(invoke);
return invoke; return res1;
} }
} }