尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
多模态RAG工程:图文混检三策略+RRF融合,从失败案例看信息检索新范式
一个真实的失败案例上个月团队接手一个 case给一份 200 页的行业研究报告做问答系统。数据源里的文字被切成 2000 多个 chunk用 BGE-Large 向量化pgvector 存好Rerank 模型也接上了。测试第一轮就翻车。用户问“2024 年 Q3 的市场份额分布”——检索命中的是一段泛泛的总结文字真正答案在同一页的一张饼图里。用户问“架构图里的缓存层放在什么位置”——命中的是缓存两个字出现的技术术语列表架构图本身完全没被索引。问题不出在检索算法也不出在 embedding 质量。问题是一个多模态文档被当作纯文字文档处理图表里的信息从索引阶段就消失了。这是纯文字 RAG 在图文混排文档上的通病。行业研报、财报、技术白皮书、产品手册——这些高价值密度文档里30% 到 60% 的信息藏在图表、示意图、截图、公式图片里。传统 pipeline 用 OCR 抓一点文字回来但饼图的比例、架构图的拓扑、示意图的逻辑关系OCR 抓不到。今天这篇聊多模态 RAG 的三种索引策略、跨模态检索的实现以及怎么用融合排序把它们拼在一起。文末有一份可直接跑的 Python demo不依赖任何 API key。三种索引策略的原理先把选项摆开。策略 AOCR 文字索引。最传统。对每张图跑 OCR抓出文字后当作普通 chunk 索引。优点是零改造pipeline 复用缺点是无 OCR 内容图表、示意图、无字照片直接丢失图片里的语义结构也保留不了。策略 BCaption 文字索引。对每张图用 VLMVision Language Model生成描述性 caption再把 caption 当作文本索引。等于让模型先看图说话然后按文字去检索。这条路的好处是索引层完全复用现有向量库工程改动小风险是 caption 质量直接决定检索上限模型没说的信息就永远检不到。策略 C视觉嵌入 Late Interaction。直接把图像编码进向量空间跳过文字这一步。代表工作是 ColPali——把 PDF 页面当作图像输入到视觉语言模型的编码器输出 patch 级别的向量序列不是把整张图压成一个向量。查询侧的文本也输出 token 级向量。检索时用 ColBERT 的 Late Interaction 思想每个 query token 和图像所有 patch 算相似度取最大再对所有 query token 求和。三种策略的对比维度策略 AOCR策略 BCaption策略 C视觉嵌入索引成本低中每张图跑一次 VLM高需要专门模型检索质量差丢信息中依赖 caption好端到端存储开销小小大多向量部署复杂度低低中高适合场景图片少的文档中小规模、图片语义可描述大规模、图表密集实际工程里我一般把 B 和 C 组合起来用然后靠融合排序去调。下面看代码。Caption 策略实现策略 B 的核心是对每张图生成一段能被文本 embedding 抓住的描述。真实系统里这段调用 Qwen-VL 或 DeepSeek-VL2demo 里我用可预测的 mock 函数替代保证不联网也能跑。from dataclasses import dataclass, fieldfrom typing import List, Dict, Any, Tuple, Optionalimport hashlib, math, random---------- 数据结构 ----------dataclassclass TextBlock:block_id: strtext: strpage: intdataclassclass ImageBlock:block_id: strimage_ref: strpage: intkind: str “figure” # figure / chart / table / photodataclassclass MultimodalDocument:doc_id: strtitle: strtext_blocks: List[TextBlock] field(default_factorylist)image_blocks: List[ImageBlock] field(default_factorylist)meta: Dict[str, Any] field(default_factorydict)---------- Mock embedding caption ----------def _stable_vec(seed_str: str, dim: int 128) - List[float]:“”“基于内容哈希生成稳定单位向量避免每次运行结果漂移。”“”seed_int int(hashlib.sha256(seed_str.encode()).hexdigest(), 16) % (2**32)rng random.Random(seed_int)v [rng.gauss(0.0, 1.0) for _ in range(dim)]n math.sqrt(sum(x*x for x in v)) or 1.0return [x/n for x in v]def cosine(a: List[float], b: List[float]) - float:return sum(x*y for x, y in zip(a, b)) # 已归一化def mock_caption(image: ImageBlock) - str:“”“真实系统这里调用 VLMdemo 用规则模拟。”“”hints {“chart”: “柱状图展示了各季度收入趋势”,“table”: “表格列出了模型参数与推理成本”,“figure”: “架构示意图描述了检索与生成流程”,“photo”: “现场照片记录了服务器机房”,}return f[{image.kind}] {hints.get(image.kind, ‘未知图像’)}位于第{image.page}页def mock_text_embed(text: str, dim: int 128) - List[float]:return _stable_vec(fTXT::{text}, dim)---------- Caption 索引器 ----------class CaptionBasedIndexer:def __init__(self):self.entries: List[Tuple[str, str, List[float], ImageBlock]] []def add\_document(self, doc: MultimodalDocument) - None: for img in doc.image\_blocks: cap mock\_caption(img) vec mock\_text\_embed(cap) self.entries.append((doc.doc\_id, cap, vec, img)) def search(self, query: str, top\_k: int 5) - List[Dict[str, Any]]: qv mock\_text\_embed(query) scored [{ doc\_id: d, block\_id: img.block\_id, caption: cap, score: cosine(qv, v), source: caption, } for d, cap, v, img in self.entries] scored.sort(keylambda x: x[score], reverseTrue) return scored[:top\_k]生产环境里mock_caption会被替换成真实 VLM 调用。这里有个经验caption 的 prompt 要明确写出你希望被检索到的关键信息类型——比如请描述图表的横纵轴、主要趋势、极值点——而不是让模型自由发挥。VLM 默认会写文学化的描述那种描述对检索是负资产。视觉嵌入策略实现策略 C 的关键区别在两点一是图像被拆成 patch每个 patch 有独立向量二是检索用 Late Interaction不是单向量点积。def mock_visual_embed(image: ImageBlock, dim: int 128) - List[List[float]]:“”“ColPali思路一张图输出 N 个 patch 向量。”“”n_patches 8return [_stable_vec(fIMG::{image.image_ref}::p{i}, dim) for i in range(n_patches)]def mock_visual_query_embed(query: str, dim: int 128) - List[List[float]]:“”“查询侧同样输出 token 级向量。”“”tokens list(query)[:6] or [query]return [_stable_vec(fQTOK::{query}::{t}::{i}, dim)for i, t in enumerate(tokens)]class VisualEmbeddingIndexer:def __init__(self):self.entries: List[Tuple[str, List[List[float]], ImageBlock]] []def add\_document(self, doc: MultimodalDocument) - None: for img in doc.image\_blocks: self.entries.append((doc.doc\_id, mock\_visual\_embed(img), img)) staticmethod def late\_interaction(q\_tokens: List[List[float]], patches: List[List[float]]) - float: ColBERT / ColPali每个 query token 对所有 patch 取 max sim再平均。 if not q\_tokens or not patches: return 0.0 total 0.0 for qt in q\_tokens: total max(cosine(qt, p) for p in patches) return total / len(q\_tokens) def search(self, query: str, top\_k: int 5) - List[Dict[str, Any]]: q\_tokens mock\_visual\_query\_embed(query) scored [{ doc\_id: d, block\_id: img.block\_id, kind: img.kind, score: self.late\_interaction(q\_tokens, patches), source: visual, } for d, patches, img in self.entries] scored.sort(keylambda x: x[score], reverseTrue) return scored[:top\_k]Late Interaction 的直觉是query “各季度收入趋势” 里的季度“收入”趋势这几个 token会分别在图像的不同 patch 上找到最相关的区域左侧图例、柱状图区域、横轴时间标签每个 token 独立打分再融合。相比单向量点积它保留了细粒度的对齐信息。代价是索引存储从 O(N) 变成 O(N × patch数)检索时的计算量也线性增加。生产环境用 ColPali 时索引侧一般会用 PLAID 之类的压缩方案把 float32 向量量化成 int8 或 int4把存储成本压回一个可接受的量级。统一检索接口 图搜图上层需要一个统一入口屏蔽策略差异同时支持文搜图和图搜图两个方向class MultimodalRetriever:def __init__(self, cap: CaptionBasedIndexer,vis: VisualEmbeddingIndexer):self.cap capself.vis visdef add\_document(self, doc: MultimodalDocument) - None: self.cap.add\_document(doc) self.vis.add\_document(doc) def retrieve(self, text\_query: Optional[str] None, image\_query: Optional[ImageBlock] None, top\_k: int 5) - Dict[str, List[Dict[str, Any]]]: results: Dict[str, List[Dict[str, Any]]] {} if text\_query: results[caption] self.cap.search(text\_query, top\_k) results[visual] self.vis.search(text\_query, top\_k) if image\_query: # 图搜图把查询图当作一组 patch 向量与库内 patch Late Interaction q\_patches mock\_visual\_embed(image\_query) scored [{ doc\_id: d, block\_id: img.block\_id, kind: img.kind, score: VisualEmbeddingIndexer.late\_interaction(q\_patches, p), source: image2image, } for d, p, img in self.vis.entries] scored.sort(keylambda x: x[score], reverseTrue) results[image2image] scored[:top\_k] return results图搜图的场景比想象中常见技术支持工单里用户传截图问这个报错怎么解决或者设计评审里找一下和这张图类似的历史设计。共用一套 patch embedding 就能同时支持这两个方向不需要另建索引。融合排序RRF 算法实现多路召回之后必须融合。分数直接相加行不通——不同来源的分数分布差异极大caption 走文本 cosine 是 0-1visual 的 Late Interaction 平均是 0-1 但均值明显不同加权和很难调。Reciprocal Rank FusionCormack, 2009绕开了这个问题只看排名不看分数。一个 item 在某路结果里排第 r 位贡献分是1 / (k r)k 是平滑常数论文推荐 60。多路加起来就是最终分。class FusionRanker:def __init__(self, k: int 60):self.k kdef fuse(self, ranked\_lists: Dict[str, List[Dict[str, Any]]], top\_k: int 5) - List[Dict[str, Any]]: scores: Dict[str, float] {} detail: Dict[str, Dict[str, Any]] {} for source, lst in ranked\_lists.items(): for rank, item in enumerate(lst): key f{item[doc\_id]}::{item[block\_id]} scores[key] scores.get(key, 0.0) 1.0 / (self.k rank 1) if key not in detail: detail[key] {\*\*item, rrf\_sources: [source]} else: detail[key][rrf\_sources].append(source) fused [{\*\*detail[k\_], rrf\_score: s} for k\_, s in scores.items()] fused.sort(keylambda x: x[rrf\_score], reverseTrue) return fused[:top\_k]RRF 的工程价值在于加一路召回不用重新调权重。你想接入 BM25加进来就是。想再加一路 Rerank加进来就是。参数只有一个 k而且不敏感60 基本一劳永逸。完整 demo 流程把上面全部串起来def build_demo_corpus() - List[MultimodalDocument]:return [MultimodalDocument(doc_id“D1”, title“Q3财报解读”,text_blocks[TextBlock(“D1-t1”, “本季度整体营收环比上升。”, 1)],image_blocks[ImageBlock(“D1-i1”, “d1_p1_chart.png”, 1, “chart”),ImageBlock(“D1-i2”, “d1_p2_table.png”, 2, “table”),]),MultimodalDocument(doc_id“D2”, title“推理系统架构”,text_blocks[TextBlock(“D2-t1”, “系统采用分层缓存策略。”, 3)],image_blocks[ImageBlock(“D2-i1”, “d2_p1_arch.png”, 1, “figure”),ImageBlock(“D2-i2”, “d2_p4_photo.png”, 4, “photo”),]),]def main():docs build_demo_corpus()retriever MultimodalRetriever(CaptionBasedIndexer(), VisualEmbeddingIndexer())for d in docs:retriever.add_document(d)query 各季度收入趋势 print(f[Query] {query}) results retriever.retrieve(text\_queryquery, top\_k3) for source, lst in results.items(): print(f-- {source} --) for r in lst: print(f {r[doc\_id]}/{r[block\_id]} score{r[score]:.4f}) fused FusionRanker(k60).fuse(results, top\_k3) print( RRF 融合 ) for r in fused: print(f {r[doc\_id]}/{r[block\_id]} frrf{r[rrf\_score]:.4f} sources{r[rrf\_sources]}) # 图搜图 demo拿 D1 的图去搜相似图 query\_image docs[0].image\_blocks[0] print( 图搜图 ) for r in retriever.retrieve(image\_queryquery\_image, top\_k3)[image2image]: print(f {r[doc\_id]}/{r[block\_id]} score{r[score]:.4f})if __name__ “__main__”:main()这段代码保存为mm_rag_demo.py后python3 mm_rag_demo.py直接可跑。会看到 Caption 路和 Visual 路各自返回 top-3然后 RRF 融合出一个综合列表最后图搜图返回相似图。因为 mock embedding 是内容哈希驱动的伪随机具体排名对 demo 不重要重点是整个 pipeline 的骨架能跑通。真实系统里替换三个 mock 函数mock_caption→ 调 Qwen-VL 或 DeepSeek-VL2mock_text_embed→ 调 BGE / OpenAI text-embedding-3-largemock_visual_embed/mock_visual_query_embed→ 调 ColPali 或类似模型其余 pipeline 代码零改动。选型决策指南三种策略不是二选一是组合问题。我的决策路径文档以纯文字为主图片 10%只上策略 AOCR 到能抓的都抓上。没必要为少数图片引入复杂 pipeline。中小规模知识库 10 万张图图片语义可描述上策略 B。VLM 生成 caption 的成本可以接受索引侧完全复用现有向量库。技术手册、产品说明这类场景最合适。大规模知识库 10 万张图或图表极密集学术论文、财报上策略 C或 B C 融合。策略 C 的初期投入大模型部署、存储成本但检索质量和维护成本长期占优。精度要求极高、图文语义关联紧密医学影像、工程图B C 融合再叠一层专门微调的 Rerank。这类场景通常有充足预算也有能力做领域 Fine-tune。图搜图场景强需求客服工单、UI 设计库强制上策略 CB 完全覆盖不了。一个反直觉的经验不要过早上策略 C。ColPali 类模型的存储成本容易失控——一张 A4 页面切成 1024 个 patch每 patch 128 维 float32 就是 512KB。10 万页就是 50GB还没算副本和索引开销。除非策略 B 已经明显吃力否则从 B 起步、监控 recall 指标、真正不够用了再加 C。​最后我在一线科技企业深耕十二载见证过太多因技术更迭而跃迁的案例。那些率先拥抱 AI 的同事早已在效率与薪资上形成代际优势我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我整理出这套 AI 大模型突围资料包✅AI大模型学习路线图✅Agent行业报告✅100集大模型视频教程✅大模型书籍PDF✅DeepSeek教程✅AI产品经理入门资料完整的大模型学习和面试资料已经上传带到CSDN的官方了有需要的朋友可以扫描下方二维码免费领取【保证100%免费】​​为什么说现在普通人就业/升职加薪的首选是AI大模型人工智能技术的爆发式增长正以不可逆转之势重塑就业市场版图。从DeepSeek等国产大模型引发的科技圈热议到全国两会关于AI产业发展的政策聚焦再到招聘会上排起的长队AI的热度已从技术领域渗透到就业市场的每一个角落。智联招聘的最新数据给出了最直观的印证2025年2月AI领域求职人数同比增幅突破200%远超其他行业平均水平整个人工智能行业的求职增速达到33.4%位居各行业榜首其中人工智能工程师岗位的求职热度更是飙升69.6%。AI产业的快速扩张也让人才供需矛盾愈发突出。麦肯锡报告明确预测到2030年中国AI专业人才需求将达600万人人才缺口可能高达400万人这一缺口不仅存在于核心技术领域更蔓延至产业应用的各个环节。​​资料包有什么①从入门到精通的全套视频教程⑤⑥包含提示词工程、RAG、Agent等技术点② AI大模型学习路线图还有视频解说全过程AI大模型学习路线③学习电子书籍和技术文档市面上的大模型书籍确实太多了这些是我精选出来的④各大厂大模型面试题目详解⑤ 这些资料真的有用吗?这份资料由我和鲁为民博士共同整理鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利同时还斩获了吴文俊人工智能科学技术奖。目前我正在和鲁博士共同进行人工智能的研究。所有的视频教程由智泊AI老师录制且资料与智泊AI共享相互补充。这份学习大礼包应该算是现在最全面的大模型学习资料了。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念‌通过动态追踪大模型开发、数据标注伦理等前沿技术趋势‌构建起前沿课程智能实训精准就业的高效培养体系。课堂上不光教理论还带着学员做了十多个真实项目。学员要亲自上手搞数据清洗、模型调优这些硬核操作把课本知识变成真本事‌​​​​如果说你是以下人群中的其中一类都可以来智泊AI学习人工智能找到高薪工作一次小小的“投资”换来的是终身受益应届毕业生‌无工作经验但想要系统学习AI大模型技术期待通过实战项目掌握核心技术。零基础转型‌非技术背景但关注AI应用场景计划通过低代码工具实现“AI行业”跨界‌。业务赋能 ‌突破瓶颈传统开发者Java/前端等学习Transformer架构与LangChain框架向AI全栈工程师转型‌。获取方式有需要的小伙伴可以保存图片到wx扫描二v码免费领取【保证100%免费】**​
RELATED

相关推荐

Linux UIO驱动实战:ARM+FPGA异构平台PS/PL中断处理

Linux UIO驱动实战:ARM+FPGA异构平台PS/PL中断处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 2:37:47
荧光标记脂质DOPE-FITC与FITC-PEG-DOPE:结构、应用与避坑指南

荧光标记脂质DOPE-FITC与FITC-PEG-DOPE:结构、应用与避坑指南

选用荧光标记脂质之前,先搞清楚你买到的到底是什么、标记在哪、会不会淬灭。这篇文章不聊空泛的概念,直接围绕DOPE-FITC和FITC-PEG-DOPE这两种材料,把化学结构、设计逻辑、在脂质体实验里的真实用法、以及我踩过的坑一次讲透。我最早接触&quo…

📅 2026/10/15 2:32:46
DeepSeek Harness热门插件盘点:10个实用插件,覆盖多Agent、上下文和多模态

DeepSeek Harness热门插件盘点:10个实用插件,覆盖多Agent、上下文和多模态

DeepSeek Harness是DeepSeek AI开发的开源Agent运行框架,采用“一切皆插件”的架构,工具、界面和运行能力都可以组合扩展。截至2026年10月14日,社区已经出现插件市场、上下文可视化、多Agent协作、手机访问和视觉工具等多条路线。这份盘点按用…

📅 2026/10/15 2:32:46
MORE NEWS

更多资讯

📰

广义Hough变换的自适应阈值区间:解决投票累积的调参难题

简介:《基于自适应阈值区间的广义Hough变换图形识别算法》是一篇面向图像处理与模式识别研究人员的学术论文,由宋晓宇等人撰写。文章针对传统霍夫变换计算量大、阈值设定困难及易产生误检等问题,提出基于自适应阈值区间的改进算法&#xff0c…

📰

ChatGPT指令集实战:从PDF拆解到角色扮演稳定输出

简介:这份PDF文档聚焦ChatGPT提示词(Prompt)的系统讲解,面向K12教育场景以及人工智能、深度学习、自然语言处理爱好者,帮助读者理解并运用指令设计来获取高质量AI回复。文档从Prompt是什么切入,介绍其在智能…

📰

基于MP2642GR-Z与MK24FN256VDC12的2S电池主动均衡BMS设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

DeepSeek职场落地实战:结构化提示语与轻量部署指南

简介:本资源是一份聚焦DeepSeek大模型职场落地实践的深度技术解析PDF,面向具备一定编程与AI基础的研发人员、产品经理、设计师及关注人机协同的职场技术从业者,旨在解决LLM提示工程应用浅、模型选型难、智能体开发缺路径等现实问题。内容系统…

📰

海康机器人算法SDK实战:从跑通Demo到工业级部署

简介:本资源是面向工业视觉开发工程师与自动化系统集成人员的海康机器人VisionMaster算法SDK配套Demo详解文档,聚焦图像处理、算法调用与二次开发实践,助力快速掌握工业相机场景下的缺陷检测、定位识别等核心能力。压缩包为1个1.85MB的PDF文件…

📰

覆盖索引与超长分页优化

什么是覆盖索引覆盖索引是指:查询用到的列(包括 SELECT 返回的列、WHERE / ORDER BY / GROUP BY 用到的列),全部都能在同一个索引中找到,从而不用回表。用 id(主键)查询时,直接走聚簇…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬