diff --git a/README.md b/README.md
index 0737c834..b018d5b6 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,11 @@
+
+
+
+
+
|
|
@@ -170,8 +175,8 @@
#### 🚀 部署方式选择
| 部署方式 | 特点 | 适用场景 | 部署文档 | 配置要求 | 视频教程 |
|---------|------|---------|---------|---------|---------|
-| **最简化安装** | 智能对话、IOT、MCP、视觉感知,数据存储在配置文件 | 低配置环境,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - |
-| **全模块安装** | 智能对话、IOT、MCP、视觉感知、OTA、智控台,数据存储在数据库 | 完整功能体验 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
+| **最简化安装** | 智能对话、IOT、MCP、视觉感知 | 低配置环境,数据存储在配置文件,无需数据库 | [①Docker版](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②源码部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| 如果使用`FunASR`要2核4G,如果全API,要2核2G | - |
+| **全模块安装** | 智能对话、IOT、MCP接入点、视觉感知、OTA、智控台 | 完整功能体验,数据存储在数据库 |[①Docker版](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [②源码部署](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) / [③源码部署自动更新教程](./docs/dev-ops-integration.md) | 如果使用`FunASR`要4核8G,如果全API,要2核4G| [本地源码启动视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
> 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据
@@ -186,18 +191,22 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
#### 🚩 配置说明和推荐
> [!Note]
-> 本项目默认的配置是`入门全免费`设置,如果想效果更优,推荐使用`流式配置`。
+> 本项目提供两种配置方案:
>
-> 本项目自`0.5.2`版本,已支持使用流式配置,相比`0.5`版本以前,响应速度提升约`2.5秒`
+> 1. `入门全免费`配置:适合个人家庭使用,所有组件均采用免费方案,无需额外付费。
+>
+> 2. `流式配置`:适合演示、培训、超过2个并发等场景,采用流式处理技术,响应速度更快,体验更佳。
+>
+> 自`0.5.2`版本起,项目支持流式配置,相比早期版本,响应速度提升约`2.5秒`,显著改善用户体验。
| 模块名称 | 入门全免费设置 | 流式配置 |
|:---:|:---:|:---:|
-| ASR(语音识别) | FunASR(本地) | 👍DoubaoStreamASR(火山流式语音识别) |
+| ASR(语音识别) | FunASR(本地) | 👍FunASRServer 或 👍DoubaoStreamASR |
| LLM(大模型) | ChatGLMLLM(智谱glm-4-flash) | 👍DoubaoLLM(火山doubao-1-5-pro-32k-250115) |
| VLLM(视觉大模型) | ChatGLMVLLM(智谱glm-4v-flash) | 👍QwenVLVLLM(千问qwen2.5-vl-3b-instructh) |
-| TTS(语音合成) | 👍LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山双流式语音合成) |
-| Intent(意图识别) | function_call(函数调用) | ✅function_call(函数调用) |
-| Memory(记忆功能) | mem_local_short(本地短期记忆) | ✅mem_local_short(本地短期记忆) |
+| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山双流式语音合成) |
+| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
+| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
#### 🔧 测试工具
本项目提供以下测试工具,帮助您验证系统和选择合适的模型:
@@ -222,7 +231,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
| 视觉感知系统 | 支持多种VLLM(视觉大模型),实现多模态交互 |
| 意图识别系统 | 支持LLM意图识别、Function Call函数调用,提供插件化意图处理机制 |
| 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 |
-| IOT/MCP控制协议 | 支持设备注册管理、智能控制接口,同时支持IOT、MCP控制协议 |
+| 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 |
| 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理 |
| 测试工具 | 提供性能测试工具、视觉模型测试工具和音频交互测试工具 |
| 部署支持 | 支持Docker部署和本地部署,提供完整的配置文件管理 |
diff --git a/docs/homeassistant-integration.md b/docs/homeassistant-integration.md
index 474049c1..7e69c229 100644
--- a/docs/homeassistant-integration.md
+++ b/docs/homeassistant-integration.md
@@ -89,55 +89,25 @@ http://homeassistant.local:8123
公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1;
```
-这段字符,我们成为“设备清单字符”需要保存好,等一下有用。
-
+这段字符,我们称为“设备清单字符”需要保存好,等一下有用。
#### 2. 登录`智控台`
-使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来
+
-编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。
+使用管理员账号,登录`智控台`。在`智能体管理`,找到你的智能体,再点击`配置角色`。
+将意图识别设置成`函数调用`或`LLM意图识别`。这时你会看到右侧有一个`编辑功能`。点击`编辑功能`按钮,会弹出`功能管理`的框。
-编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的
+在`功能管理`的框里,你需要勾选`HomeAssistant设备状态查询`和`HomeAssistant设备状态修改`。
-```
-http://192.168.4.7:8123
-```
+勾选后,在`已选功能`点击`HomeAssistant设备状态查询`,然后在`参数配置`里配置你的`HomeAssistant`地址、密钥、设备清单字符。
-编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去
+编辑好后,点击`保存配置`,这时`功能管理`的框会隐藏,这时你再点击保存智能体配置。
+保存成功后,即可唤醒设备操作。
-#### 3. 设置`意图识别`函数
-
-在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑
-
-然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state”
-
-修改前
-```
-change_role;get_weather;get_news;play_music
-```
-
-修改后
-
-```
-change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state
-```
-
-#### 4. 手动重启xiaozhi-server
-
-重启xiaozhi-server程序
-
-
-#### 5. 确认角色配置是否设置了函数意图识别
-
-在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色”
-
-确认意图识别(Intent),是否选择“函数调用意图识别”
-
-
-#### 6. 唤醒设别进行控制
+#### 3. 唤醒设别进行控制
尝试和esp32说,“打开XXX灯”
diff --git a/docs/images/demo13.png b/docs/images/demo13.png
new file mode 100644
index 00000000..1ad79fa3
Binary files /dev/null and b/docs/images/demo13.png differ
diff --git a/docs/images/image-ha-integration-06.png b/docs/images/image-ha-integration-06.png
new file mode 100644
index 00000000..56ef98fc
Binary files /dev/null and b/docs/images/image-ha-integration-06.png differ
diff --git a/docs/mcp-endpoint-integration.md b/docs/mcp-endpoint-integration.md
new file mode 100644
index 00000000..ca72aef1
--- /dev/null
+++ b/docs/mcp-endpoint-integration.md
@@ -0,0 +1,134 @@
+# MCP 接入点部署使用指南
+
+本教程包含2个部分
+- 1、如何开启mcp接入点
+- 2、如何为智能体接入一个简单的mcp功能,如计算器功能
+
+部署的前提条件:
+- 1、你已经部署了全模块,因为mcp接入点需要全模块中的智控台功能
+- 2、你想在不修改xiaozhi-server项目的前提下,扩展小智的功能
+
+# 如何开启mcp接入点
+
+## 第一步,下载mcp接入点项目源码
+
+浏览器打开[mcp接入点项目地址](https://github.com/xinnan-tech/mcp-endpoint-server)
+
+打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
+
+点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-endpoint-server-main`
+你需要把它重命名成`mcp-endpoint-server`。
+
+## 第二步,启动程序
+这个项目是一个很简单的项目,建议使用docker运行。不过如果你不想使用docker运行,你可以参考[这个页面](https://github.com/xinnan-tech/mcp-endpoint-server/blob/main/README_dev.md)使用源码运行。以下是docker运行的方法
+
+```
+# 进入本项目源码根目录
+cd mcp-endpoint-server
+
+# 清除缓存
+docker compose -f docker-compose.yml down
+docker stop mcp-endpoint-server
+docker rm mcp-endpoint-server
+docker rmi ghcr.nju.edu.cn/xinnan-tech/mcp-endpoint-server:latest
+
+# 启动docker容器
+docker compose -f docker-compose.yml up -d
+# 查看日志
+docker logs -f mcp-endpoint-server
+```
+
+此时,日志里会输出类似以下的日志
+```
+======================================================
+接口地址: http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx
+=======上面的地址是MCP接入点地址,请勿泄露给任何人============
+```
+
+请你把接口地址复制出来:
+
+由于你是docker部署,切不可直接使用上面的地址!
+
+由于你是docker部署,切不可直接使用上面的地址!
+
+由于你是docker部署,切不可直接使用上面的地址!
+
+你先把地址复制出来,放在一个草稿里,你要知道你的电脑的局域网ip是什么,例如我的电脑局域网ip是`192.168.1.25`,那么
+原来我的接口地址
+```
+http://172.1.1.1:8004/mcp_endpoint/health?key=xxxx
+```
+就要改成
+```
+http://192.168.1.25:8004/mcp_endpoint/health?key=xxxx
+```
+
+改好后,请使用浏览器直接访问这个接口。当浏览器出现类似这样的代码,说明是成功了。
+```
+{"result":{"status":"success","connections":{"tool_connections":0,"robot_connections":0,"total_connections":0}},"error":null,"id":null,"jsonrpc":"2.0"}
+```
+
+请你保留好这个`接口地址`,下一步要用到。
+
+## 第三步,配置智控台
+
+使用管理员账号,登录智控台,点击顶部`参数字典`,选择`参数管理`功能。
+
+然后搜索参数`server.mcp_endpoint`,此时,它的值应该是`null`值。
+点击修改按钮,把上一步得来的`接口地址`粘贴到`参数值`里。然后保存。
+
+如果能保存成功,说明一切顺利,你可以去智能体查看效果了。如果不成功,说明智控台无法访问mcp接入点,很大概率是网络防火墙,或者没有填写正确的局域网ip。
+
+# 如何为智能体接入一个简单的mcp功能,如计算器功能
+
+如果以上步骤顺利,你可以进入智能体管理,点击`配置角色`,在`意图识别`的右边,有一个`编辑功能`的按钮。
+
+点击这个按钮。在弹出的页面里,位于底部,会有`MCP接入点`,正常来说,会显示这个智能体的`MCP接入点地址`,接下来,我们来给这个智能体扩展一个基于MCP技术的计算器的功能。
+
+这个`MCP接入点地址`很重要,你等一下会用到。
+
+## 第一步 下载虾哥MCP计算器项目代码
+
+浏览器打开虾哥写的[计算器项目](https://github.com/78/mcp-calculator),
+
+打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
+
+点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`mcp-calculatorr-main`
+你需要把它重命名成`mcp-calculator`。接下来,我们用命令行进入项目目录即安装依赖
+
+
+```bash
+# 进入项目目录
+cd mcp-calculator
+
+conda remove -n mcp-calculator --all -y
+conda create -n mcp-calculator python=3.10 -y
+conda activate mcp-calculator
+
+pip install -r requirements.txt
+```
+
+## 第二步 启动
+
+启动前,先从你的智控台的智能体里,复制到了MCP接入点的地址。
+
+例如我的智能体的mcp地址是
+```
+ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
+```
+
+开始输入命令
+
+```bash
+export MCP_ENDPOINT=ws://192.168.4.7:8004/mcp_endpoint/mcp/?token=abc
+```
+
+输入完后,启动程序
+
+```bash
+python mcp_pipe.py calculator.py
+```
+
+
+启动完后,你再进入智控台,点击刷新MCP的接入状态,就会看到你扩展的功能列表了。
+
diff --git a/docs/newsnow_plugin_config.md b/docs/newsnow_plugin_config.md
new file mode 100644
index 00000000..88744423
--- /dev/null
+++ b/docs/newsnow_plugin_config.md
@@ -0,0 +1,84 @@
+# get_news_from_newsnow 插件新闻源配置指南
+
+## 概述
+
+`get_news_from_newsnow` 插件现在支持通过Web管理界面动态配置新闻源,不再需要修改代码。用户可以在智控台中为每个智能体配置不同的新闻源。
+
+## 配置方式
+
+### 1. 通过Web管理界面配置(推荐)
+
+1. 登录智控台
+2. 进入"角色配置"页面
+3. 选择要配置的智能体
+4. 点击"编辑功能"按钮
+5. 在右侧参数配置区域找到"newsnow新闻聚合"插件
+6. 在"新闻源配置"字段中输入JSON格式的新闻源配置
+
+### 2. 配置文件方式
+
+在 `config.yaml` 中配置:
+
+```yaml
+plugins:
+ get_news_from_newsnow:
+ url: "https://newsnow.busiyi.world/api/s?id="
+ news_sources:
+ thepaper: "澎湃新闻"
+ baidu: "百度热搜"
+ cls-depth: "财联社"
+ # 可以添加更多新闻源
+ weibo: "微博头条"
+ douyin: "抖音热搜"
+ solidot: "奇客Solidot"
+```
+
+## 新闻源配置格式
+
+新闻源配置使用JSON格式,格式为:
+
+```json
+{
+ "source_id": "显示名称",
+ "source_id2": "显示名称2"
+}
+```
+
+### 配置示例
+
+```json
+{
+ "thepaper": "澎湃新闻",
+ "baidu": "百度热搜",
+ "cls-depth": "财联社",
+ "weibo": "微博头条",
+ "douyin": "抖音热搜",
+ "solidot": "奇客Solidot"
+}
+```
+
+## 默认配置
+
+如果未配置新闻源,插件将使用以下默认配置:
+
+```json
+{
+ "thepaper": "澎湃新闻",
+ "baidu": "百度热搜",
+ "cls-depth": "财联社"
+}
+```
+
+## 使用说明
+
+1. **配置新闻源**:在Web界面或配置文件中设置新闻源
+2. **调用插件**:用户可以说"播报新闻"或"获取新闻"
+3. **指定新闻源**:用户可以说"播报澎湃新闻"或"获取百度热搜"
+4. **获取详情**:用户可以说"详细介绍这条新闻"
+
+## 注意事项
+
+1. 新闻源的 `source_id` 必须与API接口支持的ID一致
+2. 配置更改后需要重启服务或重新加载配置
+3. 如果配置的新闻源无效,插件会自动使用默认新闻源
+4. JSON格式必须正确,否则会使用默认配置
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java
index 44294756..32a121fa 100644
--- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java
+++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java
@@ -111,6 +111,11 @@ public interface Constant {
*/
String FILE_EXTENSION_SEG = ".";
+ /**
+ * mcp接入点路径
+ */
+ String SERVER_MCP_ENDPOINT = "server.mcp_endpoint";
+
/**
* 无记忆
*/
@@ -227,7 +232,7 @@ public interface Constant {
/**
* 版本号
*/
- public static final String VERSION = "0.5.5";
+ public static final String VERSION = "0.6.1";
/**
* 无效固件URL
diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java
new file mode 100644
index 00000000..660e3cba
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/common/utils/AESUtils.java
@@ -0,0 +1,78 @@
+package xiaozhi.common.utils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+public class AESUtils {
+
+ private static final String ALGORITHM = "AES";
+ private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
+
+ /**
+ * AES加密
+ *
+ * @param key 密钥(16位、24位或32位)
+ * @param plainText 待加密字符串
+ * @return 加密后的Base64字符串
+ */
+ public static String encrypt(String key, String plainText) {
+ try {
+ // 确保密钥长度为16、24或32位
+ byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
+ SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
+
+ Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+
+ byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(encryptedBytes);
+ } catch (Exception e) {
+ throw new RuntimeException("AES加密失败", e);
+ }
+ }
+
+ /**
+ * AES解密
+ *
+ * @param key 密钥(16位、24位或32位)
+ * @param encryptedText 待解密的Base64字符串
+ * @return 解密后的字符串
+ */
+ public static String decrypt(String key, String encryptedText) {
+ try {
+ // 确保密钥长度为16、24或32位
+ byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
+ SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
+
+ Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
+
+ byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
+ byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
+ return new String(decryptedBytes, StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ throw new RuntimeException("AES解密失败", e);
+ }
+ }
+
+ /**
+ * 填充密钥到指定长度(16、24或32位)
+ *
+ * @param keyBytes 原始密钥字节数组
+ * @return 填充后的密钥字节数组
+ */
+ private static byte[] padKey(byte[] keyBytes) {
+ int keyLength = keyBytes.length;
+ if (keyLength == 16 || keyLength == 24 || keyLength == 32) {
+ return keyBytes;
+ }
+
+ // 如果密钥长度不足,用0填充;如果超过,截取前32位
+ byte[] paddedKey = new byte[32];
+ System.arraycopy(keyBytes, 0, paddedKey, 0, Math.min(keyLength, 32));
+ return paddedKey;
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/HashEncryptionUtil.java b/main/manager-api/src/main/java/xiaozhi/common/utils/HashEncryptionUtil.java
new file mode 100644
index 00000000..79b05192
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/common/utils/HashEncryptionUtil.java
@@ -0,0 +1,52 @@
+package xiaozhi.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 哈希加密算法的工具类
+ * @author zjy
+ */
+@Slf4j
+public class HashEncryptionUtil {
+ /**
+ * 使用md5进行加密
+ * @param context 被加密的内容
+ * @return 哈希值
+ */
+ public static String Md5hexDigest(String context){
+ return hexDigest(context,"MD5");
+ }
+
+ /**
+ * 指定哈希算法进行加密
+ * @param context 被加密的内容
+ * @param algorithm 哈希算法
+ * @return 哈希值
+ */
+ public static String hexDigest(String context,String algorithm ){
+ // 获取MD5算法实例
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ log.error("加密失败的算法:{}",algorithm);
+ throw new RuntimeException("加密失败,"+ algorithm +"哈希算法系统不支持");
+ }
+ // 计算智能体id的MD5值
+ byte[] messageDigest = md.digest(context.getBytes());
+ // 将字节数组转换为十六进制字符串
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : messageDigest) {
+ String hex = Integer.toHexString(0xFF & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java
new file mode 100644
index 00000000..3f9fe27d
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java
@@ -0,0 +1,31 @@
+package xiaozhi.common.utils;
+
+/**
+ * 返回响应体工具类
+ */
+public class ResultUtils
+{
+ public static Result success(T data) {
+ return new Result().ok(data);
+ }
+
+ public static Result error() {
+ return new Result().error();
+ }
+
+ public static Result error(String msg) {
+ return new Result().error(msg);
+ }
+
+ public static Result error(int errorCode, String msg) {
+ return new Result().error(errorCode, msg);
+ }
+
+ public static Result error(int errorCode) {
+ return new Result().error(errorCode);
+ }
+
+ public static Result empty() {
+ return new Result();
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
index 8aa912a4..bfd82e41 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java
@@ -1,6 +1,5 @@
package xiaozhi.modules.agent.controller;
-import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -33,8 +32,8 @@ import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail;
-import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.Result;
+import xiaozhi.common.utils.ResultUtils;
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
import xiaozhi.modules.agent.dto.AgentCreateDTO;
@@ -45,8 +44,10 @@ import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
+import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.AgentTemplateService;
+import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.user.SecurityUser;
@@ -61,6 +62,7 @@ public class AgentController {
private final DeviceService deviceService;
private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService;
+ private final AgentPluginMappingService agentPluginMappingService;
private final RedisUtils redisUtils;
@GetMapping("/list")
@@ -88,46 +90,17 @@ public class AgentController {
@GetMapping("/{id}")
@Operation(summary = "获取智能体详情")
@RequiresPermissions("sys:role:normal")
- public Result getAgentById(@PathVariable("id") String id) {
- AgentEntity agent = agentService.getAgentById(id);
- return new Result().ok(agent);
+ public Result getAgentById(@PathVariable("id") String id) {
+ AgentInfoVO agent = agentService.getAgentById(id);
+ return ResultUtils.success(agent);
}
@PostMapping
@Operation(summary = "创建智能体")
@RequiresPermissions("sys:role:normal")
public Result save(@RequestBody @Valid AgentCreateDTO dto) {
- AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
-
- // 获取默认模板
- AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
- if (template != null) {
- // 设置模板中的默认值
- entity.setAsrModelId(template.getAsrModelId());
- entity.setVadModelId(template.getVadModelId());
- entity.setLlmModelId(template.getLlmModelId());
- entity.setVllmModelId(template.getVllmModelId());
- entity.setTtsModelId(template.getTtsModelId());
- entity.setTtsVoiceId(template.getTtsVoiceId());
- entity.setMemModelId(template.getMemModelId());
- entity.setIntentModelId(template.getIntentModelId());
- entity.setSystemPrompt(template.getSystemPrompt());
- entity.setSummaryMemory(template.getSummaryMemory());
- entity.setChatHistoryConf(template.getChatHistoryConf());
- entity.setLangCode(template.getLangCode());
- entity.setLanguage(template.getLanguage());
- }
-
- // 设置用户ID和创建者信息
- UserDetail user = SecurityUser.getUser();
- entity.setUserId(user.getId());
- entity.setCreator(user.getId());
- entity.setCreatedAt(new Date());
-
- // ID、智能体编码和排序会在Service层自动生成
- agentService.insert(entity);
-
- return new Result().ok(entity.getId());
+ String agentId = agentService.createAgent(dto);
+ return new Result().ok(agentId);
}
@PutMapping("/saveMemory/{macAddress}")
@@ -139,88 +112,15 @@ public class AgentController {
}
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
- return updateAgentById(device.getAgentId(), agentUpdateDTO);
+ agentService.updateAgentById(device.getAgentId(), agentUpdateDTO);
+ return new Result<>();
}
@PutMapping("/{id}")
@Operation(summary = "更新智能体")
@RequiresPermissions("sys:role:normal")
public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
- return updateAgentById(id, dto);
- }
-
- private Result updateAgentById(String id, AgentUpdateDTO dto) {
- // 先查询现有实体
- AgentEntity existingEntity = agentService.getAgentById(id);
- if (existingEntity == null) {
- return new Result().error("智能体不存在");
- }
-
- // 只更新提供的非空字段
- if (dto.getAgentName() != null) {
- existingEntity.setAgentName(dto.getAgentName());
- }
- if (dto.getAgentCode() != null) {
- existingEntity.setAgentCode(dto.getAgentCode());
- }
- if (dto.getAsrModelId() != null) {
- existingEntity.setAsrModelId(dto.getAsrModelId());
- }
- if (dto.getVadModelId() != null) {
- existingEntity.setVadModelId(dto.getVadModelId());
- }
- if (dto.getLlmModelId() != null) {
- existingEntity.setLlmModelId(dto.getLlmModelId());
- }
- if (dto.getVllmModelId() != null) {
- existingEntity.setVllmModelId(dto.getVllmModelId());
- }
- if (dto.getTtsModelId() != null) {
- existingEntity.setTtsModelId(dto.getTtsModelId());
- }
- if (dto.getTtsVoiceId() != null) {
- existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
- }
- if (dto.getMemModelId() != null) {
- existingEntity.setMemModelId(dto.getMemModelId());
- }
- if (dto.getIntentModelId() != null) {
- existingEntity.setIntentModelId(dto.getIntentModelId());
- }
- if (dto.getSystemPrompt() != null) {
- existingEntity.setSystemPrompt(dto.getSystemPrompt());
- }
- if (dto.getSummaryMemory() != null) {
- existingEntity.setSummaryMemory(dto.getSummaryMemory());
- }
- if (dto.getChatHistoryConf() != null) {
- existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
- }
- if (dto.getLangCode() != null) {
- existingEntity.setLangCode(dto.getLangCode());
- }
- if (dto.getLanguage() != null) {
- existingEntity.setLanguage(dto.getLanguage());
- }
- if (dto.getSort() != null) {
- existingEntity.setSort(dto.getSort());
- }
-
- // 设置更新者信息
- UserDetail user = SecurityUser.getUser();
- existingEntity.setUpdater(user.getId());
- existingEntity.setUpdatedAt(new Date());
-
- // 更新记忆策略
- if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
- // 删除所有记录
- agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
- existingEntity.setSummaryMemory("");
- } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
- // 删除音频数据
- agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
- }
- agentService.updateById(existingEntity);
+ agentService.updateAgentById(id, dto);
return new Result<>();
}
@@ -232,6 +132,8 @@ public class AgentController {
deviceService.deleteByAgentId(id);
// 删除关联的聊天记录
agentChatHistoryService.deleteByAgentId(id, true, true);
+ // 删除关联的插件
+ agentPluginMappingService.deleteByAgentId(id);
// 再删除智能体
agentService.deleteById(id);
return new Result<>();
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentMcpAccessPointController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentMcpAccessPointController.java
new file mode 100644
index 00000000..3f369774
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentMcpAccessPointController.java
@@ -0,0 +1,66 @@
+package xiaozhi.modules.agent.controller;
+
+import java.util.List;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import xiaozhi.common.user.UserDetail;
+import xiaozhi.common.utils.Result;
+import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
+import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.security.user.SecurityUser;
+
+@Tag(name = "智能体Mcp接入点管理")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/agent/mcp")
+public class AgentMcpAccessPointController {
+ private final AgentMcpAccessPointService agentMcpAccessPointService;
+ private final AgentService agentService;
+
+ /**
+ * 获取智能体的Mcp接入点地址
+ *
+ * @param audioId 智能体id
+ * @return 返回错误提醒或者Mcp接入点地址
+ */
+ @Operation(summary = "获取智能体的Mcp接入点地址")
+ @GetMapping("/address/{agentId}")
+ @RequiresPermissions("sys:role:normal")
+ public Result getAgentMcpAccessAddress(@PathVariable("agentId") String agentId) {
+ // 获取当前用户
+ UserDetail user = SecurityUser.getUser();
+
+ // 检查权限
+ if (!agentService.checkAgentPermission(agentId, user.getId())) {
+ return new Result().error("没有权限查看该智能体的MCP接入点地址");
+ }
+ String agentMcpAccessAddress = agentMcpAccessPointService.getAgentMcpAccessAddress(agentId);
+ if (agentMcpAccessAddress == null) {
+ return new Result().ok("请联系管理员进入参数管理配置mcp接入点地址");
+ }
+ return new Result().ok(agentMcpAccessAddress);
+ }
+
+ @Operation(summary = "获取智能体的Mcp工具列表")
+ @GetMapping("/tools/{agentId}")
+ @RequiresPermissions("sys:role:normal")
+ public Result> getAgentMcpToolsList(@PathVariable("agentId") String agentId) {
+ // 获取当前用户
+ UserDetail user = SecurityUser.getUser();
+
+ // 检查权限
+ if (!agentService.checkAgentPermission(agentId, user.getId())) {
+ return new Result>().error("没有权限查看该智能体的MCP工具列表");
+ }
+ List agentMcpToolsList = agentMcpAccessPointService.getAgentMcpToolsList(agentId);
+ return new Result>().ok(agentMcpToolsList);
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
index 3b4be452..03e569cd 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java
@@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.agent.entity.AgentEntity;
+import xiaozhi.modules.agent.vo.AgentInfoVO;
@Mapper
public interface AgentDao extends BaseDao {
@@ -28,4 +29,11 @@ public interface AgentDao extends BaseDao {
" WHERE d.mac_address = #{macAddress} " +
" ORDER BY d.id DESC LIMIT 1")
AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
+
+ /**
+ * 根据id查询agent信息,包括插件信息
+ *
+ * @param agentId 智能体ID
+ */
+ AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java
new file mode 100644
index 00000000..2fe81753
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.agent.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import xiaozhi.modules.agent.entity.AgentPluginMapping;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+/**
+* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Mapper
+* @createDate 2025-05-25 22:33:17
+* @Entity xiaozhi.modules.agent.entity.AgentPluginMapping
+*/
+@Mapper
+public interface AgentPluginMappingMapper extends BaseMapper {
+ List selectPluginsByAgentId(@Param("agentId") String agentId);
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java
index 32190d98..0e3d9bc3 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java
@@ -1,6 +1,8 @@
package xiaozhi.modules.agent.dto;
import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -15,19 +17,19 @@ import lombok.Data;
public class AgentUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L;
- @Schema(description = "智能体编码", example = "AGT_1234567890", required = false)
+ @Schema(description = "智能体编码", example = "AGT_1234567890", nullable = true)
private String agentCode;
- @Schema(description = "智能体名称", example = "客服助手", required = false)
+ @Schema(description = "智能体名称", example = "客服助手", nullable = true)
private String agentName;
- @Schema(description = "语音识别模型标识", example = "asr_model_02", required = false)
+ @Schema(description = "语音识别模型标识", example = "asr_model_02", nullable = true)
private String asrModelId;
- @Schema(description = "语音活动检测标识", example = "vad_model_02", required = false)
+ @Schema(description = "语音活动检测标识", example = "vad_model_02", nullable = true)
private String vadModelId;
- @Schema(description = "大语言模型标识", example = "llm_model_02", required = false)
+ @Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true)
private String llmModelId;
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
@@ -36,31 +38,46 @@ public class AgentUpdateDTO implements Serializable {
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
private String ttsModelId;
- @Schema(description = "音色标识", example = "voice_02", required = false)
+ @Schema(description = "音色标识", example = "voice_02", nullable = true)
private String ttsVoiceId;
- @Schema(description = "记忆模型标识", example = "mem_model_02", required = false)
+ @Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true)
private String memModelId;
- @Schema(description = "意图模型标识", example = "intent_model_02", required = false)
+ @Schema(description = "意图模型标识", example = "intent_model_02", nullable = true)
private String intentModelId;
- @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
+ @Schema(description = "插件函数信息", nullable = true)
+ private List functions;
+
+ @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", nullable = true)
private String systemPrompt;
- @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
- "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
+ @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n"
+ + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", nullable = true)
private String summaryMemory;
- @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false)
+ @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", nullable = true)
private Integer chatHistoryConf;
- @Schema(description = "语言编码", example = "zh_CN", required = false)
+ @Schema(description = "语言编码", example = "zh_CN", nullable = true)
private String langCode;
- @Schema(description = "交互语种", example = "中文", required = false)
+ @Schema(description = "交互语种", example = "中文", nullable = true)
private String language;
- @Schema(description = "排序", example = "1", required = false)
+ @Schema(description = "排序", example = "1", nullable = true)
private Integer sort;
+
+ @Data
+ @Schema(description = "插件函数信息")
+ public static class FunctionInfo implements Serializable {
+ @Schema(description = "插件ID", example = "plugin_01")
+ private String pluginId;
+
+ @Schema(description = "函数参数信息", nullable = true)
+ private HashMap paramInfo;
+
+ private static final long serialVersionUID = 1L;
+ }
}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java
new file mode 100644
index 00000000..e4fac002
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java
@@ -0,0 +1,32 @@
+package xiaozhi.modules.agent.dto;
+
+import lombok.Data;
+
+/**
+ * MCP JSON-RPC 请求 DTO
+ */
+@Data
+public class McpJsonRpcRequest {
+ private String jsonrpc = "2.0";
+ private String method;
+ private Object params;
+ private Integer id;
+
+ public McpJsonRpcRequest() {
+ }
+
+ public McpJsonRpcRequest(String method) {
+ this.method = method;
+ }
+
+ public McpJsonRpcRequest(String method, Object params, Integer id) {
+ this.method = method;
+ this.params = params;
+ this.id = id;
+ }
+
+ public McpJsonRpcRequest(String method, Object params) {
+ this.method = method;
+ this.params = params;
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java
new file mode 100644
index 00000000..1bd667e6
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java
@@ -0,0 +1,48 @@
+package xiaozhi.modules.agent.dto;
+
+import lombok.Data;
+
+/**
+ * MCP JSON-RPC 响应 DTO
+ */
+@Data
+public class McpJsonRpcResponse {
+ private String jsonrpc = "2.0";
+ private Integer id;
+ private McpResult result;
+ private McpError error;
+
+ public McpJsonRpcResponse() {
+ }
+
+ @Data
+ public static class McpResult {
+ private String type;
+ private String message;
+ private String agent_id;
+ private McpTool[] tools;
+
+ public McpResult() {
+ }
+ }
+
+ @Data
+ public static class McpTool {
+ private String name;
+ private String description;
+ private Object inputSchema;
+
+ public McpTool() {
+ }
+ }
+
+ @Data
+ public static class McpError {
+ private Integer code;
+ private String message;
+ private Object data;
+
+ public McpError() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java
new file mode 100644
index 00000000..ae99b0eb
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java
@@ -0,0 +1,54 @@
+package xiaozhi.modules.agent.entity;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * Agent与插件的唯一映射表
+ *
+ * @TableName ai_agent_plugin_mapping
+ */
+@Data
+@TableName(value = "ai_agent_plugin_mapping")
+@Schema(description = "Agent与插件的唯一映射表")
+public class AgentPluginMapping implements Serializable {
+ /**
+ * 主键
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ @Schema(description = "映射信息主键ID")
+ private Long id;
+
+ /**
+ * 智能体ID
+ */
+ @Schema(description = "智能体ID")
+ private String agentId;
+
+ /**
+ * 插件ID
+ */
+ @Schema(description = "插件ID")
+ private String pluginId;
+
+ /**
+ * 插件参数(Json)格式
+ */
+ @Schema(description = "插件参数(Json)格式")
+ private String paramInfo;
+
+ // 冗余字段,用于方便在根据id查询插件时,对照查出插件的Provider_code,详见dao层xml文件
+ @TableField(exist = false)
+ @Schema(description = "插件provider_code, 对应表ai_model_provider")
+ private String providerCode;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentMcpAccessPointService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentMcpAccessPointService.java
new file mode 100644
index 00000000..336ade65
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentMcpAccessPointService.java
@@ -0,0 +1,25 @@
+package xiaozhi.modules.agent.service;
+
+
+import java.util.List;
+
+/**
+ * 智能体Mcp接入点处理service
+ *
+ * @author zjy
+ */
+public interface AgentMcpAccessPointService {
+ /**
+ * 获取智能体的mcp接入点地址
+ * @param id 智能体id
+ * @return mcp接入点地址
+ */
+ String getAgentMcpAccessAddress(String id);
+
+ /**
+ * 获取智能体的mcp接入点已有的工具列表
+ * @param id 智能体id
+ * @return 工具列表
+ */
+ List getAgentMcpToolsList(String id);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java
new file mode 100644
index 00000000..79d97d2e
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java
@@ -0,0 +1,29 @@
+package xiaozhi.modules.agent.service;
+
+import java.util.List;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import xiaozhi.modules.agent.entity.AgentPluginMapping;
+
+/**
+ * @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service
+ * @createDate 2025-05-25 22:33:17
+ */
+public interface AgentPluginMappingService extends IService {
+
+ /**
+ * 根据智能体id获取插件参数
+ *
+ * @param agentId
+ * @return
+ */
+ List agentPluginParamsByAgentId(String agentId);
+
+ /**
+ * 根据智能体id删除插件参数
+ *
+ * @param agentId
+ */
+ void deleteByAgentId(String agentId);
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
index 2104d353..a9d4e5bb 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
@@ -5,8 +5,11 @@ import java.util.Map;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
+import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO;
+import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
+import xiaozhi.modules.agent.vo.AgentInfoVO;
/**
* 智能体表处理service
@@ -30,7 +33,7 @@ public interface AgentService extends BaseService {
* @param id 智能体ID
* @return 智能体实体
*/
- AgentEntity getAgentById(String id);
+ AgentInfoVO getAgentById(String id);
/**
* 插入智能体
@@ -79,4 +82,20 @@ public interface AgentService extends BaseService {
* @return 是否有权限
*/
boolean checkAgentPermission(String agentId, Long userId);
+
+ /**
+ * 更新智能体
+ *
+ * @param agentId 智能体ID
+ * @param dto 更新智能体所需的信息
+ */
+ void updateAgentById(String agentId, AgentUpdateDTO dto);
+
+ /**
+ * 创建智能体
+ *
+ * @param dto 创建智能体所需的信息
+ * @return 创建的智能体ID
+ */
+ String createAgent(AgentCreateDTO dto);
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java
index 087c1b9d..36b59993 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java
@@ -19,6 +19,8 @@ import xiaozhi.modules.agent.service.AgentChatAudioService;
import xiaozhi.modules.agent.service.AgentChatHistoryService;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
+import xiaozhi.modules.device.entity.DeviceEntity;
+import xiaozhi.modules.device.service.DeviceService;
/**
* {@link AgentChatHistoryBizService} impl
@@ -35,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
private final AgentChatHistoryService agentChatHistoryService;
private final AgentChatAudioService agentChatAudioService;
private final RedisUtils redisUtils;
+ private final DeviceService deviceService;
/**
* 处理聊天记录上报,包括文件上传和相关信息记录
@@ -68,6 +71,15 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
// 更新设备最后对话时间
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
+
+ // 更新设备最后连接时间
+ DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
+ if (device != null) {
+ deviceService.updateDeviceConnectionInfo(agentId, device.getId(), null);
+ } else {
+ log.warn("聊天记录上报时,未找到mac地址为 {} 的设备", macAddress);
+ }
+
return Boolean.TRUE;
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java
new file mode 100644
index 00000000..e45d2201
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java
@@ -0,0 +1,180 @@
+package xiaozhi.modules.agent.service.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import xiaozhi.common.constant.Constant;
+import xiaozhi.common.utils.AESUtils;
+import xiaozhi.common.utils.HashEncryptionUtil;
+import xiaozhi.common.utils.JsonUtils;
+import xiaozhi.modules.agent.dto.McpJsonRpcRequest;
+import xiaozhi.modules.agent.dto.McpJsonRpcResponse;
+import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
+import xiaozhi.modules.sys.service.SysParamsService;
+import xiaozhi.modules.sys.utils.WebSocketClientManager;
+
+@AllArgsConstructor
+@Service
+@Slf4j
+public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointService {
+ private SysParamsService sysParamsService;
+
+ @Override
+ public String getAgentMcpAccessAddress(String id) {
+ // 获取到mcp的地址
+ String url = sysParamsService.getValue(Constant.SERVER_MCP_ENDPOINT, true);
+ if (StringUtils.isBlank(url) || "null".equals(url)) {
+ return null;
+ }
+ URI uri = getURI(url);
+ // 获取智能体mcp的url前缀
+ String agentMcpUrl = getAgentMcpUrl(uri);
+ // 获取密钥
+ String key = getSecretKey(uri);
+ // 获取加密的token
+ String encryptToken = encryptToken(id, key);
+ // 对token进行URL编码
+ String encodedToken = URLEncoder.encode(encryptToken, StandardCharsets.UTF_8);
+ // 返回智能体Mcp路径的格式
+ agentMcpUrl = "%s/mcp/?token=%s".formatted(agentMcpUrl, encodedToken);
+ return agentMcpUrl;
+ }
+
+ @Override
+ public List getAgentMcpToolsList(String id) {
+ String wsUrl = getAgentMcpAccessAddress(id);
+ if (StringUtils.isBlank(wsUrl)) {
+ return List.of();
+ }
+
+ // 将 /mcp 替换为 /call
+ wsUrl = wsUrl.replace("/mcp/", "/call/");
+
+ try {
+ // 创建 WebSocket 连接
+ try (WebSocketClientManager client = WebSocketClientManager.build(
+ new WebSocketClientManager.Builder()
+ .uri(wsUrl)
+ .connectTimeout(5, TimeUnit.SECONDS)
+ .maxSessionDuration(20, TimeUnit.SECONDS))) {
+
+ // 发送初始化通知
+ McpJsonRpcRequest initRequest = new McpJsonRpcRequest("notifications/initialized");
+ client.sendJson(initRequest);
+
+ // 等待 0.2 秒
+ Thread.sleep(200);
+
+ // 发送工具列表请求
+ McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 1);
+ client.sendJson(toolsRequest);
+
+ // 监听响应,直到收到包含 id=1 的响应
+ List responses = client.listener(response -> {
+ try {
+ McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class);
+ return jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId());
+ } catch (Exception e) {
+ log.warn("解析响应失败: {}", response, e);
+ return false;
+ }
+ });
+
+ // 处理响应
+ for (String response : responses) {
+ try {
+ McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class);
+ if (jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId())
+ && jsonResponse.getResult() != null && jsonResponse.getResult().getTools() != null) {
+
+ // 提取工具名称列表
+ return java.util.Arrays.stream(jsonResponse.getResult().getTools())
+ .map(McpJsonRpcResponse.McpTool::getName)
+ .collect(Collectors.toList());
+ }
+ } catch (Exception e) {
+ log.warn("处理工具列表响应失败: {}", response, e);
+ }
+ }
+
+ log.warn("未找到有效的工具列表响应");
+ return List.of();
+
+ }
+ } catch (Exception e) {
+ log.error("获取智能体 MCP 工具列表失败,智能体ID: {}", id, e);
+ return List.of();
+ }
+ }
+
+ /**
+ * 获取URI对象
+ *
+ * @param url 路径
+ * @return URI对象
+ */
+ private static URI getURI(String url) {
+ try {
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ log.error("路径格式不正确路径:{},\n错误信息:{}", url, e.getMessage());
+ throw new RuntimeException("mcp的地址存在错误,请进入参数管理修改mcp接入点地址");
+ }
+ }
+
+ /**
+ * 获取密钥
+ *
+ * @param uri mcp地址
+ * @return 密钥
+ */
+ private static String getSecretKey(URI uri) {
+ // 获取参数
+ String query = uri.getQuery();
+ // 获取aes加密密钥
+ String str = "key=";
+ return query.substring(query.indexOf(str) + str.length());
+ }
+
+ /**
+ * 获取智能体mcp接入点url
+ *
+ * @param uri mcp地址
+ * @return 智能体mcp接入点url
+ */
+ private String getAgentMcpUrl(URI uri) {
+ // 获取协议
+ String wsScheme = (uri.getScheme().equals("https")) ? "wss" : "ws";
+ // 获取主机,端口,路径
+ String path = uri.getSchemeSpecificPart();
+ // 获取到最后一个/前的path
+ path = path.substring(0, path.lastIndexOf("/"));
+ return wsScheme + ":" + path;
+ }
+
+ /**
+ * 获取对智能体id加密的token
+ *
+ * @param agentId 智能体id
+ * @param key 加密密钥
+ * @return 加密后token
+ */
+ private static String encryptToken(String agentId, String key) {
+ // 使用md5对智能体id进行加密
+ String md5 = HashEncryptionUtil.Md5hexDigest(agentId);
+ // aes需要加密文本
+ String json = "{\"agentId\": \"%s\"}".formatted(md5);
+ // 加密后成token值
+ return AESUtils.encrypt(key, json);
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java
new file mode 100644
index 00000000..2a53d474
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java
@@ -0,0 +1,37 @@
+package xiaozhi.modules.agent.service.impl;
+
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import lombok.RequiredArgsConstructor;
+import xiaozhi.modules.agent.dao.AgentPluginMappingMapper;
+import xiaozhi.modules.agent.entity.AgentPluginMapping;
+import xiaozhi.modules.agent.service.AgentPluginMappingService;
+
+/**
+ * @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service实现
+ * @createDate 2025-05-25 22:33:17
+ */
+@Service
+@RequiredArgsConstructor
+public class AgentPluginMappingServiceImpl extends ServiceImpl
+ implements AgentPluginMappingService {
+ private final AgentPluginMappingMapper agentPluginMappingMapper;
+
+ @Override
+ public List agentPluginParamsByAgentId(String agentId) {
+ return agentPluginMappingMapper.selectPluginsByAgentId(agentId);
+ }
+
+ @Override
+ public void deleteByAgentId(String agentId) {
+ UpdateWrapper updateWrapper = new UpdateWrapper<>();
+ updateWrapper.eq("agent_id", agentId);
+ agentPluginMappingMapper.delete(updateWrapper);
+ }
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java
index ad6e75c3..36aa1192 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java
@@ -1,12 +1,17 @@
package xiaozhi.modules.agent.service.impl;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -14,16 +19,30 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
+import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl;
+import xiaozhi.common.user.UserDetail;
+import xiaozhi.common.utils.ConvertUtils;
+import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.agent.dao.AgentDao;
+import xiaozhi.modules.agent.dto.AgentCreateDTO;
import xiaozhi.modules.agent.dto.AgentDTO;
+import xiaozhi.modules.agent.dto.AgentUpdateDTO;
import xiaozhi.modules.agent.entity.AgentEntity;
+import xiaozhi.modules.agent.entity.AgentPluginMapping;
+import xiaozhi.modules.agent.entity.AgentTemplateEntity;
+import xiaozhi.modules.agent.service.AgentChatHistoryService;
+import xiaozhi.modules.agent.service.AgentPluginMappingService;
import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.agent.service.AgentTemplateService;
+import xiaozhi.modules.agent.vo.AgentInfoVO;
import xiaozhi.modules.device.service.DeviceService;
+import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.service.ModelConfigService;
+import xiaozhi.modules.model.service.ModelProviderService;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.enums.SuperAdminEnum;
import xiaozhi.modules.timbre.service.TimbreService;
@@ -36,6 +55,10 @@ public class AgentServiceImpl extends BaseServiceImpl imp
private final ModelConfigService modelConfigService;
private final RedisUtils redisUtils;
private final DeviceService deviceService;
+ private final AgentPluginMappingService agentPluginMappingService;
+ private final AgentChatHistoryService agentChatHistoryService;
+ private final AgentTemplateService agentTemplateService;
+ private final ModelProviderService modelProviderService;
@Override
public PageData adminAgentList(Map params) {
@@ -46,15 +69,20 @@ public class AgentServiceImpl extends BaseServiceImpl imp
}
@Override
- public AgentEntity getAgentById(String id) {
- AgentEntity agent = agentDao.selectById(id);
- if (agent != null && agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
- agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
- } else if (agent != null && agent.getMemModelId() != null
- && !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)
- && agent.getChatHistoryConf() == null) {
- agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
+ public AgentInfoVO getAgentById(String id) {
+ AgentInfoVO agent = agentDao.selectAgentInfoById(id);
+
+ if (agent == null) {
+ throw new RenException("智能体不存在");
}
+
+ if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
+ agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
+ if (agent.getChatHistoryConf() == null) {
+ agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
+ }
+ }
+ // 无需额外查询插件列表,已通过SQL查询出来
return agent;
}
@@ -167,4 +195,198 @@ public class AgentServiceImpl extends BaseServiceImpl imp
// 检查是否是智能体的所有者
return userId.equals(agent.getUserId());
}
+
+ // 根据id更新智能体信息
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateAgentById(String agentId, AgentUpdateDTO dto) {
+ // 先查询现有实体
+ AgentEntity existingEntity = this.getAgentById(agentId);
+ if (existingEntity == null) {
+ throw new RuntimeException("智能体不存在");
+ }
+
+ // 只更新提供的非空字段
+ if (dto.getAgentName() != null) {
+ existingEntity.setAgentName(dto.getAgentName());
+ }
+ if (dto.getAgentCode() != null) {
+ existingEntity.setAgentCode(dto.getAgentCode());
+ }
+ if (dto.getAsrModelId() != null) {
+ existingEntity.setAsrModelId(dto.getAsrModelId());
+ }
+ if (dto.getVadModelId() != null) {
+ existingEntity.setVadModelId(dto.getVadModelId());
+ }
+ if (dto.getLlmModelId() != null) {
+ existingEntity.setLlmModelId(dto.getLlmModelId());
+ }
+ if (dto.getVllmModelId() != null) {
+ existingEntity.setVllmModelId(dto.getVllmModelId());
+ }
+ if (dto.getTtsModelId() != null) {
+ existingEntity.setTtsModelId(dto.getTtsModelId());
+ }
+ if (dto.getTtsVoiceId() != null) {
+ existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
+ }
+ if (dto.getMemModelId() != null) {
+ existingEntity.setMemModelId(dto.getMemModelId());
+ }
+ if (dto.getIntentModelId() != null) {
+ existingEntity.setIntentModelId(dto.getIntentModelId());
+ }
+ if (dto.getSystemPrompt() != null) {
+ existingEntity.setSystemPrompt(dto.getSystemPrompt());
+ }
+ if (dto.getSummaryMemory() != null) {
+ existingEntity.setSummaryMemory(dto.getSummaryMemory());
+ }
+ if (dto.getChatHistoryConf() != null) {
+ existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
+ }
+ if (dto.getLangCode() != null) {
+ existingEntity.setLangCode(dto.getLangCode());
+ }
+ if (dto.getLanguage() != null) {
+ existingEntity.setLanguage(dto.getLanguage());
+ }
+ if (dto.getSort() != null) {
+ existingEntity.setSort(dto.getSort());
+ }
+
+ // 更新函数插件信息
+ List functions = dto.getFunctions();
+ if (functions != null) {
+ // 1. 收集本次提交的 pluginId
+ List newPluginIds = functions.stream()
+ .map(AgentUpdateDTO.FunctionInfo::getPluginId)
+ .toList();
+
+ // 2. 查询当前agent现有的所有映射
+ List existing = agentPluginMappingService.list(
+ new QueryWrapper()
+ .eq("agent_id", agentId));
+ Map existMap = existing.stream()
+ .collect(Collectors.toMap(AgentPluginMapping::getPluginId, Function.identity()));
+
+ // 3. 构造所有要 保存或更新 的实体
+ List allToPersist = functions.stream().map(info -> {
+ AgentPluginMapping m = new AgentPluginMapping();
+ m.setAgentId(agentId);
+ m.setPluginId(info.getPluginId());
+ m.setParamInfo(JsonUtils.toJsonString(info.getParamInfo()));
+ AgentPluginMapping old = existMap.get(info.getPluginId());
+ if (old != null) {
+ // 已存在,设置id表示更新
+ m.setId(old.getId());
+ }
+ return m;
+ }).toList();
+
+ // 4. 拆分:已有ID的走更新,无ID的走插入
+ List toUpdate = allToPersist.stream()
+ .filter(m -> m.getId() != null)
+ .toList();
+ List toInsert = allToPersist.stream()
+ .filter(m -> m.getId() == null)
+ .toList();
+
+ if (!toUpdate.isEmpty()) {
+ agentPluginMappingService.updateBatchById(toUpdate);
+ }
+ if (!toInsert.isEmpty()) {
+ agentPluginMappingService.saveBatch(toInsert);
+ }
+
+ // 5. 删除本次不在提交列表里的插件映射
+ List toDelete = existing.stream()
+ .filter(old -> !newPluginIds.contains(old.getPluginId()))
+ .map(AgentPluginMapping::getId)
+ .toList();
+ if (!toDelete.isEmpty()) {
+ agentPluginMappingService.removeBatchByIds(toDelete);
+ }
+ }
+
+ // 设置更新者信息
+ UserDetail user = SecurityUser.getUser();
+ existingEntity.setUpdater(user.getId());
+ existingEntity.setUpdatedAt(new Date());
+
+ // 更新记忆策略
+ if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
+ // 删除所有记录
+ agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
+ existingEntity.setSummaryMemory("");
+ } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
+ // 删除音频数据
+ agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
+ }
+ this.updateById(existingEntity);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String createAgent(AgentCreateDTO dto) {
+ // 转换为实体
+ AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
+
+ // 获取默认模板
+ AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
+ if (template != null) {
+ // 设置模板中的默认值
+ entity.setAsrModelId(template.getAsrModelId());
+ entity.setVadModelId(template.getVadModelId());
+ entity.setLlmModelId(template.getLlmModelId());
+ entity.setVllmModelId(template.getVllmModelId());
+ entity.setTtsModelId(template.getTtsModelId());
+ entity.setTtsVoiceId(template.getTtsVoiceId());
+ entity.setMemModelId(template.getMemModelId());
+ entity.setIntentModelId(template.getIntentModelId());
+ entity.setSystemPrompt(template.getSystemPrompt());
+ entity.setSummaryMemory(template.getSummaryMemory());
+ entity.setChatHistoryConf(template.getChatHistoryConf());
+ entity.setLangCode(template.getLangCode());
+ entity.setLanguage(template.getLanguage());
+ }
+
+ // 设置用户ID和创建者信息
+ UserDetail user = SecurityUser.getUser();
+ entity.setUserId(user.getId());
+ entity.setCreator(user.getId());
+ entity.setCreatedAt(new Date());
+
+ // 保存智能体
+ insert(entity);
+
+ // 设置默认插件
+ List toInsert = new ArrayList<>();
+ // 播放音乐、查天气、查新闻
+ String[] pluginIds = new String[] { "SYSTEM_PLUGIN_MUSIC", "SYSTEM_PLUGIN_WEATHER",
+ "SYSTEM_PLUGIN_NEWS_NEWSNOW" };
+ for (String pluginId : pluginIds) {
+ ModelProviderDTO provider = modelProviderService.getById(pluginId);
+ if (provider == null) {
+ continue;
+ }
+ AgentPluginMapping mapping = new AgentPluginMapping();
+ mapping.setPluginId(pluginId);
+
+ Map paramInfo = new HashMap<>();
+ List