在构建企业级 RAG(检索增强生成)系统的初期,绝大多数开发者都会倾向于采用一种直觉化的数据模型:Dataset -> Document -> Segment。
这是一个典型的以检索为中心(Retrieval-Centric)的设计。在这个模型里,Dataset(数据集)是系统绝对的“一等公民”。它就像一个大箩筐,不仅装载着所有的向量切片,还承载着 Embedding 模型的配置、切块大小(Chunk Size)的规则,甚至还要被迫承担起给文档分类、打标签的杂活。
在 Knowledge Hub (以下简称 KH) 研发的雏形阶段,我也曾为了追求开发速度(毕竟当时只有我一个人配合 AI 结对)而短暂采用了这种扁平模型。但随着业务深入到 Git 仓库的多级目录同步、细粒度的角色权限控制(ACL)以及 RAPTOR(层级递归摘要)时,这个模型展现出了极其严重的扩展性天花板。
今天,我想通过对 internal/project 和 internal/dataset 源码演进的复盘,聊聊为什么在复杂的知识治理场景下,我们必须强行在“检索”之上,建立一层显式的“内容意图”。
一、架构的阵痛:当 Dataset 沦为逻辑泥潭
在真实的业务场景下,企业知识的组织方式从来不是扁平的。
1.1 场景还原:无法还原的目录语义
当时,我需要让前端在 UI 上完美还原一个深达六层的 Git 仓库目录树(例如 docs/api/v1/internal/auth/oauth2.md)。在早期的扁平模型下,我只能在 Document 记录上存一个 path 字符串。
这引发了一系列连锁反应:
- 查询低效:当我想查出
/docs/api/目录下的所有子节点时,数据库必须进行全文模糊匹配,性能极差。 - 权限断层:我想给
/docs/internal/目录设为“仅管理员可见”,由于没有真实的目录节点实体,我必须递归地去修改该目录下成千上万份文档的权限字段。 - 状态不一致:一旦 Git 端发生了目录重命名,系统需要对底层成千上万个切片的元数据进行全量更新,这在并发环境下简直是运维噩梦。
1.2 AI 给出的“诱人陷阱”
当我把这个痛点抛给 AI 时,它最初给出的重构方案是:在 Dataset 模型里加一个 directory_tree 的 JSONB 字段,用来缓存整棵树。
从代码实现上看,这个方案写起来最快,能迅速解决前端展示问题。但在做最后裁决时,我感知到了一种强烈的架构不适感。
如果把代表“人类组织意图”的目录树,强行塞进代表“机器检索物理”的 Dataset 里,Dataset 就会沦为一个无所不包的 God Object(上帝对象)。未来,无论我是想优化向量算法,还是想调整目录逻辑,都会在这个泥潭里牵一发而动全身。
工程师的直觉告诉我:这债,我不能欠。 于是我选择了另一条更厚重、但也更稳健的路——引入 Project 层。
二、架构升维:Project(意图)与 Dataset(物理)的分离
在 internal/model/project.go 和 dataset.go 中,我确立了一套全新的分层契约。这本质上是对 Spec-Driven Development (SDD, 规范驱动开发) 理念的深度实践。
2.1 Project:内容主权的回归
Project 代表了人类视角的“内容世界”。
在 project.go 源码中,我们定义了它负责的所有非检索事务:
- SourceType:明确知识的来源(是受控的 Git,还是原生的 Managed)。
- ProjectNode (VFS):维护真实的、具有物理 ID 路径的目录树。
- AccessPolicy:存储复杂的组织权限策略。
在 SDD 的语境下,Project 就是一份声明式的“意图代码”。它只定义知识应该长成什么样,而不关心它是如何被计算成向量的。
2.2 Dataset:检索物理的归位
Dataset 退回到它最擅长的角色——底层检索引擎。
在 dataset.go 中,它现在只关心:
- EmbeddingProvider:用哪个模型去编码。
- RetrievalStrategy:是语义检索、混合检索还是关键词检索。
- 调优参数:TopK、ScoreThreshold、VectorWeight。
这两者之间,仅仅通过一个 BoundDatasetID 建立 1:1 的物理映射。这种“逻辑在前,物理在后”的解耦,让 KH 获得了极强的工程免疫力。
三、后端的降维打击:物化路径 (Materialized Path) 与性能
在实现 ProjectNode 这个 VFS 实体时,我再次与 AI 在方案选型上产生了深度博弈。
AI 惯性地推荐了基于 parent_id 的递归查找方案。在处理前端组件时,这种方案很直观,但在后端数据库层,递归查询是性能的死穴。尤其是在一个人负责所有运维工作的单兵环境下,我必须确保系统的查询效率是可预测的。
我强令系统引入了 Materialized Path(物化路径) 方案。
每个节点都有一个形如 /{root_id}/{mid_id}/{current_id}/ 的路径字段。
这个决策带来了质的飞跃:
- $O(1)$ 级目录查询:我们只需要执行一条
LIKE '{current_path}%'配合前缀索引,就能在常数时间内拉出整棵子树。 - 权限继承的毫秒化:当计算目录穿透权限时,物化路径让我们能用极简的 SQL 逻辑实现大范围的隔离。
这种基于后端索引优势对应用层逻辑的“降维打击”,正是架构解耦带来的红利。
四、RAPTOR:为什么它必须建立在 Project 的骨架上?
引入 Project 层最大的惊喜,在于它为 KH 实现 Hierarchical RAG (RAPTOR) 提供了天然的土壤。
传统的 RAG 检索存在一个致命弱点:它能给你砖头(切片),但它没法给你大厦的蓝图。当你问“这个项目的权限体系是怎么演进的?”时,AI 往往只能根据相似度抓取几个碎片,拼凑出的答案通常断章取义。
市面上常见的 RAPTOR 实现,是让算法去算“语义聚类”。但那种机器算出来的层级,人类根本看不懂,也没法审计。
在 KH 的架构下,因为有了 ProjectNode 维护的真实目录,我们直接利用用户规划的业务目录作为摘要树的骨架。
- 每个目录节点(如
/internal/rag/raptor)都可以自动汇总其子节点的知识,生成高层级的SummaryEmbedding。 - 检索时,系统会先匹配高层目录摘要,锁定“知识林区”,再向下探测“知识树叶”。
这种“业务逻辑驱动”的层级检索,保证了 AI 给出的回答始终具备极强的人类可解释性。 如果没有当初强行把 Project 抽离出来的决策,这一切高级检索特性都将成为无法落地的空中楼阁。
五、Hermes 闭环:系统自省的起点
作为这个项目的唯一开发者,我意识到系统必须具备一定的“自我进化”能力,我称之为 Hermes 闭环。
在重构 Project 层的期间,我让 AI 承担了一个角色:架构自体检员。
每当我们在 DEV_LOG.md 记录下一次重构阵痛后,我会指挥 AI 去“对齐”现有的源码实现与 spec.md。
AI 曾在审计过程中主动向我反馈:
“由于我们现在采用了
materialized_path管理 VFS,internal/dataset里的某些旧元数据标记已经出现了语义冗余。建议将其彻底清理,以维持架构的纯粹性。”
这种基于 Specs 和 Logs 发现设计缺陷、并主动提议重构的模式,让我深刻感受到:架构师的价值不在于码字,而在于建立这种能让系统不断优化的规则闭环。
结语:在 AI 时代,守住“解耦”的底线
回看 Knowledge Hub 把 Project 放到前面的重构历程,我感触最深的是:AI 给我们带来的执行力越强,我们对架构洁癖的坚持就越重要。
AI 可以在一分钟内写完几千行 CRUD,但它无法替你做出“解耦”这种违背短期效率、但决定长远生死的决定。在这个时代,程序员的真正壁垒,在于你能否在面对看似便捷的“打补丁”方案时,坚定地拔出架构的手术刀;在于你能否通过定义清晰的逻辑边界,让系统在复杂的业务冲刷下,依然保持那种数学般纯粹的优雅。
Knowledge Hub 的 Project 架构,并不是为了炫技,它是我们向“通用工业级知识治理”迈出的、最冷静也最坚决的一步。