模板方法模式:多模态文档处理流水线
模板方法模式:多模态文档处理流水线
学习目标
理解 Khoj 如何通过 TextToEntries 抽象基类和模板方法模式,统一处理 PDF、Markdown、Notion、DOCX、图片等 9 种内容类型。
前置知识
本章涉及分块策略的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 Khoj 的文档处理流水线实现。
项目实践
流水线架构
模板方法:统一的四步流水线
TextToEntries 抽象基类定义了所有处理器共享的流程:
1. 提取(Extract) → 从原始格式中提取文本(各处理器独立实现)2. 分块(Split) → 按 token 上限分割(共享 RecursiveCharacterTextSplitter)3. 嵌入(Embed) → 计算向量嵌入(共享,支持本地/远程后端)4. 存储(Store) → 保存到 DB + 磁盘缓存(共享,含变更检测)其中步骤 1 是 @abstractmethod,各处理器独立实现。步骤 2-4 在基类中实现,所有子类共享。
9 种内容处理器
| 处理器 | 数据源 | 提取方式 | 特殊处理 |
|---|---|---|---|
PDFToEntries | PDF 文件 | PyMuPDFLoader | 逐页提取,null 字符清理 |
MarkdownToEntries | .md 文件 | 文件读取 | 保留标题结构 |
DocxToEntries | .docx 文件 | docx2txt | 表格处理 |
NotionToEntries | Notion API | OAuth token | 页面层级递归拉取 |
GithubToEntries | GitHub API | PAT token | 仓库/分支配置 |
ImageToEntries | 图片文件 | rapidocr-onnxruntime OCR | 多语言 OCR |
OrgToEntries | Emacs org 文件 | 文件解析 | org 语法解析 |
PlaintextToEntries | 纯文本文件 | 文件读取 | 无特殊处理 |
Token 级分块策略
所有处理器共享相同的分块逻辑:
RecursiveCharacterTextSplitter( chunk_size=256, # 最大 token 数 separators=[ # 分割边界优先级 "\n\n", # 段落边界 "\n", # 换行 "!", # 感叹号 "?", # 问号 ".", # 句号 " ", # 空格 "\t", # Tab "" # 逐字符(最后的 fallback) ])这个策略的核心思路:优先在语义边界分割。段落 > 句子 > 单词 > 字符,越高层级的边界越优先。256 token 的限制确保了嵌入模型处理短文本的精度。
嵌入缓存与变更检测
文档上传 → 计算 hashed_value (SHA) ↓ 是否已有相同 hash? ├── 是 → 跳过,使用已有嵌入 └── 否 → 计算嵌入 → 保存到 DB + 磁盘 .pt 文件
文档更新 → 比较 hashed_value ├── 未变 → 不重新索引 ├── 已变 → 重新索引 └── 已删除 → 从索引中移除磁盘 .pt 缓存(PyTorch 序列化格式)避免了重启后重复计算嵌入。对于大型文档库,这可以显著缩短启动时间。
内容上传 API
PUT /api/content 接收多部分文件上传:
请求参数: - files: 文件列表 - type: 内容类型(pdf, markdown, docx, notion, github, image, org-mode, plaintext) - 可选的格式特定配置(如 GitHub 的 owner/repo/branch)
响应: - 索引状态(成功/失败/跳过) - 新增/更新/删除的条目数量问题与规避
| 问题 | 影响 | 规避策略 |
|---|---|---|
| 256 token 分块过小 | 长上下文信息丢失 | 检索时可合并相邻片段;或增大 chunk_size(但会降低精度) |
| OCR 质量依赖图片质量 | 扫描件或低分辨率图片识别率低 | rapidocr-onnxruntime 对多语言支持好,但仍建议用户上传清晰文档 |
| Notion API 速率限制 | 大量页面同步可能超时 | 分页拉取,每页之间间隔 |
| 嵌入缓存与模型更新不同步 | 更换 embedding 模型后缓存过期 | 需要手动清除 .pt 缓存文件,触发全量重新嵌入 |
设计取舍
为什么选择模板方法而非策略模式?
模板方法锁定了流程(提取 → 分块 → 嵌入 → 存储),新处理器只需要实现提取步骤。如果改为策略模式,每个处理器需要独立管理整个流程,容易导致各处理器行为不一致。模板方法的核心优势是一致性保证——所有处理器都经过相同的分块、嵌入和存储流程。
为什么 chunk_size 选择 256 而非更大的值?
小 chunk 的优势是嵌入更精确(短文本的语义表示更集中),检索结果更聚焦。缺点是可能丢失上下文——回答一个问题可能需要多个片段拼合。256 是在这两个极端之间的折中。如果应用场景需要更长上下文(如总结整篇文档),可以考虑增大到 512 或 1024。