← 返回笔记索引

项目实践

RAG 最难的不是调用模型,而是知识工程:zglab-rag 的文档与 Chunk 设计

说明 zglab-rag 如何把已登记 Markdown 变成可追溯、可增量更新且具备公开边界的 KnowledgeChunk。

RAG 最难的不是调用模型,而是知识工程:zglab-rag 的文档与 Chunk 设计

目录

问题从哪里开始

RAG 的第一步不是选择某个 Embedding 模型,而是回答“哪些文本可以成为知识、它们在原文中的位置是 什么、何时发生了变化”。若只把目录下的 .md 文件按固定长度切开,后续会同时失去引用位置、更新 边界和权限边界。

zglab-rag 只处理 config/sources.yaml 中显式登记的来源。来源声明 idkindscopevisibility、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_iddocument_idsource_idscopevisibility、title、 section_path、content、chunk_indexcontent_hashsource_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 系统都难以恢复可信边界。