TECH DEEP DIVE
Weknora知识库文档处理全流程详解
RAG 管道与 Wiki 生成的完整技术架构拆解
目 录 CONTENTS
总体架构
两条独立管道的全景概览
RAG 管道详解
文档解析 → 分块 → 向量化 → 双存储
Wiki 管道详解
MAP/REDUCE 两阶段 + 容错机制
RAG 与 Wiki 的关系总结
两条管道的对比与协作
RAG 管道负责"搜得到",Wiki 管道负责"看得懂"
总体架构
知识库文档处理分为两条独立但可并行的管道,共享"文档解析"和"文本分块"两个阶段后分道扬镳:
两条管道共享"文档解析"和"文本分块"两个阶段,之后分道扬镳。
RAG 管道详解
2.1 整体流程
上传文档后,RAG 管道依次经过 5 个阶段:
文档解析 — 调用外部 docreader 服务,将各种格式统一转为 Markdown 文本 + 图片列表
文本分块 — 三级自适应策略链(标题感知 → 启发式边界 → 递归分割)
父子分块(可选) — 父 chunk(4096字符)+ 子 chunk(384字符)双层结构
向量化 + 索引入库 — 批量 embedding,同时写入向量库和关系数据库
后处理(可选) — 摘要生成 + 问题生成(为每个 chunk 生成可能的检索问题)
2.2 文档解析
调用外部 docreader 服务(独立微服务)
支持 PDF、Word、Markdown、HTML、TXT、图片(OCR)等格式
输出:统一的 Markdown 格式文本 + 图片链接列表
解析结果不直接入库,而是传给分块模块继续处理
2.3 文本分块:三级自适应策略链
分块策略按优先级自动回退,上一级不适用则自动 fallback 到下一级:
Tier 1
标题感知分割
Tier 2
启发式边界分割
Tier 3
递归分割(兜底)
优先级从高到低,自动 fallback
✦ Tier 1:标题感知分割
适用场景:有 Markdown 标题结构(# ## ### 等)的文档
核心逻辑:按标题边界切分文档,维护标题层级栈(HeadingHierarchy),追踪"面包屑"路径。小节内容放得下 → 整个小节作为一个 chunk;太大 → 交给下一级。每个 chunk 带上 ContextHeader,如 # 第一章 > ## 1.1 概述,帮助 embedding 模型理解上下文。
✦ Tier 2:启发式边界分割
适用场景:论文、法律文档、技术规范等有结构化线索的文档
按优先级扫描边界信号:换页符 → 章节标记 → 编号章节 → 全大写标题行 → 分隔符 → 连续空行。使用贪心装箱算法:逐个累加段落,超过 chunk_size 时开新 chunk。
✦ Tier 3:递归分割(万能兜底)
适用场景:所有文档的最终兜底方案
分隔符优先级:\n\n(段落)→ \n(行)→ 。?!(句子结束)→ 字符级强制截断。
保护机制:LaTeX 公式、Markdown 表格、代码块、图片链接被视为原子单元,绝不切割。硬上限:所有 chunk 最多 7500 字符。Markdown 表格被切分时,后续 chunk 自动 prepend 表头行,防止列名信息丢失。
重叠机制:优先在 \n\n → \n → 。?! 处切分,确保重叠内容在语义边界处开始,而不是在句子中间截断。
2.4 父子分块机制
父子分块是一种双层粒度设计:用小子 chunk 做精准检索,命中后返回父 chunk 的大段上下文给 LLM。
✦ 检索流程
语义搜索
命中子 chunk
查父 chunk
SQL 读取大段内容
喂给 LLM
大窗口上下文
小粒度检索 → 大粒度上下文
父子关系建立:纯数据结构,不依赖语义。分割阶段父 chunk 按数组排列,子 chunk 记录 ParentIndex → 入库时转为 UUID 写入 parent_chunk_id → 检索时通过外键关联。
无标题文档:父子分块与文档有没有标题完全无关。无标题时 Tier 3 递归分割兜底,ContextHeader 为空字符串,父子关系靠 ParentIndex → parent_chunk_id 纯机械链路建立。
2.5 向量化与索引入库
关键设计:同一条记录同时支持向量搜索和关键词搜索。不是分别写两份数据,而是同一个 IndexInfo 对象存入向量库的一条记录中:
不同向量库的 BM25 实现方式
核心结论:没有独立的 BM25 倒排索引表,BM25 是向量数据库引擎自身提供的能力。写入只做一次,检索时分两路:Vector → 向量相似度搜索,Keywords → BM25 关键词搜索,结果在应用层做融合排序。
✦ 批量处理策略
每批 40 条 IndexInfo 为一组
最多 5 个并发批次同时处理
失败时 5 次指数退避重试(200ms → 3200ms)
单条输入最长 20000 字符,图片 base64 替换为 [image] 占位符
2.6 双存储模型
chunks 表关键字段
Wiki 管道详解
3.1 整体流程
Wiki 管道在 RAG 管道完成后启动,经历 5 个阶段:
入队 — 延迟 30 秒去抖,创建 WikiPendingOp,触发异步任务
MAP 阶段 — 并发处理每个文档:候选 Slug 提取 → 摘要生成 → Chunk Citation 分类
目录规划 — LLM 为所有新增实体/概念规划目录结构,复用已有 wiki_folders
REDUCE 阶段 — 按 slug 聚合多文档贡献,合并生成或更新 Wiki 页面
Finalize 收尾 — 清理死链接、注入交叉链接、更新索引页面、记录变更
3.2 触发条件
Wiki 管道需要同时满足以下条件才会启动:
知识库的 IndexingStrategy.WikiEnabled == true
文档解析后有文本类 chunk(非纯图片文档)
知识库配置了 synthesis model(用于 LLM 生成)
3.3 入队机制
文档处理完成(RAG 管道入库后),后处理阶段检查 WikiEnabled:是 → 创建 WikiPendingOp 持久化到 task_pending_ops 表,触发 Wiki Ingest 异步任务(延迟 30 秒去抖);否 → 跳过。
去抖设计:30 秒内多次上传同一知识库的文档,只触发一次 Wiki Ingest 任务,避免频繁重建。
3.4 MAP 阶段详细逻辑
✦ 并发模型
默认 10 个并发(可通过 WikiConfig 调整)
每个文档独立处理,互不干扰
失败不阻塞整个批次,记录失败后继续
✦ Pass 0:候选 Slug 提取
对每个文档,LLM 分析其全部 chunk 内容,提取实体和概念的骨架:
Slug 提取输出示例
{
"name": "循环神经网络",
"slug": "concept/recurrent-neural-network",
"aliases": ["RNN", "循环神经网路"],
"description": "一种用于处理序列数据的神经网络架构"
}
✦ 文档摘要生成
用 LLM 为文档生成结构化摘要,摘要中包含关键实体/概念的 [[slug]] 链接。摘要页面类型为 summary。
3.5 目录规划(Taxonomy Plan)
用 LLM 分析当前批次所有提取出的实体/概念 slug,规划它们应该放在哪个目录下。复用已有的 wiki_folders,如果目录路径已存在则直接关联,不存在则创建新目录。
3.6 REDUCE 阶段详细逻辑
Wiki 管道最核心的阶段
MAP 阶段的输出(slugUpdates)按 slug 分组,并发处理(默认 10 并发)。对每个 slug:读取现有 Wiki 页面 → 合并新贡献 → LLM 重写/更新 → 写入 wiki_pages 表。
并发控制:不同批次的 Map 可能产生同一 slug 的更新,用 Redis 分布式锁(withSlugLock)保证同一时间只有一个 Reduce 在写同一 slug。
3.7 Finalize 收尾
所有 slug 的 Reduce 完成后,触发 Finalize 任务(延迟执行,合并在一个任务中):
清理死链接 — 扫描所有 Wiki 页面中的 [[slug]] 链接,移除指向已删除页面的链接
注入交叉链接 — 在相关页面之间添加 [[链接]]
重建索引页面 — 更新 wiki 首页的目录结构和"最近更新"列表
文件夹清理 — 删除空目录
3.8 Wiki 数据库存储
wiki_pages 表
wiki_folders 表
wiki_page_revisions 表
wiki_page_issues 表
task_pending_ops 表(持久化任务队列)
3.9 容错与并发控制
去抖:30 秒内同一知识库多次上传,只触发一次 Ingest
并发领取:多个 Worker 通过 claimed_at 领取互不重叠的批次
slug 锁:同一 slug 的 Reduce 操作串行化(Redis 分布式锁)
Finalize 合流:多个批次的 Finalize 合并为一个任务执行
失败重试:失败文档重新入队,有 fail_count 上限
知识库删除保护:删除时自动清理所有待处理任务
RAG 与 Wiki 的关系总结
两条管道共享文档解析和文本分块的结果,但后续处理完全独立
RAG 管道负责"搜得到",Wiki 管道负责"看得懂"
既然看到这里了,如果觉得有用,随手点个赞、在看、转发三连吧。
THANKS FOR READING
夜雨聆风