document-indexing(文档向量化索引与检索)
触发条件
当用户出现以下意图时使用本 skill:
- "把这份 PDF 索引/向量化/建库"
- "提取 PDF/PPTX/DOC/XLSX 文本"
- "扫描页 OCR"
- "建索引/检索/查询文档"
- "给一批文档建检索系统"
- "文档分块/embedding/TF-IDF"
适用范围
- ✅ 多源文档:PDF(含扫描页)、PPTX、.doc/.docx、.xlsx、图片(JPG/PNG)
- ✅ 中文为主、英文数字混杂的文档(单字分词 + 连续字母数字词)
- ✅ 离线环境(TF-IDF 稀疏向量,无需预训练模型)
- ❌ 不适用:纯英文文档(分词器针对中文优化)、需要 dense embedding 语义检索的场景(见"升级路径")
工具链位置
- 脚本目录:
~/.voyah/skills/document-indexing/scripts/ - 数据目录:
~/.voyah/skills/document-indexing/data/(自动创建) - 依赖:见同目录
package.json,首次使用前需npm install
核心流程(5 阶段)
[1. 提取] 按文件类型选脚本 → 输出 data/docs/<doc>/pages/*.json + *.png
│
▼
[2. 扫描页识别] scan-pages-report.mjs → data/docs/<doc>/scan-pages-to-ocr.json
│
▼
[3. OCR] ocr-scan-pages.mjs → data/docs/<doc>/ocr-results.json(无扫描页可跳过)
│
▼
[4. 分块+合并] chunking.mjs → merge-ocr.mjs → data/docs/<doc>/chunks-merged.json
│
▼
[5. 全局合并+索引] merge-chunks.mjs → index-tfidf.mjs → data/chunks-merged.json + data/index.json
│
▼
[查询] query.mjs "问题" 5 → Top-K 结果(含 sourceDoc/pageNo/chunkId)
阶段 1:提取(按文件类型选脚本)
| 文件类型 | 脚本 | 命令 |
|---|---|---|
extract-pdf.mjs |
node extract-pdf.mjs --pdf=<path> --doc=doc1 [--max=152] |
|
| PPTX | extract-pptx.mjs |
node extract-pptx.mjs --pptx=<path> --doc=doc4 |
| .doc/.docx | extract-doc.mjs |
node extract-doc.mjs --docx=<path> --doc=doc5 |
| .xlsx | extract-xlsx.mjs |
node extract-xlsx.mjs --xlsx=<path> --doc=doc6 |
| 图片 | extract-image.mjs |
node extract-image.mjs --image=<path> --doc=doc7 |
强制 checkpoint:
- 每份文档必须用
--doc=<name>指定独立子目录名(如 doc1, doc2),避免覆盖 --max仅 PDF 需要(大文件分批处理),其他类型自动全量- 输出结构统一:
data/docs/<doc>/pages/page-NNN.json+manifest.json
阶段 2:扫描页识别
node scan-pages-report.mjs --doc=doc1
- 识别"文本极少但含图片"的扫描页
- 输出
data/docs/<doc>/scan-pages-to-ocr.json - 跳过条件:PPTX/.doc/.xlsx/纯文本 PDF 无扫描页,此阶段可跳过直接进入阶段 4
阶段 3:扫描页 OCR
node ocr-scan-pages.mjs --doc=doc1 [--max=152]
- 用 tesseract.js + chi_sim 对扫描页图片做 OCR
- 增量处理:已 OCR 的图片跳过,每 10 张保存一次防中断
- 输出
data/docs/<doc>/ocr-results.json - 跳过条件:无扫描页时跳过(merge-ocr.mjs 会自动处理)
阶段 4:分块 + 合并 OCR
node chunking.mjs --doc=doc1
node merge-ocr.mjs --doc=doc1
- chunking:按段落切分,目标 500 字、重叠 80 字
- merge-ocr:把 OCR 文本附加到对应 chunk;无 OCR 时自动复制 chunks.json → chunks-merged.json
- 输出
data/docs/<doc>/chunks-merged.json
阶段 5:全局合并 + 索引
node merge-chunks.mjs
node index-tfidf.mjs
- merge-chunks:合并所有
data/docs/*/chunks-merged.json,chunkId 加文档前缀(如doc1:p091-c000) - index-tfidf:构建 TF-IDF 索引
- 输出
data/chunks-merged.json+data/index.json
查询
node query.mjs "查询问题" 5
- 返回 Top-K 结果,含 score、sourceDoc、pageNo、chunkId、text 预览
- 支持简体中文查询繁体内容(相似度略低)
完整示例(处理一份新 PDF)
cd ~/.voyah/skills/document-indexing/scripts
# 首次使用:安装依赖
npm install
# 处理 doc1
node extract-pdf.mjs --pdf="C:/path/to/file.pdf" --doc=doc1 --max=152
node scan-pages-report.mjs --doc=doc1
node ocr-scan-pages.mjs --doc=doc1
node chunking.mjs --doc=doc1
node merge-ocr.mjs --doc=doc1
# 处理第二份
node extract-pdf.mjs --pdf="C:/path/to/file2.pdf" --doc=doc2
node scan-pages-report.mjs --doc=doc2
node ocr-scan-pages.mjs --doc=doc2
node chunking.mjs --doc=doc2
node merge-ocr.mjs --doc=doc2
# 重建全局索引
node merge-chunks.mjs
node index-tfidf.mjs
# 查询
node query.mjs "光伏发电项目" 5
关键踩坑(必读)
1. 多文档数据覆盖
- 坑:单目录模式下,处理第二份文档会覆盖第一份
- 解:每份文档用
--doc=<name>指定独立子目录,merge-chunks.mjs 自动加前缀
2. 无扫描页时 merge-ocr 行为
- 坑:无 OCR 文件时 merge-ocr 退出,导致 chunks-merged.json 不生成,后续 merge-chunks 跳过该文档
- 解:merge-ocr.mjs 已修复,无 OCR 时自动复制 chunks.json → chunks-merged.json
3. PPTX HTML 文本污染
- 坑:pptxtojson 的 content 字段是 HTML,直接用会污染 chunks、TF-IDF 被标签词干扰
- 解:extract-pptx.mjs 的 stripHtml 已剥离标签、解码实体;表格 cell.text 也是 HTML,同样处理
4. .doc 旧格式
- 坑:.doc(OLE 二进制)无法用 pdfjs 解析,系统无 LibreOffice/Word
- 解:word-extractor 直接解析二进制,按 ~800 字生成"虚拟页",不提取图片
5. 繁体中文
- TF-IDF 单字分词对繁体同样适用(按 Unicode Unified_Ideograph 判断)
- 简体查询能命中繁体内容,用繁体关键词可提升召回
6. 图片对象异步解析(PDF)
- pdfjs 的图片 XObject 是异步解析的,getOperatorList 之后未必 resolved
- extract-pdf.mjs 的 getImageObject 用 callback + 8 秒超时兜底
升级路径(可选)
- Dense Embedding:有网络时可换
vectra+TransformersEmbeddings(Xenova/all-MiniLM-L6-v2) - OCR 升级:图片预处理(二值化、放大)+
chi_sim_best或 PaddleOCR / 多模态大模型 - 服务化:query.mjs 包装为 HTTP API 或飞书 bot
与其他 skill 协作
official-document-generationskill 在"建数据字典"阶段可调用query.mjs从已索引文档中检索关键数据- 查询结果含 sourceDoc/pageNo,便于在正文中标注数据来源