mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-11 02:37:06 +00:00
feat:恢复mcp模块 动态agent
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ nbdist/
|
|||||||
!*/build/*.xml
|
!*/build/*.xml
|
||||||
|
|
||||||
.flattened-pom.xml
|
.flattened-pom.xml
|
||||||
|
/.claude/settings.local.json
|
||||||
|
|||||||
7
pom.xml
7
pom.xml
@@ -397,13 +397,6 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MCP模块 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ruoyi</groupId>
|
|
||||||
<artifactId>ruoyi-mcp</artifactId>
|
|
||||||
<version>${revision}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 企业微信SDK -->
|
<!-- 企业微信SDK -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.binarywang</groupId>
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
|||||||
@@ -110,11 +110,6 @@
|
|||||||
<artifactId>ruoyi-aiflow</artifactId>
|
<artifactId>ruoyi-aiflow</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MCP模块 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ruoyi</groupId>
|
|
||||||
<artifactId>ruoyi-mcp</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>de.codecentric</groupId>
|
<groupId>de.codecentric</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.ruoyi.config;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BeanDefinitionRegistry后置处理器
|
||||||
|
* 解决 MapStruct Plus 生成的 mapper 冲突问题
|
||||||
|
*
|
||||||
|
* @author ruoyi team
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class MapperConflictResolver implements BeanDefinitionRegistryPostProcessor {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MapperConflictResolver.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||||
|
String[] beanNames = registry.getBeanDefinitionNames();
|
||||||
|
|
||||||
|
// 查找冲突的 mapper bean
|
||||||
|
for (String beanName : beanNames) {
|
||||||
|
if (beanName.equals("chatMessageBoToChatMessageMapperImpl")) {
|
||||||
|
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
|
||||||
|
String beanClassName = beanDefinition.getBeanClassName();
|
||||||
|
|
||||||
|
log.info("Found mapper bean: {} -> {}", beanName, beanClassName);
|
||||||
|
|
||||||
|
// 如果是 org.ruoyi.domain.bo.chat 包下的(冲突的),移除它
|
||||||
|
if (beanClassName != null && beanClassName.startsWith("org.ruoyi.domain.bo.chat")) {
|
||||||
|
log.warn("Removing conflicting bean definition: {} ({})", beanName, beanClassName);
|
||||||
|
registry.removeBeanDefinition(beanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||||
|
// 不需要实现
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
<module>ruoyi-demo</module>
|
<module>ruoyi-demo</module>
|
||||||
<module>ruoyi-generator</module>
|
<module>ruoyi-generator</module>
|
||||||
<module>ruoyi-job</module>
|
<module>ruoyi-job</module>
|
||||||
<module>ruoyi-mcp</module>
|
|
||||||
<module>ruoyi-system</module>
|
<module>ruoyi-system</module>
|
||||||
<module>ruoyi-wechat</module>
|
<module>ruoyi-wechat</module>
|
||||||
<module>ruoyi-workflow</module>
|
<module>ruoyi-workflow</module>
|
||||||
|
|||||||
244
ruoyi-modules/ruoyi-chat/docs/frontend-guide.md
Normal file
244
ruoyi-modules/ruoyi-chat/docs/frontend-guide.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# MCP工具管理前端开发指南
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
- Node.js >= 16.0
|
||||||
|
- Vue 3
|
||||||
|
- Element Plus
|
||||||
|
- ECharts (用于图表展示)
|
||||||
|
- Axios (用于HTTP请求)
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install element-plus echarts axios
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ruoyi-ui/
|
||||||
|
├── src/
|
||||||
|
│ ├── api/
|
||||||
|
│ │ └── mcp/
|
||||||
|
│ │ └── tool.js # API接口
|
||||||
|
│ ├── views/
|
||||||
|
│ │ └── mcp/
|
||||||
|
│ │ ├── tool/
|
||||||
|
│ │ │ └── index.vue # 工具管理页面
|
||||||
|
│ │ ├── market/
|
||||||
|
│ │ │ └── index.vue # 市场管理页面
|
||||||
|
│ │ └── log/
|
||||||
|
│ │ └── index.vue # 调用日志页面
|
||||||
|
│ └── utils/
|
||||||
|
│ └── request.js # Axios封装
|
||||||
|
```
|
||||||
|
|
||||||
|
## 菜单配置
|
||||||
|
|
||||||
|
在系统菜单管理中添加以下菜单:
|
||||||
|
|
||||||
|
### 1. MCP工具管理
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 菜单名称 | MCP工具管理 |
|
||||||
|
| 菜单类型 | 目录 |
|
||||||
|
| 显示顺序 | 1 |
|
||||||
|
| 路由地址 | mcp |
|
||||||
|
| 组件路径 | |
|
||||||
|
|
||||||
|
#### 子菜单:工具列表
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 菜单名称 | 工具列表 |
|
||||||
|
| 菜单类型 | 菜单 |
|
||||||
|
| 显示顺序 | 1 |
|
||||||
|
| 路由地址 | tool |
|
||||||
|
| 组件路径 | mcp/tool/index |
|
||||||
|
| 权限标识 | mcp:tool:list |
|
||||||
|
|
||||||
|
#### 子菜单:市场管理
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 菜单名称 | 市场管理 |
|
||||||
|
| 菜单类型 | 菜单 |
|
||||||
|
| 显示顺序 | 2 |
|
||||||
|
| 路由地址 | market |
|
||||||
|
| 组件路径 | mcp/market/index |
|
||||||
|
| 权限标识 | mcp:market:list |
|
||||||
|
|
||||||
|
#### 子菜单:调用日志
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 菜单名称 | 调用日志 |
|
||||||
|
| 菜单类型 | 菜单 |
|
||||||
|
| 显示顺序 | 3 |
|
||||||
|
| 路由地址 | log |
|
||||||
|
| 组件路径 | mcp/log/index |
|
||||||
|
| 权限标识 | mcp:tool:query |
|
||||||
|
|
||||||
|
## 权限配置
|
||||||
|
|
||||||
|
| 权限标识 | 权限名称 | 说明 |
|
||||||
|
|----------|----------|------|
|
||||||
|
| mcp:tool:list | 工具列表 | 查看工具列表 |
|
||||||
|
| mcp:tool:query | 工具查询 | 查看工具详情 |
|
||||||
|
| mcp:tool:add | 工具新增 | 新增工具 |
|
||||||
|
| mcp:tool:edit | 工具修改 | 修改工具 |
|
||||||
|
| mcp:tool:remove | 工具删除 | 删除工具 |
|
||||||
|
| mcp:tool:export | 工具导出 | 导出工具数据 |
|
||||||
|
| mcp:market:list | 市场列表 | 查看市场列表 |
|
||||||
|
| mcp:market:query | 市场查询 | 查看市场详情 |
|
||||||
|
| mcp:market:add | 市场新增 | 新增市场 |
|
||||||
|
| mcp:market:edit | 市场修改 | 修改市场 |
|
||||||
|
| mcp:market:remove | 市场删除 | 删除市场 |
|
||||||
|
|
||||||
|
## 路由配置
|
||||||
|
|
||||||
|
在路由配置文件中添加:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
path: '/mcp',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/mcp/tool',
|
||||||
|
name: 'Mcp',
|
||||||
|
meta: { title: 'MCP工具管理', icon: 'tools' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'tool',
|
||||||
|
name: 'McpTool',
|
||||||
|
component: () => import('@/views/mcp/tool/index'),
|
||||||
|
meta: { title: '工具列表', icon: 'tool' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'market',
|
||||||
|
name: 'McpMarket',
|
||||||
|
component: () => import('@/views/mcp/market/index'),
|
||||||
|
meta: { title: '市场管理', icon: 'shop' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'log',
|
||||||
|
name: 'McpLog',
|
||||||
|
component: () => import('@/views/mcp/log/index'),
|
||||||
|
meta: { title: '调用日志', icon: 'document' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API请求配置
|
||||||
|
|
||||||
|
确保Axios请求拦截器正确配置:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/utils/request.js
|
||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: process.env.VUE_APP_BASE_API,
|
||||||
|
timeout: 30000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
// 添加token
|
||||||
|
const token = localStorage.getItem('Admin-Token')
|
||||||
|
if (token) {
|
||||||
|
config.headers['Authorization'] = 'Bearer ' + token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
service.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
const res = response.data
|
||||||
|
if (res.code !== 200) {
|
||||||
|
ElMessage.error(res.msg || '请求失败')
|
||||||
|
return Promise.reject(new Error(res.msg || '请求失败'))
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发步骤
|
||||||
|
|
||||||
|
1. **复制代码文件**
|
||||||
|
- 将 `tool.js` 复制到 `src/api/mcp/` 目录
|
||||||
|
- 将 `*.vue` 文件复制到对应的视图目录
|
||||||
|
|
||||||
|
2. **安装依赖**
|
||||||
|
```bash
|
||||||
|
npm install element-plus echarts
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **配置路由**
|
||||||
|
- 在路由配置中添加MCP相关路由
|
||||||
|
|
||||||
|
4. **配置菜单**
|
||||||
|
- 在系统管理中添加菜单
|
||||||
|
|
||||||
|
5. **配置权限**
|
||||||
|
- 在系统管理中添加权限标识
|
||||||
|
|
||||||
|
6. **测试功能**
|
||||||
|
- 启动开发服务器
|
||||||
|
- 测试各项功能
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **工具类型说明**
|
||||||
|
- BUILTIN: 内置工具(系统自带,不可编辑)
|
||||||
|
- LOCAL: 本地STDIO工具(通过命令行启动)
|
||||||
|
- REMOTE: 远程HTTP工具(通过网络连接)
|
||||||
|
|
||||||
|
2. **配置JSON格式**
|
||||||
|
- LOCAL类型: `{"command": "npx", "args": ["-y", "@example/tool"], "env": {}}`
|
||||||
|
- REMOTE类型: `{"baseUrl": "http://localhost:8080/mcp"}`
|
||||||
|
|
||||||
|
3. **错误处理**
|
||||||
|
- 工具连接测试可能超时,请合理设置超时时间
|
||||||
|
- 删除工具前请确认没有正在运行的Agent使用该工具
|
||||||
|
|
||||||
|
4. **性能优化**
|
||||||
|
- 调用日志数据量大时,建议使用分页加载
|
||||||
|
- 图表数据建议缓存处理,避免频繁请求
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 跨域问题
|
||||||
|
在 `vue.config.js` 中配置代理:
|
||||||
|
```javascript
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 图表不显示
|
||||||
|
确保ECharts容器有固定高度,并在数据加载后初始化图表。
|
||||||
|
|
||||||
|
### 3. 权限不生效
|
||||||
|
检查菜单权限配置和后端接口权限注解是否一致。
|
||||||
336
ruoyi-modules/ruoyi-chat/docs/mcp-api-spec.md
Normal file
336
ruoyi-modules/ruoyi-chat/docs/mcp-api-spec.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# MCP工具管理模块 - API接口文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本文档描述了MCP工具管理模块的REST API接口,供前端开发人员参考。
|
||||||
|
|
||||||
|
## 基础信息
|
||||||
|
|
||||||
|
- **Base URL**: `/api/mcp`
|
||||||
|
- **认证方式**: Bearer Token (SaToken)
|
||||||
|
- **响应格式**: JSON
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. MCP工具管理
|
||||||
|
|
||||||
|
### 1.1 查询工具列表(分页)
|
||||||
|
|
||||||
|
**接口**: `GET /tool/list`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:list`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| name | String | 否 | 工具名称(模糊查询) |
|
||||||
|
| description | String | 否 | 工具描述(模糊查询) |
|
||||||
|
| type | String | 否 | 工具类型:LOCAL/REMOTE/BUILTIN |
|
||||||
|
| status | String | 否 | 状态:0-启用, 1-禁用 |
|
||||||
|
| pageNum | Integer | 是 | 页码,默认1 |
|
||||||
|
| pageSize | Integer | 是 | 每页数量,默认10 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "ReadFileTool",
|
||||||
|
"description": "读取文件内容工具",
|
||||||
|
"type": "BUILTIN",
|
||||||
|
"status": "0",
|
||||||
|
"configJson": null,
|
||||||
|
"createTime": "2026-03-08 10:00:00",
|
||||||
|
"updateTime": "2026-03-08 10:00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 查询工具列表(不分页)
|
||||||
|
|
||||||
|
**接口**: `GET /tool/all`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:list`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| keyword | String | 否 | 关键词 |
|
||||||
|
| type | String | 否 | 工具类型 |
|
||||||
|
| status | String | 否 | 状态 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "ReadFileTool",
|
||||||
|
"description": "读取文件内容工具",
|
||||||
|
"type": "BUILTIN",
|
||||||
|
"status": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 获取工具详情
|
||||||
|
|
||||||
|
**接口**: `GET /tool/{id}`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:query`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 工具ID |
|
||||||
|
|
||||||
|
### 1.4 新增工具
|
||||||
|
|
||||||
|
**接口**: `POST /tool`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:add`
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "MyMcpTool",
|
||||||
|
"description": "我的MCP工具",
|
||||||
|
"type": "REMOTE",
|
||||||
|
"status": "0",
|
||||||
|
"configJson": "{\"baseUrl\": \"http://localhost:8080/mcp\"}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 修改工具
|
||||||
|
|
||||||
|
**接口**: `PUT /tool`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:edit`
|
||||||
|
|
||||||
|
**请求体**: 同新增工具
|
||||||
|
|
||||||
|
### 1.6 删除工具
|
||||||
|
|
||||||
|
**接口**: `DELETE /tool/{ids}`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:remove`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| ids | String | 是 | 工具ID,多个用逗号分隔 |
|
||||||
|
|
||||||
|
### 1.7 更新工具状态
|
||||||
|
|
||||||
|
**接口**: `PUT /tool/{id}/status`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:edit`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| id | Long | 是 | 工具ID |
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| status | String | 是 | 状态:0-启用, 1-禁用 |
|
||||||
|
|
||||||
|
### 1.8 测试工具连接
|
||||||
|
|
||||||
|
**接口**: `POST /tool/{id}/test`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:query`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "连接测试成功",
|
||||||
|
"toolCount": 5,
|
||||||
|
"tools": ["tool1", "tool2", "tool3", "tool4", "tool5"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. MCP市场管理
|
||||||
|
|
||||||
|
### 2.1 查询市场列表
|
||||||
|
|
||||||
|
**接口**: `GET /market/list`
|
||||||
|
|
||||||
|
**权限**: `mcp:market:list`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| name | String | 否 | 市场名称 |
|
||||||
|
| description | String | 否 | 市场描述 |
|
||||||
|
| status | String | 否 | 状态 |
|
||||||
|
| pageNum | Integer | 是 | 页码 |
|
||||||
|
| pageSize | Integer | 是 | 每页数量 |
|
||||||
|
|
||||||
|
### 2.2 获取市场工具列表
|
||||||
|
|
||||||
|
**接口**: `GET /market/{marketId}/tools`
|
||||||
|
|
||||||
|
**权限**: `mcp:market:query`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| marketId | Long | 是 | 市场ID |
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| page | Integer | 否 | 页码,默认1 |
|
||||||
|
| size | Integer | 否 | 每页数量,默认10 |
|
||||||
|
|
||||||
|
### 2.3 刷新市场工具
|
||||||
|
|
||||||
|
**接口**: `POST /market/{marketId}/refresh`
|
||||||
|
|
||||||
|
**权限**: `mcp:market:edit`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "刷新成功",
|
||||||
|
"addedCount": 3,
|
||||||
|
"updatedCount": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 加载工具到本地
|
||||||
|
|
||||||
|
**接口**: `POST /market/tool/{toolId}/load`
|
||||||
|
|
||||||
|
**权限**: `mcp:market:edit`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| toolId | Long | 是 | 市场工具ID |
|
||||||
|
|
||||||
|
### 2.5 批量加载工具
|
||||||
|
|
||||||
|
**接口**: `POST /market/tools/batchLoad`
|
||||||
|
|
||||||
|
**权限**: `mcp:market:edit`
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"toolIds": [1, 2, 3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 工具调用日志
|
||||||
|
|
||||||
|
### 3.1 查询调用日志
|
||||||
|
|
||||||
|
**接口**: `GET /tool/callLog`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:query`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| toolId | Long | 否 | 工具ID |
|
||||||
|
| sessionId | Long | 否 | 会话ID |
|
||||||
|
| startDate | Date | 否 | 开始日期 |
|
||||||
|
| endDate | Date | 否 | 结束日期 |
|
||||||
|
| pageNum | Integer | 是 | 页码 |
|
||||||
|
| pageSize | Integer | 是 | 每页数量 |
|
||||||
|
|
||||||
|
### 3.2 获取工具统计
|
||||||
|
|
||||||
|
**接口**: `GET /tool/{toolId}/metrics`
|
||||||
|
|
||||||
|
**权限**: `mcp:tool:query`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"toolId": 1,
|
||||||
|
"toolName": "ReadFileTool",
|
||||||
|
"today": {
|
||||||
|
"callCount": 100,
|
||||||
|
"successCount": 95,
|
||||||
|
"failureCount": 5,
|
||||||
|
"avgDurationMs": 150,
|
||||||
|
"successRate": 95.0
|
||||||
|
},
|
||||||
|
"week": {
|
||||||
|
"callCount": 500,
|
||||||
|
"successCount": 475,
|
||||||
|
"failureCount": 25,
|
||||||
|
"avgDurationMs": 160,
|
||||||
|
"successRate": 95.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 状态码说明
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 200 | 请求成功 |
|
||||||
|
| 401 | 未认证 |
|
||||||
|
| 403 | 无权限 |
|
||||||
|
| 404 | 资源不存在 |
|
||||||
|
| 500 | 服务器错误 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 前端页面需求
|
||||||
|
|
||||||
|
### 5.1 MCP工具管理页面 (`/mcp/tool`)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 工具列表展示(分页)
|
||||||
|
- 工具搜索和筛选
|
||||||
|
- 新增/编辑/删除工具
|
||||||
|
- 工具状态切换
|
||||||
|
- 工具连接测试
|
||||||
|
|
||||||
|
**表格列**:
|
||||||
|
- 工具名称
|
||||||
|
- 工具描述
|
||||||
|
- 工具类型(标签显示)
|
||||||
|
- 状态(开关)
|
||||||
|
- 创建时间
|
||||||
|
- 操作(编辑、删除、测试)
|
||||||
|
|
||||||
|
### 5.2 MCP市场管理页面 (`/mcp/market`)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 市场列表展示
|
||||||
|
- 市场工具浏览
|
||||||
|
- 刷新市场工具
|
||||||
|
- 加载工具到本地
|
||||||
|
|
||||||
|
### 5.3 工具调用日志页面 (`/mcp/log`)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 调用日志列表
|
||||||
|
- 按工具/日期筛选
|
||||||
|
- 成功率统计
|
||||||
|
- 响应时间统计
|
||||||
|
|
||||||
|
**图表**:
|
||||||
|
- 每日调用次数趋势图
|
||||||
|
- 工具调用成功率饼图
|
||||||
|
- 平均响应时间柱状图
|
||||||
@@ -19,11 +19,6 @@
|
|||||||
<artifactId>ruoyi-common-chat</artifactId>
|
<artifactId>ruoyi-common-chat</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ruoyi</groupId>
|
|
||||||
<artifactId>ruoyi-mcp</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ruoyi</groupId>
|
<groupId>org.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common-sensitive</artifactId>
|
<artifactId>ruoyi-common-sensitive</artifactId>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dev.langchain4j.service.UserMessage;
|
|||||||
import dev.langchain4j.service.V;
|
import dev.langchain4j.service.V;
|
||||||
|
|
||||||
|
|
||||||
public interface ChartGenerationAgent {
|
public interface ChartGenerationAgent extends Agent {
|
||||||
|
|
||||||
@SystemMessage("""
|
@SystemMessage("""
|
||||||
You are a chart generation specialist. Your only task is to generate Apache ECharts
|
You are a chart generation specialist. Your only task is to generate Apache ECharts
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dev.langchain4j.service.SystemMessage;
|
|||||||
import dev.langchain4j.service.UserMessage;
|
import dev.langchain4j.service.UserMessage;
|
||||||
import dev.langchain4j.service.V;
|
import dev.langchain4j.service.V;
|
||||||
|
|
||||||
public interface McpAgent {
|
public interface McpAgent extends Agent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统提示词:通用工具调用智能体
|
* 系统提示词:通用工具调用智能体
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.ruoyi.agent;
|
|||||||
|
|
||||||
import dev.langchain4j.agentic.Agent;
|
import dev.langchain4j.agentic.Agent;
|
||||||
import dev.langchain4j.service.SystemMessage;
|
import dev.langchain4j.service.SystemMessage;
|
||||||
import dev.langchain4j.service.TokenStream;
|
|
||||||
import dev.langchain4j.service.UserMessage;
|
import dev.langchain4j.service.UserMessage;
|
||||||
import dev.langchain4j.service.V;
|
import dev.langchain4j.service.V;
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ import dev.langchain4j.service.V;
|
|||||||
* and returning relevant data and analysis results.
|
* and returning relevant data and analysis results.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface SqlAgent {
|
public interface SqlAgent extends Agent {
|
||||||
|
|
||||||
@SystemMessage("""
|
@SystemMessage("""
|
||||||
This agent is designed for MySQL 5.7
|
This agent is designed for MySQL 5.7
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.ruoyi.agent;
|
|
||||||
|
|
||||||
import dev.langchain4j.agentic.Agent;
|
|
||||||
import dev.langchain4j.service.TokenStream;
|
|
||||||
import dev.langchain4j.service.UserMessage;
|
|
||||||
import dev.langchain4j.service.V;
|
|
||||||
|
|
||||||
public interface StreamingCreativeWriter {
|
|
||||||
@UserMessage("""
|
|
||||||
You are a creative writer.
|
|
||||||
Generate a draft of a story no more than
|
|
||||||
3 sentences long around the given topic.
|
|
||||||
Return only the story and nothing else.
|
|
||||||
The topic is {{topic}}.
|
|
||||||
""")
|
|
||||||
@Agent("Generates a story based on the given topic")
|
|
||||||
TokenStream generateStory(@V("topic") String topic);
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import dev.langchain4j.service.V;
|
|||||||
* A web search assistant that answers natural language questions by searching the internet
|
* A web search assistant that answers natural language questions by searching the internet
|
||||||
* and returning relevant information from web pages.
|
* and returning relevant information from web pages.
|
||||||
*/
|
*/
|
||||||
public interface WebSearchAgent {
|
public interface WebSearchAgent extends Agent {
|
||||||
|
|
||||||
@SystemMessage("""
|
@SystemMessage("""
|
||||||
You are a web search assistant. Answer questions by searching and retrieving web content.
|
You are a web search assistant. Answer questions by searching and retrieving web content.
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public class TableSchemaManager {
|
|||||||
/**
|
/**
|
||||||
* 加载所有允许的表的结构信息
|
* 加载所有允许的表的结构信息
|
||||||
*/
|
*/
|
||||||
private void loadAllowedTableSchemas() throws SQLException {
|
private void loadAllowedTableSchemas() {
|
||||||
List<String> allowedTables = getAllowedTableNames();
|
List<String> allowedTables = getAllowedTableNames();
|
||||||
for (String tableName : allowedTables) {
|
for (String tableName : allowedTables) {
|
||||||
try {
|
try {
|
||||||
@@ -191,10 +191,7 @@ public class TableSchemaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> allowedTables = getAllowedTableNames();
|
List<String> allowedTables = getAllowedTableNames();
|
||||||
return allowedTables.stream()
|
return allowedTables.stream().map(schemaCache::get).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
.map(schemaCache::get)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import java.util.Map;
|
|||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.ruoyi.common.core.utils.SpringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||||
|
|
||||||
import dev.langchain4j.agent.tool.Tool;
|
import dev.langchain4j.agent.tool.Tool;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 SQL 查询的 Tool
|
* 执行 SQL 查询的 Tool
|
||||||
@@ -25,10 +26,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ExecuteSqlQueryTool {
|
public class ExecuteSqlQueryTool implements BuiltinToolProvider {
|
||||||
|
|
||||||
@Autowired(required = false)
|
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
|
||||||
private DataSource dataSource;
|
private DataSource getDataSource() {
|
||||||
|
return SpringUtils.getBean(DataSource.class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 SELECT SQL 查询
|
* 执行 SELECT SQL 查询
|
||||||
@@ -52,6 +55,7 @@ public class ExecuteSqlQueryTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
DataSource dataSource = getDataSource();
|
||||||
if (dataSource == null) {
|
if (dataSource == null) {
|
||||||
return "Error: Database datasource not configured";
|
return "Error: Database datasource not configured";
|
||||||
}
|
}
|
||||||
@@ -177,4 +181,19 @@ public class ExecuteSqlQueryTool {
|
|||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolName() {
|
||||||
|
return "execute_sql_query";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "执行SQL查询";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.ruoyi.agent.domain.TableStructure;
|
import org.ruoyi.agent.domain.TableStructure;
|
||||||
import org.ruoyi.agent.manager.TableSchemaManager;
|
import org.ruoyi.agent.manager.TableSchemaManager;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.ruoyi.common.core.utils.SpringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import dev.langchain4j.agent.tool.Tool;
|
import dev.langchain4j.agent.tool.Tool;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询数据库所有表的 Tool
|
* 查询数据库所有表的 Tool
|
||||||
@@ -16,11 +17,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class QueryAllTablesTool {
|
public class QueryAllTablesTool implements BuiltinToolProvider {
|
||||||
|
|
||||||
|
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
|
||||||
|
private TableSchemaManager getTableSchemaManager() {
|
||||||
|
return SpringUtils.getBean(TableSchemaManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TableSchemaManager tableSchemaManager; // 注入管理器
|
|
||||||
/**
|
/**
|
||||||
* 查询数据库中所有表
|
* 查询数据库中所有表
|
||||||
* 返回数据库中存在的所有表的列表
|
* 返回数据库中存在的所有表的列表
|
||||||
@@ -30,36 +33,49 @@ 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 {
|
||||||
// 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑)
|
// 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑)
|
||||||
List<TableStructure> tableSchemas = tableSchemaManager.getAllowedTableSchemas();
|
List<TableStructure> tableSchemas = getTableSchemaManager().getAllowedTableSchemas();
|
||||||
|
|
||||||
if (tableSchemas == null || tableSchemas.isEmpty()) {
|
if (tableSchemas == null || tableSchemas.isEmpty()) {
|
||||||
return "No tables found in database or cache is empty.";
|
return "No tables found in database or cache is empty.";
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 格式化结果
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\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 retrieved {} tables from schema cache", tableSchemas.size());
|
|
||||||
return result.toString();
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error retrieving tables from cache", e);
|
|
||||||
return "Error: " + e.getMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 格式化结果
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\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 retrieved {} tables from schema cache", tableSchemas.size());
|
||||||
|
return result.toString();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error retrieving tables from cache", e);
|
||||||
|
return "Error: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolName() {
|
||||||
|
return "query_all_tables";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "查询所有表";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Query all tables in the database and return table names and basic information";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,23 @@ import java.sql.ResultSet;
|
|||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.ruoyi.common.core.utils.SpringUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||||
|
|
||||||
import dev.langchain4j.agent.tool.Tool;
|
import dev.langchain4j.agent.tool.Tool;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class QueryTableSchemaTool {
|
public class QueryTableSchemaTool implements BuiltinToolProvider {
|
||||||
|
|
||||||
@Autowired(required = false)
|
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
|
||||||
private DataSource dataSource;
|
private DataSource getDataSource() {
|
||||||
|
return SpringUtils.getBean(DataSource.class);
|
||||||
|
}
|
||||||
|
|
||||||
@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) {
|
||||||
@@ -35,7 +38,7 @@ public class QueryTableSchemaTool {
|
|||||||
|
|
||||||
String sql = "SHOW CREATE TABLE `" + tableName + "`";
|
String sql = "SHOW CREATE TABLE `" + tableName + "`";
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection();
|
try (Connection connection = getDataSource().getConnection();
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
ResultSet rs = ps.executeQuery()) {
|
ResultSet rs = ps.executeQuery()) {
|
||||||
|
|
||||||
@@ -54,4 +57,19 @@ public class QueryTableSchemaTool {
|
|||||||
DynamicDataSourceContextHolder.clear();
|
DynamicDataSourceContextHolder.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolName() {
|
||||||
|
return "query_table_schema";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "查询表结构";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Query the CREATE TABLE statement (DDL) for a specific table by table name";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.ruoyi.mcp.config;
|
package org.ruoyi.config.mcp;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
import org.ruoyi.mcp.enums.McpToolStatus;
|
import org.ruoyi.enums.McpToolStatus;
|
||||||
import org.ruoyi.mcp.mapper.McpToolMapper;
|
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||||
import org.ruoyi.mcp.service.core.BuiltinToolDefinition;
|
import org.ruoyi.mcp.service.core.BuiltinToolDefinition;
|
||||||
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
|
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
|
||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.controller;
|
package org.ruoyi.controller.mcp;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -11,11 +11,12 @@ import org.ruoyi.common.log.enums.BusinessType;
|
|||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.common.web.core.BaseController;
|
import org.ruoyi.common.web.core.BaseController;
|
||||||
import org.ruoyi.mcp.domain.bo.McpMarketBo;
|
import org.ruoyi.domain.bo.mcp.McpMarketBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
|
||||||
import org.ruoyi.mcp.domain.vo.McpMarketVo;
|
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
|
||||||
import org.ruoyi.mcp.service.IMcpMarketService;
|
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||||
|
import org.ruoyi.service.mcp.IMcpMarketService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ public class McpMarketController extends BaseController {
|
|||||||
@SaCheckPermission("mcp:market:edit")
|
@SaCheckPermission("mcp:market:edit")
|
||||||
@Log(title = "MCP市场管理", businessType = BusinessType.UPDATE)
|
@Log(title = "MCP市场管理", businessType = BusinessType.UPDATE)
|
||||||
@PostMapping("/{marketId}/refresh")
|
@PostMapping("/{marketId}/refresh")
|
||||||
public R<org.ruoyi.mcp.domain.dto.McpMarketRefreshResult> refreshMarketTools(@PathVariable Long marketId) {
|
public R<McpMarketRefreshResult> refreshMarketTools(@PathVariable Long marketId) {
|
||||||
return R.ok(mcpMarketService.refreshMarketTools(marketId));
|
return R.ok(mcpMarketService.refreshMarketTools(marketId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.controller;
|
package org.ruoyi.controller.mcp;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -11,11 +11,11 @@ import org.ruoyi.common.log.enums.BusinessType;
|
|||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.common.web.core.BaseController;
|
import org.ruoyi.common.web.core.BaseController;
|
||||||
import org.ruoyi.mcp.domain.bo.McpToolBo;
|
import org.ruoyi.domain.bo.mcp.McpToolBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpToolListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolTestResult;
|
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
|
||||||
import org.ruoyi.mcp.domain.vo.McpToolVo;
|
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||||
import org.ruoyi.mcp.service.IMcpToolService;
|
import org.ruoyi.service.mcp.IMcpToolService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.bo;
|
package org.ruoyi.domain.bo.mcp;
|
||||||
|
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
@@ -6,7 +6,7 @@ import jakarta.validation.constraints.Size;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarket;
|
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP 市场业务对象
|
* MCP 市场业务对象
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.bo;
|
package org.ruoyi.domain.bo.mcp;
|
||||||
|
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
@@ -6,7 +6,7 @@ import jakarta.validation.constraints.Size;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.ruoyi.mcp.domain.dto;
|
package org.ruoyi.domain.dto.mcp;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarket;
|
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.dto;
|
package org.ruoyi.domain.dto.mcp;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.ruoyi.mcp.domain.dto;
|
package org.ruoyi.domain.dto.mcp;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarketTool;
|
import org.ruoyi.domain.entity.mcp.McpMarketTool;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.ruoyi.mcp.domain.dto;
|
package org.ruoyi.domain.dto.mcp;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.dto;
|
package org.ruoyi.domain.dto.mcp;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.entity;
|
package org.ruoyi.domain.entity.mcp;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.entity;
|
package org.ruoyi.domain.entity.mcp;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.domain.entity;
|
package org.ruoyi.domain.entity.mcp;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package org.ruoyi.mcp.domain.vo;
|
package org.ruoyi.domain.vo.mcp;
|
||||||
|
|
||||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarket;
|
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.ruoyi.mcp.domain.vo;
|
package org.ruoyi.domain.vo.mcp;
|
||||||
|
|
||||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@@ -20,9 +19,6 @@ import java.util.Date;
|
|||||||
@AutoMapper(target = McpTool.class)
|
@AutoMapper(target = McpTool.class)
|
||||||
public class McpToolVo implements Serializable {
|
public class McpToolVo implements Serializable {
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具ID
|
* 工具ID
|
||||||
*/
|
*/
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.enums;
|
package org.ruoyi.enums;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.ruoyi.mcp.mapper;
|
package org.ruoyi.mapper.mcp;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarket;
|
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||||
import org.ruoyi.mcp.domain.vo.McpMarketVo;
|
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP 市场信息 Mapper
|
* MCP 市场信息 Mapper
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.ruoyi.mcp.mapper;
|
package org.ruoyi.mapper.mcp;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarketTool;
|
import org.ruoyi.domain.entity.mcp.McpMarketTool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP 市场工具关联 Mapper
|
* MCP 市场工具关联 Mapper
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.ruoyi.mcp.mapper;
|
package org.ruoyi.mapper.mcp;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
import org.ruoyi.mcp.domain.vo.McpToolVo;
|
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCP 工具信息 Mapper
|
* MCP 工具信息 Mapper
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package org.ruoyi.mcp.service.core;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置工具注册表
|
||||||
|
* 自动发现并注册所有实现 {@link BuiltinToolProvider} 接口的工具
|
||||||
|
*
|
||||||
|
* <p>工具注册流程:
|
||||||
|
* <ol>
|
||||||
|
* <li>Spring 自动注入所有 {@link BuiltinToolProvider} 实现</li>
|
||||||
|
* <li>{@link #init()} 方法在 Bean 初始化后自动调用</li>
|
||||||
|
* <li>将所有工具注册到内部 Map</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>添加新工具只需:
|
||||||
|
* <ol>
|
||||||
|
* <li>创建一个类实现 {@link BuiltinToolProvider} 接口</li>
|
||||||
|
* <li>添加 {@code @Component} 注解</li>
|
||||||
|
* <li>工具会自动被发现和注册</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @author ruoyi team
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BuiltinToolRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具类型常量
|
||||||
|
*/
|
||||||
|
public static final String TYPE_BUILTIN = "BUILTIN";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring 自动注入所有实现 BuiltinToolProvider 接口的 Bean
|
||||||
|
* 注意:这些是 Spring 代理,不能直接用于 LangChain4j
|
||||||
|
* 我们需要提取 Class 信息以便创建新实例
|
||||||
|
*/
|
||||||
|
private final List<BuiltinToolProvider> toolProviders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置工具类映射表 (工具名称 -> 工具类)
|
||||||
|
* 存储 Class 对象而不是实例,以便创建不带 Spring 代理的新实例
|
||||||
|
*/
|
||||||
|
private final Map<String, Class<?>> registeredToolClasses = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置工具显示名称映射表 (工具名称 -> 显示名称)
|
||||||
|
*/
|
||||||
|
private final Map<String, String> displayNames = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化方法,在 Bean 创建后自动调用
|
||||||
|
* 提取工具类信息而不是存储 Spring 代理实例
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
log.info("开始注册内置工具,发现 {} 个工具提供者", toolProviders.size());
|
||||||
|
|
||||||
|
// 1. 注册通过 Spring 自动发现工具
|
||||||
|
for (BuiltinToolProvider provider : toolProviders) {
|
||||||
|
String toolName = provider.getToolName();
|
||||||
|
|
||||||
|
if (registeredToolClasses.containsKey(toolName)) {
|
||||||
|
log.warn("工具名称重复: {},将覆盖原有注册", toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 ClassUtils.getUserClass 获取原始类,避免 Spring CGLIB 代理类
|
||||||
|
Class<?> targetClass = ClassUtils.getUserClass(provider);
|
||||||
|
registeredToolClasses.put(toolName, targetClass);
|
||||||
|
displayNames.put(toolName, provider.getDisplayName());
|
||||||
|
log.info("注册内置工具: {} ({}) - 原始类: {}", toolName, provider.getDisplayName(), targetClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("内置工具注册完成,共 {} 个工具", registeredToolClasses.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工具提供者(返回 Spring 代理,仅用于元数据查询)
|
||||||
|
*
|
||||||
|
* @param toolName 工具名称
|
||||||
|
* @return 工具提供者,如果不存在则返回 null
|
||||||
|
*/
|
||||||
|
public BuiltinToolProvider getToolProvider(String toolName) {
|
||||||
|
// 这个方法返回 Spring 代理,仅用于获取元数据
|
||||||
|
for (BuiltinToolProvider provider : toolProviders) {
|
||||||
|
if (provider.getToolName().equals(toolName)) {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查工具是否已注册
|
||||||
|
*
|
||||||
|
* @param toolName 工具名称
|
||||||
|
* @return 是否已注册
|
||||||
|
*/
|
||||||
|
public boolean hasTool(String toolName) {
|
||||||
|
return registeredToolClasses.containsKey(toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有内置工具定义
|
||||||
|
*
|
||||||
|
* @return 内置工具定义集合
|
||||||
|
*/
|
||||||
|
public Collection<BuiltinToolDefinition> getAllBuiltinTools() {
|
||||||
|
return displayNames.entrySet().stream()
|
||||||
|
.map(entry -> new BuiltinToolDefinition(
|
||||||
|
entry.getKey(),
|
||||||
|
entry.getValue(),
|
||||||
|
"" // Description can be added later if needed
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有内置工具对象
|
||||||
|
* 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices
|
||||||
|
* 注意:每次调用都创建新实例,以避免 Spring CGLIB 代理问题
|
||||||
|
*
|
||||||
|
* @return 内置工具对象列表
|
||||||
|
*/
|
||||||
|
public List<Object> getAllBuiltinToolObjects() {
|
||||||
|
List<Object> toolInstances = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
for (java.util.Map.Entry<String, Class<?>> entry : registeredToolClasses.entrySet()) {
|
||||||
|
try {
|
||||||
|
// 使用无参构造函数创建新实例,保留 @Tool 注解
|
||||||
|
Object instance = entry.getValue().getDeclaredConstructor().newInstance();
|
||||||
|
toolInstances.add(instance);
|
||||||
|
log.debug("创建工具实例: {}", entry.getKey());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建工具实例失败: {} - {}", entry.getKey(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据工具名称获取工具对象
|
||||||
|
* 注意:每次调用都创建新实例,以避免 Spring CGLIB 代理问题
|
||||||
|
*
|
||||||
|
* @param toolName 工具名称
|
||||||
|
* @return 工具对象,如果不存在则返回 null
|
||||||
|
*/
|
||||||
|
public Object getBuiltinToolObject(String toolName) {
|
||||||
|
Class<?> toolClass = registeredToolClasses.get(toolName);
|
||||||
|
if (toolClass == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用无参构造函数创建新实例,保留 @Tool 注解
|
||||||
|
return toolClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建工具实例失败: {} - {}", toolName, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.ruoyi.mcp.service.core;
|
package org.ruoyi.mcp.service.core;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import dev.langchain4j.mcp.McpToolProvider;
|
import dev.langchain4j.mcp.McpToolProvider;
|
||||||
@@ -12,9 +13,9 @@ import dev.langchain4j.service.tool.ToolProvider;
|
|||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
import org.ruoyi.mcp.enums.McpToolStatus;
|
import org.ruoyi.enums.McpToolStatus;
|
||||||
import org.ruoyi.mcp.mapper.McpToolMapper;
|
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -99,7 +100,7 @@ public class LangChain4jMcpToolProviderService {
|
|||||||
*/
|
*/
|
||||||
public ToolProvider getAllEnabledToolsProvider() {
|
public ToolProvider getAllEnabledToolsProvider() {
|
||||||
List<McpTool> enabledTools = mcpToolMapper.selectList(
|
List<McpTool> enabledTools = mcpToolMapper.selectList(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<McpTool>()
|
new LambdaQueryWrapper<McpTool>()
|
||||||
.eq(McpTool::getStatus, McpToolStatus.ENABLED.getValue())
|
.eq(McpTool::getStatus, McpToolStatus.ENABLED.getValue())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -330,6 +331,11 @@ public class LangChain4jMcpToolProviderService {
|
|||||||
// 处理 Windows 系统的命令
|
// 处理 Windows 系统的命令
|
||||||
command = resolveCommand(command);
|
command = resolveCommand(command);
|
||||||
|
|
||||||
|
// 检查命令是否可用
|
||||||
|
if (!isCommandAvailable(command)) {
|
||||||
|
throw new IllegalArgumentException("Command '" + command + "' is not available on this system. Please install the required package or use a different tool.");
|
||||||
|
}
|
||||||
|
|
||||||
// 构建完整命令列表
|
// 构建完整命令列表
|
||||||
List<String> fullCommand = new ArrayList<>();
|
List<String> fullCommand = new ArrayList<>();
|
||||||
fullCommand.add(command);
|
fullCommand.add(command);
|
||||||
@@ -349,6 +355,29 @@ public class LangChain4jMcpToolProviderService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查命令是否在系统上可用
|
||||||
|
*/
|
||||||
|
private boolean isCommandAvailable(String command) {
|
||||||
|
try {
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(command, "--version");
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
Process process = pb.start();
|
||||||
|
boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
|
||||||
|
if (!finished) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int exitCode = process.exitValue();
|
||||||
|
// 对于某些命令,--version 可能返回非零退出码,所以我们只检查进程是否能启动
|
||||||
|
// 如果进程能启动并退出(无论退出码是什么),我们认为命令可用
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Command '{}' is not available: {}", command, e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建远程 HTTP/SSE Client
|
* 创建远程 HTTP/SSE Client
|
||||||
*/
|
*/
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.ruoyi.mcp.service.core;
|
||||||
|
|
||||||
|
import dev.langchain4j.service.tool.ToolProvider;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一工具提供工厂
|
||||||
|
* 整合所有类型的MCP工具提供者,为Agent和Chat服务提供统一的工具获取入口
|
||||||
|
*
|
||||||
|
* <p>支持的工具类型:
|
||||||
|
* <ul>
|
||||||
|
* <li>BUILTIN - 内置工具(如文件操作工具)</li>
|
||||||
|
* <li>LOCAL - 本地STDIO工具(通过命令行启动的MCP服务器)</li>
|
||||||
|
* <li>REMOTE - 远程HTTP/SSE工具(通过网络连接的MCP服务器)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author ruoyi team
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ToolProviderFactory {
|
||||||
|
|
||||||
|
private final BuiltinToolRegistry builtinToolRegistry;
|
||||||
|
private final LangChain4jMcpToolProviderService langChain4jMcpToolProviderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已启用的MCP工具的ToolProvider
|
||||||
|
*
|
||||||
|
* @return ToolProvider实例
|
||||||
|
*/
|
||||||
|
public ToolProvider getAllEnabledMcpToolsProvider() {
|
||||||
|
return langChain4jMcpToolProviderService.getAllEnabledToolsProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有 BUILTIN 工具对象
|
||||||
|
* 这些对象包含 @Tool 注解的方法,可直接用于 AgenticServices
|
||||||
|
*
|
||||||
|
* @return BUILTIN 工具对象列表
|
||||||
|
*/
|
||||||
|
public List<Object> getAllBuiltinToolObjects() {
|
||||||
|
return builtinToolRegistry.getAllBuiltinToolObjects();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,41 @@
|
|||||||
package org.ruoyi.service.chat.impl;
|
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.SupervisorResponseStrategy;
|
|
||||||
import dev.langchain4j.community.model.dashscope.QwenChatModel;
|
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.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.memory.chat.MessageWindowChatMemory;
|
||||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
|
||||||
import dev.langchain4j.service.tool.ToolProvider;
|
import dev.langchain4j.service.tool.ToolProvider;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.ruoyi.agent.ChartGenerationAgent;
|
import org.ruoyi.agent.McpAgent;
|
||||||
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.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.ReSumeRunner;
|
||||||
import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner;
|
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.enums.RoleType;
|
||||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
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.chatMessage.AbstractChatMessageService;
|
||||||
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
|
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
|
||||||
import org.ruoyi.common.core.utils.ObjectUtils;
|
import org.ruoyi.common.core.utils.ObjectUtils;
|
||||||
import org.ruoyi.common.core.utils.SpringUtils;
|
import org.ruoyi.common.core.utils.SpringUtils;
|
||||||
import org.ruoyi.common.core.utils.StringUtils;
|
import org.ruoyi.common.core.utils.StringUtils;
|
||||||
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
import org.ruoyi.common.sse.utils.SseMessageUtils;
|
||||||
|
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
||||||
import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore;
|
import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
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;
|
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 已包含完整的历史上下文和当前消息
|
* 注意:messages 已包含完整的历史上下文和当前消息
|
||||||
@@ -232,7 +212,8 @@ public abstract class AbstractStreamingChatService extends AbstractChatMessageSe
|
|||||||
* @param chatRequest 聊天请求
|
* @param chatRequest 聊天请求
|
||||||
* @param handler 响应处理器
|
* @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();
|
public abstract String getProviderName();
|
||||||
|
|
||||||
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
||||||
// 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器
|
log.info("执行Agent任务,消息: {}", userMessage);
|
||||||
// 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取)
|
// 加载所有可用的 Agent,让 Supervisor 根据任务类型自动选择
|
||||||
// McpTransport transport = new StdioMcpTransport.Builder()
|
return doAgentWithAllAgents(userMessage, chatModelVo);
|
||||||
// .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y",
|
}
|
||||||
// "bing-cn-mcp"
|
|
||||||
// ))
|
|
||||||
// .logEvents(true)
|
|
||||||
// .build();
|
|
||||||
|
|
||||||
// // 步骤2: 创建MCP客户端
|
/**
|
||||||
// McpClient mcpClient = new DefaultMcpClient.Builder()
|
* 使用单一 Agent 处理所有任务
|
||||||
// .transport(transport)
|
* 不使用 Supervisor 模式,而是使用 MCP Agent 来处理所有任务
|
||||||
// .build();
|
*
|
||||||
|
* @param userMessage 用户消息
|
||||||
|
* @param chatModelVo 聊天模型配置
|
||||||
|
* @return Agent 响应结果
|
||||||
|
*/
|
||||||
|
protected String doAgentWithAllAgents(String userMessage, ChatModelVo chatModelVo) {
|
||||||
|
|
||||||
// // 步骤3: 配置工具提供者
|
try {
|
||||||
// ToolProvider toolProvider = McpToolProvider.builder()
|
// 1. 加载 LLM 模型
|
||||||
// .mcpClients(List.of(mcpClient))
|
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||||
// .build();
|
.apiKey(chatModelVo.getApiKey())
|
||||||
|
.modelName(chatModelVo.getModelName())
|
||||||
|
|
||||||
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();
|
.build();
|
||||||
|
|
||||||
SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class)
|
// 2. 获取统一工具提供工厂
|
||||||
.chatModel(
|
ToolProviderFactory toolProviderFactory = SpringUtils.getBean(ToolProviderFactory.class);
|
||||||
qwenChatModel)
|
|
||||||
.tools(
|
|
||||||
SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取
|
|
||||||
SpringUtils.getBean(QueryTableSchemaTool.class),
|
|
||||||
SpringUtils.getBean(ExecuteSqlQueryTool.class)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class)
|
// 3. 获取所有可用的工具
|
||||||
// .chatModel(PLANNER_MODEL)
|
|
||||||
// .toolProvider(toolProvider)
|
|
||||||
// .build();
|
|
||||||
|
|
||||||
ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class)
|
// 3.1 添加 BUILTIN 工具对象(包括 SQL 工具)
|
||||||
.chatModel(
|
List<Object> builtinTools = toolProviderFactory.getAllBuiltinToolObjects();
|
||||||
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);
|
List<Object> allTools = new ArrayList<>(builtinTools);
|
||||||
System.out.println(invoke);
|
|
||||||
return res1;
|
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;
|
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.community.model.dashscope.QwenStreamingChatModel;
|
||||||
import dev.langchain4j.data.message.ChatMessage;
|
import dev.langchain4j.data.message.ChatMessage;
|
||||||
import dev.langchain4j.data.message.SystemMessage;
|
import dev.langchain4j.data.message.SystemMessage;
|
||||||
import dev.langchain4j.data.message.UserMessage;
|
import dev.langchain4j.data.message.UserMessage;
|
||||||
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 lombok.extern.slf4j.Slf4j;
|
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.dto.request.ChatRequest;
|
||||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||||
import org.ruoyi.common.core.utils.SpringUtils;
|
|
||||||
import org.ruoyi.enums.ChatModeType;
|
import org.ruoyi.enums.ChatModeType;
|
||||||
import org.ruoyi.mcp.service.core.ToolProviderFactory;
|
|
||||||
import org.ruoyi.service.chat.impl.AbstractStreamingChatService;
|
import org.ruoyi.service.chat.impl.AbstractStreamingChatService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* qianWenAI服务调用
|
* qianWenAI服务调用
|
||||||
@@ -39,9 +30,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
|||||||
// 添加文档解析的前缀字段
|
// 添加文档解析的前缀字段
|
||||||
private static final String UPLOAD_FILE_API_PREFIX = "fileid";
|
private static final String UPLOAD_FILE_API_PREFIX = "fileid";
|
||||||
|
|
||||||
// 用于线程安全的锁
|
|
||||||
private final ReentrantLock cacheLock = new ReentrantLock();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) {
|
protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) {
|
||||||
return QwenStreamingChatModel.builder()
|
return QwenStreamingChatModel.builder()
|
||||||
@@ -91,69 +79,6 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
|||||||
}).orElse(messagesWithMemory);
|
}).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
|
@Override
|
||||||
public String getProviderName() {
|
public String getProviderName() {
|
||||||
return ChatModeType.QIAN_WEN.getCode();
|
return ChatModeType.QIAN_WEN.getCode();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package org.ruoyi.mcp.service;
|
package org.ruoyi.service.mcp;
|
||||||
|
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.mcp.domain.bo.McpMarketBo;
|
import org.ruoyi.domain.bo.mcp.McpMarketBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketRefreshResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
|
||||||
import org.ruoyi.mcp.domain.vo.McpMarketVo;
|
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package org.ruoyi.mcp.service;
|
package org.ruoyi.service.mcp;
|
||||||
|
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.mcp.domain.bo.McpToolBo;
|
import org.ruoyi.domain.bo.mcp.McpToolBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpToolListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolTestResult;
|
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
|
||||||
import org.ruoyi.mcp.domain.vo.McpToolVo;
|
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.service.impl;
|
package org.ruoyi.service.mcp.impl;
|
||||||
|
|
||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpResponse;
|
||||||
@@ -13,19 +13,19 @@ import org.ruoyi.common.core.exception.ServiceException;
|
|||||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.mcp.domain.bo.McpMarketBo;
|
import org.ruoyi.domain.bo.mcp.McpMarketBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketRefreshResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpMarketToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarket;
|
import org.ruoyi.domain.entity.mcp.McpMarket;
|
||||||
import org.ruoyi.mcp.domain.entity.McpMarketTool;
|
import org.ruoyi.domain.entity.mcp.McpMarketTool;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
import org.ruoyi.mcp.domain.vo.McpMarketVo;
|
import org.ruoyi.domain.vo.mcp.McpMarketVo;
|
||||||
import org.ruoyi.mcp.enums.McpToolStatus;
|
import org.ruoyi.enums.McpToolStatus;
|
||||||
import org.ruoyi.mcp.mapper.McpMarketMapper;
|
import org.ruoyi.mapper.mcp.McpMarketMapper;
|
||||||
import org.ruoyi.mcp.mapper.McpMarketToolMapper;
|
import org.ruoyi.mapper.mcp.McpMarketToolMapper;
|
||||||
import org.ruoyi.mcp.mapper.McpToolMapper;
|
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||||
import org.ruoyi.mcp.service.IMcpMarketService;
|
import org.ruoyi.service.mcp.IMcpMarketService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.ruoyi.mcp.service.impl;
|
package org.ruoyi.service.mcp.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
@@ -9,14 +9,14 @@ import org.ruoyi.common.core.exception.ServiceException;
|
|||||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.ruoyi.mcp.domain.bo.McpToolBo;
|
import org.ruoyi.domain.bo.mcp.McpToolBo;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolListResult;
|
import org.ruoyi.domain.dto.mcp.McpToolListResult;
|
||||||
import org.ruoyi.mcp.domain.dto.McpToolTestResult;
|
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
|
||||||
import org.ruoyi.mcp.domain.entity.McpTool;
|
import org.ruoyi.domain.entity.mcp.McpTool;
|
||||||
import org.ruoyi.mcp.domain.vo.McpToolVo;
|
import org.ruoyi.domain.vo.mcp.McpToolVo;
|
||||||
import org.ruoyi.mcp.enums.McpToolStatus;
|
import org.ruoyi.enums.McpToolStatus;
|
||||||
import org.ruoyi.mcp.mapper.McpToolMapper;
|
import org.ruoyi.mapper.mcp.McpToolMapper;
|
||||||
import org.ruoyi.mcp.service.IMcpToolService;
|
import org.ruoyi.service.mcp.IMcpToolService;
|
||||||
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
|
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
|
||||||
import org.ruoyi.mcp.service.core.LangChain4jMcpToolProviderService;
|
import org.ruoyi.mcp.service.core.LangChain4jMcpToolProviderService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user