联犀知识库如何读懂 Word、Excel、PDF 与图片:多模态检索与关联实践
约 5496 字大约 18 分钟
2026-07-11
引子:一张表格引发的搜索失败
假设你在知识库里上传了一份《混凝土抗压强度检测标准》的 PDF,里面有一张表格,列出了不同标号混凝土的标准抗压强度。用户问:"C30 混凝土 28 天抗压强度标准值是多少?"
纯文本 RAG 的做法是把 PDF 抽成文字,再切成一段段小文本(俗称 chunk)做向量检索。问题来了:表格被抽成文字后,行和列的对齐关系丢了。"C30" 和 "30.0 MPa" 可能出现在两个不相邻的 chunk 里,向量检索只召回其中一个,模型要么答错,要么答不出来。
这不是 embedding 模型不够强的问题。即使你换用更大的模型、更好的向量库,输入本身已经丢失了"表格结构"和"模态语义"。图片、公式、扫描件同理。多模态知识库要做的,不是让检索模型更聪明,而是让输入保留足够多的原始信息。
一句话总结:传统 RAG 把 PDF 当成"长字符串"读,多模态 RAG 则把它当成"带图、带表、带公式、带结构"的真实文档来读。
术语速查
阅读前先快速了解几个核心概念:
| 术语 | 含义 |
|---|---|
| RAG | 检索增强生成,先从知识库找到相关内容,再交给 AI 生成回答。 |
| 多模态 | 文本、图片、表格、公式等不同信息形态的统称。 |
| chunk | 知识库检索的最小单元,一段有独立语义的小文本。 |
| embedding / 向量 | 语义的压缩编码,意思相近的内容在向量空间里距离更近。 |
| content_list | 联犀统一的文档中间格式,把各种文件都转成带类型、位置、顺序的元素数组。 |
| artifact | 知识库里的非文本媒体对象,如图片、表格截图、公式渲染图。 |
| Docling | 一个开源文档解析器,能把复杂版式 PDF/Word/Excel 解析成结构化数据。 |
| VLM | 视觉语言模型,能同时看懂图片和理解文字,如 GPT-4V、Qwen-VL。 |
| 跨模态关系 | 不同模态内容之间的关联,例如"这段文字描述了那张图"。 |
为什么知识库必须支持多模态
企业知识库里的文档从来不是纯文本:规范标准里有表格,作业指导书里有流程图和照片,检测报告是 Excel/PDF,论文里有公式和实验图。纯文本处理会带来三种失效:丢信息、丢结构、丢关联。支持多模态不是炫技,而是让知识库能处理真实业务文档。
多模态 RAG 到底难在哪里
把 Word、Excel、PDF、图片接入知识库,至少要在四个层面解决问题:
解析层。不同格式解析结果差异极大:PDF 可能是扫描图,Excel 有多个 sheet 和合并单元格,Word 里图文混排。
表示层。文本可直接切成 chunk,但图片、表格、公式不行。把图片 OCR 成文字会丢失布局;把表格拍平会丢失行列关系;把公式当成普通字符会丢失数学语义。
关联层。文档里"如图 1 所示"和图 1 必须建立关联;表格数据可能支撑某段观点。没有这些关系,检索到的内容就是孤立碎片。
检索层。用户问"图 1 说明了什么"不能只搜文字;问"表 2 最大值"不能只返回表格所在页。检索必须感知模态意图。
这四个层面环环相扣。任何一环掉链子,多模态 RAG 都会退化成"图片附近有几行字"的朴素版本。
关键决策:先统一协议,再谈多模态
面对这些挑战,联犀做的第一个关键决策不是引入更强大的模型,而是定义一套统一的中间协议:content_list。
它的作用是把"解析"和"索引"解耦。无论前面用的是 Docling、MinerU,还是 Go 本地轻量解析器,最终都输出成同一种结构。下游的切片、向量化、关系构建只面向 content_list,不用关心原始格式。
通俗理解:
content_list把 PDF、Word、Excel 等不同格式统一转成"带类型、带位置、带顺序"的标准描述。
content_list 长什么样
content_list 是一个数组,每个元素代表文档中的一个语义单元。下面是一份更贴近真实文档的示例,包含标题、正文、表格(含 footnote)、图片(含 footnote)、公式和结论段:
[
{
"type": "text",
"text": "混凝土抗压强度检测标准",
"text_level": 1,
"page_idx": 1,
"_order_index": 0,
"_section_path": []
},
{
"type": "text",
"text": "本章规定混凝土立方体抗压强度的试验方法、设备要求和结果判定。",
"text_level": 0,
"page_idx": 1,
"_order_index": 1,
"_section_path": ["混凝土抗压强度检测标准"]
},
{
"type": "table",
"table_body": "| 强度等级 | 标准值(MPa) | 养护龄期(d) |\n|----------|-------------|-------------|\n| C30 | 30.0 | 28 |\n| C40 | 40.0 | 28 |",
"table_caption": "表 1 混凝土抗压强度标准值",
"table_footnote": "数据来源于 GB/T 50081。",
"page_idx": 2,
"_order_index": 2,
"_section_path": ["混凝土抗压强度检测标准", "强度等级"]
},
{
"type": "image",
"img_path": "oss://knowledge/assets/doc-001/image-001.png",
"image_caption": "图 1 标准养护试件示意图",
"image_footnote": "尺寸单位:mm",
"page_idx": 2,
"_order_index": 3,
"_section_path": ["混凝土抗压强度检测标准", "试件制备"]
},
{
"type": "equation",
"latex": "f_{cu}=\\frac{F}{A}",
"text": "混凝土立方体抗压强度等于破坏荷载 F 除以受压面积 A",
"page_idx": 3,
"_order_index": 4,
"_section_path": ["混凝土抗压强度检测标准", "计算公式"]
},
{
"type": "text",
"text": "试验结果应精确到 0.1 MPa,以三个试件测值的算术平均值作为该组试件的抗压强度。",
"text_level": 0,
"page_idx": 3,
"_order_index": 5,
"_section_path": ["混凝土抗压强度检测标准", "结果判定"]
}
]每个元素都包含四类信息:类型与内容(type、text、table_body、img_path、latex)、位置信息(page_idx、_bbox)、阅读顺序(_order_index)和章节路径(_section_path)。
这个协议是解析器和索引器之间的契约,既不是 Docling 的私有格式,也不是某个模型的输入格式。只要解析器能输出 content_list,就可以接入联犀的知识库链路。RAG-Anything 也采用了类似的抽象,这是多模态 RAG 框架的共识:先把不同模态拍平成统一的原子单元,再谈后续处理。
解析层:可选、可降级、不绑架主链路
联犀把 Docling 作为可选的高级解析服务,但默认不启用。系统通过一个 Parser Gateway 做路由决策:
- 全局开关:
AdvancedParser.Enabled控制整个服务是否允许调用 Docling。 - 知识库开关:创建/编辑知识库时可以选择是否启用高级解析。
- 文件级路由:按扩展名、文件大小、并发槽位、超时时间决定是否进入 Docling。
如果任一条件不满足,或者 Docling 调用超时、队列已满、返回错误,系统都会自动降级到 Go 本地轻量解析。导入不会失败,只是失去版式分析能力。
为什么默认不启用 Docling?
因为成本。Docling CPU 镜像包含版面分析、OCR、表格识别等模型,体积在 8 GB 以上;首次启动需要下载模型权重;多副本运行时每个容器独立加载模型,内存不共享。对于只有简单文本的文档,Go 本地解析毫秒级即可完成,没必要付出这个代价。
从实测数据来看,Docling 解析复杂版式文档秒级,Go 本地解析毫秒级。Parser Gateway 让系统只在值得时启用 Docling。
类比:Docling 像"工业 CT",能看清复杂结构但成本高;Go 本地解析像"普通尺子",测直线很快。Parser Gateway 就是按需选择尺子或 CT。
Go 本地解析能做什么,不能做什么
Go 本地解析适合处理结构清晰的电子文档:按段落提取 Word 文本、按 sheet/行读取 Excel、按文本流提取 PDF、直接拆分 Markdown/TXT。
但它做不了:复杂版式分析(多栏 PDF、图文混排)、表格结构识别(合并单元格、跨行跨列)、图片语义描述、公式识别、扫描件 OCR。
这些能力正是 Docling / MinerU 等 Python 解析器的强项。Python 生态在版式分析、OCR、表格识别、公式识别上有大量成熟模型和工具链,而 Go 生态在这些领域几乎空白。因此联犀的策略不是用 Go 重写解析器,而是把解析器作为可插拔服务:Go 负责工程化调度,Python 负责版式理解。
索引层:把图、表、公式变成可检索的 chunk
content_list 进入索引链路后,不同类型的元素会生成不同的 chunk。
文本:段落和标题按原有方式切片,_section_path 作为附加元数据写入,保证同一章节的 chunk 可以被一起召回。
表格:保留 Markdown 格式的表格体,同时生成 caption 和 VLM 描述 chunk。复杂表格识别后可能出现重复列或空单元格,因此增加了后处理:去除空行空列、合并相邻重复列、补充分隔行。表格原图也会作为 artifact(非文本媒体对象)保留,用于详情页展示和兜底检索。
图片:根据配置选择两种模式。placeholder 模式下只保留 caption 和位置信息,不额外消耗存储;embedded 模式下,图片 base64 被上传到对象存储,img_path 被替换为对象存储路径。图片还会调用 VLM 生成详细描述,描述文本作为 chunk 入向量库。
公式:保留 LaTeX 源码,并生成自然语言解释 chunk。这样用户搜索"公式中 (f_{cu}) 的含义"时,能被解释性文本召回。
多模态描述生成:上下文比模型更重要
多模态内容如果没有上下文,描述很容易泛泛而谈。RAG-Anything 的 ContextExtractor 会为每个模态单元提取邻居信息:前后页文本、相邻文本单元、标题和 caption,再拼入 prompt。联犀吸收了这一思想:为图片/表格/公式生成描述时,会注入其章节路径、邻近文本和 caption,让同一张图片在不同章节得到不同侧重的描述。描述生成不是默认开启的,成本敏感场景下可先只把 caption / table_body / latex 作为 chunk 入向量库。
跨模态关系
光有 chunk 还不够,必须保留它们之间的关系。联犀在 chunk 关系表中扩展了六类关系:
| 关系类型 | 含义 | 示例 |
|---|---|---|
describes | chunk 描述某个 artifact | 文本段落描述图 1 |
illustrates | image 解释某段文本 | 图 1 说明某段工艺流程 |
contains_data | table 包含某些实体数据 | 表 1 包含 C30/C40 强度数据 |
represents | equation 表示某个概念 | 公式表示抗压强度计算方式 |
next_to | 阅读顺序相邻 | 段落 A 和下一段落 B |
belongs_to | chunk 属于某个章节 | 某段属于"试验方法"章节 |
这些关系让检索不再只依赖向量相似度。当系统召回一个图片 chunk 时,可以同时把描述它的文本 chunk、同章节的上下文 chunk 一起带出来。这与 RAG-Anything 的双图索引思想一致:文本部分走文本关系图,非文本部分走跨模态关系图,最后通过实体名称对齐融合。
一句话理解:跨模态关系就像给文档画了一张"关系地图",让系统知道"这段文字在说这张图"、"这个表格包含那个概念的数据"。
高效索引:三层索引结构
为了同时支持语义检索和结构导航,联犀采用三层索引。可以把它们理解为一家图书馆的三套检索系统:一套按"意思相近"找书,一套按"关键词"找书,一套按"书架位置"找书。
稠密向量索引。每个 chunk 生成一个 embedding(语义向量),存入 pgvector。HNSW 索引保证大规模知识库下的检索效率。图片/表格/公式的描述 chunk、caption chunk、表格体 chunk 都进入同一向量空间,保证查询向量与索引向量语义可比。
embedding 是什么? 可以理解为"语义的压缩编码"。两句话意思越近,它们的 embedding 在向量空间里就越近,检索时找"意思最接近"的内容。
关键词索引。对每个 chunk 提取关键词,存入 keywords TEXT[] 字段并建立 GIN 索引。关键词召回对专有名词、型号编号、标准代号特别有效,能弥补向量检索在精确匹配上的不足。
chunk 关系图谱。基于 next_to、belongs_to、describes 等关系构建图结构,检索时沿关系扩展邻居 chunk,补全被切片打断的上下文。RAG-Anything 的双图索引把"文本知识图"和"跨模态知识图"分开构建再合并;联犀当前以 chunk 级关系图为主,逐步向实体级关系图演进。
如何关联检索:跨模态关系与多路召回
多模态检索的关键,是让用户用自然语言问图、表、公式相关问题时,系统能召回正确的模态内容,并补齐上下文。
模态意图识别
查询里出现"图、图片、figure、表、表格、table、公式、equation"等词时,系统会判断用户可能在找某种模态的内容,并在首轮召回里提升对应 chunk_type 的权重。这一步成本很低,但能显著改善"搜表只返回正文"的问题。
三路召回
联犀的检索链路包含三路灯,可以理解为三个侦探同时查案,最后把线索汇总:
- 稠密向量召回:基于 pgvector,用 embedding 找语义相似的 chunk。它负责"意思相近"。
- 关键词召回:基于
keywords TEXT[]数组和 GIN 索引,找关键词重叠的 chunk。它负责"精确命中"。 - chunk 关系图谱扩展:沿
describes、next_to、belongs_to等关系扩展邻居 chunk,补全上下文。它负责"找回被切开的段落"。
三路灯的结果用 Reciprocal Rank Fusion(RRF)融合,避免单一召回路径的短板。同一 chunk 只要在任意一路中排名靠前,就有机会进入最终结果。
RRF 是什么? 一种"投票机制":每个路径都给候选 chunk 一个排名,再换算成综合得分。只要某个 chunk 在任意路径里排名靠前,就有机会进入最终结果。
查询期 VLM 反引用
如果首轮召回命中的是图片 artifact,系统会把图片的原图数据送回 VLM,让它结合具体问题生成增强描述。这比单纯依赖编译期生成的图片描述更准确,因为不同问题关注图片的不同细节。
例如,同一张"标准养护试件"图片:问"图 1 是什么",VLM 回答"这是标准养护试件示意图";问"图 1 中的试件尺寸是多少",VLM 会聚焦尺寸标注。这一步只在确实命中图片时触发,并配有时长和配额限制,避免成本和延迟失控。
一个检索过程的例子:用户问"图 1 说明了什么?",系统先识别到要找图片,再用向量召回命中"图 1"的图片 chunk,接着沿
describes找到描述它的文本、沿belongs_to找到同章节上下文,最后把这些内容一起交给模型生成回答。
增量更新:内容 hash 与局部重编
大文档更新时,如果只看文件名或修改时间,容易误判:用户可能只是移动了文件位置,系统却触发全量重编;或者用户只改了一页内容,系统却重新处理整份文档。
联犀为每个文档计算一个 ContentListHash,基于 content_list 和文本摘要生成。同名文件更新时比较新/旧 hash:
- hash 相同:内容没有变化,只同步目录结构,跳过重编。
- hash 不同:内容变化,清理旧的 artifact、chunk、关系、向量,重新编译。
更细粒度的增量更新可基于 element fingerprint:对每个 content_list 元素计算 page_idx + order_index + content hash 的组合指纹。文档删除时也会同步清理该文档下的所有 artifact 与 chunk 关系,避免索引残留无效数据。这样重复上传相同文件不会触发编译,只有真正修改内容的文件才会重编。
工程控制:开关、限流与成本
多模态解析带来能力的同时也带来了成本。联犀通过三层控制降低风险:全局开关控制是否允许调用 Docling;知识库开关让单个知识库自主选择;文件级路由按扩展名、大小、并发、超时动态决策。
Docling 服务通过 Docker 容器化部署,aisvr 侧通过信号量控制并发,队列满时自动降级到 Go 本地解析,既保护 Docling 服务,也保证导入流程不中断。VLM/LLM 描述生成只在需要时触发,并配有时长和配额限制。
与 RAG-Anything、Docling 的对比
RAG-Anything 是一个完整的多模态 RAG 框架,把解析、描述、索引、检索耦合在一起;Docling 是一个强大的文档解析器;联犀则是把 Docling 的解析能力和 RAG-Anything 的多模态索引思想,落地到现有的 Go + pgvector 工程体系中。
| 维度 | RAG-Anything | Docling | 联犀落地方案 |
|---|---|---|---|
| 定位 | 端到端多模态 RAG 框架 | 文档解析器 | Agent 可调用的业务知识库 |
| 解析器 | MinerU/Docling/PaddleOCR | 自身 | Docling(可选)+ Go 本地解析 |
| 索引 | 双图 + 稠密向量表 | 仅输出结构化 JSON | pgvector + chunk 关系图 |
| 检索 | 图导航 + 语义匹配 | 不做检索 | 向量 + 关键词 + 图谱 + Rerank |
| VLM 反引用 | 支持 | 不做 | 可选 |
| 部署 | Python 独立服务 | Python 容器 | 无状态 Docker 容器,可选启用 |
| 多租户 | 需自行实现 | 无 | 原生租户隔离 |
核心结论是:不把 RAG-Anything 整体搬进来,只吸收其 content_list 抽象、ModalProcessor 上下文思想、双图索引思想和 VLM 反引用思想;解析层用 Docling,索引/检索层复用联犀现有链路。 这样既能获得多模态能力,又不引入完整的 Python RAG 框架,降低异构系统的运维复杂度。
效果、边界与未解问题
从落地结果看,这套方案让复杂版式 PDF、Word、Excel 能够稳定解析,图片、表格、公式正式进入检索链路,搜索可以召回对应模态的内容。Docling 作为可选插件,不对不启用它的知识库产生任何影响。
但仍有几件事需要继续调优:
- embedding 阈值:短文本/表格描述的向量得分偏低,默认阈值可能过滤掉它们。
- 复杂版式后处理:Docling 对复杂 Excel 可能出现重复列、空列,需要后处理或兜底。
- 旧版 Office 格式:
.doc和.xls当前不支持,需确定提示转换或后端转换兜底。 - 成本测算:Docling 容器资源、VLM/LLM token 消耗、大规模文档 P95 耗时仍需测算。
- 多模态效果验证:需构造含图/表/公式的测试集,对比启用 Docling 前后的真实召回与问答准确率。
关键决策总结
- 统一协议先于多模态算法。没有
content_list这样的中间层,解析器和索引器会互相绑架,每新增一种格式都要改整条链路。 - 高级解析器是可选插件,不是默认路径。Docling 能力强但成本高,只有复杂版式文档才值得启用,简单文档继续走轻量解析。
- 不保存原始 JSON,只保留 content_list。Docling 的原始输出很重,落盘会造成对象存储压力;内存中转后只保留结构化结果和媒体资源。
- 跨模态关系是检索质量的放大器,不是替代品。向量召回仍然是主力,关系扩展用来补全上下文,两者结合才能回答多模态问题。
- 多模态描述生成按需触发,控制成本。VLM 不是默认调用,而是在编译期和查询期按条件触发,避免能力变成负担。
- 渐进式吸收,不照搬框架。RAG-Anything 的双图索引、ModalProcessor、VLM 反引用思想被吸收,但落地到联犀现有 Go + pgvector 架构,避免引入完整 Python 框架。
多模态知识库让 AI 能处理真实世界里本来就复杂的内容。联犀的思路是:用统一协议隔离格式差异,用可选解析器承担版式分析成本,用跨模态关系和多路召回把图、表、公式纳入检索链路。
更新日志
2026/7/13 13:48
查看所有更新日志
74246-Merge #8 into master from feature/blog-multimodal-kb-expand于2fec5-docs(blog): 重写第26篇多模态知识库博客,增强技术分析深度于273bb-docs(blog): 新增联犀知识库多模态检索与关联实践技术博客于
