Skip to content

知识库路径型目录实体化 ​

状态:proposed 类型:feature Owner:backend/package/yuxi/services/knowledge_folder_service.py

问题 ​

目录上传和部分外部导入通过 source_path 把相对路径保存到文件记录的 filename。文件列表按路径分段动态投影虚拟文件夹,因此可以保留目录层级,却没有独立文件夹记录、稳定 file_id、创建人和创建时间,也不能安全使用真实文件夹的移动与重命名能力。

文件夹管理已经以真实 knowledge_files 记录和 parent_id 关系作为可写目录模型。新的可写知识库目录上传继续产生虚拟文件夹,会让相同用户操作得到两种能力不同但视觉相同的目录。列表查询时静默创建真实目录又会让读取产生批量持久化副作用,并在分页、重试和并发读取下产生不可预测结果。

提案 ​

可写且支持文档的知识库在接收带 source_paths 的目录上传时实体化目录层级。用例从规范化后的相对路径提取目录段,按父子顺序幂等取得或创建真实文件夹记录,并把文件记录保存为叶子文件名和对应真实 parent_id。真实文件夹记录保存执行上传的 created_by 和服务端创建时间。普通多文件上传没有目录相对路径时继续写入当前目标文件夹,不额外创建目录。

目录实体化属于写用例,不属于列表 repository。HTTP 路由只完成请求和权限适配;用例流程进入 yuxi.services,目录与文件的查询、精确字段更新和事务锁进入 repository。目录创建与文件归属更新按同一知识库目录树锁串行化;同一父目录下的同名真实文件夹必须幂等复用或明确冲突,不能创建平行重复目录。批量上传继续允许文件级部分成功,但每个成功文件的目录链和文件归属必须原子发布,失败不能留下该文件对应的半条目录关系。

自动上传实体化进入实现前必须新增数据库拥有的同级名称不变量。目录名称先执行 Unicode NFC 和产品确定的大小写归一化,并把归一化值持久化;唯一键覆盖 kb_id、把根目录 NULL parent_id 映射到稳定根标记后的父目录,以及归一化名称。文件和文件夹共享同级名称空间:同名真实文件返回结构化冲突,同名真实文件夹幂等复用,同名虚拟目录拒绝自动创建并要求先显式转换。schema 收敛先审计历史重复项,不能用创建索引失败发现生产冲突,也不能通过自动追加序号静默改名。

客户端提交的整个 source_paths manifest 在持久化前统一规范化并构建目录 Trie。绝对路径、空段、.、..、混用分隔符后重复、超长段、超长总路径、超深目录和规范化后重复的完整路径在任何目录写入前拒绝。成功文件按有限批次进入同一知识库目录锁;每批只创建 Trie 中缺失的目录并原子写入文件归属。同步批次上限必须由真实 PostgreSQL 压测确定并固定在 Owner 处,超过上限明确拒绝;不静默切换进程内后台任务。重试通过上传批次幂等键和规范化完整路径识别,不能把同一对象重复登记为新文件。

已有虚拟文件夹不在文件列表行内逐个迁移,也不在列表读取时自动写入。知识库详情通过临时检测接口读取仍含 / 路径段的历史文件数和剩余目录层级数;检测到历史兼容数据时,文件数统计卡改为 warning 状态和感叹号。管理者点击卡片后看到知识库级转换说明并启动唯一后台任务,创建人记录启动转换的当前操作者。

后台任务按最多 500 条文件记录扫描,每个事务只剥离一层路径:在知识库目录树 advisory lock 内复用同名真实文件夹或创建缺失文件夹,再提交该批文件的新 parent_id 和剩余 filename。每层提交后即成为最终事实;SSE 只读取任务快照,断开连接不取消任务。进程中断时已提交批次保留,再次检测和启动会从仍含路径段的记录继续。普通文件占用目标名称、重复真实文件夹等冲突记录留在原位置并进入任务结果,不能阻塞其他无冲突路径,也不能静默改名。

只读 Connector 保持虚拟路径投影且拒绝实体化。MinIO 对象 URL、文件 file_id、解析 Markdown 和向量身份不因目录实体化而移动;实现前必须确认所有按文件名冗余保存的 Chunk、知识导图和其他派生数据 Owner。依赖展示名的派生状态在同一用例中精确更新或明确标记为待刷新,不能静默保留相互矛盾的名称。

前端对新的目录上传结果展示真实文件夹。历史迁移入口只属于知识库文件统计卡,不进入单个虚拟文件夹操作菜单。弹窗展示进度与当前消息,并明确关闭弹窗或 SSE 中断不会撤销已完成批次;任务终态后重新执行检测并读取知识库统计,不使用乐观树结构代替持久化事实。

替代方案 ​

  • 继续只使用虚拟文件夹:导入最简单且适合只读外部数据,但可写知识库会长期存在两套外观相同、能力不同的目录模型。
  • 在列表查询时自动转换:调用方无需显式操作,但读取会批量写数据库,分页、缓存、失败重试和权限语义都难以预测,因此拒绝。
  • 上传完成后把整批文件放进一个后台迁移任务:适合超大批次,但会长期暴露“上传完成、目录尚未实体化”的中间态;首期优先让每个成功文件的目录关系原子发布,超过验证规模阈值后再单独评估后台任务。
  • 在每个虚拟文件夹行上提供转换:迁移需要用户逐层操作且无法表达知识库整体剩余量,拒绝。
  • 用单一事务递归实体化全部后代:一次得到完整真实树,但事务范围和失败回滚成本随目录规模扩大,拒绝;后台任务改为每批、每层提交。
  • 移动 MinIO 对象以匹配展示目录:对象身份目前不由展示目录拥有,迁移会增加无必要的外部副作用,因此拒绝。

验收标准 ​

验收主张失败面语义 Owner直接证据 / 命令负向案例当前结果
新目录上传产生带稳定 ID、父关系、创建人和时间的真实文件夹仍只把完整相对路径写入 filename,或目录与文件分批发布上传用例、KnowledgeFileRepository、knowledge_files真实 HTTP/PostgreSQL integration:上传后重新读取文件夹和文件记录目录链创建中注入失败,不能观察到文件挂在半成品目录下Not run
并发上传复用同一目录链且不产生同级重复文件夹仅在应用层先查后建,竞态下重复创建repository 事务、规范化名称唯一约束和目录树锁真实 PostgreSQL 并发 integration,回读同级目录记录数两个请求同时上传到同一路径、根目录 NULL 父关系、Unicode/大小写等价名称均只能产生一条目录记录Not run
知识库级任务分批实体化全部无冲突路径、记录启动者并保持文件身份和对象地址SSE 断开取消任务、一个冲突回滚其他路径、改变 file_id 或 MinIO URL实体化 service、Tasker、文件 repository、MinIO 元数据契约真实 HTTP/PostgreSQL integration:不建立 SSE 也完成任务,终态后回读目录树、创建人和原文件 ID注入同名普通文件与中断,已完成批次保留且重跑只处理剩余路径Not run
只读和无管理权限调用方不能实体化仅由前端隐藏,直接调用仍可写FastAPI 权限依赖、只读 Connector真实 HTTP integration只读用户、只读 Connector 和跨知识库路径请求返回拒绝且数据库不变Not run
文件名派生状态不会与实体化后的目录事实冲突KnowledgeFile 已变更但 Chunk、知识导图或搜索结果仍显示旧路径各派生数据 Owner对实际使用文件名的 Owner 做集成回读;无 consumer 时以负向符号搜索证明建立包含知识导图/已索引文件的虚拟目录后转换,旧名称不得继续作为当前事实Not run
文件统计卡对历史数据显示 warning 并通过 SSE 展示知识库级进度单个文件夹行出现迁移入口,或关闭弹窗取消后端任务DataBaseInfoView.vue 与知识库 API 封装前端 unit、lint、build、真实浏览器录屏中断 SSE 后任务继续,重新检测仍反映数据库剩余事实Not run

风险 ​

路径规范化、同级名称冲突和并发创建会直接影响持久目录树。实现需要真实 PostgreSQL 事务、规范化名称唯一约束和确定性并发负控,不能用 mock 调用次数替代。现有 knowledge_files 尚未拥有该数据约束,因此自动上传实体化不得在 schema 审计、归一化字段和根目录唯一语义落地前启用。

历史虚拟目录可能包含大量文件。迁移任务每批最多扫描 500 条记录并逐层提交,SSE 生命周期不拥有任务;进程停止会让当前 Tasker 任务进入可观察终态,已经提交的数据不回滚,重新启动迁移从剩余路径继续。自动上传的有限批次上限仍需压测证据,不能直接复用历史迁移批次。

从完整相对路径改为 parent_id + 叶子名称 会改变展示名事实。文件内容、对象和向量以 file_id 为身份的主张需要由实际回读证明;任何仍以旧文件名作为身份或引用的 consumer 都必须在实现前纳入同一事务、失效流程或明确排除实体化能力。

本项目基于 MIT License 开源,欢迎使用和贡献。