RAGFlow v0.19圖文混排:詳細拆解+預處理增強案例
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 渲染中通過<img>標簽或 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 <img>標簽。
這種做法的核心是利用 MinIO 存儲、占位符機制、后端(Dify Code 節點)替換邏輯,增強了 URL 的穩定性,但 Dify Code 節點批處理導致的非流式輸出會帶來明顯的體驗問題。(當時選擇 RAGFlow+Dify 的拼接方案,主要是受制于 RAGFlow 的 agent 模塊還不支持自定義的 code 節點。)
1.3體驗提升
為了解決后端替換帶來的延遲和非流式問題,我選擇把圖片占位符的替換工作從后端(Dify)轉移到了用戶瀏覽器前端。通過編寫油猴腳本 (Tampermonkey script)在 Dify 聊天界面加載的時候,腳本異步獲取 map.json 文件,然后實時監控 LLM 的流式文本輸出,一旦檢測到預設的圖片占位符,就立即將其在前端動態替換為<img>標簽,從而實現圖片隨文本流式同步顯示。
選擇這種做法的核心原因是可以非侵入式的修改 Dify 前端行為,免去了直接增強 RAGFlow 后端流式處理能力或前端渲染邏輯的修改風險。畢竟這種做法成本高,且影響框架升級。
2、v0.19 的圖片顯示邏輯
為了方便大家理解,這部分我從 API 文檔作為切入點,再過渡到后端代碼實現理解,最后通過實際操作中的部分截圖來印證和展示效果。
注:為了 升級 v0.19,不需要重新部署。修改 ragflow/docker/.env 文件,更新 RAGFLOW_IMAGE 變量為你想要升級到的版本號。然后運行下述 Docker 命令會拉取 .env 文件中指定的新版本鏡像,并使用新的鏡像重新創建和啟動容器。(由于數據卷是獨立于容器的,新的容器會重新掛載已存在的數據卷,從而保留你的歷史項目文件。)
# 修改env
RAGFLOW_IMAGE=infiniflow/ragflow:v0.19.0
#在docker目錄下運行
docker compose -f docker/docker-compose.yml pull
docker compose -f docker/docker-compose.yml up -d
2.1API 接口
打開 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() - st
except 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_id
del 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’面板(如圖右側所示),可以審查這些圖片預覽對應的<img>標簽的 src 屬性。可以發現,它并不是一個指向 MinIO 或其他靜態文件服務器的直接圖片 URL。相反,它的路徑是類似/v1/document/image/{image_id}這樣的格式。這清晰地表明,前端是通過調用 RAGFlow 后端的一個特定 API 接口來獲取圖片內容的,而 image_id 正是這個接口定位具體圖片的關鍵參數。
注:通過 API 端點提供圖片的方式,是更符合最佳實踐實現精細化權限控制的推薦做法。 它不像直接暴露靜態文件 URL 那樣難以控制訪問。每一次圖片請求都需要經過后端的鑒權邏輯。
渲染邏輯總結
瀏覽器要加載并渲染一個外部資源(如圖片、iframe 內容、通過 AJAX 獲取的數據),它必須通過一個 URL 來定位這個資源。所以,可能會有盆友好奇縮略圖和 Document Previewer 既然“不是直接的 MinIO URL”,是怎么在前端頁面顯示的?這個疑問的關鍵是理解“URL”的含義和瀏覽器的工作方式。
當我們說它“不是直接的 MinIO URL”時,指的是<img>標簽的 src 屬性或者 Document Previewer 加載內容的源,并不是一個指向 MinIO 存儲桶中某個文件對象的永久、公開、任何人都可以直接訪問的靜態鏈接(比如 http://minio.example.com/mybucket/myimage.jpg這種)。以縮略圖為例,其<img>標簽的 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 參數都有調整,各位更新腳本可以注意下。
4、寫在最后
RAGFlow 的 img_id 方案作為原生內置功能,具有顯著的工程優勢。這種設計簡化了用戶端配置,提高了系統穩定性和集成度,從架構層面規避了 LLM 篡改圖片鏈接的風險。統一的后端 API 調用為權限控制和圖片資源管理提供了堅實基礎,這對企業級應用的安全性和可維護性至關重要。
然而,原生方案也帶來了文檔預處理的復雜性。RAGFlow 的分塊算法主要基于文本結構和版面分析,對于復雜的業務文檔(如維修手冊、技術規范等),往往難以準確識別業務語義邊界,容易出現圖文分離、上下文割裂等問題。特別是當圖片與相關文本在物理布局上分散時,傳統的分塊策略很難保持它們的邏輯關聯性。
上述演示的PDF重組方案,主要目的是在保留 RAGFlow 原生 img_id 機制的所有優勢(穩定性、安全性、企業級特性)的同時,又通過符合業務邏輯的預處理解決復雜文檔的前置打磨。它不是對原生方案的替代,更多是一種增強和優化,讓 RAGFlow 在處理結構化業務文檔時表現更佳。