在落地 PDF RAG 问答场景的过程中,相信很多朋友和我一样,都遇到过一个经典痛点:传统纯文本 RAG 只能识别 PDF 明文文本,完全“看不见”图片内的关键信息。
日常使用的技术白皮书、产品手册、教程文档中,大量核心标题、功能介绍、按钮说明、架构图示都以位图形式存在。如果仅依赖简单的文本提取,会直接丢失半数有效信息,导致大模型问答结果片面、不准确。
最近我基于 PyMuPDF + PyMuPDF4LLM + Qwen-VL 实现了一套轻量化图文混合 PDF RAG 方案,可同时解析PDF文本与图片信息,完整还原文档内容。今天把完整实现思路、踩坑经验和优化方案分享出来,和大家一起交流学习,也欢迎大家多多指正~
完整代码见 github.com/MaNongWuDao…
一、传统 PDF RAG 的核心短板
常规的 PDF RAG 流程非常标准化:PDF 文本提取 → 内容分块 → 建立索引 → 检索问答。这套流程在纯文本文档中表现稳定,但面对图文混合文档存在致命缺陷。
我本次测试使用的 demo.pdf 就是典型案例:文档可直接提取的纯文本仅336个字符,能正常读取到 Versatile、User-friendly 等正文内容,但页面核心信息全部封装在位图中,包括:
-
核心标题:
The Progressive JavaScript Framework -
框架介绍文案:
An approachable, performant and versatile framework... -
功能按钮文字:
Why Vue、Get Started、Install
如图:
使用 page.get_text() 等常规方法完全无法获取以上信息。这也让我意识到:真正可用的生产级 PDF RAG,必须同时具备文本解析与图片理解能力。
二、整体技术方案设计
为解决图文信息丢失问题,我搭配了一套轻量、易落地的技术组合,兼顾开发成本与落地效果,非常适合快速验证场景和小规模文档处理:
-
PyMuPDF:底层读取PDF页面,精准获取图片坐标、页码等结构化信息
-
PyMuPDF4LLM:一键将PDF页面转为标准化Markdown,批量导出页面图片资源
-
Qwen-VL:多模态模型,负责生成图片可检索描述、原生图片理解问答
-
轻量化关键词检索:无需向量库,实现文本块、图片块的统一检索排序
-
OpenAI 兼容接口:通过 DashScope 快速调用 Qwen 系列模型,适配性极强
最终实现效果:
正在解析 PDF:demo.pdf
PDF 页数:1
生成检索块:正文 1,图片 1,合计 2
写入缓存:demo.pdf.chunks.json
图片目录:demo.pdf.images
RAG 已启动,输入 exit 退出。
问题:Vue是什么
检索结果:
1. 第 1 页 | 图片 | score=4.5585 | 这张图片是 Vue.js 官方网站的首页截图,属于网页界面类型。图片中央显示了标题:“The Progressive JavaScript Framework”,其中“Progressive”一词使用...
2. 第 1 页 | 正文 | score=0.0343 | 在上面 # Versatile A rich, incrementally adoptable ecosystem that scales between a library and a full...
正在调用阿里百炼模型...
回答:
Vue 是一个渐进式 JavaScript 框架,用于构建网页用户界面。它具有以下特点:
- **易于上手、高性能且多功能**:Vue 是一个易于学习和使用的框架,同时具备高性能和灵活性,适用于构建各种规模的 Web 用户界面。[第 1 页]
- **渐进式框架**:Vue 可以逐步采用,既可以作为库使用,也可以扩展为完整的框架。[第 1 页]
- **基于标准技术**:Vue 构建在标准 HTML、CSS 和 JavaScript 之上,提供直观的 API 和世界级的文档支持。[第 1 页]
- **高效渲染系统**:Vue 具有真正响应式的、经过编译优化的渲染系统,通常不需要手动优化。[第 1 页]
这些信息来源于 Vue.js 官方网站的首页截图及描述内容。[第 1 页]
可以看到图片中的内容也被检索出来了,这样极大补齐了图文结合的pdf图片信息丢失的问题。
核心数据流
整套方案打通了「解析-索引-检索-问答-容错」全链路,完整流程如下:
PDF
-> PyMuPDF4LLM 提取页面 Markdown 文本
-> 批量导出页面原图资源
-> PyMuPDF 补充图片页码、bbox坐标信息
-> 文本内容分块 + 图片内容描述分块
-> 统一结构化存入 JSON 索引缓存
-> 轻量化关键词检索匹配
-> 关联文本上下文 + 原始图片输入模型
-> 输出带精准页码引用的问答结果
这里分享一个核心设计思路:不将图片单纯转为文本替代,而是双信息留存。一方面生成图片文字描述用于检索召回,另一方面保留原始图片文件用于模型精准理解,兼顾检索效率与问答精度。
三、核心功能实现细节
下面拆解关键代码实现与核心逻辑,每一步都是落地过程中总结的最优实践,规避了很多常见坑点。
1. 一键解析PDF,同步提取文本与图片
通过 pymupdf4llm 可以高效实现PDF文本、图片同步解析,核心代码如下:
pages = pymupdf4llm.to_markdown(
pdf_path,
page_chunks=True,
write_images=True,
image_path=image_dir,
image_format="png",
show_progress=False,
table_strategy="lines_strict",
)
三个核心参数缺一不可:
-
page_chunks=True:按页面拆分内容,保留完整页码边界,方便后续溯源 -
write_images=True:自动导出页面所有图片,避免图片信息遗漏 -
image_path:自定义图片存储路径,统一资源管理
解析后文本中会残留  占位符,直接留存会污染检索上下文。因此需要通过正则提取图片路径后,彻底清除占位符:
image_pattern = re.compile(r"![[^]]*](([^)]+))")
image_references = image_pattern.findall(page_text)
page_text = image_pattern.sub("", page_text)
同时通过 PyMuPDF 获取图片 bbox 坐标信息,为后续图片高亮、区域检索、溯源定位预留扩展能力:
image_infos = page.get_image_info(xrefs=True)
bbox = image_infos[image_index]["bbox"]
2. 统一数据结构,简化图文检索逻辑
为了避免文本、图片分两套检索逻辑,我定义了统一的 Chunk 数据模型,实现图文内容结构化统一管理:
@dataclass
class Chunk:
chunk_id: str
source: str
page: int
text: str
terms: list[str]
kind: str = "text"
image_path: str | None = None
image_bbox: tuple[float, float, float, float] | None = None
caption: str = ""
所有检索单元共用一套结构:
-
文本块:
kind="text",text 存储正文内容 -
图片块:
kind="image",text 存储图片检索描述,绑定原图路径与坐标
这套设计极大简化了检索流程,无需区分图文类型,统一排序、统一召回,逻辑更简洁、维护成本更低。
3. 生成图片检索描述,解决图片不可检索问题
原始图片无法直接参与关键词检索,这是图文RAG的核心难点。我的解决方案是:通过 Qwen-VL 精准提取图片关键信息,生成标准化检索文案。
模型提示词严格遵循「客观提取、不猜测、不脑补」原则,保证描述精准可用:
response = client.chat.completions.create(
model=vision_model,
temperature=0,
messages=[
{
"role": "system",
"content": (
"你是 PDF 图片索引助手。请用中文简洁描述图片,"
"逐字提取图中可见的标题、按钮和关键文字,再说明"
"图片类型与主要对象。不要猜测无法确认的信息。"
),
},
{
"role": "user",
"content": [
{"type": "text", "text": f"请描述 PDF 第 {page} 页的这张图片。"},
{"type": "image_url", "image_url": {"url": image_to_data_url(image_path)}},
],
},
],
)
生成的描述文案可直接用于关键词检索,实现「通过文字召回图片资源」的效果。
4. 智能加权检索,精准匹配图文意图
本次实现没有引入重型向量数据库,通过轻量化关键词检索+意图加权,主要为大家能够快速上手,看到效果。检索打分综合多重维度:
-
查询词与文本块的 IDF 加权重合度
-
精准字符串、数字、编码匹配优先级
-
文本长度权重修正
-
用户视觉查询意图加权
针对中英文混合场景,做了分词适配:中文采用二元词组拆分,英文、数字按单词精准拆分,提升匹配精度。
同时做了意图识别优化:如果用户提问包含「图片、图中、figure」等视觉关键词,自动给图片检索块增加权重,优先召回图片内容,贴合用户真实需求:
image_intent_bonus = (
4.0
if chunk.kind == "image" and is_image_question(query)
else 0.0
)
5. 原图级多模态问答,规避信息损耗
这里想分享一个关键认知:仅靠图片描述回答必然存在信息损耗。模型生成的描述会精简细节,无法替代原图信息。
因此问答阶段,我会将检索到的原图编码为 Data URL,和文本上下文一起传入 Qwen-VL 模型,实现「描述召回+原图理解」的双层保障:
content = [
{"type": "text", "text": user_prompt},
{
"type": "image_url",
"image_url": {"url": "data:image/png;base64,..."},
},
]
同时增加降级容错机制:如果多模态模型调用失败,自动回落至纯文本问答,使用已生成的图片描述作为上下文,保证系统不宕机、可用。
6. 缓存优化与异常容错
PDF解析、图片导出、模型生成描述均耗时较高,为提升复用效率,我将完整索引结果缓存至 JSON 文件。缓存有效性会严格校验:文件是否存在、是否晚于PDF更新时间、图片资源是否完整。
针对模型调用、网络波动等异常场景,设计了双层降级策略:
-
单张图片描述生成失败时,默认填充「第N页图片」基础描述,不阻断整体索引流程
-
存在异常降级的索引结果,不写入长期缓存,避免污染数据,网络恢复后可重新生成完整索引
四、实测效果验证
基于单页 demo.pdf 完整测试,系统运行稳定,所有核心功能均达标:
-
成功生成:正文检索块1个、图片检索块1个,完整覆盖文档所有有效信息
-
视觉类提问可优先命中图片块,精准匹配图片内标题、按钮文字
-
原图编码、多模态问答、异常降级、缓存校验功能全部正常
-
10项单元测试全部通过,代码编译无报错
实测可以很好的解决传统RAG丢失图片信息的问题,问答精准度提升非常明显。
五、落地实战核心经验(避坑干货)
在整个调试、落地过程中,踩了不少坑,总结了几条关键经验,分享给大家参考,少走弯路:
-
必须清理Markdown图片占位符:默认导出的占位符会混入文本索引,污染检索上下文,导致匹配异常,务必正则清除
-
图片描述+原图缺一不可:描述负责高效检索召回,原图负责精准问答理解,单一方案都有明显短板
-
保留页码与bbox坐标:结构化坐标信息是后续图片高亮、区域检索、溯源预览的基础,不要在解析阶段丢失
-
模型调用必须做降级处理:网络、API配额、模型服务异常都是常态,容错机制是系统稳定运行的关键
-
不缓存不完整数据:临时降级的索引结果仅实时使用,不持久化,避免长期数据污染
六、方案后续优化方向
当前方案适合单文件、小规模文档的快速落地与验证,后续可以持续迭代,升级为生产级方案:
-
接入向量数据库,实现文本、图片描述的语义检索,提升模糊查询匹配效果
-
新增跨模态图片向量检索,适配大规模图片库召回场景
-
针对性优化表格、流程图、架构图的专属解析策略
-
增加OCR兜底能力,适配无文本层的扫描版PDF
-
引入重排序模型,优化图文混合检索结果的排序精度
-
完善前端适配,支持结果页码、图片坐标溯源预览与高亮
七、总结
区别于传统纯文本PDF RAG,这套 PyMuPDF+PyMuPDF4LLM+Qwen-VL 图文混合方案,核心是搭建了一条完整的「图片解析-定位-索引-检索-多模态问答-容错」链路。
通过轻量化技术组合,低成本解决了PDF图片信息丢失的行业常见问题,适配技术文档、产品手册、课件、报告等图文混合场景。整体方案轻量化、易落地、容错性强,非常适合作为PDF多模态RAG的入门落地版本,也可作为生产级方案的迭代基础。
以上就是本次的实战分享,方案还有很多可优化的空间,欢迎各位大佬交流探讨、共同学习进步!