做 RAG 的人大多有个盲区:眼睛只盯着文字。
你花了两周调 chunk size,换了三种 embedding 模型,reranker 也加上了——《Rerank 在 RAG 中的角色:Bi-Encoder vs Cross-Encoder》里讲的那些优化你全做了。但回头一看,技术文档里的截图、架构图、参数表格,这些图片承载的信息,你的系统压根没碰。
这不是小问题。kapa.ai 的团队最近在 Hacker News 上分享了他们的生产实践(89 points),标题直白:How we index images for RAG。他们服务 200+ 客户,知识库里有数百万张图片,每天处理百万级查询。这篇文章把他们的方案拆开来看,顺便聊聊 ColPali 等另一条路线,以及你到底该选哪条。
图片在技术文档里到底干了什么
kapa.ai 把文档图片分成两类,这个分类非常实用。
说明型(illustrative):文字说"点击设置图标",旁边的截图标出了图标在哪、长什么样。图片让答案更直观,但信息本身在文字里也有——用户不看图也能做事,只是要多找一会儿。
承重型(load-bearing):接线图、参数规格表、认证矩阵、颜色可选表——这些图里的数值只存在于图片中。你不看图,就根本不知道答案。
他们跑了上千条真实客户问题验证:有图片上下文时,LLM 评委(McNemar 检验,p < 0.05)显著偏好带图片的答案。效果不是微调能弥补的那种——是用户能感知到的质变。
但问题来了:怎么把这些图片喂给 LLM?
查询时看图:一个看起来对、实际上贵到用不起的方案
直觉反应是:检索到相关 chunk 后,把它们引用的图片一起丢给多模态模型(GPT-5.1、Claude 4.6 Sonnet 等)。kapa.ai 测试了这个方案,发现三个结构性问题:
1. 贵得离谱。 原始图片让 GPT 单次查询成本增加 27%,Claude 增加 51%(Claude 把一张图编码成约 975 tokens,GPT 约 716)。kapa.ai 每天百万级查询,这笔钱根本花不起。
2. 塞不进去。 一个典型问题检索 10-30 个 chunk,平均引用 20-30 张图片,长尾能到 130 张。Claude 的 payload 上限 30 MB,OpenAI 的 50 MB——超过 20 张图就撞墙了。
3. CLIP 式嵌入在技术文档上失效。 CLIP 这类视觉嵌入模型擅长的是"这是一只猫"还是"这是一条狗",不是"这个表格第三行的耐火等级是多少"。短技术查询(“how do I configure X”)和图表细节之间,语义鸿沟太大。
kapa.ai 的结论很干脆:这些不是工程细节能调掉的问题,是今天多模态生态的结构性限制。于是他们换了一条路。
索引时描述一次,查询时当文字检索
核心思路反过来:重活在索引时干一次,查询时完全不碰图片。
流程很清晰:
- 索引阶段:用视觉语言模型(VLM)给每张图片生成一段文字描述(caption)
- 存储:caption 作为独立的文本 chunk 存进向量数据库
- 查询阶段:检索器照常做文本检索,如果 caption chunk 被命中,就把它放进上下文
这样查询时的成本几乎不增加(纯文本检索),延迟也几乎不变。“看图"这个昂贵操作只发生一次——在文档摄入的时候。
听起来简单,但生产环境里有三个关键细节决定了成败。
关键细节一:大多数图片是垃圾,过滤是第一步
你不能给知识库里的每张图都生成描述。Logo、头像、社交预览图、装饰性横幅——这些占了大多数。
kapa.ai 用启发式规则做第一轮过滤(丢掉不支持的格式、过小的图片等),然后用分类器做第二轮过滤。在明确的图片上,分类器准确率 96.8%(F1 0.974),但在模糊图片上准确率暴跌到 59.8%。
原因很根本:一张倒计时截图,你分不清它是装饰性横幅还是功能性内容。图片分类在边界情况下就是很难。
关键细节二:上下文比模型大小更重要
给 VLM 喂图片时,把图片前后的段落一起喂进去,caption 质量会大幅提升。没有上下文时,一个文件上传对话框的描述是"一个有文件选择按钮的网页”;有上下文后,描述变成"项目配置页面中的文件上传对话框,用于导入自定义规则集"。
另一个有意思的发现:贵的模型几乎不值得。 他们测试了五个模型,从 Claude 4.6 Sonnet 到 GPT-5.4 nano。小型模型(GPT-5.4 mini)生成的 caption 和四倍价格的大模型几乎无法区分。
这很符合直觉——描述一张截图不需要博士级推理,需要的是看清图片内容并用准确的语言表述。小模型就够了。
关键细节三:独立存储优于内联替换
两种存储 caption 的方式:
- 内联(inline):替换图片的 alt text,让 caption 和原文混在一起
- 独立(separate):每个 caption 作为单独的 chunk 存储,原文 chunk 保持不变
直觉告诉你会内联更好——caption 和上下文紧挨着嘛。但实际测试中,独立存储在成本和图片使用率上都赢了。
原因:内联 caption 会膨胀每个它所在的 chunk,而这些 chunk 每次查询都要发送。独立 chunk 只在相关时才被检索到,不相关的查询根本不会为它付 token 费。
生产环境数据
端到端测试(三个客户项目,GPT-5.1 和 Claude 4.6 Sonnet):
| 指标 | 纯文本基线 | 加入图片 caption |
|---|---|---|
| 图片被引用比例 | 0% | 10%-64% |
| 答案质量(LLM 评委) | 基线 | 显著更好(p < 0.05) |
| 单次查询成本增加 | — | 1%-6% |
| 延迟(首 token 时间) | — | 亚秒级增加 |
| 图片放置正确率 | — | 94%-99% |
用 1%-6% 的成本换来 10%-64% 的图片引用率提升,且图片放置正确率 94%-99%。这笔账怎么算都划算。
另一条路:ColPali 的视觉检索方案
kapa.ai 的方案是"把图片变成文字再检索"。ColPali(2024,illuin-tech,2652 stars)走了完全不同的路:直接在视觉空间做检索。
ColPali 用 PaliGemma-3B 的 ViT 输出作为多向量表示,按 ColBERT 的方式训练。它跳过了 OCR 和版面分析,直接把文档的视觉特征编码成嵌入,查询时在视觉空间里做相似度匹配。
ColPali 的优势在于:
- 不需要 OCR 或版面识别管线——端到端一个模型搞定
- 对图表、表格等视觉信息天然友好——不像 CLIP 那样丢失细节
- 在 ViDoRe benchmark 上表现优异
但 kapa.ai 在生产中对 CLIP 类方法的批评同样适用于 ColPali:
- 短查询 vs 长文档的语义鸿沟——用户问"how do I configure X",和一张架构截图之间的匹配,视觉嵌入很难做好
- 推理成本——每次查询都要对所有候选文档做视觉编码比对,在百万级查询场景下不现实
- 工程复杂度——需要维护专门的视觉检索基础设施
说白了,ColPali 更适合文档问答场景(比如你有一个 PDF 库,每份文档都要理解图表),而 kapa.ai 的方案更适合大规模知识库检索场景(技术文档 RAG,查询量大,成本敏感)。
实操建议:你的 RAG 管线怎么选
根据你的场景,选择不同的路线:
场景 A:技术文档 RAG,查询量大(>1 万次/天) → kapa.ai 方案:索引时用 VLM 生成 caption,独立 chunk 存储,查询走纯文本。成本可控,延迟低,效果有保障。
场景 B:文档问答,查询量小,但文档图表密集 → ColPali 方案:直接在视觉空间检索,跳过 OCR 管线。适合研究、法律、金融等需要理解复杂图表的领域。
场景 C:预算有限,只想快速验证 → 先用 OCR + 图片 alt text 提取文字,和文本 chunk 一起存。效果不如前两种,但零额外成本。
场景 D:已有成熟文本 RAG,只想补上图片能力 → 按 kapa.ai 的方案做增量:给已有文档的图片批量生成 caption,作为新 chunk 加入向量库。不需要改动现有管线。
几个容易踩的坑
1. 别用大模型生成 caption。 前面说了,小模型就够。用 GPT-4o 描述截图是拿大炮打蚊子。
2. 一定要喂上下文。 没有前后文的图片描述质量差两个档次,这个投入绝对值得。
3. 过滤比生成更重要。 大多数图片是噪声,不过滤的话 caption chunk 会严重污染检索结果。
4. 独立存储,不要内联。 这个反直觉,但实测数据很清楚。
5. 别把图片当文本的附属品。 在 RAG 管线设计时就把图片处理作为一等公民,而不是"后续优化"。
写在最后
RAG 领域的优化已经卷到了 text chunking、embedding、reranking 这些层面,但图片处理一直是个被忽视的角落。kapa.ai 的方案之所以有价值,不是因为技术多新,而是因为它在生产环境里跑通了——百万级查询、200+ 客户、有数据支撑。
如果你在做技术文档类的 RAG,我建议认真考虑这个方案。投入小(1%-6% 成本),回报大(10%-64% 图片引用率),且不需要重构现有管线。
相关阅读:
- 《Rerank 在 RAG 中的角色:Bi-Encoder vs Cross-Encoder》 — 如果你还没加 reranker,先看这篇
- 《Stash:开源 AI 记忆层的工程实践》 — RAG 之外的另一种知识管理思路
信源: