|
@@ -0,0 +1,154 @@
|
|
|
|
|
+# 知识库模块设计与实施方案(修订版 2)
|
|
|
|
|
+
|
|
|
|
|
+## 调研结论(方案依据)
|
|
|
|
|
+
|
|
|
|
|
+项目已有可复用的完整基础设施,本方案**最大化复用现有模式**:
|
|
|
|
|
+- **隔离模式**:`agent` 表已有 `user_id + is_builtin` 口径(内置=全局可见只读、自建=仅创建者可见,后端过滤),`AgentExpertController` 的 CRUD 模式直接照搬。
|
|
|
|
|
+- **RAG 组件**:`EmbeddingProvider`(本地 ONNX bge-m3 / 在线模型双模)、`AttachmentVectorMapper`(pgvector 动态表原生 SQL)、`AttachmentDocIndexer`(分块+并发闸门+预算超时)全部现成。
|
|
|
|
|
+- **异步范式**:项目无 @Async/MQ,用 Spring 事件 + 后台线程(仿 `TemplateMetaChangedEvent` → `@EventListener` 异步索引)。
|
|
|
|
|
+- **Agentic 工具范式**:`RagSchemaSearchTool`(@Tool 注解注册进 Toolkit,模型自主调用)+ `appendRagPrompt` 系统提示词注入。
|
|
|
|
|
+- **文件下载范式**:后端 `LocalFileStorageUtil`(`/file-download/{path}`)+ 前端 `artifactApi.downloadArtifactFile`(手动带 token fetch blob → `downloadByData` 落盘)。
|
|
|
|
|
+- **存储**:主库 PostgreSQL(pgvector 已装);文件落盘 `LocalFileStorageUtil`;SQL 脚本手工执行(无 Flyway)。
|
|
|
|
|
+
|
|
|
|
|
+## 已确认的决策与要求
|
|
|
|
|
+1. Agent 绑定 **1 个**知识库(agent 表加 `knowledge_base_id`)
|
|
|
|
|
+2. RAG 检索:**Agentic 工具式**;**工具默认注册到绑定了知识库的 agent 上**——作为基础设施工具(不进工具组白名单、不受 tools_allow_json 限制、无需手动勾选),绑定即自动生效
|
|
|
|
|
+3. 前后端一起做;**前端样式布局参考专家页 `aiExpert/index.vue`** 卡片版式
|
|
|
|
|
+4. 内置知识库由**管理员(userId=1)**维护
|
|
|
|
|
+5. 已上传文档支持**下载**,**新建下载记录表**存下载行为
|
|
|
|
|
+6. **文档列表按上传者隔离:用户只能看到/下载/管理自己上传的文档**(本次新增)
|
|
|
|
|
+
|
|
|
|
|
+## 两层隔离模型(核心口径)
|
|
|
|
|
+
|
|
|
|
|
+| 层级 | 隔离规则 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 知识库本体 | 内置(is_builtin=1)全局可见可绑定;自建仅创建者可见可绑定 |
|
|
|
|
|
+| 库内文档 | **列表/详情/下载/删除/重试 只对上传者本人开放**(user_id 过滤)。内置库里管理员上传的文档清单对普通用户不可见 |
|
|
|
|
|
+| RAG 检索 | `search_knowledge` 检索**绑定知识库的全量文档**(与文档列表隔离解耦)——内置库对普通用户「知识可用、清单保密」,这正是共享库的价值 |
|
|
|
|
|
+| 绑定校验 | agent 绑定时校验知识库对当前用户可见(内置 or 自己的) |
|
|
|
|
|
+
|
|
|
|
|
+## 需求补充(我补充的遗漏项)
|
|
|
|
|
+
|
|
|
|
|
+| 补充项 | 设计 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 文档解析落盘 | Tika 抽取全文存 sidecar .txt(重索引无需重新解析原件),落 `knowledge/{kbId}/doc/` |
|
|
|
|
|
+| 异步向量化 | 上传后事件驱动异步索引,状态机 PENDING→INDEXING→READY/FAILED + fail_reason |
|
|
|
|
|
+| 重新索引 | 手动 reindex 接口;`tableTag()` 按模型隔离向量表,换模型不污染旧向量 |
|
|
|
|
|
+| 删除级联 | 删知识库 → 删全库文档文件+sidecar+向量行+下载记录 + 解绑引用该 KB 的 agent;删文档 → 三清 + 下载记录 |
|
|
|
|
|
+| 内置库初始化 | SQL 插入内置知识库(固定 id=1,is_builtin=1,user_id=1) |
|
|
|
|
|
+| 限制配置 | `KnowledgeProperties`:max-docs-per-user-per-kb、max-file-size、chunk-size/overlap、index-concurrency |
|
|
|
|
|
+| 我的文档数 | KB 卡片展示「我的文档 N 篇」(按 kb_id+user_id group by 统计,不冗余全库计数) |
|
|
|
|
|
+
|
|
|
|
|
+## 一、数据库(新建 `sql/knowledge_base.sql`,幂等)
|
|
|
|
|
+
|
|
|
|
|
+```sql
|
|
|
|
|
+-- 知识库
|
|
|
|
|
+create table if not exists knowledge_base (
|
|
|
|
|
+ id bigint primary key, -- 雪花ID;内置库固定 id=1
|
|
|
|
|
+ user_id bigint not null, -- 创建者(内置=1 管理员)
|
|
|
|
|
+ is_builtin smallint not null default 0, -- 1=内置(全局可见只读)
|
|
|
|
|
+ name varchar(100) not null,
|
|
|
|
|
+ description varchar(500),
|
|
|
|
|
+ create_time timestamp default current_timestamp,
|
|
|
|
|
+ update_time timestamp
|
|
|
|
|
+);
|
|
|
|
|
+create index if not exists idx_kb_user on knowledge_base(user_id);
|
|
|
|
|
+
|
|
|
|
|
+-- 知识库文档(元数据 + 解析/索引状态;user_id 即上传者,列表隔离依据)
|
|
|
|
|
+create table if not exists knowledge_doc (
|
|
|
|
|
+ id bigint primary key,
|
|
|
|
|
+ kb_id bigint not null,
|
|
|
|
|
+ user_id bigint not null, -- 上传者(列表/下载隔离依据)
|
|
|
|
|
+ file_name varchar(500) not null,
|
|
|
|
|
+ file_path varchar(1000) not null, -- 原件(下载用)
|
|
|
|
|
+ sidecar_path varchar(1000), -- Tika 全文 .txt(向量化用)
|
|
|
|
|
+ mime varchar(100),
|
|
|
|
|
+ size_bytes bigint default 0,
|
|
|
|
|
+ char_count integer default 0,
|
|
|
|
|
+ chunk_count integer default 0, -- 已向量化块数
|
|
|
|
|
+ index_status varchar(20) default 'PENDING', -- PENDING/INDEXING/READY/FAILED
|
|
|
|
|
+ fail_reason varchar(1000),
|
|
|
|
|
+ download_count integer default 0, -- 冗余下载次数(我的文档列表展示)
|
|
|
|
|
+ create_time timestamp default current_timestamp,
|
|
|
|
|
+ update_time timestamp
|
|
|
|
|
+);
|
|
|
|
|
+create index if not exists idx_kd_kb_user on knowledge_doc(kb_id, user_id);
|
|
|
|
|
+
|
|
|
|
|
+-- 下载记录表(谁在何时下载了哪个文档)
|
|
|
|
|
+create table if not exists knowledge_doc_download_log (
|
|
|
|
|
+ id bigint primary key, -- 雪花ID
|
|
|
|
|
+ kb_id bigint not null,
|
|
|
|
|
+ doc_id bigint not null,
|
|
|
|
|
+ user_id bigint not null, -- 下载人
|
|
|
|
|
+ file_name varchar(500) not null, -- 下载时文件名快照
|
|
|
|
|
+ download_time timestamp default current_timestamp
|
|
|
|
|
+);
|
|
|
|
|
+create index if not exists idx_kdl_doc on knowledge_doc_download_log(doc_id);
|
|
|
|
|
+create index if not exists idx_kdl_user on knowledge_doc_download_log(user_id);
|
|
|
|
|
+
|
|
|
|
|
+-- 内置知识库
|
|
|
|
|
+insert into knowledge_base (id, user_id, is_builtin, name, description)
|
|
|
|
|
+values (1, 1, 1, '系统通用知识库', '系统内置,所有用户可见(管理员维护)')
|
|
|
|
|
+on conflict (id) do nothing;
|
|
|
|
|
+
|
|
|
|
|
+-- agent 绑定知识库
|
|
|
|
|
+alter table agent add column if not exists knowledge_base_id bigint;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 二、后端(ai-server 新增 `module/knowledge` 包)
|
|
|
|
|
+
|
|
|
|
|
+### 实体/Mapper
|
|
|
|
|
+- `KnowledgeBase`、`KnowledgeDoc`、`KnowledgeDocDownloadLog`(MyBatis-Plus,`IdType.ASSIGN_ID`,仿 `ChatAttachment`)
|
|
|
|
|
+- `KnowledgeVectorMapper`:pgvector 原生 SQL(动态表名建表/插入/cosine 检索/按 docId 删/按 kbId 删),仿 `AttachmentVectorMapper`;检索 WHERE `payload->>'kbId'` 双保险
|
|
|
|
|
+
|
|
|
|
|
+### Service
|
|
|
|
|
+- `KnowledgeBaseService`(隔离规则仿 `AgentService` 专家管理区,userId 显式传参便于单测):
|
|
|
|
|
+ - `list(userId)`:内置 + 自己的,后端过滤,内置在前;VO 带「我的文档数」(group by 统计)
|
|
|
|
|
+ - `create/update/delete`:内置拒绝编辑/删除;删除时级联(全库文档+sidecar+向量行+下载记录)+ `detachKnowledgeBase(kbId)` 解绑 agent 并 `evictAgentPool`
|
|
|
|
|
+ - `requireVisible(userId, kbId)`:内置 or 自己的(绑定校验用)
|
|
|
|
|
+- `KnowledgeDocService`(**文档操作全部按上传者隔离**):
|
|
|
|
|
+ - `listMine(userId, kbId)`:知识库可见性校验后,**只返回 user_id=当前用户 的文档**
|
|
|
|
|
+ - `upload(userId, kbId, file)`:权限(自建库=库主人;内置库=仅管理员 userId=1 可传)→ 类型白名单 → 落盘 → Tika 抽全文存 sidecar → 登记 doc(PENDING)→ 发布 `KnowledgeDocIndexEvent`
|
|
|
|
|
+ - `download(userId, docId)`:**requireOwnedDoc(只允许下载自己上传的)** → 写下载记录 + download_count+1 → 返回文件流
|
|
|
|
|
+ - `delete(userId, docId)`:requireOwnedDoc → DB 行 + 原件 + sidecar + 向量行三清 + 下载记录
|
|
|
|
|
+ - `reindex(userId, docId)`:requireOwnedDoc → 状态重置 PENDING 重发事件
|
|
|
|
|
+- `KnowledgeDocIndexer`(`@EventListener` 异步,仿 `AttachmentDocIndexer`):
|
|
|
|
|
+ - 读 sidecar → `chunk()` 分块(固定窗口+重叠+段落对齐)→ `EmbeddingProvider.embed` → 写 pgvector `kb_vec_{kbId}{tag}_d{dims}` → 更新 doc.chunk_count/READY;异常置 FAILED+fail_reason
|
|
|
|
|
+ - 成员级公平 Semaphore 全局嵌入并发闸门
|
|
|
|
|
+
|
|
|
|
|
+### 工具与 Agent 接入
|
|
|
|
|
+- `KnowledgeSearchTool`(仿 `RagSchemaSearchTool`):`@Tool(name="search_knowledge", readOnly=true)`,构造时绑定 kbId+kbName,参数 `query`/`top_k`,返回片段(content+来源文档名+相似度)
|
|
|
|
|
+- **默认注册语义**:agent 绑定了知识库 → `AgentService.buildAgent()` 自动把 `KnowledgeSearchTool` 注册为基础设施工具(与 `RagSchemaSearchTool` 同路径,不进任何工具组、不受 `tools_allow_json`/工具包白名单约束、`reset_equipped_tools` 不收起),并 `appendKbPrompt` 追加使用指引;未绑定知识库的 agent 不注册该工具(省 token)。EmbeddingProvider 不可用时跳过注册并 log.warn(fail-open)
|
|
|
|
|
+- `AgentEntity` 加 `knowledgeBaseId`;`SaveExpertDTO` 加可选 `knowledgeBaseId`;保存时校验存在性+可见性;`writeExpert` 显式 set 该列(null 照写,支持解绑)
|
|
|
|
|
+
|
|
|
|
|
+### Controller(`KnowledgeBaseController`,`/knowledge-bases`,仿 `AgentExpertController`)
|
|
|
|
|
+- `GET /` 列表;`POST /` 创建;`PUT /{id}`(内置拒绝);`DELETE /{id}`(内置拒绝)
|
|
|
|
|
+- `GET /{id}/docs` **我的文档列表**;`POST /{id}/docs`(multipart 上传);`DELETE /{id}/docs/{docId}`
|
|
|
|
|
+- `GET /{id}/docs/{docId}/download` 下载(校验归属+记下载记录+文件流)
|
|
|
|
|
+
|
|
|
|
|
+## 三、前端(样式布局对齐专家页 aiExpert/index.vue)
|
|
|
|
|
+
|
|
|
|
|
+1. **api/knowledgeApi.ts** + `types.ts` 类型:KB CRUD、我的文档列表/上传/删除/下载(下载仿 `artifactApi.downloadArtifactFile`:带 token fetch blob → `downloadByData`)
|
|
|
|
|
+2. **views/aiKnowledge/index.vue**:完全对齐专家页版式——
|
|
|
|
|
+ - 同款 panel 结构:卡头(标题「知识库」+ 摘要统计「内置 N · 我的 N」+ 名称搜索框 + 刷新/新建按钮)
|
|
|
|
|
+ - 同款卡片网格:卡片头图标+名称+「内置/自定义」chip 标签、描述、chips(我的文档数/索引状态摘要)、卡底操作区(主操作「管理文档」+ 图标按钮:详情/编辑/删除,内置卡片不渲染编辑删除,后端另有双重校验)
|
|
|
|
|
+ - 空态复用 `EmptyState`;详情弹框复用 `BasicModal`
|
|
|
|
|
+3. **文档管理弹窗**(卡片「管理文档」打开,同款 Modal 版式):**标题标注「我的上传文档」**,表格列:文件名/大小/块数/索引状态 tag/失败原因/下载次数/操作[下载|重试|删除];上传按钮;文档列表接口天然只返回当前用户上传的数据,无需前端过滤
|
|
|
|
|
+4. **aiWorkbench/index.vue**:`RAIL_ITEMS` 增加 `{ key: 'knowledge', icon: 'mdi:bookshelf', label: '知识库' }`
|
|
|
|
|
+5. **AgentFormModal.vue**:加「绑定知识库」下拉(数据源=知识库列表,可清空=不绑定),随 `SaveExpertParams` 提交
|
|
|
|
|
+
|
|
|
|
|
+## 四、实施顺序
|
|
|
|
|
+
|
|
|
|
|
+1. SQL 脚本 + 实体/Mapper(含下载记录表)
|
|
|
|
|
+2. KnowledgeBaseService / KnowledgeDocService(隔离、级联、下载记录)
|
|
|
|
|
+3. KnowledgeDocIndexer(异步索引)+ 事件
|
|
|
|
|
+4. KnowledgeSearchTool + AgentService/AgentEntity/SaveExpertDTO 接入
|
|
|
|
|
+5. KnowledgeBaseController(含下载接口)
|
|
|
|
|
+6. 前端:API → 知识库页(对齐专家页版式)→ 文档管理弹窗 → 工作台入口 → AgentFormModal 绑定选择
|
|
|
|
|
+7. 单测(隔离规则:内置只读/归属校验/文档按上传者隔离/下载权限/级联删除)
|
|
|
|
|
+
|
|
|
|
|
+## 五、明确不做
|
|
|
|
|
+- 不引入新向量库/MQ/@Async(与项目现状一致)
|
|
|
|
|
+- 不扩展权限系统(沿用 user_id + is_builtin + 管理员=1 口径)
|
|
|
|
|
+- 不做文档在线预览(仅列表管理 + 下载)
|
|
|
|
|
+- 对话链路(AgentChatServiceImpl)不改动——工具注册随 buildAgent 生效,池化驱逐已有机制
|