RAG 最难的不是调用模型,而是知识工程:zglab-rag 的文档与 Chunk 设计
目录
问题从哪里开始
RAG 的第一步不是选择某个 Embedding 模型,而是回答“哪些文本可以成为知识、它们在原文中的位置是
什么、何时发生了变化”。若只把目录下的 .md 文件按固定长度切开,后续会同时失去引用位置、更新
边界和权限边界。
zglab-rag 只处理 config/sources.yaml 中显式登记的来源。来源声明 id、kind、scope、
visibility、include 与 exclude;不会扫描用户全部 GitHub 仓库,也不会默认收录源码、lockfile 或
依赖目录。
从 Markdown 到 Chunk
每篇文档先解析 YAML frontmatter 和 Markdown 正文,形成框架无关的 KnowledgeDocument;随后产生
KnowledgeChunk。缺失必要 metadata、错误 frontmatter 或空正文都会明确失败,而不是悄悄生成一条
不可用记录。
flowchart LR
R[已登记来源] --> L[Markdown Loader]
L --> D[KnowledgeDocument]
D --> H[标题层级解析]
H --> C[Structure-aware Chunker]
C --> K[KnowledgeChunk]
K --> I[增量 Index Planner]
每个 Chunk 至少携带 chunk_id、document_id、source_id、scope、visibility、title、
section_path、content、chunk_index、content_hash、source_path 与可用的 revision。这些字段
不是“方便展示的附属信息”,而是后续检索、引用、权限和更新的合同。
结构感知切分
正文首先按 Markdown 标题层级组织,例如 H1 > H2 > H3。Chunk 优先停在章节语义边界;只有章节
超过配置化的最大长度时,才进行带 overlap 的二级切分。当前默认建议的量级是 target 约 500~800
中文字符等价长度、max 约 1,200,overlap 约 100~150,并非在核心逻辑中写死的常量。
flowchart TD
M[Markdown 正文] --> S[按 H1/H2/H3 建立 Section]
S --> Q{章节是否超过 max size?}
Q -->|否| A[一个语义 Chunk]
Q -->|是| B[二级滑动切分]
B --> O[仅在二级切分保留 overlap]
A --> P[保留完整 section_path]
O --> P
固定字符切分容易让“原因”“结论”和“限制”落到不同片段。结构切分也不保证每个 Chunk 都完美独立, 但至少让检索和引用能说明文本处于哪条标题路径,而不是只给出一个文件名。
元数据为何是检索的一部分
section_path 既参与 contextual composition,也让引用能映射回文档中的具体章节。source_path 是
对外 Sources 的来源定位;scope 支持未来按知识域过滤;visibility 必须从 source 完整继承,
公网检索强制只保留 public。
这意味着权限不是检索命中后的展示开关。private Chunk 即使分数更高,也不能进入公网 Context,更不能 在错误信息、调试字段或引用中泄露 metadata。
稳定身份与增量更新
chunk_id 由文档内容和章节定位确定性产生:相同文档内容与章节重新 ingestion 不会随机换 ID。
content_hash 则为未来增量索引提供内容变化判据。Embedding 层进一步记录精确 composition 输入的
hash 与 embedding profile,因此 title 或 section path 的变化即使正文不变,也会触发正确的重算。
revision unchanged → skip
revision changed → ingestion → chunk diff
→ new / changed / unchanged / deleted
→ only embed new + changed → atomic apply
这种设计让“更新一篇 Notes”不必重嵌入全部知识库,也让删除来源中的段落可以清理对应索引,而不是 留下无法追溯的旧向量。
关键决策与方法论
- 语义边界优先于字符边界:Chunk 是检索与引用的共同单位,不只是模型输入窗口。
- metadata 与正文同等重要:没有标题路径和可见性,命中的文本无法安全解释。
- 稳定 ID 优先于随机 UUID:否则重复构建会破坏差异规划、评测对齐和引用追踪。
- 配置化尺寸而非神秘常量:不同语料和模型可以比较策略,而不是在代码里寻找隐含参数。
知识工程的结果不是“切出了多少段”,而是建立可验证的资料单元。后续模型、向量库和生成器可以替换, 但如果这一层失去 provenance,整个 RAG 系统都难以恢复可信边界。