5.4 KiB
5.4 KiB
多格式文档支持 — 设计文档
日期: 2026-07-10 版本: 1.0
目标
将 md-vector-db 从"仅 Markdown"扩展为支持 .txt、.pdf、.html 的多格式文档向量数据库。
非目标
- 不支持
.docx、.pptx、.epub等 Office/电子书格式(留待后续扩展) - 不改变现有的嵌入和检索流程
- 不改变 HTTP API 的请求/响应模型
架构
模块结构
src/core/
├── splitters/ # 新建目录
│ ├── __init__.py # 导出 registry + 所有 Splitter
│ ├── base.py # Splitter Protocol + BaseTextSplitter (ABC)
│ ├── markdown.py # MarkdownSplitter (从 ingest.py 移入)
│ ├── text.py # TextSplitter (纯文本按段落+标点硬切)
│ ├── pdf.py # PDFSplitter (pymupdf → TextSplitter)
│ ├── html.py # HTMLSplitter (bs4 → TextSplitter)
│ └── registry.py # 工厂 + 扩展名→Splitter 映射表
├── ingest.py # 精简,用 registry.get_splitter() 自动选择
类继承
Splitter (Protocol)
└── BaseTextSplitter (ABC) # max_size, overlap, _split_single_paragraph
├── MarkdownSplitter # 已有,从 ingest.py 移入
├── TextSplitter # 纯文本:按 \n\n 切段落,超长按标点硬切
├── PDFSplitter # 读取 PDF → extract_text() → 委托 TextSplitter
└── HTMLSplitter # 读取 HTML → get_text() → 委托 TextSplitter
PDFSplitter 和 HTMLSplitter 不继承 BaseTextSplitter,而是组合一个 TextSplitter 实例。它们实现 Splitter Protocol,在 split() 中:提取纯文本 → 委托 TextSplitter.split()。
注册表
registry.py 维护默认扩展名映射:
_DEFAULT_MAP = {
".md": "markdown",
".markdown": "markdown",
".txt": "text",
".pdf": "pdf",
".html": "html",
".htm": "html",
}
def get_splitter(file_path: str, **config) -> Splitter:
"""根据扩展名自动选择 Splitter,未匹配回退到 TextSplitter."""
依赖策略
pymupdf和beautifulsoup4作为可选依赖- 首次使用 PDF/HTML 格式时才
import,库缺失时抛ImportError带安装提示 - 安装方式:
uv sync --extra pdf # PDF 支持
uv sync --extra html # HTML 支持
uv sync --extra all # 全部可选依赖
ingest.py 改动
DocumentIngestor.init
已有 splitter 可选参数,保持不变。显式传入的 splitter 覆盖自动选择。
ingest_file()
改为使用 get_splitter(file_path, ...) 自动选择 splitter:
def ingest_file(self, file_path: str) -> int:
splitter = self.splitter or get_splitter(file_path,
max_size=..., overlap=...)
...
ingest_directory()
将 rglob("*.md") 改为遍历所有支持格式:
_SUPPORTED_SUFFIXES = {".md", ".markdown", ".txt", ".pdf", ".html", ".htm"}
def ingest_directory(self, dir_path: str) -> dict[str, int]:
for f in Path(dir_path).rglob("*"):
if f.suffix.lower() in _SUPPORTED_SUFFIXES:
...
数据流
文件路径 → get_splitter(path)
├── .md → MarkdownSplitter.split(text)
├── .txt → TextSplitter.split(text)
├── .pdf → PDFSplitter.split(binary)
│ └── pymupdf 提取文字 → TextSplitter.split(text)
└── .html → HTMLSplitter.split(html)
└── bs4 去标签 → TextSplitter.split(text)
↓
batch_embed(chunks)
↓
ChromaDB.add()
测试策略
tests/test_splitters_text.py— TextSplitter 段落切分、硬切、overlaptests/test_splitters_registry.py— 扩展名映射、回退逻辑、自定义注册tests/test_splitters_pdf.py— PDF 提取文字 + 分块(需 pymupdf)tests/test_splitters_html.py— HTML 去标签 + 分块(需 bs4)tests/test_ingest.py— 补ingest_directory多格式遍历测试
CLI/API 影响
- CLI:零改动。
ingest和ingest-dir自动获得多格式能力 - API:零改动。
POST /api/v1/ingest的file_path自动支持 ingest_content():仍默认使用MarkdownSplitter(处理 Markdown 字符串),显式传入 content 时不变
风险
| 风险 | 缓解 |
|---|---|
| PDF 提取失败(扫描件/图片 PDF) | 抛明确异常,跳过该文件 |
| HTML 标签复杂导致去标签不干净 | 用soup.get_text(separator="\n") 保留段落结构 |
ingest.py 迁移 MarkdownSplitter 后旧 import 路径失效 |
在ingest.py 保留兼容 import:from src.core.splitters.markdown import MarkdownSplitter |