上一篇文章聊了一个多模态 RAG 中遇到的问题图片明明被检索到最终回答却可能没有正确展示。这次想把视角往前移聊聊图片是如何被检索到的。在项目中我通过 MinerU 解析 PDF将图片保存到 MinIO。但图片存下来只是第一步更重要的是当用户用自然语言提问时系统应该如何找到对应的图片我最终采用的方案是Caption VLM 双路文本 Embedding再通过 RRF 融合检索结果。这篇文章就结合实际代码聊聊为什么这么做以及后来重新审视这个方案时的一些思考。一、图片怎么才能被自然语言检索先考虑一张操作手册里的 SSH 配置截图。文档作者可能给它写了一个图注图14 SSH 登录配置但用户的问题可能是如何进行 SSH 网络登录MobaXterm 的 Remote host 在哪里填写SSH 登录默认使用什么端口这些问题可能指向同一张图片却有不同的语义侧重点。如果我们采用普通文本 Embedding 模型就无法直接将原始图片作为输入。一种比较自然的做法是先将图片转换为文字描述再使用文本 Embedding让用户问题与图片描述在同一个文本向量空间中进行匹配。于是我首先考虑使用 VLM 来理解图片内容。但实际文档中还有一份现成的语义信息值得利用图片的原始图注Caption。MinerU 解析 PDF 时可以提取部分图片对应的图注。我认为这部分信息很有价值因为它通常是文档作者对图片用途或整体内容的概括。而 VLM 更擅长补充图片中实际可见的信息例如按钮、命令、界面元素和操作内容。于是我选择同时保留两种语义表示。Caption 主要保留文档赋予图片的语义VLM 则主要补充图片自身的视觉语义。二、Caption VLM 双路 Embedding 怎么实现这里需要先说明双路 Embedding 并不是使用两个视觉模型分别编码图片而是构造两段不同的文本分别调用同一个文本 Embedding 模型。1. Caption 路径这一条路径比较简单。在代码中caption_embedding_text()主要提取原始图注和所属章节路径形成类似下面的文本Source caption: SSH 登录配置 Section path: 使用前的准备 / SSH 网络登录之所以附带章节路径是因为有些图片的图注很短单独一个图片名称可能不足以表达它所属的操作场景。而对于没有原始图注的图片如果仍有章节路径这条路径也可能生成索引只是语义信息相对有限。2. VLM 路径另一条路径更复杂。离线阶段会把图片送入 VLM同时提供原始图注、章节信息和辅助上下文要求模型输出结构化描述。其中重点包括summary图片整体内容visible_text图片中可见的文字key_elements关键界面元素user_action图片直接支持的操作visual_evidence可观察到的视觉事实search_keywords辅助检索的关键词例如一张 SSH 配置截图可能被表示为Summary: MobaXterm SSH 会话配置界面 Visible text: Remote host; SSH; 22 Key elements: 主机地址输入框; 端口输入框 User action: 配置 SSH 远程连接 Keywords: SSH; MobaXterm; 远程登录这里是用于说明结构的示例并非实际 VLM 调用日志。值得注意的是VLM 描述并非完全独立于 Caption。生成时提供了图注和上下文因此两条语义表示可能存在信息重合。3. 分别向量化入库生成两种文本之后核心处理逻辑可以简化为texts { caption: caption_embedding_text(image), vlm: vlm_embedding_text(image), } for route, text in texts.items(): if not text: continue vector (await async_embedding_func([text]))[0] # 将向量及 route、asset_id 等信息写入数据库在项目中向量存储使用 PostgreSQL pgvector图片原文件则通过 MinIO 管理。图片向量表采用的联合主键是(document_id, asset_id, route)因此同一张图片可以对应不同的索引记录但仍然通过相同的asset_id关联到原始图片资产。三、在线阶段两条检索结果如何融合离线索引建好后在线阶段需要解决两个问题第一同一张图片可能同时被两条路径召回。第二两条路径的检索分数不一定适合直接相加。我的处理方式是分路检索、按 Asset ID 合并再使用 RRF 排名融合。1. 两路独立检索用户 Query 首先经过文本 Embedding得到查询向量。随后分别在 Caption 和 VLM 索引中进行余弦相似度检索各自返回 Top-K 候选。在源码的image_route_rows()中就是分别查询route captionroute vlm并记录图片在各自路径中的排名。如果同一个 Asset ID 在两路中都有命中就把两条排名信息合并到同一张图片上。2. 使用 RRF 融合排名RRF 全称 Reciprocal Rank Fusion即倒数排名融合。它主要依据各路的排序位置而不是直接相加原始相似度。项目里的核心逻辑是image_ranks row.get(_image_route_ranks) or {} if image_ranks: score sum( 1 / (RRF_K rank) for rank in image_ranks.values() )其中RRF_K 60。举个简单的例子图片Caption 排名VLM 排名RRF 得分A1未命中0.0164B10100.0286C230.0320注表格是根据公式构造的示例并非实际测试结果。可以发现图片 B 虽然没有在任何一条路径中排第一但因为两路都有命中最终得分仍然高于只在 Caption 中排名第一的图片 A。这也体现了 RRF 的一个特点它会让多个通道共同命中的候选获得累计排名贡献。当初选择这套方案主要是因为我认为 Caption 和 VLM 都具有检索价值不希望提前给某一路设置更高权重。不过RRF 只是图片候选的融合环节。实际系统还会根据问题意图将图片候选与文本、表格候选进行配额组装并不会直接把图片 RRF 排名当成最终图文回答的展示顺序。四、回过头看双路索引一定比单路好吗现在重新审视这个设计我觉得需要考虑两个问题。1. 为什么不直接拼接 Caption 和 VLM 描述如果把两段文本拼在一起再生成一个向量整个流程确实会更简单。但当初我担心的是不同语义信息混在一起后可能难以突出各自的检索重点。例如Caption 偏向图片用途VLM 描述偏向具体界面元素。将它们合并到一个向量中可能使某些细粒度信息的匹配能力受到影响。不过这并不意味着拼接一定更差。对于一张语义比较集中的图片拼接后的文本可能同样连贯而且一个向量就能覆盖大部分查询。双路索引更明确的价值是保留了两种语义表示的独立检索入口而不是已经证明它具有更高准确率。2. 通用性和检索质量如何平衡实际知识库可能接入不同类型的文档。技术手册中的图片可能只有简短图注学术论文的图注可能非常详细PPT 里的示意图又可能完全没有图注。很难提前判断 Caption 和 VLM 哪一路始终更有价值。因此我当初更倾向于采用统一的双路处理方式尽可能兼顾不同文档的特点。但它也有成本和局限两路 Embedding 意味着额外的向量存储与检索开销VLM 描述也可能遗漏细节或产生错误。另外当前代码中即使缺少有效视觉描述只要存在章节路径VLM 路径也可能构建出一条信息量较低的索引。如果两条路径包含高度重复的章节信息RRF 的共同命中奖励也不一定代表真正获得了互补证据。所以通用性不能简单理解为所有图片都无条件采用两路索引。后续更值得探索的是如何结合图注完整度、视觉描述质量和文档类型判断哪一路值得参与检索。当然也不只有文本化图片这一种方案。视觉 Embedding、OCR 文本提取以及先检索文本再关联图片都是值得比较的路线。只是这些方案我目前还没有做过完整的对照实验。最后回顾这套 Caption VLM 双路索引设计我最大的感受是多模态 RAG 中图片能否被准确检索不只是 Embedding 模型的问题更是图片语义如何构建与组织的问题。Caption 提供原始文档中的语义线索VLM 补充视觉内容两路分别建立索引再通过 RRF 融合排序是我当时为了兼顾不同文档而做出的工程选择。但它是否真的优于 Caption Only、VLM Only或者直接拼接文本后生成单向量还需要在相同查询集上进行验证。后续我也想围绕这些方案做一次更具体的检索对照。毕竟能解释一个方案为什么这样设计很重要能通过实验知道它在什么情况下有效同样重要。