mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ac7e5be9c | ||
|
|
ebfcfe148d | ||
|
|
435860d339 | ||
|
|
b7cb97a373 | ||
|
|
e7b1daf021 | ||
|
|
bbf2febd6d | ||
|
|
586520f321 | ||
|
|
22262be7cd | ||
|
|
9021eac54e | ||
|
|
b72967bcc3 | ||
|
|
469f836a82 | ||
|
|
1e8960f37c | ||
|
|
7aad8da6f6 | ||
|
|
dd24dc0abc | ||
|
|
4f6cdc745f | ||
|
|
48087582c2 | ||
|
|
fbb0e02fdd | ||
|
|
d7ef85f6d2 | ||
|
|
2f2bd38f1e | ||
|
|
bc48a169f4 | ||
|
|
e777db6ef3 | ||
|
|
60498b24ae | ||
|
|
caae839b1d | ||
|
|
6a14d694b0 | ||
|
|
d86387971a | ||
|
|
ff1076deac | ||
|
|
5b986bf57f | ||
|
|
f1b2080d99 | ||
|
|
ed41de7b8e | ||
|
|
42a132f5cd | ||
|
|
7b2f6c2eee | ||
|
|
ee5a53d67d | ||
|
|
f691e9f0c9 | ||
|
|
7a32c208fb | ||
|
|
6984763ddc | ||
|
|
d6c3659d70 | ||
|
|
d51c2a241c | ||
|
|
a1d37b61e4 | ||
|
|
8b24151756 | ||
|
|
a9854756ec | ||
|
|
f95bed4189 | ||
|
|
f7bf29a5e4 | ||
|
|
5913dbbb7b | ||
|
|
f6bb55ae90 | ||
|
|
33e712b5fb | ||
|
|
1c26faddc4 | ||
|
|
ac887139ce | ||
|
|
bc2c68e89f | ||
|
|
cdc2363af1 | ||
|
|
ba07c623df | ||
|
|
344a73b87b | ||
|
|
9a81b6c0ee | ||
|
|
96f65e7c35 | ||
|
|
ff2cdeb483 | ||
|
|
fe8b82fcda | ||
|
|
f007e6614d | ||
|
|
0eafd37bf6 | ||
|
|
af3bc09403 | ||
|
|
90ebb8d499 | ||
|
|
f2c6cb8641 | ||
|
|
116a6973cf | ||
|
|
769407a5b0 | ||
|
|
a4583e273a | ||
|
|
645462abd0 | ||
|
|
b49c4520f9 | ||
|
|
acf45ae8ad | ||
|
|
0d1c18f5dd | ||
|
|
7b020ba55c | ||
|
|
cc081bc9eb | ||
|
|
187c2ee977 | ||
|
|
00ab407b04 | ||
|
|
bda7a90551 | ||
|
|
71b72f5b51 | ||
|
|
61fd8b21e0 | ||
|
|
9a4610ab04 | ||
|
|
fd8296262e | ||
|
|
63dfcf6659 | ||
|
|
8897b4f3c6 | ||
|
|
8d46585101 | ||
|
|
6967b2bce2 | ||
|
|
16a38bbfc4 | ||
|
|
861df73eab | ||
|
|
fa9809bee1 | ||
|
|
2a93745d01 | ||
|
|
85fab64588 | ||
|
|
b22f430e98 | ||
|
|
c3da3e937e | ||
|
|
4ea3bea85c | ||
|
|
520e24a54e | ||
|
|
e82cc53bfb | ||
|
|
3bddc1c722 | ||
|
|
f68fc5c90d | ||
|
|
ea8001a9c8 | ||
|
|
ca10d7290d | ||
|
|
7b3db72285 | ||
|
|
3639871dd2 | ||
|
|
5fb7fbe08f | ||
|
|
73f8a0977c | ||
|
|
87b99e0353 | ||
|
|
795dcec698 | ||
|
|
4f61d21d17 | ||
|
|
8a84428724 | ||
|
|
46f1dee20e | ||
|
|
75e24af858 | ||
|
|
fae6db2198 | ||
|
|
32aea32e2c | ||
|
|
63d9dae18b | ||
|
|
041c254ea4 | ||
|
|
378d68aba9 | ||
|
|
4076136fe4 | ||
|
|
1e6e250dac | ||
|
|
42b495d556 | ||
|
|
c88d709d56 | ||
|
|
fa523dc615 | ||
|
|
8718a07c7e | ||
|
|
39124d4930 | ||
|
|
8dbc0b9689 | ||
|
|
9fd3501605 | ||
|
|
4f72a516d3 | ||
|
|
e4cfabeb7e | ||
|
|
85a83727fd | ||
|
|
a31e16c1d9 | ||
|
|
3ab5dc49ac | ||
|
|
005dc55db3 | ||
|
|
ba1cf14ed4 | ||
|
|
6da32a60c0 | ||
|
|
ea9778f422 | ||
|
|
f0b176768d | ||
|
|
5aa671d6ff | ||
|
|
d02d094219 | ||
|
|
f5b4bae927 | ||
|
|
19fdf5d917 | ||
|
|
9682f615cf | ||
|
|
8162adeca1 | ||
|
|
ba2749430f | ||
|
|
e3088f4410 | ||
|
|
614b662e3d | ||
|
|
f5b9995f89 | ||
|
|
0ddd9ba8ff | ||
|
|
9c8d614d78 | ||
|
|
0075550229 | ||
|
|
4a547be882 | ||
|
|
d721270bed | ||
|
|
fe2e178b19 | ||
|
|
21337e5046 | ||
|
|
1c8b271e20 | ||
|
|
3d1160b6bb | ||
|
|
086c4a55e6 | ||
|
|
889f2f9a75 | ||
|
|
07b686be14 | ||
|
|
4d6b453485 | ||
|
|
9909045f9d | ||
|
|
b0dc4d88db | ||
|
|
bbf3858735 | ||
|
|
73573e2f12 | ||
|
|
99e136786e | ||
|
|
205be0e4ec | ||
|
|
2e64f45a31 | ||
|
|
818b4f2f6d | ||
|
|
5d72f9f79d | ||
|
|
22d0dcdf58 | ||
|
|
122d1def1d | ||
|
|
0ee349df2d | ||
|
|
4dde66339b | ||
|
|
0064f975c2 | ||
|
|
43234f4798 | ||
|
|
48034fc000 | ||
|
|
3fc33e9095 | ||
|
|
da71dce860 | ||
|
|
b7e4408a0f | ||
|
|
4f4f8ca54e | ||
|
|
99c12a0fed | ||
|
|
dcd0ef3f40 | ||
|
|
2f53b921fe | ||
|
|
0f16a8d31f | ||
|
|
3051dc8327 | ||
|
|
c313997a61 | ||
|
|
07c0c764d3 | ||
|
|
e339a501aa | ||
|
|
f2c541b4ab | ||
|
|
206af1c074 | ||
|
|
344729c9a6 | ||
|
|
3b1c2d3b1b | ||
|
|
3660af9619 | ||
|
|
4905ba836c | ||
|
|
4b573fb4e2 | ||
|
|
6ae0af278b | ||
|
|
15650e1a6c | ||
|
|
ce00c862fe | ||
|
|
a50229431b | ||
|
|
275102f5b7 | ||
|
|
ff6286ffcc | ||
|
|
974fe6c3b7 | ||
|
|
b78292b270 | ||
|
|
3559a7018c | ||
|
|
9368885355 | ||
|
|
3222d6175d | ||
|
|
28f9f23449 | ||
|
|
5e7d192ebc | ||
|
|
d6a32416d7 | ||
|
|
6a2c01763e | ||
|
|
e1dd272708 | ||
|
|
3e7bc0606a | ||
|
|
9d9ca1475d | ||
|
|
43ddc0135c | ||
|
|
98f907a97b | ||
|
|
b2ddf060c9 | ||
|
|
5e612276db | ||
|
|
cbb293e7fa | ||
|
|
fdb050f6ed | ||
|
|
f83ddf79fa | ||
|
|
95b340a7a3 | ||
|
|
d46ac89e58 | ||
|
|
80b0912f84 | ||
|
|
e0b4431064 | ||
|
|
e3f16f91cf | ||
|
|
5f39e5c308 | ||
|
|
83304c9ff4 | ||
|
|
863cfb575c | ||
|
|
2944040648 | ||
|
|
5f19b9b0d0 | ||
|
|
2948ed091c | ||
|
|
bfa638e17f | ||
|
|
97ee397b54 | ||
|
|
f7fbe7efbe | ||
|
|
ce6b0bd099 | ||
|
|
8643960104 | ||
|
|
b4549ebc59 | ||
|
|
a602cddd05 | ||
|
|
c4278864d1 | ||
|
|
7094548ecf | ||
|
|
363fffada8 | ||
|
|
c2c3eb7106 | ||
|
|
8e19a64f15 | ||
|
|
9042b7e6d7 | ||
|
|
ad1855cfb5 | ||
|
|
efa0d1095d | ||
|
|
a8c13d740b | ||
|
|
7475e9829f | ||
|
|
b7dde1dae2 | ||
|
|
778c4560fd | ||
|
|
fa31096cd8 | ||
|
|
fd1dd39463 | ||
|
|
ecd3e12a41 | ||
|
|
0a8121e849 | ||
|
|
e93f9d7111 | ||
|
|
3bdccfea62 | ||
|
|
fdf98985a1 | ||
|
|
6c3609e79e | ||
|
|
5fa50339b4 | ||
|
|
f0c95bc987 | ||
|
|
a9aa709502 | ||
|
|
8edc23b888 | ||
|
|
c4fc510a27 | ||
|
|
134000996b | ||
|
|
111909c26d | ||
|
|
f66966d72f | ||
|
|
1f6a89ada5 | ||
|
|
31a1180b79 | ||
|
|
77b4a416a9 | ||
|
|
06a2e6fbfa | ||
|
|
c8a28e98d6 | ||
|
|
564adbd851 | ||
|
|
ad93f43145 | ||
|
|
2bf1123647 | ||
|
|
a6ff6426e9 | ||
|
|
da7a6933f5 | ||
|
|
54cac0d2fd | ||
|
|
de7aedab96 | ||
|
|
7fe718c887 | ||
|
|
36eaefb8b0 | ||
|
|
3a61ec84b3 | ||
|
|
dadf05ab4e | ||
|
|
c1bf92d243 | ||
|
|
c1959e0795 | ||
|
|
40a6b7434b | ||
|
|
24519a2732 | ||
|
|
c190abe4b1 | ||
|
|
07d5b3cd2a | ||
|
|
81e923b4f7 | ||
|
|
bcb51729a5 | ||
|
|
a731c1d01f | ||
|
|
0bb5a37ace | ||
|
|
a711827996 | ||
|
|
8f854e6f74 | ||
|
|
f1b5afbf5c | ||
|
|
84f6930389 | ||
|
|
f415dd4482 | ||
|
|
927f65e106 | ||
|
|
4b6b3082c7 | ||
|
|
4c550245db | ||
|
|
341ed22370 | ||
|
|
6851ccacf3 | ||
|
|
815af687ea | ||
|
|
e9373b59c8 | ||
|
|
3f3f3fcd57 | ||
|
|
d003e66310 | ||
|
|
e6108ccbbd | ||
|
|
a01f01198c | ||
|
|
cc96eaeb8f | ||
|
|
e25ca3d1d3 | ||
|
|
9c111b2d5e | ||
|
|
436a01fb6e | ||
|
|
9b6620dd4a | ||
|
|
85919e800e | ||
|
|
6cdbe45313 |
@@ -37,7 +37,7 @@ jobs:
|
|||||||
file: Dockerfile-server-base
|
file: Dockerfile-server-base
|
||||||
push: true
|
push: true
|
||||||
tags: ghcr.io/${{ github.repository }}:server-base
|
tags: ghcr.io/${{ github.repository }}:server-base
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha,scope=server-base
|
cache-from: type=gha,scope=server-base
|
||||||
cache-to: type=gha,mode=max,scope=server-base
|
cache-to: type=gha,mode=max,scope=server-base
|
||||||
build-args: |
|
build-args: |
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1},ghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1},ghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }}
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
build-args: |
|
build-args: |
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1},ghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1},ghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }}
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
build-args: |
|
build-args: |
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ __pycache__/
|
|||||||
.idea/
|
.idea/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
.vscode
|
||||||
|
.claude
|
||||||
|
AGENTS.md
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|||||||
+9
-8
@@ -1,5 +1,5 @@
|
|||||||
# 第一阶段:构建Vue前端
|
# 第一阶段:构建Vue前端
|
||||||
FROM node:18 as web-builder
|
FROM node:18 AS web-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY main/manager-web/package*.json ./
|
COPY main/manager-web/package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
@@ -7,7 +7,7 @@ COPY main/manager-web .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# 第二阶段:构建Java后端
|
# 第二阶段:构建Java后端
|
||||||
FROM maven:3.9.4-eclipse-temurin-21 as api-builder
|
FROM maven:3.9.4-eclipse-temurin-21 AS api-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY main/manager-api/pom.xml .
|
COPY main/manager-api/pom.xml .
|
||||||
COPY main/manager-api/src ./src
|
COPY main/manager-api/src ./src
|
||||||
@@ -18,18 +18,19 @@ FROM bellsoft/liberica-runtime-container:jre-21-glibc
|
|||||||
|
|
||||||
# 安装Nginx和字体库
|
# 安装Nginx和字体库
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
apk add --no-cache --no-scripts \
|
||||||
nginx \
|
nginx \
|
||||||
bash \
|
bash \
|
||||||
fontconfig \
|
fontconfig \
|
||||||
ttf-dejavu \
|
ttf-dejavu \
|
||||||
msttcorefonts-installer \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& ACCEPT_EULA=Y apk add --no-cache msttcorefonts-installer \
|
&& mkdir -p /run/nginx /var/log/nginx /var/tmp/nginx /etc/nginx/conf.d
|
||||||
&& fc-cache -f -v \
|
|
||||||
&& rm -rf /var/cache/apk/*
|
# 复制项目自带的中文字体
|
||||||
|
COPY main/manager-web/public/generator/static/fonts/*.ttf /usr/share/fonts/
|
||||||
|
|
||||||
# 更新字体缓存
|
# 更新字体缓存
|
||||||
RUN (printf 'YES\n' | update-ms-fonts || true) && fc-cache -f -v
|
RUN fc-cache -f -v
|
||||||
|
|
||||||
# 配置Nginx
|
# 配置Nginx
|
||||||
COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
|
COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
||||||
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
||||||
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
||||||
|
<a href="./README_pt_BR.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-DFE0E5"></a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
</a>
|
</a>
|
||||||
@@ -242,7 +243,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
|||||||
| 智能对话 | 支持多种LLM(大语言模型),实现智能对话 |
|
| 智能对话 | 支持多种LLM(大语言模型),实现智能对话 |
|
||||||
| 视觉感知 | 支持多种VLLM(视觉大模型),实现多模态交互 |
|
| 视觉感知 | 支持多种VLLM(视觉大模型),实现多模态交互 |
|
||||||
| 意图识别 | 支持外挂的大模型意图识别、大模型自主函数调用,提供插件化意图处理机制 |
|
| 意图识别 | 支持外挂的大模型意图识别、大模型自主函数调用,提供插件化意图处理机制 |
|
||||||
| 记忆系统 | 支持本地短期记忆、mem0ai接口记忆,具备记忆总结功能 |
|
| 记忆系统 | 支持本地短期记忆、mem0ai接口记忆、PowerMem智能记忆,具备记忆总结功能 |
|
||||||
| 知识库 | 支持RAGFlow知识库,让大模型判断需要调度知识库后再回答 |
|
| 知识库 | 支持RAGFlow知识库,让大模型判断需要调度知识库后再回答 |
|
||||||
| 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 |
|
| 工具调用 | 支持客户端IOT协议、客户MCP协议、服务端MCP协议、MCP接入点协议、自定义工具函数 |
|
||||||
| 指令下发 | 依托MQTT协议,支持从智控台将MCP指令下发到ESP32设备 |
|
| 指令下发 | 依托MQTT协议,支持从智控台将MCP指令下发到ESP32设备 |
|
||||||
@@ -330,6 +331,7 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
|||||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
||||||
|:------:|:---------------:|:----:|:---------:|:--:|
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
| Memory | mem0ai | 接口调用 | 1000次/月额度 | |
|
| Memory | mem0ai | 接口调用 | 1000次/月额度 | |
|
||||||
|
| Memory | [powermem](./docs/powermem-integration.md) | 本地总结 | 取决于LLM和DB | OceanBase开源,支持智能检索 |
|
||||||
| Memory | mem_local_short | 本地总结 | 免费 | |
|
| Memory | mem_local_short | 本地总结 | 免费 | |
|
||||||
| Memory | nomem | 无记忆模式 | 免费 | |
|
| Memory | nomem | 无记忆模式 | 免费 | |
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -21,6 +21,7 @@ Unterstützt MQTT+UDP-Protokoll, Websocket-Protokoll, MCP-Endpunkte und Stimmabd
|
|||||||
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
||||||
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
||||||
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DBEDFA"></a>
|
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DBEDFA"></a>
|
||||||
|
<a href="./README_pt_BR.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-DFE0E5"></a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
</a>
|
</a>
|
||||||
@@ -240,7 +241,7 @@ Dieses Projekt bietet die folgenden Testwerkzeuge, um Ihnen bei der Überprüfun
|
|||||||
| Intelligenter Dialog | Unterstützt mehrere LLM (große Sprachmodelle), implementiert intelligenten Dialog |
|
| Intelligenter Dialog | Unterstützt mehrere LLM (große Sprachmodelle), implementiert intelligenten Dialog |
|
||||||
| Visuelle Wahrnehmung | Unterstützt mehrere VLLM (Vision Large Models), implementiert multimodale Interaktion |
|
| Visuelle Wahrnehmung | Unterstützt mehrere VLLM (Vision Large Models), implementiert multimodale Interaktion |
|
||||||
| Absichtserkennung | Unterstützt LLM-Absichtserkennung, Function Call-Funktionsaufruf, bietet plugin-basierten Absichtsverarbeitungsmechanismus |
|
| Absichtserkennung | Unterstützt LLM-Absichtserkennung, Function Call-Funktionsaufruf, bietet plugin-basierten Absichtsverarbeitungsmechanismus |
|
||||||
| Gedächtnissystem | Unterstützt lokales Kurzzeitgedächtnis, mem0ai-Schnittstellengedächtnis, mit Gedächtniszusammenfassungsfunktion |
|
| Gedächtnissystem | Unterstützt lokales Kurzzeitgedächtnis, mem0ai-Schnittstellengedächtnis, PowerMem intelligentes Gedächtnis, mit Gedächtniszusammenfassungsfunktion |
|
||||||
| Wissensdatenbank | Unterstützt RAGFlow-Wissensdatenbank, ermöglicht großem Modell die Bewertung, ob Wissensdatenbank benötigt wird, bevor geantwortet wird |
|
| Wissensdatenbank | Unterstützt RAGFlow-Wissensdatenbank, ermöglicht großem Modell die Bewertung, ob Wissensdatenbank benötigt wird, bevor geantwortet wird |
|
||||||
| Werkzeugaufruf | Unterstützt Client-IOT-Protokoll, Client-MCP-Protokoll, Server-MCP-Protokoll, MCP-Endpunktprotokoll, benutzerdefinierte Werkzeugfunktionen |
|
| Werkzeugaufruf | Unterstützt Client-IOT-Protokoll, Client-MCP-Protokoll, Server-MCP-Protokoll, MCP-Endpunktprotokoll, benutzerdefinierte Werkzeugfunktionen |
|
||||||
| Befehlsübermittlung | Basierend auf MQTT-Protokoll, unterstützt die Übermittlung von MCP-Befehlen von der intelligenten Steuerkonsole an ESP32-Geräte |
|
| Befehlsübermittlung | Basierend auf MQTT-Protokoll, unterstützt die Übermittlung von MCP-Befehlen von der intelligenten Steuerkonsole an ESP32-Geräte |
|
||||||
@@ -258,7 +259,7 @@ Wenn Sie ein Softwareentwickler sind, finden Sie hier einen [Offenen Brief an En
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Produktökosystem 👬
|
## Produktökosystem 👬
|
||||||
Xiaozhi ist ein Ökosystem. Wenn Sie dieses Produkt verwenden, können Sie sich auch andere [hervorragende Projekte](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) in diesem Ökosystem ansehen
|
Xiaozhi ist ein Ökosystem. Wenn Sie dieses Produkt verwenden, können Sie sich auch andere [hervorragende Projekte](https://github.com/78/xiaozhi-esp32/blob/main/README_zh.md#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) in diesem Ökosystem ansehen
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -328,6 +329,7 @@ Tatsächlich kann jedes VLLM, das OpenAI-Schnittstellenaufrufe unterstützt, int
|
|||||||
| Typ | Plattformname | Verwendungsmethode | Preismodell | Hinweise |
|
| Typ | Plattformname | Verwendungsmethode | Preismodell | Hinweise |
|
||||||
|:------:|:---------------:|:----:|:---------:|:--:|
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
| Memory | mem0ai | Schnittstellenaufrufe | 1000 Mal/Monat Kontingent | |
|
| Memory | mem0ai | Schnittstellenaufrufe | 1000 Mal/Monat Kontingent | |
|
||||||
|
| Memory | [powermem](./docs/powermem-integration.md) | Lokale Zusammenfassung | Abhängig von LLM und DB | OceanBase Open Source, unterstützt intelligente Abfrage |
|
||||||
| Memory | mem_local_short | Lokale Zusammenfassung | Kostenlos | |
|
| Memory | mem_local_short | Lokale Zusammenfassung | Kostenlos | |
|
||||||
| Memory | nomem | Kein Gedächtnismodus | Kostenlos | |
|
| Memory | nomem | Kein Gedächtnismodus | Kostenlos | |
|
||||||
|
|
||||||
|
|||||||
+16
-14
@@ -21,6 +21,7 @@ Support for MQTT+UDP protocol, Websocket protocol, MCP access point, voiceprint
|
|||||||
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DBEDFA"></a>
|
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DBEDFA"></a>
|
||||||
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
||||||
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
||||||
|
<a href="./README_pt_BR.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-DFE0E5"></a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
</a>
|
</a>
|
||||||
@@ -184,6 +185,7 @@ This project provides two deployment methods. Please choose based on your specif
|
|||||||
| **Simplified Installation** | Intelligent dialogue, single agent management | Low-configuration environments, data stored in config files, no database required | [①Docker Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Source Code Deployment](./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)| 2 cores 4GB if using `FunASR`, 2 cores 2GB if all APIs | - |
|
| **Simplified Installation** | Intelligent dialogue, single agent management | Low-configuration environments, data stored in config files, no database required | [①Docker Version](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Source Code Deployment](./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)| 2 cores 4GB if using `FunASR`, 2 cores 2GB if all APIs | - |
|
||||||
| **Full Module Installation** | Intelligent dialogue, multi-user management, multi-agent management, intelligent console interface operation | Complete functionality experience, data stored in database |[①Docker Version](./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) / [②Source Code Deployment](./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) / [③Source Code Deployment Auto-Update Tutorial](./docs/dev-ops-integration.md) | 4 cores 8GB if using `FunASR`, 2 cores 4GB if all APIs| [Local Source Code Startup Video Tutorial](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
|
| **Full Module Installation** | Intelligent dialogue, multi-user management, multi-agent management, intelligent console interface operation | Complete functionality experience, data stored in database |[①Docker Version](./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) / [②Source Code Deployment](./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) / [③Source Code Deployment Auto-Update Tutorial](./docs/dev-ops-integration.md) | 4 cores 8GB if using `FunASR`, 2 cores 4GB if all APIs| [Local Source Code Startup Video Tutorial](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
|
||||||
|
|
||||||
|
For frequently asked questions and related tutorials, please refer to [this link](./docs/FAQ.md)
|
||||||
|
|
||||||
> 💡 Note: Below is a test platform deployed with the latest code. You can burn and test if needed. Concurrent users: 6, data will be cleared daily.
|
> 💡 Note: Below is a test platform deployed with the latest code. You can burn and test if needed. Concurrent users: 6, data will be cleared daily.
|
||||||
|
|
||||||
@@ -215,14 +217,15 @@ Websocket Interface Address: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
|||||||
| Intent(Intent Recognition) | function_call(Function calling) | function_call(Function calling) |
|
| Intent(Intent Recognition) | function_call(Function calling) | function_call(Function calling) |
|
||||||
| Memory(Memory function) | mem_local_short(Local short-term memory) | mem_local_short(Local short-term memory) |
|
| Memory(Memory function) | mem_local_short(Local short-term memory) | mem_local_short(Local short-term memory) |
|
||||||
|
|
||||||
|
If you are concerned about the latency of each component, please refer to the [Xiaozhi Component Performance Test Report](https://github.com/xinnan-tech/xiaozhi-performance-research), and test in your own environment following the test methods in the report.
|
||||||
|
|
||||||
#### 🔧 Testing Tools
|
#### 🔧 Testing Tools
|
||||||
This project provides the following testing tools to help you verify the system and choose suitable models:
|
This project provides the following testing tools to help you verify the system and choose suitable models:
|
||||||
|
|
||||||
| Tool Name | Location | Usage Method | Function Description |
|
| Tool Name | Location | Usage Method | Function Description |
|
||||||
|:---:|:---|:---:|:---:|
|
|:---:|:---|:---:|:---:|
|
||||||
| Audio Interaction Test Tool | main》xiaozhi-server》test》test_page.html | Open directly with Google Chrome | Tests audio playback and reception functions, verifies if Python-side audio processing is normal |
|
| Audio Interaction Test Tool | main》xiaozhi-server》test》test_page.html | Open directly with Google Chrome | Tests audio playback and reception functions, verifies if Python-side audio processing is normal |
|
||||||
| Model Response Test Tool 1 | main》xiaozhi-server》performance_tester.py | Execute `python performance_tester.py` | Tests response speed of three core modules: ASR(speech recognition), LLM(large model), TTS(speech synthesis) |
|
| Model Response Test Tool | main》xiaozhi-server》performance_tester.py | Execute `python performance_tester.py` | Tests response speed of three core modules: ASR(speech recognition), LLM(large model), VLLM(vision model), TTS(speech synthesis) |
|
||||||
| Model Response Test Tool 2 | main》xiaozhi-server》performance_tester_vllm.py | Execute `python performance_tester_vllm.py` | Tests VLLM(vision model) response speed |
|
|
||||||
|
|
||||||
> 💡 Note: When testing model speed, only models with configured keys will be tested.
|
> 💡 Note: When testing model speed, only models with configured keys will be tested.
|
||||||
|
|
||||||
@@ -238,10 +241,10 @@ This project provides the following testing tools to help you verify the system
|
|||||||
| Intelligent Dialogue | Supports multiple LLM(large language models), implements intelligent dialogue |
|
| Intelligent Dialogue | Supports multiple LLM(large language models), implements intelligent dialogue |
|
||||||
| Visual Perception | Supports multiple VLLM(vision large models), implements multimodal interaction |
|
| 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 |
|
| 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 |
|
| Memory System | Supports local short-term memory, mem0ai interface memory, PowerMem intelligent 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 |
|
| 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 |
|
| Tool Calling | Supports client IOT protocol, client MCP protocol, server MCP protocol, MCP endpoint protocol, custom tool functions |
|
||||||
|
| Command Delivery | Supports MCP command delivery to ESP32 devices via MQTT protocol from Smart Console |
|
||||||
| Management Backend | Provides Web management interface, supports user management, system configuration and device management; Supports Simplified Chinese, Traditional Chinese and English display |
|
| Management Backend | Provides Web management interface, supports user management, system configuration and device management; Supports Simplified Chinese, Traditional Chinese and English display |
|
||||||
| Testing Tools | Provides performance testing tools, vision model testing tools, and audio interaction testing tools |
|
| Testing Tools | Provides performance testing tools, vision model testing tools, and audio interaction testing tools |
|
||||||
| Deployment Support | Supports Docker deployment and local deployment, provides complete configuration file management |
|
| Deployment Support | Supports Docker deployment and local deployment, provides complete configuration file management |
|
||||||
@@ -249,20 +252,14 @@ This project provides the following testing tools to help you verify the system
|
|||||||
|
|
||||||
### Under Development 🚧
|
### Under Development 🚧
|
||||||
|
|
||||||
To learn about specific development plan progress, [click here](https://github.com/users/xinnan-tech/projects/3)
|
To learn about specific development plan progress, [click here](https://github.com/users/xinnan-tech/projects/3). For frequently asked questions and related tutorials, please refer to [this link](./docs/FAQ.md)
|
||||||
|
|
||||||
If you are a software developer, here is an [Open Letter to Developers](docs/contributor_open_letter.md). Welcome to join!
|
If you are a software developer, here is an [Open Letter to Developers](docs/contributor_open_letter.md). Welcome to join!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Product Ecosystem 👬
|
## Product Ecosystem 👬
|
||||||
Xiaozhi is an ecosystem. When using this product, you can also check out other [excellent projects](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) in this ecosystem
|
Xiaozhi is an ecosystem. When using this product, you can also check out other [excellent projects](https://github.com/78/xiaozhi-esp32/blob/main/README_zh.md#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) in this ecosystem
|
||||||
|
|
||||||
| Project Name | Project Address | Project Description |
|
|
||||||
|:---------------------|:--------|:--------|
|
|
||||||
| Xiaozhi Android Client | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | An Android and iOS voice dialogue application based on xiaozhi-server, supporting real-time voice interaction and text dialogue.<br/>Currently a Flutter version, connecting iOS and Android platforms. |
|
|
||||||
| Xiaozhi Desktop Client | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | This project provides a Python-based AI client for beginners, allowing users to experience Xiaozhi AI functionality through code even without physical hardware conditions. |
|
|
||||||
| Xiaozhi Java Server | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | Xiaozhi open-source backend service Java version is a Java-based open-source project.<br/>It includes frontend and backend services, aiming to provide users with a complete backend service solution. |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -276,8 +273,10 @@ Xiaozhi is an ecosystem. When using this product, you can also check out other [
|
|||||||
| Dify interface calls | Dify | - |
|
| Dify interface calls | Dify | - |
|
||||||
| FastGPT interface calls | FastGPT | - |
|
| FastGPT interface calls | FastGPT | - |
|
||||||
| Coze interface calls | Coze | - |
|
| Coze interface calls | Coze | - |
|
||||||
|
| Xinference interface calls | Xinference | - |
|
||||||
|
| HomeAssistant interface calls | HomeAssistant | - |
|
||||||
|
|
||||||
In fact, any LLM that supports OpenAI interface calls can be integrated and used, including Xinference and HomeAssistant interfaces.
|
In fact, any LLM that supports OpenAI interface calls can be integrated and used.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -296,7 +295,7 @@ In fact, any VLLM that supports OpenAI interface calls can be integrated and use
|
|||||||
| Usage Method | Supported Platforms | Free Platforms |
|
| Usage Method | Supported Platforms | Free Platforms |
|
||||||
|:---:|:---:|:---:|
|
|:---:|:---:|:---:|
|
||||||
| Interface calls | EdgeTTS, iFLYTEK, Volcano Engine, Tencent Cloud, Alibaba Cloud and Bailian, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS, Lingxi Streaming TTS, MinimaxTTS | Lingxi Streaming TTS, EdgeTTS, CosyVoiceSiliconflow(partial) |
|
| Interface calls | EdgeTTS, iFLYTEK, Volcano Engine, Tencent Cloud, Alibaba Cloud and Bailian, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS, Lingxi Streaming TTS, MinimaxTTS | Lingxi Streaming TTS, EdgeTTS, CosyVoiceSiliconflow(partial) |
|
||||||
| Local services | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS |
|
| Local services | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, Index-TTS, PaddleSpeech | Index-TTS, PaddleSpeech, FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -330,7 +329,9 @@ In fact, any VLLM that supports OpenAI interface calls can be integrated and use
|
|||||||
| Type | Platform Name | Usage Method | Pricing Model | Notes |
|
| Type | Platform Name | Usage Method | Pricing Model | Notes |
|
||||||
|:------:|:---------------:|:----:|:---------:|:--:|
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
| Memory | mem0ai | Interface calls | 1000 times/month quota | |
|
| Memory | mem0ai | Interface calls | 1000 times/month quota | |
|
||||||
|
| Memory | [powermem](./docs/powermem-integration.md) | Local summarization | Depends on LLM and DB | OceanBase open source, supports intelligent retrieval |
|
||||||
| Memory | mem_local_short | Local summarization | Free | |
|
| Memory | mem_local_short | Local summarization | Free | |
|
||||||
|
| Memory | nomem | No memory mode | Free | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -340,6 +341,7 @@ In fact, any VLLM that supports OpenAI interface calls can be integrated and use
|
|||||||
|:------:|:-------------:|:----:|:-------:|:---------------------:|
|
|:------:|:-------------:|:----:|:-------:|:---------------------:|
|
||||||
| Intent | intent_llm | Interface calls | Based on LLM pricing | Recognizes intent through large models, strong generalization |
|
| Intent | intent_llm | Interface calls | Based on LLM pricing | Recognizes intent through large models, strong generalization |
|
||||||
| Intent | function_call | Interface calls | Based on LLM pricing | Completes intent through large model function calling, fast speed, good effect |
|
| Intent | function_call | Interface calls | Based on LLM pricing | Completes intent through large model function calling, fast speed, good effect |
|
||||||
|
| Intent | nointent | No intent mode | Free | Does not perform intent recognition, directly returns dialogue result |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+376
@@ -0,0 +1,376 @@
|
|||||||
|
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
||||||
|
|
||||||
|
<h1 align="center">Serviço Backend Xiaozhi xiaozhi-esp32-server</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Este projeto é baseado na teoria e tecnologia de inteligência simbiótica humano-máquina para desenvolver sistemas inteligentes de hardware e software para terminais<br/>fornecendo serviços de backend para o projeto de hardware inteligente de código aberto
|
||||||
|
<a href="https://github.com/78/xiaozhi-esp32">xiaozhi-esp32</a><br/>
|
||||||
|
Implementado usando Python, Java e Vue de acordo com o <a href="https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh">Protocolo de Comunicação Xiaozhi</a><br/>
|
||||||
|
Suporte ao protocolo MQTT+UDP, protocolo WebSocket, ponto de acesso MCP, reconhecimento de impressão vocal e base de conhecimento
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./docs/FAQ.md">Perguntas Frequentes</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">Reportar Problemas</a>
|
||||||
|
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">Documentação de Implantação</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">Notas de Lançamento</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./README.md"><img alt="简体中文版自述文件" src="https://img.shields.io/badge/简体中文-DFE0E5"></a>
|
||||||
|
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
||||||
|
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DFE0E5"></a>
|
||||||
|
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
||||||
|
<a href="./README_pt_BR.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-DBEDFA"></a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/LICENSE">
|
||||||
|
<img alt="GitHub pull requests" src="https://img.shields.io/badge/license-MIT-white?labelColor=black" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server">
|
||||||
|
<img alt="stars" src="https://img.shields.io/github/stars/xinnan-tech/xiaozhi-esp32-server?color=ffcb47&labelColor=black" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Liderado pela Equipe do Professor Siyuan Liu (Universidade de Tecnologia do Sul da China)
|
||||||
|
</br>
|
||||||
|
刘思源教授团队主导研发(华南理工大学)
|
||||||
|
</br>
|
||||||
|
<img src="./docs/images/hnlg.jpg" alt="Universidade de Tecnologia do Sul da China (华南理工大学)" width="50%">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Público-Alvo 👥
|
||||||
|
|
||||||
|
Este projeto requer dispositivos de hardware ESP32 para funcionar. Se você adquiriu hardware relacionado ao ESP32, conectou-se com sucesso ao serviço backend implantado pelo Brother Xia e deseja construir seu próprio serviço backend `xiaozhi-esp32` de forma independente, então este projeto é perfeito para você.
|
||||||
|
|
||||||
|
Quer ver os efeitos de uso? Clique nos vídeos abaixo 🎥
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Experiência de velocidade de resposta" src="docs/images/demo9.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1vchQzaEse" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Segredo da otimização de velocidade" src="docs/images/demo6.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1C1tCzUEZh" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Cenário médico complexo" src="docs/images/demo1.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1zUW5zJEkq" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Envio de comandos MQTT" src="docs/images/demo4.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Exu3zqEDe" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Reconhecimento de impressão vocal" src="docs/images/demo14.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Controle de interruptores de eletrodomésticos" src="docs/images/demo5.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1ZQKUzYExM" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Ponto de acesso MCP" src="docs/images/demo13.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1TJ7WzzEo6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Tarefas com múltiplos comandos" src="docs/images/demo11.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Reproduzir música" src="docs/images/demo7.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Plugin de clima" src="docs/images/demo8.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV12J7WzBEaH" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Interrupção em tempo real" src="docs/images/demo10.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Co76z7EvK" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Fotografar e identificar objetos" src="docs/images/demo12.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Timbre de voz personalizado" src="docs/images/demo2.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Comunicação em cantonês" src="docs/images/demo3.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Transmissão de notícias" src="docs/images/demo0.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Avisos ⚠️
|
||||||
|
|
||||||
|
1. Este projeto é um software de código aberto. Este software não possui parceria comercial com nenhum provedor de serviços de API de terceiros (incluindo, mas não se limitando a reconhecimento de fala, modelos de linguagem, síntese de voz e outras plataformas) com os quais se conecta, e não fornece nenhuma forma de garantia quanto à qualidade de serviço ou segurança financeira desses provedores. Recomenda-se que os usuários priorizem provedores de serviço com licenças comerciais relevantes e leiam cuidadosamente seus termos de serviço e políticas de privacidade. Este software não armazena nenhuma chave de conta, não participa de fluxos de fundos e não assume o risco de perda de fundos recarregados.
|
||||||
|
|
||||||
|
2. A funcionalidade deste projeto não está completa e não passou por avaliação de segurança de rede. Por favor, não o utilize em ambientes de produção. Se você implantar este projeto para fins de aprendizado em um ambiente de rede pública, certifique-se de que as medidas de proteção necessárias estejam em vigor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentação de Implantação
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Este projeto oferece dois métodos de implantação. Por favor, escolha de acordo com suas necessidades específicas:
|
||||||
|
|
||||||
|
#### 🚀 Seleção do Método de Implantação
|
||||||
|
| Método de Implantação | Funcionalidades | Cenários Aplicáveis | Documentação de Implantação | Requisitos de Configuração | Tutoriais em Vídeo |
|
||||||
|
|---------|------|---------|---------|---------|---------|
|
||||||
|
| **Instalação Simplificada** | Diálogo inteligente, gerenciamento de agente único | Ambientes de baixa configuração, dados armazenados em arquivos de configuração, sem necessidade de banco de dados | [①Versão Docker](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) / [②Implantação via Código-Fonte](./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)| 2 núcleos 4GB se usar `FunASR`, 2 núcleos 2GB se todas APIs | - |
|
||||||
|
| **Instalação de Módulo Completo** | Diálogo inteligente, gerenciamento multiusuário, gerenciamento de múltiplos agentes, operação de interface do console inteligente | Experiência com funcionalidade completa, dados armazenados em banco de dados |[①Versão 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) / [②Implantação via Código-Fonte](./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) / [③Tutorial de Atualização Automática via Código-Fonte](./docs/dev-ops-integration.md) | 4 núcleos 8GB se usar `FunASR`, 2 núcleos 4GB se todas APIs| [Tutorial em Vídeo de Inicialização via Código-Fonte Local](https://www.bilibili.com/video/BV1wBJhz4Ewe) |
|
||||||
|
|
||||||
|
Perguntas frequentes e tutoriais relacionados podem ser consultados [neste link](./docs/FAQ.md)
|
||||||
|
|
||||||
|
> 💡 Nota: Abaixo está uma plataforma de teste implantada com o código mais recente. Você pode gravar e testar se necessário. Usuários simultâneos: 6, os dados serão limpos diariamente.
|
||||||
|
|
||||||
|
```
|
||||||
|
Endereço do Console de Controle Inteligente: https://2662r3426b.vicp.fun
|
||||||
|
Endereço do Console de Controle Inteligente (H5): https://2662r3426b.vicp.fun/h5/index.html
|
||||||
|
|
||||||
|
Ferramenta de Teste de Serviço: https://2662r3426b.vicp.fun/test/
|
||||||
|
Endereço da Interface OTA: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
|
Endereço da Interface WebSocket: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 🚩 Descrição e Recomendações de Configuração
|
||||||
|
> [!Note]
|
||||||
|
> Este projeto oferece dois esquemas de configuração:
|
||||||
|
>
|
||||||
|
> 1. `Configurações Gratuitas Nível Básico`: Adequado para uso pessoal e doméstico, todos os componentes utilizam soluções gratuitas, sem necessidade de pagamento adicional.
|
||||||
|
>
|
||||||
|
> 2. `Configuração de Streaming`: Adequado para demonstrações, treinamentos, cenários com mais de 2 usuários simultâneos, etc. Utiliza tecnologia de processamento em streaming para velocidade de resposta mais rápida e melhor experiência.
|
||||||
|
>
|
||||||
|
> A partir da versão `0.5.2`, o projeto suporta configuração de streaming. Em comparação com versões anteriores, a velocidade de resposta é melhorada em aproximadamente `2,5 segundos`, melhorando significativamente a experiência do usuário.
|
||||||
|
|
||||||
|
| Nome do Módulo | Configurações Gratuitas Nível Básico | Configuração de Streaming |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| ASR(Reconhecimento de Fala) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
|
||||||
|
| LLM(Modelo de Linguagem) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
|
||||||
|
| VLLM(Modelo de Visão) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
|
||||||
|
| TTS(Síntese de Voz) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||||
|
| Intent(Reconhecimento de Intenção) | function_call(Chamada de função) | function_call(Chamada de função) |
|
||||||
|
| Memory(Função de Memória) | mem_local_short(Memória local de curto prazo) | mem_local_short(Memória local de curto prazo) |
|
||||||
|
|
||||||
|
Se você está preocupado com o tempo de resposta de cada componente, consulte o [Relatório de Teste de Desempenho dos Componentes Xiaozhi](https://github.com/xinnan-tech/xiaozhi-performance-research), e teste em seu próprio ambiente seguindo os métodos de teste do relatório.
|
||||||
|
|
||||||
|
#### 🔧 Ferramentas de Teste
|
||||||
|
Este projeto fornece as seguintes ferramentas de teste para ajudá-lo a verificar o sistema e escolher modelos adequados:
|
||||||
|
|
||||||
|
| Nome da Ferramenta | Localização | Método de Uso | Descrição da Função |
|
||||||
|
|:---:|:---|:---:|:---:|
|
||||||
|
| Ferramenta de Teste de Interação por Áudio | main》xiaozhi-server》test》test_page.html | Abrir diretamente com Google Chrome | Testa as funções de reprodução e recepção de áudio, verifica se o processamento de áudio no lado Python está normal |
|
||||||
|
| Ferramenta de Teste de Resposta de Modelo | main》xiaozhi-server》performance_tester.py | Execute `python performance_tester.py` | Testa a velocidade de resposta dos três módulos principais: ASR(reconhecimento de fala), LLM(modelo de linguagem), VLLM(modelo de visão), TTS(síntese de voz) |
|
||||||
|
|
||||||
|
> 💡 Nota: Ao testar a velocidade dos modelos, apenas os modelos com chaves configuradas serão testados.
|
||||||
|
|
||||||
|
---
|
||||||
|
## Lista de Funcionalidades ✨
|
||||||
|
### Implementado ✅
|
||||||
|

|
||||||
|
| Módulo de Funcionalidade | Descrição |
|
||||||
|
|:---:|:---|
|
||||||
|
| Arquitetura Principal | Baseado em [gateway MQTT+UDP](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mqtt-gateway-integration.md), servidores WebSocket e HTTP, fornece sistema completo de gerenciamento de console e autenticação |
|
||||||
|
| Interação por Voz | Suporta ASR em streaming (reconhecimento de fala), TTS em streaming (síntese de voz), VAD (detecção de atividade vocal), suporta reconhecimento multilíngue e processamento de voz |
|
||||||
|
| Reconhecimento de Impressão Vocal | Suporta registro, gerenciamento e reconhecimento de impressão vocal de múltiplos usuários, processa em paralelo com o ASR, reconhecimento de identidade do falante em tempo real e repassa ao LLM para respostas personalizadas |
|
||||||
|
| Diálogo Inteligente | Suporta múltiplos LLM (modelos de linguagem de grande porte), implementa diálogo inteligente |
|
||||||
|
| Percepção Visual | Suporta múltiplos VLLM (modelos de visão de grande porte), implementa interação multimodal |
|
||||||
|
| Reconhecimento de Intenção | Suporta reconhecimento de intenção por LLM, Function Call (chamada de função), fornece mecanismo de processamento de intenção baseado em plugins |
|
||||||
|
| Sistema de Memória | Suporta memória local de curto prazo, memória via interface mem0ai, memória inteligente PowerMem, com funcionalidade de resumo de memória |
|
||||||
|
| Base de Conhecimento | Suporta base de conhecimento RAGFlow, permitindo que o LLM julgue se deve acionar a base de conhecimento após receber a pergunta do usuário, e então responda à pergunta |
|
||||||
|
| Chamada de Ferramentas | Suporta protocolo IOT do cliente, protocolo MCP do cliente, protocolo MCP do servidor, protocolo de endpoint MCP, funções de ferramentas personalizadas |
|
||||||
|
| Envio de Comandos | Suporta envio de comandos MCP para dispositivos ESP32 via protocolo MQTT a partir do Console Inteligente |
|
||||||
|
| Backend de Gerenciamento | Fornece interface de gerenciamento Web, suporta gerenciamento de usuários, configuração do sistema e gerenciamento de dispositivos; Suporta exibição em Chinês Simplificado, Chinês Tradicional e Inglês |
|
||||||
|
| Ferramentas de Teste | Fornece ferramentas de teste de desempenho, ferramentas de teste de modelo de visão e ferramentas de teste de interação por áudio |
|
||||||
|
| Suporte à Implantação | Suporta implantação via Docker e implantação local, fornece gerenciamento completo de arquivos de configuração |
|
||||||
|
| Sistema de Plugins | Suporta extensões de plugins funcionais, desenvolvimento de plugins personalizados e carregamento dinâmico de plugins |
|
||||||
|
|
||||||
|
### Em Desenvolvimento 🚧
|
||||||
|
|
||||||
|
Para conhecer o progresso específico do plano de desenvolvimento, [clique aqui](https://github.com/users/xinnan-tech/projects/3). Perguntas frequentes e tutoriais relacionados podem ser consultados [neste link](./docs/FAQ.md)
|
||||||
|
|
||||||
|
Se você é um desenvolvedor de software, aqui está uma [Carta Aberta aos Desenvolvedores](docs/contributor_open_letter.md). Seja bem-vindo a participar!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ecossistema do Produto 👬
|
||||||
|
Xiaozhi é um ecossistema. Ao utilizar este produto, você também pode conferir outros [projetos excelentes](https://github.com/78/xiaozhi-esp32/blob/main/README_zh.md#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) neste ecossistema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lista de Plataformas/Componentes Suportados 📋
|
||||||
|
### LLM Modelos de Linguagem
|
||||||
|
|
||||||
|
| Método de Uso | Plataformas Suportadas | Plataformas Gratuitas |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| Chamadas via interface OpenAI | Alibaba Bailian, Volcano Engine, DeepSeek, Zhipu, Gemini, iFLYTEK | Zhipu, Gemini |
|
||||||
|
| Chamadas via interface Ollama | Ollama | - |
|
||||||
|
| Chamadas via interface Dify | Dify | - |
|
||||||
|
| Chamadas via interface FastGPT | FastGPT | - |
|
||||||
|
| Chamadas via interface Coze | Coze | - |
|
||||||
|
| Chamadas via interface Xinference | Xinference | - |
|
||||||
|
| Chamadas via interface HomeAssistant | HomeAssistant | - |
|
||||||
|
|
||||||
|
Na verdade, qualquer LLM que suporte chamadas via interface openai pode ser integrado e utilizado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### VLLM Modelos de Visão
|
||||||
|
|
||||||
|
| Método de Uso | Plataformas Suportadas | Plataformas Gratuitas |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| Chamadas via interface OpenAI | Alibaba Bailian, Zhipu ChatGLMVLLM | Zhipu ChatGLMVLLM |
|
||||||
|
|
||||||
|
Na verdade, qualquer VLLM que suporte chamadas via interface OpenAI pode ser integrado e utilizado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TTS Síntese de Voz
|
||||||
|
|
||||||
|
| Método de Uso | Plataformas Suportadas | Plataformas Gratuitas |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| Chamadas via interface | EdgeTTS, iFLYTEK, Volcano Engine, Tencent Cloud, Alibaba Cloud e Bailian, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS, Lingxi Streaming TTS, MinimaxTTS | Lingxi Streaming TTS, EdgeTTS, CosyVoiceSiliconflow(parcial) |
|
||||||
|
| Serviços locais | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, Index-TTS, PaddleSpeech | Index-TTS, PaddleSpeech, FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### VAD Detecção de Atividade Vocal
|
||||||
|
|
||||||
|
| Tipo | Nome da Plataforma | Método de Uso | Modelo de Preço | Observações |
|
||||||
|
|:---:|:---------:|:----:|:----:|:--:|
|
||||||
|
| VAD | SileroVAD | Uso local | Gratuito | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ASR Reconhecimento de Fala
|
||||||
|
|
||||||
|
| Método de Uso | Plataformas Suportadas | Plataformas Gratuitas |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| Uso local | FunASR, SherpaASR | FunASR, SherpaASR |
|
||||||
|
| Chamadas via interface | FunASRServer, Volcano Engine, iFLYTEK, Tencent Cloud, Alibaba Cloud, Baidu Cloud, OpenAI ASR | FunASRServer |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Reconhecimento de Impressão Vocal
|
||||||
|
|
||||||
|
| Método de Uso | Plataformas Suportadas | Plataformas Gratuitas |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| Uso local | 3D-Speaker | 3D-Speaker |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Armazenamento de Memória
|
||||||
|
|
||||||
|
| Tipo | Nome da Plataforma | Método de Uso | Modelo de Preço | Observações |
|
||||||
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
|
| Memória | mem0ai | Chamadas via interface | Cota de 1000 vezes/mês | |
|
||||||
|
| Memória | [powermem](./docs/powermem-integration.md) | Resumo local | Depende do LLM e BD | OceanBase de código aberto, suporta busca inteligente |
|
||||||
|
| Memória | mem_local_short | Resumo local | Gratuito | |
|
||||||
|
| Memória | nomem | Modo sem memória | Gratuito | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Reconhecimento de Intenção
|
||||||
|
|
||||||
|
| Tipo | Nome da Plataforma | Método de Uso | Modelo de Preço | Observações |
|
||||||
|
|:------:|:-------------:|:----:|:-------:|:---------------------:|
|
||||||
|
| Intenção | intent_llm | Chamadas via interface | Baseado no preço do LLM | Reconhece intenção através de modelos de linguagem, forte generalização |
|
||||||
|
| Intenção | function_call | Chamadas via interface | Baseado no preço do LLM | Completa a intenção através de chamada de função do modelo de linguagem, velocidade rápida, bom resultado |
|
||||||
|
| Intenção | nointent | Modo sem intenção | Gratuito | Não realiza reconhecimento de intenção, retorna diretamente o resultado do diálogo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### RAG Geração Aumentada por Recuperação
|
||||||
|
|
||||||
|
| Tipo | Nome da Plataforma | Método de Uso | Modelo de Preço | Observações |
|
||||||
|
|:------:|:-------------:|:----:|:-------:|:---------------------:|
|
||||||
|
| RAG | ragflow | Chamadas via interface | Cobrado com base nos tokens consumidos para fatiamento e segmentação de palavras | Utiliza o recurso de geração aumentada por recuperação do RagFlow para fornecer respostas de diálogo mais precisas |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agradecimentos 🙏
|
||||||
|
|
||||||
|
| Logo | Projeto/Empresa | Descrição |
|
||||||
|
|:---:|:---:|:---|
|
||||||
|
| <img src="./docs/images/logo_bailing.png" width="160"> | [Robô de Diálogo por Voz Bailing](https://github.com/wwbin2017/bailing) | Este projeto foi inspirado pelo [Robô de Diálogo por Voz Bailing](https://github.com/wwbin2017/bailing) e implementado com base nele |
|
||||||
|
| <img src="./docs/images/logo_tenclass.png" width="160"> | [Tenclass](https://www.tenclass.com/) | Agradecimentos à [Tenclass](https://www.tenclass.com/) por formular protocolos de comunicação padrão, soluções de compatibilidade multidispositivo e demonstrações práticas de cenários de alta concorrência para o ecossistema Xiaozhi; fornecendo suporte completo de documentação técnica para este projeto |
|
||||||
|
| <img src="./docs/images/logo_xuanfeng.png" width="160"> | [Xuanfeng Technology (玄凤科技)](https://github.com/Eric0308) | Agradecimentos à [Xuanfeng Technology](https://github.com/Eric0308) por contribuir com o framework de chamada de função, protocolo de comunicação MCP e implementação do mecanismo de chamada baseado em plugins. Através de um sistema padronizado de agendamento de instruções e capacidades de expansão dinâmica, melhora significativamente a eficiência de interação e extensibilidade funcional dos dispositivos de frontend (IoT) |
|
||||||
|
| <img src="./docs/images/logo_junsen.png" width="160"> | [huangjunsen](https://github.com/huangjunsen0406) | Agradecimentos a [huangjunsen](https://github.com/huangjunsen0406) por contribuir com o módulo `Console de Controle Inteligente Mobile`, que permite controle eficiente e interação em tempo real em dispositivos móveis, melhorando significativamente a conveniência operacional e a eficiência de gerenciamento do sistema em cenários móveis. |
|
||||||
|
| <img src="./docs/images/logo_huiyuan.png" width="160"> | [Huiyuan Design (汇远设计)](http://ui.kwd988.net/) | Agradecimentos à [Huiyuan Design](http://ui.kwd988.net/) por fornecer soluções visuais profissionais para este projeto, utilizando sua experiência prática de design atendendo mais de mil empresas para potencializar a experiência do usuário deste produto |
|
||||||
|
| <img src="./docs/images/logo_qinren.png" width="160"> | [Xi'an Qinren Information Technology (西安勤人信息科技)](https://www.029app.com/) | Agradecimentos à [Xi'an Qinren Information Technology](https://www.029app.com/) por aprofundar o sistema visual deste projeto, garantindo consistência e extensibilidade do estilo de design geral em aplicações de múltiplos cenários |
|
||||||
|
| <img src="./docs/images/logo_contributors.png" width="160"> | [Contribuidores de Código](https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors) | Agradecimentos a [todos os contribuidores de código](https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors), seus esforços tornaram o projeto mais robusto e poderoso. |
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
|
||||||
|
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
+4
-2
@@ -21,6 +21,7 @@ Hỗ trợ giao thức MQTT+UDP, giao thức Websocket, điểm truy cập MCP,
|
|||||||
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
<a href="./README_en.md"><img alt="README in English" src="https://img.shields.io/badge/English-DFE0E5"></a>
|
||||||
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DBEDFA"></a>
|
<a href="./README_vi.md"><img alt="Tiếng Việt" src="https://img.shields.io/badge/Tiếng Việt-DBEDFA"></a>
|
||||||
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
<a href="./README_de.md"><img alt="Deutsch" src="https://img.shields.io/badge/Deutsch-DFE0E5"></a>
|
||||||
|
<a href="./README_pt_BR.md"><img alt="Português (Brasil)" src="https://img.shields.io/badge/Português (Brasil)-DFE0E5"></a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
</a>
|
</a>
|
||||||
@@ -241,7 +242,7 @@ Dự án này cung cấp các công cụ kiểm tra sau để giúp bạn xác m
|
|||||||
| Đối thoại thông minh | Hỗ trợ nhiều LLM(Mô hình ngôn ngữ lớn), thực hiện đối thoại thông minh |
|
| Đối thoại thông minh | Hỗ trợ nhiều LLM(Mô hình ngôn ngữ lớn), thực hiện đối thoại thông minh |
|
||||||
| Cảm nhận thị giác | Hỗ trợ nhiều VLLM(Mô hình lớn thị giác), thực hiện tương tác đa phương thức |
|
| Cảm nhận thị giác | Hỗ trợ nhiều VLLM(Mô hình lớn thị giác), thực hiện tương tác đa phương thức |
|
||||||
| Nhận dạng ý định | Hỗ trợ nhận dạng ý định mô hình lớn gắn ngoài, gọi hàm tự chủ mô hình lớn, cung cấp cơ chế xử lý ý định dạng plugin |
|
| Nhận dạng ý định | Hỗ trợ nhận dạng ý định mô hình lớn gắn ngoài, gọi hàm tự chủ mô hình lớn, cung cấp cơ chế xử lý ý định dạng plugin |
|
||||||
| Hệ thống bộ nhớ | Hỗ trợ bộ nhớ ngắn hạn cục bộ, bộ nhớ giao diện mem0ai, có chức năng tóm tắt bộ nhớ |
|
| Hệ thống bộ nhớ | Hỗ trợ bộ nhớ ngắn hạn cục bộ, bộ nhớ giao diện mem0ai, bộ nhớ thông minh PowerMem, có chức năng tóm tắt bộ nhớ |
|
||||||
| Kho tri thức | Hỗ trợ kho tri thức RAGFlow, cho phép mô hình lớn đánh giá cần gọi kho tri thức trước khi trả lời |
|
| Kho tri thức | Hỗ trợ kho tri thức RAGFlow, cho phép mô hình lớn đánh giá cần gọi kho tri thức trước khi trả lời |
|
||||||
| Gọi công cụ | Hỗ trợ giao thức IOT phía client, giao thức MCP phía client, giao thức MCP phía server, giao thức điểm truy cập MCP, hàm công cụ tùy chỉnh |
|
| Gọi công cụ | Hỗ trợ giao thức IOT phía client, giao thức MCP phía client, giao thức MCP phía server, giao thức điểm truy cập MCP, hàm công cụ tùy chỉnh |
|
||||||
| Gửi lệnh | Dựa vào giao thức MQTT, hỗ trợ gửi lệnh MCP từ bảng điều khiển thông minh xuống thiết bị ESP32 |
|
| Gửi lệnh | Dựa vào giao thức MQTT, hỗ trợ gửi lệnh MCP từ bảng điều khiển thông minh xuống thiết bị ESP32 |
|
||||||
@@ -259,7 +260,7 @@ Nếu bạn là một nhà phát triển phần mềm, đây có một [Lá thư
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Hệ sinh thái sản phẩm 👬
|
## Hệ sinh thái sản phẩm 👬
|
||||||
Xiaozhi là một hệ sinh thái, khi bạn sử dụng sản phẩm này, bạn cũng có thể xem các [dự án xuất sắc](https://github.com/78/xiaozhi-esp32?tab=readme-ov-file#related-open-source-projects) khác trong hệ sinh thái này
|
Xiaozhi là một hệ sinh thái, khi bạn sử dụng sản phẩm này, bạn cũng có thể xem các [dự án xuất sắc](https://github.com/78/xiaozhi-esp32/blob/main/README_zh.md#%E7%9B%B8%E5%85%B3%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE) khác trong hệ sinh thái này
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -329,6 +330,7 @@ Trên thực tế, bất kỳ VLLM nào hỗ trợ gọi giao diện openai đ
|
|||||||
| Loại | Tên nền tảng | Phương pháp sử dụng | Mô hình thu phí | Ghi chú |
|
| Loại | Tên nền tảng | Phương pháp sử dụng | Mô hình thu phí | Ghi chú |
|
||||||
|:------:|:---------------:|:----:|:---------:|:--:|
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
| Memory | mem0ai | Gọi giao diện | Hạn mức 1000 lần/tháng | |
|
| Memory | mem0ai | Gọi giao diện | Hạn mức 1000 lần/tháng | |
|
||||||
|
| Memory | [powermem](./docs/powermem-integration.md) | Tóm tắt cục bộ | Phụ thuộc vào LLM và DB | OceanBase mã nguồn mở, hỗ trợ tìm kiếm thông minh |
|
||||||
| Memory | mem_local_short | Tóm tắt cục bộ | Miễn phí | |
|
| Memory | mem_local_short | Tóm tắt cục bộ | Miễn phí | |
|
||||||
| Memory | nomem | Chế độ không có bộ nhớ | Miễn phí | |
|
| Memory | nomem | Chế độ không có bộ nhớ | Miễn phí | |
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ VAD:
|
|||||||
8、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
8、[新闻插件源配置指南](./newsnow_plugin_config.md)<br/>
|
||||||
9、[知识库ragflow集成指南](./ragflow-integration.md)<br/>
|
9、[知识库ragflow集成指南](./ragflow-integration.md)<br/>
|
||||||
10、[如何部署上下文源](./context-provider-integration.md)<br/>
|
10、[如何部署上下文源](./context-provider-integration.md)<br/>
|
||||||
|
11、[如何集成PowerMem智能记忆](./powermem-integration.md)<br/>
|
||||||
|
|
||||||
### 11、语音克隆、本地语音部署相关教程
|
### 11、语音克隆、本地语音部署相关教程
|
||||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ pm2 restart xz-mqtt
|
|||||||
```
|
```
|
||||||
192.168.0.7:8884
|
192.168.0.7:8884
|
||||||
```
|
```
|
||||||
4. 在智控台顶部,点击`参数管理`,搜索`server.mqtt_manager_api`,点击编辑,填入你在`.env`文件中设置的`PUBLIC_IP`+`:`+`UDP_PORT`。类似这样
|
4. 在智控台顶部,点击`参数管理`,搜索`server.mqtt_manager_api`,点击编辑,填入你在`.env`文件中设置的`PUBLIC_IP`+`:`+`API_PORT`。类似这样
|
||||||
```
|
```
|
||||||
192.168.0.7:8007
|
192.168.0.7:8007
|
||||||
```
|
```
|
||||||
@@ -154,7 +154,7 @@ curl 'http://localhost:8002/xiaozhi/ota/' \
|
|||||||
pm2 logs xz-mqtt
|
pm2 logs xz-mqtt
|
||||||
```
|
```
|
||||||
|
|
||||||
## 第三部分:全模块运行实现小智硬件MQTT+UDP连接
|
## 第三部分:单模块运行xiaozhi-server实现小智硬件MQTT+UDP连接
|
||||||
|
|
||||||
打开你的`data/.config.yaml`文件,在`server`下找到`mqtt_gateway`填入你在`.env`文件中设置的`PUBLIC_IP`+`:`+`MQTT_PORT`。类似这样
|
打开你的`data/.config.yaml`文件,在`server`下找到`mqtt_gateway`填入你在`.env`文件中设置的`PUBLIC_IP`+`:`+`MQTT_PORT`。类似这样
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,345 @@
|
|||||||
|
# PowerMem 记忆组件集成指南
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
[PowerMem](https://www.powermem.ai/) 是由 OceanBase 开源的 Agent 记忆组件,通过本地 LLM 进行记忆总结和智能检索,为 AI 代理提供高效的记忆管理功能。
|
||||||
|
|
||||||
|
费用说明:PowerMem 本身开源免费,实际费用取决于您选择的 LLM 和数据库:
|
||||||
|
- 使用 SQLite + 免费 LLM(如智谱 glm-4-flash)= **完全免费**
|
||||||
|
- 使用云端 LLM 或云端数据库 = 按对应服务收费
|
||||||
|
|
||||||
|
> 💡 **最佳性能提示**:PowerMem 配合 OceanBase 使用可实现最大性能释放,SQLite 仅建议在资源不足的情况下使用。
|
||||||
|
|
||||||
|
- **GitHub**: https://github.com/oceanbase/powermem
|
||||||
|
- **官网**: https://www.powermem.ai/
|
||||||
|
- **使用示例**: https://github.com/oceanbase/powermem/tree/main/examples
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **本地总结**:通过 LLM 在本地进行记忆总结和提取
|
||||||
|
- **用户画像**:通过 `UserMemory` 自动提取用户信息(姓名、职业、兴趣等),持续更新用户画像
|
||||||
|
- **智能遗忘**:基于艾宾浩斯遗忘曲线,自动"遗忘"过时噪声信息
|
||||||
|
- **多种存储后端**:支持 OceanBase(推荐,最佳性能)、SeekDB(推荐,AI应用存储一体)、PostgreSQL、SQLite(轻量备选)
|
||||||
|
- **多种 LLM 支持**:通义千问、智谱(glm-4-flash 免费)、OpenAI 等
|
||||||
|
- **智能检索**:基于向量搜索的语义检索能力
|
||||||
|
- **私有部署**:完全支持本地私有化部署
|
||||||
|
- **异步操作**:高效的异步记忆管理
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
PowerMem 已添加到项目依赖中,如果需要手动安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install powermem
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
在 `config.yaml` 中配置 PowerMem:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
selected_module:
|
||||||
|
Memory: powermem
|
||||||
|
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
# 是否启用用户画像功能
|
||||||
|
# 用户画像支持: oceanbase、seekdb、sqlite (powermem 0.3.0+)
|
||||||
|
enable_user_profile: true
|
||||||
|
|
||||||
|
# ========== LLM 配置 ==========
|
||||||
|
llm:
|
||||||
|
provider: openai # 可选: qwen, openai, zhipu 等
|
||||||
|
config:
|
||||||
|
api_key: 你的LLM API密钥
|
||||||
|
model: qwen-plus
|
||||||
|
# openai_base_url: https://api.openai.com/v1 # 可选,自定义服务地址
|
||||||
|
|
||||||
|
# ========== Embedding 配置 ==========
|
||||||
|
embedder:
|
||||||
|
provider: openai # 可选: qwen, openai 等
|
||||||
|
config:
|
||||||
|
api_key: 你的嵌入模型API密钥
|
||||||
|
model: text-embedding-v4
|
||||||
|
openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
# embedding_dims: 1024 # 向量维度,非1536时需配置
|
||||||
|
|
||||||
|
# ========== Database 配置 ==========
|
||||||
|
vector_store:
|
||||||
|
provider: sqlite # 可选: oceanbase(推荐), seekdb(推荐), postgres, sqlite(轻量)
|
||||||
|
config: {} # SQLite 无需额外配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置参数详解
|
||||||
|
|
||||||
|
#### LLM 配置
|
||||||
|
|
||||||
|
| 参数 | 说明 | 可选值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `llm.provider` | LLM 提供商 | `qwen`, `openai`, `zhipu` 等 |
|
||||||
|
| `llm.config.api_key` | API 密钥 | - |
|
||||||
|
| `llm.config.model` | 模型名称 | 根据提供商选择 |
|
||||||
|
| `llm.config.openai_base_url` | 自定义服务地址(可选) | - |
|
||||||
|
|
||||||
|
#### Embedding 配置
|
||||||
|
|
||||||
|
| 参数 | 说明 | 可选值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `embedder.provider` | 嵌入模型提供商 | `qwen`, `openai` 等 |
|
||||||
|
| `embedder.config.api_key` | API 密钥 | - |
|
||||||
|
| `embedder.config.model` | 模型名称 | 根据提供商选择 |
|
||||||
|
| `embedder.config.openai_base_url` | 自定义服务地址(可选) | - |
|
||||||
|
|
||||||
|
#### Database 配置
|
||||||
|
|
||||||
|
| 参数 | 说明 | 可选值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `vector_store.provider` | 存储后端类型 | `oceanbase`(推荐), `seekdb`(推荐), `postgres`, `sqlite`(轻量) |
|
||||||
|
| `vector_store.config` | 数据库连接配置 | 根据 provider 设置 |
|
||||||
|
|
||||||
|
### 记忆模式说明
|
||||||
|
|
||||||
|
PowerMem 支持两种记忆模式:
|
||||||
|
|
||||||
|
| 模式 | 配置 | 功能 | 存储要求 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| **普通记忆** | `enable_user_profile: false` | 对话记忆存储与检索 | 支持所有数据库 |
|
||||||
|
| **用户画像** | `enable_user_profile: true` | 记忆 + 自动提取用户画像 | oceanbase、seekdb、sqlite |
|
||||||
|
|
||||||
|
> 📌 **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。
|
||||||
|
|
||||||
|
### 使用通义千问(推荐)
|
||||||
|
|
||||||
|
1. 访问 [阿里云百炼平台](https://bailian.console.aliyun.com/) 注册账号
|
||||||
|
2. 在 [API Key 管理](https://bailian.console.aliyun.com/?apiKey=1#/api-key) 页面获取 API 密钥
|
||||||
|
3. 配置如下:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
enable_user_profile: true
|
||||||
|
llm:
|
||||||
|
provider: qwen
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: qwen-plus
|
||||||
|
embedder:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: text-embedding-v4
|
||||||
|
openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
vector_store:
|
||||||
|
provider: sqlite
|
||||||
|
config: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用智谱免费 LLM(完全免费方案)
|
||||||
|
|
||||||
|
智谱提供免费的 glm-4-flash 模型,配合 SQLite 可实现完全免费使用:
|
||||||
|
|
||||||
|
1. 访问 [智谱AI开放平台](https://bigmodel.cn/) 注册账号
|
||||||
|
2. 在 [API Keys](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) 页面获取 API 密钥
|
||||||
|
3. 配置如下:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
enable_user_profile: true
|
||||||
|
llm:
|
||||||
|
provider: openai # 使用 openai 兼容模式
|
||||||
|
config:
|
||||||
|
api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx
|
||||||
|
model: glm-4-flash
|
||||||
|
openai_base_url: https://open.bigmodel.cn/api/paas/v4/
|
||||||
|
embedder:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx
|
||||||
|
model: embedding-3
|
||||||
|
openai_base_url: https://open.bigmodel.cn/api/paas/v4/
|
||||||
|
vector_store:
|
||||||
|
provider: sqlite
|
||||||
|
config: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 OpenAI
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
enable_user_profile: true
|
||||||
|
llm:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: gpt-4o-mini
|
||||||
|
openai_base_url: https://api.openai.com/v1
|
||||||
|
embedder:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: text-embedding-3-small
|
||||||
|
openai_base_url: https://api.openai.com/v1
|
||||||
|
vector_store:
|
||||||
|
provider: sqlite
|
||||||
|
config: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 OceanBase(最佳性能方案)
|
||||||
|
|
||||||
|
OceanBase 是 PowerMem 的最佳搭档,可实现最大性能释放:
|
||||||
|
|
||||||
|
1. 部署 OceanBase 数据库(支持开源本地部署或使用云服务)
|
||||||
|
- 开源部署:https://github.com/oceanbase/oceanbase
|
||||||
|
- 云服务:https://www.oceanbase.com/
|
||||||
|
2. 配置如下:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
enable_user_profile: true
|
||||||
|
llm:
|
||||||
|
provider: qwen
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: qwen-plus
|
||||||
|
embedder:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: text-embedding-v4
|
||||||
|
openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
vector_store:
|
||||||
|
provider: oceanbase
|
||||||
|
config:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 2881
|
||||||
|
user: root@test
|
||||||
|
password: your_password
|
||||||
|
db_name: powermem
|
||||||
|
collection_name: memories # 默认值
|
||||||
|
embedding_model_dims: 1536 # 嵌入向量维度,必需参数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设备记忆隔离
|
||||||
|
|
||||||
|
PowerMem 会自动使用设备 ID(`device_id`)作为 `user_id` 进行记忆隔离。这意味着:
|
||||||
|
|
||||||
|
- 每个设备拥有独立的记忆空间
|
||||||
|
- 不同设备之间的记忆完全隔离
|
||||||
|
- 同一设备的多次对话可以共享记忆上下文
|
||||||
|
|
||||||
|
## 用户画像(UserMemory)
|
||||||
|
|
||||||
|
PowerMem 提供 `UserMemory` 类,可自动从对话中提取用户画像信息。
|
||||||
|
|
||||||
|
> 📌 **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。
|
||||||
|
|
||||||
|
### 启用用户画像
|
||||||
|
|
||||||
|
在配置中设置 `enable_user_profile: true` 即可启用:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
powermem:
|
||||||
|
type: powermem
|
||||||
|
enable_user_profile: true # 启用用户画像
|
||||||
|
llm:
|
||||||
|
provider: qwen
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: qwen-plus
|
||||||
|
embedder:
|
||||||
|
provider: openai
|
||||||
|
config:
|
||||||
|
api_key: sk-xxxxxxxxxxxxxxxx
|
||||||
|
model: text-embedding-v4
|
||||||
|
openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
vector_store:
|
||||||
|
provider: sqlite # 用户画像支持: oceanbase、seekdb、sqlite
|
||||||
|
config: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户画像能力
|
||||||
|
|
||||||
|
| 能力 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **信息提取** | 自动从对话中提取姓名、年龄、职业、兴趣等 |
|
||||||
|
| **持续更新** | 随着对话进行,不断完善用户画像 |
|
||||||
|
| **画像检索** | 将用户画像与记忆搜索结合,提升检索相关性 |
|
||||||
|
| **智能遗忘** | 基于艾宾浩斯遗忘曲线,淡化过时信息 |
|
||||||
|
|
||||||
|
### 工作原理
|
||||||
|
|
||||||
|
启用用户画像后,小智在查询记忆时会自动返回:
|
||||||
|
1. **用户画像**:用户的基本信息、兴趣爱好等
|
||||||
|
2. **相关记忆**:与当前对话相关的历史记忆
|
||||||
|
|
||||||
|
> ✅ **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。
|
||||||
|
|
||||||
|
## 与其他记忆组件的对比
|
||||||
|
|
||||||
|
| 特性 | PowerMem | mem0ai | mem_local_short |
|
||||||
|
|------|----------|--------|-----------------|
|
||||||
|
| 工作方式 | 本地总结 | 云端接口 | 本地总结 |
|
||||||
|
| 存储位置 | 本地/云端DB | 云端 | 本地YAML |
|
||||||
|
| 费用 | 取决于LLM和DB | 1000次/月免费 | 完全免费 |
|
||||||
|
| 智能检索 | ✅ 向量搜索 | ✅ 向量搜索 | ❌ 全量返回 |
|
||||||
|
| 用户画像 | ✅ UserMemory | ❌ | ❌ |
|
||||||
|
| 智能遗忘 | ✅ 遗忘曲线 | ❌ | ❌ |
|
||||||
|
| 私有部署 | ✅ 支持 | ❌ 仅云端 | ✅ 支持 |
|
||||||
|
| 数据库支持 | OceanBase(推荐)/SeekDB/PostgreSQL/SQLite | - | YAML 文件 |
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. API 密钥错误
|
||||||
|
|
||||||
|
如果出现 `API key is required` 错误,请检查:
|
||||||
|
- `llm_api_key` 和 `embedding_api_key` 是否正确填写
|
||||||
|
- API 密钥是否有效
|
||||||
|
|
||||||
|
### 2. 模型不存在
|
||||||
|
|
||||||
|
如果出现模型不存在的错误,请确认:
|
||||||
|
- `llm_model` 和 `embedding_model` 名称是否正确
|
||||||
|
- 对应的模型服务是否已开通
|
||||||
|
|
||||||
|
### 3. 连接超时
|
||||||
|
|
||||||
|
如果出现连接超时,可以尝试:
|
||||||
|
- 检查网络连接
|
||||||
|
- 如果使用代理,配置 `llm_base_url` 和 `embedding_base_url`
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
可以在虚拟环境中测试 PowerMem 是否正常工作:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# 测试 PowerMem 导入
|
||||||
|
python -c "from powermem import AsyncMemory; print('PowerMem 导入成功')"
|
||||||
|
|
||||||
|
# 测试 UserMemory 导入(用户画像功能)
|
||||||
|
python -c "from powermem import UserMemory; print('UserMemory 导入成功')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更多资源
|
||||||
|
|
||||||
|
- [PowerMem 官方文档](https://www.powermem.ai/)
|
||||||
|
- [PowerMem GitHub 仓库](https://github.com/oceanbase/powermem)
|
||||||
|
- [PowerMem 使用示例](https://github.com/oceanbase/powermem/tree/main/examples)
|
||||||
|
- [OceanBase 官网](https://www.oceanbase.com/)
|
||||||
|
- [OceanBase GitHub](https://github.com/oceanbase/oceanbase)
|
||||||
|
- [SeekDB GitHub](https://github.com/oceanbase/seekdb)(AI原生搜索数据库)
|
||||||
|
- [阿里云百炼平台](https://bailian.console.aliyun.com/)
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# 知识库模块全量集成测试报告
|
||||||
|
|
||||||
|
## 1. 测试背景
|
||||||
|
针对 `KnowledgeBaseController` 和 `KnowledgeFilesController` 共 14 个接口进行了深度集成测试。主要解决了本地影子库与 RAGFlow 远程服务之间的状态对齐、数据反序列化兼容性以及批量操作逻辑安全性问题。
|
||||||
|
|
||||||
|
## 2. 修复的核心 Bug 清单 (Hotfixes)
|
||||||
|
|
||||||
|
| 模块 | 问题类型 | 修复方案 | 验证结果 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **DTO** | `positions` 反序列化失败 | 类型从 `List<Integer>` 提升为 `Object`,支持嵌套数组 | ✅ 已验证 |
|
||||||
|
| **DTO** | 日期格式不兼容 | 针对 RAGFlow 的 RFC 1123 格式,将 `Date` 改为 `String` 透传 | ✅ 已验证 |
|
||||||
|
| **请求** | 检索参数 `null` 拒绝 | 增加 `@JsonInclude(NON_NULL)`,跳过可选字段的空值序列化 | ✅ 已验证 |
|
||||||
|
| **同步** | 状态自愈死锁 | 增加 `CANCEL/FAIL` 状态的 60s 低频同步机制,防止逻辑错误锁定 | ✅ 已验证 |
|
||||||
|
| **逻辑** | 删除守卫逻辑错误 | 将拦截条件从 `status="1"` 修正为 `run="RUNNING"` | ✅ 已验证 |
|
||||||
|
|
||||||
|
## 3. 全量接口测试统计
|
||||||
|
|
||||||
|
### KnowledgeBaseController (7/7)
|
||||||
|
- [x] 分页查询 (`GET /datasets`)
|
||||||
|
- [x] 详情获取 (`GET /datasets/{id}`)
|
||||||
|
- [x] 创建知识库 (`POST /datasets`)
|
||||||
|
- [x] 修改配置 (`PUT /datasets/{id}`)
|
||||||
|
- [x] 物理删除 (`DELETE /datasets/{id}`)
|
||||||
|
- [x] 批量删除 (`DELETE /datasets/batch`)
|
||||||
|
- [x] 模型列表获取 (`GET /datasets/rag-models`)
|
||||||
|
|
||||||
|
### KnowledgeFilesController (7/7)
|
||||||
|
- [x] 文档列表与同步 (`GET /datasets/{id}/documents`)
|
||||||
|
- [x] 状态过滤查询 (`GET /datasets/{id}/documents/status/{s}`)
|
||||||
|
- [x] 文档上传 (`POST /datasets/{id}/documents`)
|
||||||
|
- [x] 触发解析 (`POST /datasets/{id}/chunks`)
|
||||||
|
- [x] 切片详情 (`GET /datasets/{id}/documents/{docId}/chunks`)
|
||||||
|
- [x] 召回测试 (`POST /datasets/{id}/retrieval-test`)
|
||||||
|
- [x] 批量删除文档 (`DELETE /datasets/{id}/documents`)
|
||||||
|
|
||||||
|
## 4. 自动化审计结论
|
||||||
|
通过执行 `comprehensive_audit.ps1` 自动化脚本,模拟了“创建->上传->解析->同步->检索->删除”的完整生产链路。
|
||||||
|
- **解析成功率**:100%
|
||||||
|
- **数据准确性**:DTO 转换无异常,坐标及得分提取正常
|
||||||
|
- **系统安全性**:解析中拦截机制生效
|
||||||
|
- **结论**:**准生产就绪 (Production Ready)**
|
||||||
|
|
||||||
|
---
|
||||||
|
*报告生成时间:2026-02-13*
|
||||||
|
*审核:dora--1206563805@qq.com*
|
||||||
@@ -2,7 +2,9 @@ package xiaozhi.common.config;
|
|||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RestTemplate配置
|
* RestTemplate配置
|
||||||
@@ -12,6 +14,8 @@ public class RestTemplateConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate restTemplate() {
|
public RestTemplate restTemplate() {
|
||||||
return new RestTemplate();
|
JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory();
|
||||||
|
factory.setReadTimeout(Duration.ofSeconds(30));
|
||||||
|
return new RestTemplate(factory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ public class SwaggerConfig {
|
|||||||
public GroupedOpenApi oatApi() {
|
public GroupedOpenApi oatApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("ota")
|
.group("ota")
|
||||||
.pathsToMatch("/ota/**")
|
.pathsToMatch("/ota/**", "/otaMag/**")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +79,22 @@ public class SwaggerConfig {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi knowledgeApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("knowledge")
|
||||||
|
.pathsToMatch("/datasets/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi botApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("bot")
|
||||||
|
.pathsToMatch("/api/v1/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
return new OpenAPI().info(new Info()
|
return new OpenAPI().info(new Info()
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ public interface Constant {
|
|||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.8.11";
|
public static final String VERSION = "0.9.2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无效固件URL
|
* 无效固件URL
|
||||||
|
|||||||
@@ -240,4 +240,15 @@ public interface ErrorCode {
|
|||||||
int RAG_ADAPTER_CACHE_ERROR = 10191; // 适配器缓存错误
|
int RAG_ADAPTER_CACHE_ERROR = 10191; // 适配器缓存错误
|
||||||
int RAG_ADAPTER_TYPE_NOT_FOUND = 10192; // 适配器类型未找到
|
int RAG_ADAPTER_TYPE_NOT_FOUND = 10192; // 适配器类型未找到
|
||||||
|
|
||||||
|
// 设备工具相关错误码
|
||||||
|
int DEVICE_ID_NOT_NULL = 10193; // 设备ID不能为空
|
||||||
|
int DEVICE_NOT_EXIST = 10194; // 设备不存在
|
||||||
|
int OTA_UPLOAD_COUNT_EXCEED = 10195; // OTA上传次数超过限制
|
||||||
|
|
||||||
|
// 智能体标签相关错误码
|
||||||
|
int AGENT_TAG_NAME_DUPLICATE = 10196; // 标签名称已存在
|
||||||
|
int AGENT_TAG_NAME_EMPTY = 10197; // 标签名称不能为空
|
||||||
|
int AGENT_TAG_NOT_EXIST = 10198; // 标签不存在
|
||||||
|
|
||||||
|
int RAG_DOCUMENT_PARSING_DELETE_ERROR = 10199; // 文档解析中,禁止删除
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,23 +13,25 @@ public class RenException extends RuntimeException {
|
|||||||
private String msg;
|
private String msg;
|
||||||
|
|
||||||
public RenException(int code) {
|
public RenException(int code) {
|
||||||
|
super(MessageUtils.getMessage(code));
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.msg = MessageUtils.getMessage(code);
|
this.msg = MessageUtils.getMessage(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenException(int code, String... params) {
|
public RenException(int code, String... params) {
|
||||||
|
super(MessageUtils.getMessage(code, params));
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.msg = MessageUtils.getMessage(code, params);
|
this.msg = MessageUtils.getMessage(code, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenException(int code, Throwable e) {
|
public RenException(int code, Throwable e) {
|
||||||
super(e);
|
super(MessageUtils.getMessage(code), e);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.msg = MessageUtils.getMessage(code);
|
this.msg = MessageUtils.getMessage(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenException(int code, Throwable e, String... params) {
|
public RenException(int code, Throwable e, String... params) {
|
||||||
super(e);
|
super(MessageUtils.getMessage(code, params), e);
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.msg = MessageUtils.getMessage(code, params);
|
this.msg = MessageUtils.getMessage(code, params);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,4 +166,25 @@ public class RedisKeys {
|
|||||||
public static String getTmpRegisterMacKey(String deviceId) {
|
public static String getTmpRegisterMacKey(String deviceId) {
|
||||||
return "tmp_register_mac:" + deviceId;
|
return "tmp_register_mac:" + deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA绑定设备
|
||||||
|
*/
|
||||||
|
public static String getOtaActivationCode(String activationCode) {
|
||||||
|
return "ota:activation:code:" + activationCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA获取设备mac相关信息
|
||||||
|
*/
|
||||||
|
public static String getOtaDeviceActivationInfo(String deviceId) {
|
||||||
|
return "ota:activation:data:" + deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA上传次数
|
||||||
|
*/
|
||||||
|
public static String getOtaUploadCountKey(Long username) {
|
||||||
|
return "ota:upload:count:" + username;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,15 @@ public class Sm2DecryptUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 解密SM2加密内容,提取验证码并验证
|
* 解密SM2加密内容,提取验证码并验证
|
||||||
|
*
|
||||||
* @param encryptedPassword SM2加密的密码字符串
|
* @param encryptedPassword SM2加密的密码字符串
|
||||||
* @param captchaId 验证码ID
|
* @param captchaId 验证码ID
|
||||||
* @param captchaService 验证码服务
|
* @param captchaService 验证码服务
|
||||||
* @param sysParamsService 系统参数服务
|
* @param sysParamsService 系统参数服务
|
||||||
* @return 解密后的实际密码
|
* @return 解密后的实际密码
|
||||||
*/
|
*/
|
||||||
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
|
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
|
||||||
CaptchaService captchaService, SysParamsService sysParamsService) {
|
CaptchaService captchaService, SysParamsService sysParamsService) {
|
||||||
// 获取SM2私钥
|
// 获取SM2私钥
|
||||||
String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true);
|
String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true);
|
||||||
if (StringUtils.isBlank(privateKeyStr)) {
|
if (StringUtils.isBlank(privateKeyStr)) {
|
||||||
@@ -47,13 +48,14 @@ public class Sm2DecryptUtil {
|
|||||||
String embeddedCaptcha = decryptedContent.substring(0, CAPTCHA_LENGTH);
|
String embeddedCaptcha = decryptedContent.substring(0, CAPTCHA_LENGTH);
|
||||||
String actualPassword = decryptedContent.substring(CAPTCHA_LENGTH);
|
String actualPassword = decryptedContent.substring(CAPTCHA_LENGTH);
|
||||||
|
|
||||||
// 验证嵌入的验证码是否正确
|
|
||||||
boolean embeddedCaptchaValid = captchaService.validate(captchaId, embeddedCaptcha, true);
|
boolean embeddedCaptchaValid = captchaService.validate(captchaId, embeddedCaptcha, true);
|
||||||
if (!embeddedCaptchaValid) {
|
if (!embeddedCaptchaValid) {
|
||||||
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actualPassword;
|
return actualPassword;
|
||||||
|
} else if (decryptedContent.length() > 0) {
|
||||||
|
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||||
} else {
|
} else {
|
||||||
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
|
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用工具类
|
||||||
|
*/
|
||||||
|
public class ToolUtil {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ToolUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象是否不为空(新增)
|
||||||
|
*/
|
||||||
|
public static boolean isNotEmpty(Object o) {
|
||||||
|
return !isEmpty(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象是否为空
|
||||||
|
*/
|
||||||
|
public static boolean isEmpty(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o instanceof String) {
|
||||||
|
if (o.toString().trim().equals("")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof List) {
|
||||||
|
if (((List) o).size() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof Map) {
|
||||||
|
if (((Map) o).size() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof Set) {
|
||||||
|
if (((Set) o).size() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof Object[]) {
|
||||||
|
if (((Object[]) o).length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof int[]) {
|
||||||
|
if (((int[]) o).length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (o instanceof long[]) {
|
||||||
|
if (((long[]) o).length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象组中是否存在空对象
|
||||||
|
*/
|
||||||
|
public static boolean isOneEmpty(Object... os) {
|
||||||
|
for (Object o : os) {
|
||||||
|
if (isEmpty(o)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象组中是否全是空对象
|
||||||
|
*/
|
||||||
|
public static boolean isAllEmpty(Object... os) {
|
||||||
|
for (Object o : os) {
|
||||||
|
if (!isEmpty(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-2
@@ -42,6 +42,9 @@ import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
|||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTagService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
||||||
@@ -69,13 +72,18 @@ public class AgentController {
|
|||||||
private final AgentContextProviderService agentContextProviderService;
|
private final AgentContextProviderService agentContextProviderService;
|
||||||
private final AgentChatSummaryService agentChatSummaryService;
|
private final AgentChatSummaryService agentChatSummaryService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
|
private final AgentTagService agentTagService;
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取用户智能体列表")
|
@Operation(summary = "获取用户智能体列表")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<AgentDTO>> getUserAgents() {
|
public Result<List<AgentDTO>> getUserAgents(
|
||||||
|
@RequestParam(value = "keyword", required = false) String keyword,
|
||||||
|
@RequestParam(value = "searchType", defaultValue = "name") String searchType) {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
List<AgentDTO> agents = agentService.getUserAgents(user.getId());
|
|
||||||
|
// 直接调用整合后的getUserAgents方法,无需再区分搜索和普通查询
|
||||||
|
List<AgentDTO> agents = agentService.getUserAgents(user.getId(), keyword, searchType);
|
||||||
return new Result<List<AgentDTO>>().ok(agents);
|
return new Result<List<AgentDTO>>().ok(agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,4 +279,50 @@ public class AgentController {
|
|||||||
.body(audioData);
|
.body(audioData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tag")
|
||||||
|
@Operation(summary = "创建标签")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<AgentTagEntity> createTag(@RequestBody Map<String, String> params) {
|
||||||
|
String tagName = params.get("tagName");
|
||||||
|
if (StringUtils.isBlank(tagName)) {
|
||||||
|
return new Result<AgentTagEntity>().error("标签名称不能为空");
|
||||||
|
}
|
||||||
|
AgentTagEntity tag = agentTagService.saveTag(tagName);
|
||||||
|
return new Result<AgentTagEntity>().ok(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tag/list")
|
||||||
|
@Operation(summary = "获取所有标签列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<AgentTagDTO>> getAllTags() {
|
||||||
|
List<AgentTagDTO> tags = agentTagService.getAllTags();
|
||||||
|
return new Result<List<AgentTagDTO>>().ok(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/tag/{id}")
|
||||||
|
@Operation(summary = "删除标签")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> deleteTag(@PathVariable String id) {
|
||||||
|
agentTagService.deleteTag(id);
|
||||||
|
return new Result<Void>().ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/tags")
|
||||||
|
@Operation(summary = "获取智能体的标签")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<AgentTagDTO>> getAgentTags(@PathVariable String id) {
|
||||||
|
List<AgentTagDTO> tags = agentTagService.getTagsByAgentId(id);
|
||||||
|
return new Result<List<AgentTagDTO>>().ok(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/tags")
|
||||||
|
@Operation(summary = "保存智能体的标签")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
||||||
|
List<String> tagIds = (List<String>) params.get("tagIds");
|
||||||
|
List<String> tagNames = (List<String>) params.get("tagNames");
|
||||||
|
agentTagService.saveAgentTags(id, tagIds, tagNames);
|
||||||
|
return new Result<Void>().ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -28,7 +28,7 @@ public class AgentMcpAccessPointController {
|
|||||||
/**
|
/**
|
||||||
* 获取智能体的Mcp接入点地址
|
* 获取智能体的Mcp接入点地址
|
||||||
*
|
*
|
||||||
* @param audioId 智能体id
|
* @param agentId 智能体id
|
||||||
* @return 返回错误提醒或者Mcp接入点地址
|
* @return 返回错误提醒或者Mcp接入点地址
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "获取智能体的Mcp接入点地址")
|
@Operation(summary = "获取智能体的Mcp接入点地址")
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import xiaozhi.common.dao.BaseDao;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AgentTagDao extends BaseDao<AgentTagEntity> {
|
||||||
|
|
||||||
|
List<AgentTagEntity> selectByAgentId(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
List<AgentTagEntity> selectByAgentIds(@Param("agentIds") List<String> agentIds);
|
||||||
|
|
||||||
|
List<AgentTagEntity> selectAll();
|
||||||
|
|
||||||
|
List<String> selectAgentIdsByTagName(@Param("tagName") String tagName);
|
||||||
|
|
||||||
|
List<AgentTagEntity> selectByTagNames(@Param("tagNames") List<String> tagNames);
|
||||||
|
|
||||||
|
int batchInsert(@Param("list") List<AgentTagEntity> tagList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import xiaozhi.common.dao.BaseDao;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagRelationEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AgentTagRelationDao extends BaseDao<AgentTagRelationEntity> {
|
||||||
|
|
||||||
|
int deleteByAgentId(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
int insertRelation(AgentTagRelationEntity relation);
|
||||||
|
|
||||||
|
int batchInsertRelation(@Param("list") List<AgentTagRelationEntity> relations);
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package xiaozhi.modules.agent.dto;
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 智能体数据传输对象
|
* 智能体数据传输对象
|
||||||
@@ -45,4 +47,7 @@ public class AgentDTO {
|
|||||||
|
|
||||||
@Schema(description = "设备数量", example = "10")
|
@Schema(description = "设备数量", example = "10")
|
||||||
private Integer deviceCount;
|
private Integer deviceCount;
|
||||||
|
|
||||||
|
@Schema(description = "标签列表")
|
||||||
|
private List<AgentTagDTO> tags;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体标签DTO")
|
||||||
|
public class AgentTagDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "标签ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "标签名称")
|
||||||
|
private String tagName;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package xiaozhi.modules.agent.dto;
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -41,6 +42,18 @@ public class AgentUpdateDTO implements Serializable {
|
|||||||
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
||||||
private String ttsVoiceId;
|
private String ttsVoiceId;
|
||||||
|
|
||||||
|
@Schema(description = "音色语言", example = "普通话", nullable = true)
|
||||||
|
private String ttsLanguage;
|
||||||
|
|
||||||
|
@Schema(description = "TTS音量", example = "50", nullable = true)
|
||||||
|
private Integer ttsVolume;
|
||||||
|
|
||||||
|
@Schema(description = "TTS语速", example = "50", nullable = true)
|
||||||
|
private Integer ttsRate;
|
||||||
|
|
||||||
|
@Schema(description = "TTS音调", example = "50", nullable = true)
|
||||||
|
private Integer ttsPitch;
|
||||||
|
|
||||||
@Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true)
|
@Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true)
|
||||||
private String memModelId;
|
private String memModelId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package xiaozhi.modules.agent.entity;
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
@@ -45,6 +46,18 @@ public class AgentEntity {
|
|||||||
@Schema(description = "音色标识")
|
@Schema(description = "音色标识")
|
||||||
private String ttsVoiceId;
|
private String ttsVoiceId;
|
||||||
|
|
||||||
|
@Schema(description = "音色语言")
|
||||||
|
private String ttsLanguage;
|
||||||
|
|
||||||
|
@Schema(description = "TTS音量")
|
||||||
|
private Integer ttsVolume;
|
||||||
|
|
||||||
|
@Schema(description = "TTS语速")
|
||||||
|
private Integer ttsRate;
|
||||||
|
|
||||||
|
@Schema(description = "TTS音调")
|
||||||
|
private Integer ttsPitch;
|
||||||
|
|
||||||
@Schema(description = "记忆模型标识")
|
@Schema(description = "记忆模型标识")
|
||||||
private String memModelId;
|
private String memModelId;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("ai_agent_tag")
|
||||||
|
@Schema(description = "智能体标签")
|
||||||
|
public class AgentTagEntity {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "标签名称")
|
||||||
|
private String tagName;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@Schema(description = "创建者")
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新者")
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@Schema(description = "删除标记")
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("ai_agent_tag_relation")
|
||||||
|
@Schema(description = "智能体标签关联")
|
||||||
|
public class AgentTagRelationEntity {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
@Schema(description = "主键")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "智能体ID")
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
@Schema(description = "标签ID")
|
||||||
|
private String tagId;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@Schema(description = "创建者")
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新者")
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updatedAt;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package xiaozhi.modules.agent.entity;
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
@@ -64,6 +65,26 @@ public class AgentTemplateEntity implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String ttsVoiceId;
|
private String ttsVoiceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色语言
|
||||||
|
*/
|
||||||
|
private String ttsLanguage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS音量
|
||||||
|
*/
|
||||||
|
private Integer ttsVolume;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS语速
|
||||||
|
*/
|
||||||
|
private Integer ttsRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS音调
|
||||||
|
*/
|
||||||
|
private Integer ttsPitch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记忆模型标识
|
* 记忆模型标识
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -54,9 +54,11 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* 获取用户智能体列表
|
* 获取用户智能体列表
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
|
* @param keyword 搜索关键词
|
||||||
|
* @param searchType 搜索类型(name - 按名称搜索,mac - 按MAC地址搜索)
|
||||||
* @return 智能体列表
|
* @return 智能体列表
|
||||||
*/
|
*/
|
||||||
List<AgentDTO> getUserAgents(Long userId);
|
List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据智能体ID获取设备数量
|
* 根据智能体ID获取设备数量
|
||||||
@@ -98,4 +100,6 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* @return 创建的智能体ID
|
* @return 创建的智能体ID
|
||||||
*/
|
*/
|
||||||
String createAgent(AgentCreateDTO dto);
|
String createAgent(AgentCreateDTO dto);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
|
|
||||||
|
public interface AgentTagService {
|
||||||
|
|
||||||
|
AgentTagEntity saveTag(String tagName);
|
||||||
|
|
||||||
|
void deleteTag(String tagId);
|
||||||
|
|
||||||
|
List<AgentTagDTO> getTagsByAgentId(String agentId);
|
||||||
|
|
||||||
|
void saveAgentTags(String agentId, List<String> tagIds, List<String> tagNames);
|
||||||
|
|
||||||
|
void deleteAgentTags(String agentId);
|
||||||
|
|
||||||
|
List<AgentTagDTO> getTagsByAgentIds(List<String> agentIds);
|
||||||
|
|
||||||
|
List<AgentTagDTO> getAllTags();
|
||||||
|
|
||||||
|
List<String> getAgentIdsByTagName(String tagName);
|
||||||
|
}
|
||||||
+8
-7
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ import xiaozhi.common.constant.Constant;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.agent.Enums.AgentChatHistoryType;
|
import xiaozhi.modules.agent.Enums.AgentChatHistoryType;
|
||||||
import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
|
import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||||
@@ -86,13 +88,12 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
if (deleteAudio) {
|
if (deleteAudio) {
|
||||||
// 分批删除音频,避免超时
|
// 分批删除音频,避免超时
|
||||||
List<String> audioIds = baseMapper.getAudioIdsByAgentId(agentId);
|
List<String> audioIds = baseMapper.getAudioIdsByAgentId(agentId);
|
||||||
if (audioIds != null && !audioIds.isEmpty()) {
|
if (ToolUtil.isNotEmpty(audioIds)) {
|
||||||
int batchSize = 1000; // 每批删除1000条
|
// 每批删除1000条
|
||||||
for (int i = 0; i < audioIds.size(); i += batchSize) {
|
List<List<String>> batch = ListUtil.split(audioIds, 1000);
|
||||||
int end = Math.min(i + batchSize, audioIds.size());
|
batch.forEach(dataList->{
|
||||||
List<String> batch = audioIds.subList(i, end);
|
baseMapper.deleteAudioByIds(dataList);
|
||||||
baseMapper.deleteAudioByIds(batch);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deleteAudio && !deleteText) {
|
if (deleteAudio && !deleteText) {
|
||||||
|
|||||||
+16
-16
@@ -78,11 +78,11 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
// 4. 生成总结(generateSummaryFromMessages方法已包含长度限制逻辑)
|
// 4. 生成总结(generateSummaryFromMessages方法已包含长度限制逻辑)
|
||||||
String summary = generateSummaryFromMessages(meaningfulMessages, agentId);
|
String summary = generateSummaryFromMessages(meaningfulMessages, agentId);
|
||||||
|
|
||||||
System.out.println("成功生成会话 " + sessionId + " 的聊天记录总结,长度: " + summary.length() + " 字符");
|
log.info("成功生成会话 {} 的聊天记录总结,长度: {} 字符", sessionId, summary.length());
|
||||||
return new AgentChatSummaryDTO(sessionId, agentId, summary);
|
return new AgentChatSummaryDTO(sessionId, agentId, summary);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("生成会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage());
|
log.error("生成会话 {} 的聊天记录总结时发生错误: {}", sessionId, e.getMessage());
|
||||||
return new AgentChatSummaryDTO(sessionId, "生成总结时发生错误: " + e.getMessage());
|
return new AgentChatSummaryDTO(sessionId, "生成总结时发生错误: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,14 +93,14 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
// 1. 生成总结
|
// 1. 生成总结
|
||||||
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
||||||
if (!summaryDTO.isSuccess()) {
|
if (!summaryDTO.isSuccess()) {
|
||||||
System.err.println("生成总结失败: " + summaryDTO.getErrorMessage());
|
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取设备信息(通过会话关联的设备)
|
// 2. 获取设备信息(通过会话关联的设备)
|
||||||
DeviceEntity device = getDeviceBySessionId(sessionId);
|
DeviceEntity device = getDeviceBySessionId(sessionId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
System.err.println("未找到与会话 " + sessionId + " 关联的设备");
|
log.info("未找到与会话 {} 关联的设备", sessionId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,11 +116,11 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
System.out.println("成功保存会话 " + sessionId + " 的聊天记录总结到智能体 " + device.getAgentId());
|
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, device.getAgentId());
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("保存会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage());
|
log.error("保存会话 {} 的聊天记录总结时发生错误: {}", sessionId, e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
}
|
}
|
||||||
return agentChatHistoryService.getChatHistoryBySessionId(agentId, sessionId);
|
return agentChatHistoryService.getChatHistoryBySessionId(agentId, sessionId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取会话 " + sessionId + " 的聊天记录失败: " + e.getMessage());
|
log.error("获取会话 {} 的聊天记录失败: {}", sessionId, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper);
|
AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper);
|
||||||
return entity != null ? entity.getAgentId() : null;
|
return entity != null ? entity.getAgentId() : null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("根据会话ID " + sessionId + " 查找智能体ID失败: " + e.getMessage());
|
log.error("根据会话ID {} 查找智能体ID失败: {}", sessionId, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("调用Java端LLM服务失败: " + e.getMessage());
|
log.error("调用Java端LLM服务失败: {}", e.getMessage());
|
||||||
throw new RuntimeException("LLM服务不可用,无法生成聊天总结");
|
throw new RuntimeException("LLM服务不可用,无法生成聊天总结");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +295,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
// 返回智能体的当前总结记忆
|
// 返回智能体的当前总结记忆
|
||||||
return agentInfo.getSummaryMemory();
|
return agentInfo.getSummaryMemory();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取智能体历史记忆失败,agentId: " + agentId + ", 错误: " + e.getMessage());
|
log.error("获取智能体历史记忆失败,agentId: {}, 错误: {}", agentId, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
String modelId = getMemorySummaryModelId(agentId);
|
String modelId = getMemorySummaryModelId(agentId);
|
||||||
|
|
||||||
if (StringUtils.isBlank(modelId)) {
|
if (StringUtils.isBlank(modelId)) {
|
||||||
System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
||||||
return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null);
|
return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
|
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage());
|
log.error("调用Java端LLM服务异常,agentId: {}, 错误: {}", agentId, e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
String modelId = getMemorySummaryModelId(agentId);
|
String modelId = getMemorySummaryModelId(agentId);
|
||||||
|
|
||||||
if (StringUtils.isBlank(modelId)) {
|
if (StringUtils.isBlank(modelId)) {
|
||||||
System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
||||||
return llmService.generateSummary(conversation);
|
return llmService.generateSummary(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +351,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
|
throw new RuntimeException("Java端LLM服务返回异常: " + summary);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage());
|
log.error("调用Java端LLM服务异常,agentId: {}, 错误: {}", agentId, e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,7 +394,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
|
|
||||||
return llmModelId;
|
return llmModelId;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取记忆总结LLM模型ID失败,agentId: " + agentId + ", 错误: " + e.getMessage());
|
log.error("获取记忆总结LLM模型ID失败,agentId: {}, 错误: {}", agentId, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("根据会话ID " + sessionId + " 查找设备信息失败: " + e.getMessage());
|
log.error("根据会话ID {} 查找设备信息失败: {}", sessionId, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -147,6 +147,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
|||||||
List<String> result = toolsList.stream()
|
List<String> result = toolsList.stream()
|
||||||
.map(tool -> (String) tool.get("name"))
|
.map(tool -> (String) tool.get("name"))
|
||||||
.filter(name -> name != null)
|
.filter(name -> name != null)
|
||||||
|
.sorted()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size());
|
log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size());
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+103
-28
@@ -5,6 +5,7 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -28,20 +29,26 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
|||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.agent.dao.AgentDao;
|
import xiaozhi.modules.agent.dao.AgentDao;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
import xiaozhi.modules.agent.entity.AgentContextProviderEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTagService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||||
import xiaozhi.modules.model.dto.VoiceDTO;
|
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||||
@@ -56,6 +63,7 @@ import xiaozhi.modules.timbre.service.TimbreService;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
||||||
private final AgentDao agentDao;
|
private final AgentDao agentDao;
|
||||||
|
private final AgentTagDao agentTagDao;
|
||||||
private final TimbreService timbreModelService;
|
private final TimbreService timbreModelService;
|
||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
@@ -65,6 +73,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
private final AgentTemplateService agentTemplateService;
|
private final AgentTemplateService agentTemplateService;
|
||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
private final AgentContextProviderService agentContextProviderService;
|
private final AgentContextProviderService agentContextProviderService;
|
||||||
|
private final AgentTagService agentTagService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||||
@@ -84,9 +93,9 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
||||||
if (agent.getChatHistoryConf() == null) {
|
}
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
|
if (agent.getChatHistoryConf() == null) {
|
||||||
}
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询上下文源配置
|
// 查询上下文源配置
|
||||||
@@ -127,38 +136,91 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AgentDTO> getUserAgents(Long userId) {
|
public List<AgentDTO> getUserAgents(Long userId, String keyword, String searchType) {
|
||||||
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
|
QueryWrapper<AgentEntity> queryWrapper = new QueryWrapper<>();
|
||||||
wrapper.eq("user_id", userId);
|
queryWrapper.eq("user_id", userId).orderByDesc("created_at");
|
||||||
List<AgentEntity> agents = agentDao.selectList(wrapper);
|
|
||||||
return agents.stream().map(agent -> {
|
|
||||||
AgentDTO dto = new AgentDTO();
|
|
||||||
dto.setId(agent.getId());
|
|
||||||
dto.setAgentName(agent.getAgentName());
|
|
||||||
dto.setSystemPrompt(agent.getSystemPrompt());
|
|
||||||
|
|
||||||
// 获取 TTS 模型名称
|
// 如果有搜索关键词,根据搜索类型添加相应的查询条件
|
||||||
dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId()));
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
|
if ("mac".equals(searchType)) {
|
||||||
|
// 按MAC地址搜索:先搜索设备,再获取对应的智能体
|
||||||
|
List<DeviceEntity> devices = Optional
|
||||||
|
.ofNullable(deviceService.searchDevicesByMacAddress(keyword, userId)).orElseGet(ArrayList::new);
|
||||||
|
// 获取设备对应的智能体ID列表
|
||||||
|
List<String> agentIds = devices.stream()
|
||||||
|
.map(DeviceEntity::getAgentId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (ToolUtil.isNotEmpty(agentIds)) {
|
||||||
|
queryWrapper.in("id", agentIds);
|
||||||
|
} else {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 按名称搜索(默认):同时搜索智能体名称和标签名
|
||||||
|
List<String> tagAgentIds = agentTagService.getAgentIdsByTagName(keyword);
|
||||||
|
if (ToolUtil.isNotEmpty(tagAgentIds)) {
|
||||||
|
queryWrapper.and(wrapper -> wrapper
|
||||||
|
.like("agent_name", keyword)
|
||||||
|
.or()
|
||||||
|
.in("id", tagAgentIds));
|
||||||
|
} else {
|
||||||
|
queryWrapper.like("agent_name", keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 LLM 模型名称
|
// 执行查询
|
||||||
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
|
List<AgentEntity> agentEntities = baseDao.selectList(queryWrapper);
|
||||||
|
|
||||||
// 获取 VLLM 模型名称
|
// 转换为DTO并设置所有必要字段
|
||||||
dto.setVllmModelName(modelConfigService.getModelNameById(agent.getVllmModelId()));
|
return agentEntities.stream().map(this::buildAgentDTO).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
// 获取记忆模型名称
|
/**
|
||||||
dto.setMemModelId(agent.getMemModelId());
|
* 将AgentEntity转换为AgentDTO
|
||||||
|
*/
|
||||||
|
private AgentDTO buildAgentDTO(AgentEntity agent) {
|
||||||
|
AgentDTO dto = new AgentDTO();
|
||||||
|
dto.setId(agent.getId());
|
||||||
|
dto.setAgentName(agent.getAgentName());
|
||||||
|
dto.setSystemPrompt(agent.getSystemPrompt());
|
||||||
|
|
||||||
// 获取 TTS 音色名称
|
// 获取 TTS 模型名称
|
||||||
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
|
dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId()));
|
||||||
|
|
||||||
// 获取智能体最近的最后连接时长
|
// 获取 LLM 模型名称
|
||||||
dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId()));
|
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
|
||||||
|
|
||||||
// 获取设备数量
|
// 获取 VLLM 模型名称
|
||||||
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
|
dto.setVllmModelName(modelConfigService.getModelNameById(agent.getVllmModelId()));
|
||||||
return dto;
|
|
||||||
}).collect(Collectors.toList());
|
// 获取记忆模型名称
|
||||||
|
dto.setMemModelId(agent.getMemModelId());
|
||||||
|
|
||||||
|
// 获取 TTS 音色名称
|
||||||
|
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
|
||||||
|
|
||||||
|
// 获取智能体最近的最后连接时长
|
||||||
|
dto.setLastConnectedAt(deviceService.getLatestLastConnectionTime(agent.getId()));
|
||||||
|
|
||||||
|
// 获取设备数量
|
||||||
|
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
|
||||||
|
|
||||||
|
// 获取标签列表
|
||||||
|
List<AgentTagEntity> tags = agentTagDao.selectByAgentId(agent.getId());
|
||||||
|
if (ToolUtil.isNotEmpty(tags)) {
|
||||||
|
dto.setTags(tags.stream().map(this::convertTagToDTO).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentTagDTO convertTagToDTO(AgentTagEntity entity) {
|
||||||
|
AgentTagDTO dto = new AgentTagDTO();
|
||||||
|
dto.setId(entity.getId());
|
||||||
|
dto.setTagName(entity.getTagName());
|
||||||
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -247,6 +309,18 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
if (dto.getTtsVoiceId() != null) {
|
if (dto.getTtsVoiceId() != null) {
|
||||||
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
||||||
}
|
}
|
||||||
|
if (dto.getTtsLanguage() != null) {
|
||||||
|
existingEntity.setTtsLanguage(dto.getTtsLanguage());
|
||||||
|
}
|
||||||
|
if (dto.getTtsVolume() != null) {
|
||||||
|
existingEntity.setTtsVolume(dto.getTtsVolume());
|
||||||
|
}
|
||||||
|
if (dto.getTtsRate() != null) {
|
||||||
|
existingEntity.setTtsRate(dto.getTtsRate());
|
||||||
|
}
|
||||||
|
if (dto.getTtsPitch() != null) {
|
||||||
|
existingEntity.setTtsPitch(dto.getTtsPitch());
|
||||||
|
}
|
||||||
if (dto.getMemModelId() != null) {
|
if (dto.getMemModelId() != null) {
|
||||||
existingEntity.setMemModelId(dto.getMemModelId());
|
existingEntity.setMemModelId(dto.getMemModelId());
|
||||||
}
|
}
|
||||||
@@ -468,4 +542,5 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
agentPluginMappingService.saveBatch(toInsert);
|
agentPluginMappingService.saveBatch(toInsert);
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+198
@@ -0,0 +1,198 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentTagRelationDao;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentTagDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTagRelationEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTagService;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AgentTagServiceImpl extends BaseServiceImpl<AgentTagDao, AgentTagEntity> implements AgentTagService {
|
||||||
|
|
||||||
|
private final AgentTagRelationDao agentTagRelationDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AgentTagEntity saveTag(String tagName) {
|
||||||
|
if (tagName == null || tagName.trim().isEmpty()) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_TAG_NAME_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryWrapper<AgentTagEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("tag_name", tagName);
|
||||||
|
wrapper.eq("deleted", 0);
|
||||||
|
AgentTagEntity existTag = baseDao.selectOne(wrapper);
|
||||||
|
if (existTag != null) {
|
||||||
|
return existTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
AgentTagEntity tag = new AgentTagEntity();
|
||||||
|
tag.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
tag.setTagName(tagName);
|
||||||
|
tag.setSort(0);
|
||||||
|
tag.setCreatedAt(new Date());
|
||||||
|
tag.setUpdatedAt(new Date());
|
||||||
|
tag.setDeleted(0);
|
||||||
|
baseDao.insert(tag);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteTag(String tagId) {
|
||||||
|
AgentTagEntity tag = baseDao.selectById(tagId);
|
||||||
|
if (tag != null) {
|
||||||
|
tag.setDeleted(1);
|
||||||
|
tag.setUpdatedAt(new Date());
|
||||||
|
baseDao.updateById(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentTagDTO> getTagsByAgentId(String agentId) {
|
||||||
|
List<AgentTagEntity> tags = baseDao.selectByAgentId(agentId);
|
||||||
|
return tags.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void saveAgentTags(String agentId, List<String> tagIds, List<String> tagNames) {
|
||||||
|
agentTagRelationDao.deleteByAgentId(agentId);
|
||||||
|
|
||||||
|
List<AgentTagEntity> currentTags = baseDao.selectByAgentId(agentId);
|
||||||
|
List<String> currentTagNames = currentTags.stream()
|
||||||
|
.map(AgentTagEntity::getTagName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<String> allTagIds = new ArrayList<>();
|
||||||
|
List<String> newTagNames = new ArrayList<>();
|
||||||
|
|
||||||
|
if (tagNames != null && !tagNames.isEmpty()) {
|
||||||
|
Set<String> addedTagNames = new HashSet<>();
|
||||||
|
for (String tagName : tagNames) {
|
||||||
|
if (tagName == null || tagName.trim().isEmpty()) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_TAG_NAME_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTagNames.contains(tagName) || addedTagNames.contains(tagName)) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_TAG_NAME_DUPLICATE);
|
||||||
|
}
|
||||||
|
addedTagNames.add(tagName);
|
||||||
|
newTagNames.add(tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AgentTagEntity> existTags = new ArrayList<>();
|
||||||
|
if (!newTagNames.isEmpty()) {
|
||||||
|
existTags = baseDao.selectByTagNames(newTagNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, AgentTagEntity> existTagMap = existTags.stream()
|
||||||
|
.collect(Collectors.toMap(AgentTagEntity::getTagName, t -> t, (a, b) -> a));
|
||||||
|
|
||||||
|
List<AgentTagEntity> tagsToInsert = new ArrayList<>();
|
||||||
|
for (String tagName : newTagNames) {
|
||||||
|
AgentTagEntity existTag = existTagMap.get(tagName);
|
||||||
|
if (existTag != null) {
|
||||||
|
allTagIds.add(existTag.getId());
|
||||||
|
} else {
|
||||||
|
AgentTagEntity tag = new AgentTagEntity();
|
||||||
|
tag.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
tag.setTagName(tagName);
|
||||||
|
tag.setSort(0);
|
||||||
|
tag.setDeleted(0);
|
||||||
|
tag.setCreatedAt(new Date());
|
||||||
|
tag.setUpdatedAt(new Date());
|
||||||
|
tagsToInsert.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tagsToInsert.isEmpty()) {
|
||||||
|
baseDao.batchInsert(tagsToInsert);
|
||||||
|
for (AgentTagEntity tag : tagsToInsert) {
|
||||||
|
allTagIds.add(tag.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagIds != null && !tagIds.isEmpty()) {
|
||||||
|
List<AgentTagEntity> tagIdEntities = baseDao.selectBatchIds(tagIds);
|
||||||
|
for (AgentTagEntity tag : tagIdEntities) {
|
||||||
|
if (tag != null && (currentTagNames.contains(tag.getTagName()) ||
|
||||||
|
newTagNames.contains(tag.getTagName()))) {
|
||||||
|
throw new RenException(ErrorCode.AGENT_TAG_NAME_DUPLICATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allTagIds.addAll(tagIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allTagIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AgentTagRelationEntity> relations = new ArrayList<>();
|
||||||
|
Date now = new Date();
|
||||||
|
int sort = 0;
|
||||||
|
for (String tagId : allTagIds) {
|
||||||
|
AgentTagRelationEntity relation = new AgentTagRelationEntity();
|
||||||
|
relation.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
relation.setAgentId(agentId);
|
||||||
|
relation.setTagId(tagId);
|
||||||
|
relation.setSort(sort++);
|
||||||
|
relation.setCreatedAt(now);
|
||||||
|
relation.setUpdatedAt(now);
|
||||||
|
relations.add(relation);
|
||||||
|
}
|
||||||
|
agentTagRelationDao.batchInsertRelation(relations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteAgentTags(String agentId) {
|
||||||
|
agentTagRelationDao.deleteByAgentId(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentTagDTO> getTagsByAgentIds(List<String> agentIds) {
|
||||||
|
if (agentIds == null || agentIds.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<AgentTagEntity> tags = baseDao.selectByAgentIds(agentIds);
|
||||||
|
return tags.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentTagDTO> getAllTags() {
|
||||||
|
List<AgentTagEntity> tags = baseDao.selectAll();
|
||||||
|
return tags.stream().map(this::convertToDTO).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAgentIdsByTagName(String tagName) {
|
||||||
|
return baseDao.selectAgentIdsByTagName(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentTagDTO convertToDTO(AgentTagEntity entity) {
|
||||||
|
AgentTagDTO dto = new AgentTagDTO();
|
||||||
|
dto.setId(entity.getId());
|
||||||
|
dto.setTagName(entity.getTagName());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
-3
@@ -87,6 +87,10 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
agent.getVadModelId(),
|
agent.getVadModelId(),
|
||||||
agent.getAsrModelId(),
|
agent.getAsrModelId(),
|
||||||
null,
|
null,
|
||||||
@@ -135,15 +139,24 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
String voice = null;
|
String voice = null;
|
||||||
String referenceAudio = null;
|
String referenceAudio = null;
|
||||||
String referenceText = null;
|
String referenceText = null;
|
||||||
|
String language = null;
|
||||||
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
|
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
|
||||||
if (timbre != null) {
|
if (timbre != null) {
|
||||||
voice = timbre.getTtsVoice();
|
voice = timbre.getTtsVoice();
|
||||||
referenceAudio = timbre.getReferenceAudio();
|
referenceAudio = timbre.getReferenceAudio();
|
||||||
referenceText = timbre.getReferenceText();
|
referenceText = timbre.getReferenceText();
|
||||||
|
// 优先使用用户选择的语言,如果没有则使用音色支持的第一个语言
|
||||||
|
if (StringUtils.isNotBlank(agent.getTtsLanguage())) {
|
||||||
|
language = agent.getTtsLanguage();
|
||||||
|
} else if (StringUtils.isNotBlank(timbre.getLanguages())) {
|
||||||
|
language = timbre.getLanguages().split("、")[0].trim();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
VoiceCloneEntity voice_print = cloneVoiceService.selectById(agent.getTtsVoiceId());
|
VoiceCloneEntity voice_print = cloneVoiceService.selectById(agent.getTtsVoiceId());
|
||||||
if (voice_print != null) {
|
if (voice_print != null) {
|
||||||
voice = voice_print.getVoiceId();
|
voice = voice_print.getVoiceId();
|
||||||
|
// 优先使用用户选择的语言,如果没有则使用默认值
|
||||||
|
language = StringUtils.isNotBlank(agent.getTtsLanguage()) ? agent.getTtsLanguage() : "普通话";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 构建返回数据
|
// 构建返回数据
|
||||||
@@ -163,11 +176,11 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
}
|
}
|
||||||
result.put("chat_history_conf", chatHistoryConf);
|
result.put("chat_history_conf", chatHistoryConf);
|
||||||
// 如果客户端已实例化模型,则不返回
|
// 如果客户端已实例化模型,则不返回
|
||||||
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
String alreadySelectedVadModelId = selectedModule.get("VAD");
|
||||||
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
||||||
agent.setVadModelId(null);
|
agent.setVadModelId(null);
|
||||||
}
|
}
|
||||||
String alreadySelectedAsrModelId = (String) selectedModule.get("ASR");
|
String alreadySelectedAsrModelId = selectedModule.get("ASR");
|
||||||
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
|
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
|
||||||
agent.setAsrModelId(null);
|
agent.setAsrModelId(null);
|
||||||
}
|
}
|
||||||
@@ -208,6 +221,10 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
voice,
|
voice,
|
||||||
referenceAudio,
|
referenceAudio,
|
||||||
referenceText,
|
referenceText,
|
||||||
|
language,
|
||||||
|
agent.getTtsVolume(),
|
||||||
|
agent.getTtsRate(),
|
||||||
|
agent.getTtsPitch(),
|
||||||
agent.getVadModelId(),
|
agent.getVadModelId(),
|
||||||
agent.getAsrModelId(),
|
agent.getAsrModelId(),
|
||||||
agent.getLlmModelId(),
|
agent.getLlmModelId(),
|
||||||
@@ -302,7 +319,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
private void buildVoiceprintConfig(String agentId, Map<String, Object> result) {
|
private void buildVoiceprintConfig(String agentId, Map<String, Object> result) {
|
||||||
try {
|
try {
|
||||||
// 获取声纹接口地址
|
// 获取声纹接口地址
|
||||||
String voiceprintUrl = sysParamsService.getValue("server.voice_print", true);
|
String voiceprintUrl = sysParamsService.getValue(Constant.SERVER_VOICE_PRINT, true);
|
||||||
if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) {
|
if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -385,6 +402,10 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
String voice,
|
String voice,
|
||||||
String referenceAudio,
|
String referenceAudio,
|
||||||
String referenceText,
|
String referenceText,
|
||||||
|
String language,
|
||||||
|
Integer ttsVolume,
|
||||||
|
Integer ttsRate,
|
||||||
|
Integer ttsPitch,
|
||||||
String vadModelId,
|
String vadModelId,
|
||||||
String asrModelId,
|
String asrModelId,
|
||||||
String llmModelId,
|
String llmModelId,
|
||||||
@@ -423,6 +444,14 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
((Map<String, Object>) model.getConfigJson()).put("ref_audio", referenceAudio);
|
((Map<String, Object>) model.getConfigJson()).put("ref_audio", referenceAudio);
|
||||||
if (referenceText != null)
|
if (referenceText != null)
|
||||||
((Map<String, Object>) model.getConfigJson()).put("ref_text", referenceText);
|
((Map<String, Object>) model.getConfigJson()).put("ref_text", referenceText);
|
||||||
|
if (language != null)
|
||||||
|
((Map<String, Object>) model.getConfigJson()).put("language", language);
|
||||||
|
if (ttsVolume != null)
|
||||||
|
((Map<String, Object>) model.getConfigJson()).put("ttsVolume", ttsVolume);
|
||||||
|
if (ttsRate != null)
|
||||||
|
((Map<String, Object>) model.getConfigJson()).put("ttsRate", ttsRate);
|
||||||
|
if (ttsPitch != null)
|
||||||
|
((Map<String, Object>) model.getConfigJson()).put("ttsPitch", ttsPitch);
|
||||||
|
|
||||||
// 火山引擎声音克隆需要替换resource_id
|
// 火山引擎声音克隆需要替换resource_id
|
||||||
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
|
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
|
||||||
|
|||||||
+34
-85
@@ -1,14 +1,11 @@
|
|||||||
package xiaozhi.modules.device.controller;
|
package xiaozhi.modules.device.controller;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -16,9 +13,6 @@ import org.springframework.web.bind.annotation.PutMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -30,6 +24,7 @@ import xiaozhi.common.user.UserDetail;
|
|||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceToolsCallReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
@@ -44,16 +39,11 @@ public class DeviceController {
|
|||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final SysParamsService sysParamsService;
|
private final SysParamsService sysParamsService;
|
||||||
private final RestTemplate restTemplate;
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService,
|
public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService) {
|
||||||
RestTemplate restTemplate, ObjectMapper objectMapper) {
|
|
||||||
this.deviceService = deviceService;
|
this.deviceService = deviceService;
|
||||||
this.redisUtils = redisUtils;
|
this.redisUtils = redisUtils;
|
||||||
this.sysParamsService = sysParamsService;
|
this.sysParamsService = sysParamsService;
|
||||||
this.restTemplate = restTemplate;
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bind/{agentId}/{deviceCode}")
|
@PostMapping("/bind/{agentId}/{deviceCode}")
|
||||||
@@ -99,83 +89,12 @@ public class DeviceController {
|
|||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) {
|
public Result<String> forwardToMqttGateway(@PathVariable String agentId, @RequestBody String requestBody) {
|
||||||
try {
|
try {
|
||||||
// 从系统参数中获取MQTT网关地址
|
return new Result<String>().ok(deviceService.getDeviceOnlineData(agentId));
|
||||||
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
|
||||||
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
|
||||||
return new Result<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前用户的设备列表
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId(), agentId);
|
|
||||||
|
|
||||||
// 构建deviceIds数组
|
|
||||||
java.util.List<String> deviceIds = new java.util.ArrayList<>();
|
|
||||||
for (DeviceEntity device : devices) {
|
|
||||||
String macAddress = device.getMacAddress() != null ? device.getMacAddress() : "unknown";
|
|
||||||
String groupId = device.getBoard() != null ? device.getBoard() : "GID_default";
|
|
||||||
|
|
||||||
// 替换冒号为下划线
|
|
||||||
groupId = groupId.replace(":", "_");
|
|
||||||
macAddress = macAddress.replace(":", "_");
|
|
||||||
|
|
||||||
// 构建mqtt客户端ID格式:groupId@@@macAddress@@@macAddress
|
|
||||||
String mqttClientId = groupId + "@@@" + macAddress + "@@@" + macAddress;
|
|
||||||
deviceIds.add(mqttClientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建完整的URL
|
|
||||||
String url = "http://" + mqttGatewayUrl + "/api/devices/status";
|
|
||||||
|
|
||||||
// 设置请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set("Content-Type", "application/json");
|
|
||||||
|
|
||||||
// 生成Bearer令牌
|
|
||||||
String token = generateBearerToken();
|
|
||||||
if (token == null) {
|
|
||||||
return new Result<String>().error("令牌生成失败");
|
|
||||||
}
|
|
||||||
headers.set("Authorization", "Bearer " + token);
|
|
||||||
|
|
||||||
// 构建请求体JSON
|
|
||||||
String jsonBody = "{\"clientIds\":" + objectMapper.writeValueAsString(deviceIds) + "}";
|
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, headers);
|
|
||||||
|
|
||||||
// 发送POST请求
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
|
|
||||||
|
|
||||||
// 返回响应
|
|
||||||
return new Result<String>().ok(response.getBody());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return new Result<String>().error("转发请求失败: " + e.getMessage());
|
return new Result<String>().error("转发请求失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateBearerToken() {
|
|
||||||
try {
|
|
||||||
// 获取当前日期,格式为yyyy-MM-dd
|
|
||||||
String dateStr = java.time.LocalDate.now()
|
|
||||||
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
||||||
|
|
||||||
// 获取MQTT签名密钥
|
|
||||||
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
|
||||||
if (StringUtils.isBlank(signatureKey)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将日期字符串与MQTT_SIGNATURE_KEY连接
|
|
||||||
String tokenContent = dateStr + signatureKey;
|
|
||||||
|
|
||||||
// 对连接后的字符串进行SHA256哈希计算
|
|
||||||
String token = org.apache.commons.codec.digest.DigestUtils.sha256Hex(tokenContent);
|
|
||||||
|
|
||||||
return token;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/unbind")
|
@PostMapping("/unbind")
|
||||||
@Operation(summary = "解绑设备")
|
@Operation(summary = "解绑设备")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
@@ -210,4 +129,34 @@ public class DeviceController {
|
|||||||
deviceService.manualAddDevice(user.getId(), dto);
|
deviceService.manualAddDevice(user.getId(), dto);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tools/list/{deviceId}")
|
||||||
|
@Operation(summary = "获取设备工具列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Object> getDeviceTools(@PathVariable String deviceId) {
|
||||||
|
Object toolsData = deviceService.getDeviceTools(deviceId);
|
||||||
|
if (toolsData == null) {
|
||||||
|
return new Result<Object>().error(ErrorCode.DEVICE_NOT_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result<Object>().ok(toolsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tools/call/{deviceId}")
|
||||||
|
@Operation(summary = "调用设备工具")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Object> callDeviceTool(@PathVariable String deviceId,
|
||||||
|
@Valid @RequestBody DeviceToolsCallReqDTO request) {
|
||||||
|
String toolName = request.getName();
|
||||||
|
Map<String, Object> arguments = request.getArguments();
|
||||||
|
|
||||||
|
Object result = deviceService.callDeviceTool(deviceId, toolName, arguments);
|
||||||
|
if (result == null) {
|
||||||
|
return new Result<Object>().error(ErrorCode.DEVICE_NOT_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Object> response = new Result<Object>();
|
||||||
|
response.setMsg("Tools called successfully");
|
||||||
|
return response.ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+59
-9
@@ -7,7 +7,9 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -36,6 +38,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
@@ -43,8 +46,11 @@ import xiaozhi.common.utils.Result;
|
|||||||
import xiaozhi.common.validator.ValidatorUtils;
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
import xiaozhi.modules.device.entity.OtaEntity;
|
import xiaozhi.modules.device.entity.OtaEntity;
|
||||||
import xiaozhi.modules.device.service.OtaService;
|
import xiaozhi.modules.device.service.OtaService;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@Tag(name = "设备管理", description = "OTA 相关接口")
|
@Tag(name = "固件升级管理", description = "OTA 相关接口")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -53,6 +59,7 @@ public class OTAMagController {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(OTAController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OTAController.class);
|
||||||
private final OtaService otaService;
|
private final OtaService otaService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "分页查询 OTA 固件信息")
|
@Operation(summary = "分页查询 OTA 固件信息")
|
||||||
@@ -145,15 +152,11 @@ public class OTAMagController {
|
|||||||
|
|
||||||
// 检查下载次数
|
// 检查下载次数
|
||||||
String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid);
|
String downloadCountKey = RedisKeys.getOtaDownloadCountKey(uuid);
|
||||||
Integer downloadCount = (Integer) redisUtils.get(downloadCountKey);
|
Integer downloadCount = (Integer) Optional.ofNullable(redisUtils.get(downloadCountKey)).orElse(0);
|
||||||
if (downloadCount == null) {
|
|
||||||
downloadCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果下载次数超过3次,返回404
|
// 如果下载次数超过3次,返回404
|
||||||
if (downloadCount >= 3) {
|
if (downloadCount >= 3) {
|
||||||
redisUtils.delete(downloadCountKey);
|
redisUtils.delete(List.of(downloadCountKey, RedisKeys.getOtaIdKey(uuid)));
|
||||||
redisUtils.delete(RedisKeys.getOtaIdKey(uuid));
|
|
||||||
logger.warn("Download limit exceeded for UUID: {}", uuid);
|
logger.warn("Download limit exceeded for UUID: {}", uuid);
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
@@ -162,7 +165,17 @@ public class OTAMagController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取固件信息
|
// 获取固件信息
|
||||||
OtaEntity otaEntity = otaService.selectById(id);
|
OtaEntity otaEntity = null;
|
||||||
|
if (id.indexOf("file:") == 0) {
|
||||||
|
id = id.substring(5);
|
||||||
|
otaEntity = new OtaEntity();
|
||||||
|
otaEntity.setFirmwarePath(id);
|
||||||
|
otaEntity.setType("assets");
|
||||||
|
otaEntity.setVersion("1.0.0");
|
||||||
|
} else {
|
||||||
|
otaEntity = otaService.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) {
|
if (otaEntity == null || StringUtils.isBlank(otaEntity.getFirmwarePath())) {
|
||||||
logger.warn("Firmware not found or path is empty for ID: {}", id);
|
logger.warn("Firmware not found or path is empty for ID: {}", id);
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
@@ -170,6 +183,7 @@ public class OTAMagController {
|
|||||||
|
|
||||||
// 获取文件路径 - 确保路径是绝对路径或正确的相对路径
|
// 获取文件路径 - 确保路径是绝对路径或正确的相对路径
|
||||||
String firmwarePath = otaEntity.getFirmwarePath();
|
String firmwarePath = otaEntity.getFirmwarePath();
|
||||||
|
String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion();
|
||||||
Path path;
|
Path path;
|
||||||
|
|
||||||
// 检查是否是绝对路径
|
// 检查是否是绝对路径
|
||||||
@@ -203,7 +217,7 @@ public class OTAMagController {
|
|||||||
byte[] fileContent = Files.readAllBytes(path);
|
byte[] fileContent = Files.readAllBytes(path);
|
||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
String originalFilename = otaEntity.getType() + "_" + otaEntity.getVersion();
|
|
||||||
if (firmwarePath.contains(".")) {
|
if (firmwarePath.contains(".")) {
|
||||||
String extension = firmwarePath.substring(firmwarePath.lastIndexOf("."));
|
String extension = firmwarePath.substring(firmwarePath.lastIndexOf("."));
|
||||||
originalFilename += extension;
|
originalFilename += extension;
|
||||||
@@ -279,6 +293,42 @@ public class OTAMagController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/uploadAssetsBin")
|
||||||
|
@Operation(summary = "上传资源固件文件")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<String> uploadAssetsBin(@RequestParam("file") MultipartFile file) {
|
||||||
|
String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true);
|
||||||
|
if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) {
|
||||||
|
return new Result<String>().error(ErrorCode.OTA_URL_EMPTY);
|
||||||
|
}
|
||||||
|
logger.info("username:{},uploadAssetsBin size: {}", SecurityUser.getUser().getUsername(), file.getSize());
|
||||||
|
// 验证文件大小 (资源固件最大20MB)
|
||||||
|
if (file.getSize() > 20 * 1024 * 1024) {
|
||||||
|
return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE);
|
||||||
|
}
|
||||||
|
// 普通用户只能每天上传50次
|
||||||
|
if (SecurityUser.getUser().getSuperAdmin() == SuperAdminEnum.NO.value()) {
|
||||||
|
String uploadCountKey = RedisKeys.getOtaUploadCountKey(SecurityUser.getUser().getId());
|
||||||
|
Integer uploadCount = (Integer) Optional.ofNullable(redisUtils.get(uploadCountKey)).orElse(0);
|
||||||
|
if (uploadCount >= 50) {
|
||||||
|
return new Result<String>().error(ErrorCode.OTA_UPLOAD_COUNT_EXCEED);
|
||||||
|
}
|
||||||
|
// 增加上传次数
|
||||||
|
redisUtils.increment(RedisKeys.getOtaUploadCountKey(SecurityUser.getUser().getId()),
|
||||||
|
RedisUtils.DEFAULT_EXPIRE);
|
||||||
|
}
|
||||||
|
Result<String> result = uploadFirmware(file);
|
||||||
|
|
||||||
|
// 生成资源文件路径
|
||||||
|
if (StringUtils.isNotBlank(result.getData())) {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
redisUtils.set(RedisKeys.getOtaIdKey(uuid), "file:" + result.getData());
|
||||||
|
String downloadUrl = otaUrl.replace("/ota/", "/otaMag/download/") + uuid;
|
||||||
|
result.setData(downloadUrl);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException {
|
private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException {
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
byte[] digest = md.digest(file.getBytes());
|
byte[] digest = md.digest(file.getBytes());
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package xiaozhi.modules.device.dto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DeviceToolsCallReqDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "工具名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Map<String, Object> arguments;
|
||||||
|
}
|
||||||
@@ -2,17 +2,22 @@ package xiaozhi.modules.device.service;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||||
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
|
|
||||||
public interface DeviceService extends BaseService<DeviceEntity> {
|
public interface DeviceService extends BaseService<DeviceEntity> {
|
||||||
|
/**
|
||||||
|
* 获取设备在线数据
|
||||||
|
*/
|
||||||
|
String getDeviceOnlineData(String agentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查设备是否激活
|
* 检查设备是否激活
|
||||||
@@ -83,6 +88,7 @@ public interface DeviceService extends BaseService<DeviceEntity> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取这个智能体设备理的最近的最后连接时间
|
* 获取这个智能体设备理的最近的最后连接时间
|
||||||
|
*
|
||||||
* @param agentId 智能体id
|
* @param agentId 智能体id
|
||||||
* @return 返回设备最近的最后连接时间
|
* @return 返回设备最近的最后连接时间
|
||||||
*/
|
*/
|
||||||
@@ -108,4 +114,23 @@ public interface DeviceService extends BaseService<DeviceEntity> {
|
|||||||
*/
|
*/
|
||||||
String generateWebSocketToken(String clientId, String username) throws Exception;
|
String generateWebSocketToken(String clientId, String username) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据MAC地址搜索设备
|
||||||
|
*
|
||||||
|
* @param macAddress MAC地址关键词
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 设备列表
|
||||||
|
*/
|
||||||
|
List<DeviceEntity> searchDevicesByMacAddress(String macAddress, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备工具列表
|
||||||
|
*/
|
||||||
|
Object getDeviceTools(String deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用设备工具
|
||||||
|
*/
|
||||||
|
Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments);
|
||||||
|
|
||||||
}
|
}
|
||||||
+290
-16
@@ -4,13 +4,17 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
@@ -26,7 +30,18 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.json.JSONArray;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -40,6 +55,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
|||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.DateUtils;
|
import xiaozhi.common.utils.DateUtils;
|
||||||
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.device.dao.DeviceDao;
|
import xiaozhi.modules.device.dao.DeviceDao;
|
||||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||||
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
@@ -88,16 +104,16 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
if (StringUtils.isBlank(activationCode)) {
|
if (StringUtils.isBlank(activationCode)) {
|
||||||
throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY);
|
throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY);
|
||||||
}
|
}
|
||||||
String deviceKey = "ota:activation:code:" + activationCode;
|
String deviceKey = RedisKeys.getOtaActivationCode(activationCode);
|
||||||
Object cacheDeviceId = redisUtils.get(deviceKey);
|
Object cacheDeviceId = redisUtils.get(deviceKey);
|
||||||
if (cacheDeviceId == null) {
|
if (ToolUtil.isEmpty(cacheDeviceId)) {
|
||||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
String deviceId = (String) cacheDeviceId;
|
String deviceId = (String) cacheDeviceId;
|
||||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||||
String cacheDeviceKey = String.format("ota:activation:data:%s", safeDeviceId);
|
String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
||||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
||||||
if (cacheMap == null) {
|
if (ToolUtil.isEmpty(cacheMap)) {
|
||||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||||
}
|
}
|
||||||
String cachedCode = (String) cacheMap.get("activation_code");
|
String cachedCode = (String) cacheMap.get("activation_code");
|
||||||
@@ -133,19 +149,56 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
deviceEntity.setLastConnectedAt(currentTime);
|
deviceEntity.setLastConnectedAt(currentTime);
|
||||||
deviceDao.insert(deviceEntity);
|
deviceDao.insert(deviceEntity);
|
||||||
|
|
||||||
// 清理redis缓存
|
// 清理redis缓存、清除智能体设备数量缓存
|
||||||
redisUtils.delete(cacheDeviceKey);
|
redisUtils.delete(List.of(cacheDeviceKey, deviceKey, RedisKeys.getAgentDeviceCountById(agentId)));
|
||||||
redisUtils.delete(deviceKey);
|
|
||||||
|
|
||||||
// 添加:清除智能体设备数量缓存
|
|
||||||
redisUtils.delete(RedisKeys.getAgentDeviceCountById(agentId));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备在线数据
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId,
|
public String getDeviceOnlineData(String agentId) {
|
||||||
DeviceReportReqDTO deviceReport) {
|
// 从系统参数中获取MQTT网关地址
|
||||||
|
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||||
|
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 构建完整的URL
|
||||||
|
String url = StrUtil.format("http://{}/api/devices/status", mqttGatewayUrl);
|
||||||
|
|
||||||
|
// 获取当前用户的设备列表
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
List<DeviceEntity> devices = getUserDevices(user.getId(), agentId);
|
||||||
|
|
||||||
|
// 构建deviceIds数组
|
||||||
|
Set<String> deviceIds = devices.stream().map(o -> {
|
||||||
|
String macAddress = Optional.ofNullable(o.getMacAddress()).orElse("unknown").replace(":", "_");
|
||||||
|
String groupId = Optional.ofNullable(o.getBoard()).orElse("GID_default").replace(":", "_");
|
||||||
|
return StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 构建请求入参
|
||||||
|
Map<String, Set<String>> params = MapUtil
|
||||||
|
.builder(new HashMap<String, Set<String>>())
|
||||||
|
.put("clientIds", deviceIds).build();
|
||||||
|
|
||||||
|
if (ToolUtil.isNotEmpty(deviceIds)) {
|
||||||
|
// 发送请求
|
||||||
|
String resultMessage = HttpRequest.post(url)
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
||||||
|
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
||||||
|
.body(JSONUtil.toJsonStr(params))
|
||||||
|
.timeout(10000) // 超时,毫秒
|
||||||
|
.execute().body();
|
||||||
|
return resultMessage;
|
||||||
|
}
|
||||||
|
// 返回响应
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceReportRespDTO checkDeviceActive(String macAddress, String clientId, DeviceReportReqDTO deviceReport) {
|
||||||
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
||||||
response.setServer_time(buildServerTime());
|
response.setServer_time(buildServerTime());
|
||||||
|
|
||||||
@@ -356,8 +409,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
|
|
||||||
private String getDeviceCacheKey(String deviceId) {
|
private String getDeviceCacheKey(String deviceId) {
|
||||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||||
String dataKey = String.format("ota:activation:data:%s", safeDeviceId);
|
return RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
||||||
return dataKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) {
|
public DeviceReportRespDTO.Activation buildActivation(String deviceId, DeviceReportReqDTO deviceReport) {
|
||||||
@@ -396,7 +448,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
redisUtils.set(dataKey, dataMap);
|
redisUtils.set(dataKey, dataMap);
|
||||||
|
|
||||||
// 写入反查激活码 key
|
// 写入反查激活码 key
|
||||||
String codeKey = "ota:activation:code:" + newCode;
|
String codeKey = RedisKeys.getOtaActivationCode(newCode);
|
||||||
redisUtils.set(codeKey, deviceId);
|
redisUtils.set(codeKey, deviceId);
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
@@ -496,6 +548,14 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
redisUtils.delete(RedisKeys.getAgentDeviceCountById(dto.getAgentId()));
|
redisUtils.delete(RedisKeys.getAgentDeviceCountById(dto.getAgentId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceEntity> searchDevicesByMacAddress(String macAddress, Long userId) {
|
||||||
|
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.like("mac_address", macAddress);
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
return deviceDao.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成MQTT密码签名
|
* 生成MQTT密码签名
|
||||||
*
|
*
|
||||||
@@ -598,4 +658,218 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
|
|
||||||
return mqtt;
|
return mqtt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成BearerToken
|
||||||
|
*/
|
||||||
|
private String generateBearerToken() {
|
||||||
|
try {
|
||||||
|
String dateStr = DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN);
|
||||||
|
String signatureKey = sysParamsService.getValue(Constant.SERVER_MQTT_SECRET, false);
|
||||||
|
if (ToolUtil.isEmpty(signatureKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return DigestUtil.sha256Hex(dateStr + signatureKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getDeviceTools(String deviceId) {
|
||||||
|
// 从系统参数中获取MQTT网关地址
|
||||||
|
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||||
|
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备信息
|
||||||
|
DeviceEntity device = baseDao.selectById(deviceId);
|
||||||
|
if (device == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查设备是否属于当前用户
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
if (!device.getUserId().equals(user.getId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建clientId
|
||||||
|
String macAddress = Optional.ofNullable(device.getMacAddress()).orElse("unknown").replace(":", "_");
|
||||||
|
String groupId = Optional.ofNullable(device.getBoard()).orElse("GID_default").replace(":", "_");
|
||||||
|
String clientId = StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
|
||||||
|
|
||||||
|
// 构建完整的URL
|
||||||
|
String url = StrUtil.format("http://{}/api/commands/{}", mqttGatewayUrl, clientId);
|
||||||
|
|
||||||
|
// 存储所有工具列表
|
||||||
|
List<Object> allTools = new ArrayList<>();
|
||||||
|
String cursor = null;
|
||||||
|
|
||||||
|
// 循环获取分页数据
|
||||||
|
while (true) {
|
||||||
|
// 构建params
|
||||||
|
Map<String, Object> paramsMap = MapUtil.builder(new HashMap<String, Object>())
|
||||||
|
.put("withUserTools", true)
|
||||||
|
.build();
|
||||||
|
// 如果有cursor,添加到请求参数中
|
||||||
|
if (StringUtils.isNotBlank(cursor)) {
|
||||||
|
paramsMap.put("cursor", cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
Map<String, Object> payload = MapUtil
|
||||||
|
.builder(new HashMap<String, Object>())
|
||||||
|
.put("jsonrpc", "2.0")
|
||||||
|
.put("id", 2)
|
||||||
|
.put("method", "tools/list")
|
||||||
|
.put("params", paramsMap)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, Object> requestBody = MapUtil
|
||||||
|
.builder(new HashMap<String, Object>())
|
||||||
|
.put("type", "mcp")
|
||||||
|
.put("payload", payload)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
String resultMessage = HttpRequest.post(url)
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
||||||
|
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
||||||
|
.body(JSONUtil.toJsonStr(requestBody))
|
||||||
|
.timeout(10000) // 超时,毫秒
|
||||||
|
.execute().body();
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
if (StringUtils.isBlank(resultMessage)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject jsonObject = JSONUtil.parseObj(resultMessage);
|
||||||
|
if (!jsonObject.getBool("success", false)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = jsonObject.getJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前页的工具列表
|
||||||
|
JSONArray tools = data.getJSONArray("tools");
|
||||||
|
if (tools != null && !tools.isEmpty()) {
|
||||||
|
allTools.addAll(tools);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取下一页的cursor
|
||||||
|
String nextCursor = data.getStr("nextCursor");
|
||||||
|
if (StringUtils.isBlank(nextCursor)) {
|
||||||
|
// 没有下一页了
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor = nextCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建返回结果
|
||||||
|
if (allTools.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultData = new HashMap<>();
|
||||||
|
resultData.put("tools", allTools);
|
||||||
|
return resultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments) {
|
||||||
|
// 从系统参数中获取MQTT网关地址
|
||||||
|
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||||
|
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备信息
|
||||||
|
DeviceEntity device = baseDao.selectById(deviceId);
|
||||||
|
if (device == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查设备是否属于当前用户
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
if (!device.getUserId().equals(user.getId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建clientId
|
||||||
|
String macAddress = Optional.ofNullable(device.getMacAddress()).orElse("unknown").replace(":", "_");
|
||||||
|
String groupId = Optional.ofNullable(device.getBoard()).orElse("GID_default").replace(":", "_");
|
||||||
|
String clientId = StrUtil.format("{}@@@{}@@@{}", groupId, macAddress, macAddress);
|
||||||
|
|
||||||
|
// 构建完整的URL
|
||||||
|
String url = StrUtil.format("http://{}/api/commands/{}", mqttGatewayUrl, clientId);
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
Map<String, Object> params = MapUtil
|
||||||
|
.builder(new HashMap<String, Object>())
|
||||||
|
.put("name", toolName)
|
||||||
|
.put("arguments", arguments)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, Object> payload = MapUtil
|
||||||
|
.builder(new HashMap<String, Object>())
|
||||||
|
.put("jsonrpc", "2.0")
|
||||||
|
.put("id", 2)
|
||||||
|
.put("method", "tools/call")
|
||||||
|
.put("params", params)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, Object> requestBody = MapUtil
|
||||||
|
.builder(new HashMap<String, Object>())
|
||||||
|
.put("type", "mcp")
|
||||||
|
.put("payload", payload)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
String resultMessage = HttpRequest.post(url)
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
||||||
|
.header(Header.AUTHORIZATION, "Bearer " + generateBearerToken())
|
||||||
|
.body(JSONUtil.toJsonStr(requestBody))
|
||||||
|
.timeout(10000) // 超时,毫秒
|
||||||
|
.execute().body();
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
if (StringUtils.isNotBlank(resultMessage)) {
|
||||||
|
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(resultMessage);
|
||||||
|
if (jsonObject.getBool("success", false)) {
|
||||||
|
cn.hutool.json.JSONObject data = jsonObject.getJSONObject("data");
|
||||||
|
if (data != null) {
|
||||||
|
cn.hutool.json.JSONArray content = data.getJSONArray("content");
|
||||||
|
if (content != null && content.size() > 0) {
|
||||||
|
cn.hutool.json.JSONObject firstContent = content.getJSONObject(0);
|
||||||
|
if (firstContent != null && "text".equals(firstContent.getStr("type"))) {
|
||||||
|
String text = firstContent.getStr("text");
|
||||||
|
if (StringUtils.isNotBlank(text)) {
|
||||||
|
String trimmedText = text.trim();
|
||||||
|
if (trimmedText.startsWith("{") || trimmedText.startsWith("[")) {
|
||||||
|
try {
|
||||||
|
return JSONUtil.parseObj(trimmedText);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return trimmedText;
|
||||||
|
}
|
||||||
|
} else if ("true".equals(trimmedText)) {
|
||||||
|
return true;
|
||||||
|
} else if ("false".equals(trimmedText)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return trimmedText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ public class OtaServiceImpl extends BaseServiceImpl<OtaDao, OtaEntity> implement
|
|||||||
// 同类固件只保留最新的一条
|
// 同类固件只保留最新的一条
|
||||||
List<OtaEntity> otaList = baseDao.selectList(queryWrapper);
|
List<OtaEntity> otaList = baseDao.selectList(queryWrapper);
|
||||||
if (otaList != null && otaList.size() > 0) {
|
if (otaList != null && otaList.size() > 0) {
|
||||||
OtaEntity otaBefore = otaList.getFirst();
|
OtaEntity otaBefore = otaList.get(0);
|
||||||
entity.setId(otaBefore.getId());
|
entity.setId(otaBefore.getId());
|
||||||
baseDao.updateById(entity);
|
baseDao.updateById(entity);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.knowledge.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库模块定时任务配置
|
||||||
|
* 启用 Spring Schedule 能力
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableScheduling
|
||||||
|
public class RAGTaskConfig {
|
||||||
|
}
|
||||||
+21
-17
@@ -1,7 +1,6 @@
|
|||||||
package xiaozhi.modules.knowledge.controller;
|
package xiaozhi.modules.knowledge.controller;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
@@ -24,8 +23,11 @@ import xiaozhi.common.exception.ErrorCode;
|
|||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||||
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
||||||
|
import xiaozhi.modules.knowledge.service.KnowledgeManagerService;
|
||||||
|
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -35,6 +37,7 @@ import xiaozhi.modules.security.user.SecurityUser;
|
|||||||
public class KnowledgeBaseController {
|
public class KnowledgeBaseController {
|
||||||
|
|
||||||
private final KnowledgeBaseService knowledgeBaseService;
|
private final KnowledgeBaseService knowledgeBaseService;
|
||||||
|
private final KnowledgeManagerService knowledgeManagerService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "分页查询知识库列表")
|
@Operation(summary = "分页查询知识库列表")
|
||||||
@@ -95,6 +98,8 @@ public class KnowledgeBaseController {
|
|||||||
throw new RenException(ErrorCode.NO_PERMISSION);
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [FIX] 注入 ID,防止 Service 层找不到记录
|
||||||
|
knowledgeBaseDTO.setId(existingKnowledgeBase.getId());
|
||||||
knowledgeBaseDTO.setDatasetId(datasetId);
|
knowledgeBaseDTO.setDatasetId(datasetId);
|
||||||
KnowledgeBaseDTO resp = knowledgeBaseService.update(knowledgeBaseDTO);
|
KnowledgeBaseDTO resp = knowledgeBaseService.update(knowledgeBaseDTO);
|
||||||
return new Result<KnowledgeBaseDTO>().ok(resp);
|
return new Result<KnowledgeBaseDTO>().ok(resp);
|
||||||
@@ -116,7 +121,8 @@ public class KnowledgeBaseController {
|
|||||||
throw new RenException(ErrorCode.NO_PERMISSION);
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
knowledgeBaseService.deleteByDatasetId(datasetId);
|
// [Architecture Fix] 通过编排层级联删除,防止孤儿数据并解决循环依赖
|
||||||
|
knowledgeManagerService.deleteDatasetWithFiles(datasetId);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,20 +137,18 @@ public class KnowledgeBaseController {
|
|||||||
|
|
||||||
// 获取当前登录用户ID
|
// 获取当前登录用户ID
|
||||||
Long currentUserId = SecurityUser.getUserId();
|
Long currentUserId = SecurityUser.getUserId();
|
||||||
String[] idArray = ids.split(",");
|
List<String> idList = Arrays.asList(ids.split(","));
|
||||||
for (String datasetId : idArray) {
|
List<KnowledgeBaseDTO> knowledgeBaseDTOs = Optional.ofNullable(knowledgeBaseService.getByDatasetIdList(idList))
|
||||||
if (StringUtils.isNotBlank(datasetId)) {
|
.orElseGet(ArrayList::new);
|
||||||
// 先获取现有知识库信息以检查权限
|
if (ToolUtil.isNotEmpty(knowledgeBaseDTOs)) {
|
||||||
KnowledgeBaseDTO existingKnowledgeBase = knowledgeBaseService.getByDatasetId(datasetId.trim());
|
knowledgeBaseDTOs.forEach(item -> {
|
||||||
|
|
||||||
// 检查权限:用户只能删除自己创建的知识库
|
// 检查权限:用户只能删除自己创建的知识库
|
||||||
if (existingKnowledgeBase.getCreator() == null
|
if (item.getCreator() == null || !item.getCreator().equals(currentUserId)) {
|
||||||
|| !existingKnowledgeBase.getCreator().equals(currentUserId)) {
|
|
||||||
throw new RenException(ErrorCode.NO_PERMISSION);
|
throw new RenException(ErrorCode.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
// [Architecture Fix] 通过编排层级联删除
|
||||||
knowledgeBaseService.deleteByDatasetId(datasetId.trim());
|
knowledgeManagerService.deleteDatasetWithFiles(item.getDatasetId());
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
@@ -152,8 +156,8 @@ public class KnowledgeBaseController {
|
|||||||
@GetMapping("/rag-models")
|
@GetMapping("/rag-models")
|
||||||
@Operation(summary = "获取RAG模型列表")
|
@Operation(summary = "获取RAG模型列表")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<Map<String, Object>>> getRAGModels() {
|
public Result<List<ModelConfigEntity>> getRAGModels() {
|
||||||
List<Map<String, Object>> result = knowledgeBaseService.getRAGModels();
|
List<ModelConfigEntity> result = knowledgeBaseService.getRAGModels();
|
||||||
return new Result<List<Map<String, Object>>>().ok(result);
|
return new Result<List<ModelConfigEntity>>().ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+64
-62
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
@@ -20,6 +20,9 @@ import xiaozhi.common.page.PageData;
|
|||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
|
||||||
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
||||||
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
@@ -57,22 +60,17 @@ public class KnowledgeFilesController {
|
|||||||
public Result<PageData<KnowledgeFilesDTO>> getPageList(
|
public Result<PageData<KnowledgeFilesDTO>> getPageList(
|
||||||
@PathVariable("dataset_id") String datasetId,
|
@PathVariable("dataset_id") String datasetId,
|
||||||
@RequestParam(required = false) String name,
|
@RequestParam(required = false) String name,
|
||||||
@RequestParam(required = false) Integer status,
|
@RequestParam(required = false) String status,
|
||||||
@RequestParam(required = false, defaultValue = "1") Integer page,
|
@RequestParam(required = false, defaultValue = "1") Integer page,
|
||||||
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
||||||
// 验证知识库权限
|
// 验证知识库权限
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
// 如果指定了状态参数,使用状态查询接口
|
// 组装参数
|
||||||
if (status != null) {
|
|
||||||
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size);
|
|
||||||
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 否则使用通用查询接口
|
|
||||||
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
|
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
|
||||||
knowledgeFilesDTO.setDatasetId(datasetId);
|
knowledgeFilesDTO.setDatasetId(datasetId);
|
||||||
knowledgeFilesDTO.setName(name);
|
knowledgeFilesDTO.setName(name);
|
||||||
|
knowledgeFilesDTO.setStatus(status);
|
||||||
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size);
|
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size);
|
||||||
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
||||||
}
|
}
|
||||||
@@ -82,13 +80,16 @@ public class KnowledgeFilesController {
|
|||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<PageData<KnowledgeFilesDTO>> getPageListByStatus(
|
public Result<PageData<KnowledgeFilesDTO>> getPageListByStatus(
|
||||||
@PathVariable("dataset_id") String datasetId,
|
@PathVariable("dataset_id") String datasetId,
|
||||||
@PathVariable("status") Integer status,
|
@PathVariable("status") String status,
|
||||||
@RequestParam(required = false, defaultValue = "1") Integer page,
|
@RequestParam(required = false, defaultValue = "1") Integer page,
|
||||||
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
||||||
// 验证知识库权限
|
// 验证知识库权限
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
// 组装参数
|
||||||
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size);
|
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
|
||||||
|
knowledgeFilesDTO.setDatasetId(datasetId);
|
||||||
|
knowledgeFilesDTO.setStatus(status);
|
||||||
|
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size);
|
||||||
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,16 +114,29 @@ public class KnowledgeFilesController {
|
|||||||
return new Result<KnowledgeFilesDTO>().ok(resp);
|
return new Result<KnowledgeFilesDTO>().ok(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/documents/{document_id}")
|
@DeleteMapping("/documents")
|
||||||
@Operation(summary = "删除单个文档")
|
@Operation(summary = "批量删除文档")
|
||||||
@Parameter(name = "document_id", description = "文档ID", required = true)
|
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> delete(@PathVariable("dataset_id") String datasetId,
|
public Result<Void> delete(@PathVariable("dataset_id") String datasetId,
|
||||||
|
@RequestBody DocumentDTO.BatchIdReq req) {
|
||||||
|
// 验证知识库权限
|
||||||
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
|
knowledgeFilesService.deleteDocuments(datasetId, req);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/documents/{document_id}")
|
||||||
|
@Operation(summary = "删除单个文档")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> deleteSingle(@PathVariable("dataset_id") String datasetId,
|
||||||
@PathVariable("document_id") String documentId) {
|
@PathVariable("document_id") String documentId) {
|
||||||
// 验证知识库权限
|
// 验证知识库权限
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
knowledgeFilesService.deleteByDocumentId(documentId, datasetId);
|
DocumentDTO.BatchIdReq req = new DocumentDTO.BatchIdReq();
|
||||||
|
req.setIds(java.util.Collections.singletonList(documentId));
|
||||||
|
knowledgeFilesService.deleteDocuments(datasetId, req);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,65 +164,53 @@ public class KnowledgeFilesController {
|
|||||||
@GetMapping("/documents/{document_id}/chunks")
|
@GetMapping("/documents/{document_id}/chunks")
|
||||||
@Operation(summary = "列出指定文档的切片")
|
@Operation(summary = "列出指定文档的切片")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Map<String, Object>> listChunks(@PathVariable("dataset_id") String datasetId,
|
public Result<ChunkDTO.ListVO> listChunks(
|
||||||
|
@PathVariable("dataset_id") String datasetId,
|
||||||
@PathVariable("document_id") String documentId,
|
@PathVariable("document_id") String documentId,
|
||||||
@RequestParam(required = false) String keywords,
|
@ParameterObject ChunkDTO.ListReq req) {
|
||||||
@RequestParam(required = false, defaultValue = "1") Integer page,
|
|
||||||
@RequestParam(required = false, defaultValue = "1024") Integer page_size,
|
// 验证权限 (内部已包含知识库存在性校验与归属权校验)
|
||||||
@RequestParam(required = false) String id) {
|
|
||||||
// 验证知识库权限
|
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
Map<String, Object> result = knowledgeFilesService.listChunks(datasetId, documentId, keywords, page, page_size,
|
// 设置默认值
|
||||||
id);
|
if (req.getPage() == null)
|
||||||
return new Result<Map<String, Object>>().ok(result);
|
req.setPage(1);
|
||||||
|
if (req.getPageSize() == null)
|
||||||
|
req.setPageSize(50);
|
||||||
|
|
||||||
|
// 调用服务层获取强类型切片列表
|
||||||
|
ChunkDTO.ListVO result = knowledgeFilesService.listChunks(datasetId, documentId, req);
|
||||||
|
return new Result<ChunkDTO.ListVO>().ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 召回测试
|
|
||||||
*/
|
|
||||||
@PostMapping("/retrieval-test")
|
@PostMapping("/retrieval-test")
|
||||||
@Operation(summary = "召回测试")
|
@Operation(summary = "召回测试")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Map<String, Object>> retrievalTest(@PathVariable("dataset_id") String datasetId,
|
public Result<RetrievalDTO.ResultVO> retrievalTest(
|
||||||
@RequestBody Map<String, Object> params) {
|
@PathVariable("dataset_id") String datasetId,
|
||||||
|
@RequestBody RetrievalDTO.TestReq req) {
|
||||||
|
|
||||||
// 验证知识库权限
|
// 验证知识库权限
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
try {
|
// 业务下沉逻辑:如果未指定知识库ID,则设为当前路径中的 datasetId
|
||||||
// 提取参数
|
if (req.getDatasetIds() == null || req.getDatasetIds().isEmpty()) {
|
||||||
String question = (String) params.get("question");
|
req.setDatasetIds(java.util.Arrays.asList(datasetId));
|
||||||
if (question == null || question.trim().isEmpty()) {
|
|
||||||
return new Result<Map<String, Object>>().error("问题不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> datasetIds = (List<String>) params.get("dataset_ids");
|
|
||||||
List<String> documentIds = (List<String>) params.get("document_ids");
|
|
||||||
Integer page = (Integer) params.get("page");
|
|
||||||
Integer pageSize = (Integer) params.get("page_size");
|
|
||||||
Float similarityThreshold = (Float) params.get("similarity_threshold");
|
|
||||||
Float vectorSimilarityWeight = (Float) params.get("vector_similarity_weight");
|
|
||||||
Integer topK = (Integer) params.get("top_k");
|
|
||||||
String rerankId = (String) params.get("rerank_id");
|
|
||||||
Boolean keyword = (Boolean) params.get("keyword");
|
|
||||||
Boolean highlight = (Boolean) params.get("highlight");
|
|
||||||
List<String> crossLanguages = (List<String>) params.get("cross_languages");
|
|
||||||
Map<String, Object> metadataCondition = (Map<String, Object>) params.get("metadata_condition");
|
|
||||||
|
|
||||||
// 如果未指定数据集ID,使用当前数据集
|
|
||||||
if (datasetIds == null || datasetIds.isEmpty()) {
|
|
||||||
datasetIds = java.util.Arrays.asList(datasetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> result = knowledgeFilesService.retrievalTest(
|
|
||||||
question, datasetIds, documentIds, page, pageSize, similarityThreshold,
|
|
||||||
vectorSimilarityWeight, topK, rerankId, keyword, highlight, crossLanguages, metadataCondition);
|
|
||||||
|
|
||||||
return new Result<Map<String, Object>>().ok(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new Result<Map<String, Object>>().error("召回测试失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Reinforce] 强管控分页参数,防止 RAGFlow 端出现 Negative Slicing 报错
|
||||||
|
if (req.getPage() == null || req.getPage() < 1) {
|
||||||
|
req.setPage(1);
|
||||||
|
}
|
||||||
|
if (req.getPageSize() == null || req.getPageSize() < 1) {
|
||||||
|
req.setPageSize(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用检索服务,返回强类型聚合对象
|
||||||
|
RetrievalDTO.ResultVO result = knowledgeFilesService.retrievalTest(req);
|
||||||
|
return new Result<RetrievalDTO.ResultVO>().ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析JSON字符串为Map对象
|
* 解析JSON字符串为Map对象
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.common.dao.BaseDao;
|
||||||
|
import xiaozhi.modules.knowledge.entity.DocumentEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档 DAO
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface DocumentDao extends BaseDao<DocumentEntity> {
|
||||||
|
}
|
||||||
@@ -19,4 +19,17 @@ public interface KnowledgeBaseDao extends BaseDao<KnowledgeBaseEntity> {
|
|||||||
*/
|
*/
|
||||||
void deletePluginMappingByKnowledgeBaseId(@Param("knowledgeBaseId") String knowledgeBaseId);
|
void deletePluginMappingByKnowledgeBaseId(@Param("knowledgeBaseId") String knowledgeBaseId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用维度原子更新知识库统计信息
|
||||||
|
*
|
||||||
|
* @param datasetId 数据集ID
|
||||||
|
* @param docDelta 文档数增量
|
||||||
|
* @param chunkDelta 分块数增量
|
||||||
|
* @param tokenDelta Token数增量
|
||||||
|
*/
|
||||||
|
void updateStatsAfterChange(@Param("datasetId") String datasetId,
|
||||||
|
@Param("docDelta") Integer docDelta,
|
||||||
|
@Param("chunkDelta") Long chunkDelta,
|
||||||
|
@Param("tokenDelta") Long tokenDelta);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# RAGFlow API Interface Classification
|
||||||
|
|
||||||
|
## 1. External APIs (三方接入体系)
|
||||||
|
**Path Prefix:** `/api/v1`
|
||||||
|
**Authentication:** API Key (`@token_required`)
|
||||||
|
**Primary Use:** External system integration, SDK usage.
|
||||||
|
|
||||||
|
| Interface Type | Python File Path | Class/Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `agent_bot_completions` | `/api/v1/agentbots/<agent_id>/completions` | Agent Bot completion |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `begin_inputs` | `/api/v1/agentbots/<agent_id>/inputs` | Get Agent Bot inputs |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `list_agents` | `/api/v1/agents` | List Agents |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `create_agent` | `/api/v1/agents` | Create Agent |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `update_agent` | `/api/v1/agents/<agent_id>` | Update Agent |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `delete_agent` | `/api/v1/agents/<agent_id>` | Delete Agent |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `agent_completions` | `/api/v1/agents/<agent_id>/completions` | Agent completion |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `create_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Create Agent Session |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `list_agent_session` | `/api/v1/agents/<agent_id>/sessions` | List Agent Sessions |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `delete_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Delete Agent Session |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `agents_completion_openai_compatibility` | `/api/v1/agents_openai/<agent_id>/chat/completions` | OpenAI compatible Agent completion |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `chatbot_completions` | `/api/v1/chatbots/<dialog_id>/completions` | Chatbot completion |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `chatbots_inputs` | `/api/v1/chatbots/<dialog_id>/info` | Chatbot info |
|
||||||
|
| **External** | `api/apps/sdk/chat.py` | `create` | `/api/v1/chats` | Create Chat |
|
||||||
|
| **External** | `api/apps/sdk/chat.py` | `delete_chats` | `/api/v1/chats` | Delete Chat |
|
||||||
|
| **External** | `api/apps/sdk/chat.py` | `list_chat` | `/api/v1/chats` | List Chats |
|
||||||
|
| **External** | `api/apps/sdk/chat.py` | `update` | `/api/v1/chats/<chat_id>` | Update Chat |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `chat_completion` | `/api/v1/chats/<chat_id>/completions` | Chat completion |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `create` | `/api/v1/chats/<chat_id>/sessions` | Create Chat Session |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `list_session` | `/api/v1/chats/<chat_id>/sessions` | List Chat Sessions |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `delete` | `/api/v1/chats/<chat_id>/sessions` | Delete Chat Session |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `update` | `/api/v1/chats/<chat_id>/sessions/<session_id>` | Update Chat Session |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `chat_completion_openai_like` | `/api/v1/chats_openai/<chat_id>/chat/completions` | OpenAI compatible Chat completion |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `create` | `/api/v1/datasets` | Create Dataset |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `delete` | `/api/v1/datasets` | Delete Dataset |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `list_datasets` | `/api/v1/datasets` | List Datasets |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `update` | `/api/v1/datasets/<dataset_id>` | Update Dataset |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `parse` | `/api/v1/datasets/<dataset_id>/chunks` | Parse Document Chunks |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `stop_parsing` | `/api/v1/datasets/<dataset_id>/chunks` | Stop Parsing |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `upload` | `/api/v1/datasets/<dataset_id>/documents` | Upload Document |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `list_docs` | `/api/v1/datasets/<dataset_id>/documents` | List Documents |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `delete` | `/api/v1/datasets/<dataset_id>/documents` | Delete Document |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `update_doc` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Update Document |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `download` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Download Document |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `list_chunks` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | List Chunks |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `add_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Add Chunk |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `update_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>` | Update Chunk |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Knowledge Graph |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `delete_knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Delete Knowledge Graph |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `metadata_summary` | `/api/v1/datasets/<dataset_id>/metadata/summary` | Metadata Summary |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `metadata_batch_update` | `/api/v1/datasets/<dataset_id>/metadata/update` | Batch Update Metadata |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `run_graphrag` | `/api/v1/datasets/<dataset_id>/run_graphrag` | Run GraphRAG |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `run_raptor` | `/api/v1/datasets/<dataset_id>/run_raptor` | Run Raptor |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `trace_graphrag` | `/api/v1/datasets/<dataset_id>/trace_graphrag` | Trace GraphRAG |
|
||||||
|
| **External** | `api/apps/sdk/dataset.py` | `trace_raptor` | `/api/v1/datasets/<dataset_id>/trace_raptor` | Trace Raptor |
|
||||||
|
| **External** | `api/apps/sdk/dify_retrieval.py` | `retrieval` | `/api/v1/dify/retrieval` | Dify Retrieval |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `get_all_parent_folders` | `/api/v1/file/all_parent_folder` | Get All Parent Folders |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `convert` | `/api/v1/file/convert` | File Convert |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `create` | `/api/v1/file/create` | File Create |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `download_attachment` | `/api/v1/file/download/<attachment_id>` | Download Attachment |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `get` | `/api/v1/file/get/<file_id>` | Get File |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `list_files` | `/api/v1/file/list` | List Files |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `move` | `/api/v1/file/mv` | Move File |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `get_parent_folder` | `/api/v1/file/parent_folder` | Get Parent Folder |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `rename` | `/api/v1/file/rename` | Rename File |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `rm` | `/api/v1/file/rm` | Remove File |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `get_root_folder` | `/api/v1/file/root_folder` | Get Root Folder |
|
||||||
|
| **External** | `api/apps/sdk/files.py` | `upload` | `/api/v1/file/upload` | Upload File |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `retrieval_test` | `/api/v1/retrieval` | Retrieval Test |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `ask_about_embedded` | `/api/v1/searchbots/ask` | Searchbot Ask |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `detail_share_embedded` | `/api/v1/searchbots/detail` | Searchbot Detail |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `mindmap` | `/api/v1/searchbots/mindmap` | Searchbot Mindmap |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `related_questions_embedded` | `/api/v1/searchbots/related_questions` | Searchbot Related Questions |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `retrieval_test_embedded` | `/api/v1/searchbots/retrieval_test` | Searchbot Retrieval Test |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `ask_about` | `/api/v1/sessions/ask` | Session Ask |
|
||||||
|
| **External** | `api/apps/sdk/session.py` | `related_questions` | `/api/v1/sessions/related_questions` | Session Related Questions |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `webhook` | `/api/v1/webhook_test/<agent_id>` | Webhook Test |
|
||||||
|
| **External** | `api/apps/sdk/agents.py` | `webhook_trace` | `/api/v1/webhook_trace/<agent_id>` | Webhook Trace |
|
||||||
|
| **External** | `api/apps/sdk/doc.py` | `rm_chunk` | `/api/v1datasets/<dataset_id>/documents/<document_id>/chunks` | Remove Chunk |
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Internal APIs (内部前端体系)
|
||||||
|
**Path Prefix:** `/v1/<app_name>` matches file `api/apps/<app_name>_app.py`
|
||||||
|
**Authentication:** Session/Cookie (`@login_required`)
|
||||||
|
**Primary Use:** RAGFlow Web Frontend.
|
||||||
|
|
||||||
|
**Selected Core Interfaces:**
|
||||||
|
|
||||||
|
| Interface Type | Python File Path | Class/Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Internal | `api/apps/user_app.py` | `login` | `/v1/user/login` | User Login (Frontend) |
|
||||||
|
| Internal | `api/apps/user_app.py` | `log_out` | `/v1/user/logout` | User Logout |
|
||||||
|
| Internal | `api/apps/user_app.py` | `user_add` | `/v1/user/register` | User Registration |
|
||||||
|
| Internal | `api/apps/user_app.py` | `user_profile` | `/v1/user/info` | User Profile Info |
|
||||||
|
| Internal | `api/apps/api_app.py` | `new_token` | `/v1/api/new_token` | Generate new API Token |
|
||||||
|
| Internal | `api/apps/conversation_app.py` | `set_conversation` | `/v1/conversation/set` | Create/Update Conversation |
|
||||||
|
| Internal | `api/apps/conversation_app.py` | `completion` | `/v1/conversation/completion` | Chat Conversation Completion |
|
||||||
|
| Internal | `api/apps/kb_app.py` | `list_kbs` | `/v1/kb/list` | List Knowledge Bases |
|
||||||
|
| Internal | `api/apps/kb_app.py` | `create` | `/v1/kb/create` | Create Knowledge Base |
|
||||||
|
| Internal | `api/apps/document_app.py` | `upload` | `/v1/document/upload` | Upload Document to KB |
|
||||||
|
| Internal | `api/apps/document_app.py` | `parse` | `/v1/document/parse` | Parse Document |
|
||||||
|
|
||||||
|
*(For a complete list of all 200+ internal APIs, please refer to the `api_endpoints.txt` file or the full scan results)*
|
||||||
+279
@@ -0,0 +1,279 @@
|
|||||||
|
# RAGFlow Agent 与 Dify 兼容接口详解 (Agent & Dify Compatibility)
|
||||||
|
|
||||||
|
## 1. Dify 兼容检索 - `retrieval`
|
||||||
|
**接口描述**: 模拟 Dify API 格式的知识库检索接口。此接口主要用于让现有的 Dify 客户端或系统能够方便地接入 RAGFlow 的知识库检索能力。它支持文本检索、混合检索以及通过元数据过滤文档。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/dify/retrieval`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| knowledge_id | string | 是 | - | **知识库 ID**。 |
|
||||||
|
| query | string | 是 | - | **查询文本**。用户输入的检索问题。 |
|
||||||
|
| use_kg | boolean | 否 | false | **使用知识图谱**。是否结合知识图谱进行检索。 |
|
||||||
|
| retrieval_setting | object | 否 | {} | **检索配置**。包含相似度阈值和 Top-K。 |
|
||||||
|
| metadata_condition | object | 否 | {} | **元数据过滤条件**。用于筛选特定文档。 |
|
||||||
|
|
||||||
|
#### 参数详情 (Detail Objects)
|
||||||
|
**retrieval_setting**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"score_threshold": 0.5, // 相似度阈值 (default: 0.0)
|
||||||
|
"top_k": 5 // 返回数量 (default: 1024)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**metadata_condition**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"logic": "and", // 逻辑关系 (and/or)
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"name": "author", // 字段名
|
||||||
|
"comparison_operator": "eq",// 运算符 (eq, ne, gt, lt 等)
|
||||||
|
"value": "Alice" // 字段值
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"content": "RAGFlow 是一个基于深度文档理解的检索增强生成引擎...",
|
||||||
|
"score": 0.92,
|
||||||
|
"title": "RAGFlow_Introduction.pdf",
|
||||||
|
"metadata": {
|
||||||
|
"doc_id": "doc_uuid_123",
|
||||||
|
"author": "Alice",
|
||||||
|
"publish_year": "2024"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "DeepDOC 模型能够精准识别复杂的表格结构...",
|
||||||
|
"score": 0.88,
|
||||||
|
"title": "DeepDOC_Tech_Report.pdf",
|
||||||
|
"metadata": {
|
||||||
|
"doc_id": "doc_uuid_456",
|
||||||
|
"author": "Bob"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 创建 Agent 会话 - `create_agent_session`
|
||||||
|
**接口描述**: 创建一个新的 Agent 会话 (Session)。会话是用户与 Agent 交互的上下文容器,保存了历史对话记录和 DSL(领域特定语言)状态。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| user_id | string | 否 | **用户标识**。用于区分不同终端用户的会话。若不传,默认为当前 Tenant ID。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "session_uuid_new_123",
|
||||||
|
"agent_id": "agent_uuid_abc",
|
||||||
|
"user_id": "user_123",
|
||||||
|
"source": "agent",
|
||||||
|
"dsl": { ... }, // 完整的 Agent DSL 定义
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "你好!我是你的智能助手,有什么可以帮你的吗?" // Prologue (开场白)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 获取 Agent 会话列表 - `list_agent_session`
|
||||||
|
**接口描述**: 分页获取指定 Agent 下的会话列表。支持按 ID 或 User ID 过滤。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | **页码**。 |
|
||||||
|
| page_size | int | 否 | 30 | **每页数量**。 |
|
||||||
|
| orderby | string | 否 | "update_time" | **排序字段**。 |
|
||||||
|
| desc | boolean | 否 | true | **是否降序**。 |
|
||||||
|
| id | string | 否 | - | **会话 ID**。精确筛选。 |
|
||||||
|
| user_id | string | 否 | - | **用户标识**。筛选特定用户的会话。 |
|
||||||
|
| dsl | boolean | 否 | true | **包含 DSL**。是否在返回结果中包含完整的 DSL 结构 (数据量较大)。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "session_uuid_123",
|
||||||
|
"agent_id": "agent_uuid_abc",
|
||||||
|
"user_id": "user_123",
|
||||||
|
"create_time": 1715000000000,
|
||||||
|
"update_time": 1715000050000,
|
||||||
|
"source": "agent",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hi there!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is RAG?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 删除 Agent 会话 - `delete_agent_session`
|
||||||
|
**接口描述**: 批量删除 Agent 会话。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ids | array<string> | 否 | **会话 ID 列表**。若不传该参数,将尝试删除(或清空)该 Agent 下的所有会话(需谨慎)。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": ["session_id_1", "session_id_2"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"success_count": 2,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Agent 对话 (流式) - `agent_completions`
|
||||||
|
**接口描述**: 向 Agent 发送用户问题并获取回复。这是 Agent 交互的核心接口,支持 **Server-Sent Events (SSE)** 流式响应。Agent 会根据编排好的 DSL 流程执行(可能涉及多个节点、知识库检索、LLM 推理等),并实时推送执行过程和最终结果。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| session_id | string | 是 | - | **会话 ID**。必须是 `create_agent_session` 返回的 ID。 |
|
||||||
|
| question | string | 是 | - | **用户问题**。 |
|
||||||
|
| stream | boolean | 否 | true | **是否流式响应**。强烈建议设为 `true` 以获得更好的用户体验。 |
|
||||||
|
| return_trace | boolean | 否 | false | **返回执行轨迹**。如果为 `true`,流式响应中将包含各个节点的执行过程数据 (Trace)。 |
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
响应是一个 SSE 流,每一行以 `data:` 开头,包含一个 JSON 对象。
|
||||||
|
|
||||||
|
**Event Types**:
|
||||||
|
- `message`: 普通文本消息片段。
|
||||||
|
- `node_finished`: (当 `return_trace=true` 时) 节点执行完成事件,包含节点输出数据。
|
||||||
|
- `message_end`: 消息结束。
|
||||||
|
- `[DONE]`: 流结束标志。
|
||||||
|
|
||||||
|
#### Stream Chunk Examples:
|
||||||
|
|
||||||
|
**1. 文本生成片段 (message)**:
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "success", "data": {"content": "Hello", "reference": {}, "id": "msg_uuid_1"}, "event": "message"}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "success", "data": {"content": " world", "reference": {}, "id": "msg_uuid_1"}, "event": "message"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 节点执行轨迹 (node_finished, return_trace=true)**:
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "success", "data": {"component_id": "retrieval_node_1", "content": "...", "trace": [...]}, "event": "node_finished"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 最终结束 (DONE)**:
|
||||||
|
```text
|
||||||
|
data:[DONE]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Non-Stream Response (stream=false)
|
||||||
|
如果不使用流式响应,将等待 Agent 全流程执行完毕后一次性返回 JSON。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"content": "Hello world! This is the final answer.",
|
||||||
|
"reference": {
|
||||||
|
"chunk_id_1": { ... } // 引用来源
|
||||||
|
},
|
||||||
|
"trace": [ ... ] // 如果 return_trace=true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
## 1. 获取 Agent 列表 - `list_agents`
|
||||||
|
**接口描述**: 分页查询当前租户下的所有 Agent 列表,支持按 ID 或标题筛选。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/agents`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | 页码 |
|
||||||
|
| page_size | int | 否 | 30 | 每页条数 |
|
||||||
|
| orderby | string | 否 | update_time | 排序字段 (create_time, update_time, title) |
|
||||||
|
| desc | boolean | 否 | True | 是否降序排列 (True: 降序, False: 升序) |
|
||||||
|
| id | string | 否 | - | 按 Agent ID 精确筛选 |
|
||||||
|
| title | string | 否 | - | 按 Agent 标题精确筛选 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "e0d34e2c-...",
|
||||||
|
"title": "My Assistant",
|
||||||
|
"description": "A helpful AI assistant",
|
||||||
|
"dsl": { ... }, // Agent 的 DSL 流程定义
|
||||||
|
"user_id": "tenant_123",
|
||||||
|
"avatar": "", // 头像 Base64 或 URL
|
||||||
|
"canvas_category": "Agent",
|
||||||
|
"create_time": 1715623400000,
|
||||||
|
"update_time": 1715624500000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 创建 Agent - `create_agent`
|
||||||
|
**接口描述**: 创建一个新的 Agent,必须包含标题和 DSL 定义。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/agents`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| title | string | 是 | - | Agent 的名称 (必须唯一) |
|
||||||
|
| dsl | object | 是 | - | Agent 的流程定义 (节点、连线配置) |
|
||||||
|
| description | string | 否 | - | Agent 的功能描述 |
|
||||||
|
| avatar | string | 否 | - | Agent 头像 (Base64 字符串或 URL) |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 更新 Agent - `update_agent`
|
||||||
|
**接口描述**: 更新指定 Agent 的配置信息,支持增量更新(仅传递需要修改的字段)。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | 要更新的 Agent ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| title | string | 否 | - | 新的 Agent 名称 |
|
||||||
|
| dsl | object | 否 | - | 新的 DSL 流程定义 |
|
||||||
|
| description | string | 否 | - | 新的功能描述 |
|
||||||
|
| avatar | string | 否 | - | 新的头像 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 删除 Agent - `delete_agent`
|
||||||
|
**接口描述**: 根据 ID 删除指定的 Agent。此操作不可恢复。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/agents/<agent_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | 要删除的 Agent ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Webhook 测试触发 - `webhook`
|
||||||
|
**接口描述**: 用于测试 Agent 的 Webhook 触发功能。该接口模拟外部系统调用,触发 Agent 按照配置的 "Begin" 节点逻辑开始执行。支持同步等待结果或流式返回(取决于 Agent 配置)。
|
||||||
|
**请求方法**: `POST` (支持 GET/PUT/DELETE 等,取决于 Canvas 配置)
|
||||||
|
**接口地址**: `/api/v1/webhook_test/<agent_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | Agent 的唯一标识符 |
|
||||||
|
|
||||||
|
#### Query / Headers / Body Parameters
|
||||||
|
**说明**: 此接口的参数完全动态,取决于 Agent 画布中 **"Begin" (开始)** 节点的 **Webhook** 配置。
|
||||||
|
- 如果配置了 Query 参数验证,则需在 URL 中传递对应参数。
|
||||||
|
- 如果配置了 Header 验证,则需传递对应 Header。
|
||||||
|
- **Body**: 通常为 JSON 格式,包含 Agent 运行所需的变量(inputs)或上下文数据。
|
||||||
|
|
||||||
|
**Body Example (JSON)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inputs": {
|
||||||
|
"topic": "AI Trends",
|
||||||
|
"style": "professional"
|
||||||
|
},
|
||||||
|
"query": "Start generation"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json` (或 `text/event-stream`)
|
||||||
|
|
||||||
|
**即时响应模式 (Immediately)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"content": "生成的回答内容...",
|
||||||
|
"usage": { ... }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**流式响应模式 (SSE)**:
|
||||||
|
如果不使用 `webhook_test` 而是生产环境 `webhook` 且配置为 SSE,则返回流式数据。但在 `webhook_test` 接口中,通常配合 `webhook_trace` 进行异步调试。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Webhook 执行轨迹查询 - `webhook_trace`
|
||||||
|
**接口描述**: 轮询查询 Agent 在 Webhook 测试触发后的执行日志和中间状态。采用长轮询或游标机制,实时获取执行进度。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/webhook_trace/<agent_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | Agent 的唯一标识符 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| since_ts | float | 否 | 当前时间 | 起始时间戳。返回此时间之后的日志事件。首次调用可不传(获取当前时间作为游标)。 |
|
||||||
|
| webhook_id | string | 否 | - | Webhook 会话 ID。用于锁定特定的某次执行记录。首次轮询时不传,接口会返回新生成的 ID。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"webhook_id": "YWdlbnxxxx...", // 当前追踪的会话 ID (加密串)
|
||||||
|
"finished": false, // 执行是否已结束 (true/false)
|
||||||
|
"next_since_ts": 1715629999.5, // 下一次轮询应使用的 since_ts
|
||||||
|
"events": [ // 本次轮询获取到的新事件列表
|
||||||
|
{
|
||||||
|
"ts": 1715629998.1,
|
||||||
|
"event": "message", // 事件类型: message, start_to_think, finished, error 等
|
||||||
|
"data": {
|
||||||
|
"content": "思考中...",
|
||||||
|
"reference": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 💡 最佳实践 (调试流程)
|
||||||
|
1. **初始化**: 调用 `GET /webhook_trace/<id>` (不带参数),获取 `next_since_ts` (记为 `T0`)。
|
||||||
|
2. **触发**: 调用 `POST /webhook_test/<id>` 发送测试数据。
|
||||||
|
3. **首帧捕获**: 循环调用 `GET /webhook_trace/<id>?since_ts=T0`,直到返回 `webhook_id` (记为 `WID`) 和第一批 `events`。
|
||||||
|
4. **持续追踪**: 使用 `WID` 和响应中的 `next_since_ts` 持续轮询,直到 `data.finished == true`。
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
# RAGFlow 对话交互接口详解 (Chat Completion & OpenAI Compatibility)
|
||||||
|
|
||||||
|
## 5. 对话助手对话 (流式) - `chat_completion`
|
||||||
|
**接口描述**: 发送问题给对话助手 (Assistant/Chat) 并获取回复。这是 RAGFlow 最核心的原生对话接口,支持 **Server-Sent Events (SSE)** 流式响应。它会根据助手绑定的知识库进行 RAG 检索生成。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| session_id | string | 是 | - | **会话 ID**。从 `create_chat_session` 获取。 |
|
||||||
|
| question | string | 是 | - | **用户问题**。 |
|
||||||
|
| stream | boolean | 否 | true | **是否流式响应**。 |
|
||||||
|
| quote | boolean | 否 | false | **返回引用**。是否在响应中包含检索到的引用片段。 |
|
||||||
|
| doc_ids | string | 否 | - | **限定文档 ID**。多个 ID 用逗号分隔,仅检索指定文档。 |
|
||||||
|
| metadata_condition | object | 否 | {} | **元数据过滤**。用于限定检索范围。 |
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
每一行数据以 `data:` 开头,包含一个 JSON 对象。
|
||||||
|
|
||||||
|
**Event Example**:
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "success", "data": {"answer": "Hello", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "success", "data": {"answer": " world!", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "success", "data": {"answer": "", "reference": {"chunk_1": {...}}}} // 引用数据
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Non-Stream Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"answer": "Hello world! This is the generated response.",
|
||||||
|
"reference": {
|
||||||
|
"chunk_id_1": {
|
||||||
|
"content_with_weight": "Original text...",
|
||||||
|
"doc_name": "manual.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. OpenAI 兼容对话 - `chat_completion_openai_like`
|
||||||
|
**接口描述**: 提供与 **OpenAI API (`/v1/chat/completions`)** 完全兼容的接口。允许开发者使用 LangChain、OpenAI Python SDK 或其他支持 OpenAI 协议的工具直接调用 RAGFlow,实现无缝迁移。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/chats_openai/<chat_id>/chat/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID**。在此上下文中充当 "Base URL" 的一部分。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON - OpenAI Standard)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| messages | array | 是 | **消息列表**。包含 `role` (system/user/assistant) 和 `content`。 |
|
||||||
|
| model | string | 是 | **模型名称**。可以是任意非空字符串 (RAGFlow 会使用助手预设的模型)。 |
|
||||||
|
| stream | boolean | 否 | **是否流式**。默认为 `true`。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "ragflow_default",
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "You are a helpful assistant."},
|
||||||
|
{"role": "user", "content": "Explain quantum physics."}
|
||||||
|
],
|
||||||
|
"stream": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response - OpenAI Format)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
严格遵循 OpenAI Chunk 格式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000000, "model": "model", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": null}]}
|
||||||
|
|
||||||
|
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000001, "model": "model", "choices": [{"index": 0, "delta": {"content": "Quantum"}, "finish_reason": null}]}
|
||||||
|
|
||||||
|
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000002, "model": "model", "choices": [{"index": 0, "delta": {"content": " physics"}, "finish_reason": null}]}
|
||||||
|
|
||||||
|
data: [DONE]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 嵌入式 Chatbot 对话 - `chatbot_completions`
|
||||||
|
**接口描述**: 专为 **嵌入式窗口 (Embed Window)** 设计的公开对话接口。它通常用于将 RAGFlow 助手作为客服窗口嵌入到第三方网站。与普通接口不同,它通过 `Authorization` Header 传递 **Beta Token** (即 API Key) 进行鉴权,且通常面向最终用户。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/chatbots/<dialog_id>/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dialog_id | string | 是 | **助手 ID** (Dialog ID)。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| question | string | 是 | - | **用户问题**。 |
|
||||||
|
| stream | boolean | 否 | true | **是否流式**。 |
|
||||||
|
| session_id | string | 否 | - | **会话 ID**。用于维持上下文。 |
|
||||||
|
| quote | boolean | 否 | false | **返回引用**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
与 `chat_completion` 类似,返回 RAGFlow 原生 SSE 格式。
|
||||||
|
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "success", "data": {"answer": "Here is the answer...", "reference": {}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Chatbot 初始化信息 - `chatbots_inputs`
|
||||||
|
**接口描述**: 获取嵌入式 Chatbot 的初始化配置信息。通常在前端组件加载时调用,用于展示助手的头像、名称、开场白 (Prologue) 等信息。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/chatbots/<dialog_id>/info`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dialog_id | string | 是 | **助手 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"title": "IT Support Bot", // 助手名称
|
||||||
|
"avatar": "http://...", // 头像 URL
|
||||||
|
"prologue": "Hi! How can I help?" // 开场白
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
# RAGFlow 聊天助手会话管理接口详解 (Chat Assistant Session Management)
|
||||||
|
|
||||||
|
## 1. 创建会话 - `create_chat_session`
|
||||||
|
**接口描述**: 为指定的聊天助手 (Chat/Assistant) 创建一个新的会话。系统会自动加载该助手的开场白 (Prologue) 作为第一条消息。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID** (Assistant/Dialog ID)。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| name | string | 否 | "New session" | **会话名称**。 |
|
||||||
|
| user_id | string | 否 | - | **用户标识**。用于区分不同终端用户的会话。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Consulting regarding RAG",
|
||||||
|
"user_id": "client_001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "session_uuid_123",
|
||||||
|
"chat_id": "chat_uuid_abc",
|
||||||
|
"name": "Consulting regarding RAG",
|
||||||
|
"user_id": "client_001",
|
||||||
|
"create_time": 1715000000000,
|
||||||
|
"create_date": "2024-05-01 10:00:00",
|
||||||
|
"update_time": 1715000000000,
|
||||||
|
"update_date": "2024-05-01 10:00:00",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hi! I am your AI assistant. How can I help you today?" // 自动加载的开场白
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 获取会话列表 - `list_chat_session`
|
||||||
|
**接口描述**: 分页获取指定助手下的会话列表。支持按名称或用户 ID 过滤。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | **页码**。 |
|
||||||
|
| page_size | int | 否 | 30 | **每页数量**。 |
|
||||||
|
| orderby | string | 否 | "create_time" | **排序字段**。 |
|
||||||
|
| desc | boolean | 否 | true | **是否降序**。 |
|
||||||
|
| name | string | 否 | - | **会话名称搜索**。 |
|
||||||
|
| id | string | 否 | - | **会话 ID 精确筛选**。 |
|
||||||
|
| user_id | string | 否 | - | **用户标识筛选**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "session_uuid_123",
|
||||||
|
"chat_id": "chat_uuid_abc",
|
||||||
|
"name": "Consulting regarding RAG",
|
||||||
|
"user_id": "client_001",
|
||||||
|
"create_time": 1715000000000,
|
||||||
|
"create_date": "2024-05-01 10:00:00",
|
||||||
|
"update_time": 1715000050000,
|
||||||
|
"update_date": "2024-05-01 10:00:50",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hi! I am your AI assistant. How can I help you today?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is RAGFlow?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "session_uuid_456",
|
||||||
|
"chat_id": "chat_uuid_abc",
|
||||||
|
"name": "New session",
|
||||||
|
"user_id": "client_002",
|
||||||
|
"create_time": 1714900000000,
|
||||||
|
"create_date": "2024-04-30 09:00:00",
|
||||||
|
"update_time": 1714900000000,
|
||||||
|
"update_date": "2024-04-30 09:00:00",
|
||||||
|
"messages": [ ... ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 更新会话 - `update_chat_session`
|
||||||
|
**接口描述**: 更新会话信息。目前主要用于 **重命名** 会话。注意:不能通过此接口修改消息记录 (`messages`)。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>/sessions/<session_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID**。 |
|
||||||
|
| session_id | string | 是 | **会话 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| name | string | 否 | **新的会话名称**。不可为空字符串。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "RAG Technical Discussion"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 删除会话 - `delete_chat_session`
|
||||||
|
**接口描述**: 批量删除指定助手下的会话。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | **助手 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ids | array<string> | 否 | **待删除的会话 ID 列表**。若不传该参数,将尝试删除该助手下的**所有会话**(请极其谨慎使用)。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": ["session_uuid_123", "session_uuid_456"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null // 若全部删除成功
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (部分成功时)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "Partially deleted 1 sessions with 1 errors",
|
||||||
|
"data": {
|
||||||
|
"success_count": 1,
|
||||||
|
"errors": ["The chat doesn't own the session session_uuid_999"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
## 1. 创建助手应用 - `create`
|
||||||
|
**接口描述**: 创建一个新的对话助手(Chat Assistant)。支持配置关联知识库、LLM 模型参数、提示词(Prompt)以及开场白等高级设置。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/chats`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| name | string | 是 | - | 助手应用名称 (租户内唯一) |
|
||||||
|
| avatar | string | 否 | - | 助手头像 (URL 或 Base64 字符串) |
|
||||||
|
| description | string | 否 | "A helpful Assistant" | 助手的功能描述 |
|
||||||
|
| dataset_ids | array | 否 | [] | 关联的知识库 ID 列表 (必须是当前租户有权限访问的知识库) |
|
||||||
|
| llm | object | 否 | - | LLM 模型生成配置 (如模型名称、温度等) |
|
||||||
|
| prompt | object | 否 | - | 提示词引擎与检索配置 (包含 System Prompt, Opener, Rerank 等) |
|
||||||
|
|
||||||
|
**`llm` 对象详细结构**:
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| model_name | string | 是 | - | 模型名称 (例如: `deepseek-chat`, `gpt-4`, `qwen-turbo`) |
|
||||||
|
| temperature | float | 否 | 0.1 | 温度系数 (0.0 ~ 1.0),越高越随机,越低越确定 |
|
||||||
|
| top_p | float | 否 | 0.3 | 核采样概率阈值 |
|
||||||
|
| max_tokens | int | 否 | 512 | 单次回答的最大 Token 数限制 |
|
||||||
|
| presence_penalty | float | 否 | 0.4 | 话题新鲜度惩罚 (-2.0 ~ 2.0),正值鼓励讨论新话题 |
|
||||||
|
| frequency_penalty | float | 否 | 0.7 | 频率惩罚 (-2.0 ~ 2.0),正值减少重复词汇 |
|
||||||
|
|
||||||
|
**`prompt` 对象详细结构**:
|
||||||
|
*注意:此对象包含“提示词配置”与“检索策略配置”两部分。*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| prompt | string | 否 | (内置默认提示词) | **System Prompt (系统提示词)**。给大模型的角色指令,例如 "你是一个客服..."。可使用变量占位符 `{knowledge}`。 |
|
||||||
|
| opener | string | 否 | "Hi! I'm your assistant..." | **开场白**。用户进入对话窗口时,助手自动发送的第一条欢迎语。 |
|
||||||
|
| show_quote | boolean | 否 | true | **显示引用**。回答中是否标注来源文档 (e.g., [1])。 |
|
||||||
|
| variables | array | 否 | `[{"key": "knowledge", "optional": false}]` | **变量列表**。定义用于填充 System Prompt 的变量。`knowledge` 为保留变量,代表检索到的知识片段。 |
|
||||||
|
| rerank_model | string | 否 | - | **重排序模型 ID**。配置后会对检索结果进行二次精排 (如 `BAAI/bge-reranker-v2-m3`)。 |
|
||||||
|
| keywords_similarity_weight | float | 否 | 0.7 | **关键字权重** (0.0 ~ 1.0)。控制混合检索的比例。更接近 1.0 侧重关键字匹配,更接近 0.0 侧重向量语义匹配。 |
|
||||||
|
| similarity_threshold | float | 否 | 0.2 | **相似度阈值** (0.0 ~ 1.0)。低于此相似度的文档块将被过滤,不喂给大模型。 |
|
||||||
|
| top_n | int | 否 | 6 | **Top N**。最终截取并输入给大模型的文档块数量。 |
|
||||||
|
| empty_response | string | 否 | "Sorry! No relevant..." | **空结果回复**。当没有检索到相关知识库内容时的兜底回复。 |
|
||||||
|
| tts | boolean | 否 | false | **启用 TTS**。是否将助手的文本回答自动转为语音播放。 |
|
||||||
|
| refine_multiturn | boolean | 否 | true | **多轮对话优化**。是否根据历史上下文重写用户问题 (Query Rewrite) 以提高检索准确率。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "e0d34e2c-1234-5678-9xxx-xxxxxxxxxxxx",
|
||||||
|
"name": "企业知识库助手",
|
||||||
|
"avatar": "http://example.com/avatar.png",
|
||||||
|
"description": "用于回答员工内部问题的 AI",
|
||||||
|
"dataset_ids": ["kb_123", "kb_456"],
|
||||||
|
"llm": {
|
||||||
|
"model_name": "deepseek-chat",
|
||||||
|
"temperature": 0.1,
|
||||||
|
"top_p": 0.3,
|
||||||
|
"max_tokens": 512,
|
||||||
|
"presence_penalty": 0.4,
|
||||||
|
"frequency_penalty": 0.7
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"prompt": "你是一个智能助手,请根据以下知识回答问题:\n{knowledge}",
|
||||||
|
"opener": "你好!有什么可以帮你的?",
|
||||||
|
"show_quote": true,
|
||||||
|
"variables": [
|
||||||
|
{ "key": "knowledge", "optional": false }
|
||||||
|
],
|
||||||
|
"rerank_model": "",
|
||||||
|
"keywords_similarity_weight": 0.7,
|
||||||
|
"similarity_threshold": 0.2,
|
||||||
|
"top_n": 8,
|
||||||
|
"empty_response": "抱歉,知识库中没有找到相关答案。",
|
||||||
|
"tts": false,
|
||||||
|
"refine_multiturn": true
|
||||||
|
},
|
||||||
|
"create_time": 1715623400000,
|
||||||
|
"update_time": 1715624500000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 获取助手列表 - `list_chat`
|
||||||
|
**接口描述**: 获取当前租户下的所有助手应用列表。支持分页、排序及按名称/ID筛选。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/chats`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | 页码 |
|
||||||
|
| page_size | int | 否 | 30 | 每页条数 |
|
||||||
|
| orderby | string | 否 | create_time | 排序字段 (`create_time`, `update_time`) |
|
||||||
|
| desc | boolean | 否 | true | 是否降序排列 (`true`: 降序, `false`: 升序) |
|
||||||
|
| name | string | 否 | - | 按名称模糊搜索 (支持 partial match) |
|
||||||
|
| id | string | 否 | - | 按 ID 精确筛选 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "e0d34e2c-...",
|
||||||
|
"name": "客服机器人",
|
||||||
|
"avatar": "http://...",
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"id": "kb_1",
|
||||||
|
"name": "产品手册",
|
||||||
|
"avatar": "",
|
||||||
|
"chunk_num": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"llm": { ... }, // (结构同 create 接口响应)
|
||||||
|
"prompt": { ... }, // (结构同 create 接口响应)
|
||||||
|
"create_time": 1715623400000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 更新助手配置 - `update`
|
||||||
|
**接口描述**: 更新指定助手应用的配置信息。支持全量或增量更新部分字段。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/chats/<chat_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chat_id | string | 是 | 助手应用 ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(以下所有字段均为可选,仅传递需要修改的字段即可)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| name | string | - | 新的助手名称 |
|
||||||
|
| avatar | string | - | 新的头像 URL 或 Base64 |
|
||||||
|
| dataset_ids | array | - | **全量替换**关联的知识库 ID 列表 |
|
||||||
|
| llm | object | - | 更新 LLM 配置。需包含 `model_name`,其他字段覆盖更新。 |
|
||||||
|
| prompt | object | - | 更新提示词配置。支持增量更新 (e.g. 只改 `opener`)。 |
|
||||||
|
| show_quotation | boolean | - | 是否显示引用来源 (此字段直接位于根对象下,对应 prompt.show_quote) |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 批量删除助手 - `delete_chats`
|
||||||
|
**接口描述**: 批量删除一个或多个助手应用。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/chats`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ids | array<string> | 是 | 要删除的助手应用 ID 列表。**⚠️ 注意:若列表为空或不传,虽然后端有全量删除逻辑,但在实际业务中应严谨传递 ID。** |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": ["chat_id_1001", "chat_id_1002"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"success_count": 2, // 成功删除的数量
|
||||||
|
"errors": [] // 失败原因列表 (如 ID 不存在)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
## 1. 创建知识库 - `create`
|
||||||
|
**接口描述**: 创建一个新的知识库(Dataset),用于存储和检索文档数据。支持配置嵌入模型(Embedding Model)、解析方法、权限范围等。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| name | string | 是 | - | **知识库名称**。在同一个租户(Tenant)内必须唯一。 |
|
||||||
|
| avatar | string | 否 | "" | **知识库头像**。Base64 编码的图片字符串。 |
|
||||||
|
| description | string | 否 | "" | **描述信息**。用于说明知识库的用途或内容概要。 |
|
||||||
|
| embedding_model | string | 否 | (系统默认) | **嵌入模型名称** (例如 `BAAI/bge-large-zh-v1.5`)。若不传,则自动使用系统设置的默认 Embedding 模型。 |
|
||||||
|
| permission | string | 否 | "me" | **可见权限**。`me`: 仅自己可见;`team`: 团队内所有成员可见。 |
|
||||||
|
| chunk_method | string | 否 | "naive" | **默认分块解析方法**。当上传文件未指定解析方式时使用。可选值: `naive` (通用), `manual` (手动), `qa` (Q&A拆分), `table` (表格), `paper` (论文), `book` (书籍), `laws` (法律), `presentation` (PPT), `picture` (图片), `one` (单文档), `email` (邮件)。 |
|
||||||
|
| parser_config | object | 否 | (见下文) | **解析器详细配置**。根据 `chunk_method` 的不同而变化。 |
|
||||||
|
|
||||||
|
**`parser_config` 默认配置参数 (Naive 通用模式)**:
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chunk_token_num | int | 512 | **切片最大 Token 数**。超过该长度会被截断到下一块。 |
|
||||||
|
| delimiter | string | "\\n" | **分段分隔符**。用于识别段落边界。 |
|
||||||
|
| layout_recognize | string | "DeepDOC" | **布局识别模型**。用于处理复杂文档结构 (如 `DeepDOC` 或 `Simple`)。 |
|
||||||
|
| html4excel | boolean | false | **Excel转HTML**。是否将 Excel 表格转为 HTML 格式进行解析。 |
|
||||||
|
| auto_keywords | int | 0 | **自动关键词抽取**。0 表示不抽取;N>0 表示为每个切片抽取 N 个关键词。 |
|
||||||
|
| auto_questions | int | 0 | **自动问题生成**。0 表示不生成;N>0 表示为每个切片生成 N 个相关问题。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "kb_uuid_12345678",
|
||||||
|
"name": "企业产品手册",
|
||||||
|
"avatar": "",
|
||||||
|
"tenant_id": "tenant_001",
|
||||||
|
"description": "存放所有产品相关的说明文档",
|
||||||
|
"embedding_model": "BAAI/bge-large-zh-v1.5",
|
||||||
|
"permission": "me",
|
||||||
|
"chunk_method": "naive",
|
||||||
|
"parser_config": {
|
||||||
|
"chunk_token_num": 512,
|
||||||
|
"delimiter": "\n",
|
||||||
|
"layout_recognize": "DeepDOC",
|
||||||
|
"html4excel": false,
|
||||||
|
"auto_keywords": 0,
|
||||||
|
"auto_questions": 0
|
||||||
|
},
|
||||||
|
"chunk_count": 0,
|
||||||
|
"document_count": 0,
|
||||||
|
"create_time": 1715623400000,
|
||||||
|
"update_time": 1715624500000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 删除知识库 - `delete`
|
||||||
|
**接口描述**: 批量删除一个或多个知识库。删除知识库将连带删除其中的所有文档和索引数据,**不可恢复**。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/datasets`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ids | array<string> | 是 | **ID 列表**。指定要删除的知识库 ID。如果传递 `null`,则会**清空当前租户下所有**知识库(高危操作,请谨慎使用)。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": ["kb_id_101", "kb_id_102"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "Successfully deleted 2 datasets, 0 failed...",
|
||||||
|
"data": {
|
||||||
|
"success_count": 2, // 成功删除的数量
|
||||||
|
"errors": [] // 失败的 ID 及原因列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 获取知识库列表 - `list_datasets`
|
||||||
|
**接口描述**: 获取当前用户(及团队)有权限访问的知识库列表。支持分页、排序和筛选。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | **页码**。从 1 开始。 |
|
||||||
|
| page_size | int | 否 | 30 | **每页条数**。 |
|
||||||
|
| orderby | string | 否 | "create_time" | **排序字段**。可选值: `create_time` (创建时间), `update_time` (更新时间), `document_count` (文档数)。 |
|
||||||
|
| desc | boolean | 否 | true | **是否降序**。`true`: 降序 (最新的在前); `false`: 升序。 |
|
||||||
|
| name | string | 否 | - | **名称筛选**。支持模糊匹配。 |
|
||||||
|
| id | string | 否 | - | **ID 筛选**。精确匹配知识库 ID。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "kb_uuid_123",
|
||||||
|
"name": "HR 政策库",
|
||||||
|
"document_count": 12, // 包含的文档数量
|
||||||
|
"token_num": 10240, // 总 Token 数
|
||||||
|
"chunk_count": 150, // 总切片数
|
||||||
|
"create_time": 1715623400000,
|
||||||
|
"permission": "team",
|
||||||
|
"embedding_model": "BAAI/bge-large-zh-v1.5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1 // 匹配查询条件的总记录数 (用户分页计算)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 更新知识库配置 - `update`
|
||||||
|
**接口描述**: 更新指定知识库的配置信息。注意:如果知识库内已有解析过的切片,通常不允许修改嵌入模型 (`embedding_model`)。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(以下所有字段均为可选,仅传递需要修改的字段即可)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| name | string | - | **新名称**。需保持租户内唯一。 |
|
||||||
|
| avatar | string | - | **新头像**。Base64 字符串。 |
|
||||||
|
| description | string | - | **新描述**。 |
|
||||||
|
| permission | string | - | **新权限**。`me` 或 `team`。 |
|
||||||
|
| embedding_model | string | - | **嵌入模型**。**注意**: 仅当知识库为空(chunk_count=0)时才允许修改。 |
|
||||||
|
| chunk_method | string | - | **默认解析方法**。修改后将应用于后续新上传的文件 (旧文件解析方式不变)。 |
|
||||||
|
| parser_config | object | - | **解析器配置**。全量覆盖旧配置 (结构参考 create 接口)。 |
|
||||||
|
| pagerank | int | 0 | **PageRank 权重**。仅在使用 Elasticsearch 引擎且需调整图谱权重时设置。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "kb_uuid_...",
|
||||||
|
"name": "新名称",
|
||||||
|
"update_time": 1715629999000,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 获取知识图谱数据 - `knowledge_graph`
|
||||||
|
**接口描述**: 获取知识库构建的知识图谱数据,包含节点(Nodes)和边(Edges),用于前端可视化展示(如 ECharts 力导向图)。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/knowledge_graph`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"graph": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "node_1",
|
||||||
|
"label": "人工智能", // 节点显示的文本
|
||||||
|
"pagerank": 0.05, // PageRank 权重 (决定节点大小)
|
||||||
|
"color": "#fcb", // 节点颜色
|
||||||
|
"img": "" // 节点图标 (如有)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "node_2",
|
||||||
|
"label": "机器学习",
|
||||||
|
"pagerank": 0.03,
|
||||||
|
"color": "#e2b"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"source": "node_1", // 起始节点 ID
|
||||||
|
"target": "node_2", // 目标节点 ID
|
||||||
|
"weight": 0.8, // 边权重 (决定连线粗细)
|
||||||
|
"label": "includes" // 关系名称 (显示在连线上)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mind_map": { // 思维导图结构的保留字段 (通常用于脑图展示)
|
||||||
|
"root": {
|
||||||
|
"id": "root_node",
|
||||||
|
"children": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 清空知识图谱数据 - `delete_knowledge_graph`
|
||||||
|
**接口描述**: 删除指定知识库中已生成的知识图谱索引数据(包括所有实体节点和关系边)。
|
||||||
|
**注意**: 此操作**不会**删除原始文档或普通的向量索引,仅仅是重置图谱结构。如果需要重新生成图谱,请再次调用 `chunk` 相关接口或使用 `run_graphrag`。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/knowledge_graph`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Body Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 运行/触发 GraphRAG 索引任务 - `run_graphrag`
|
||||||
|
**接口描述**: 触发后台异步任务,对知识库中的文档进行 GraphRAG 索引构建。此过程会使用 LLM 抽取实体(Entities)和关系(Relationships),并构建全局社区摘要。
|
||||||
|
**前提条件**: 知识库中必须包含已解析的文档。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/run_graphrag`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(Body 可为空 `{}`, 后续版本将扩展以下配置参数)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| entity_types | array | ["organization", "person", "geo", "event"] | **(预留)** 指定要抽取的实体类型列表。 |
|
||||||
|
| method | string | "light" | **(预留)** 构建模式: `light` (轻量级), `general` (标准), `complex` (深度)。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"graphrag_task_id": "task_uuid_12345678" // 异步任务 ID,用于后续追踪进度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 运行/触发 RAPTOR 递归摘要任务 - `run_raptor`
|
||||||
|
**接口描述**: 触发后台异步任务,对知识库中的文档运行 RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval) 算法。
|
||||||
|
**功能说明**: 该算法会递归地对文档块进行聚类和摘要,生成多层级的树状索引,显著提升对长文档和复杂问题的回答能力。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/run_raptor`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(Body 可为空 `{}`, 后续版本将扩展以下配置参数)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| max_cluster | int | 64 | **(预留)** 最大聚类数。 |
|
||||||
|
| prompt | string | (内置摘要提示词) | **(预留)** 用于生成摘要的 Prompt。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"raptor_task_id": "task_uuid_87654321" // 异步任务 ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 查询 GraphRAG 任务进度 - `trace_graphrag`
|
||||||
|
**接口描述**: 查询指定知识库当前 **GraphRAG** 索引构建任务的实时状态。支持长轮询机制监测进度。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/trace_graphrag`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "task_uuid_12345678", // 任务 ID
|
||||||
|
"doc_id": "doc_uuid_...", // 当前正在处理的文档 ID (如果是多文档任务)
|
||||||
|
"from_page": 0, // 当前处理的起始页码
|
||||||
|
"to_page": 10, // 当前处理的结束页码
|
||||||
|
"progress": 0.45, // **总进度** (0.0 ~ 1.0)。0.0: 未开始/刚开始; 1.0: 完成; -1.0: 失败。
|
||||||
|
"progress_msg": "Extracting entities from chunk 25...", // **当前状态描述**。用于前端展示 Loading 提示。
|
||||||
|
"create_time": 1715623400000,
|
||||||
|
"update_time": 1715624500000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 查询 RAPTOR 任务进度 - `trace_raptor`
|
||||||
|
**接口描述**: 查询指定知识库当前 **RAPTOR** 递归摘要任务的实时状态。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/trace_raptor`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | 知识库 ID |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "task_uuid_87654321",
|
||||||
|
"progress": 1.0, // 进度值。1.0 表示树构建完成。
|
||||||
|
"progress_msg": "Tree construction completed.", // 状态消息。
|
||||||
|
"create_time": 1715629000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,757 @@
|
|||||||
|
## 1. 上传文档 - `upload`
|
||||||
|
**接口描述**: 向指定的知识库上传一个或多个文档文件。上传后,文档将立即被存入文件系统/对象存储,并在数据库中创建记录。默认解析状态为 `UNSTART` (未开始),解析配置将继承自 KnowledgeBase 的默认设置。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
**Content-Type**: `multipart/form-data`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。指定文档归属的知识库。 |
|
||||||
|
|
||||||
|
#### Form Data Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file | file | 是 | **文件二进制流**。支持多文件上传 (Multiple Files)。<br>支持格式: PDF, DOCX, TXT, MD, CS, HTML, CSV, XLSX, PPTX 等。<br>单文件大小限制请参考系统配置 (默认通常为 10MB/100MB)。 |
|
||||||
|
| parent_path | string | 否 | **父级目录路径**。类似于文件系统的文件夹结构,默认为 `/`。如果指定 (如 `/docs/v1/`),文档将在该虚拟路径下列出。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003",
|
||||||
|
"thumbnail": null,
|
||||||
|
"dataset_id": "d1234567890abcdef1234567890abcde",
|
||||||
|
"chunk_method": "naive",
|
||||||
|
"pipeline_id": null,
|
||||||
|
"parser_config": {
|
||||||
|
"chunk_token_num": 512,
|
||||||
|
"delimiter": "\\n",
|
||||||
|
"layout_recognize": "DeepDOC",
|
||||||
|
"html4excel": false,
|
||||||
|
"auto_keywords": 0,
|
||||||
|
"auto_questions": 0,
|
||||||
|
"topn_tags": 3,
|
||||||
|
"raptor": {
|
||||||
|
"use_raptor": false
|
||||||
|
},
|
||||||
|
"graphrag": {
|
||||||
|
"use_graphrag": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_type": "local",
|
||||||
|
"type": "pdf",
|
||||||
|
"created_by": "user_id_123",
|
||||||
|
"name": "UserGuide_v2.pdf",
|
||||||
|
"location": "UserGuide_v2.pdf",
|
||||||
|
"size": 102400,
|
||||||
|
"token_count": 0,
|
||||||
|
"chunk_count": 0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"progress_msg": "",
|
||||||
|
"process_begin_at": null,
|
||||||
|
"process_duration": 0.0,
|
||||||
|
"meta_fields": {},
|
||||||
|
"suffix": "pdf",
|
||||||
|
"run": "UNSTART",
|
||||||
|
"status": "1",
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715623400123,
|
||||||
|
"update_date": "2024-05-13 10:03:20"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 获取文档列表 - `list_docs`
|
||||||
|
**接口描述**: 查询知识库下的文档列表。支持分页检索、关键词搜索、状态筛选等功能。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | **页码**。从 1 开始计数。 |
|
||||||
|
| page_size | int | 否 | 30 | **每页数量**。 |
|
||||||
|
| orderby | string | 否 | "create_time" | **排序字段**。支持 `create_time` (创建时间), `name` (文件名), `size` (大小) 等。 |
|
||||||
|
| desc | boolean | 否 | true | **是否降序**。`true` (最新/最大在前), `false` (最旧/最小在前)。 |
|
||||||
|
| id | string | 否 | - | **精确筛选 ID**。仅返回指定 ID 的文档。 |
|
||||||
|
| name | string | 否 | - | **精确筛选文件名**。仅返回指定名称的文档。 |
|
||||||
|
| keywords | string | 否 | - | **模糊搜索**。匹配文档名称包含该关键词的记录。 |
|
||||||
|
| suffix | array | 否 | - | **文件后缀筛选** (如 `pdf`, `docx`)。 |
|
||||||
|
| run | array | 否 | - | **运行状态筛选**。可选值: `UNSTART`, `RUNNING`, `CANCEL`, `DONE`, `FAIL`。 |
|
||||||
|
| create_time_from | int | 否 | 0 | **起始时间戳** (毫秒)。查询在此时间之后创建的文档。 |
|
||||||
|
| create_time_to | int | 否 | 0 | **结束时间戳** (毫秒)。查询在此时间之前创建的文档。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total": 128,
|
||||||
|
"docs": [
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003",
|
||||||
|
"thumbnail": null,
|
||||||
|
"dataset_id": "d1234567890abcdef1234567890abcde",
|
||||||
|
"chunk_method": "naive",
|
||||||
|
"pipeline_id": null,
|
||||||
|
"parser_config": {
|
||||||
|
"chunk_token_num": 512,
|
||||||
|
"delimiter": "\\n",
|
||||||
|
"layout_recognize": "DeepDOC",
|
||||||
|
"html4excel": false,
|
||||||
|
"auto_keywords": 0,
|
||||||
|
"auto_questions": 0,
|
||||||
|
"topn_tags": 3,
|
||||||
|
"raptor": {
|
||||||
|
"use_raptor": false
|
||||||
|
},
|
||||||
|
"graphrag": {
|
||||||
|
"use_graphrag": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_type": "local",
|
||||||
|
"type": "pdf",
|
||||||
|
"created_by": "user_id_123",
|
||||||
|
"name": "UserGuide_v2.pdf",
|
||||||
|
"location": "UserGuide_v2.pdf",
|
||||||
|
"size": 102400,
|
||||||
|
"token_count": 45000,
|
||||||
|
"chunk_count": 120,
|
||||||
|
"progress": 1.0,
|
||||||
|
"progress_msg": "Parsing finished",
|
||||||
|
"process_begin_at": "2024-05-13 10:05:00",
|
||||||
|
"process_duration": 45.2,
|
||||||
|
"meta_fields": {
|
||||||
|
"author": "RAGFlow Team",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"suffix": "pdf",
|
||||||
|
"run": "DONE",
|
||||||
|
"status": "1",
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715623450000,
|
||||||
|
"update_date": "2024-05-13 10:05:45"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 更新文档信息 - `update_doc`
|
||||||
|
**接口描述**: 更新文档的名称、状态或解析配置。
|
||||||
|
**特别注意**: 如果修改了 `chunk_method` 或 `parser_config`,后端会自动将 `run` 状态重置为 `UNSTART`,并清除已有的 chunk 数据,等待重新解析。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(仅需传递要修改的字段)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| name | string | **新文档名称**。需包含文件后缀且不能改变原始文件类型 (如从 `.pdf` 改为 `.txt` 会导致错误)。 |
|
||||||
|
| enabled | boolean | **启用/禁用**。`true`: 启用 (DEFAULT, 对应 status="1"); `false`: 禁用 (对应 status="0")。禁用后该文档不参与检索。 |
|
||||||
|
| chunk_method | string | **解析方法**。可选值: `naive`, `manual`, `qa`, `table`, `paper`, `book`, `laws`, `presentation`, `picture`, `one`, `knowledge_graph`, `email`。 |
|
||||||
|
| parser_config | object | **解析器详细配置**。应与 `chunk_method` 匹配。以下列出 `naive` (通用) 方法的完整配置参数。 |
|
||||||
|
|
||||||
|
**parser_config (Naive 模式全量参数)**:
|
||||||
|
| 参数名 | 类型 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chunk_token_num | int | 512 | **切片最大 Token 数**。 |
|
||||||
|
| delimiter | string | "\\n" | **分段符**。支持转义字符。 |
|
||||||
|
| layout_recognize | string | "DeepDOC" | **布局识别模型**。可选 `DeepDOC` 或 `Simple`。 |
|
||||||
|
| html4excel | boolean | false | **Excel转HTML**。是否将 Excel 解析为 HTML 表格。 |
|
||||||
|
| auto_keywords | int | 0 | **自动关键词数量**。0 表示不抽取。 |
|
||||||
|
| auto_questions | int | 0 | **自动问题数量**。0 表示不生成。 |
|
||||||
|
| topn_tags | int | 3 | **自动标签数量**。 |
|
||||||
|
| raptor | object | `{ "use_raptor": false }` | **RAPTOR 配置**。设置 `use_raptor: true` 可开启递归摘要索引。 |
|
||||||
|
| graphrag | object | `{ "use_graphrag": false }` | **GraphRAG 配置**。设置 `use_graphrag: true` 可开启图谱增强。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003",
|
||||||
|
"thumbnail": null,
|
||||||
|
"dataset_id": "d1234567890abcdef1234567890abcde",
|
||||||
|
"chunk_method": "naive",
|
||||||
|
"pipeline_id": null,
|
||||||
|
"parser_config": {
|
||||||
|
"chunk_token_num": 1024,
|
||||||
|
"delimiter": "\\n",
|
||||||
|
"layout_recognize": "DeepDOC",
|
||||||
|
"html4excel": false,
|
||||||
|
"auto_keywords": 0,
|
||||||
|
"auto_questions": 0,
|
||||||
|
"topn_tags": 3,
|
||||||
|
"raptor": {
|
||||||
|
"use_raptor": false
|
||||||
|
},
|
||||||
|
"graphrag": {
|
||||||
|
"use_graphrag": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_type": "local",
|
||||||
|
"type": "pdf",
|
||||||
|
"created_by": "user_id_123",
|
||||||
|
"name": "Renamed_Guide.pdf",
|
||||||
|
"location": "UserGuide_v2.pdf",
|
||||||
|
"size": 102400,
|
||||||
|
"token_count": 45000,
|
||||||
|
"chunk_count": 0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"progress_msg": "",
|
||||||
|
"process_begin_at": null,
|
||||||
|
"process_duration": 0.0,
|
||||||
|
"meta_fields": {},
|
||||||
|
"suffix": "pdf",
|
||||||
|
"run": "UNSTART",
|
||||||
|
"status": "0",
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715629999000,
|
||||||
|
"update_date": "2024-05-13 12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 删除文档 - `delete`
|
||||||
|
**接口描述**: 物理删除一个或多个文档。此操作不可恢复,将同时删除数据库记录、MinIO 中的源文件以及 Elasticsearch 中的所有相关切片索引。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ids | array<string> | 是 | **文档 ID 列表**。必须指定要删除的文档 ID。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 下载/预览原始文件 - `download`
|
||||||
|
**接口描述**: 获取文档的原始二进制文件流。响应头将会包含 `Content-Disposition` 字段,指示浏览器以附件形式下载。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/octet-stream`
|
||||||
|
**Content-Disposition**: `attachment; filename="UserGuide_v2.pdf"`
|
||||||
|
|
||||||
|
*(直接返回文件的二进制数据流)*
|
||||||
|
|
||||||
|
|
||||||
|
## 6. 触发/重试文档解析 - `parse`
|
||||||
|
**接口描述**: 手动触发文档的解析任务。通常在上传文件后、或修改了解析配置(如 `chunk_method`)后调用此接口。支持批量触发。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/chunks`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| document_ids | array<string> | 是 | **文档 ID 列表**。指定需要(重新)解析的文档 ID。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"document_ids": ["doc_id_1", "doc_id_2"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 停止文档解析 - `stop_parsing`
|
||||||
|
**接口描述**: 停止当前正在进行的文档解析任务。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/chunks`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| document_ids | array<string> | 是 | **文档 ID 列表**。指定要停止解析的任务。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 获取切片列表 - `list_chunks`
|
||||||
|
**接口描述**: 获取指定文档已解析出的切片(Chunk)列表。支持分页和关键词搜索。返回结果包含文档的详细元数据和具体的切片内容。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| page | int | 否 | 1 | **页码**。 |
|
||||||
|
| page_size | int | 否 | 30 | **每页数量**。 |
|
||||||
|
| keywords | string | 否 | - | **搜索关键词**。在切片内容中进行全文检索。 |
|
||||||
|
| id | string | 否 | - | **精确切片 ID**。若指定,则只返回该 ID 对应的切片。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total": 150,
|
||||||
|
"chunks": [
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003_0",
|
||||||
|
"content": "RAGFlow 是一款基于深度文档理解的开源 RAG(检索增强生成)引擎。它旨在为各种规模的企业提供精简的 RAG 工作流。RAGFlow 结合了传统文档处理的稳健性与现代大语言模型(LLM)的生成能力,确保在处理复杂格式数据(如 PDF 表格、扫描件等)时依然能保持极高的召回率和准确性。",
|
||||||
|
"document_id": "doc_uuid_123",
|
||||||
|
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"important_keywords": ["RAGFlow", "开源", "深度文档理解", "LLM"],
|
||||||
|
"questions": ["什么是 RAGFlow?", "RAGFlow 的主要特点是什么?"],
|
||||||
|
"image_id": "",
|
||||||
|
"dataset_id": "kb_uuid_456",
|
||||||
|
"available": true,
|
||||||
|
"positions": [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003_1",
|
||||||
|
"content": "主要特性:\n1. **深度文档解析**:内置 DeepDOC 识别引擎,精准还原表格、段落结构。\n2. **多路召回**:支持关键词 + 向量的混合检索。\n3. **可视化编排**:提供基于 Graph 的工作流编排能力。",
|
||||||
|
"document_id": "doc_uuid_123",
|
||||||
|
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"important_keywords": ["DeepDOC", "混合检索", "可视化编排"],
|
||||||
|
"questions": [],
|
||||||
|
"image_id": "img_uuid_789",
|
||||||
|
"dataset_id": "kb_uuid_456",
|
||||||
|
"available": true,
|
||||||
|
"positions": [2]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"doc": {
|
||||||
|
"id": "doc_uuid_123",
|
||||||
|
"name": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"chunk_count": 150,
|
||||||
|
"token_count": 45000,
|
||||||
|
"chunk_method": "naive",
|
||||||
|
"run": "DONE",
|
||||||
|
"status": "1",
|
||||||
|
"progress": 1.0,
|
||||||
|
"progress_msg": "Parsing finished",
|
||||||
|
"process_begin_at": "2024-05-13 10:05:00",
|
||||||
|
"process_duration": 45.2,
|
||||||
|
"meta_fields": {
|
||||||
|
"author": "RAGFlow Team",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715623450000,
|
||||||
|
"update_date": "2024-05-13 10:05:45",
|
||||||
|
"dataset_id": "kb_uuid_456"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 手动新增切片 - `add_chunk`
|
||||||
|
**接口描述**: 向指定文档中手动添加一个新的切片。系统会自动计算该切片的向量嵌入 (Embedding)。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| content | string | 是 | **切片内容**。手动输入的文本内容。 |
|
||||||
|
| important_keywords | array<string> | 否 | **重要关键词**。用于关键词检索增强。 |
|
||||||
|
| questions | array<string> | 否 | **预设问题**。用于 Q&A 检索模式增强。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"chunk": {
|
||||||
|
"id": "new_chunk_uuid_999",
|
||||||
|
"content": "这是管理员手动添加的一条补充切片,用于修正文档中缺失的关键信息。",
|
||||||
|
"document_id": "doc_uuid_123",
|
||||||
|
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"important_keywords": ["手动添加", "补充信息"],
|
||||||
|
"questions": ["如何手动添加切片?"],
|
||||||
|
"image_id": "",
|
||||||
|
"dataset_id": "kb_uuid_456",
|
||||||
|
"available": true,
|
||||||
|
"positions": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 修改切片信息 - `update_chunk`
|
||||||
|
**接口描述**: 修改已存在的切片内容、关键词、可用状态等。修改内容后,系统会自动重新计算向量。
|
||||||
|
**请求方法**: `PUT`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
| chunk_id | string | 是 | **切片 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
*(以下字段均为可选,仅传递需修改的字段)*
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| content | string | **新的切片内容**。 |
|
||||||
|
| important_keywords | array<string> | **更新关键词列表**。覆盖原有列表。 |
|
||||||
|
| available | boolean | **启用/禁用**。`true`: 启用 (默认); `false`: 禁用 (检索时将忽略此切片)。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 删除切片 - `rm_chunk`
|
||||||
|
**接口描述**: 批量删除文档中的指定切片。
|
||||||
|
**请求方法**: `DELETE`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
| document_id | string | 是 | **文档 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| chunk_ids | array<string> | 是 | **切片 ID 列表**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "deleted 2 chunks",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 12. 获取元数据摘要 - `metadata_summary`
|
||||||
|
**接口描述**: 获取知识库中所有文档的元数据摘要信息。通常用于前端展示知识库的数据分布概况,例如不同文件类型的数量统计、文件状态分布等。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/metadata/summary`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"summary": {
|
||||||
|
"total_doc_count": 120,
|
||||||
|
"total_token_count": 500000,
|
||||||
|
"file_type_distribution": {
|
||||||
|
"pdf": 80,
|
||||||
|
"docx": 30,
|
||||||
|
"txt": 10
|
||||||
|
},
|
||||||
|
"status_distribution": {
|
||||||
|
"1": 118, // 正常启用
|
||||||
|
"0": 2 // 禁用
|
||||||
|
},
|
||||||
|
"custom_metadata": {
|
||||||
|
"author": {
|
||||||
|
"Alice": 50,
|
||||||
|
"Bob": 30
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"HR": 20,
|
||||||
|
"Engineering": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 批量更新元数据 - `metadata_batch_update`
|
||||||
|
**接口描述**: 对知识库中的文档进行批量元数据修改。支持基于复杂的条件筛选文档,然后执行批量更新或删除元数据字段的操作。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/datasets/<dataset_id>/metadata/update`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| dataset_id | string | 是 | **知识库 ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| selector | object | 否 | **筛选器**。定义要更新哪些文档。如果不传,可能作用于全量文档(请谨慎)。 |
|
||||||
|
| updates | array | 否 | **更新操作列表**。包含 `key` 和 `value`。 |
|
||||||
|
| deletes | array | 否 | **删除操作列表**。包含 `key`。 |
|
||||||
|
|
||||||
|
**Request Example (复杂场景)**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"selector": {
|
||||||
|
"document_ids": ["doc_id_101", "doc_id_102"],
|
||||||
|
"metadata_condition": {
|
||||||
|
"logic": "and",
|
||||||
|
"conditions": [
|
||||||
|
{"key": "author", "value": "OldName", "operator": "eq"},
|
||||||
|
{"key": "status", "value": "draft", "operator": "eq"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updates": [
|
||||||
|
{"key": "author", "value": "Admin"},
|
||||||
|
{"key": "reviewed_by", "value": "ManagerA"}
|
||||||
|
],
|
||||||
|
"deletes": [
|
||||||
|
{"key": "temp_tag"},
|
||||||
|
{"key": "draft_flag"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"updated": 2, // 实际更新成功的文档数量
|
||||||
|
"matched_docs": 2 // 匹配到的文档数量
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 检索测试 (Hit Test) - `retrieval_test`
|
||||||
|
**接口描述**: 在指定的知识库中进行模拟检索测试。此接口用于验证分段(Chunk)质量、检索参数(相似度阈值、Top K)的效果,是调试 RAG 效果的核心工具。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/retrieval`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
**注意**: 即使是简单的查询,由于包含较多配置参数,本接口也设计为 `POST` 请求。
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| dataset_ids | array<string> | 是 | - | **目标知识库 ID 列表**。支持跨多个知识库检索。 |
|
||||||
|
| question | string | 是 | - | **用户查询问题**。 |
|
||||||
|
| similarity_threshold | float | 否 | 0.2 | **相似度阈值**。低于此分数的 Chunk 将被过滤。 |
|
||||||
|
| vector_similarity_weight | float | 否 | 0.3 | **向量权重**。混合检索时,向量检索结果的权重 (0~1)。剩余权重归于关键词检索。 |
|
||||||
|
| top_k | int | 否 | 1024 | **初筛数量**。向量检索返回的候选切片数量。 |
|
||||||
|
| rerank_id | string | 否 | - | **重排模型 ID**。若指定,将对检索结果进行 Rerank 二次排序。 |
|
||||||
|
| highlight | boolean | 否 | true | **高亮匹配**。是否在返回内容中高亮关键词。 |
|
||||||
|
| keyword | boolean | 否 | false | **关键词增强**。是否使用 LLM 提取问题关键词以增强检索。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total": 15,
|
||||||
|
"chunks": [
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003_12",
|
||||||
|
"content": "RAGFlow 支持多种文档解析模式,其中 DeepDOC 模式特别适合处理包含大量表格和扫描件的 PDF 文档。它使用深度学习模型识别文档布局,精准提取表格内容。",
|
||||||
|
"document_id": "doc_uuid_123",
|
||||||
|
"dataset_id": "kb_uuid_456",
|
||||||
|
"document_name": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"document_keyword": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"similarity": 0.88,
|
||||||
|
"vector_similarity": 0.85,
|
||||||
|
"term_similarity": 0.92,
|
||||||
|
"index": 12,
|
||||||
|
"highlight": "RAGFlow 支持多种<em>文档解析模式</em>,其中 <em>DeepDOC</em> 模式特别适合处理包含大量表格和扫描件的 PDF 文档。",
|
||||||
|
"important_keywords": ["DeepDOC", "PDF"],
|
||||||
|
"questions": ["DeepDOC 模式有什么用?"],
|
||||||
|
"image_id": "",
|
||||||
|
"positions": [12]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003_15",
|
||||||
|
"content": "如果文档主要由纯文本构成,建议使用 Naive 模式。该模式解析速度快,适合通用场景。",
|
||||||
|
"document_id": "doc_uuid_123",
|
||||||
|
"dataset_id": "kb_uuid_456",
|
||||||
|
"document_name": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"document_keyword": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"similarity": 0.45,
|
||||||
|
"vector_similarity": 0.40,
|
||||||
|
"term_similarity": 0.50,
|
||||||
|
"index": 15,
|
||||||
|
"highlight": "如果文档主要由纯文本构成,建议使用 <em>Naive</em> 模式。",
|
||||||
|
"important_keywords": ["Naive", "纯文本"],
|
||||||
|
"questions": [],
|
||||||
|
"image_id": "",
|
||||||
|
"positions": [15]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"doc_aggs": [
|
||||||
|
{
|
||||||
|
"doc_name": "RAGFlow_UserGuide_v2.pdf",
|
||||||
|
"doc_id": "doc_uuid_123",
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
# RAGFlow 文件管理接口详解 (File Management API)
|
||||||
|
|
||||||
|
## 1. 上传文件 - `upload`
|
||||||
|
**接口描述**: 上传一个或多个文件到指定文件夹。支持多文件上传 (Multipart)。上传成功后,文件将存储在 MinIO/S3 中,并返回文件元数据列表。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/upload`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
**Content-Type**: `multipart/form-data`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Form Data Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file | file | 是 | **文件二进制流**。支持多文件上传。 |
|
||||||
|
| parent_id | string | 否 | **父级目录 ID**。如果省略,默认上传到根目录 (root)。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "user_uuid_789",
|
||||||
|
"type": "pdf",
|
||||||
|
"name": "ProjectReport.pdf",
|
||||||
|
"location": "ProjectReport.pdf",
|
||||||
|
"size": 204800,
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715623400123,
|
||||||
|
"update_date": "2024-05-13 10:03:20"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 新建文件夹 - `create`
|
||||||
|
**接口描述**: 在指定父目录下创建一个新的文件夹(逻辑目录)。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/create`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| name | string | 是 | **文件夹名称**。同一目录下不可重名。 |
|
||||||
|
| parent_id | string | 否 | **父级目录 ID**。省略则默认为根目录。 |
|
||||||
|
| type | string | 是 | **类型**。固定值为 `FOLDER` 创建文件夹。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Year2024_Reports",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"type": "FOLDER"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "folder_uuid_abc",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "user_uuid_789",
|
||||||
|
"name": "Year2024_Reports",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1715623500000,
|
||||||
|
"create_date": "2024-05-13 10:05:00",
|
||||||
|
"update_time": 1715623500000,
|
||||||
|
"update_date": "2024-05-13 10:05:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 获取文件列表 - `list_files`
|
||||||
|
**接口描述**: 分页获取指定文件夹下的文件和子文件夹列表。支持按名称模糊搜索。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/list`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| parent_id | string | 否 | (Root) | **父级目录 ID**。指定要查看的目录 ID。 |
|
||||||
|
| keywords | string | 否 | - | **搜索关键词**。按文件名模糊搜索。 |
|
||||||
|
| page | int | 否 | 1 | **页码**。 |
|
||||||
|
| page_size | int | 否 | 15 | **每页数量**。 |
|
||||||
|
| orderby | string | 否 | "create_time" | **排序字段**。 |
|
||||||
|
| desc | boolean | 否 | true | **是否降序**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total": 25,
|
||||||
|
"parent_folder": {
|
||||||
|
"id": "root_folder_id_123",
|
||||||
|
"parent_id": "",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "system",
|
||||||
|
"name": "ROOT",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1710000000000,
|
||||||
|
"create_date": "2024-03-01 00:00:00",
|
||||||
|
"update_time": 1710000000000,
|
||||||
|
"update_date": "2024-03-01 00:00:00"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"id": "folder_uuid_abc",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "user_uuid_789",
|
||||||
|
"name": "Year2024_Reports",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1715623500000,
|
||||||
|
"create_date": "2024-05-13 10:05:00",
|
||||||
|
"update_time": 1715623500000,
|
||||||
|
"update_date": "2024-05-13 10:05:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e457f92e3c0411ef8d4c0242ac120003",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "user_uuid_789",
|
||||||
|
"name": "ProjectReport.pdf",
|
||||||
|
"location": "ProjectReport.pdf",
|
||||||
|
"size": 204800,
|
||||||
|
"type": "pdf",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1715623400123,
|
||||||
|
"create_date": "2024-05-13 10:03:20",
|
||||||
|
"update_time": 1715623400123,
|
||||||
|
"update_date": "2024-05-13 10:03:20"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 获取文件流 (下载) - `get`
|
||||||
|
**接口描述**: 通过文件 ID 下载文件内容。不同于获取元数据,该接口直接返回文件的二进制流(Octet-stream 或 Image 等)。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/get/<file_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_id | string | 是 | **文件 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/octet-stream` (或具体 MIME 类型如 `image/png`)
|
||||||
|
|
||||||
|
*(返回二进制文件流)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 下载附件 - `download_attachment`
|
||||||
|
**接口描述**: 这是一个通用的附件下载接口,通常用于系统内部引用或特定路径的下载。它使用 `attachment_id`(通常对应 MinIO 中的存储路径/Key)来检索文件。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/download/<attachment_id>`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| attachment_id | string | 是 | **附件 ID / 存储 Key**。通常对应底层存储的唯一标识符。 |
|
||||||
|
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| ext | string | 否 | "markdown" | **文件扩展名**。用于设置响应头中的 Content-Type。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/octet-stream` (或根据 ext 参数推断)
|
||||||
|
|
||||||
|
*(返回二进制文件流)*
|
||||||
|
|
||||||
|
|
||||||
|
## 6. 重命名文件/文件夹 - `rename`
|
||||||
|
**接口描述**: 修改文件或文件夹的名称。对于文件,通常不允许修改扩展名(后缀)。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/rename`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_id | string | 是 | **目标文件/文件夹 ID**。 |
|
||||||
|
| name | string | 是 | **新名称**。需符合文件命名规范,且同一目录下不可重名。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_id": "file_uuid_123",
|
||||||
|
"name": "New_Report_Final.pdf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 移动文件/文件夹 - `move`
|
||||||
|
**接口描述**: 批量移动文件或文件夹到指定的目录 (Move)。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/mv`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| src_file_ids | array<string> | 是 | **源文件/文件夹 ID 列表**。支持批量移动。 |
|
||||||
|
| dest_file_id | string | 是 | **目标文件夹 ID**。必须是已存在的文件夹 ID。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"src_file_ids": ["file_id_1", "file_id_2"],
|
||||||
|
"dest_file_id": "folder_id_target"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 删除文件/文件夹 - `rm`
|
||||||
|
**接口描述**: 批量删除文件或文件夹。如果是文件夹,将递归删除其下的所有内容。此操作不可恢复。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/rm`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_ids | array<string> | 是 | **待删除的文件/文件夹 ID 列表**。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_ids": ["file_uuid_to_delete_1", "folder_uuid_to_delete_2"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 文件转知识库文档 - `convert`
|
||||||
|
**接口描述**: 将已上传的文件(File)导入到指定的知识库(Dataset)中,转换为文档(Document)并进行解析。这是一个“文件 -> 知识库”的桥接操作。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/file/convert`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_ids | array<string> | 是 | **源文件 ID 列表**。必须是已存在于文件管理系统中的 ID。 |
|
||||||
|
| kb_ids | array<string> | 是 | **目标知识库 ID 列表**。文件将被同时导入到这些知识库中。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_ids": ["file_uuid_pdf_1", "file_uuid_txt_2"],
|
||||||
|
"kb_ids": ["dataset_uuid_A"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "mapping_uuid_1",
|
||||||
|
"file_id": "file_uuid_pdf_1",
|
||||||
|
"document_id": "doc_uuid_created_in_kb_A",
|
||||||
|
"create_time": 1715623600123,
|
||||||
|
"create_date": "2024-05-13 10:06:40",
|
||||||
|
"update_time": 1715623600123,
|
||||||
|
"update_date": "2024-05-13 10:06:40"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mapping_uuid_2",
|
||||||
|
"file_id": "file_uuid_txt_2",
|
||||||
|
"document_id": "doc_uuid_created_in_kb_A",
|
||||||
|
"create_time": 1715623600124,
|
||||||
|
"create_date": "2024-05-13 10:06:40",
|
||||||
|
"update_time": 1715623600124,
|
||||||
|
"update_date": "2024-05-13 10:06:40"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 10. 获取根目录信息 - `get_root_folder`
|
||||||
|
**接口描述**: 获取当前用户的根目录文件夹信息。每个用户(Tenant)都有且仅有一个系统自动创建的根目录。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/root_folder`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Query Parameters
|
||||||
|
无
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"root_folder": {
|
||||||
|
"id": "root_folder_id_123",
|
||||||
|
"parent_id": "",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "system",
|
||||||
|
"name": "ROOT",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1710000000000,
|
||||||
|
"create_date": "2024-03-01 00:00:00",
|
||||||
|
"update_time": 1710000000000,
|
||||||
|
"update_date": "2024-03-01 00:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 获取父目录信息 - `get_parent_folder`
|
||||||
|
**接口描述**: 获取指定文件或文件夹的直接父级目录信息。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/parent_folder`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_id | string | 是 | **当前文件/文件夹 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"parent_folder": {
|
||||||
|
"id": "root_folder_id_123",
|
||||||
|
"parent_id": "",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "system",
|
||||||
|
"name": "ROOT",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1710000000000,
|
||||||
|
"create_date": "2024-03-01 00:00:00",
|
||||||
|
"update_time": 1710000000000,
|
||||||
|
"update_date": "2024-03-01 00:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 获取完整路径 (面包屑) - `get_all_parent_folders`
|
||||||
|
**接口描述**: 获取指定文件或文件夹的所有上级目录列表,形成完整的路径链。返回的列表顺序通常是从根目录到直接父目录(有序)。此接口常用于前端展示“面包屑导航” (Breadcrumbs)。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/file/all_parent_folder`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| file_id | string | 是 | **目标文件/文件夹 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"parent_folders": [
|
||||||
|
{
|
||||||
|
"id": "root_folder_id_123",
|
||||||
|
"parent_id": "",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "system",
|
||||||
|
"name": "ROOT",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1710000000000,
|
||||||
|
"create_date": "2024-03-01 00:00:00",
|
||||||
|
"update_time": 1710000000000,
|
||||||
|
"update_date": "2024-03-01 00:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "folder_project_a_id",
|
||||||
|
"parent_id": "root_folder_id_123",
|
||||||
|
"tenant_id": "tenant_uuid_456",
|
||||||
|
"created_by": "user_id_001",
|
||||||
|
"name": "Project A Docs",
|
||||||
|
"location": "",
|
||||||
|
"size": 0,
|
||||||
|
"type": "folder",
|
||||||
|
"source_type": "",
|
||||||
|
"create_time": 1715000000000,
|
||||||
|
"create_date": "2024-05-01 09:00:00",
|
||||||
|
"update_time": 1715000000000,
|
||||||
|
"update_date": "2024-05-01 09:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
# RAGFlow 搜索机器人 & AgentBot 接口详解 (SearchBot & AgentBot)
|
||||||
|
|
||||||
|
## 1. 搜索机器人对话 - `ask_about_embedded`
|
||||||
|
**接口描述**: 面向 **SearchBot (搜索机器人)** 的核心对话接口,通常用于嵌入式知识库问答场景。与普通 Chat 不同,它更侧重于从指定的 `kb_ids` 中直接检索答案,且鉴权使用 `Authorization: Bearer <Beta_Token>` (即 API Key)。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/searchbots/ask`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| question | string | 是 | - | **用户问题**。 |
|
||||||
|
| kb_ids | array<string> | 是 | - | **知识库 ID 列表**。限定从哪些知识库中检索。 |
|
||||||
|
| search_id | string | 否 | - | **搜索应用 ID**。如果指定,将使用该搜索应用的配置 (Search App Config)。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"question": "What is the refund policy?",
|
||||||
|
"kb_ids": ["dataset_uuid_1", "dataset_uuid_2"],
|
||||||
|
"search_id": "search_app_uuid_abc"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "", "data": {"answer": "According to the ", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "", "data": {"answer": "policy, refunds are processed within 7 days.", "reference": {"chunk_1": {"content_with_weight": "Refunds...", "doc_name": "policy.pdf"}}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "", "data": true} // 结束标志
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 获取思维导图 - `mindmap`
|
||||||
|
**接口描述**: 根据用户的查询或对话上下文,生成用于前端展示的思维导图数据结构。这通常用于帮助用户梳理复杂的搜索结果或知识结构。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/searchbots/mindmap`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| question | string | 是 | **用户问题/主题**。 |
|
||||||
|
| kb_ids | array<string> | 是 | **知识库 ID 列表**。 |
|
||||||
|
| search_id | string | 否 | **搜索应用 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"root": {
|
||||||
|
"text": "Refund Policy", // 根节点文本
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"text": "Conditions",
|
||||||
|
"children": [
|
||||||
|
{ "text": "Product defect" },
|
||||||
|
{ "text": "Shipping error" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Timeline",
|
||||||
|
"children": [
|
||||||
|
{ "text": "7-14 business days" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 获取相关推荐问题 - `related_questions_embedded`
|
||||||
|
**接口描述**: 根据用户当前的问题,生成一组相关的推荐问题 (Suggest Questions)。常用于搜索结果页底部的“猜你想问”。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/searchbots/related_questions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| question | string | 是 | **用户当前问题**。 |
|
||||||
|
| search_id | string | 否 | **搜索应用 ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
"How to apply for a refund online?",
|
||||||
|
"What items are non-refundable?",
|
||||||
|
"Contact customer support"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 获取 AgentBot 输入项 - `begin_inputs`
|
||||||
|
**接口描述**: 获取 **AgentBot** (嵌入式 Agent) 的初始化信息,特别是前置输入项 (Prolog/Inputs)。这用于在用户开始对话前,展示一个表单让用户输入必要信息(如姓名、邮箱、API Key 等),这些信息会被传递给 Agent 的 `Begin` 节点。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/agentbots/<agent_id>/inputs`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"title": "Booking Assistant",
|
||||||
|
"avatar": "http://...",
|
||||||
|
"prologue": "Welcome! Please tell me your details.",
|
||||||
|
"inputs": { // `Begin` 节点定义的输入变量
|
||||||
|
"user_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Your Name",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Contact Email",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": "chat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. AgentBot 对话交互 - `agent_bot_completions`
|
||||||
|
**接口描述**: 面向 **AgentBot** 的嵌入式对话接口。与 `agent_completions` 类似,但它专为无需登录的 C 端用户设计,通过 API Key 鉴权。它支持完整的 Agent 流程执行和流式响应。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/agentbots/<agent_id>/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| session_id | string | 是 | **会话 ID**。 |
|
||||||
|
| inputs | object | 否 | **前置输入值**。对应 `begin_inputs` 中定义的变量,如 `{"user_name": "Alice"}`。 |
|
||||||
|
| query | string | 否 | **用户输入**。 |
|
||||||
|
| stream | boolean | 否 | **是否流式**。默认 `true`。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session_id": "session_uuid_123",
|
||||||
|
"inputs": {
|
||||||
|
"user_name": "Bob"
|
||||||
|
},
|
||||||
|
"query": "I want to book a room.",
|
||||||
|
"stream": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
```text
|
||||||
|
data:{"event": "message", "data": {"content": "Hello Bob, ", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"event": "message", "data": {"content": "when do you want to check in?", "reference": {}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Agent OpenAI 兼容接口 - `agents_completion_openai_compatibility`
|
||||||
|
**接口描述**: 专门针对 Agent 的 **OpenAI 兼容** 接口。这使得外部工具可以像调用 OpenAI Chat Completion 一样调用 RAGFlow 配置好的复杂 Agent。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/agents_openai/<agent_id>/chat/completions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Path Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| agent_id | string | 是 | **Agent ID**。 |
|
||||||
|
|
||||||
|
#### Body Parameters (OpenAI Standard)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| messages | array | 是 | 包含 `role`, `content` 的消息数组。 |
|
||||||
|
| model | string | 是 | 占位符,任意字符串。 |
|
||||||
|
| stream | boolean | 否 | 默认 `true`。 |
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response - OpenAI Format)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
```text
|
||||||
|
data: {"id": "agent-chat-uuid", "object": "chat.completion.chunk", "created": 1715000000, "model": "ragflow_agent", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": null}]}
|
||||||
|
|
||||||
|
data: {"id": "agent-chat-uuid", "object": "chat.completion.chunk", "created": 1715000001, "model": "ragflow_agent", "choices": [{"index": 0, "delta": {"content": "Processing your request..."}, "finish_reason": null}]}
|
||||||
|
|
||||||
|
data: [DONE]
|
||||||
|
```
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
# RAGFlow SearchBot 补充与通用会话接口详解 (Session Extras)
|
||||||
|
|
||||||
|
## 1. 获取引用详情 - `detail_share_embedded`
|
||||||
|
**接口描述**: 当用户点击 SearchBot 回复中的引用标号 (e.g., [1]) 时,调用此接口获取该引用的详细内容(包括原文片段、来源文档名等)。此接口通常用于前端展示“引用来源”侧边栏或弹窗。它使用 API Key (Beta Token) 进行鉴权。
|
||||||
|
**请求方法**: `GET`
|
||||||
|
**接口地址**: `/api/v1/searchbots/detail`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Query Parameters
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| search_id | string | 是 | **搜索应用/SearchBot ID**。此接口需要验证调用者是否有权访问该 SearchBot。 |
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": "search_app_uuid_123",
|
||||||
|
"title": "IT Knowledge Base",
|
||||||
|
"description": "Tech support search bot",
|
||||||
|
"kb_ids": ["kb_uuid_1", "kb_uuid_2"],
|
||||||
|
"search_config": {
|
||||||
|
"top_k": 5,
|
||||||
|
"similarity_threshold": 0.5
|
||||||
|
},
|
||||||
|
// 注意:此接口目前主要返回 Search App 的详情配置,
|
||||||
|
// 前端通常使用 search_config 或其他信息来辅助展示引用。
|
||||||
|
// 具体引用内容的文本通常已包含在 `ask` 接口的 `reference` 字段中。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. SearchBot 检索测试 - `retrieval_test_embedded`
|
||||||
|
**接口描述**: 面向 SearchBot 的**检索效果测试**接口。它不通过 LLM 生成答案,而是直接返回 RAG 检索到的文档片段 (`chunks`)。这用于调试 SearchBot 的检索参数(如相似度阈值、Top-K)是否合理。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/searchbots/retrieval_test`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| kb_id | string/array | 是 | - | **知识库 ID** (或列表)。支持单个 ID 字符串或 ID 列表。 |
|
||||||
|
| question | string | 是 | - | **测试查询词**。 |
|
||||||
|
| page | int | 否 | 1 | **页码**。 |
|
||||||
|
| size | int | 否 | 30 | **每页数量**。 |
|
||||||
|
| doc_ids | array<string> | 否 | - | **限定文档 ID**。仅在指定文档中检索。 |
|
||||||
|
| similarity_threshold | float | 否 | 0.0 | **相似度阈值**。 |
|
||||||
|
| top_k | int | 否 | 1024 | **Top-K 数量**。 |
|
||||||
|
| highlight | boolean | 否 | false | **高亮匹配**。是否在返回内容中标记匹配关键词。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kb_id": ["dataset_uuid_1"],
|
||||||
|
"question": "refund policy",
|
||||||
|
"top_k": 5,
|
||||||
|
"highlight": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total": 12, // 命中总是
|
||||||
|
"chunks": [
|
||||||
|
{
|
||||||
|
"content_with_weight": "Refunds are processed within <em>7 days</em>...", // 支持高亮
|
||||||
|
"doc_name": "policy.pdf",
|
||||||
|
"doc_id": "doc_uuid_101",
|
||||||
|
"similarity": 0.92,
|
||||||
|
"img_id": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content_with_weight": "Product return guidelines...",
|
||||||
|
"doc_name": "guidelines.docx",
|
||||||
|
"doc_id": "doc_uuid_102",
|
||||||
|
"similarity": 0.88
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"labels": [] // 如果启用了查询标签功能
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 通用会话问答 - `ask_about`
|
||||||
|
**接口描述**: **内部/测试用**的通用会话问答接口。与 `ask_embedded` 不同,此接口通常用于 RAGFlow 控制台内部的“调试”或“预览”功能,鉴权依赖用户的登录 Token (User Token),且必须显式指定 `dataset_ids`。它不绑定特定的 Chat/Agent/SearchBot 配置。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/sessions/ask`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <USER_TOKEN>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| question | string | 是 | **用户问题**。 |
|
||||||
|
| dataset_ids | array<string> | 是 | **知识库 ID 列表**。必须是当前用户有权访问的知识库。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"question": "Summary of report",
|
||||||
|
"dataset_ids": ["dataset_uuid_internal_1"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Stream Response)
|
||||||
|
**Content-Type**: `text/event-stream`
|
||||||
|
|
||||||
|
```text
|
||||||
|
data:{"code": 0, "message": "", "data": {"answer": "Here is the summary:", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "", "data": {"answer": " The report indicates...", "reference": {}}}
|
||||||
|
|
||||||
|
data:{"code": 0, "message": "", "data": true} // 结束
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 通用相关问题 - `related_questions`
|
||||||
|
**接口描述**: **内部/测试用**的通用相关问题推荐接口。根据用户的问题和行业背景,利用 LLM 生成推荐问题。通常用于内部测试台。
|
||||||
|
**请求方法**: `POST`
|
||||||
|
**接口地址**: `/api/v1/sessions/related_questions`
|
||||||
|
**鉴权方式**: Header `Authorization: Bearer <USER_TOKEN>`
|
||||||
|
|
||||||
|
### 请求参数 (Request)
|
||||||
|
#### Body Parameters (JSON)
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| question | string | 是 | - | **原始问题/关键词**。 |
|
||||||
|
| industry | string | 否 | "" | **行业背景** (e.g., "Finance", "Healthcare")。帮助 LLM 生成更专业的推荐。 |
|
||||||
|
|
||||||
|
**Request Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"question": "Data privacy",
|
||||||
|
"industry": "IT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应参数 (Response)
|
||||||
|
**Content-Type**: `application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
"GDPR compliance checklist",
|
||||||
|
"Data encryption standards",
|
||||||
|
"User consent management"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# RAGFlow External API Reference (Grouped by File)
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/agents.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `list_agents` | `/api/v1/agents` | List Agents |
|
||||||
|
| `create_agent` | `/api/v1/agents` | Create Agent |
|
||||||
|
| `update_agent` | `/api/v1/agents/<agent_id>` | Update Agent |
|
||||||
|
| `delete_agent` | `/api/v1/agents/<agent_id>` | Delete Agent |
|
||||||
|
| `webhook` | `/api/v1/webhook_test/<agent_id>` | Webhook Test |
|
||||||
|
| `webhook_trace` | `/api/v1/webhook_trace/<agent_id>` | Webhook Trace |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/chat.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `create` | `/api/v1/chats` | Create Chat |
|
||||||
|
| `delete_chats` | `/api/v1/chats` | Delete Chat |
|
||||||
|
| `list_chat` | `/api/v1/chats` | List Chats |
|
||||||
|
| `update` | `/api/v1/chats/<chat_id>` | Update Chat |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/dataset.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `create` | `/api/v1/datasets` | Create Dataset |
|
||||||
|
| `delete` | `/api/v1/datasets` | Delete Dataset |
|
||||||
|
| `list_datasets` | `/api/v1/datasets` | List Datasets |
|
||||||
|
| `update` | `/api/v1/datasets/<dataset_id>` | Update Dataset |
|
||||||
|
| `knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Knowledge Graph |
|
||||||
|
| `delete_knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Delete Knowledge Graph |
|
||||||
|
| `run_graphrag` | `/api/v1/datasets/<dataset_id>/run_graphrag` | Run GraphRAG |
|
||||||
|
| `run_raptor` | `/api/v1/datasets/<dataset_id>/run_raptor` | Run Raptor |
|
||||||
|
| `trace_graphrag` | `/api/v1/datasets/<dataset_id>/trace_graphrag` | Trace GraphRAG |
|
||||||
|
| `trace_raptor` | `/api/v1/datasets/<dataset_id>/trace_raptor` | Trace Raptor |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/dify_retrieval.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `retrieval` | `/api/v1/dify/retrieval` | Dify Retrieval |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/doc.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `parse` | `/api/v1/datasets/<dataset_id>/chunks` | Parse Document Chunks |
|
||||||
|
| `stop_parsing` | `/api/v1/datasets/<dataset_id>/chunks` | Stop Parsing |
|
||||||
|
| `upload` | `/api/v1/datasets/<dataset_id>/documents` | Upload Document |
|
||||||
|
| `list_docs` | `/api/v1/datasets/<dataset_id>/documents` | List Documents |
|
||||||
|
| `delete` | `/api/v1/datasets/<dataset_id>/documents` | Delete Document |
|
||||||
|
| `update_doc` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Update Document |
|
||||||
|
| `download` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Download Document |
|
||||||
|
| `list_chunks` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | List Chunks |
|
||||||
|
| `add_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Add Chunk |
|
||||||
|
| `update_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>` | Update Chunk |
|
||||||
|
| `rm_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Remove Chunk |
|
||||||
|
| `metadata_summary` | `/api/v1/datasets/<dataset_id>/metadata/summary` | Metadata Summary |
|
||||||
|
| `metadata_batch_update` | `/api/v1/datasets/<dataset_id>/metadata/update` | Batch Update Metadata |
|
||||||
|
| `retrieval_test` | `/api/v1/retrieval` | Retrieval Test |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/files.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `get_all_parent_folders` | `/api/v1/file/all_parent_folder` | Get All Parent Folders |
|
||||||
|
| `convert` | `/api/v1/file/convert` | File Convert |
|
||||||
|
| `create` | `/api/v1/file/create` | File Create |
|
||||||
|
| `download_attachment` | `/api/v1/file/download/<attachment_id>` | Download Attachment |
|
||||||
|
| `get` | `/api/v1/file/get/<file_id>` | Get File |
|
||||||
|
| `list_files` | `/api/v1/file/list` | List Files |
|
||||||
|
| `move` | `/api/v1/file/mv` | Move File |
|
||||||
|
| `get_parent_folder` | `/api/v1/file/parent_folder` | Get Parent Folder |
|
||||||
|
| `rename` | `/api/v1/file/rename` | Rename File |
|
||||||
|
| `rm` | `/api/v1/file/rm` | Remove File |
|
||||||
|
| `get_root_folder` | `/api/v1/file/root_folder` | Get Root Folder |
|
||||||
|
| `upload` | `/api/v1/file/upload` | Upload File |
|
||||||
|
|
||||||
|
## File: `api/apps/sdk/session.py`
|
||||||
|
| Function Name | URL Pattern | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `agent_bot_completions` | `/api/v1/agentbots/<agent_id>/completions` | Agent Bot completion |
|
||||||
|
| `begin_inputs` | `/api/v1/agentbots/<agent_id>/inputs` | Get Agent Bot inputs |
|
||||||
|
| `agent_completions` | `/api/v1/agents/<agent_id>/completions` | Agent completion |
|
||||||
|
| `create_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Create Agent Session |
|
||||||
|
| `list_agent_session` | `/api/v1/agents/<agent_id>/sessions` | List Agent Sessions |
|
||||||
|
| `delete_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Delete Agent Session |
|
||||||
|
| `agents_completion_openai_compatibility` | `/api/v1/agents_openai/<agent_id>/chat/completions` | OpenAI compatible Agent completion |
|
||||||
|
| `chatbot_completions` | `/api/v1/chatbots/<dialog_id>/completions` | Chatbot completion |
|
||||||
|
| `chatbots_inputs` | `/api/v1/chatbots/<dialog_id>/info` | Chatbot info |
|
||||||
|
| `chat_completion` | `/api/v1/chats/<chat_id>/completions` | Chat completion |
|
||||||
|
| `create` | `/api/v1/chats/<chat_id>/sessions` | Create Chat Session |
|
||||||
|
| `list_session` | `/api/v1/chats/<chat_id>/sessions` | List Chat Sessions |
|
||||||
|
| `delete` | `/api/v1/chats/<chat_id>/sessions` | Delete Chat Session |
|
||||||
|
| `update` | `/api/v1/chats/<chat_id>/sessions/<session_id>` | Update Chat Session |
|
||||||
|
| `chat_completion_openai_like` | `/api/v1/chats_openai/<chat_id>/chat/completions` | OpenAI compatible Chat completion |
|
||||||
|
| `ask_about_embedded` | `/api/v1/searchbots/ask` | Searchbot Ask |
|
||||||
|
| `detail_share_embedded` | `/api/v1/searchbots/detail` | Searchbot Detail |
|
||||||
|
| `mindmap` | `/api/v1/searchbots/mindmap` | Searchbot Mindmap |
|
||||||
|
| `related_questions_embedded` | `/api/v1/searchbots/related_questions` | Searchbot Related Questions |
|
||||||
|
| `retrieval_test_embedded` | `/api/v1/searchbots/retrieval_test` | Searchbot Retrieval Test |
|
||||||
|
| `ask_about` | `/api/v1/sessions/ask` | Session Ask |
|
||||||
|
| `related_questions` | `/api/v1/sessions/related_questions` | Session Related Questions |
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# RAGFlow API 接口文档索引 (Unofficial Detailed Guide)
|
||||||
|
|
||||||
|
本文档汇集了 RAGFlow 核心模块的 API 详解。所有文档均遵循 **Zero Omissions (无省略)** 原则,全字段展开并包含中文注释。
|
||||||
|
|
||||||
|
## 📚 1. 知识库与文档管理 (Knowledge & Documents)
|
||||||
|
核心的数据管理模块,负责上传文件、解析文档与建立索引。
|
||||||
|
|
||||||
|
- **[知识库管理 (Dataset)](./RAGFlow_Dataset接口详解.md)**
|
||||||
|
- 涵盖知识库的创建、列表查询、更新、删除等接口。
|
||||||
|
- **[文档处理 (Document)](./RAGFlow_Document接口详解.md)**
|
||||||
|
- 涵盖文档的上传 (Upload)、解析配置更新 (Update)、解析状态查询 (Run Status)。
|
||||||
|
- **切片管理**: 解析后的 Chunk 列表查询、增删改查。
|
||||||
|
- **检索测试**: 直接对知识库进行召回测试 (Retrieval Test)。
|
||||||
|
- **[文件管理 (File)](./RAGFlow_File接口详解.md)**
|
||||||
|
- 类似网盘的文件操作体系。
|
||||||
|
- **CRUD**: 上传、下载、列表。
|
||||||
|
- **目录**: 文件夹创建、面包屑导航 (`get_all_parent_folders`)。
|
||||||
|
- **操作**: 移动、重命名、删除、导入知识库 (`convert`).
|
||||||
|
|
||||||
|
## 💬 2. 聊天助手 (Chat Assistant)
|
||||||
|
RAGFlow 原生的对话助手体系,基于 Assistant (Dialog) 模型。
|
||||||
|
|
||||||
|
- **[会话管理 (Chat Session)](./RAGFlow_Chat_Session接口详解.md)**
|
||||||
|
- 管理 `/chats/` 下的会话生命周期。
|
||||||
|
- 创建会话、获取历史记录、重命名、批量删除。
|
||||||
|
- **[对话交互 (Chat Completion)](./RAGFlow_Chat_Completion接口详解.md)**
|
||||||
|
- **Core Chat**: 原生流式对话 (`/chats/<id>/completions`), 支持引用 (`quote`)。
|
||||||
|
- **OpenAI Compatible**: 完美兼容 OpenAI `/v1/chat/completions` 协议。
|
||||||
|
- **Embedded Bot**: 面向 C 端嵌入窗口的对话接口 (`/chatbots/`).
|
||||||
|
|
||||||
|
## 🤖 3. Agent 与 机器人 (Agent & Bots)
|
||||||
|
基于 Graph (DAG) 编排的复杂应用与各类机器人扩展。
|
||||||
|
|
||||||
|
- **[Agent 与 Dify 兼容 (Agent & Dify)](./RAGFlow_Agent_Dify接口详解.md)**
|
||||||
|
- **Agent Session**: Agent 的会话管理与流式对话 (`agent_completions`)。
|
||||||
|
- **Dify Adapter**: 兼容 Dify 协议的检索接口 (`retrieval`).
|
||||||
|
- **[SearchBot 与 AgentBot](./RAGFlow_SearchBot_AgentBot接口详解.md)**
|
||||||
|
- **SearchBot**: 纯搜索机器人,支持思维导图 (`mindmap`)、相关问题 (`related_questions`).
|
||||||
|
- **AgentBot**: 嵌入式 Agent,支持前置表单 (`begin_inputs`).
|
||||||
|
- **Agent OpenAI**: Agent 的 OpenAI 兼容接口。
|
||||||
|
|
||||||
|
## 🛠️ 4. 其他 (Extras)
|
||||||
|
- **[通用与补充接口 (Session Extras)](./RAGFlow_Session_Extra接口详解.md)**
|
||||||
|
- **引用详情**: 获取 SearchBot 引用来源 (`detail_share_embedded`).
|
||||||
|
- **通用问答**: 内部调试用的直接问答 (`ask_about`).
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档 DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "知识库文档")
|
||||||
|
public class DocumentDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "本地ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "知识库ID")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "RAGFlow文档ID")
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "文件大小")
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
@Schema(description = "文件类型")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析配置")
|
||||||
|
private Map<String, Object> parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "处理状态 (1:解析中 3:成功 4:失败)")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息")
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
@Schema(description = "分块数量")
|
||||||
|
private Integer chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "Token数量")
|
||||||
|
private Long tokenCount;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用")
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@Schema(description = "上传进度 (虚拟字段)")
|
||||||
|
private Double progress;
|
||||||
|
|
||||||
|
@Schema(description = "缩略图/预览图 (虚拟字段)")
|
||||||
|
private String thumbnail;
|
||||||
|
}
|
||||||
@@ -26,9 +26,30 @@ public class KnowledgeBaseDTO implements Serializable {
|
|||||||
@Schema(description = "知识库名称")
|
@Schema(description = "知识库名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "知识库头像(Base64)")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
@Schema(description = "知识库描述")
|
@Schema(description = "知识库描述")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "嵌入模型名称")
|
||||||
|
private String embeddingModel;
|
||||||
|
|
||||||
|
@Schema(description = "权限设置: me/team")
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析器配置(JSON String)")
|
||||||
|
private String parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "分块总数")
|
||||||
|
private Long chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "总Token数")
|
||||||
|
private Long tokenNum;
|
||||||
|
|
||||||
@Schema(description = "状态(0:禁用 1:启用)")
|
@Schema(description = "状态(0:禁用 1:启用)")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|||||||
+27
-4
@@ -6,10 +6,12 @@ import java.util.Date;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "知识库文档")
|
@Schema(description = "知识库文档")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class KnowledgeFilesDTO implements Serializable {
|
public class KnowledgeFilesDTO implements Serializable {
|
||||||
|
|
||||||
@Serial
|
@Serial
|
||||||
@@ -35,7 +37,19 @@ public class KnowledgeFilesDTO implements Serializable {
|
|||||||
@Schema(description = "文件路径")
|
@Schema(description = "文件路径")
|
||||||
private String filePath;
|
private String filePath;
|
||||||
|
|
||||||
@Schema(description = "元数据字段")
|
@Schema(description = "解析进度 (0.0 ~ 1.0)")
|
||||||
|
private Double progress;
|
||||||
|
|
||||||
|
@Schema(description = "缩略图 (Base64 或 URL)")
|
||||||
|
private String thumbnail;
|
||||||
|
|
||||||
|
@Schema(description = "解析耗时 (单位: 秒)")
|
||||||
|
private Double processDuration;
|
||||||
|
|
||||||
|
@Schema(description = "来源类型 (local, s3, url 等)")
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
|
@Schema(description = "元数据字段 (Map 格式)")
|
||||||
private Map<String, Object> metaFields;
|
private Map<String, Object> metaFields;
|
||||||
|
|
||||||
@Schema(description = "分块方法")
|
@Schema(description = "分块方法")
|
||||||
@@ -44,10 +58,10 @@ public class KnowledgeFilesDTO implements Serializable {
|
|||||||
@Schema(description = "解析器配置")
|
@Schema(description = "解析器配置")
|
||||||
private Map<String, Object> parserConfig;
|
private Map<String, Object> parserConfig;
|
||||||
|
|
||||||
@Schema(description = "状态")
|
@Schema(description = "可用状态 (1: 启用/正常, 0: 禁用/失效)")
|
||||||
private Integer status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "文档解析状态")
|
@Schema(description = "运行状态 (UNSTART/RUNNING/CANCEL/DONE/FAIL)")
|
||||||
private String run;
|
private String run;
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
@Schema(description = "创建者")
|
||||||
@@ -62,6 +76,15 @@ public class KnowledgeFilesDTO implements Serializable {
|
|||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
private Date updatedAt;
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@Schema(description = "分块数量")
|
||||||
|
private Integer chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "Token数量")
|
||||||
|
private Long tokenCount;
|
||||||
|
|
||||||
|
@Schema(description = "解析错误信息")
|
||||||
|
private String error;
|
||||||
|
|
||||||
// 文档解析状态常量定义
|
// 文档解析状态常量定义
|
||||||
private static final Integer STATUS_UNSTART = 0;
|
private static final Integer STATUS_UNSTART = 0;
|
||||||
private static final Integer STATUS_RUNNING = 1;
|
private static final Integer STATUS_RUNNING = 1;
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.agent;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
@Schema(description = "智能体 (Agent) 管理聚合 DTO")
|
||||||
|
public class AgentDTO {
|
||||||
|
|
||||||
|
// ========== 1. Agent 管理 (CRUD) - 对应 RAGFlow_Agent接口详解 ==========
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Agent 创建请求")
|
||||||
|
public static class CreateReq implements Serializable {
|
||||||
|
@Schema(description = "Agent 标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "My Agent")
|
||||||
|
@NotBlank(message = "Agent 标题不能为空")
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "DSL 定义 (画布 JSON)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "DSL 定义不能为空")
|
||||||
|
@JsonProperty("dsl")
|
||||||
|
private Map<String, Object> dsl;
|
||||||
|
|
||||||
|
@Schema(description = "描述", example = "这是一个测试 Agent")
|
||||||
|
@JsonProperty("description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "头像 URL", example = "http://example.com/avatar.png")
|
||||||
|
@JsonProperty("avatar")
|
||||||
|
private String avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Agent 更新请求")
|
||||||
|
public static class UpdateReq implements Serializable {
|
||||||
|
@Schema(description = "Agent 标题", example = "Updated Agent")
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "DSL 定义 (画布 JSON)")
|
||||||
|
@JsonProperty("dsl")
|
||||||
|
private Map<String, Object> dsl;
|
||||||
|
|
||||||
|
@Schema(description = "描述")
|
||||||
|
@JsonProperty("description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "头像 URL")
|
||||||
|
@JsonProperty("avatar")
|
||||||
|
private String avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Agent 列表请求")
|
||||||
|
public static class ListReq implements Serializable {
|
||||||
|
@Schema(description = "页码", defaultValue = "1")
|
||||||
|
@JsonProperty("page")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer page = 1;
|
||||||
|
|
||||||
|
@Schema(description = "每页大小", defaultValue = "10")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段", defaultValue = "update_time")
|
||||||
|
@JsonProperty("orderby")
|
||||||
|
@Builder.Default
|
||||||
|
private String orderby = "update_time";
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", defaultValue = "true")
|
||||||
|
@JsonProperty("desc")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean desc = true;
|
||||||
|
|
||||||
|
@Schema(description = "Agent ID 过滤")
|
||||||
|
@JsonProperty("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "标题模糊搜索")
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Agent 响应对象")
|
||||||
|
public static class AgentVO implements Serializable {
|
||||||
|
@Schema(description = "Agent ID")
|
||||||
|
@JsonProperty("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "标题")
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "描述")
|
||||||
|
@JsonProperty("description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "头像")
|
||||||
|
@JsonProperty("avatar")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "DSL 定义")
|
||||||
|
@JsonProperty("dsl")
|
||||||
|
private Map<String, Object> dsl;
|
||||||
|
|
||||||
|
@Schema(description = "创建者 ID")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "画布分类")
|
||||||
|
@JsonProperty("canvas_category")
|
||||||
|
private String canvasCategory;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 2. Webhook 调试与追踪 - 对应 RAGFlow_Agent接口详解 ==========
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Webhook 触发请求 (参数动态)")
|
||||||
|
public static class WebhookTriggerReq implements Serializable {
|
||||||
|
@Schema(description = "输入变量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "输入变量不能为空")
|
||||||
|
@JsonProperty("inputs")
|
||||||
|
private Map<String, Object> inputs;
|
||||||
|
|
||||||
|
@Schema(description = "查询词", example = "Hello")
|
||||||
|
@JsonProperty("query")
|
||||||
|
private String query;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Webhook 追踪请求")
|
||||||
|
public static class WebhookTraceReq implements Serializable {
|
||||||
|
@Schema(description = "时间戳游标", example = "1700000000.0")
|
||||||
|
@JsonProperty("since_ts")
|
||||||
|
private Double sinceTs;
|
||||||
|
|
||||||
|
@Schema(description = "Webhook ID")
|
||||||
|
@JsonProperty("webhook_id")
|
||||||
|
private String webhookId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Webhook 追踪响应")
|
||||||
|
public static class WebhookTraceVO implements Serializable {
|
||||||
|
@Schema(description = "Webhook ID")
|
||||||
|
@JsonProperty("webhook_id")
|
||||||
|
private String webhookId;
|
||||||
|
|
||||||
|
@Schema(description = "是否结束")
|
||||||
|
@JsonProperty("finished")
|
||||||
|
private Boolean finished;
|
||||||
|
|
||||||
|
@Schema(description = "下一次查询的时间戳游标")
|
||||||
|
@JsonProperty("next_since_ts")
|
||||||
|
private Double nextSinceTs;
|
||||||
|
|
||||||
|
@Schema(description = "事件列表")
|
||||||
|
@JsonProperty("events")
|
||||||
|
private List<TraceEvent> events;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "追踪事件项")
|
||||||
|
public static class TraceEvent implements Serializable {
|
||||||
|
@Schema(description = "时间戳")
|
||||||
|
@JsonProperty("ts")
|
||||||
|
private Double ts;
|
||||||
|
|
||||||
|
@Schema(description = "事件类型")
|
||||||
|
@JsonProperty("event")
|
||||||
|
private String event;
|
||||||
|
|
||||||
|
@Schema(description = "事件数据")
|
||||||
|
@JsonProperty("data")
|
||||||
|
private Object data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 3. Agent 会话 (Session) - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Session 创建请求")
|
||||||
|
public static class SessionCreateReq implements Serializable {
|
||||||
|
@Schema(description = "用户 ID")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Session 列表请求")
|
||||||
|
public static class SessionListReq implements Serializable {
|
||||||
|
@Schema(description = "页码", defaultValue = "1")
|
||||||
|
@JsonProperty("page")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer page = 1;
|
||||||
|
|
||||||
|
@Schema(description = "每页大小", defaultValue = "10")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
@Builder.Default
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段", defaultValue = "create_time")
|
||||||
|
@JsonProperty("orderby")
|
||||||
|
@Builder.Default
|
||||||
|
private String orderby = "create_time";
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", defaultValue = "true")
|
||||||
|
@JsonProperty("desc")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean desc = true;
|
||||||
|
|
||||||
|
@Schema(description = "Session ID")
|
||||||
|
@JsonProperty("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "用户 ID")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "是否返回 DSL")
|
||||||
|
@JsonProperty("dsl")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean dsl = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Session 批量删除请求")
|
||||||
|
public static class SessionBatchDeleteReq implements Serializable {
|
||||||
|
@Schema(description = "会话 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("ids")
|
||||||
|
@NotEmpty(message = "ID列表不能为空")
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Session 响应对象")
|
||||||
|
public static class SessionVO implements Serializable {
|
||||||
|
@Schema(description = "Session ID")
|
||||||
|
@JsonProperty("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "Agent ID")
|
||||||
|
@JsonProperty("agent_id")
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
@Schema(description = "用户 ID")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "来源")
|
||||||
|
@JsonProperty("source")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "DSL 定义")
|
||||||
|
@JsonProperty("dsl")
|
||||||
|
private Map<String, Object> dsl;
|
||||||
|
|
||||||
|
@Schema(description = "消息列表")
|
||||||
|
@JsonProperty("messages")
|
||||||
|
private List<Map<String, Object>> messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 4. Agent 对话 (Completion) - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Completion 对话请求")
|
||||||
|
public static class CompletionReq implements Serializable {
|
||||||
|
@Schema(description = "会话 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "会话 ID 不能为空")
|
||||||
|
@JsonProperty("session_id")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "用户问题")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "是否流式返回", defaultValue = "true")
|
||||||
|
@JsonProperty("stream")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean stream = true;
|
||||||
|
|
||||||
|
@Schema(description = "是否返回追踪信息", defaultValue = "false")
|
||||||
|
@JsonProperty("return_trace")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean returnTrace = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Completion 对话响应")
|
||||||
|
public static class CompletionVO implements Serializable {
|
||||||
|
@Schema(description = "会话 ID")
|
||||||
|
@JsonProperty("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "回复内容")
|
||||||
|
@JsonProperty("content")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "引用来源")
|
||||||
|
@JsonProperty("reference")
|
||||||
|
private Map<String, Object> reference;
|
||||||
|
|
||||||
|
@Schema(description = "追踪信息")
|
||||||
|
@JsonProperty("trace")
|
||||||
|
private List<Object> trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 5. Dify 兼容检索 - 对应 RAGFlow_Agent_Dify接口详解 ==========
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Dify 兼容检索请求")
|
||||||
|
public static class DifyRetrievalReq implements Serializable {
|
||||||
|
@Schema(description = "知识库 ID")
|
||||||
|
@JsonProperty("knowledge_id")
|
||||||
|
private String knowledgeId;
|
||||||
|
|
||||||
|
@Schema(description = "查询词")
|
||||||
|
@JsonProperty("query")
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
@Schema(description = "检索设置")
|
||||||
|
@JsonProperty("retrieval_setting")
|
||||||
|
private Map<String, Object> retrievalSetting;
|
||||||
|
|
||||||
|
@Schema(description = "元数据过滤条件")
|
||||||
|
@JsonProperty("metadata_condition")
|
||||||
|
private Map<String, Object> metadataCondition;
|
||||||
|
|
||||||
|
@Schema(description = "是否使用知识图谱")
|
||||||
|
@JsonProperty("use_kg")
|
||||||
|
private Boolean useKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "Dify 兼容检索响应")
|
||||||
|
public static class DifyRetrievalVO implements Serializable {
|
||||||
|
@Schema(description = "检索结果列表")
|
||||||
|
@JsonProperty("records")
|
||||||
|
private List<Record> records;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "检索记录")
|
||||||
|
public static class Record implements Serializable {
|
||||||
|
@Schema(description = "内容")
|
||||||
|
@JsonProperty("content")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "相似度分数")
|
||||||
|
@JsonProperty("score")
|
||||||
|
private Double score;
|
||||||
|
|
||||||
|
@Schema(description = "标题")
|
||||||
|
@JsonProperty("title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "元数据")
|
||||||
|
@JsonProperty("metadata")
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.bot;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
@Schema(description = "外部机器人 (Bot) 聚合 DTO")
|
||||||
|
public class BotDTO {
|
||||||
|
|
||||||
|
// ========== 1. SearchBot (检索机器人) ==========
|
||||||
|
|
||||||
|
// 对应 /api/v1/searchbots/ask
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "SearchBot 提问请求")
|
||||||
|
public static class SearchAskReq implements Serializable {
|
||||||
|
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is RAG?")
|
||||||
|
@NotBlank(message = "问题不能为空")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "是否返回引用", defaultValue = "false")
|
||||||
|
@JsonProperty("quote")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean quote = false;
|
||||||
|
|
||||||
|
@Schema(description = "是否流式返回", defaultValue = "true")
|
||||||
|
@JsonProperty("stream")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean stream = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "SearchBot 提问响应")
|
||||||
|
public static class SearchAskVO implements Serializable {
|
||||||
|
@Schema(description = "回答内容")
|
||||||
|
@JsonProperty("answer")
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
@Schema(description = "引用来源 (Value 结构通常对应 RetrievalDTO.HitVO)")
|
||||||
|
@JsonProperty("reference")
|
||||||
|
private Map<String, Object> reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应 /api/v1/searchbots/related_questions
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "相关问题请求")
|
||||||
|
public static class RelatedQuestionReq implements Serializable {
|
||||||
|
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "问题不能为空")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应 /api/v1/searchbots/mindmap
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "思维导图请求")
|
||||||
|
public static class MindMapReq implements Serializable {
|
||||||
|
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "问题不能为空")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 2. AgentBot (嵌入式 Agent) ==========
|
||||||
|
|
||||||
|
// 对应 /api/v1/agentbots/{id}/inputs
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "AgentBot 输入参数请求")
|
||||||
|
public static class AgentInputsReq implements Serializable {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "AgentBot 输入参数定义响应")
|
||||||
|
public static class AgentInputsVO implements Serializable {
|
||||||
|
@Schema(description = "表单变量定义列表")
|
||||||
|
@JsonProperty("variables")
|
||||||
|
private List<Map<String, Object>> variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应 /api/v1/agentbots/{id}/completions
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "AgentBot 对话请求")
|
||||||
|
public static class AgentCompletionReq implements Serializable {
|
||||||
|
@Schema(description = "输入参数值")
|
||||||
|
@JsonProperty("inputs")
|
||||||
|
private Map<String, Object> inputs;
|
||||||
|
|
||||||
|
@Schema(description = "用户查询", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "查询内容不能为空")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "是否流式返回", defaultValue = "true")
|
||||||
|
@JsonProperty("stream")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean stream = true;
|
||||||
|
|
||||||
|
@Schema(description = "会话 ID")
|
||||||
|
@JsonProperty("session_id")
|
||||||
|
private String sessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.chat;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天对话请求 DTO (OpenAI 兼容格式)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "聊天对话请求")
|
||||||
|
public class ChatCompletionRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "模型标识 (对应 agent_id 或 bot_id)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("model")
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
@Schema(description = "对话消息列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("messages")
|
||||||
|
private List<Message> messages;
|
||||||
|
|
||||||
|
@Schema(description = "是否流式返回", defaultValue = "false")
|
||||||
|
@JsonProperty("stream")
|
||||||
|
private Boolean stream = false;
|
||||||
|
|
||||||
|
@Schema(description = "温度系数 (0-1)", defaultValue = "0.7")
|
||||||
|
@JsonProperty("temperature")
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
@Schema(description = "Session ID (可选,用于延续会话)")
|
||||||
|
@JsonProperty("session_id")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "其他RAGFlow特定参数 (可选)")
|
||||||
|
private Map<String, Object> extra;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Message implements Serializable {
|
||||||
|
@Schema(description = "角色 (system, user, assistant)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.chat;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话管理聚合 DTO
|
||||||
|
* <p>
|
||||||
|
* 容器类,内含对话助手、会话和消息的所有请求/响应对象。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Schema(description = "对话管理聚合 DTO")
|
||||||
|
public class ChatDTO {
|
||||||
|
|
||||||
|
// ========== 1. 对话助手 (Assistant/Bot) 相关 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提示词配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "提示词配置")
|
||||||
|
public static class PromptConfig implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "系统提示词", example = "你是一个专业的客服助手...")
|
||||||
|
@JsonProperty("prompt")
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
@Schema(description = "开场白", example = "您好,我是您的智能助手,请问有什么可以帮您?")
|
||||||
|
private String opener;
|
||||||
|
|
||||||
|
@Schema(description = "空结果回复", example = "抱歉,我没有找到相关信息。")
|
||||||
|
@JsonProperty("empty_response")
|
||||||
|
private String emptyResponse;
|
||||||
|
|
||||||
|
@Schema(description = "是否展示引用", example = "true")
|
||||||
|
@JsonProperty("show_quote")
|
||||||
|
private Boolean quote;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用 TTS", example = "false")
|
||||||
|
private Boolean tts;
|
||||||
|
|
||||||
|
@Schema(description = "相似度阈值 (0.0 - 1.0)", example = "0.2")
|
||||||
|
@JsonProperty("similarity_threshold")
|
||||||
|
private Float similarityThreshold;
|
||||||
|
|
||||||
|
@Schema(description = "关键词相似度权重 (0.0 - 1.0)", example = "0.7")
|
||||||
|
@JsonProperty("keywords_similarity_weight")
|
||||||
|
private Float vectorSimilarityWeight;
|
||||||
|
|
||||||
|
@Schema(description = "检索 Top N", example = "6")
|
||||||
|
@JsonProperty("top_n")
|
||||||
|
private Integer topK;
|
||||||
|
|
||||||
|
@Schema(description = "Rerank 模型", example = "rerank_model_001")
|
||||||
|
@JsonProperty("rerank_model")
|
||||||
|
private String rerankId;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用多轮对话优化", example = "false")
|
||||||
|
@JsonProperty("refine_multiturn")
|
||||||
|
private Boolean refineMultigraph;
|
||||||
|
|
||||||
|
@Schema(description = "变量列表")
|
||||||
|
private List<Map<String, Object>> variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM 配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "LLM 模型配置")
|
||||||
|
public static class LLMConfig implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "模型名称不能为空")
|
||||||
|
@Schema(description = "模型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-4")
|
||||||
|
@JsonProperty("model_name")
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
@Schema(description = "温度参数 (0.0 - 2.0)", example = "0.7")
|
||||||
|
private Float temperature;
|
||||||
|
|
||||||
|
@Schema(description = "Top P 采样", example = "0.9")
|
||||||
|
@JsonProperty("top_p")
|
||||||
|
private Float topP;
|
||||||
|
|
||||||
|
@Schema(description = "最大 Token 数", example = "4096")
|
||||||
|
@JsonProperty("max_tokens")
|
||||||
|
private Integer maxTokens;
|
||||||
|
|
||||||
|
@Schema(description = "存在惩罚", example = "0.0")
|
||||||
|
@JsonProperty("presence_penalty")
|
||||||
|
private Float presencePenalty;
|
||||||
|
|
||||||
|
@Schema(description = "频率惩罚", example = "0.0")
|
||||||
|
@JsonProperty("frequency_penalty")
|
||||||
|
private Float frequencyPenalty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建助手请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "创建助手请求")
|
||||||
|
public static class AssistantCreateReq implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "助手名称不能为空")
|
||||||
|
@Schema(description = "助手名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能客服助手")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "助手头像 (Base64 编码)", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
|
||||||
|
@JsonProperty("dataset_ids")
|
||||||
|
private List<String> datasetIds;
|
||||||
|
|
||||||
|
@Schema(description = "助手描述", example = "这是一个智能客服助手")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "LLM 模型配置")
|
||||||
|
@JsonProperty("llm")
|
||||||
|
private LLMConfig llm;
|
||||||
|
|
||||||
|
@Schema(description = "提示词配置")
|
||||||
|
@JsonProperty("prompt")
|
||||||
|
private PromptConfig promptConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新助手请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "更新助手请求")
|
||||||
|
public static class AssistantUpdateReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "助手名称", example = "智能客服助手 V2")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "助手头像 (Base64 编码)", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
|
||||||
|
@JsonProperty("dataset_ids")
|
||||||
|
private List<String> datasetIds;
|
||||||
|
|
||||||
|
@Schema(description = "助手描述", example = "这是一个智能客服助手")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "LLM 模型配置")
|
||||||
|
@JsonProperty("llm")
|
||||||
|
private LLMConfig llm;
|
||||||
|
|
||||||
|
@Schema(description = "提示词配置")
|
||||||
|
@JsonProperty("prompt")
|
||||||
|
private PromptConfig promptConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询助手列表请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "查询助手列表请求")
|
||||||
|
public static class AssistantListReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "页码 (从 1 开始)", example = "1")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "30")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "按名称过滤 (模糊匹配)", example = "客服")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段: create_time / update_time", example = "create_time")
|
||||||
|
private String orderby;
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", example = "true")
|
||||||
|
private Boolean desc;
|
||||||
|
|
||||||
|
@Schema(description = "按 ID 精确筛选", example = "assistant_001")
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 助手详情 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "助手详情 VO")
|
||||||
|
public static class AssistantVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "助手 ID", example = "assistant_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "租户 ID", example = "tenant_001")
|
||||||
|
@JsonProperty("tenant_id")
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "助手名称", example = "智能客服助手")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "助手头像", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "关联的知识库 ID 列表")
|
||||||
|
@JsonProperty("dataset_ids")
|
||||||
|
private List<String> datasetIds;
|
||||||
|
|
||||||
|
@Schema(description = "关联的知识库列表 (详情)")
|
||||||
|
private List<SimpleDatasetVO> datasets;
|
||||||
|
|
||||||
|
@Schema(description = "助手描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "LLM 模型配置")
|
||||||
|
@JsonProperty("llm")
|
||||||
|
private LLMConfig llm;
|
||||||
|
|
||||||
|
@Schema(description = "提示词配置")
|
||||||
|
@JsonProperty("prompt")
|
||||||
|
private PromptConfig promptConfig;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除助手请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "删除助手请求")
|
||||||
|
public static class AssistantDeleteReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "要删除的助手 ID 列表", example = "[\"assistant_001\", \"assistant_002\"]")
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 2. 会话 (Session) 相关 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建会话请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "创建会话请求")
|
||||||
|
public static class SessionCreateReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "会话名称", example = "技术咨询会话")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "用户 ID", example = "user_001")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会话请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "更新会话请求")
|
||||||
|
public static class SessionUpdateReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "会话名称", example = "技术咨询会话 - 更新")
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话列表请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "查询会话列表请求")
|
||||||
|
public static class SessionListReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "助手 ID", example = "assistant_001")
|
||||||
|
@JsonProperty("assistant_id")
|
||||||
|
private String assistantId;
|
||||||
|
|
||||||
|
@Schema(description = "页码 (从 1 开始)", example = "1")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "30")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "按名称过滤", example = "技术")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段", example = "create_time")
|
||||||
|
private String orderby;
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", example = "true")
|
||||||
|
private Boolean desc;
|
||||||
|
|
||||||
|
@Schema(description = "会话 ID 精确筛选", example = "session_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "用户标识筛选", example = "user_001")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话详情 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "会话详情 VO")
|
||||||
|
public static class SessionVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "会话 ID", example = "session_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "助手 ID", example = "assistant_001")
|
||||||
|
@JsonProperty("chat_id")
|
||||||
|
private String chatId;
|
||||||
|
|
||||||
|
@Schema(description = "助手 ID (兼容旧版)", example = "assistant_001")
|
||||||
|
@JsonProperty("assistant_id")
|
||||||
|
private String assistantId;
|
||||||
|
|
||||||
|
@Schema(description = "会话名称", example = "技术咨询会话")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "创建日期", example = "2024-05-01 10:00:00")
|
||||||
|
@JsonProperty("create_date")
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@Schema(description = "更新日期", example = "2024-05-01 10:00:00")
|
||||||
|
@JsonProperty("update_date")
|
||||||
|
private String updateDate;
|
||||||
|
|
||||||
|
@Schema(description = "用户 ID", example = "user_001")
|
||||||
|
@JsonProperty("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Schema(description = "对话历史消息列表")
|
||||||
|
private List<Map<String, Object>> messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "删除会话请求")
|
||||||
|
public static class SessionDeleteReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "要删除的会话 ID 列表", example = "[\"session_001\", \"session_002\"]")
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 3. 消息/对话 (Completion) 相关 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "发送消息请求")
|
||||||
|
public static class CompletionReq implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "问题内容不能为空")
|
||||||
|
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "请介绍一下你们的产品")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "是否使用流式响应 (SSE)", example = "true")
|
||||||
|
@Builder.Default
|
||||||
|
private Boolean stream = true;
|
||||||
|
|
||||||
|
@NotBlank(message = "会话 ID 不能为空")
|
||||||
|
@Schema(description = "会话 ID (可选,不传则创建新会话)", example = "session_001")
|
||||||
|
@JsonProperty("session_id")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "是否展示引用", example = "true")
|
||||||
|
private Boolean quote;
|
||||||
|
|
||||||
|
@Schema(description = "指定检索的文档 ID 列表 (逗号分隔)", example = "doc_001,doc_002")
|
||||||
|
@JsonProperty("doc_ids")
|
||||||
|
private String docIds;
|
||||||
|
|
||||||
|
@Schema(description = "元数据过滤条件")
|
||||||
|
@JsonProperty("metadata_condition")
|
||||||
|
private Map<String, Object> metadataCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息响应 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "消息响应 VO")
|
||||||
|
public static class CompletionVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "AI 回答内容")
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
@Schema(description = "引用信息")
|
||||||
|
private Reference reference;
|
||||||
|
|
||||||
|
@Schema(description = "会话 ID", example = "session_001")
|
||||||
|
@JsonProperty("session_id")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "任务 ID (用于流式响应追踪)", example = "task_001")
|
||||||
|
@JsonProperty("task_id")
|
||||||
|
private String taskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 引用信息 (检索命中结果)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "引用信息")
|
||||||
|
public static class Reference implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "命中的文档块列表")
|
||||||
|
private List<xiaozhi.modules.knowledge.dto.document.RetrievalDTO.HitVO> chunks;
|
||||||
|
|
||||||
|
@Schema(description = "文档聚合信息")
|
||||||
|
@JsonProperty("doc_aggs")
|
||||||
|
private List<DocAgg> docAggs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档聚合信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文档聚合信息")
|
||||||
|
public static class DocAgg implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID", example = "doc_001")
|
||||||
|
@JsonProperty("doc_id")
|
||||||
|
private String docId;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称", example = "产品手册.pdf")
|
||||||
|
@JsonProperty("doc_name")
|
||||||
|
private String docName;
|
||||||
|
|
||||||
|
@Schema(description = "命中次数", example = "3")
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简易知识库 VO (用于 Assistant 列表)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "简易知识库 VO")
|
||||||
|
public static class SimpleDatasetVO implements Serializable {
|
||||||
|
@Schema(description = "知识库 ID")
|
||||||
|
private String id;
|
||||||
|
@Schema(description = "知识库名称")
|
||||||
|
private String name;
|
||||||
|
@Schema(description = "头像")
|
||||||
|
private String avatar;
|
||||||
|
@Schema(description = "分块数量")
|
||||||
|
@JsonProperty("chunk_num")
|
||||||
|
private Integer chunkNum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.common;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
@Schema(description = "通用扩展功能 DTO")
|
||||||
|
public class CommonDTO {
|
||||||
|
|
||||||
|
// ========== 1. 引用详情 (detail_share_embedded) ==========
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "引用详情请求")
|
||||||
|
public static class ReferenceDetailReq implements Serializable {
|
||||||
|
@Schema(description = "切片 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "切片 ID 不能为空")
|
||||||
|
@JsonProperty("chunk_id")
|
||||||
|
private String chunkId;
|
||||||
|
|
||||||
|
@Schema(description = "知识库 ID")
|
||||||
|
@JsonProperty("knowledge_id")
|
||||||
|
private String knowledgeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "引用详情响应")
|
||||||
|
public static class ReferenceDetailVO implements Serializable {
|
||||||
|
@Schema(description = "切片 ID")
|
||||||
|
@JsonProperty("chunk_id")
|
||||||
|
private String chunkId;
|
||||||
|
|
||||||
|
@Schema(description = "完整内容")
|
||||||
|
@JsonProperty("content_with_weight")
|
||||||
|
private String contentWithWeight;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称")
|
||||||
|
@JsonProperty("doc_name")
|
||||||
|
private String docName;
|
||||||
|
|
||||||
|
@Schema(description = "图片 ID 列表")
|
||||||
|
@JsonProperty("img_id")
|
||||||
|
private String imageId; // 注意:RAGFlow 有时返回 String 有时返回 List,需根据实际情况确认,暂定 String 用于 ID
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID")
|
||||||
|
@JsonProperty("doc_id")
|
||||||
|
private String docId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 2. 通用问答 (ask_about) - 调试用 ==========
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "通用问答请求 (调试用)")
|
||||||
|
public static class AskAboutReq implements Serializable {
|
||||||
|
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is this dataset about?")
|
||||||
|
@NotBlank(message = "问题不能为空")
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "数据集 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "数据集列表不能为空")
|
||||||
|
@JsonProperty("dataset_ids")
|
||||||
|
private List<String> datasetIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应通常复用 String 或者简单的 Map 结构,视具体实现而定,暂不定义专用 VO
|
||||||
|
}
|
||||||
@@ -0,0 +1,447 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.dataset;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库管理聚合 DTO
|
||||||
|
* <p>
|
||||||
|
* 容器类,内含知识库模块所有请求/响应对象的静态内部类定义。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Schema(description = "知识库管理聚合 DTO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class DatasetDTO {
|
||||||
|
|
||||||
|
// ========== 通用内部类 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析器配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "解析器配置")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ParserConfig implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "分块 token 数量", example = "128")
|
||||||
|
@JsonProperty("chunk_token_num")
|
||||||
|
private Integer chunkTokenNum;
|
||||||
|
|
||||||
|
@Schema(description = "分隔符", example = "\\n!?;。;!?")
|
||||||
|
private String delimiter;
|
||||||
|
|
||||||
|
@Schema(description = "布局识别模型: DeepDOC / Simple", example = "DeepDOC")
|
||||||
|
@JsonProperty("layout_recognize")
|
||||||
|
private String layoutRecognize;
|
||||||
|
|
||||||
|
@Schema(description = "是否将 Excel 转为 HTML", example = "false")
|
||||||
|
private Boolean html4excel;
|
||||||
|
|
||||||
|
@Schema(description = "自动生成关键词数量 (0 表示关闭)", example = "0")
|
||||||
|
@JsonProperty("auto_keywords")
|
||||||
|
private Integer autoKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "自动生成问题数量 (0 表示关闭)", example = "0")
|
||||||
|
@JsonProperty("auto_questions")
|
||||||
|
private Integer autoQuestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 请求类 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建知识库请求 (映射接口 1: create)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "创建知识库请求")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class CreateReq implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "知识库名称不能为空")
|
||||||
|
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "my_dataset")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "知识库头像 (Base64 编码)", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "知识库描述", example = "用于存储产品文档")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
|
||||||
|
@JsonProperty("embedding_model")
|
||||||
|
private String embeddingModel;
|
||||||
|
|
||||||
|
@Schema(description = "权限设置: me / team", example = "me")
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法: naive / manual / qa / table / paper / book / laws / presentation / picture / one / knowledge_graph / email", example = "naive")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析器配置")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private ParserConfig parserConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新知识库请求 (映射接口 4: update)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "更新知识库请求")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class UpdateReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "知识库名称", example = "updated_dataset")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "知识库头像 (Base64 编码)", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "知识库描述", example = "更新后的描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "权限设置: me / team", example = "team")
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
|
||||||
|
@JsonProperty("embedding_model")
|
||||||
|
private String embeddingModel;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法: naive / manual / qa / table / paper / book / laws / presentation / picture / one / knowledge_graph / email", example = "naive")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析器配置")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private ParserConfig parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "PageRank 权重 (0-100)", example = "50")
|
||||||
|
private Integer pagerank;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询知识库列表请求 (映射接口 3: list_datasets)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "查询知识库列表请求")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ListReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "页码 (从 1 开始)", example = "1")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "30")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段: create_time / update_time", example = "create_time")
|
||||||
|
private String orderby;
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", example = "true")
|
||||||
|
private Boolean desc;
|
||||||
|
|
||||||
|
@Schema(description = "按名称过滤 (模糊匹配)", example = "my_dataset")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "按知识库 ID 过滤", example = "abc123")
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除知识库请求 (映射接口 2: delete)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "批量删除知识库请求")
|
||||||
|
public static class BatchIdReq implements Serializable {
|
||||||
|
|
||||||
|
@NotNull(message = "知识库 ID 列表不能为空")
|
||||||
|
@Size(min = 1, message = "至少需要一个知识库 ID")
|
||||||
|
@Schema(description = "知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"id1\", \"id2\"]")
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行 GraphRAG 请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "运行 GraphRAG 请求")
|
||||||
|
public static class RunGraphRagReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "实体类型列表", example = "[\"person\", \"organization\"]")
|
||||||
|
@JsonProperty("entity_types")
|
||||||
|
private List<String> entityTypes;
|
||||||
|
|
||||||
|
@Schema(description = "构建方法: light / fast / full", example = "light")
|
||||||
|
private String method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行 RAPTOR 请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "运行 RAPTOR 请求")
|
||||||
|
public static class RunRaptorReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "最大聚类数", example = "64")
|
||||||
|
@JsonProperty("max_cluster")
|
||||||
|
private Integer maxCluster;
|
||||||
|
|
||||||
|
@Schema(description = "自定义提示词", example = "请总结以下内容...")
|
||||||
|
private String prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步任务 ID 响应 VO (映射接口 7/8: run_graphrag/run_raptor)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "异步任务 ID 响应")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class TaskIdVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "GraphRAG 任务 ID", example = "task_uuid_12345678")
|
||||||
|
@JsonProperty("graphrag_task_id")
|
||||||
|
private String graphragTaskId;
|
||||||
|
|
||||||
|
@Schema(description = "RAPTOR 任务 ID", example = "task_uuid_87654321")
|
||||||
|
@JsonProperty("raptor_task_id")
|
||||||
|
private String raptorTaskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 响应类 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库详情 VO (映射接口 1/3 的返回数据项)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "知识库详情 VO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class InfoVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "知识库 ID", example = "abc123")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "知识库名称", example = "my_dataset")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "知识库头像 (Base64 编码)", example = "")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "租户 ID", example = "tenant_001")
|
||||||
|
@JsonProperty("tenant_id")
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "知识库描述", example = "用于存储产品文档")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
|
||||||
|
@JsonProperty("embedding_model")
|
||||||
|
private String embeddingModel;
|
||||||
|
|
||||||
|
@Schema(description = "权限设置: me / team", example = "me")
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法", example = "naive")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析器配置")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private ParserConfig parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "分块总数", example = "1024")
|
||||||
|
@JsonProperty("chunk_count")
|
||||||
|
private Long chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "文档总数", example = "50")
|
||||||
|
@JsonProperty("document_count")
|
||||||
|
private Long documentCount;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "总 Token 数", example = "102400")
|
||||||
|
@JsonProperty("token_num")
|
||||||
|
private Long tokenNum;
|
||||||
|
|
||||||
|
@Schema(description = "创建日期 (格式: yyyy-MM-dd HH:mm:ss)")
|
||||||
|
@JsonProperty("create_date")
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@Schema(description = "最后更新日期 (格式: yyyy-MM-dd HH:mm:ss)")
|
||||||
|
@JsonProperty("update_date")
|
||||||
|
private String updateDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作响应 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "批量操作响应 VO")
|
||||||
|
public static class BatchOperationVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "成功操作数量", example = "5")
|
||||||
|
@JsonProperty("success_count")
|
||||||
|
private Integer successCount;
|
||||||
|
|
||||||
|
@Schema(description = "错误列表")
|
||||||
|
private List<Object> errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 知识图谱相关 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识图谱数据 VO (映射接口 5: knowledge_graph)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "知识图谱数据 VO")
|
||||||
|
public static class GraphVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "图谱节点列表")
|
||||||
|
private List<Node> nodes;
|
||||||
|
|
||||||
|
@Schema(description = "图谱边列表")
|
||||||
|
private List<Edge> edges;
|
||||||
|
|
||||||
|
@Schema(description = "思维导图数据")
|
||||||
|
@JsonProperty("mind_map")
|
||||||
|
private Map<String, Object> mindMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图谱节点
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "图谱节点")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class Node implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "节点 ID", example = "node_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "节点标签", example = "产品")
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
@Schema(description = "PageRank 值", example = "0.85")
|
||||||
|
private Double pagerank;
|
||||||
|
|
||||||
|
@Schema(description = "节点颜色", example = "#FF5733")
|
||||||
|
private String color;
|
||||||
|
|
||||||
|
@Schema(description = "节点图片 URL", example = "https://example.com/icon.png")
|
||||||
|
private String img;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图谱边
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "图谱边")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class Edge implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "源节点 ID", example = "node_001")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "目标节点 ID", example = "node_002")
|
||||||
|
private String target;
|
||||||
|
|
||||||
|
@Schema(description = "边权重", example = "0.75")
|
||||||
|
private Double weight;
|
||||||
|
|
||||||
|
@Schema(description = "边标签 (关系描述)", example = "属于")
|
||||||
|
private String label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 异步任务追踪 (GraphRAG/RAPTOR) ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步任务追踪 VO (映射接口 9/10: 任务进度返回)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "异步任务追踪 VO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class TaskTraceVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "任务 ID", example = "task_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID", example = "doc_001")
|
||||||
|
@JsonProperty("doc_id")
|
||||||
|
private String docId;
|
||||||
|
|
||||||
|
@Schema(description = "起始页码", example = "1")
|
||||||
|
@JsonProperty("from_page")
|
||||||
|
private Integer fromPage;
|
||||||
|
|
||||||
|
@Schema(description = "结束页码", example = "10")
|
||||||
|
@JsonProperty("to_page")
|
||||||
|
private Integer toPage;
|
||||||
|
|
||||||
|
@Schema(description = "进度百分比 (0.0 - 1.0)", example = "0.75")
|
||||||
|
private Double progress;
|
||||||
|
|
||||||
|
@Schema(description = "进度消息", example = "正在处理第 5 页...")
|
||||||
|
@JsonProperty("progress_msg")
|
||||||
|
private String progressMsg;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.document;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切片管理聚合 DTO
|
||||||
|
*/
|
||||||
|
@Schema(description = "切片管理聚合 DTO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ChunkDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增切片请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "新增切片请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class AddReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "切片内容不能为空")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "重要关键词列表")
|
||||||
|
@JsonProperty("important_keywords")
|
||||||
|
private List<String> importantKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "预设问题列表")
|
||||||
|
private List<String> questions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新切片请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "更新切片请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class UpdateReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "新的切片内容")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "更新关键词列表 (覆盖原有列表)")
|
||||||
|
@JsonProperty("important_keywords")
|
||||||
|
private List<String> importantKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "启用/禁用 (true: 启用, false: 禁用)")
|
||||||
|
private Boolean available;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取切片列表请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "获取切片列表请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ListReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "页码 (默认 1)")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量 (默认 30)")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "搜索关键词 (全文检索)")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "精确切片 ID")
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除切片请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "批量删除切片请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class RemoveReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("chunk_ids")
|
||||||
|
@NotEmpty(message = "切片ID列表不能为空")
|
||||||
|
private List<String> chunkIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档切片信息 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "文档切片信息")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class InfoVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片 ID (通常为 document_id + 索引)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "切片文本内容 (全文检索的主要对象)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "所属文档 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("document_id")
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称 / 关键词")
|
||||||
|
@JsonProperty("docnm_kwd")
|
||||||
|
private String docnmKwd;
|
||||||
|
|
||||||
|
@Schema(description = "重要关键词列表 (用于关键词增强检索)")
|
||||||
|
@JsonProperty("important_keywords")
|
||||||
|
private List<String> importantKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "预设问题列表 (用于 Q&A 模式增强)")
|
||||||
|
private List<String> questions;
|
||||||
|
|
||||||
|
@Schema(description = "关联的图片 ID")
|
||||||
|
@JsonProperty("image_id")
|
||||||
|
private String imageId;
|
||||||
|
|
||||||
|
@Schema(description = "所属知识库 ID")
|
||||||
|
@JsonProperty("dataset_id")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "切片是否可用 (true: 参与检索, false: 被禁用)")
|
||||||
|
private Boolean available;
|
||||||
|
|
||||||
|
@Schema(description = "切片在原文中的位置索引列表 (RAGFlow返回嵌套数组, 如 [[start, end, filename]])")
|
||||||
|
private List<List<Object>> positions;
|
||||||
|
|
||||||
|
@Schema(description = "Token ID 列表")
|
||||||
|
@JsonProperty("token")
|
||||||
|
private List<Integer> token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分片列表聚合响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "分片列表聚合响应")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ListVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片信息列表")
|
||||||
|
private List<InfoVO> chunks;
|
||||||
|
|
||||||
|
@Schema(description = "关联的文档详细信息")
|
||||||
|
private DocumentDTO.InfoVO doc;
|
||||||
|
|
||||||
|
@Schema(description = "总记录数")
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
|
}
|
||||||
+407
@@ -0,0 +1,407 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.document;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档管理聚合 DTO
|
||||||
|
*/
|
||||||
|
@Schema(description = "文档管理聚合 DTO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class DocumentDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文档请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "上传文档请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class UploadReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "知识库 ID (必须指定归属)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("dataset_id")
|
||||||
|
@NotBlank(message = "知识库ID不能为空")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "文件名 (如果指定,则覆盖原始文件名)")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private DocumentDTO.InfoVO.ChunkMethod chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析参数配置")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private DocumentDTO.InfoVO.ParserConfig parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "虚拟文件夹路径 (默认为 /)")
|
||||||
|
@JsonProperty("parent_path")
|
||||||
|
private String parentPath;
|
||||||
|
|
||||||
|
@Schema(description = "元数据字段")
|
||||||
|
@JsonProperty("meta")
|
||||||
|
private Map<String, Object> metaFields;
|
||||||
|
|
||||||
|
@Schema(description = "文件二进制流 (支持 PDF, DOCX, TXT, MD 等多种格式)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "上传文件不能为空")
|
||||||
|
private org.springframework.web.multipart.MultipartFile file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新文档请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "更新文档请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class UpdateReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "新文档名称 (必须包含文件后缀,且不能更改原始类型)")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "启用/禁用状态 (true: 启用, false: 禁用; 禁用后不参与检索)")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
@Schema(description = "新解析方法 (修改此项会重置解析状态)")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private InfoVO.ChunkMethod chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "新解析器详细配置 (应与 chunk_method 配套使用)")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private InfoVO.ParserConfig parserConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文档列表请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "获取文档列表请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ListReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "页码 (默认: 1)")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量 (默认: 30)")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段 (可选: create_time, name, size; 默认: create_time)")
|
||||||
|
private String orderby;
|
||||||
|
|
||||||
|
@Schema(description = "是否降序排列 (true: 最新/最大在前; false: 最旧/最小在前; 默认: true)")
|
||||||
|
private Boolean desc;
|
||||||
|
|
||||||
|
@Schema(description = "精确筛选: 文档 ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "精确筛选: 文档完整名称 (含后缀)")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "模糊搜索: 文档名称关键词")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "筛选: 文件后缀列表 (如 ['pdf', 'docx'])")
|
||||||
|
private List<String> suffix;
|
||||||
|
|
||||||
|
@Schema(description = "筛选: 运行状态列表")
|
||||||
|
private List<InfoVO.RunStatus> run;
|
||||||
|
|
||||||
|
@Schema(description = "筛选: 起始创建时间 (时间戳, 毫秒)")
|
||||||
|
@JsonProperty("create_time_from")
|
||||||
|
private Long createTimeFrom;
|
||||||
|
|
||||||
|
@Schema(description = "筛选: 结束创建时间 (时间戳, 毫秒)")
|
||||||
|
@JsonProperty("create_time_to")
|
||||||
|
private Long createTimeTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量文档操作请求参数 (用于删除、解析等)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "批量文档操作请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class BatchIdReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("ids") // 为了兼容性,也可以考虑支持 document_ids,但这里统一叫 ids
|
||||||
|
@JsonAlias("document_ids")
|
||||||
|
@NotEmpty(message = "文档ID列表不能为空")
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库文档信息 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "知识库文档信息")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class InfoVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID (唯一标识)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "文档缩略图 URL (Base64 或 链接)")
|
||||||
|
private String thumbnail;
|
||||||
|
|
||||||
|
@Schema(description = "所属知识库 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("dataset_id")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "文档解析方法 (决定了文档如何被切片)")
|
||||||
|
@JsonProperty("chunk_method")
|
||||||
|
private ChunkMethod chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "关联的 ETL Pipeline ID (如有)")
|
||||||
|
@JsonProperty("pipeline_id")
|
||||||
|
private String pipelineId;
|
||||||
|
|
||||||
|
@Schema(description = "文档解析器的详细配置")
|
||||||
|
@JsonProperty("parser_config")
|
||||||
|
private ParserConfig parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "来源类型 (如 local, s3, url 等)")
|
||||||
|
@JsonProperty("source_type")
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
|
@Schema(description = "文档文件类型 (如 pdf, docx, txt)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "创建者用户 ID")
|
||||||
|
@JsonProperty("created_by")
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称 (包含扩展名)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "文件存储路径或位置标识")
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
@Schema(description = "文件大小 (单位: Bytes)")
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
@Schema(description = "包含的 Token 总数 (解析后统计)")
|
||||||
|
@JsonProperty("token_count")
|
||||||
|
private Long tokenCount;
|
||||||
|
|
||||||
|
@Schema(description = "包含的切片 (Chunk) 总数")
|
||||||
|
@JsonProperty("chunk_count")
|
||||||
|
private Long chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "解析进度 (0.0 ~ 1.0, 1.0 表示完成)")
|
||||||
|
private Double progress;
|
||||||
|
|
||||||
|
@Schema(description = "当前进度描述或错误信息")
|
||||||
|
@JsonProperty("progress_msg")
|
||||||
|
private String progressMsg;
|
||||||
|
|
||||||
|
@Schema(description = "开始处理的时间戳 (RAGFlow返回RFC1123格式)")
|
||||||
|
@JsonProperty("process_begin_at")
|
||||||
|
private String processBeginAt;
|
||||||
|
|
||||||
|
@Schema(description = "处理总耗时 (单位: 秒)")
|
||||||
|
@JsonProperty("process_duration")
|
||||||
|
private Double processDuration;
|
||||||
|
|
||||||
|
@Schema(description = "自定义元数据字段 (Key-Value 键值对)")
|
||||||
|
@JsonProperty("meta_fields")
|
||||||
|
private Map<String, Object> metaFields;
|
||||||
|
|
||||||
|
@Schema(description = "文件后缀名 (不含点)")
|
||||||
|
private String suffix;
|
||||||
|
|
||||||
|
@Schema(description = "文档解析运行状态")
|
||||||
|
private RunStatus run;
|
||||||
|
|
||||||
|
@Schema(description = "文档可用状态 (1: 启用/正常, 0: 禁用/失效)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳, 毫秒)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "创建日期 (RAGFlow返回RFC1123格式)")
|
||||||
|
@JsonProperty("create_date")
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@Schema(description = "最后更新时间 (时间戳, 毫秒)")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "最后更新日期 (RAGFlow返回RFC1123格式)")
|
||||||
|
@JsonProperty("update_date")
|
||||||
|
private String updateDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析方法枚举 (ChunkMethod)
|
||||||
|
*/
|
||||||
|
public enum ChunkMethod {
|
||||||
|
@Schema(description = "通用模式: 适用于大多数纯文本或混合文档")
|
||||||
|
@JsonProperty("naive")
|
||||||
|
NAIVE,
|
||||||
|
@Schema(description = "手动模式: 允许用户手动编辑切片")
|
||||||
|
@JsonProperty("manual")
|
||||||
|
MANUAL,
|
||||||
|
@Schema(description = "问答模式: 专门优化 Q&A 格式的文档")
|
||||||
|
@JsonProperty("qa")
|
||||||
|
QA,
|
||||||
|
@Schema(description = "表格模式: 专门优化 Excel 或 CSV 等表格数据")
|
||||||
|
@JsonProperty("table")
|
||||||
|
TABLE,
|
||||||
|
@Schema(description = "论文模式: 针对学术论文排版优化")
|
||||||
|
@JsonProperty("paper")
|
||||||
|
PAPER,
|
||||||
|
@Schema(description = "书籍模式: 针对书籍章节结构优化")
|
||||||
|
@JsonProperty("book")
|
||||||
|
BOOK,
|
||||||
|
@Schema(description = "法律法规模式: 针对法律条文结构优化")
|
||||||
|
@JsonProperty("laws")
|
||||||
|
LAWS,
|
||||||
|
@Schema(description = "演示文稿模式: 针对 PPT 等演示文件优化")
|
||||||
|
@JsonProperty("presentation")
|
||||||
|
PRESENTATION,
|
||||||
|
@Schema(description = "图片模式: 针对图片内容进行 OCR 和描述")
|
||||||
|
@JsonProperty("picture")
|
||||||
|
PICTURE,
|
||||||
|
@Schema(description = "整体模式: 将整个文档作为一个切片")
|
||||||
|
@JsonProperty("one")
|
||||||
|
ONE,
|
||||||
|
@Schema(description = "知识图谱模式: 提取实体关系构建图谱")
|
||||||
|
@JsonProperty("knowledge_graph")
|
||||||
|
KNOWLEDGE_GRAPH,
|
||||||
|
@Schema(description = "邮件模式: 针对邮件格式优化")
|
||||||
|
@JsonProperty("email")
|
||||||
|
EMAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行状态枚举 (RunStatus)
|
||||||
|
*/
|
||||||
|
public enum RunStatus {
|
||||||
|
@Schema(description = "未开始: 等待解析队列")
|
||||||
|
@JsonProperty("UNSTART")
|
||||||
|
UNSTART,
|
||||||
|
@Schema(description = "进行中: 正在解析或索引")
|
||||||
|
@JsonProperty("RUNNING")
|
||||||
|
RUNNING,
|
||||||
|
@Schema(description = "已取消: 用户手动取消")
|
||||||
|
@JsonProperty("CANCEL")
|
||||||
|
CANCEL,
|
||||||
|
@Schema(description = "已完成: 解析成功")
|
||||||
|
@JsonProperty("DONE")
|
||||||
|
DONE,
|
||||||
|
@Schema(description = "失败: 解析过程中出错")
|
||||||
|
@JsonProperty("FAIL")
|
||||||
|
FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局识别模型枚举
|
||||||
|
*/
|
||||||
|
public enum LayoutRecognize {
|
||||||
|
@Schema(description = "深度文档理解模型: 适合复杂排版")
|
||||||
|
@JsonProperty("DeepDOC")
|
||||||
|
DeepDOC,
|
||||||
|
@Schema(description = "简单规则模型: 适合纯文本")
|
||||||
|
@JsonProperty("Simple")
|
||||||
|
Simple;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "文档解析器参数配置")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ParserConfig implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片最大 Token 数 (建议值: 512, 1024, 2048)")
|
||||||
|
@JsonProperty("chunk_token_num")
|
||||||
|
private Integer chunkTokenNum;
|
||||||
|
|
||||||
|
@Schema(description = "分段分隔符 (支持转义字符, 如 \\n)")
|
||||||
|
private String delimiter;
|
||||||
|
|
||||||
|
@Schema(description = "布局识别模型 (DeepDOC/Simple)")
|
||||||
|
@JsonProperty("layout_recognize")
|
||||||
|
private LayoutRecognize layoutRecognize;
|
||||||
|
|
||||||
|
@Schema(description = "是否将 Excel 转换为 HTML 表格")
|
||||||
|
@JsonProperty("html4excel")
|
||||||
|
private Boolean html4excel;
|
||||||
|
|
||||||
|
@Schema(description = "自动提取关键词数量 (0 表示不提取)")
|
||||||
|
@JsonProperty("auto_keywords")
|
||||||
|
private Integer autoKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "自动生成问题数量 (0 表示不生成)")
|
||||||
|
@JsonProperty("auto_questions")
|
||||||
|
private Integer autoQuestions;
|
||||||
|
|
||||||
|
@Schema(description = "自动生成标签数量")
|
||||||
|
@JsonProperty("topn_tags")
|
||||||
|
private Integer topnTags;
|
||||||
|
|
||||||
|
@Schema(description = "RAPTOR 高级索引配置")
|
||||||
|
private RaptorConfig raptor;
|
||||||
|
|
||||||
|
@Schema(description = "GraphRAG 知识图谱配置")
|
||||||
|
@JsonProperty("graphrag")
|
||||||
|
private GraphRagConfig graphRag;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "RAPTOR (递归摘要索引) 配置")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class RaptorConfig implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
@Schema(description = "是否启用 RAPTOR 索引")
|
||||||
|
@JsonProperty("use_raptor")
|
||||||
|
private Boolean useRaptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "GraphRAG (图增强检索) 配置")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class GraphRagConfig implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
@Schema(description = "是否启用 GraphRAG 索引")
|
||||||
|
@JsonProperty("use_graphrag")
|
||||||
|
private Boolean useGraphRag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+307
@@ -0,0 +1,307 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.document;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检索与元数据管理聚合 DTO
|
||||||
|
*/
|
||||||
|
@Schema(description = "检索与元数据管理聚合 DTO")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class RetrievalDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档聚合信息 (VO)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "文档聚合信息")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class DocAggVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称")
|
||||||
|
@JsonProperty("doc_name")
|
||||||
|
private String docName;
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID")
|
||||||
|
@JsonProperty("doc_id")
|
||||||
|
private String docId;
|
||||||
|
|
||||||
|
@Schema(description = "数量")
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检索测试请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "检索测试请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public static class TestReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("dataset_ids")
|
||||||
|
@NotEmpty(message = "知识库ID列表不能为空")
|
||||||
|
private List<String> datasetIds;
|
||||||
|
|
||||||
|
@Schema(description = "文档 ID 列表 (可选,用于限定检索范围)")
|
||||||
|
@JsonProperty("document_ids")
|
||||||
|
private List<String> documentIds;
|
||||||
|
|
||||||
|
@Schema(description = "检索问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "检索问题不能为空")
|
||||||
|
private String question;
|
||||||
|
|
||||||
|
@Schema(description = "页码 (默认 1)")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量 (默认 10)")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "相似度阈值 (默认 0.2)")
|
||||||
|
@JsonProperty("similarity_threshold")
|
||||||
|
private Float similarityThreshold;
|
||||||
|
|
||||||
|
@Schema(description = "向量相似度权重 (默认 0.3)")
|
||||||
|
@JsonProperty("vector_similarity_weight")
|
||||||
|
private Float vectorSimilarityWeight;
|
||||||
|
|
||||||
|
@Schema(description = "返回 Top K 切片 (默认 1024)")
|
||||||
|
@JsonProperty("top_k")
|
||||||
|
private Integer topK;
|
||||||
|
|
||||||
|
@Schema(description = "重排序模型 ID")
|
||||||
|
@JsonProperty("rerank_id")
|
||||||
|
private String rerankId;
|
||||||
|
|
||||||
|
@Schema(description = "是否高亮关键词")
|
||||||
|
private Boolean highlight;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用关键词检索")
|
||||||
|
private Boolean keyword;
|
||||||
|
|
||||||
|
@Schema(description = "跨语言翻译列表 (可选)")
|
||||||
|
@JsonProperty("cross_languages")
|
||||||
|
private List<String> crossLanguages;
|
||||||
|
|
||||||
|
@Schema(description = "元数据过滤条件 (JSON 对象)")
|
||||||
|
@JsonProperty("metadata_condition")
|
||||||
|
private Map<String, Object> metadataCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检索命中结果 (VO)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "检索命中切片详情")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class HitVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "切片 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "所属文档 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("document_id")
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
@Schema(description = "所属知识库 ID")
|
||||||
|
@JsonProperty("dataset_id")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称")
|
||||||
|
@JsonProperty("document_name")
|
||||||
|
private String documentName;
|
||||||
|
|
||||||
|
@Schema(description = "文档关键词")
|
||||||
|
@JsonProperty("document_keyword")
|
||||||
|
private String documentKeyword;
|
||||||
|
|
||||||
|
@Schema(description = "综合相似度", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Float similarity;
|
||||||
|
|
||||||
|
@Schema(description = "向量相似度")
|
||||||
|
@JsonProperty("vector_similarity")
|
||||||
|
private Float vectorSimilarity;
|
||||||
|
|
||||||
|
@Schema(description = "关键词相似度")
|
||||||
|
@JsonProperty("term_similarity")
|
||||||
|
private Float termSimilarity;
|
||||||
|
|
||||||
|
@Schema(description = "索引位置")
|
||||||
|
private Integer index;
|
||||||
|
|
||||||
|
@Schema(description = "高亮内容")
|
||||||
|
private String highlight;
|
||||||
|
|
||||||
|
@Schema(description = "重要关键词列表")
|
||||||
|
@JsonProperty("important_keywords")
|
||||||
|
private List<String> importantKeywords;
|
||||||
|
|
||||||
|
@Schema(description = "预设问题列表")
|
||||||
|
private List<String> questions;
|
||||||
|
|
||||||
|
@Schema(description = "图片 ID")
|
||||||
|
@JsonProperty("image_id")
|
||||||
|
private String imageId;
|
||||||
|
|
||||||
|
@Schema(description = "位置索引 (RAGFlow返回嵌套数组, 如 [[start, end, filename]])")
|
||||||
|
private Object positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库元数据摘要 (VO)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "知识库元数据摘要信息")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class MetaSummaryVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "文档总数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("total_doc_count")
|
||||||
|
private Long totalDocCount;
|
||||||
|
|
||||||
|
@Schema(description = "Token 总数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@JsonProperty("total_token_count")
|
||||||
|
private Long totalTokenCount;
|
||||||
|
|
||||||
|
@Schema(description = "文件类型分布 (key: 文件后缀, value: 数量)")
|
||||||
|
@JsonProperty("file_type_distribution")
|
||||||
|
private Map<String, Long> fileTypeDistribution;
|
||||||
|
|
||||||
|
@Schema(description = "文状态分布 (key: 状态码, value: 数量)")
|
||||||
|
@JsonProperty("status_distribution")
|
||||||
|
private Map<String, Long> statusDistribution;
|
||||||
|
|
||||||
|
@Schema(description = "自定义元数据统计 (key: 字段名, value: 数量/值)")
|
||||||
|
@JsonProperty("custom_metadata")
|
||||||
|
private Map<String, Object> customMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新元数据请求参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "批量更新元数据请求参数")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class MetaBatchReq implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "筛选器: 用于指定要更新的文档范围 (默认全部)")
|
||||||
|
private Selector selector;
|
||||||
|
|
||||||
|
@Schema(description = "新增或更新的元数据列表")
|
||||||
|
private List<UpdateItem> updates;
|
||||||
|
|
||||||
|
@Schema(description = "需要删除的元数据键列表")
|
||||||
|
private List<DeleteItem> deletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档筛选器
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "元数据更新筛选器")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class Selector implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "指定文档 ID 列表")
|
||||||
|
@JsonProperty("document_ids")
|
||||||
|
private List<String> documentIds;
|
||||||
|
|
||||||
|
@Schema(description = "元数据条件匹配 (key: 字段名, value: 匹配值)")
|
||||||
|
@JsonProperty("metadata_condition")
|
||||||
|
private Map<String, Object> metadataCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "元数据更新项")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class UpdateItem implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "元数据键名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@Schema(description = "元数据值", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Object value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "元数据删除项")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class DeleteItem implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "需删除的元数据键名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 召回测试结果聚合响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "召回测试结果聚合响应")
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ResultVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "检索命中的切片列表")
|
||||||
|
private List<HitVO> chunks;
|
||||||
|
|
||||||
|
@Schema(description = "文档分布统计")
|
||||||
|
@JsonProperty("doc_aggs")
|
||||||
|
private List<DocAggVO> docAggs;
|
||||||
|
|
||||||
|
@Schema(description = "总命中记录数")
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
package xiaozhi.modules.knowledge.dto.file;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件管理聚合 DTO
|
||||||
|
* <p>
|
||||||
|
* 容器类,内含文件模块所有请求/响应对象的静态内部类定义。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Schema(description = "文件管理聚合 DTO")
|
||||||
|
public class FileDTO {
|
||||||
|
|
||||||
|
// ========== 请求类 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传请求 (对应接口 1: upload)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文件上传请求")
|
||||||
|
public static class UploadReq implements Serializable {
|
||||||
|
|
||||||
|
@NotNull(message = "文件不能为空")
|
||||||
|
@Schema(description = "上传的文件", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private MultipartFile file;
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹 ID (为空则上传到根目录)", example = "folder_001")
|
||||||
|
@JsonProperty("parent_id")
|
||||||
|
private String parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建文件夹请求 (对应接口 2: create)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "新建文件夹请求")
|
||||||
|
public static class CreateReq implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "文件夹名称不能为空")
|
||||||
|
@Schema(description = "文件夹名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "新建文件夹")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹 ID (为空则创建在根目录)", example = "folder_001")
|
||||||
|
@JsonProperty("parent_id")
|
||||||
|
private String parentId;
|
||||||
|
|
||||||
|
@NotBlank(message = "类型不能为空")
|
||||||
|
@Schema(description = "类型: FOLDER", requiredMode = Schema.RequiredMode.REQUIRED, example = "FOLDER")
|
||||||
|
@Builder.Default
|
||||||
|
private String type = "FOLDER";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名请求 (对应接口 6: rename)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "重命名请求")
|
||||||
|
public static class RenameReq implements Serializable {
|
||||||
|
|
||||||
|
@NotBlank(message = "文件 ID 不能为空")
|
||||||
|
@Schema(description = "文件/文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "file_001")
|
||||||
|
@JsonProperty("file_id")
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
@NotBlank(message = "新名称不能为空")
|
||||||
|
@Schema(description = "新名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重命名后的文件")
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动请求 (对应接口 7: move)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "移动请求")
|
||||||
|
public static class MoveReq implements Serializable {
|
||||||
|
|
||||||
|
@NotEmpty(message = "源文件 ID 列表不能为空")
|
||||||
|
@Schema(description = "源文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
||||||
|
@JsonProperty("src_file_ids")
|
||||||
|
private List<String> srcFileIds;
|
||||||
|
|
||||||
|
@NotBlank(message = "目标文件夹 ID 不能为空")
|
||||||
|
@Schema(description = "目标文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "folder_002")
|
||||||
|
@JsonProperty("dest_file_id")
|
||||||
|
private String destFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除请求 (对应接口 8: rm)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "批量删除请求")
|
||||||
|
public static class RemoveReq implements Serializable {
|
||||||
|
|
||||||
|
@NotEmpty(message = "文件 ID 列表不能为空")
|
||||||
|
@Schema(description = "文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
||||||
|
@JsonProperty("file_ids")
|
||||||
|
private List<String> fileIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入知识库请求 (对应接口 9: convert)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "导入知识库请求")
|
||||||
|
public static class ConvertReq implements Serializable {
|
||||||
|
|
||||||
|
@NotEmpty(message = "文件 ID 列表不能为空")
|
||||||
|
@Schema(description = "文件 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
|
||||||
|
@JsonProperty("file_ids")
|
||||||
|
private List<String> fileIds;
|
||||||
|
|
||||||
|
@NotEmpty(message = "知识库 ID 列表不能为空")
|
||||||
|
@Schema(description = "目标知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"kb_001\"]")
|
||||||
|
@JsonProperty("kb_ids")
|
||||||
|
private List<String> kbIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表查询请求 (对应接口 3: list_files)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "列表查询请求")
|
||||||
|
public static class ListReq implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹 ID (为空则查询根目录)", example = "folder_001")
|
||||||
|
@JsonProperty("parent_id")
|
||||||
|
private String parentId;
|
||||||
|
|
||||||
|
@Schema(description = "关键词搜索", example = "文档")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "页码 (从 1 开始)", example = "1")
|
||||||
|
private Integer page;
|
||||||
|
|
||||||
|
@Schema(description = "每页数量", example = "30")
|
||||||
|
@JsonProperty("page_size")
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
@Schema(description = "排序字段: create_time / update_time / name / size", example = "create_time")
|
||||||
|
private String orderby;
|
||||||
|
|
||||||
|
@Schema(description = "是否降序", example = "true")
|
||||||
|
private Boolean desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 响应类 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件/文件夹基础信息 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文件/文件夹基础信息")
|
||||||
|
public static class InfoVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "文件/文件夹 ID", example = "file_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹 ID", example = "folder_001")
|
||||||
|
@JsonProperty("parent_id")
|
||||||
|
private String parentId;
|
||||||
|
|
||||||
|
@Schema(description = "租户 ID", example = "tenant_001")
|
||||||
|
@JsonProperty("tenant_id")
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "创建者 ID", example = "user_001")
|
||||||
|
@JsonProperty("created_by")
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@Schema(description = "类型: FOLDER / FILE", example = "FOLDER")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "名称", example = "我的文件夹")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "路径位置", example = "/root/folder")
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
@Schema(description = "文件大小 (字节)", example = "1024")
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
@Schema(description = "来源类型", example = "local")
|
||||||
|
@JsonProperty("source_type")
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
|
||||||
|
@JsonProperty("create_date")
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
|
||||||
|
@JsonProperty("update_date")
|
||||||
|
private String updateDate;
|
||||||
|
|
||||||
|
@Schema(description = "文件扩展名", example = "pdf")
|
||||||
|
private String extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表响应 VO (对应接口 3: list_files)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文件列表响应")
|
||||||
|
public static class ListVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "总记录数", example = "100")
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "当前父文件夹信息")
|
||||||
|
@JsonProperty("parent_folder")
|
||||||
|
private InfoVO parentFolder;
|
||||||
|
|
||||||
|
@Schema(description = "文件/文件夹列表")
|
||||||
|
private List<InfoVO> files;
|
||||||
|
|
||||||
|
@Schema(description = "面包屑导航路径")
|
||||||
|
private List<InfoVO> breadcrumb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换结果项 VO (对应接口 9: convert)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文件转换结果项")
|
||||||
|
public static class ConvertVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "转换记录 ID", example = "convert_001")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "源文件 ID", example = "file_001")
|
||||||
|
@JsonProperty("file_id")
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
@Schema(description = "目标文档 ID", example = "doc_001")
|
||||||
|
@JsonProperty("document_id")
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
|
||||||
|
@JsonProperty("create_time")
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
|
||||||
|
@JsonProperty("create_date")
|
||||||
|
private String createDate;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
|
||||||
|
@JsonProperty("update_time")
|
||||||
|
private Long updateTime;
|
||||||
|
|
||||||
|
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
|
||||||
|
@JsonProperty("update_date")
|
||||||
|
private String updateDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换状态 VO (对应接口 10: get_convert_status)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "文件转换状态")
|
||||||
|
public static class ConvertStatusVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "转换状态: pending / processing / completed / failed", example = "completed")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "转换进度 (0.0 - 1.0)", example = "1.0")
|
||||||
|
private Float progress;
|
||||||
|
|
||||||
|
@Schema(description = "状态消息", example = "转换完成")
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 面包屑 VO (对应接口 12: all_parent_folder)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "面包屑导航 (所有父文件夹)")
|
||||||
|
public static class BreadcrumbVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹列表 (从根到当前的路径)")
|
||||||
|
@JsonProperty("parent_folders")
|
||||||
|
private List<InfoVO> parentFolders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根目录信息 VO (对应接口 10: get_root_folder)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "根目录信息")
|
||||||
|
public static class RootFolderVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "根文件夹信息")
|
||||||
|
@JsonProperty("root_folder")
|
||||||
|
private InfoVO rootFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父目录信息 VO (对应接口 11: get_parent_folder)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "父目录信息")
|
||||||
|
public static class ParentFolderVO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "父文件夹信息")
|
||||||
|
@JsonProperty("parent_folder")
|
||||||
|
private InfoVO parentFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package xiaozhi.modules.knowledge.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档表 (Shadow DB for RAGFlow Documents)
|
||||||
|
* 对应表名: ai_knowledge_document
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "ai_rag_knowledge_document", autoResultMap = true)
|
||||||
|
@Schema(description = "知识库文档表")
|
||||||
|
public class DocumentEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
@Schema(description = "本地唯一ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "知识库ID (关联 ai_rag_dataset.dataset_id)")
|
||||||
|
private String datasetId;
|
||||||
|
|
||||||
|
@Schema(description = "RAGFlow文档ID (远程ID)")
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
@Schema(description = "文档名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "文件大小(Bytes)")
|
||||||
|
private Long size;
|
||||||
|
|
||||||
|
@Schema(description = "文件类型(pdf/doc/txt等)")
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析配置(JSON String)")
|
||||||
|
private String parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "可用状态 (1: 启用/正常, 0: 禁用/失效)")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "运行状态 (UNSTART/RUNNING/CANCEL/DONE/FAIL)")
|
||||||
|
private String run;
|
||||||
|
|
||||||
|
@Schema(description = "解析进度 (0.0 ~ 1.0)")
|
||||||
|
private Double progress;
|
||||||
|
|
||||||
|
@Schema(description = "缩略图 (Base64 或 URL)")
|
||||||
|
private String thumbnail;
|
||||||
|
|
||||||
|
@Schema(description = "解析耗时 (单位: 秒)")
|
||||||
|
private Double processDuration;
|
||||||
|
|
||||||
|
@Schema(description = "自定义元数据 (JSON 格式)")
|
||||||
|
private String metaFields;
|
||||||
|
|
||||||
|
@Schema(description = "来源类型 (local, s3, url 等)")
|
||||||
|
private String sourceType;
|
||||||
|
|
||||||
|
@Schema(description = "解析错误信息")
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
@Schema(description = "分块数量")
|
||||||
|
private Integer chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "Token数量")
|
||||||
|
private Long tokenCount;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用 (0:禁用 1:启用)")
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
@Schema(description = "创建者")
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@Schema(description = "最新同步时间")
|
||||||
|
private Date lastSyncAt;
|
||||||
|
}
|
||||||
+29
-1
@@ -23,15 +23,43 @@ public class KnowledgeBaseEntity {
|
|||||||
@Schema(description = "知识库ID")
|
@Schema(description = "知识库ID")
|
||||||
private String datasetId;
|
private String datasetId;
|
||||||
|
|
||||||
@Schema(description = "RAG模型配置ID")
|
// @Deprecated
|
||||||
|
@Schema(description = "RAG模型配置ID (连接RAGFlow的凭证指针)")
|
||||||
private String ragModelId;
|
private String ragModelId;
|
||||||
|
|
||||||
|
@Schema(description = "租户ID")
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
@Schema(description = "知识库名称")
|
@Schema(description = "知识库名称")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "知识库头像(Base64)")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
@Schema(description = "知识库描述")
|
@Schema(description = "知识库描述")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "嵌入模型名称")
|
||||||
|
private String embeddingModel;
|
||||||
|
|
||||||
|
@Schema(description = "权限设置: me/team")
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Schema(description = "分块方法")
|
||||||
|
private String chunkMethod;
|
||||||
|
|
||||||
|
@Schema(description = "解析器配置(JSON String)")
|
||||||
|
private String parserConfig;
|
||||||
|
|
||||||
|
@Schema(description = "分块总数")
|
||||||
|
private Long chunkCount;
|
||||||
|
|
||||||
|
@Schema(description = "文档总数")
|
||||||
|
private Long documentCount;
|
||||||
|
|
||||||
|
@Schema(description = "总Token数")
|
||||||
|
private Long tokenNum;
|
||||||
|
|
||||||
@Schema(description = "状态(0:禁用 1:启用)")
|
@Schema(description = "状态(0:禁用 1:启用)")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|||||||
+64
-49
@@ -3,10 +3,14 @@ package xiaozhi.modules.knowledge.rag;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库API适配器抽象基类
|
* 知识库API适配器抽象基类
|
||||||
@@ -46,35 +50,24 @@ public abstract class KnowledgeBaseAdapter {
|
|||||||
* @return 分页数据
|
* @return 分页数据
|
||||||
*/
|
*/
|
||||||
public abstract PageData<KnowledgeFilesDTO> getDocumentList(String datasetId,
|
public abstract PageData<KnowledgeFilesDTO> getDocumentList(String datasetId,
|
||||||
Map<String, Object> queryParams,
|
DocumentDTO.ListReq req);
|
||||||
Integer page,
|
|
||||||
Integer limit);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文档ID获取文档详情
|
* 根据文档ID获取文档详情
|
||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @return 文档详情
|
* @param documentId 文档ID
|
||||||
|
* @return 文档详情 (强类型 InfoVO)
|
||||||
*/
|
*/
|
||||||
public abstract KnowledgeFilesDTO getDocumentById(String datasetId, String documentId);
|
public abstract DocumentDTO.InfoVO getDocumentById(String datasetId, String documentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文档到知识库
|
* 上传文档到知识库
|
||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param req 上传请求参数
|
||||||
* @param file 上传的文件
|
|
||||||
* @param name 文档名称
|
|
||||||
* @param metaFields 元数据字段
|
|
||||||
* @param chunkMethod 分块方法
|
|
||||||
* @param parserConfig 解析器配置
|
|
||||||
* @return 上传的文档信息
|
* @return 上传的文档信息
|
||||||
*/
|
*/
|
||||||
public abstract KnowledgeFilesDTO uploadDocument(String datasetId,
|
public abstract KnowledgeFilesDTO uploadDocument(DocumentDTO.UploadReq req);
|
||||||
MultipartFile file,
|
|
||||||
String name,
|
|
||||||
Map<String, Object> metaFields,
|
|
||||||
String chunkMethod,
|
|
||||||
Map<String, Object> parserConfig);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据状态分页查询文档列表
|
* 根据状态分页查询文档列表
|
||||||
@@ -91,12 +84,12 @@ public abstract class KnowledgeBaseAdapter {
|
|||||||
Integer limit);
|
Integer limit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除文档
|
* 删除文档 (支持批量删除)
|
||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @param documentId 文档ID
|
* @param req 包含文档ID列表的请求对象
|
||||||
*/
|
*/
|
||||||
public abstract void deleteDocument(String datasetId, String documentId);
|
public abstract void deleteDocument(String datasetId, DocumentDTO.BatchIdReq req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析文档(切块)
|
* 解析文档(切块)
|
||||||
@@ -112,32 +105,21 @@ public abstract class KnowledgeBaseAdapter {
|
|||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @param documentId 文档ID
|
* @param documentId 文档ID
|
||||||
* @param keywords 关键词过滤
|
* @param req 列表请求参数 (分页、关键词等)
|
||||||
* @param page 页码
|
* @return 切片列表VO
|
||||||
* @param pageSize 每页数量
|
|
||||||
* @param chunkId 切片ID
|
|
||||||
* @return 切片列表信息
|
|
||||||
*/
|
*/
|
||||||
public abstract Map<String, Object> listChunks(String datasetId,
|
public abstract ChunkDTO.ListVO listChunks(String datasetId,
|
||||||
String documentId,
|
String documentId,
|
||||||
String keywords,
|
ChunkDTO.ListReq req);
|
||||||
Integer page,
|
|
||||||
Integer pageSize,
|
|
||||||
String chunkId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 召回测试 - 从知识库中检索相关切片
|
* 召回测试 - 从知识库中检索相关切片
|
||||||
*
|
*
|
||||||
* @param question 用户查询
|
* @param req 检索测试请求参数
|
||||||
* @param datasetIds 数据集ID列表
|
|
||||||
* @param documentIds 文档ID列表
|
|
||||||
* @param retrievalParams 检索参数
|
|
||||||
* @return 召回测试结果
|
* @return 召回测试结果
|
||||||
*/
|
*/
|
||||||
public abstract Map<String, Object> retrievalTest(String question,
|
public abstract RetrievalDTO.ResultVO retrievalTest(
|
||||||
List<String> datasetIds,
|
RetrievalDTO.TestReq req);
|
||||||
List<String> documentIds,
|
|
||||||
Map<String, Object> retrievalParams);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试连接
|
* 测试连接
|
||||||
@@ -170,25 +152,27 @@ public abstract class KnowledgeBaseAdapter {
|
|||||||
/**
|
/**
|
||||||
* 创建数据集
|
* 创建数据集
|
||||||
*
|
*
|
||||||
* @param createParams 创建参数
|
* @param req 创建参数
|
||||||
* @return 数据集ID
|
* @return 数据集详情
|
||||||
*/
|
*/
|
||||||
public abstract String createDataset(Map<String, Object> createParams);
|
public abstract DatasetDTO.InfoVO createDataset(DatasetDTO.CreateReq req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新数据集
|
* 更新数据集
|
||||||
*
|
*
|
||||||
* @param datasetId 数据集ID
|
* @param datasetId 数据集ID
|
||||||
* @param updateParams 更新参数
|
* @param req 更新参数
|
||||||
|
* @return 数据集详情
|
||||||
*/
|
*/
|
||||||
public abstract void updateDataset(String datasetId, Map<String, Object> updateParams);
|
public abstract DatasetDTO.InfoVO updateDataset(String datasetId, DatasetDTO.UpdateReq req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除数据集
|
* 删除数据集
|
||||||
*
|
*
|
||||||
* @param datasetId 数据集ID
|
* @param req 删除请求参数(包含ID列表)
|
||||||
|
* @return 批量操作结果
|
||||||
*/
|
*/
|
||||||
public abstract void deleteDataset(String datasetId);
|
public abstract DatasetDTO.BatchOperationVO deleteDataset(DatasetDTO.BatchIdReq req);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取数据集的文档数量
|
* 获取数据集的文档数量
|
||||||
@@ -197,4 +181,35 @@ public abstract class KnowledgeBaseAdapter {
|
|||||||
* @return 文档数量
|
* @return 文档数量
|
||||||
*/
|
*/
|
||||||
public abstract Integer getDocumentCount(String datasetId);
|
public abstract Integer getDocumentCount(String datasetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送流式请求 (SSE)
|
||||||
|
*
|
||||||
|
* @param endpoint API端点
|
||||||
|
* @param body 请求体
|
||||||
|
* @param onData 数据回调
|
||||||
|
*/
|
||||||
|
public abstract void postStream(String endpoint, Object body, Consumer<String> onData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SearchBot 提问
|
||||||
|
*
|
||||||
|
* @param config RAG配置
|
||||||
|
* @param body 请求体
|
||||||
|
* @param onData 数据回调
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
public abstract Object postSearchBotAsk(Map<String, Object> config, Object body,
|
||||||
|
Consumer<String> onData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AgentBot 对话
|
||||||
|
*
|
||||||
|
* @param config RAG配置
|
||||||
|
* @param agentId Agent ID
|
||||||
|
* @param body 请求体
|
||||||
|
* @param onData 数据回调
|
||||||
|
*/
|
||||||
|
public abstract void postAgentBotCompletion(Map<String, Object> config, String agentId, Object body,
|
||||||
|
Consumer<String> onData);
|
||||||
}
|
}
|
||||||
+10
-1
@@ -22,6 +22,9 @@ public class KnowledgeBaseAdapterFactory {
|
|||||||
// 适配器实例缓存
|
// 适配器实例缓存
|
||||||
private static final Map<String, KnowledgeBaseAdapter> adapterCache = new ConcurrentHashMap<>();
|
private static final Map<String, KnowledgeBaseAdapter> adapterCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 最大缓存实例数,防止内存泄露 (Issue 9)
|
||||||
|
private static final int MAX_CACHE_SIZE = 50;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// 注册内置适配器类型
|
// 注册内置适配器类型
|
||||||
registerAdapter("ragflow", xiaozhi.modules.knowledge.rag.impl.RAGFlowAdapter.class);
|
registerAdapter("ragflow", xiaozhi.modules.knowledge.rag.impl.RAGFlowAdapter.class);
|
||||||
@@ -61,7 +64,13 @@ public class KnowledgeBaseAdapterFactory {
|
|||||||
// 创建新的适配器实例
|
// 创建新的适配器实例
|
||||||
KnowledgeBaseAdapter adapter = createAdapter(adapterType, config);
|
KnowledgeBaseAdapter adapter = createAdapter(adapterType, config);
|
||||||
|
|
||||||
// 缓存适配器实例
|
// 缓存适配器实例 (带容量限制检查)
|
||||||
|
if (adapterCache.size() >= MAX_CACHE_SIZE) {
|
||||||
|
log.warn("适配器缓存已达上限 ({}),执行内存保护性清除", MAX_CACHE_SIZE);
|
||||||
|
// 简单处理:直接清空,生产环境下建议使用 LRU
|
||||||
|
adapterCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
adapterCache.put(cacheKey, adapter);
|
adapterCache.put(cacheKey, adapter);
|
||||||
log.info("创建并缓存适配器实例: {}", cacheKey);
|
log.info("创建并缓存适配器实例: {}", cacheKey);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package xiaozhi.modules.knowledge.rag;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAGFlow HTTP Client
|
||||||
|
* 统一处理HTTP通信、鉴权、超时与错误解析
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class RAGFlowClient {
|
||||||
|
|
||||||
|
private final String baseUrl;
|
||||||
|
private final String apiKey;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
// 默认超时时间 (秒)
|
||||||
|
private static final int DEFAULT_TIMEOUT = 30;
|
||||||
|
|
||||||
|
public RAGFlowClient(String baseUrl, String apiKey) {
|
||||||
|
this(baseUrl, apiKey, DEFAULT_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RAGFlowClient(String baseUrl, String apiKey, int timeoutSeconds) {
|
||||||
|
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
this.objectMapper = new ObjectMapper();
|
||||||
|
// [Reinforce] 兼容 RAGFlow 返回的 RFC 1123 日期格式 (如: Tue, 10 Feb 2026 10:27:35 GMT)
|
||||||
|
this.objectMapper
|
||||||
|
.setDateFormat(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US));
|
||||||
|
this.objectMapper.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
|
|
||||||
|
// 优先从 Spring 上下文中获取池化的 RestTemplate Bean (Issue 3: 连接池化)
|
||||||
|
RestTemplate pooledTemplate = null;
|
||||||
|
try {
|
||||||
|
pooledTemplate = xiaozhi.common.utils.SpringContextUtils.getBean(RestTemplate.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("无法从 SpringContext 获取池化 RestTemplate,将退化为简单连接模式: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false) { // Force new RestTemplate for debugging
|
||||||
|
this.restTemplate = pooledTemplate;
|
||||||
|
log.debug("RAGFlowClient 已成功挂载全局池化 RestTemplate");
|
||||||
|
} else {
|
||||||
|
// 兜底方案:配置超时并创建简单 RestTemplate
|
||||||
|
log.info("RAGFlowClient 初始化: 使用独立 RestTemplate (Debug Mode)");
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(timeoutSeconds * 1000);
|
||||||
|
factory.setReadTimeout(timeoutSeconds * 1000);
|
||||||
|
this.restTemplate = new RestTemplate(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 GET 请求
|
||||||
|
*/
|
||||||
|
public Map<String, Object> get(String endpoint, Map<String, Object> queryParams) {
|
||||||
|
String url = buildUrl(endpoint, queryParams);
|
||||||
|
log.debug("GET {}", url);
|
||||||
|
return execute(url, HttpMethod.GET, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 POST 请求 (JSON)
|
||||||
|
*/
|
||||||
|
public Map<String, Object> post(String endpoint, Object body) {
|
||||||
|
String url = buildUrl(endpoint, null);
|
||||||
|
log.info("RAGFlow Client POST Request: URL={}, BodyType={}", url,
|
||||||
|
body != null ? body.getClass().getName() : "null");
|
||||||
|
try {
|
||||||
|
return execute(url, HttpMethod.POST, body);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("RAGFlow Client POST Failed: URL={}", url, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 DELETE 请求
|
||||||
|
*/
|
||||||
|
public Map<String, Object> delete(String endpoint, Object body) {
|
||||||
|
String url = buildUrl(endpoint, null);
|
||||||
|
log.debug("DELETE {}", url);
|
||||||
|
return execute(url, HttpMethod.DELETE, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 PUT 请求
|
||||||
|
*/
|
||||||
|
public Map<String, Object> put(String endpoint, Object body) {
|
||||||
|
String url = buildUrl(endpoint, null);
|
||||||
|
log.debug("PUT {}", url);
|
||||||
|
return execute(url, HttpMethod.PUT, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 Multipart 请求 (文件上传)
|
||||||
|
*/
|
||||||
|
public Map<String, Object> postMultipart(String endpoint, MultiValueMap<String, Object> parts) {
|
||||||
|
String url = buildUrl(endpoint, null);
|
||||||
|
log.debug("POST MULTIPART {}", url);
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
headers.setBearerAuth(apiKey);
|
||||||
|
// 为了防止中文文件名乱码,某些环境可能需要设置 Charset,但在 Multipart 中通常由 Part header 控制
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(parts, headers);
|
||||||
|
|
||||||
|
return doExecute(url, HttpMethod.POST, requestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> execute(String url, HttpMethod method, Object body) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.setBearerAuth(apiKey);
|
||||||
|
// 强制 UTF-8
|
||||||
|
headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
|
||||||
|
return doExecute(url, method, requestEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> doExecute(String url, HttpMethod method, HttpEntity<?> requestEntity) {
|
||||||
|
try {
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
|
||||||
|
|
||||||
|
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
log.error("RAGFlow API Error Status: {}", response.getStatusCode());
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "HTTP " + response.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
String responseBody = response.getBody();
|
||||||
|
if (responseBody == null) {
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "Empty Response");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = objectMapper.readValue(responseBody, Map.class);
|
||||||
|
|
||||||
|
Integer code = (Integer) map.get("code");
|
||||||
|
if (code != null && code != 0) {
|
||||||
|
String msg = (String) map.get("message");
|
||||||
|
log.error("RAGFlow Business Error: code={}, msg={}", code, msg);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, msg != null ? msg : "Unknown RAGFlow Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回 data 字段,如果 data 不存在则返回整个 map (视具体情况,通常 RAGFlow 返回 code=0, data=...)
|
||||||
|
// 兼容性处理:如果 external caller 需要 check code,这里已经 check 过了。
|
||||||
|
// 统一返回 wrap 了 code 的 map 还是只返回 data?
|
||||||
|
// 根据分析报告,旧逻辑 check code==0 后取 data.
|
||||||
|
// 这里我们返回整个 Map,让 Adapter 决定怎么取,或者我们直接在这里剥离?
|
||||||
|
// 建议:为了灵活性,返回全量 Map,但在 Client 层做 code!=0 的抛错。
|
||||||
|
return map;
|
||||||
|
|
||||||
|
} catch (RenException re) {
|
||||||
|
throw re;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("RAGFlow Client Execute Error! URL: {}, Method: {}, Body Type: {}", url, method,
|
||||||
|
requestEntity.getBody() != null ? requestEntity.getBody().getClass().getName() : "null");
|
||||||
|
log.error("Full exception stack trace: ", e);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "Request Failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildUrl(String endpoint, Map<String, Object> queryParams) {
|
||||||
|
StringBuilder sb = new StringBuilder(baseUrl);
|
||||||
|
if (!endpoint.startsWith("/")) {
|
||||||
|
sb.append("/");
|
||||||
|
}
|
||||||
|
sb.append(endpoint);
|
||||||
|
|
||||||
|
if (queryParams != null && !queryParams.isEmpty()) {
|
||||||
|
sb.append("?");
|
||||||
|
queryParams.forEach((k, v) -> {
|
||||||
|
if (v != null) {
|
||||||
|
try {
|
||||||
|
sb.append(k).append("=")
|
||||||
|
.append(URLEncoder.encode(v.toString(),
|
||||||
|
StandardCharsets.UTF_8.name()))
|
||||||
|
.append("&");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
log.warn("参数编码失败: k={}, v={}", k, v);
|
||||||
|
sb.append(k).append("=").append(v).append("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 移除最后一个 &
|
||||||
|
sb.setLength(sb.length() - 1);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送流式 POST 请求 (SSE)
|
||||||
|
* 使用 Java 21 HttpClient 实现
|
||||||
|
*
|
||||||
|
* @param endpoint API端点
|
||||||
|
* @param body 请求体
|
||||||
|
* @param onData 数据回调(每收到一行数据调用一次)
|
||||||
|
*/
|
||||||
|
public void postStream(String endpoint, Object body, Consumer<String> onData) {
|
||||||
|
try {
|
||||||
|
String url = buildUrl(endpoint, null);
|
||||||
|
log.debug("POST STREAM {}", url);
|
||||||
|
|
||||||
|
String jsonBody = objectMapper.writeValueAsString(body);
|
||||||
|
|
||||||
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 发送请求并处理流式响应
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream())
|
||||||
|
.body()
|
||||||
|
.transferTo(new OutputStream() {
|
||||||
|
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
if (b == '\n') {
|
||||||
|
String line = buffer.toString(StandardCharsets.UTF_8);
|
||||||
|
if (!line.trim().isEmpty()) {
|
||||||
|
onData.accept(line);
|
||||||
|
}
|
||||||
|
buffer.reset();
|
||||||
|
} else {
|
||||||
|
buffer.write(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("RAGFlow Stream Request Error", e);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "Stream Request Failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+455
-892
File diff suppressed because it is too large
Load Diff
+20
-1
@@ -7,6 +7,7 @@ import xiaozhi.common.page.PageData;
|
|||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||||
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
|
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
|
||||||
|
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库知识库服务接口
|
* 知识库知识库服务接口
|
||||||
@@ -55,6 +56,14 @@ public interface KnowledgeBaseService extends BaseService<KnowledgeBaseEntity> {
|
|||||||
*/
|
*/
|
||||||
KnowledgeBaseDTO getByDatasetId(String datasetId);
|
KnowledgeBaseDTO getByDatasetId(String datasetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据知识库ID集合查询知识库
|
||||||
|
*
|
||||||
|
* @param datasetIdList 知识库ID集合
|
||||||
|
* @return 知识库详情
|
||||||
|
*/
|
||||||
|
List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据知识库ID删除知识库
|
* 根据知识库ID删除知识库
|
||||||
*
|
*
|
||||||
@@ -83,5 +92,15 @@ public interface KnowledgeBaseService extends BaseService<KnowledgeBaseEntity> {
|
|||||||
*
|
*
|
||||||
* @return RAG模型列表
|
* @return RAG模型列表
|
||||||
*/
|
*/
|
||||||
List<Map<String, Object>> getRAGModels();
|
List<ModelConfigEntity> getRAGModels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新知识库统计信息 (用于被文件服务回调)
|
||||||
|
*
|
||||||
|
* @param datasetId 知识库ID
|
||||||
|
* @param docDelta 文档数增量
|
||||||
|
* @param chunkDelta 分块数增量
|
||||||
|
* @param tokenDelta Token数增量
|
||||||
|
*/
|
||||||
|
void updateStatistics(String datasetId, Integer docDelta, Long chunkDelta, Long tokenDelta);
|
||||||
}
|
}
|
||||||
+41
-41
@@ -7,6 +7,9 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库文档服务接口
|
* 知识库文档服务接口
|
||||||
@@ -28,9 +31,9 @@ public interface KnowledgeFilesService {
|
|||||||
*
|
*
|
||||||
* @param documentId 文档ID
|
* @param documentId 文档ID
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @return 文档详情
|
* @return 文档详情 (强类型 InfoVO)
|
||||||
*/
|
*/
|
||||||
KnowledgeFilesDTO getByDocumentId(String documentId, String datasetId);
|
DocumentDTO.InfoVO getByDocumentId(String documentId, String datasetId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文档到知识库
|
* 上传文档到知识库
|
||||||
@@ -48,23 +51,12 @@ public interface KnowledgeFilesService {
|
|||||||
Map<String, Object> parserConfig);
|
Map<String, Object> parserConfig);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据状态分页查询文档列表
|
* 批量删除文档
|
||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @param status 文档解析状态(0-未开始,1-进行中,2-已取消,3-已完成,4-失败)
|
* @param req 删除请求参数 (含文档ID列表)
|
||||||
* @param page 页码
|
|
||||||
* @param limit 每页数量
|
|
||||||
* @return 分页数据
|
|
||||||
*/
|
*/
|
||||||
PageData<KnowledgeFilesDTO> getPageListByStatus(String datasetId, Integer status, Integer page, Integer limit);
|
void deleteDocuments(String datasetId, DocumentDTO.BatchIdReq req);
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据文档ID和知识库ID删除文档
|
|
||||||
*
|
|
||||||
* @param documentId 文档ID
|
|
||||||
* @param datasetId 知识库ID
|
|
||||||
*/
|
|
||||||
void deleteByDocumentId(String documentId, String datasetId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取RAG配置信息
|
* 获取RAG配置信息
|
||||||
@@ -88,36 +80,44 @@ public interface KnowledgeFilesService {
|
|||||||
*
|
*
|
||||||
* @param datasetId 知识库ID
|
* @param datasetId 知识库ID
|
||||||
* @param documentId 文档ID
|
* @param documentId 文档ID
|
||||||
* @param keywords 关键词过滤
|
* @param req 切片列表请求参数
|
||||||
* @param page 页码
|
|
||||||
* @param pageSize 每页数量
|
|
||||||
* @param chunkId 切片ID
|
|
||||||
* @return 切片列表信息
|
* @return 切片列表信息
|
||||||
*/
|
*/
|
||||||
Map<String, Object> listChunks(String datasetId, String documentId, String keywords,
|
ChunkDTO.ListVO listChunks(String datasetId, String documentId, ChunkDTO.ListReq req);
|
||||||
Integer page, Integer pageSize, String chunkId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 召回测试 - 从指定数据集或文档中检索相关切片
|
* 召回测试
|
||||||
*
|
*
|
||||||
* @param question 用户查询或查询关键词
|
* @param req 检索测试请求参数
|
||||||
* @param datasetIds 数据集ID列表
|
|
||||||
* @param documentIds 文档ID列表
|
|
||||||
* @param page 页码
|
|
||||||
* @param pageSize 每页数量
|
|
||||||
* @param similarityThreshold 最小相似度阈值
|
|
||||||
* @param vectorSimilarityWeight 向量相似度权重
|
|
||||||
* @param topK 参与向量余弦计算的切片数量
|
|
||||||
* @param rerankId 重排模型ID
|
|
||||||
* @param keyword 是否启用关键词匹配
|
|
||||||
* @param highlight 是否启用高亮显示
|
|
||||||
* @param crossLanguages 跨语言翻译列表
|
|
||||||
* @param metadataCondition 元数据过滤条件
|
|
||||||
* @return 召回测试结果
|
* @return 召回测试结果
|
||||||
*/
|
*/
|
||||||
Map<String, Object> retrievalTest(String question, List<String> datasetIds, List<String> documentIds,
|
RetrievalDTO.ResultVO retrievalTest(RetrievalDTO.TestReq req);
|
||||||
Integer page, Integer pageSize, Float similarityThreshold,
|
|
||||||
Float vectorSimilarityWeight, Integer topK, String rerankId,
|
/**
|
||||||
Boolean keyword, Boolean highlight, List<String> crossLanguages,
|
* 保存文档影子记录
|
||||||
Map<String, Object> metadataCondition);
|
*/
|
||||||
|
void saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
|
||||||
|
Map<String, Object> parserConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除文档影子记录并同步统计数据
|
||||||
|
*
|
||||||
|
* @param documentIds 文档ID列表
|
||||||
|
* @param datasetId 数据集ID
|
||||||
|
* @param chunkDelta 待扣减的总分块数
|
||||||
|
* @param tokenDelta 待扣减的总Token数
|
||||||
|
*/
|
||||||
|
void deleteDocumentShadows(List<String> documentIds, String datasetId, Long chunkDelta, Long tokenDelta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据集ID清理所有关联文档 (级联删除专用)
|
||||||
|
*
|
||||||
|
* @param datasetId 数据集ID
|
||||||
|
*/
|
||||||
|
void deleteDocumentsByDatasetId(String datasetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步所有处于 RUNNING 状态的文档 (供定时任务调用)
|
||||||
|
*/
|
||||||
|
void syncRunningDocuments();
|
||||||
}
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package xiaozhi.modules.knowledge.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库模块领域编排服务
|
||||||
|
* 用于处理跨 KnowledgeBase 和 KnowledgeFiles 的复杂业务流程,彻底解决 Service 间的循环依赖问题。
|
||||||
|
*/
|
||||||
|
public interface KnowledgeManagerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联删除知识库及其下属所有文档 (包括本地 DB 和 RAGFlow 远程数据)
|
||||||
|
*
|
||||||
|
* @param datasetId 知识库 ID
|
||||||
|
*/
|
||||||
|
void deleteDatasetWithFiles(String datasetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量级联删除知识库
|
||||||
|
*
|
||||||
|
* @param datasetIds 知识库 ID 列表
|
||||||
|
*/
|
||||||
|
void batchDeleteDatasetsWithFiles(List<String> datasetIds);
|
||||||
|
}
|
||||||
+286
-513
@@ -1,28 +1,14 @@
|
|||||||
package xiaozhi.modules.knowledge.service.impl;
|
package xiaozhi.modules.knowledge.service.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
@@ -31,9 +17,10 @@ import xiaozhi.common.redis.RedisKeys;
|
|||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.MessageUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao;
|
import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||||
|
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
|
||||||
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
|
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
|
||||||
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter;
|
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter;
|
||||||
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory;
|
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory;
|
||||||
@@ -43,6 +30,15 @@ import xiaozhi.modules.model.entity.ModelConfigEntity;
|
|||||||
import xiaozhi.modules.model.service.ModelConfigService;
|
import xiaozhi.modules.model.service.ModelConfigService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库服务实现类 (Refactored)
|
||||||
|
* 集成 RAGFlow Adapter 与 Shadow DB 模式
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -53,203 +49,51 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
|||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
private final ModelConfigDao modelConfigDao;
|
private final ModelConfigDao modelConfigDao;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private RestTemplate restTemplate = new RestTemplate();
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KnowledgeBaseEntity selectById(Serializable datasetId) {
|
|
||||||
if (datasetId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先从Redis获取缓存
|
|
||||||
String key = RedisKeys.getKnowledgeBaseCacheKey(datasetId.toString());
|
|
||||||
KnowledgeBaseEntity cachedEntity = (KnowledgeBaseEntity) redisUtils.get(key);
|
|
||||||
if (cachedEntity != null) {
|
|
||||||
return cachedEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果缓存中没有,则从数据库获取
|
|
||||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(datasetId);
|
|
||||||
if (entity == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存入Redis缓存
|
|
||||||
redisUtils.set(key, entity);
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<KnowledgeBaseDTO> getPageList(KnowledgeBaseDTO knowledgeBaseDTO, Integer page, Integer limit) {
|
public PageData<KnowledgeBaseDTO> getPageList(KnowledgeBaseDTO knowledgeBaseDTO, Integer page, Integer limit) {
|
||||||
long curPage = page;
|
Page<KnowledgeBaseEntity> pageInfo = new Page<>(page, limit);
|
||||||
long pageSize = limit;
|
|
||||||
Page<KnowledgeBaseEntity> pageInfo = new Page<>(curPage, pageSize);
|
|
||||||
|
|
||||||
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<>();
|
||||||
|
|
||||||
// 添加查询条件
|
|
||||||
if (knowledgeBaseDTO != null) {
|
if (knowledgeBaseDTO != null) {
|
||||||
queryWrapper.like(StringUtils.isNotBlank(knowledgeBaseDTO.getName()), "name", knowledgeBaseDTO.getName());
|
queryWrapper.like(StringUtils.isNotBlank(knowledgeBaseDTO.getName()), "name", knowledgeBaseDTO.getName());
|
||||||
queryWrapper.eq(knowledgeBaseDTO.getStatus() != null, "status", knowledgeBaseDTO.getStatus());
|
queryWrapper.eq(knowledgeBaseDTO.getStatus() != null, "status", knowledgeBaseDTO.getStatus());
|
||||||
queryWrapper.eq("creator", knowledgeBaseDTO.getCreator());
|
queryWrapper.eq("creator", knowledgeBaseDTO.getCreator());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加排序规则:按创建时间降序
|
|
||||||
queryWrapper.orderByDesc("created_at");
|
queryWrapper.orderByDesc("created_at");
|
||||||
|
|
||||||
IPage<KnowledgeBaseEntity> knowledgeBaseEntityIPage = knowledgeBaseDao.selectPage(pageInfo, queryWrapper);
|
IPage<KnowledgeBaseEntity> iPage = knowledgeBaseDao.selectPage(pageInfo, queryWrapper);
|
||||||
|
PageData<KnowledgeBaseDTO> pageData = getPageData(iPage, KnowledgeBaseDTO.class);
|
||||||
|
|
||||||
// 获取分页数据
|
// Enrich with Document Count from RAG (Optional / Lazy)
|
||||||
PageData<KnowledgeBaseDTO> pageData = getPageData(knowledgeBaseEntityIPage, KnowledgeBaseDTO.class);
|
|
||||||
|
|
||||||
// 为每个知识库获取文档数量
|
|
||||||
if (pageData != null && pageData.getList() != null) {
|
if (pageData != null && pageData.getList() != null) {
|
||||||
for (KnowledgeBaseDTO knowledgeBase : pageData.getList()) {
|
for (KnowledgeBaseDTO dto : pageData.getList()) {
|
||||||
try {
|
enrichDocumentCount(dto);
|
||||||
Integer documentCount = getDocumentCountFromRAG(knowledgeBase.getDatasetId(),
|
|
||||||
knowledgeBase.getRagModelId());
|
|
||||||
knowledgeBase.setDocumentCount(documentCount);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 构建详细的错误信息,包含异常类型和消息
|
|
||||||
String baseErrorMessage = e.getClass().getSimpleName() + " - 获取知识库文档数量失败";
|
|
||||||
String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
|
|
||||||
log.warn("知识库 {} {}", knowledgeBase.getDatasetId(), errorMessage);
|
|
||||||
knowledgeBase.setDocumentCount(0); // 设置默认值
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageData;
|
return pageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void enrichDocumentCount(KnowledgeBaseDTO dto) {
|
||||||
|
try {
|
||||||
|
if (StringUtils.isNotBlank(dto.getDatasetId()) && StringUtils.isNotBlank(dto.getRagModelId())) {
|
||||||
|
KnowledgeBaseAdapter adapter = getAdapterByModelId(dto.getRagModelId());
|
||||||
|
if (adapter != null) {
|
||||||
|
dto.setDocumentCount(adapter.getDocumentCount(dto.getDatasetId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("无法获取知识库 {} 的文档计数: {}", dto.getName(), e.getMessage());
|
||||||
|
dto.setDocumentCount(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KnowledgeBaseDTO getById(String id) {
|
public KnowledgeBaseDTO getById(String id) {
|
||||||
if (StringUtils.isBlank(id)) {
|
|
||||||
throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(id);
|
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(id);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KnowledgeBaseDTO save(KnowledgeBaseDTO knowledgeBaseDTO) {
|
|
||||||
if (knowledgeBaseDTO == null) {
|
|
||||||
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否存在同名知识库
|
|
||||||
checkDuplicateKnowledgeBaseName(knowledgeBaseDTO, null);
|
|
||||||
|
|
||||||
String datasetId = null;
|
|
||||||
// 调用RAG API创建数据集
|
|
||||||
try {
|
|
||||||
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
|
|
||||||
datasetId = createDatasetInRAG(
|
|
||||||
knowledgeBaseDTO.getName(),
|
|
||||||
knowledgeBaseDTO.getDescription(),
|
|
||||||
ragConfig);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 如果RAG API调用失败,直接抛出异常
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证数据集ID是否已存在
|
|
||||||
KnowledgeBaseEntity existingEntity = knowledgeBaseDao.selectOne(
|
|
||||||
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
|
|
||||||
if (existingEntity != null) {
|
|
||||||
// 如果datasetId已存在,删除RAG中的数据集并抛出异常
|
|
||||||
try {
|
|
||||||
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
|
|
||||||
deleteDatasetInRAG(datasetId, ragConfig);
|
|
||||||
} catch (Exception deleteException) {
|
|
||||||
// 提供更详细的错误信息,包括异常类型和消息
|
|
||||||
String errorMessage = "删除重复datasetId的RAG数据集失败: " + deleteException.getClass().getSimpleName();
|
|
||||||
if (deleteException.getMessage() != null) {
|
|
||||||
errorMessage += " - " + deleteException.getMessage();
|
|
||||||
}
|
|
||||||
log.warn(errorMessage, deleteException);
|
|
||||||
}
|
|
||||||
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建本地实体并保存
|
|
||||||
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(knowledgeBaseDTO, KnowledgeBaseEntity.class);
|
|
||||||
entity.setDatasetId(datasetId);
|
|
||||||
knowledgeBaseDao.insert(entity);
|
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KnowledgeBaseDTO update(KnowledgeBaseDTO knowledgeBaseDTO) {
|
|
||||||
if (knowledgeBaseDTO == null || StringUtils.isBlank(knowledgeBaseDTO.getId())) {
|
|
||||||
throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查记录是否存在
|
|
||||||
KnowledgeBaseEntity existingEntity = knowledgeBaseDao.selectById(knowledgeBaseDTO.getId());
|
|
||||||
if (existingEntity == null) {
|
|
||||||
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否存在同名知识库(排除当前记录)
|
|
||||||
checkDuplicateKnowledgeBaseName(knowledgeBaseDTO, knowledgeBaseDTO.getId());
|
|
||||||
|
|
||||||
// 验证数据集ID是否与其他记录冲突
|
|
||||||
if (StringUtils.isNotBlank(knowledgeBaseDTO.getDatasetId())) {
|
|
||||||
KnowledgeBaseEntity conflictEntity = knowledgeBaseDao.selectOne(
|
|
||||||
new QueryWrapper<KnowledgeBaseEntity>()
|
|
||||||
.eq("dataset_id", knowledgeBaseDTO.getDatasetId())
|
|
||||||
.ne("id", knowledgeBaseDTO.getId()));
|
|
||||||
if (conflictEntity != null) {
|
|
||||||
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean needRagValidation = StringUtils.isNotBlank(knowledgeBaseDTO.getDatasetId())
|
|
||||||
&& StringUtils.isNotBlank(knowledgeBaseDTO.getRagModelId());
|
|
||||||
|
|
||||||
if (needRagValidation) {
|
|
||||||
try {
|
|
||||||
// 先校验RAG配置
|
|
||||||
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
|
|
||||||
|
|
||||||
// 调用RAG API更新数据集
|
|
||||||
updateDatasetInRAG(
|
|
||||||
knowledgeBaseDTO.getDatasetId(),
|
|
||||||
knowledgeBaseDTO.getName(),
|
|
||||||
knowledgeBaseDTO.getDescription(),
|
|
||||||
ragConfig);
|
|
||||||
|
|
||||||
log.info("RAG API更新成功,datasetId: {}", knowledgeBaseDTO.getDatasetId());
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 提供更详细的错误信息,包括异常类型和消息
|
|
||||||
String errorMessage = "更新RAG数据集失败: " + e.getClass().getSimpleName();
|
|
||||||
if (e.getMessage() != null) {
|
|
||||||
errorMessage += " - " + e.getMessage();
|
|
||||||
}
|
|
||||||
log.error(errorMessage, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("datasetId或ragModelId为空,跳过RAG更新");
|
|
||||||
}
|
|
||||||
|
|
||||||
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(knowledgeBaseDTO, KnowledgeBaseEntity.class);
|
|
||||||
knowledgeBaseDao.updateById(entity);
|
|
||||||
|
|
||||||
// 删除缓存
|
|
||||||
if (entity.getDatasetId() != null) {
|
|
||||||
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,387 +102,316 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
|||||||
if (StringUtils.isBlank(datasetId)) {
|
if (StringUtils.isBlank(datasetId)) {
|
||||||
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
||||||
}
|
}
|
||||||
|
// [Production Fix] 兼容性查找:优先通过 dataset_id 找,找不到通过主键 id 找,确保前端传哪种 UUID 都能命中
|
||||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectOne(
|
KnowledgeBaseEntity entity = knowledgeBaseDao
|
||||||
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
|
.selectOne(new QueryWrapper<KnowledgeBaseEntity>()
|
||||||
|
.eq("dataset_id", datasetId)
|
||||||
|
.or()
|
||||||
|
.eq("id", datasetId));
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
|
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public KnowledgeBaseDTO save(KnowledgeBaseDTO dto) {
|
||||||
|
// 1. Validation
|
||||||
|
checkDuplicateName(dto.getName(), null);
|
||||||
|
KnowledgeBaseAdapter adapter = null;
|
||||||
|
|
||||||
|
// 2. RAG Creation
|
||||||
|
String datasetId = null;
|
||||||
|
try {
|
||||||
|
// 若未指定 RAG 模型,自动使用系统默认
|
||||||
|
if (StringUtils.isBlank(dto.getRagModelId())) {
|
||||||
|
List<ModelConfigEntity> models = getRAGModels();
|
||||||
|
if (models != null && !models.isEmpty()) {
|
||||||
|
dto.setRagModelId(models.get(0).getId());
|
||||||
|
} else {
|
||||||
|
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND, "未指定且无可用默认 RAG 模型");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> ragConfig = getValidatedRAGConfig(dto.getRagModelId());
|
||||||
|
adapter = KnowledgeBaseAdapterFactory.getAdapter((String) ragConfig.get("type"),
|
||||||
|
ragConfig);
|
||||||
|
|
||||||
|
DatasetDTO.CreateReq createReq = ConvertUtils.sourceToTarget(dto, DatasetDTO.CreateReq.class);
|
||||||
|
createReq.setName(SecurityUser.getUser().getUsername() + "_" + dto.getName());
|
||||||
|
|
||||||
|
DatasetDTO.InfoVO ragResponse = adapter.createDataset(createReq);
|
||||||
|
if (ragResponse == null || StringUtils.isBlank(ragResponse.getId())) {
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG创建返回无效: 缺失ID");
|
||||||
|
}
|
||||||
|
datasetId = ragResponse.getId();
|
||||||
|
|
||||||
|
// 3. Local Save (Shadow)
|
||||||
|
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(dto, KnowledgeBaseEntity.class);
|
||||||
|
|
||||||
|
// [Production Fix] 统一本地 ID 与 RAGFlow ID,防止前端调用 /delete 或 /update 时因 ID 混淆(本地
|
||||||
|
// UUID vs RAG UUID)导致 10163 错误
|
||||||
|
entity.setId(datasetId);
|
||||||
|
entity.setDatasetId(datasetId);
|
||||||
|
entity.setStatus(1); // Default Enabled
|
||||||
|
|
||||||
|
// ✅ FULL PERSISTENCE: 严格全量回写 (User Requirement)
|
||||||
|
// 使用强类型 DTO 属性获取,不再从 Map 中手动解析 Key
|
||||||
|
entity.setTenantId(ragResponse.getTenantId());
|
||||||
|
entity.setChunkMethod(ragResponse.getChunkMethod());
|
||||||
|
entity.setEmbeddingModel(ragResponse.getEmbeddingModel());
|
||||||
|
entity.setPermission(ragResponse.getPermission());
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(entity.getAvatar())) {
|
||||||
|
entity.setAvatar(ragResponse.getAvatar());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Config (JSON)
|
||||||
|
if (ragResponse.getParserConfig() != null) {
|
||||||
|
entity.setParserConfig(JsonUtils.toJsonString(ragResponse.getParserConfig()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric fields
|
||||||
|
entity.setChunkCount(ragResponse.getChunkCount() != null ? ragResponse.getChunkCount() : 0L);
|
||||||
|
entity.setDocumentCount(ragResponse.getDocumentCount() != null ? ragResponse.getDocumentCount() : 0L);
|
||||||
|
entity.setTokenNum(ragResponse.getTokenNum() != null ? ragResponse.getTokenNum() : 0L);
|
||||||
|
|
||||||
|
// 清空 creator/updater,让 FieldMetaObjectHandler 从 SecurityUser 自动填充
|
||||||
|
// ConvertUtils 会把 DTO 中的 creator=0 拷贝过来,导致 strictInsertFill 跳过填充
|
||||||
|
entity.setCreator(null);
|
||||||
|
entity.setUpdater(null);
|
||||||
|
|
||||||
|
knowledgeBaseDao.insert(entity);
|
||||||
|
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("RAG创建或本地保存失败", e);
|
||||||
|
// 如果datasetId已生成但在保存本地时失败,尝试回滚RAG (Best Effort)
|
||||||
|
if (StringUtils.isNotBlank(datasetId)) {
|
||||||
|
try {
|
||||||
|
if (adapter != null)
|
||||||
|
adapter.deleteDataset(
|
||||||
|
DatasetDTO.BatchIdReq.builder().ids(Collections.singletonList(datasetId)).build());
|
||||||
|
} catch (Exception rollbackEx) {
|
||||||
|
log.error("RAG回滚失败: {}", datasetId, rollbackEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e instanceof RenException) {
|
||||||
|
throw (RenException) e;
|
||||||
|
}
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "创建知识库失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public KnowledgeBaseDTO update(KnowledgeBaseDTO dto) {
|
||||||
|
log.info("Update Service Called: ID={}, DatasetID={}", dto.getId(), dto.getDatasetId());
|
||||||
|
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(dto.getId());
|
||||||
|
if (entity == null) {
|
||||||
|
log.error("Update failed: Entity not found for ID={}", dto.getId());
|
||||||
|
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDuplicateName(dto.getName(), dto.getId());
|
||||||
|
|
||||||
|
// 验证数据集ID是否与其他记录冲突
|
||||||
|
if (StringUtils.isNotBlank(dto.getDatasetId())) {
|
||||||
|
KnowledgeBaseEntity conflictEntity = knowledgeBaseDao.selectOne(
|
||||||
|
new QueryWrapper<KnowledgeBaseEntity>()
|
||||||
|
.eq("dataset_id", dto.getDatasetId())
|
||||||
|
.ne("id", dto.getId()));
|
||||||
|
if (conflictEntity != null) {
|
||||||
|
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAG Update if needed
|
||||||
|
if (StringUtils.isNotBlank(entity.getDatasetId()) && StringUtils.isNotBlank(dto.getRagModelId())) {
|
||||||
|
try {
|
||||||
|
// 🤖 AUTO-FILL: 若 DTO 未传 ragModelId (极少情况),尝试复用 Entity 中的
|
||||||
|
if (StringUtils.isBlank(dto.getRagModelId())) {
|
||||||
|
dto.setRagModelId(entity.getRagModelId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// [FIX] 智能补全:如果 DTO 里的关键字段为空,则使用 Entity 里的旧值
|
||||||
|
// 确保发给 RAGFlow 的请求包含所有必填项 (Partial Update Support)
|
||||||
|
if (StringUtils.isBlank(dto.getPermission())) {
|
||||||
|
dto.setPermission(entity.getPermission());
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(dto.getChunkMethod())) {
|
||||||
|
dto.setChunkMethod(entity.getChunkMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
KnowledgeBaseAdapter adapter = getAdapterByModelId(dto.getRagModelId());
|
||||||
|
if (adapter != null) {
|
||||||
|
DatasetDTO.UpdateReq updateReq = ConvertUtils.sourceToTarget(dto, DatasetDTO.UpdateReq.class);
|
||||||
|
|
||||||
|
// 1. 必填/核心字段前缀处理
|
||||||
|
if (StringUtils.isNotBlank(dto.getName())) {
|
||||||
|
updateReq.setName(SecurityUser.getUser().getUsername() + "_" + dto.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解析器配置支持 (如果 DTO 里有字符串形式的配置,尝试转换,但优先建议 DTO 化)
|
||||||
|
if (StringUtils.isNotBlank(dto.getParserConfig())) {
|
||||||
|
try {
|
||||||
|
DatasetDTO.ParserConfig parserConfig = JsonUtils.parseObject(dto.getParserConfig(),
|
||||||
|
DatasetDTO.ParserConfig.class);
|
||||||
|
updateReq.setParserConfig(parserConfig);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析 parser_config 失败,跳过同步", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.updateDataset(entity.getDatasetId(), updateReq);
|
||||||
|
log.info("RAG更新成功: {}", entity.getDatasetId());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("RAG更新失败", e);
|
||||||
|
// 恢复事务一致性:RAG失败则整体回滚
|
||||||
|
if (e instanceof RenException) {
|
||||||
|
throw (RenException) e;
|
||||||
|
}
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG更新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanUtils.copyProperties(dto, entity);
|
||||||
|
knowledgeBaseDao.updateById(entity);
|
||||||
|
|
||||||
|
// Clean cache
|
||||||
|
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void deleteByDatasetId(String datasetId) {
|
public void deleteByDatasetId(String datasetId) {
|
||||||
if (StringUtils.isBlank(datasetId)) {
|
if (StringUtils.isBlank(datasetId)) {
|
||||||
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("=== 开始通过datasetId删除操作 ===");
|
KnowledgeBaseEntity entity = knowledgeBaseDao
|
||||||
log.info("删除datasetId: {}", datasetId);
|
.selectOne(new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
|
||||||
|
|
||||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectOne(
|
|
||||||
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
|
|
||||||
|
|
||||||
|
// 1. 恢复 404 校验:找不到记录抛异常
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
log.warn("记录不存在,datasetId: {}", datasetId);
|
log.warn("记录不存在,datasetId: {}", datasetId);
|
||||||
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
|
|
||||||
|
|
||||||
log.info("找到记录: ID={}, datasetId={}, ragModelId={}",
|
log.info("找到记录: ID={}, datasetId={}, ragModelId={}",
|
||||||
entity.getId(), entity.getDatasetId(), entity.getRagModelId());
|
entity.getId(), entity.getDatasetId(), entity.getRagModelId());
|
||||||
|
|
||||||
// 先调用RAG API删除数据集
|
// 2. RAG Delete (Strict Mode)
|
||||||
|
// 恢复严格一致性:RAG 删除失败则抛出异常,触发事务回滚,不允许已删除本地但保留远程的脏数据
|
||||||
boolean apiDeleteSuccess = false;
|
boolean apiDeleteSuccess = false;
|
||||||
if (StringUtils.isNotBlank(entity.getDatasetId()) && StringUtils.isNotBlank(entity.getRagModelId())) {
|
if (StringUtils.isNotBlank(entity.getRagModelId()) && StringUtils.isNotBlank(entity.getDatasetId())) {
|
||||||
try {
|
try {
|
||||||
log.info("开始调用RAG API删除数据集");
|
KnowledgeBaseAdapter adapter = getAdapterByModelId(entity.getRagModelId());
|
||||||
// 在删除前进行RAG配置校验
|
if (adapter != null) {
|
||||||
Map<String, Object> ragConfig = getValidatedRAGConfig(entity.getRagModelId());
|
adapter.deleteDataset(
|
||||||
deleteDatasetInRAG(entity.getDatasetId(), ragConfig);
|
DatasetDTO.BatchIdReq.builder().ids(Collections.singletonList(datasetId)).build());
|
||||||
log.info("RAG API删除调用完成");
|
}
|
||||||
apiDeleteSuccess = true;
|
apiDeleteSuccess = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 提供更详细的错误信息,包括异常类型和消息
|
log.error("RAG删除失败,触发回滚", e);
|
||||||
String errorMessage = "删除RAG数据集失败: " + e.getClass().getSimpleName();
|
if (e instanceof RenException) {
|
||||||
if (e.getMessage() != null) {
|
throw (RenException) e;
|
||||||
errorMessage += " - " + e.getMessage();
|
|
||||||
}
|
}
|
||||||
log.error(errorMessage, e);
|
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG删除失败: " + e.getMessage());
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("datasetId或ragModelId为空,跳过RAG删除");
|
log.warn("datasetId或ragModelId为空,跳过RAG删除");
|
||||||
apiDeleteSuccess = true; // 没有RAG数据集,视为成功
|
apiDeleteSuccess = true; // 没有RAG数据集,视为成功
|
||||||
}
|
}
|
||||||
|
|
||||||
// API删除成功后再删除本地记录
|
// 3. Local Delete (Safe Order)
|
||||||
|
// 恢复正确顺序:先删子表 (Plugin Mapping),再删主表 (Entity)
|
||||||
if (apiDeleteSuccess) {
|
if (apiDeleteSuccess) {
|
||||||
log.info("开始删除ai_agent_plugin_mapping表中与知识库ID '{}' 相关的映射记录", entity.getId());
|
log.info("开始删除ai_agent_plugin_mapping表中与知识库ID '{}' 相关的映射记录", entity.getId());
|
||||||
|
log.info("开始删除关联数据, entityId: {}", entity.getId());
|
||||||
// 先删除相关的插件映射记录
|
|
||||||
knowledgeBaseDao.deletePluginMappingByKnowledgeBaseId(entity.getId());
|
knowledgeBaseDao.deletePluginMappingByKnowledgeBaseId(entity.getId());
|
||||||
log.info("插件映射记录删除完成");
|
log.info("插件映射记录删除完成");
|
||||||
|
|
||||||
int deleteCount = knowledgeBaseDao.deleteById(entity.getId());
|
int deleteCount = knowledgeBaseDao.deleteById(entity.getId());
|
||||||
log.info("本地数据库删除结果: {}", deleteCount > 0 ? "成功" : "失败");
|
log.info("本地数据库删除结果: {}", deleteCount > 0 ? "成功" : "失败");
|
||||||
|
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.info("=== 通过datasetId删除操作结束 ===");
|
@Override
|
||||||
|
public List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList) {
|
||||||
|
if (datasetIdList == null || datasetIdList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
// [Production Fix] 批量兼容性查找
|
||||||
|
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.in("dataset_id", datasetIdList).or().in("id", datasetIdList);
|
||||||
|
List<KnowledgeBaseEntity> list = knowledgeBaseDao.selectList(queryWrapper);
|
||||||
|
return ConvertUtils.sourceToTarget(list, KnowledgeBaseDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getRAGConfig(String ragModelId) {
|
public Map<String, Object> getRAGConfig(String ragModelId) {
|
||||||
if (StringUtils.isBlank(ragModelId)) {
|
return getValidatedRAGConfig(ragModelId);
|
||||||
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从缓存获取模型配置
|
|
||||||
ModelConfigEntity modelConfig = modelConfigService.getModelByIdFromCache(ragModelId);
|
|
||||||
if (modelConfig == null || modelConfig.getConfigJson() == null) {
|
|
||||||
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证是否为RAG类型配置
|
|
||||||
if (!Constant.RAG_CONFIG_TYPE.equals(modelConfig.getModelType().toUpperCase())) {
|
|
||||||
throw new RenException(ErrorCode.RAG_CONFIG_TYPE_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> config = modelConfig.getConfigJson();
|
|
||||||
|
|
||||||
// 验证必要的配置参数
|
|
||||||
validateRagConfig(config);
|
|
||||||
|
|
||||||
// 返回配置信息
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getRAGConfigByDatasetId(String datasetId) {
|
public Map<String, Object> getRAGConfigByDatasetId(String datasetId) {
|
||||||
if (StringUtils.isBlank(datasetId)) {
|
KnowledgeBaseEntity entity = knowledgeBaseDao
|
||||||
throw new RenException(ErrorCode.RAG_DATASET_ID_NOT_NULL);
|
.selectOne(new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
|
||||||
}
|
if (entity == null || StringUtils.isBlank(entity.getRagModelId())) {
|
||||||
|
|
||||||
// 根据datasetId查询知识库信息
|
|
||||||
KnowledgeBaseDTO knowledgeBase = getByDatasetId(datasetId);
|
|
||||||
if (knowledgeBase == null) {
|
|
||||||
log.warn("未找到datasetId为{}的知识库", datasetId);
|
|
||||||
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果知识库指定了ragModelId,使用该配置
|
|
||||||
String ragModelId = knowledgeBase.getRagModelId();
|
|
||||||
if (StringUtils.isBlank(ragModelId)) {
|
|
||||||
log.warn("知识库datasetId为{}未配置ragModelId", datasetId);
|
|
||||||
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
return getRAGConfig(entity.getRagModelId());
|
||||||
// 获取并返回RAG配置
|
|
||||||
return getRAGConfig(ragModelId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getRAGModels() {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
// 查询RAG类型的模型配置
|
public void updateStatistics(String datasetId, Integer docDelta, Long chunkDelta, Long tokenDelta) {
|
||||||
QueryWrapper<ModelConfigEntity> queryWrapper = new QueryWrapper<>();
|
log.info("递增更新知识库统计: datasetId={}, docs={}, chunks={}, tokens={}", datasetId, docDelta, chunkDelta, tokenDelta);
|
||||||
queryWrapper.eq("model_type", Constant.RAG_CONFIG_TYPE)
|
knowledgeBaseDao.updateStatsAfterChange(datasetId, docDelta, chunkDelta, tokenDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ModelConfigEntity> getRAGModels() {
|
||||||
|
return modelConfigDao.selectList(new QueryWrapper<ModelConfigEntity>()
|
||||||
|
.select("id", "model_name", "config_json") // Explicitly select needed fields
|
||||||
|
.eq("model_type", Constant.RAG_CONFIG_TYPE)
|
||||||
.eq("is_enabled", 1)
|
.eq("is_enabled", 1)
|
||||||
.orderByDesc("is_default")
|
.orderByDesc("is_default")
|
||||||
.orderByDesc("create_date");
|
.orderByDesc("create_date"));
|
||||||
|
|
||||||
List<ModelConfigEntity> modelConfigs = modelConfigDao.selectList(queryWrapper);
|
|
||||||
|
|
||||||
List<Map<String, Object>> modelList = new ArrayList<>();
|
|
||||||
for (ModelConfigEntity modelConfig : modelConfigs) {
|
|
||||||
Map<String, Object> modelInfo = new HashMap<>();
|
|
||||||
modelInfo.put("id", modelConfig.getId());
|
|
||||||
modelInfo.put("modelName", modelConfig.getModelName());
|
|
||||||
modelList.add(modelInfo);
|
|
||||||
}
|
|
||||||
return modelList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// --- Helpers ---
|
||||||
* 验证RAG配置中是否包含必要的参数
|
|
||||||
*/
|
private void checkDuplicateName(String name, String excludeId) {
|
||||||
private void validateRagConfig(Map<String, Object> config) {
|
if (StringUtils.isBlank(name))
|
||||||
if (config == null) {
|
return;
|
||||||
|
QueryWrapper<KnowledgeBaseEntity> qw = new QueryWrapper<>();
|
||||||
|
qw.eq("name", name).eq("creator", SecurityUser.getUserId());
|
||||||
|
if (excludeId != null)
|
||||||
|
qw.ne("id", excludeId);
|
||||||
|
if (knowledgeBaseDao.selectCount(qw) > 0) {
|
||||||
|
throw new RenException(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KnowledgeBaseAdapter getAdapterByModelId(String modelId) {
|
||||||
|
Map<String, Object> config = getValidatedRAGConfig(modelId);
|
||||||
|
return KnowledgeBaseAdapterFactory.getAdapter((String) config.get("type"), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getValidatedRAGConfig(String modelId) {
|
||||||
|
ModelConfigEntity configEntity = modelConfigService.getModelByIdFromCache(modelId);
|
||||||
|
if (configEntity == null || configEntity.getConfigJson() == null) {
|
||||||
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
Map<String, Object> config = new HashMap<>(configEntity.getConfigJson());
|
||||||
// 从配置中提取必要的参数
|
if (!config.containsKey("type")) {
|
||||||
String baseUrl = (String) config.get("base_url");
|
config.put("type", "ragflow");
|
||||||
String apiKey = (String) config.get("api_key");
|
|
||||||
|
|
||||||
// 验证base_url是否存在且非空
|
|
||||||
if (StringUtils.isBlank(baseUrl)) {
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR_URL_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证api_key是否存在且非空
|
|
||||||
if (StringUtils.isBlank(apiKey)) {
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查api_key是否包含占位符
|
|
||||||
if (apiKey.contains("你")) {
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_INVALID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证base_url格式
|
|
||||||
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR_URL_INVALID);
|
|
||||||
}
|
}
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从RAG配置中提取适配器类型
|
|
||||||
*
|
|
||||||
* @param config RAG配置
|
|
||||||
* @return 适配器类型
|
|
||||||
*/
|
|
||||||
private String extractAdapterType(Map<String, Object> config) {
|
|
||||||
if (config == null) {
|
|
||||||
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从配置中提取适配器类型
|
|
||||||
String adapterType = (String) config.get("type");
|
|
||||||
|
|
||||||
// 验证适配器类型是否存在且非空
|
|
||||||
if (StringUtils.isBlank(adapterType)) {
|
|
||||||
throw new RenException(ErrorCode.RAG_ADAPTER_TYPE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证适配器类型是否已注册
|
|
||||||
if (!KnowledgeBaseAdapterFactory.isAdapterTypeRegistered(adapterType)) {
|
|
||||||
throw new RenException(ErrorCode.RAG_ADAPTER_TYPE_NOT_SUPPORTED,
|
|
||||||
"不支持的适配器类型: " + adapterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return adapterType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用适配器创建数据集
|
|
||||||
*/
|
|
||||||
private String createDatasetInRAG(String name, String description, Map<String, Object> ragConfig) {
|
|
||||||
log.info("开始使用适配器创建数据集, name: {}", name);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 从RAG配置中提取适配器类型
|
|
||||||
String adapterType = extractAdapterType(ragConfig);
|
|
||||||
|
|
||||||
// 使用适配器工厂获取适配器实例
|
|
||||||
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
|
|
||||||
|
|
||||||
// 构建数据集创建参数
|
|
||||||
Map<String, Object> createParams = new HashMap<>();
|
|
||||||
String username = SecurityUser.getUser().getUsername();
|
|
||||||
createParams.put("name", username + "_" + name);
|
|
||||||
if (StringUtils.isNotBlank(description)) {
|
|
||||||
createParams.put("description", description);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用适配器的创建数据集方法
|
|
||||||
String datasetId = adapter.createDataset(createParams);
|
|
||||||
|
|
||||||
log.info("数据集创建成功,datasetId: {}", datasetId);
|
|
||||||
return datasetId;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 直接传递底层适配器的详细错误信息
|
|
||||||
log.error("创建数据集失败", e);
|
|
||||||
if (e instanceof RenException) {
|
|
||||||
throw (RenException) e;
|
|
||||||
}
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用适配器更新数据集
|
|
||||||
*/
|
|
||||||
private void updateDatasetInRAG(String datasetId, String name, String description,
|
|
||||||
Map<String, Object> ragConfig) {
|
|
||||||
log.info("开始使用适配器更新数据集,datasetId: {}, name: {}", datasetId, name);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 从RAG配置中提取适配器类型
|
|
||||||
String adapterType = extractAdapterType(ragConfig);
|
|
||||||
|
|
||||||
// 使用适配器工厂获取适配器实例
|
|
||||||
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
|
|
||||||
|
|
||||||
// 构建数据集更新参数
|
|
||||||
Map<String, Object> updateParams = new HashMap<>();
|
|
||||||
String username = SecurityUser.getUser().getUsername();
|
|
||||||
updateParams.put("name", username + "_" + name);
|
|
||||||
if (StringUtils.isNotBlank(description)) {
|
|
||||||
updateParams.put("description", description);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用适配器的更新数据集方法
|
|
||||||
adapter.updateDataset(datasetId, updateParams);
|
|
||||||
|
|
||||||
log.info("数据集更新成功,datasetId: {}", datasetId);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 直接传递底层适配器的详细错误信息
|
|
||||||
log.error("更新数据集失败", e);
|
|
||||||
if (e instanceof RenException) {
|
|
||||||
throw (RenException) e;
|
|
||||||
}
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用适配器删除数据集
|
|
||||||
*/
|
|
||||||
private void deleteDatasetInRAG(String datasetId, Map<String, Object> ragConfig) {
|
|
||||||
log.info("开始使用适配器删除数据集,datasetId: {}", datasetId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 从RAG配置中提取适配器类型
|
|
||||||
String adapterType = extractAdapterType(ragConfig);
|
|
||||||
|
|
||||||
// 使用适配器工厂获取适配器实例
|
|
||||||
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
|
|
||||||
|
|
||||||
// 调用适配器的删除数据集方法
|
|
||||||
adapter.deleteDataset(datasetId);
|
|
||||||
|
|
||||||
log.info("数据集删除成功,datasetId: {}", datasetId);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 直接传递底层适配器的详细错误信息
|
|
||||||
log.error("删除数据集失败", e);
|
|
||||||
if (e instanceof RenException) {
|
|
||||||
throw (RenException) e;
|
|
||||||
}
|
|
||||||
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取RAG配置并验证
|
|
||||||
*/
|
|
||||||
private Map<String, Object> getValidatedRAGConfig(String ragModelId) {
|
|
||||||
if (StringUtils.isBlank(ragModelId)) {
|
|
||||||
throw new RenException(ErrorCode.RAG_MODEL_ID_NOT_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> ragConfig = getRAGConfig(ragModelId);
|
|
||||||
|
|
||||||
// 验证RAG配置参数
|
|
||||||
validateRagConfig(ragConfig);
|
|
||||||
|
|
||||||
return ragConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否存在同名知识库
|
|
||||||
*
|
|
||||||
* @param knowledgeBaseDTO 知识库DTO
|
|
||||||
* @param excludeId 排除的ID(更新时使用)
|
|
||||||
*/
|
|
||||||
private void checkDuplicateKnowledgeBaseName(KnowledgeBaseDTO knowledgeBaseDTO, String excludeId) {
|
|
||||||
if (StringUtils.isNotBlank(knowledgeBaseDTO.getName())) {
|
|
||||||
Long currentUserId = SecurityUser.getUserId();
|
|
||||||
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<KnowledgeBaseEntity>()
|
|
||||||
.eq("name", knowledgeBaseDTO.getName())
|
|
||||||
.eq("creator", currentUserId);
|
|
||||||
|
|
||||||
// 如果提供了排除ID,则排除该记录
|
|
||||||
if (StringUtils.isNotBlank(excludeId)) {
|
|
||||||
queryWrapper.ne("id", excludeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
long count = knowledgeBaseDao.selectCount(queryWrapper);
|
|
||||||
if (count > 0) {
|
|
||||||
throw new RenException(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS,
|
|
||||||
MessageUtils.getMessage(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从适配器获取知识库的文档数量
|
|
||||||
*/
|
|
||||||
private Integer getDocumentCountFromRAG(String datasetId, String ragModelId) {
|
|
||||||
if (StringUtils.isBlank(datasetId) || StringUtils.isBlank(ragModelId)) {
|
|
||||||
log.warn("datasetId或ragModelId为空,无法获取文档数量");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("开始获取知识库 {} 的文档数量", datasetId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取RAG配置
|
|
||||||
Map<String, Object> ragConfig = getValidatedRAGConfig(ragModelId);
|
|
||||||
|
|
||||||
// 从RAG配置中提取适配器类型
|
|
||||||
String adapterType = extractAdapterType(ragConfig);
|
|
||||||
|
|
||||||
// 使用适配器工厂获取适配器实例
|
|
||||||
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
|
|
||||||
|
|
||||||
// 调用适配器的获取文档数量方法
|
|
||||||
Integer documentCount = adapter.getDocumentCount(datasetId);
|
|
||||||
|
|
||||||
log.info("获取知识库 {} 的文档数量成功: {}", datasetId, documentCount);
|
|
||||||
return documentCount;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 构建详细的错误信息,包含异常类型和消息
|
|
||||||
String baseErrorMessage = e.getClass().getSimpleName() + " - 获取知识库文档数量失败";
|
|
||||||
String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
|
|
||||||
log.error(errorMessage, e);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+517
-1089
File diff suppressed because it is too large
Load Diff
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package xiaozhi.modules.knowledge.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
||||||
|
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
||||||
|
import xiaozhi.modules.knowledge.service.KnowledgeManagerService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class KnowledgeManagerServiceImpl implements KnowledgeManagerService {
|
||||||
|
|
||||||
|
private final KnowledgeBaseService knowledgeBaseService;
|
||||||
|
private final KnowledgeFilesService knowledgeFilesService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteDatasetWithFiles(String datasetId) {
|
||||||
|
log.info("=== 级联删除开始: datasetId={} ===", datasetId);
|
||||||
|
|
||||||
|
// 1. 先调用文件服务,清理该数据集下的所有文档记录 (含 RAGFlow 端)
|
||||||
|
log.info("Step 1: 清理关联文档...");
|
||||||
|
knowledgeFilesService.deleteDocumentsByDatasetId(datasetId);
|
||||||
|
|
||||||
|
// 2. 再调用知识库服务,彻底注销数据集 (含 RAGFlow 端)
|
||||||
|
log.info("Step 2: 删除数据集主体...");
|
||||||
|
knowledgeBaseService.deleteByDatasetId(datasetId);
|
||||||
|
|
||||||
|
log.info("=== 级联删除成功: datasetId={} ===", datasetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void batchDeleteDatasetsWithFiles(List<String> datasetIds) {
|
||||||
|
if (datasetIds == null || datasetIds.isEmpty())
|
||||||
|
return;
|
||||||
|
log.info("=== 批量级联删除开始: count={} ===", datasetIds.size());
|
||||||
|
for (String id : datasetIds) {
|
||||||
|
deleteDatasetWithFiles(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package xiaozhi.modules.knowledge.task;
|
||||||
|
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库文档状态同步定时任务
|
||||||
|
*
|
||||||
|
* 作用:
|
||||||
|
* 1. 自动扫描处于 "RUNNING" (解析中) 状态的文档
|
||||||
|
* 2. 调用 RAGFlow 接口获取最新状态
|
||||||
|
* 3. 状态翻转 (RUNNING -> SUCCESS/FAIL) 时,同步更新数据库
|
||||||
|
* 4. [关键] 解析成功时,补偿更新知识库的统计信息 (TokenCount)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class DocumentStatusSyncTask {
|
||||||
|
|
||||||
|
private final KnowledgeFilesService knowledgeFilesService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每 30 秒执行一次同步
|
||||||
|
* 采用 fixedDelay,确保上一次执行完 30 秒后才开始下一次,防止积压
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedDelay = 30000)
|
||||||
|
public void syncRunningDocuments() {
|
||||||
|
try {
|
||||||
|
// log.debug("开始执行文档状态同步任务...");
|
||||||
|
knowledgeFilesService.syncRunningDocuments();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("文档状态同步任务异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,6 +129,10 @@ public class ModelController {
|
|||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return new Result<Void>().error("模型配置不存在");
|
return new Result<Void>().error("模型配置不存在");
|
||||||
}
|
}
|
||||||
|
// 不能关闭默认模型
|
||||||
|
if (status == 0 && entity.getIsDefault() > 0) {
|
||||||
|
return new Result<Void>().error("默认模型配置不允许关闭");
|
||||||
|
}
|
||||||
// 不更新ConfigJson字段
|
// 不更新ConfigJson字段
|
||||||
entity.setConfigJson(null);
|
entity.setConfigJson(null);
|
||||||
entity.setIsEnabled(status);
|
entity.setIsEnabled(status);
|
||||||
|
|||||||
@@ -23,11 +23,28 @@ public class VoiceDTO implements Serializable {
|
|||||||
@Schema(description = "音频播放地址")
|
@Schema(description = "音频播放地址")
|
||||||
private String voiceDemo;
|
private String voiceDemo;
|
||||||
|
|
||||||
|
@Schema(description = "语言类型")
|
||||||
|
private String languages;
|
||||||
|
|
||||||
|
@Schema(description = "是否为克隆音色")
|
||||||
|
private Boolean isClone;
|
||||||
|
|
||||||
// 添加双参数构造函数,保持向后兼容
|
// 添加双参数构造函数,保持向后兼容
|
||||||
public VoiceDTO(String id, String name) {
|
public VoiceDTO(String id, String name) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.voiceDemo = null;
|
this.voiceDemo = null;
|
||||||
|
this.languages = null;
|
||||||
|
this.isClone = false; // 默认不是克隆音色
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加三参数构造函数,用于普通音色
|
||||||
|
public VoiceDTO(String id, String name, String voiceDemo) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.voiceDemo = voiceDemo;
|
||||||
|
this.languages = null;
|
||||||
|
this.isClone = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+7
-7
@@ -56,7 +56,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
new QueryWrapper<ModelConfigEntity>()
|
new QueryWrapper<ModelConfigEntity>()
|
||||||
.eq("model_type", modelType)
|
.eq("model_type", modelType)
|
||||||
.eq("is_enabled", 1)
|
.eq("is_enabled", 1)
|
||||||
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")
|
.like(StringUtils.isNotBlank(modelName), "model_name", modelName)
|
||||||
.select("id", "model_name"));
|
.select("id", "model_name"));
|
||||||
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
||||||
}
|
}
|
||||||
@@ -67,14 +67,14 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
new QueryWrapper<ModelConfigEntity>()
|
new QueryWrapper<ModelConfigEntity>()
|
||||||
.eq("model_type", "llm")
|
.eq("model_type", "llm")
|
||||||
.eq("is_enabled", 1)
|
.eq("is_enabled", 1)
|
||||||
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")
|
.like(StringUtils.isNotBlank(modelName), "model_name", modelName)
|
||||||
.select("id", "model_name", "config_json"));
|
.select("id", "model_name", "config_json"));
|
||||||
|
|
||||||
return entities.stream().map(item -> {
|
return entities.stream().map(item -> {
|
||||||
LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO();
|
LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO();
|
||||||
dto.setId(item.getId());
|
dto.setId(item.getId());
|
||||||
dto.setModelName(item.getModelName());
|
dto.setModelName(item.getModelName());
|
||||||
String type = item.getConfigJson().get("type").toString();
|
String type = item.getConfigJson().getOrDefault("type", "").toString();
|
||||||
dto.setType(type);
|
dto.setType(type);
|
||||||
return dto;
|
return dto;
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -91,14 +91,13 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
Page<ModelConfigEntity> pageInfo = new Page<>(curPage, pageSize);
|
Page<ModelConfigEntity> pageInfo = new Page<>(curPage, pageSize);
|
||||||
|
|
||||||
// 添加排序规则:先按is_enabled降序,再按sort升序
|
// 添加排序规则:先按is_enabled降序,再按sort升序
|
||||||
pageInfo.addOrder(OrderItem.desc("is_enabled"));
|
pageInfo.addOrder(OrderItem.desc("is_enabled"), OrderItem.asc("sort"));
|
||||||
pageInfo.addOrder(OrderItem.asc("sort"));
|
|
||||||
|
|
||||||
IPage<ModelConfigEntity> modelConfigEntityIPage = modelConfigDao.selectPage(
|
IPage<ModelConfigEntity> modelConfigEntityIPage = modelConfigDao.selectPage(
|
||||||
pageInfo,
|
pageInfo,
|
||||||
new QueryWrapper<ModelConfigEntity>()
|
new QueryWrapper<ModelConfigEntity>()
|
||||||
.eq("model_type", modelType)
|
.eq("model_type", modelType)
|
||||||
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%"));
|
.like(StringUtils.isNotBlank(modelName), "model_name", modelName));
|
||||||
|
|
||||||
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
|
return getPageData(modelConfigEntityIPage, ModelConfigDTO.class);
|
||||||
}
|
}
|
||||||
@@ -346,6 +345,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
modelConfigEntity.setModelName(modelConfigBodyDTO.getModelName());
|
modelConfigEntity.setModelName(modelConfigBodyDTO.getModelName());
|
||||||
modelConfigEntity.setSort(modelConfigBodyDTO.getSort());
|
modelConfigEntity.setSort(modelConfigBodyDTO.getSort());
|
||||||
modelConfigEntity.setIsEnabled(modelConfigBodyDTO.getIsEnabled());
|
modelConfigEntity.setIsEnabled(modelConfigBodyDTO.getIsEnabled());
|
||||||
|
modelConfigEntity.setRemark(modelConfigBodyDTO.getRemark());
|
||||||
// 3. 处理配置JSON,仅更新非敏感字段和明确修改的敏感字段
|
// 3. 处理配置JSON,仅更新非敏感字段和明确修改的敏感字段
|
||||||
if (modelConfigBodyDTO.getConfigJson() != null && originalEntity.getConfigJson() != null) {
|
if (modelConfigBodyDTO.getConfigJson() != null && originalEntity.getConfigJson() != null) {
|
||||||
JSONObject originalJson = originalEntity.getConfigJson();
|
JSONObject originalJson = originalEntity.getConfigJson();
|
||||||
@@ -488,7 +488,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
List<ModelConfigEntity> intentConfigs = modelConfigDao.selectList(
|
List<ModelConfigEntity> intentConfigs = modelConfigDao.selectList(
|
||||||
new QueryWrapper<ModelConfigEntity>()
|
new QueryWrapper<ModelConfigEntity>()
|
||||||
.eq("model_type", "Intent")
|
.eq("model_type", "Intent")
|
||||||
.like("config_json", "%" + modelId + "%"));
|
.like("config_json", modelId));
|
||||||
if (!intentConfigs.isEmpty()) {
|
if (!intentConfigs.isEmpty()) {
|
||||||
throw new RenException(ErrorCode.LLM_REFERENCED_BY_INTENT);
|
throw new RenException(ErrorCode.LLM_REFERENCED_BY_INTENT);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-8
@@ -124,12 +124,6 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class);
|
return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
String jsonString = "\"[]\"";
|
|
||||||
JSONArray jsonArray = new JSONArray(jsonString);
|
|
||||||
System.out.println("字符串转 JSONArray: " + jsonArray.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) {
|
public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
@@ -153,8 +147,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
modelProviderDTO.setUpdater(user.getId());
|
modelProviderDTO.setUpdater(user.getId());
|
||||||
modelProviderDTO.setUpdateDate(new Date());
|
modelProviderDTO.setUpdateDate(new Date());
|
||||||
if (modelProviderDao
|
if (modelProviderDao.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||||
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
|
||||||
throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
|
throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
|
||||||
}
|
}
|
||||||
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import xiaozhi.common.dao.BaseDao;
|
import xiaozhi.common.dao.BaseDao;
|
||||||
import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
import xiaozhi.modules.sys.entity.SysDictDataEntity;
|
||||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||||
@@ -23,4 +24,9 @@ public interface SysDictDataDao extends BaseDao<SysDictDataEntity> {
|
|||||||
* @return 字典类型编码
|
* @return 字典类型编码
|
||||||
*/
|
*/
|
||||||
String getTypeByTypeId(Long dictTypeId);
|
String getTypeByTypeId(Long dictTypeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典数据ID集合获取字典类型编码集合
|
||||||
|
*/
|
||||||
|
List<String> getDictTypesByIdList(@Param("dictDataIdList") List<Long> dictDataIdList);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-11
@@ -1,9 +1,6 @@
|
|||||||
package xiaozhi.modules.sys.service.impl;
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -23,6 +20,7 @@ import xiaozhi.common.redis.RedisKeys;
|
|||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.ToolUtil;
|
||||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||||
@@ -104,13 +102,19 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void delete(Long[] ids) {
|
public void delete(Long[] ids) {
|
||||||
for (Long id : ids) {
|
List<Long> idList = Arrays.asList(ids);
|
||||||
SysDictDataEntity entity = baseDao.selectById(id);
|
if (ToolUtil.isNotEmpty(idList)) {
|
||||||
// 删除Redis缓存
|
//批量删除redis字典
|
||||||
String dictType = baseDao.getTypeByTypeId(entity.getDictTypeId());
|
List<String> redisKeyList = new ArrayList<>();
|
||||||
redisUtils.delete(RedisKeys.getDictDataByTypeKey(dictType));
|
//批量获取字典类型
|
||||||
// 删除
|
List<String> dictTypeList = Optional.ofNullable(baseDao.getDictTypesByIdList(idList)).orElseGet(ArrayList::new);
|
||||||
deleteById(id);
|
dictTypeList.forEach(dictType -> redisKeyList.add(RedisKeys.getDictDataByTypeKey(dictType)));
|
||||||
|
if (ToolUtil.isNotEmpty(redisKeyList)) {
|
||||||
|
//清除缓存
|
||||||
|
redisUtils.delete(redisKeyList);
|
||||||
|
}
|
||||||
|
//批量删除字典数据
|
||||||
|
deleteBatchIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
if (users == null || users.isEmpty()) {
|
if (users == null || users.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
SysUserEntity entity = users.getFirst();
|
SysUserEntity entity = users.get(0);
|
||||||
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
|
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -1,10 +1,6 @@
|
|||||||
package xiaozhi.modules.timbre.service.impl;
|
package xiaozhi.modules.timbre.service.impl;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -128,14 +124,13 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
if (StringUtils.isNotBlank(voiceName)) {
|
if (StringUtils.isNotBlank(voiceName)) {
|
||||||
queryWrapper.like("name", voiceName);
|
queryWrapper.like("name", voiceName);
|
||||||
}
|
}
|
||||||
List<TimbreEntity> timbreEntities = timbreDao.selectList(queryWrapper);
|
List<TimbreEntity> timbreEntities = Optional.ofNullable(timbreDao.selectList(queryWrapper)).orElseGet(ArrayList::new);
|
||||||
if (timbreEntities == null) {
|
|
||||||
timbreEntities = new ArrayList<>();
|
|
||||||
}
|
|
||||||
List<VoiceDTO> voiceDTOs = timbreEntities.stream()
|
List<VoiceDTO> voiceDTOs = timbreEntities.stream()
|
||||||
.map(entity -> {
|
.map(entity -> {
|
||||||
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
|
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
|
||||||
dto.setVoiceDemo(entity.getVoiceDemo());
|
dto.setVoiceDemo(entity.getVoiceDemo());
|
||||||
|
dto.setLanguages(entity.getLanguages()); // 设置语言类型
|
||||||
|
dto.setIsClone(false); // 设置为普通音色
|
||||||
return dto;
|
return dto;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -152,6 +147,8 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName());
|
voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName());
|
||||||
// 保留从数据库查询到的voiceDemo字段
|
// 保留从数据库查询到的voiceDemo字段
|
||||||
voiceDTO.setVoiceDemo(entity.getVoiceDemo());
|
voiceDTO.setVoiceDemo(entity.getVoiceDemo());
|
||||||
|
voiceDTO.setLanguages(entity.getLanguages());
|
||||||
|
voiceDTO.setIsClone(true); // 设置为克隆音色
|
||||||
redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(),
|
redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(),
|
||||||
RedisUtils.NOT_EXPIRE);
|
RedisUtils.NOT_EXPIRE);
|
||||||
voiceDTOs.add(0, voiceDTO);
|
voiceDTOs.add(0, voiceDTO);
|
||||||
@@ -214,6 +211,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
TimbreEntity entity = list.get(0);
|
TimbreEntity entity = list.get(0);
|
||||||
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
|
VoiceDTO dto = new VoiceDTO(entity.getId(), entity.getName());
|
||||||
dto.setVoiceDemo(entity.getVoiceDemo());
|
dto.setVoiceDemo(entity.getVoiceDemo());
|
||||||
|
dto.setIsClone(false); // 设置为普通音色
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
@@ -83,6 +83,13 @@ public class VoiceCloneController {
|
|||||||
return new Result<String>().error(ErrorCode.VOICE_CLONE_NOT_AUDIO_FILE);
|
return new Result<String>().error(ErrorCode.VOICE_CLONE_NOT_AUDIO_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加强验证文件扩展名
|
||||||
|
String originalFilename = voiceFile.getOriginalFilename();
|
||||||
|
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
|
||||||
|
if (!extension.equals(".mp3") && !extension.equals(".wav")) {
|
||||||
|
return new Result<String>().error("只允许上传.mp3和.wav格式的文件");
|
||||||
|
}
|
||||||
|
|
||||||
// 验证文件大小 (最大10MB)
|
// 验证文件大小 (最大10MB)
|
||||||
if (voiceFile.getSize() > 10 * 1024 * 1024) {
|
if (voiceFile.getSize() > 10 * 1024 * 1024) {
|
||||||
return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE);
|
return new Result<String>().error(ErrorCode.VOICE_CLONE_AUDIO_TOO_LARGE);
|
||||||
|
|||||||
@@ -17,4 +17,7 @@ public class VoiceCloneDTO {
|
|||||||
|
|
||||||
@Schema(description = "用户ID")
|
@Schema(description = "用户ID")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "语言")
|
||||||
|
private String languages;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user