从一次真实的图文问答测试出发,聊聊多模态 RAG 中的图文召回、证据选择、LLM 图片引用,以及一些工程实践中的思考。
0. 一个让我重新思考多模态 RAG 的问题
之前在做多模态 RAG 的过程中,我一直比较关注检索效果。
毕竟,如果连正确的图片都检索不到,后面的图文问答自然也无从谈起。
所以在开发过程中,我折腾过不少图文召回方案:统一 Top-K、分模态检索、意图动态配额,以及通过文本关联图片等。
但后来遇到的一类现象,让我发现自己可能把问题想得太简单了。
有些时候,相关图片已经进入检索结果,最终生成的答案却依然没有展示它们。
举个实际测试时遇到的例子。
我在实习中参与过企业内部知识库的多模态 RAG 建设,涉及从离线文档解析、数据清洗、向量化入库,到在线检索和图文回答的完整链路。
为了测试系统效果,我找了一些图文并茂的公开 PDF,主要是软件操作手册、开发板使用手册这类文档。
其中有一份《EVM-RK3588 用户手册》。
我向系统提了一个很简单的问题:
「如何进行 SSH 网络登录?」
原文相关内容在 PDF 第 12~13 页,涉及查询开发板 IP、配置 SSH 会话和验证登录三个环节,并配有三张相关截图。
然而,最终生成的答案只展示了其中两张:SSH 登录配置界面和登录成功界面。
查询 IP 地址的终端截图没有出现在回答中。
有意思的是,回答中的文字并没有明显偏离原文,SSH 登录的核心步骤基本都介绍到了。
如果只是想知道怎么登录,这个答案其实已经具备一定的实用性。
但如果我们的目标是根据操作手册生成相对完整的图文教程,那么这样的输出还存在改进空间。
而且,这种现象并不只在这一个问题上出现过。
在之前的测试中,我也遇到过相关图片已经进入 Top-K,但最后只有部分图片被展示的情况。
一开始我很自然地把注意力放在检索上。
是不是 Top-K 太小?是不是图片被文本挤掉了?是不是需要给图片分配更多候选名额?
但在尝试不同方案之后,我逐渐意识到:
多模态 RAG 的图片检索和最终图片展示,其实是两个不同的问题。
这篇文章就想结合实际项目中的设计,聊聊一张图片从进入知识库,到最终出现在答案中,需要经历哪些环节,以及为什么单纯调整 Top-K 未必能够解决漏图问题。
1. 我最开始关注的是:怎么把正确的图片召回?
先简单介绍一下项目的整体思路。
系统主要分为离线和在线两个阶段。
离线阶段,我使用 MinerU 解析 PDF,提取其中的文本、图片和表格,再进行清洗、结构化处理和向量化。
图片原文件存储在 MinIO 中,文本及图片的语义表示则通过 Embedding 构建索引,使用 PostgreSQL + pgvector 支持向量检索。
在线阶段,根据用户问题检索相关知识,组织文本和图片证据,再交给 LLM 生成答案。
1.1 图片不是存进 MinIO 就结束了
图片处理是我在离线阶段比较关注的一块。
对于普通文本,清洗、Chunk 划分和向量化之后,就已经具备了基本的语义检索能力。
但图片不太一样。
图片本身存进 MinIO,只是解决了文件存储的问题。用户用自然语言提问时,系统还需要知道这张图片讲了什么、属于哪个章节,以及它与周围文本有什么关系。
因此,我尝试通过 Caption 和 VLM 描述,为图片构建两种语义表示。
Caption 路径主要利用原始图题、章节信息等内容,描述图片在文档中的语义位置。
VLM 路径则利用视觉模型分析图片本身,提取界面文字、关键元素和操作信息等内容。
举个例子,一张 SSH 配置截图:
Caption 可能是“SSH 登录配置”;
VLM 描述则可以进一步补充 MobaXterm、Remote host、用户名、端口等界面信息。
我的理解是,Caption 更关注“这张图在文档中承担什么作用”,VLM 描述更关注“这张图实际包含什么信息”。
这两类信息可以相互补充,增加图片被不同查询命中的机会。
当然,双路索引也意味着额外的处理成本和检索融合复杂度,并不代表它天然优于单路检索。
但至少在设计上,它避免了图片的可检索性完全依赖一条简短图注。
1.2 从统一 Top-K 到动态配额
到了在线阶段,我又遇到一个问题:
文本和图片应该怎么一起召回?
我前后探索过几种思路。
| 召回策略 | 核心思路 | 主要取舍 |
|---|---|---|
| 统一 Top-K | 图文候选统一排序 | 简单,但可能出现模态间候选挤占 |
| 分模态 Top-K | 文本和图片分别召回 | 能控制模态比例,但固定配额不够灵活 |
| 意图动态配额 | 根据问题类型调整图文数量 | 更灵活,但依赖意图判断 |
| 文本关联图片 | 先召回文本,再查找关联图片 | 适合图文关联明确的文档,但受文本命中效果影响 |
这里还有一个容易忽略的细节。
不同检索通道返回的相似度分数,并不一定具有直接可比性。
如果文本和图片描述使用不同的 Embedding 模型,就不能简单地把分数混合排序。即使使用同一个模型,不同类型文本的分数分布也可能存在差异。
因此,统一 Top-K 看起来简单,实际也需要考虑分数融合和候选分布的问题。
后来我又尝试根据问题类型分配不同的图文配额。
比如对于“如何配置 SSH”这样的操作类问题,希望保留更多截图;对于“SSH 和 Telnet 有什么区别”这样的概念解释问题,则不一定需要很多图片。
这些方案各有适用场景,也确实能从不同角度改善候选的组织方式。
但它们主要解决的其实是同一个问题:
如何让相关图片进入最终检索上下文。
而在这之后,还有另外一道关卡。
2. 图片进入 Top-K,为什么最终还是可能不展示?
这是我认为整个问题里最值得深入拆解的部分。
最开始,我习惯把图文问答简化成这样一条链路:
检索图片 → 把图片信息给 LLM → LLM 输出图文答案。
但实际实现远没有这么直接。
以我的项目为例,从初始检索到最终展示,中间至少有五个阶段。
第一阶段:初始检索命中
用户问题首先与文本、Caption、VLM 描述等检索表示进行语义匹配。
在这一阶段,图片可能通过 Caption 路径命中,也可能通过 VLM 描述路径命中。
但此时得到的只是初始候选,并不代表这些图片最终都能进入模型上下文。
第二阶段:候选配额组装
系统需要综合文本、图片、表格候选,根据问题类型、Top-K 预算等条件,决定保留哪些结果。
例如,某张图片虽然进入了初始候选,但在最终 Top-K 组装时因为排序较低或配额不足被排除。
那么后面的 LLM 自然也无法引用它。
第三阶段:图片证据过滤
即使图片通过了 Top-K,也可能需要进一步检查。
例如:
图片是否具有有效的存储信息;
相似度是否满足相关性阈值;
图片描述是否满足必要的匹配规则;
图片是否属于当前允许引用的证据集合。
如果没有通过过滤,图片同样无法进入最终的可引用集合。
这里要特别注意:
“初始召回图片”“最终 Top-K 中的图片”和“允许 LLM 引用的图片”,可能是三个不同的集合。
第四阶段:LLM 选择引用
假设相关图片已经通过了前面的筛选,并且作为证据提供给了 LLM。
这时问题就变成了:
模型会不会在答案中主动引用它?
在我的实现中,LLM 需要根据图片对应的文本证据判断是否有必要引用,再输出约定好的图片引用标记。
即使某张图片是正确的,如果模型最终没有生成对应引用,图片依然不会出现。
第五阶段:后端引用解析与渲染
最后,后端需要解析模型输出中的图片标记,查找对应的图片资源,并替换为真正的图片地址。
如果标记格式不符合约定、Asset ID 不合法,或者图片资源不可访问,也可能影响最终展示。
所以,图片没有出现在最终答案里,并不能直接说明向量检索失败。
反过来也一样,图片出现在 Top-K 里,并不代表系统已经完成了全部工作。
3. 一个具体实现:通过 IMAGE_REF 控制图片引用
为了让图片引用更加可控,我的项目没有让 LLM 直接生成图片 URL,而是使用 Asset ID 作为中间标识。
整体思路比较简单。
每张图片在入库时都关联一个可以定位原始资源的标识,例如:
image_001
在构建 Prompt 时,将图片的 Caption、VLM 描述等文本化证据,以及对应的 Asset ID 提供给 LLM。
然后要求模型在引用图片时,输出特定格式的标记:
[IMAGE_REF: image_001]
后端再根据这个 ID 查找实际图片地址,例如:
[IMAGE_REF: image_001]
转换为:

这样做的好处是,LLM 不需要自己生成存储地址。
图片的身份、资源地址以及最终渲染,都由程序进行管理。
但问题也恰恰出在这里。
3.1 当模型没有输出 IMAGE_REF,会发生什么?
假设检索后,系统得到三张可引用图片:
allowed_image_ids = {image_001, image_002, image_003}
其中分别对应:
image_001:查询开发板 IP;image_002:配置 SSH 会话;image_003:SSH 登录成功。
但 LLM 最终只输出:
[IMAGE_REF: image_002]
[IMAGE_REF: image_003]
那么后端即使知道还有一张image_001,也不能凭空判断它应该插入哪段回答。
对于基于模型引用标记的渲染机制,最终展示的图片一般取决于模型实际生成的引用,以及后端允许引用的集合。
可以简化理解为:
最终展示图片 = 模型引用图片 ∩ 合法图片集合
当然,实际实现还需要经过标记解析、资源校验和 URL 替换。
这就解释了一个现象:
检索模块负责提供候选图片,但真正决定哪些候选图片被写进答案的,还包括生成和渲染环节。
3.2 为什么不能直接让 LLM 引用所有图片?
看到这里,一个很自然的想法是:
既然相关图片已经提供给模型,为什么不在 Prompt 中直接要求它把所有图片都展示出来?
问题在于,检索相关不等于回答必需。
例如用户问:
“SSH 协议是什么?”
系统可能召回多张 SSH 配置界面截图,但这些图片对于解释协议原理的帮助非常有限。
如果强制展示,就容易出现大量冗余配图。
反过来,对于“请根据手册完整介绍 SSH 登录步骤”,每个关键操作可能都值得保留对应截图。
因此,最终图片数量不应该只由检索结果数量决定,而应该结合用户的问题类型和证据作用来判断。
另外,我之前为了避免乱配图,也在 Prompt 中对引用行为做过一些约束,例如图片需要直接支持对应步骤、避免重复引用等。
这些约束有助于控制无关图片,但未必能保证关键操作截图的完整覆盖。
这里实际上存在两个不同的目标:
图片引用准确性:展示出来的图片是否真正支持答案?
必要图片覆盖率:那些确实应该展示的图片,最终有没有被遗漏?
这两个目标不能简单混为一谈。
4. 强制绑定图文,能不能解决这个问题?
在探索过程中,我也尝试过一个比较直接的思路:
既然图片和文本有对应关系,那就把图片和相关文本绑定起来。
例如,一段操作步骤命中之后,直接将对应的图片一起返回。
对于操作手册这种图文关联比较明确的文档,这种方式确实能够缓解部分漏图问题。
但很快我又发现,它并不适合所有场景。
4.1 操作类问题比较适合图文关联
例如:
“如何设置 SSH 连接?”
这类问题有明确的操作步骤。
如果步骤一对应图片 A、步骤二对应图片 B,那么根据步骤关联图片,是比较合理的做法。
系统可以减少对 LLM 自主选择图片的依赖。
4.2 跨章节问题可能出现冗余配图
但如果用户问:
“这个开发板有哪些远程管理方式?分别适合什么场景?”
这个问题可能需要检索多个章节,综合串口登录、SSH 登录等不同内容。
如果直接把命中文本所关联的所有图片都展示出来,很容易出现大量截图,而用户真正需要的是不同方式的比较和解释。
对于一些比较宽泛的问题,强制绑定图文同样不够合理。
因此,我不太认可“每召回一个相关 Chunk,就把所有关联图片全部展示”这样的通用方案。
它提高了图片展示的确定性,却可能牺牲答案的简洁性和相关性。
5. 一个我更想尝试的方向:证据选择与答案生成解耦
后来我开始思考,是否可以换一种方式。
不要把“哪些图片应该展示”和“答案应该怎么写”完全交给一次自由生成。
而是先进行证据规划,再组织自然语言答案。
这个思路并不复杂。
第一步:先确定回答需要覆盖什么
例如对于操作指导类问题,可以先抽取需要覆盖的关键操作步骤。
SSH 登录这个例子里,可以得到:
查询开发板 IP;
配置 SSH 会话;
验证登录成功。
第二步:为步骤关联证据
对每个步骤,分别选择对应的文本和图片证据。
例如:
| 操作步骤 | 文本证据 | 图片证据 |
|---|---|---|
| 查询 IP | 网络连接与 IP 查询说明 | image_001 |
| 配置 SSH | MobaXterm 配置说明 | image_002 |
| 验证登录 | SSH 登录成功说明 | image_003 |
这里的 ID 是示意,实际应该使用系统中可追溯的 Asset ID。
如果某一步没有充分的图片证据,可以不分配图片,也可以触发补充检索,而不是强行指定一张看起来相关的截图。
第三步:生成答案并校验证据
将规划好的步骤及其证据交给 LLM 生成回答。
后端再检查:
引用的 Asset ID 是否存在;
图片是否属于允许使用的证据;
关键步骤是否遗漏;
必要图片是否被正确关联;
是否出现重复或无关引用。
最后由后端负责把图片插入到对应步骤附近。
这种方式的核心,是将原本混合在一次生成中的两个任务拆开:
先决定“什么证据应该用于回答”,再决定“怎样把回答组织得自然”。
相对于完全依赖 LLM 自主输出IMAGE_REF,这种设计至少可以让图片选择过程变得更容易检查和追踪。
但它也会引入额外的证据规划成本,并且不保证规划结果一定正确。
对于跨章节问题、开放性问题和概念解释问题,证据规划也需要采用不同的策略。
所以这更像是我接下来希望进一步尝试和验证的工程方向,而不是一个已经被证明的最优解。
6. 多模态 RAG 的图文效果,到底应该怎么评测?
做这些探索时,我还遇到了另一个问题:
我们到底应该用什么标准判断一张图片有没有必要出现在答案里?
以前测试时,很多时候会直接查看最终回答,主观判断图片是否完整、位置是否合理。
但这种方式很难支撑系统性的方案对比。
例如文章开头的 SSH 案例。
原文有三张相关截图,最终只输出了两张。
如果我们把三张截图全部标注为必要证据,那么图片覆盖率就是 2/3。
但如果某个用户只是想快速了解 SSH 连接方式,那么查询 IP 的截图可能并非必需。
这两种评判方式会得到不同的结论。
因此,我觉得评测至少应该区分几个维度:
| 评测维度 | 关注的问题 |
|---|---|
| 图片召回率 | 应当检索的图片有没有被找到? |
| 图片引用覆盖率 | 必要的图片最终展示了多少? |
| 图片引用精确率 | 展示的图片有多少真正支持答案? |
| 关键步骤覆盖率 | 操作流程是否遗漏重要步骤? |
其中,图片引用覆盖率和精确率都需要建立相对明确的人工标注标准。
仅仅知道一张图片来自相关章节,还不足以判断它是否属于必需证据。
另外,如果要评测不同召回方案,也不能只看最后生成的图片数量,还需要检查中间候选集合。
否则就很容易把检索、证据过滤和 LLM 引用阶段的问题混在一起。
对于这篇文章中的 SSH 案例,目前我只能确认最终回答少展示了一张相关截图,并不能仅凭结果确定它究竟在哪个环节被排除。
这也是我接下来希望进一步完善的部分:让整个图文引用链路更可观测,而不是只看最终答案猜测原因。
7. 最后,几点实际做下来才有的感受
回头看这段多模态 RAG 的实践,我发现自己最开始有些过于关注召回策略了。
因为检索是最容易被单独观察和调整的一环。
Top-K 不合适,就调整 K;图片太少,就给图片增加配额;某些问题召回不稳定,就尝试动态分配或者关联检索。
这些优化并没有错。
但后来我发现,一个图文问答系统的最终效果,取决于多层不同的工程决策:
离线阶段决定我们保存了什么样的知识。
检索阶段决定哪些证据能够进入候选集合。
生成阶段决定模型最终使用哪些证据。
渲染阶段决定用户最后看到了什么。
每个环节都可能导致图片遗漏,也可能引入错误配图。
所以我现在依然很重视 MinerU 解析之后的数据清洗、图文关联,以及 Caption/VLM 描述的质量。
但同样不认为,只要离线阶段的数据足够好,就能自动解决所有在线生成问题。
对于操作手册类问答,我更希望答案能够覆盖关键操作和必要截图。
而对于概念性、综合性问题,我更希望模型能够选择真正有用的视觉证据,而不是机械地展示所有相关图片。
多模态 RAG 真正难的,也许不是把图片检索出来,而是让系统知道哪些图片值得被使用,以及怎样可靠地使用它们。
这是我目前做下来比较深刻的一个感受。
当然,很多方案还需要更系统的实验验证,这篇文章更多是在分享自己遇到的问题和工程思考。
后面也准备继续围绕 MinerU 文档解析、Caption/VLM 双路索引、图文召回策略和 RAG 评测做一些更深入的实践。
如果你也做过多模态 RAG,比较好奇你们是怎么处理最终图片引用的:
是让 LLM 自主选择图片,还是在业务层提前确定图片与文本的对应关系?
理解有限,欢迎大佬指正。