diff --git a/README.md b/README.md
index fc41f2bd..bca5f9ee 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
本项目基于人机共生智能理论和技术研发智能终端软硬件体系
为开源智能硬件项目
xiaozhi-esp32提供后端服务
根据小智通信协议使用Python、Java、Vue实现
-支持MQTT+UDP协议、Websocket协议、MCP接入点、声纹识别
+支持MQTT+UDP协议、Websocket协议、MCP接入点、声纹识别、知识库
@@ -248,6 +248,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
| 视觉感知 | 支持多种VLLM(视觉大模型),实现多模态交互 |
| 意图识别 | 支持LLM意图识别、Function Call函数调用,提供插件化意图处理机制 |
| 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 |
+| 知识库 | 支持RAGFlow知识库,让大模型判断需要调度知识库后再回答 |
| 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 |
| 指令下发 | 依托MQTT协议,支持从智控台将MCP指令下发到ESP32设备 |
| 管理后台 | 提供Web管理界面,支持用户管理、系统配置和设备管理;界面支持中文简体、中文繁体、英文显示 |
diff --git a/README_en.md b/README_en.md
index bc0ddea9..015f1054 100644
--- a/README_en.md
+++ b/README_en.md
@@ -6,7 +6,7 @@
This project is based on human-machine symbiotic intelligence theory and technology to develop intelligent terminal hardware and software systems
providing backend services for the open-source intelligent hardware project
xiaozhi-esp32
Implemented using Python, Java, and Vue according to the Xiaozhi Communication Protocol
-Supports MCP endpoints and voiceprint recognition
+Support for MQTT+UDP protocol, Websocket protocol, MCP access point, voiceprint recognition, and knowledge base
@@ -244,6 +244,7 @@ This project provides the following testing tools to help you verify the system
| Visual Perception | Supports multiple VLLM(vision large models), implements multimodal interaction |
| Intent Recognition | Supports LLM intent recognition, Function Call function calling, provides plugin-based intent processing mechanism |
| Memory System | Supports local short-term memory, mem0ai interface memory, with memory summarization functionality |
+| Knowledge Base | Supports RAGFlow knowledge base, enabling LLM to judge whether to schedule the knowledge base after receiving the user's question, and then answer the question |
| Command Delivery | Supports MCP command delivery to ESP32 devices via MQTT protocol from Smart Console |
| Tool Calling | Supports client IOT protocol, client MCP protocol, server MCP protocol, MCP endpoint protocol, custom tool functions |
| Management Backend | Provides Web management interface, supports user management, system configuration and device management; Supports Simplified Chinese, Traditional Chinese and English display |
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 1e237921..83df8c2c 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -79,6 +79,7 @@ VAD:
6、[MCP方法如何获取设备信息](./mcp-get-device-info.md)
7、[如何开启声纹识别](./voiceprint-integration.md)
8、[新闻插件源配置指南](./newsnow_plugin_config.md)
+9、[知识库ragflow集成指南](./ragflow-integration.md)
### 11、语音克隆、本地语音部署相关教程
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)
diff --git a/docs/ragflow-integration.md b/docs/ragflow-integration.md
new file mode 100644
index 00000000..5010cde3
--- /dev/null
+++ b/docs/ragflow-integration.md
@@ -0,0 +1,262 @@
+# ragflow 集成指南
+
+本教程主要是是两部分
+
+- 一、如何部署ragflow
+- 二、如何在智控台配置ragflow接口
+
+如果您对ragflow很熟悉,且已经部署了ragflow,可直接跳过第一部分,直接进入第二部分。但是如果你希望有人指导你部署ragflow,让它能够和`xiaozhi-esp32-server`共同使用`mysql`、`redis`基础服务,以减少资源成本,你需要从第一部分开始。
+
+# 第一部分 如何部署ragflow
+## 第一步, 确认mysql、redis是否可用
+
+ragflow需要依赖`mysql`数据库。如果你之前已经部署`智控台`,说明你已经安装了`mysql`。你可以共用它。
+
+你可以你试一下在宿主机使用`telnet`命令,看看能不能正常访问`mysql`的`3306`端口。
+``` shell
+telnet 127.0.0.1 3306
+
+telnet 127.0.0.1 6379
+```
+如果能访问到`3306`端口和`6379`端口,请忽略以下的内容,直接进入第二步。
+
+如果不能访问,你需要回忆一下,你的`mysql`是怎么安装的。
+
+如果你的mysql是通过自己使用安装包安装的,说明你的`mysql`做了网络隔离。你可能先解决访问`mysql`的`3306`端口这个问题。
+
+如果你`mysql`是通过本项目的`docker-compose_all.yml`安装的。你需要找一下你当时创建数据库的`docker-compose_all.yml`文件,修改以下的内容
+
+修改前
+``` yaml
+ xiaozhi-esp32-server-db:
+ ...
+ networks:
+ - default
+ expose:
+ - "3306:3306"
+ xiaozhi-esp32-server-redis:
+ ...
+ expose:
+ - 6379
+```
+
+修改后
+``` yaml
+ xiaozhi-esp32-server-db:
+ ...
+ networks:
+ - default
+ ports:
+ - "3306:3306"
+ xiaozhi-esp32-server-redis:
+ ...
+ ports:
+ - "6379:6379"
+```
+
+注意是将`xiaozhi-esp32-server-db`和`xiaozhi-esp32-server-redis`下面的`expose`改成`ports`。改完后,需要重新启动。以下是重启mysql的命令:
+
+``` shell
+# 进入你docker-compose_all.yml所在的文件夹,例如我的是xiaozhi-server
+cd xiaozhi-server
+docker compose -f docker-compose_all.yml down
+docker compose -f docker-compose.yml up -d
+```
+
+启动完后,在宿主机再使用`telnet`命令,看看能不能正常访问`mysql`的`3306`端口。
+``` shell
+telnet 127.0.0.1 3306
+
+telnet 127.0.0.1 6379
+```
+正常来说这样就可以访问的了。
+
+## 第二步, 创建数据库和表
+如果你的宿主机,能正常访问mysql数据库,那就在mysql上创建一个名字为`rag_flow`的数据库和`rag_flow`用户,密码为`infini_rag_flow`。
+
+``` sql
+-- 创建数据库
+CREATE DATABASE IF NOT EXISTS rag_flow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- 创建用户并授权
+CREATE USER IF NOT EXISTS 'rag_flow'@'%' IDENTIFIED BY 'infini_rag_flow';
+GRANT ALL PRIVILEGES ON rag_flow.* TO 'rag_flow'@'%';
+
+-- 刷新权限
+FLUSH PRIVILEGES;
+```
+
+## 第三步, 下载ragflow项目
+
+你需要在你电脑找一个文件夹,用来存放ragflow项目。例如我在`/home/system/xiaozhi`文件夹。
+
+你可以使用`git`命令,将ragflow项目下载到这个文件夹,本教程使用的是`2025年11月11日`提交的`dd5b8e2e1a877211d17aae5552d8895b1bca763d`版本进行安装部署。
+```
+git clone https://ghfast.top/https://github.com/infiniflow/ragflow.git
+cd ragflow
+git reset --hard dd5b8e2e1a877211d17aae5552d8895b1bca763d
+
+```
+下载完后,进入`docker`文件夹。
+``` shell
+cd docker
+```
+修改`ragflow/docker`文件夹下的`docker-compose.yml`文件,将`ragflow-cpu`和`ragflow-gpu`服务的`depends_on`配置去掉,用于解除`ragflow-cpu`服务对`mysql`的依赖。
+
+这是修改前:
+``` yaml
+ ragflow-cpu:
+ depends_on:
+ mysql:
+ condition: service_healthy
+ profiles:
+ - cpu
+ ...
+ ragflow-gpu:
+ depends_on:
+ mysql:
+ condition: service_healthy
+ profiles:
+ - gpu
+```
+这是修改后:
+``` yaml
+ ragflow-cpu:
+ profiles:
+ - cpu
+ ...
+ ragflow-gpu:
+ profiles:
+ - gpu
+```
+
+接着,修改`ragflow/docker`文件夹下的`docker-compose-base.yml`文件,去掉`mysql`和`redis`的配置。
+
+例如,删除前:
+``` yaml
+services:
+ minio:
+ image: quay.io/minio/minio:RELEASE.2025-06-13T11-33-47Z
+ ...
+ mysql:
+ image: mysql:8.0
+ ...
+ redis:
+ image: redis:6.2-alpine
+ ...
+```
+
+删除后
+``` yaml
+services:
+ minio:
+ image: quay.io/minio/minio:RELEASE.2025-06-13T11-33-47Z
+ ...
+```
+## 第四步,修改环境变量配置
+
+编辑`ragflow/docker`文件夹下的`.env`文件,找到以下配置,逐个搜索,逐个修改!逐个搜索,逐个修改!
+
+``` env
+# ragflow镜像
+RAGFLOW_IMAGE=infiniflow/ragflow@sha256:d9ff494d1bf72138ce6fe0eaf4962cf4bea471c8c266d12b8bfb9d10ac3a2f6e
+# 端口设置
+SVR_WEB_HTTP_PORT=8008 # HTTP端口
+SVR_WEB_HTTPS_PORT=8009 # HTTPS端口
+# MySQL配置 - 修改为您本地MySQL的信息
+MYSQL_HOST=host.docker.internal # 使用host.docker.internal让容器访问主机服务
+MYSQL_PORT=3306 # 本地MySQL端口
+MYSQL_USER=rag_flow # 上面创建的用户名,如果没有这项就增加这一项
+MYSQL_PASSWORD=infini_rag_flow # 上面设置的密码
+MYSQL_DBNAME=rag_flow # 数据库名称
+
+# Redis配置 - 修改为您本地Redis的信息
+REDIS_HOST=host.docker.internal # 使用host.docker.internal让容器访问主机服务
+REDIS_PORT=6379 # 本地Redis端口
+REDIS_PASSWORD= # 如果你的Redis没有设置密码,这里填写None,否则填写密码
+```
+
+注意,如果你的Redis没有设置密码,还要修改`ragflow/docker`文件夹下`service_conf.yaml.template`,将`infini_rag_flow`替换成空字符串。
+
+修改前
+``` shell
+redis:
+ db: 1
+ password: '${REDIS_PASSWORD:-infini_rag_flow}'
+ host: '${REDIS_HOST:-redis}:6379'
+```
+修改后
+``` shell
+redis:
+ db: 1
+ password: '${REDIS_PASSWORD:-}'
+ host: '${REDIS_HOST:-redis}:6379'
+```
+
+## 第五步,启动ragflow服务
+执行命令:
+``` shell
+docker-compose -f docker-compose.yml up -d
+```
+执行成功后,你可以使用`docker logs -n 20 -f docker-ragflow-cpu-1`命令,查看`docker-ragflow-cpu-1`服务的日志。
+
+如果日志中没有报错,说明ragflow服务启动成功。
+
+# 第五步,注册账号
+你可以在浏览器中访问`http://127.0.0.1:8008`,点击`Sign Up`,注册一个账号。
+
+注册成功后,你可以点击`Sign In`,登录到ragflow服务。如果你想关闭ragflow服务的注册服务,不想让其他人注册账号,你可以在`ragflow/docker`文件夹下的`.env`文件中,将`REGISTER_ENABLED`配置项设置为`0`。
+
+``` dotenv
+REGISTER_ENABLED=0
+```
+修改后,重启启动ragflow服务。
+``` shell
+docker-compose -f docker-compose.yml down
+docker-compose -f docker-compose.yml up -d
+```
+
+# 第六步,配置ragflow服务的模型
+你可以在浏览器中访问`http://127.0.0.1:8008`,点击`Sign In`,登录到ragflow服务。点击页面右上角的`头像`,进入设置页面。
+首先,在左侧导航栏中,点击`模型供应商`,进入到模型配置页面。在右侧的`可选模型`搜索框下,选择`LLM`,在列表选择你使用的模型供应商,点击`添加`,输入你的密钥;
+然后,选择`TEXT EMBEDDING`,在列表选择你使用的模型供应商,点击`添加`,输入你的密钥。
+最后,刷新一下页面,分别点击`设置默认模型`列表的LLM和Embedding,选择你使用的模型即可。请确认你的密钥开通了相应的服务,比如我是用的Embedding模型是xxx供应商的,需要去这个供应商官网查看这个模型是否需要购买资源包才能使用。
+
+
+# 第二部分 配置ragflow服务
+
+# 第一步 登录ragflow服务
+你可以在浏览器中访问`http://127.0.0.1:8008`,点击`Sign In`,登录到ragflow服务。
+
+然后点击右上角的`头像`,进入设置页面。在左侧导航栏中,点击`API`功能,然后点击"API Key"按钮。出现一个弹框,
+
+在弹框中,点击"Create new Key"按钮,生成一个API Key。复制这个`API Key`,你稍后会用到。
+
+# 第二步 配置到智控台
+确保你的智控台版本是`0.8.7`或以上。使用超级管理员账号登录到智控台。在顶部导航栏中,点击`模型配置`,在左侧导航栏中,点击`知识库`。
+
+在列表中找到`RAG_RAGFlow`,点击`编辑`按钮。
+
+在`服务地址`中,填写`http://你的ragflow服务的局域网IP:8008`,例如我的ragflow服务的局域网IP是`192.168.1.100`,那么我就填写`http://192.168.1.100:8008`。
+
+在`API密钥`中,填写之前复制的`API Key`。
+
+最后点击保存按钮。
+
+# 第二步 创建一个知识库
+使用超级管理员账号登录到智控台。在顶部导航栏中,点击`知识库`,在列表左下脚,点击`新增`按钮。填写一个知识库的名字和描述。点击保存。
+
+为了提高大模型对知识库的理解和召回能力,建议在创建知识库时,填写一个有意义的名字和描述。例如,如果你要创建一个关于`公司介绍`的知识库,那么知识库的名字可以是`公司介绍`,描述可以是`关于公司的相关信息例如公司基本信息、服务项目、联系电话、地址等。`。
+
+保存后,你可以在知识库列表中看到这个知识库。点击刚才创建的知识库的`查看`按钮,进入知识库详情页面。
+
+在知识库详情页面中,左下角点击`新增`按钮,可以上传文档到知识库。
+
+上传后,你可以在知识库详情页面中,看到上传的文档。此时可以点击文档的`解析`按钮,解析文档。
+
+解析完成后,你可以查看解析后的切片信息。你可以在知识库详情页面中,点击`召回测试`按钮,可以测试知识库的召回/检索功能。
+
+# 第三步 让小智使用ragflow知识库
+登录到智控台。在顶部导航栏中,点击`智能体`,找到你要配置的智能体,点击`配置角色`按钮。
+
+在意图识别左侧,点击`编辑功能`按钮,弹出一个弹框。在弹框中选择你要添加的知识库。保存即可。
\ No newline at end of file
diff --git a/docs/voiceprint-integration.md b/docs/voiceprint-integration.md
index 9db1af0c..fbc12b1f 100644
--- a/docs/voiceprint-integration.md
+++ b/docs/voiceprint-integration.md
@@ -28,7 +28,7 @@ telnet 127.0.0.1 3306
如果不能访问,你需要回忆一下,你的`mysql`是怎么安装的。
-如果你的mysql是通过自己使用安装包安装的,说明你的`mysql`做了网络隔离。你可能先解访问`mysql`的`3306`端口这个问题。
+如果你的mysql是通过自己使用安装包安装的,说明你的`mysql`做了网络隔离。你可能先解决访问`mysql`的`3306`端口这个问题。
如果你`mysql`是通过本项目的`docker-compose_all.yml`安装的。你需要找一下你当时创建数据库的`docker-compose_all.yml`文件,修改以下的内容
diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java
index b8525e63..659e135a 100644
--- a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java
+++ b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java
@@ -205,8 +205,12 @@ public interface ErrorCode {
int RAG_CONFIG_NOT_FOUND = 10164; // RAG配置未找到
int RAG_CONFIG_TYPE_ERROR = 10165; // RAG配置类型错误
int RAG_DEFAULT_CONFIG_NOT_FOUND = 10166; // 默认RAG配置未找到
- int RAG_API_ERROR = 10167; // RAG配置缺少必要参数
+ int RAG_API_ERROR = 10167; // RAG调用失败
int UPLOAD_FILE_ERROR = 10168; // 上传文件失败
int NO_PERMISSION = 10169; // 没有权限
int KNOWLEDGE_BASE_NAME_EXISTS = 10170; // 同名知识库已存在
+ int RAG_API_ERROR_URL_NULL = 10171; // RAG配置中base_url为空,请完善配置
+ int RAG_API_ERROR_API_KEY_NULL = 10172; // RAG配置中api_key为空,请完善配置
+ int RAG_API_ERROR_API_KEY_INVALID = 10173; // RAG配置中api_key包含占位符,请替换为实际的API密钥
+ int RAG_API_ERROR_URL_INVALID = 10174; // RAG配置中base_url格式不正确,请检查协议是否正确
}
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
index 1ebb522a..fe197fbc 100644
--- 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
@@ -85,7 +85,7 @@ public class AgentPluginMappingServiceImpl extends ServiceImpl ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
deleteDatasetInRAGFlow(datasetId, ragConfig);
} catch (Exception deleteException) {
- log.warn("删除重复datasetId的RAGFlow数据集失败: {}", deleteException.getMessage());
+ // 提供更详细的错误信息,包括异常类型和消息
+ String errorMessage = "删除重复datasetId的RAGFlow数据集失败: " + deleteException.getClass().getSimpleName();
+ if (deleteException.getMessage() != null) {
+ errorMessage += " - " + deleteException.getMessage();
+ }
+ log.warn(errorMessage, deleteException);
}
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
}
@@ -201,6 +211,35 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
+
+ // 调用RAGFlow API更新数据集
+ updateDatasetInRAGFlow(
+ knowledgeBaseDTO.getDatasetId(),
+ knowledgeBaseDTO.getName(),
+ knowledgeBaseDTO.getDescription(),
+ ragConfig);
+
+ log.info("RAGFlow API更新成功,datasetId: {}", knowledgeBaseDTO.getDatasetId());
+ } catch (Exception e) {
+ // 提供更详细的错误信息,包括异常类型和消息
+ String errorMessage = "更新RAGFlow数据集失败: " + e.getClass().getSimpleName();
+ if (e.getMessage() != null) {
+ errorMessage += " - " + e.getMessage();
+ }
+ log.error(errorMessage, e);
+ throw e;
+ }
+ } else {
+ log.warn("datasetId或ragModelId为空,跳过RAGFlow更新");
+ }
+
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(knowledgeBaseDTO, KnowledgeBaseEntity.class);
knowledgeBaseDao.updateById(entity);
@@ -209,22 +248,6 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
- updateDatasetInRAGFlow(
- knowledgeBaseDTO.getDatasetId(),
- knowledgeBaseDTO.getName(),
- knowledgeBaseDTO.getDescription(),
- ragConfig);
- } catch (Exception e) {
- // 如果RAG API调用失败,回滚本地数据库操作
- knowledgeBaseDao.updateById(existingEntity);
- throw e;
- }
- }
-
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
@@ -265,22 +288,34 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl 0 ? "成功" : "失败");
-
- // 调用RAGFlow API删除数据集
+ // 先调用RAGFlow API删除数据集
+ boolean apiDeleteSuccess = false;
if (StringUtils.isNotBlank(entity.getDatasetId()) && StringUtils.isNotBlank(entity.getRagModelId())) {
try {
log.info("开始调用RAGFlow API删除数据集");
+ // 在删除前进行RAG配置校验
Map ragConfig = getValidatedRAGConfig(entity.getRagModelId());
deleteDatasetInRAGFlow(entity.getDatasetId(), ragConfig);
log.info("RAGFlow API删除调用完成");
+ apiDeleteSuccess = true;
} catch (Exception e) {
- log.warn("删除RAGFlow数据集失败: {}", e.getMessage());
+ // 提供更详细的错误信息,包括异常类型和消息
+ String errorMessage = "删除RAGFlow数据集失败: " + e.getClass().getSimpleName();
+ if (e.getMessage() != null) {
+ errorMessage += " - " + e.getMessage();
+ }
+ log.error(errorMessage, e);
+ throw e;
}
} else {
log.warn("datasetId或ragModelId为空,跳过RAGFlow删除");
+ apiDeleteSuccess = true; // 没有RAG数据集,视为成功
+ }
+
+ // API删除成功后再删除本地记录
+ if (apiDeleteSuccess) {
+ int deleteCount = knowledgeBaseDao.deleteById(entity.getId());
+ log.info("本地数据库删除结果: {}", deleteCount > 0 ? "成功" : "失败");
}
log.info("=== 通过datasetId删除操作结束 ===");
@@ -364,7 +399,7 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl config) {
if (config == null) {
- throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
+ throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND, "RAG配置为空,请检查配置");
}
// 从配置中提取必要的参数
@@ -373,7 +408,22 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
+ ResponseEntity response;
+ try {
+ response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
+ } catch (Exception e) {
+ String errorMessage = url + e.getMessage();
+ log.error(errorMessage, e);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
log.debug("RAGFlow API响应内容: {}", response.getBody());
if (!response.getStatusCode().is2xxSuccessful()) {
- log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
- throw new RenException(ErrorCode.RAG_API_ERROR, response.getStatusCode().toString());
+ String errorMessage = response.getStatusCode() + ", 响应内容: " + response.getBody();
+ log.error(errorMessage);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
}
// 解析响应体,提取datasetId
@@ -448,16 +506,31 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl response = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, String.class);
+ ResponseEntity response;
+ try {
+ response = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, String.class);
+ } catch (Exception e) {
+ String errorMessage = url + e.getMessage();
+ log.error(errorMessage, e);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
log.debug("RAGFlow API响应内容: {}", response.getBody());
if (!response.getStatusCode().is2xxSuccessful()) {
- log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
- throw new RenException(ErrorCode.RAG_API_ERROR);
+ String errorMessage = response.getStatusCode() + ", 响应内容: " + response.getBody();
+ log.error(errorMessage);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
+
+ // 解析响应体,验证操作是否真正成功
+ String responseBody = response.getBody();
+ if (responseBody != null) {
+ try {
+ Map responseMap = objectMapper.readValue(responseBody, Map.class);
+ Integer code = (Integer) responseMap.get("code");
+ String message = (String) responseMap.get("message");
+
+ if (code != null && code != 0) {
+ String errorMessage = "RAGFlow API调用失败,响应码: " + code + ", 消息: "
+ + (message != null ? message : "未知错误");
+ log.error(errorMessage);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
+ } catch (IOException e) {
+ // 构建详细的错误信息,包含异常类型和消息
+ String baseErrorMessage = e.getClass().getSimpleName() + " - 解析RAGFlow API响应时发生IO异常";
+ String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
+ log.error(errorMessage, e);
+ throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API响应解析失败: " + errorMessage);
+ } catch (Exception e) {
+ // 构建详细的错误信息,包含异常类型和消息
+ String baseErrorMessage = e.getClass().getSimpleName() + " - 解析RAGFlow API响应失败";
+ String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
+ log.error(errorMessage, e);
+ // 如果解析失败,但响应体不为空,尝试直接使用响应体作为错误信息
+ String finalErrorMessage = responseBody;
+ if (e.getMessage() != null) {
+ finalErrorMessage += ",解析错误: " + errorMessage;
+ }
+ throw new RenException(ErrorCode.RAG_API_ERROR, finalErrorMessage);
+ }
}
log.info("RAGFlow数据集更新成功,datasetId: {}", datasetId);
@@ -545,16 +660,58 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl response = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity,
- String.class);
+ ResponseEntity response;
+ try {
+ response = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity, String.class);
+ } catch (Exception e) {
+ String errorMessage = url + e.getMessage();
+ log.error(errorMessage, e);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
log.debug("RAGFlow API响应内容: {}", response.getBody());
if (!response.getStatusCode().is2xxSuccessful()) {
- log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
- throw new RenException(ErrorCode.RAG_API_ERROR);
+ String errorMessage = response.getStatusCode() + ", 响应内容: " + response.getBody();
+ log.error(errorMessage);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
}
+
+ // 解析响应体,验证操作是否真正成功
+ String responseBody = response.getBody();
+ if (responseBody != null) {
+ try {
+ Map responseMap = objectMapper.readValue(responseBody, Map.class);
+ Integer code = (Integer) responseMap.get("code");
+ String message = (String) responseMap.get("message");
+
+ if (code != null && code != 0) {
+ String errorMessage = "RAGFlow API调用失败,响应码: " + code + ", 消息: "
+ + (message != null ? message : "未知错误");
+ log.error(errorMessage);
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
+ }
+ } catch (IOException e) {
+ // 构建详细的错误信息,包含异常类型和消息
+ String baseErrorMessage = e.getClass().getSimpleName() + " - 解析RAGFlow API响应时发生IO异常";
+ String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
+ log.error(errorMessage, e);
+ throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API响应解析失败: " + errorMessage);
+ } catch (Exception e) {
+ // 构建详细的错误信息,包含异常类型和消息
+ String baseErrorMessage = e.getClass().getSimpleName() + " - 解析RAGFlow API响应失败";
+ String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
+ log.error(errorMessage, e);
+ // 如果解析失败,但响应体不为空,尝试直接使用响应体作为错误信息
+ String finalErrorMessage = responseBody;
+ if (e.getMessage() != null) {
+ finalErrorMessage += ",解析错误: " + errorMessage;
+ }
+ throw new RenException(ErrorCode.RAG_API_ERROR, finalErrorMessage);
+ }
+ }
+
log.info("RAGFlow数据集删除成功,datasetId: {}", datasetId);
}
@@ -570,41 +727,12 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl ragConfig) {
- if (ragConfig == null) {
- throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND, "RAG配置为空,请检查配置");
- }
-
- // 从配置中提取必要的参数
- String baseUrl = (String) ragConfig.get("base_url");
- String apiKey = (String) ragConfig.get("api_key");
-
- if (StringUtils.isBlank(baseUrl)) {
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAG配置中base_url为空,请完善配置");
- }
-
- if (StringUtils.isBlank(apiKey)) {
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAG配置中api_key为空,请完善配置");
- }
-
- if (apiKey.contains("你")) {
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAG配置中api_key包含占位符'你',请替换为实际的API密钥");
- }
-
- if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAG配置中base_url格式不正确,必须以http://或https://开头");
- }
- }
-
/**
* 检查是否存在同名知识库
*
@@ -660,7 +788,14 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
+ ResponseEntity response;
+ try {
+ response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
+ } catch (Exception e) {
+ String errorMessage = url + e.getMessage();
+ log.error(errorMessage, e);
+ return 0;
+ }
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
@@ -696,8 +831,16 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl responseMap = objectMapper.readValue(responseBody, Map.class);
Integer code = (Integer) responseMap.get("code");
@@ -1220,28 +1185,20 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
log.info("文档解析成功,datasetId: {}, documentIds: {}", datasetId, documentIds);
return true;
} else {
+ // 获取错误消息,如果存在的话
String message = (String) responseMap.get("message");
- log.error("RAGFlow API调用失败,响应码: {}, 消息: {}, 响应内容: {}", code, message, responseBody);
- throw new RenException(ErrorCode.RAG_API_ERROR, response.getStatusCode().toString());
+ String errorDetail = message != null ? message : "无详细错误信息";
+ log.error("RAGFlow API调用失败,响应码: {}, 错误信息: {}", code, errorDetail);
+ throw new RenException(ErrorCode.RAG_API_ERROR, responseBody);
}
- } catch (IOException e) {
- log.error("解析RAGFlow API响应失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow响应失败: " + e.getMessage());
- } catch (HttpClientErrorException e) {
- log.error("RAGFlow API调用失败 - HTTP错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow文档失败: " + e.getMessage());
- } catch (HttpServerErrorException e) {
- log.error("RAGFlow API调用失败 - 服务器错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow文档失败: " + e.getMessage());
- } catch (ResourceAccessException e) {
- log.error("RAGFlow API调用失败 - 网络连接错误: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow文档失败: 网络连接错误 - " + e.getMessage());
} catch (Exception e) {
log.error("解析文档失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析文档失败: " + e.getMessage());
+ String errorMessage = e.getMessage() != null ? e.getMessage() : "未知错误";
+ if (e instanceof RenException) {
+ throw (RenException) e;
+ }
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
} finally {
log.info("=== 解析文档操作结束 ===");
}
@@ -1308,14 +1265,16 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
- if (!response.getStatusCode().is2xxSuccessful()) {
- log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
- throw new RenException(ErrorCode.RAG_API_ERROR, "列出切片失败");
- }
-
String responseBody = response.getBody();
log.debug("RAGFlow API响应内容: {}", responseBody);
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ log.error("RAGFlow API调用失败,状态码: {}", response.getStatusCode());
+ String errorDetail = responseBody != null ? responseBody : "无响应内容";
+ throw new RenException(ErrorCode.RAG_API_ERROR,
+ "RAGFlow API调用失败,HTTP状态码: " + response.getStatusCode() + ", 响应内容: " + errorDetail);
+ }
+
// 解析响应
Map responseMap = objectMapper.readValue(responseBody, Map.class);
Integer code = (Integer) responseMap.get("code");
@@ -1326,28 +1285,24 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
// 解析切片数据并格式化返回
return parseChunkListResponse(responseMap);
} else {
+ // 获取错误消息,如果存在的话
String message = (String) responseMap.get("message");
- log.error("RAGFlow API调用失败,响应码: {}, 消息: {}, 响应内容: {}", code, message, responseBody);
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API调用失败: " + message);
+ String errorDetail = message != null ? message : "无详细错误信息";
+ log.error("RAGFlow API调用失败,响应码: {}, 错误信息: {}", code, errorDetail);
+ throw new RenException(ErrorCode.RAG_API_ERROR, responseBody);
}
} catch (IOException e) {
log.error("解析RAGFlow API响应失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow响应失败: " + e.getMessage());
- } catch (HttpClientErrorException e) {
- log.error("RAGFlow API调用失败 - HTTP错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "列出切片失败: " + e.getMessage());
- } catch (HttpServerErrorException e) {
- log.error("RAGFlow API调用失败 - 服务器错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "列出切片失败: " + e.getMessage());
- } catch (ResourceAccessException e) {
- log.error("RAGFlow API调用失败 - 网络连接错误: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "列出切片失败: 网络连接错误 - " + e.getMessage());
+ String errorMessage = e.getMessage() != null ? e.getMessage() : "未知错误";
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
} catch (Exception e) {
log.error("列出切片失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "列出切片失败: " + e.getMessage());
+ String errorMessage = e.getMessage() != null ? e.getMessage() : "未知错误";
+ if (e instanceof RenException) {
+ throw (RenException) e;
+ }
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
} finally {
log.info("=== 列出切片操作结束 ===");
}
@@ -1736,14 +1691,16 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
- if (!response.getStatusCode().is2xxSuccessful()) {
- log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
- throw new RenException(ErrorCode.RAG_API_ERROR);
- }
-
String responseBody = response.getBody();
log.debug("RAGFlow API响应内容: {}", responseBody);
+ if (!response.getStatusCode().is2xxSuccessful()) {
+ log.error("RAGFlow API调用失败,状态码: {}", response.getStatusCode());
+ String errorDetail = responseBody != null ? responseBody : "无响应内容";
+ throw new RenException(ErrorCode.RAG_API_ERROR,
+ "RAGFlow API调用失败,HTTP状态码: " + response.getStatusCode() + ", 响应内容: " + errorDetail);
+ }
+
// 解析响应
Map responseMap = objectMapper.readValue(responseBody, Map.class);
Integer code = (Integer) responseMap.get("code");
@@ -1755,32 +1712,24 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
log.info("召回测试成功,返回 {} 条切片", result.get("total"));
return result;
} else {
- log.error("RAGFlow API响应格式错误,data字段不是Map类型: {}", dataObj);
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API响应格式错误");
+ log.error("RAGFlow API响应格式错误,data字段不是Map类型");
+ throw new RenException(ErrorCode.RAG_API_ERROR, responseBody);
}
} else {
+ // 获取错误消息,如果存在的话
String message = (String) responseMap.get("message");
- log.error("RAGFlow API调用失败,响应码: {}, 错误信息: {}", code, message);
- throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API调用失败: " + message);
+ String errorDetail = message != null ? message : "无详细错误信息";
+ log.error("RAGFlow API调用失败,响应码: {}, 错误信息: {}", code, errorDetail);
+ throw new RenException(ErrorCode.RAG_API_ERROR, responseBody);
}
- } catch (IOException e) {
- log.error("解析RAGFlow API响应失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "解析RAGFlow响应失败: " + e.getMessage());
- } catch (HttpClientErrorException e) {
- log.error("RAGFlow API调用失败 - HTTP错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "召回测试失败: " + e.getMessage());
- } catch (HttpServerErrorException e) {
- log.error("RAGFlow API调用失败 - 服务器错误: {}, 状态码: {}, 响应内容: {}",
- e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "召回测试失败: " + e.getMessage());
- } catch (ResourceAccessException e) {
- log.error("RAGFlow API调用失败 - 网络连接错误: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "召回测试失败: 网络连接错误 - " + e.getMessage());
} catch (Exception e) {
log.error("召回测试失败: {}", e.getMessage(), e);
- throw new RenException(ErrorCode.RAG_API_ERROR, "召回测试失败: " + e.getMessage());
+ String errorMessage = e.getMessage() != null ? e.getMessage() : "未知错误";
+ if (e instanceof RenException) {
+ throw (RenException) e;
+ }
+ throw new RenException(ErrorCode.RAG_API_ERROR, errorMessage);
} finally {
log.info("=== 召回测试操作结束 ===");
}
diff --git a/main/manager-api/src/main/resources/i18n/messages.properties b/main/manager-api/src/main/resources/i18n/messages.properties
index d2c02815..ebde5cdf 100644
--- a/main/manager-api/src/main/resources/i18n/messages.properties
+++ b/main/manager-api/src/main/resources/i18n/messages.properties
@@ -173,7 +173,7 @@
10164=RAG\u914D\u7F6E\u672A\u627E\u5230
10165=RAG\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF
10166=\u9ED8\u8BA4RAG\u914D\u7F6E\u672A\u627E\u5230
-10167=RAG\u63A5\u53E3\u9519\u8BEF\uFF0C{0}
+10167=\u0052\u0041\u0047\u8c03\u7528\u5931\u8d25\uFF0C{0}
10168=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25
10169=\u60A8\u6CA1\u6709\u6743\u9650\u64CD\u4F5C\u8BE5\u8BB0\u5F55
10170=\u77E5\u8BC6\u5E93\u540D\u79F0\u91CD\u590D
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/i18n/messages_en_US.properties b/main/manager-api/src/main/resources/i18n/messages_en_US.properties
index 7610fd8a..5774b8f6 100644
--- a/main/manager-api/src/main/resources/i18n/messages_en_US.properties
+++ b/main/manager-api/src/main/resources/i18n/messages_en_US.properties
@@ -173,7 +173,11 @@
10164=RAG configuration not found
10165=RAG configuration type error
10166=Default RAG configuration not found
-10167=RAG API interface error: {0}
+10167=RAG API call failed: {0}
10168=Upload file failed
10169=No permission to operate this knowledge base
-10170=Knowledge base name already exists
\ No newline at end of file
+10170=Knowledge base name already exists
+10171=RAG configuration base_url cannot be empty
+10172=RAG configuration api_key cannot be empty
+10173=RAG configuration api_key cannot contain placeholder, please replace with actual API key
+10174=RAG configuration base_url format error, must start with http or https
diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties
index e72f34b7..573d6142 100644
--- a/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties
+++ b/main/manager-api/src/main/resources/i18n/messages_zh_CN.properties
@@ -173,7 +173,11 @@
10164=RAG\u914D\u7F6E\u672A\u627E\u5230
10165=RAG\u914D\u7F6E\u7C7B\u578B\u9519\u8BEF
10166=\u9ED8\u8BA4RAG\u914D\u7F6E\u672A\u627E\u5230
-10167=RAG\u63A5\u53E3\u9519\u8BEF\uFF0C{0}
+10167=\u0052\u0041\u0047\u8c03\u7528\u5931\u8d25\uFF0C{0}
10168=\u4E0A\u4F20\u6587\u4EF6\u5931\u8D25
10169=\u60A8\u6CA1\u6709\u6743\u9650\u64CD\u4F5C\u8BE5\u8BB0\u5F55
-10170=\u77E5\u8BC6\u5E93\u540D\u79F0\u91CD\u590D
\ No newline at end of file
+10170=\u77E5\u8BC6\u5E93\u540D\u79F0\u91CD\u590D
+10171=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u4E0D\u80FD\u4E3A\u7A7A
+10172=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A
+10173=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u66F4\u6362\u4E3A\u5728\u53D6\u7684API\u53C2\u6570
+10174=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u683C\u5F0F\u9519\u8BEF\uFF0C\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties
index b5d118e3..c57f9e2c 100644
--- a/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties
+++ b/main/manager-api/src/main/resources/i18n/messages_zh_TW.properties
@@ -173,7 +173,11 @@
10164=RAG\u914D\u7F6E\u672A\u627E\u5230
10165=RAG\u914D\u7F6E\u985E\u578B\u932F\u8AA4
10166=\u9810\u8A2DRAG\u914D\u7F6E\u672A\u627E\u5230
-10167=RAG\u63A5\u53E3\u932F\u8AA4\uFF0C{0}
+10167=\u0052\u0041\u0047\u8abf\u7528\u5931\u6557\uFF0C{0}
10168=\u4E0A\u50B3\u6587\u4EF6\u5931\u6557
10169=\u60A8\u6C92\u6709\u6B0A\u9650\u64CD\u4F5C\u8A72\u8A18\u9304
10170=\u77E5\u8B58\u5EAB\u540D\u7A31\u91CD\u8907
+10171=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u4E0D\u80FD\u4E3A\u7A7A
+10172=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A
+10173=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684api_key\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u8BF7\u66F4\u6362\u4E3A\u5728\u53D6\u7684API\u53C2\u6570
+10174=\u0052\u0041\u0047\u914D\u7F6E\u4F53\u7684base_url\u683C\u5F0F\u9519\u8BEF\uFF0C\u5FC5\u987B\u4EE5http\u6216https\u5F00\u5934
diff --git a/main/manager-web/src/components/KnowledgeBaseDialog.vue b/main/manager-web/src/components/KnowledgeBaseDialog.vue
index 99d01341..a9ce84b5 100644
--- a/main/manager-web/src/components/KnowledgeBaseDialog.vue
+++ b/main/manager-web/src/components/KnowledgeBaseDialog.vue
@@ -72,6 +72,11 @@ export default {
}
],
description: [
+ {
+ required: true,
+ message: this.$t("knowledgeBaseDialog.descriptionRequired"),
+ trigger: "blur"
+ },
{
max: 200,
message: this.$t("knowledgeBaseDialog.descriptionLength"),
diff --git a/main/manager-web/src/i18n/en.js b/main/manager-web/src/i18n/en.js
index 4cec8051..a1a40942 100644
--- a/main/manager-web/src/i18n/en.js
+++ b/main/manager-web/src/i18n/en.js
@@ -1183,7 +1183,7 @@ export default {
'knowledgeBaseDialog.descriptionLengthLimit': 'Knowledge base description cannot exceed 200 characters',
// Knowledge Base Management page new view button text
- 'knowledgeBaseManagement.view': 'View',
+ 'knowledgeBaseManagement.view': 'Manage Files',
// Knowledge File Upload page text
'knowledgeFileUpload.back': 'Back',
@@ -1215,7 +1215,7 @@ export default {
'knowledgeFileUpload.statusProcessing': 'Processing',
'knowledgeFileUpload.statusCancelled': 'Cancelled',
'knowledgeFileUpload.statusCompleted': 'Completed',
- 'knowledgeFileUpload.statusFailed': 'Parse Failed',
+ 'knowledgeFileUpload.statusFailed': 'Failed',
'knowledgeFileUpload.uploadSuccess': 'Document upload successful',
'knowledgeFileUpload.uploadFailed': 'Document upload failed',
'knowledgeFileUpload.parseSuccess': 'Document parse successful',
@@ -1259,4 +1259,5 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': 'Comprehensive Similarity',
'knowledgeFileUpload.content': 'Content:',
'knowledgeFileUpload.testQuestionRequired': 'Please enter test question',
+ 'knowledgeBaseDialog.descriptionRequired': 'Please enter knowledge base description',
}
\ No newline at end of file
diff --git a/main/manager-web/src/i18n/zh_CN.js b/main/manager-web/src/i18n/zh_CN.js
index 3986e858..9bf780bc 100644
--- a/main/manager-web/src/i18n/zh_CN.js
+++ b/main/manager-web/src/i18n/zh_CN.js
@@ -1183,7 +1183,7 @@ export default {
'knowledgeBaseDialog.descriptionLengthLimit': '知识库描述不能超过200个字符',
// 知识库管理页面新增查看按钮文本
- 'knowledgeBaseManagement.view': '查看',
+ 'knowledgeBaseManagement.view': '管理文件',
// 上传文件页面文本
'knowledgeFileUpload.back': '返回',
@@ -1215,7 +1215,7 @@ export default {
'knowledgeFileUpload.statusProcessing': '处理中',
'knowledgeFileUpload.statusCancelled': '已取消',
'knowledgeFileUpload.statusCompleted': '已完成',
- 'knowledgeFileUpload.statusFailed': '解析失败',
+ 'knowledgeFileUpload.statusFailed': '失败',
'knowledgeFileUpload.uploadSuccess': '文档上传成功',
'knowledgeFileUpload.uploadFailed': '文档上传失败',
'knowledgeFileUpload.parseSuccess': '文档解析成功',
@@ -1259,4 +1259,5 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': '综合相似度',
'knowledgeFileUpload.content': '内容:',
'knowledgeFileUpload.testQuestionRequired': '请输入测试问题',
+ 'knowledgeBaseDialog.descriptionRequired': '请输入知识库描述',
}
\ No newline at end of file
diff --git a/main/manager-web/src/i18n/zh_TW.js b/main/manager-web/src/i18n/zh_TW.js
index af8872d8..55927fa7 100644
--- a/main/manager-web/src/i18n/zh_TW.js
+++ b/main/manager-web/src/i18n/zh_TW.js
@@ -1183,7 +1183,7 @@ export default {
'knowledgeBaseDialog.descriptionLengthLimit': '知識庫描述不能超過200個字符',
// 知識庫管理頁面查看按鈕文本
- 'knowledgeBaseManagement.view': '查看',
+ 'knowledgeBaseManagement.view': '管理文件',
// 知識庫文件上傳頁面文本
'knowledgeFileUpload.back': '返回',
@@ -1215,6 +1215,7 @@ export default {
'knowledgeFileUpload.statusProcessing': '處理中',
'knowledgeFileUpload.statusCancelled': '已取消',
'knowledgeFileUpload.statusCompleted': '已完成',
+ 'knowledgeFileUpload.statusFailed': '失敗',
'knowledgeFileUpload.uploadSuccess': '文檔上傳成功',
'knowledgeFileUpload.uploadFailed': '文檔上傳失敗',
'knowledgeFileUpload.parseSuccess': '文檔解析成功',
@@ -1258,4 +1259,5 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': '綜合相似度',
'knowledgeFileUpload.content': '內容:',
'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題',
+ 'knowledgeBaseDialog.descriptionRequired': '請輸入知识库描述',
}
\ No newline at end of file
diff --git a/main/manager-web/src/views/KnowledgeBaseManagement.vue b/main/manager-web/src/views/KnowledgeBaseManagement.vue
index 13707357..a672e324 100644
--- a/main/manager-web/src/views/KnowledgeBaseManagement.vue
+++ b/main/manager-web/src/views/KnowledgeBaseManagement.vue
@@ -273,11 +273,9 @@ export default {
handleSubmit: function(form) {
console.log('handleSubmit called with form:', form);
if (form.id) {
- // 编辑 - 使用dataset_id作为路径参数,form作为请求体
console.log('Editing knowledge base:', form.datasetId);
Api.knowledgeBase.updateKnowledgeBase(form.datasetId, form, (res) => {
console.log('Update response:', res);
- // 修复:检查 res.data.code 而不是 res.code
if (res.data && res.data.code === 0) {
this.dialogVisible = false;
this.fetchKnowledgeBaseList();
@@ -285,8 +283,15 @@ export default {
} else {
this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.updateFailed'));
}
- }, () => {
- this.$message.error(this.$t('knowledgeBaseManagement.updateFailed'));
+ }, (err) => {
+ console.log('Error callback received:', err);
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ console.log('后端返回错误消息:', err.data.msg || err.msg);
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeBaseManagement.updateFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeBaseManagement.updateFailed'));
+ }
});
} else {
// 新增 - 只传递必要的字段,不传递id
@@ -303,6 +308,8 @@ export default {
this.dialogVisible = false;
this.fetchKnowledgeBaseList();
this.$message.success(this.$t('knowledgeBaseManagement.addSuccess'));
+ } else {
+ this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.addFailed'));
}
}, (err) => {
console.log('Error callback received:', err);
@@ -338,8 +345,15 @@ export default {
} else {
this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
}
- }, () => {
- this.$message.error(this.$t('knowledgeBaseManagement.deleteFailed'));
+ }, (err) => {
+ console.log('Error callback received:', err);
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ console.log('后端返回错误消息:', err.data.msg || err.msg);
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeBaseManagement.deleteFailed'));
+ }
});
}).catch(() => {
this.$message({
@@ -365,8 +379,15 @@ export default {
} else {
this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
}
- }, () => {
- this.$message.error(this.$t('knowledgeBaseManagement.deleteFailed'));
+ }, (err) => {
+ console.log('Error callback received:', err);
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ console.log('后端返回错误消息:', err.data.msg || err.msg);
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeBaseManagement.deleteFailed'));
+ }
});
}).catch(() => {
this.$message({
diff --git a/main/manager-web/src/views/KnowledgeFileUpload.vue b/main/manager-web/src/views/KnowledgeFileUpload.vue
index f9db26e9..dec0da85 100644
--- a/main/manager-web/src/views/KnowledgeFileUpload.vue
+++ b/main/manager-web/src/views/KnowledgeFileUpload.vue
@@ -401,7 +401,13 @@ export default {
},
(err) => {
this.loading = false;
- this.$message.error(this.$t('knowledgeFileUpload.getListFailed'));
+ console.log('Error callback received:', err);
+ if (err && err.data) {
+ console.log('后端返回错误消息:', err.data.msg || err.msg);
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeFileUpload.getListFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeFileUpload.getListFailed'));
+ }
console.error('获取文档列表失败:', err);
this.fileList = [];
this.total = 0;
@@ -657,7 +663,12 @@ export default {
}
},
(err) => {
- reject({ success: false, fileName: file.name, error: this.$t('knowledgeFileUpload.uploadFailed') });
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ reject({ success: false, fileName: file.name, error: err.data.msg || err.msg || this.$t('knowledgeFileUpload.uploadFailed') });
+ } else {
+ reject({ success: false, fileName: file.name, error: this.$t('knowledgeFileUpload.uploadFailed') });
+ }
console.error('上传文档失败:', err);
}
);
@@ -718,7 +729,12 @@ export default {
},
(err) => {
this.uploading = false;
- this.$message.error(this.$t('knowledgeFileUpload.uploadFailed'));
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeFileUpload.uploadFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeFileUpload.uploadFailed'));
+ }
console.error('上传文档失败:', err);
}
);
@@ -751,7 +767,12 @@ export default {
}
},
(err) => {
- this.$message.error(this.$t('knowledgeFileUpload.parseFailed'));
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeFileUpload.parseFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeFileUpload.parseFailed'));
+ }
console.error('解析文档失败:', err);
}
);
@@ -784,7 +805,12 @@ export default {
}
},
(err) => {
- this.$message.error(this.$t('knowledgeFileUpload.deleteFailed'));
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ this.$message.error(err.data.msg || err.msg || this.$t('knowledgeFileUpload.deleteFailed'));
+ } else {
+ this.$message.error(this.$t('knowledgeFileUpload.deleteFailed'));
+ }
console.error('删除文档失败:', err);
}
);
@@ -829,7 +855,12 @@ export default {
}
},
(err) => {
- reject(this.$t('knowledgeFileUpload.deleteFailed'));
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ reject(err.data.msg || err.msg || this.$t('knowledgeFileUpload.deleteFailed'));
+ } else {
+ reject(this.$t('knowledgeFileUpload.deleteFailed'));
+ }
console.error('删除文档失败:', err);
}
);
@@ -938,7 +969,12 @@ export default {
},
(err) => {
this.sliceLoading = false;
- this.$message.error('获取切片列表失败');
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ this.$message.error(err.data.msg || err.msg || '获取切片列表失败');
+ } else {
+ this.$message.error('获取切片列表失败');
+ }
console.error('获取切片列表失败:', err);
this.sliceList = [];
this.sliceTotal = 0;
@@ -1055,7 +1091,12 @@ export default {
},
(err) => {
this.retrievalTestLoading = false;
- this.$message.error('召回测试失败');
+ // 错误回调处理后端返回的错误信息
+ if (err && err.data) {
+ this.$message.error(err.data.msg || err.msg || '召回测试失败');
+ } else {
+ this.$message.error('召回测试失败');
+ }
console.error('召回测试失败:', err);
}
);
diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue
index bc561f94..a0f89503 100644
--- a/main/manager-web/src/views/roleConfig.vue
+++ b/main/manager-web/src/views/roleConfig.vue
@@ -105,7 +105,7 @@
功能名称: {{ func.name }}
- {{ func.name.charAt(0) }}
+ {{ getFunctionDisplayChar(func.name) }}
acc + char.charCodeAt(0), 0);
return this.functionColorMap[hash % this.functionColorMap.length];
},
+ getFunctionDisplayChar(name) {
+ if (!name || name.length === 0) return '';
+
+ for (let i = 0; i < name.length; i++) {
+ const char = name[i];
+ if (/[\u4e00-\u9fa5a-zA-Z0-9]/.test(char)) {
+ return char;
+ }
+ }
+
+ // 如果没有找到有效字符,返回第一个字符
+ return name.charAt(0);
+ },
showFunctionIcons(type) {
return type === 'Intent' &&
this.form.model.intentModelId !== 'Intent_nointent';
diff --git a/main/xiaozhi-server/agent-base-prompt.txt b/main/xiaozhi-server/agent-base-prompt.txt
index 9eb7731b..5b2fa792 100644
--- a/main/xiaozhi-server/agent-base-prompt.txt
+++ b/main/xiaozhi-server/agent-base-prompt.txt
@@ -63,6 +63,7 @@
- 查询**详细农历信息**(宜忌、八字、节气等)。
- 除上述例外外的**任何其他信息或操作请求**(如查新闻、订闹钟、算数学、查非本地天气等)。
- 我已经给你装了摄像头,如果用户说“拍照”,你需要调用self_camera_take_photo工具说一下你看到了什么。默认question的参数是“描述一下看到的物品”
+ - 当工具列表包含search_from_ragflow,说明可以使用知识库,你应该结合用户上下文意图并结合知识库的使用描述,推断是否要调用调用知识库。
diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py
index ea34d3e8..f10cc369 100644
--- a/main/xiaozhi-server/core/utils/util.py
+++ b/main/xiaozhi-server/core/utils/util.py
@@ -224,7 +224,10 @@ def check_ffmpeg_installed() -> bool:
error_msg.append("⚠️ 发现缺少依赖库:libiconv.so.2")
error_msg.append("解决方法:在当前 conda 环境中执行:")
error_msg.append(" conda install -c conda-forge libiconv\n")
- elif "no such file or directory" in stderr_output and "ffmpeg" in stderr_output.lower():
+ elif (
+ "no such file or directory" in stderr_output
+ and "ffmpeg" in stderr_output.lower()
+ ):
error_msg.append("⚠️ 系统未找到 ffmpeg 可执行文件。")
error_msg.append("解决方法:在当前 conda 环境中执行:")
error_msg.append(" conda install -c conda-forge ffmpeg\n")
@@ -245,7 +248,9 @@ def extract_json_from_string(input_string):
return None
-def audio_to_data_stream(audio_file_path, is_opus=True, callback: Callable[[Any], Any]=None) -> None:
+def audio_to_data_stream(
+ audio_file_path, is_opus=True, callback: Callable[[Any], Any] = None
+) -> None:
# 获取文件后缀名
file_type = os.path.splitext(audio_file_path)[1]
if file_type:
@@ -262,6 +267,7 @@ def audio_to_data_stream(audio_file_path, is_opus=True, callback: Callable[[Any]
raw_data = audio.raw_data
pcm_to_data_stream(raw_data, is_opus, callback)
+
def audio_to_data(audio_file_path: str, is_opus: bool = True) -> list[bytes]:
"""
将音频文件转换为Opus/PCM编码的帧列表
@@ -313,7 +319,10 @@ def audio_to_data(audio_file_path: str, is_opus: bool = True) -> list[bytes]:
return datas
-def audio_bytes_to_data_stream(audio_bytes, file_type, is_opus, callback: Callable[[Any], Any]) -> None:
+
+def audio_bytes_to_data_stream(
+ audio_bytes, file_type, is_opus, callback: Callable[[Any], Any]
+) -> None:
"""
直接用音频二进制数据转为opus/pcm数据,支持wav、mp3、p3
"""
@@ -357,6 +366,7 @@ def pcm_to_data_stream(raw_data, is_opus=True, callback: Callable[[Any], Any] =
frame_data = chunk if isinstance(chunk, bytes) else bytes(chunk)
callback(frame_data)
+
def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
"""
将opus帧列表解码为wav字节流
@@ -383,6 +393,7 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
wf.writeframes(pcm_bytes)
return wav_buffer.getvalue()
+
def check_vad_update(before_config, new_config):
if (
new_config.get("selected_module") is None
@@ -456,6 +467,17 @@ def filter_sensitive_info(config: dict) -> dict:
filtered[k] = _filter_dict(v)
elif isinstance(v, list):
filtered[k] = [_filter_dict(i) if isinstance(i, dict) else i for i in v]
+ elif isinstance(v, str):
+ try:
+ json_data = json.loads(v)
+ if isinstance(json_data, dict):
+ filtered[k] = json.dumps(
+ _filter_dict(json_data), ensure_ascii=False
+ )
+ else:
+ filtered[k] = v
+ except (json.JSONDecodeError, TypeError):
+ filtered[k] = v
else:
filtered[k] = v
return filtered
diff --git a/main/xiaozhi-server/docker-compose_all.yml b/main/xiaozhi-server/docker-compose_all.yml
index b3602d45..327676e1 100644
--- a/main/xiaozhi-server/docker-compose_all.yml
+++ b/main/xiaozhi-server/docker-compose_all.yml
@@ -76,7 +76,7 @@ services:
- MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
# redis模块
xiaozhi-esp32-server-redis:
- image: redis
+ image: redis:8.0
expose:
- 6379
container_name: xiaozhi-esp32-server-redis
diff --git a/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py b/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
index cdb709e3..d585a762 100644
--- a/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
+++ b/main/xiaozhi-server/plugins_func/functions/search_from_ragflow.py
@@ -84,11 +84,12 @@ def search_from_ragflow(conn, question=None):
else:
contents.append(str(content))
- # 构建适合大模型的上下文内容(每段前加编号,段间两个换行)
- context_text = "\n\n".join(
- f"{i+1}. {c.strip()}" for i, c in enumerate(contents[:5])
- )
- if not context_text:
+ if contents:
+ # 组织知识库内容为引用模式
+ context_text = f"# 关于问题【{question}】查到知识库如下\n"
+ context_text += "```\n\n\n".join(contents[:5])
+ context_text += "\n```"
+ else:
context_text = "根据知识库查询结果,没有相关信息。"
return ActionResponse(Action.REQLLM, context_text, None)