RAGFlow v0.19图文混排:详细拆解+预处理增强案例
RAGFlow在5/26 正式更新了v0.19版本,其中有两点值得关注。首当其冲的是在 Agent 模块(也就是工作流)新增了代码执行组件,这个被吐槽了很久了 RAGFlow工作流编排功能,终于可以处理更加复杂的任务了。
RAGFlow在5/26 正式更新了v0.19版本,其中有两点值得关注。首当其冲的是在 Agent 模块(也就是工作流)新增了代码执行组件,这个被吐槽了很久了 RAGFlow工作流编排功能,终于可以处理更加复杂的任务了。
第二点比较大的更新,是这篇文章主要要讨论的,也就是在 Chat 和 Agent 模块中改变以往图片作为引用的展示方式,直接在正文中进行显示。进行初步测试之后发现,RAGFlow 实际是把我历史文章中提到的“占位符+映射替换”以及前端渲染的方法内置化和标准化了。但测试下来也明显发现这种原生方案同时带来了文档预处理的复杂性。
这篇试图说清楚:
RAGFlow v0.19版本图文混合回答功能的底层实现逻辑,和 URL 渲染方案的主要区别,以及如何基于业务语义驱动的PDF 重组案例,在保留 RAGFlow 原生图片显示能力(基于 img_id)的同时,对文档进行更精细化的预处理,特别是针对表格内图片这类复杂场景,以确保分块和图片关联符合预期。

以下,enjoy:
1.URL 方案的三种做法
在正式开始介绍前先快速回顾下,上述提到的我给出的三种历史 URL 方式解决方案。之前的思路是,在现有开源框架基础上,不改动核心代码,又要达到较好的流式图文问答体验,自然需要引入一些“变通方案”。本着“规避 LLM 弱点”、“模块化处理”、“逐步优化体验”的工程实践思路,我前后进行了以下三种方案的探索。

*RAGFlow的v0.19版本更新前,正好在官方群里又人在讨论我历史文章中的图片显示方案*
*1.1.初步尝试*
最初通过独立的图片服务器容器化部署,把本地图片路径转换为 HTTP URL。前端通过挂载静态文件目录,从而图片可以通过 HTTP URL 直接访问,并在 Markdown 渲染中通过标签或 Markdown 图片链接展示。

这种做法是在文档预处理时替换本地路径为 HTTP URL,依赖外部图片服务器进行实现。当然,最大的问题还是在于 LLM 会不可控的“自作聪明”修改 URL,这个方案不适合长期在生产环境使用。
*1.2*
*进阶优化*
针对 LLM 可能修改直接嵌入的图片 URL,导致加载失败的问题,我后续引入了“占位符+后端映射替换”的方案。具体做法是,把图片存储在 RAGFlow 自带的 MinIO 中,文档预处理环节在文本中插入特定格式的占位符(例如[IMG::filename.png]),同时生成一个包含占位符到 MinIO 图片实际 URL 映射关系的 map.json 文件。进而,在 Dify 这样的编排工具中,通过 HTTP 节点获取 map.json,再由 Code 节点在 LLM 输出后,根据映射关系把文本中的占位符替换为最终的 HTML 标签。

这种做法的核心是利用 MinIO 存储、占位符机制、后端(Dify Code 节点)替换逻辑,增强了 URL 的稳定性,但 Dify Code 节点批处理导致的非流式输出会带来明显的体验问题。(当时选择 RAGFlow+Dify 的拼接方案,主要是受制于 RAGFlow 的 agent 模块还不支持自定义的 code 节点。)
*1.3*
*体验提升*
为了解决后端替换带来的延迟和非流式问题,我选择把图片占位符的替换工作从后端(Dify)转移到了用户浏览器前端。通过编写油猴脚本 (Tampermonkey script)在 Dify 聊天界面加载的时候,脚本异步获取 map.json 文件,然后实时监控 LLM 的流式文本输出,一旦检测到预设的图片占位符,就立即将其在前端动态替换为标签,从而实现图片随文本流式同步显示。

选择这种做法的核心原因是可以非侵入式的修改 Dify 前端行为,免去了直接增强 RAGFlow 后端流式处理能力或前端渲染逻辑的修改风险。毕竟这种做法成本高,且影响框架升级。
*2*
v0.19 的图片显示逻辑
为了方便大家理解,这部分我从 API 文档作为切入点,再过渡到后端代码实现理解,最后通过实际操作中的部分截图来印证和展示效果。
注:为了 升级 v0.19,不需要重新部署。修改 ragflow/docker/.env 文件,更新 RAGFLOW_IMAGE 变量为你想要升级到的版本号。然后运行下述 Docker 命令会拉取 .env 文件中指定的新版本镜像,并使用新的镜像重新创建和启动容器。(由于数据卷是独立于容器的,新的容器会重新挂载已存在的数据卷,从而保留你的历史项目文件。)

# 修改envRAGFLOW_IMAGE=infiniflow/ragflow:v0.19.0
#在docker目录下运行docker compose -f docker/docker-compose.yml pulldocker compose -f docker/docker-compose.yml up -d
*2.1*
*API 接口*
打开 v0.19 的 Python API 文档,首先注意到一个关键变化:在与聊天助手或 Agent 交互后返回的 Message 对象中,引用的 Chunk 对象里增加了一个叫做为 img_id 的字符串类型。官方文档的解释是:
The ID of the snapshot of the chunk. Applicable only when the source of the chunk is an image, PPT, PPTX, or PDF file.

从这行内容可以明确看出来,img_id 正是 RAGFlow 用于标识和关联图片快照的核心 ID,它暗示了 RAGFlow 在后端对包含视觉信息的文档进行了处理,并为每个相关的文本块生成了可引用的视觉元素。那么进一步的问题就是,图片是如何被存储、如何通过这个 ID 最终在前端显示的?这个就要从后台代码中一探究竟。
*2.2*
*查看源码*
这部分主要介绍 RAGFlow 的异步任务处理模块 task_executor.py,这个脚本是理解文档解析和图片处理的关键。在整个脚本中可以找到三处和图片处理与 img_id 生成流程相关的片段,接下来我逐一做个说明:
****图片格式统一与存储 (JPEG):******
if not d.get("image"): # d 是一个 chunk 字典 _ = d.pop("image", None) d["img_id"] = "" docs.append(d) continue
try: output_buffer = BytesIO() if isinstance(d["image"], bytes): # 如果图片已经是bytes output_buffer = BytesIO(d["image"]) else: # 否则假定是PIL Image对象 d["image"].save(output_buffer, format='JPEG') # 保存为JPEG格式
st = timer() # 上传到存储 (例如MinIO) await trio.to_thread.run_sync(lambda: STORAGE_IMPL.put(task["kb_id"], d["id"], output_buffer.getvalue())) el += timer() - stexcept Exception: logging.exception( "Saving image of chunk {}/{}/{} got exception".format(task["location"], task["name"], d["id"])) raise

在 build_chunks 函数的循环内,可以看到这样的逻辑:当从文档中提取的图片数据(可能是一个 PIL Image 对象)存在时,系统会将其保存到一个 BytesIO 缓冲中,并明确指定 format=‘JPEG’。这意味着,无论原始图片格式如何,RAGFlow 在处理流程中倾向于将其标准化为 JPEG 格式进行后续存储。
然后,这些处理后的图片字节流会通过 STORAGE_IMPL.put(…)上传到对象存储服务中(MinIO)。task[“kb_id”]作为存储桶名称(或前缀),块的唯一 ID d[“id”] 作为对象名称。
*img_id 的生成与关联*
d["img_id"] = "{}-{}".format(task["kb_id"], d["id"]) # 生成img_iddel d["image"] # 从chunk字典中删除原始图片数据,因为它已被存储docs.append(d)
在图片成功上传到 MinIO 之后,RAGFlow 会为这个图片生成一个 img_id。这个 img_id 的格式是 “{知识库 ID}-{块 ID}” (“{}-{}”_format(task[“kb_id”], d[“id”]))。这个 ID 唯一标识了与该文本块关联的存储图片。
生成 img_id 后,原始的图片数据(d[“image”])会从内存中的 chunk 字典中删除,以节省资源,毕竟图片已经持久化存储了。 这个包含 img_id 的 chunk 信息最终会被索引到搜索引擎中。
*FACTORY 字典的定义*
FACTORY = { "general": naive, ParserType.NAIVE.value: naive, # ... 其他解析器 ... ParserType.PICTURE.value: picture, # 有专门的图片解析器 # ... 可能通过 naive 间接支持 Markdown}
RAGFlow 内部有一个解析器工厂(FACTORY),根据任务指定的 parser_id(文档类型或解析方式)来选择合适的 chunker 模块。对于直接上传的图片文件 (ParserType.PICTURE),会有专门的 picture 模块(rag.app.picture)来处理,它很可能会将整个图片作为一个块,并提取图片本身作为数据。

*2.3*
*前端测试*
以历史文章中经常使用的工程机械维保文档为例,为了更直观地理解 RAGFlow 前端是如何处理和显示图片的,我在浏览器的开发者工具对网络请求和页面元素进行了观察,以下结合相关截图进行说明。
*知识库与检索测试*
首先来看一下当 RAGFlow 在知识库中展示已解析文档的文本块时,图片信息是如何传递给前端的。下图左侧是‘解析块’界面,可以看到文本块‘故障名称:打着车老是熄火…’旁边有一个小小的图片预览。”


通过开发者工具(如第一张图右侧所示),捕获到了前端在加载这部分内容时,从后端获取到的数据。在返回的 JSON 响应中,可以清晰地看到一个名为 image_id 的字段,这个值正是与这个文本块关联的图片快照的唯一标识符。”
这个 image_id 就是我们之前在 API 文档和后端源码分析中多次提到的关键 ID。它由知识库 ID 和块 ID 组合而成,是 RAGFlow 内部用来索引和定位图片的核心。这证实了 RAGFlow 在前端展示知识库内容时,会首先通过 API 获取到包含 image_id 的元数据,为后续实际图片的加载做好准备。
*聊天助手*
对于聊天助手场景,如下图左侧所示,当助手在回答中引用包含图片的知识时,这些图片会以预览图的形式直接嵌入到对话流中实现图文混排。那嵌入的预览图是如何加载的呢?


通过开发者工具的‘Elements’面板(如图右侧所示),可以审查这些图片预览对应的标签的 src 属性。可以发现,它并不是一个指向 MinIO 或其他静态文件服务器的直接图片 URL。相反,它的路径是类似/v1/document/image/{image_id}这样的格式。这清晰地表明,前端是通过调用 RAGFlow 后端的一个特定 API 接口来获取图片内容的,而 image_id 正是这个接口定位具体图片的关键参数。
注:通过 API 端点提供图片的方式,是更符合最佳实践实现精细化权限控制的推荐做法。 它不像直接暴露静态文件 URL 那样难以控制访问。每一次图片请求都需要经过后端的鉴权逻辑。
*渲染逻辑总结*
浏览器要加载并渲染一个外部资源(如图片、iframe 内容、通过 AJAX 获取的数据),它必须通过一个 URL 来定位这个资源。所以,可能会有盆友好奇缩略图和 Document Previewer 既然“不是直接的 MinIO URL”,是怎么在前端页面显示的?这个疑问的关键是理解“URL”的含义和浏览器的工作方式。
当我们说它“不是直接的 MinIO URL”时,指的是标签的 src 属性或者 Document Previewer 加载内容的源,并不是一个指向 MinIO 存储桶中某个文件对象的永久、公开、任何人都可以直接访问的静态链接(比如 http://minio.example.com/mybucket/myimage.jpg这种)。以缩略图为例,其标签的 src 属性值是 /v1/document/image/{image_id}。这本身就是一个合法的 URL (更准确地说,是一个相对 URL,浏览器会根据当前页面的域名和端口补全它,变成一个完整的 HTTP/HTTPS URL,例如 http://localhost:8080/v1/document/image/…)。
简单来说,/v1/document/image/{image_id}这些 URL 指向的是 RAGFlow 后端应用程序的 API 端点,而不是直接指向 MinIO 中的静态文件。这种实现方式既保证了图片能被网页正确渲染,又有效地集成了权限管理机制。
*3*
PDF 重组方案增强
RAGFlow 这次的图文混答方案有很多好处,但是 img_id 只有在源文档本身是图片、PPT、PPTX 或 PDF 文件时才会生成。所以随之而来的问题是,如何在保留 RAGFlow 原生图片显示能力(基于 img_id)的同时,对文档进行更精细化的预处理,特别是针对表格内图片这类复杂场景,以确保分块和图片关联符合预期。同样以上述演示的维保案例为例,下面提供一个通过 PyMuPDF 等工具对 PDF 进行预处理和重组的思路参考。
*3.1*
*核心问题*
*PDF 分块不理想*

原始 PDF 的文本和图片布局可能导致 RAGFlow 分块时图文分离
维修案例等结构化内容被错误拆分,影响检索效果
*图文关联不准确*
图片与相关文本内容在不同分块中,检索时无法准确关联
RAGFlow 的 img_id 机制需要合适的文档结构才能发挥最佳效果
*3.2*
*解决方案*
PDF 重组 + RAGFlow 原生处理

预处理阶段:使用 PyMuPDF 提取文本和图片,按业务逻辑重新组织
重组阶段:使用 reportlab 重新生成 PDF,确保每个业务单元(如维修案例)独立成页
RAGFlow 处理:利用 RAGFlow 原生的 DeepDoc 和 img_id 机制处理重组后的 PDF
优化配置:针对重组 PDF 的特点优化分块策略和助手配置

*3.3*
*混合方案考量*
通过 PyMuPDF+reportlab 的 PDF 重组策略,可以实现从"物理布局优先"到"业务语义优先"的转变。在预处理阶段就按照业务逻辑重新组织文档结构,确保每个业务单元(如维修案例)的完整性,让 RAGFlow 的原生能力在更合适的文档结构上发挥作用。
项目源码已发布在星球中


注:
1、上述列举的 PDF 重组策略是个基础演示,方案的有效性依赖于业务逻辑的清晰定义,各位需要结合具体场景和实践进行验证。
2、这次更新的 API 文档中 create_dataset 方法也存在调整,原脚本中的 language 参数、embedding_model 格式和 avatar 参数都有调整,各位更新脚本可以注意下。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)