究極方案:油猴腳本實現(xiàn)RAG問答前端圖片流式體驗
一個月前發(fā)了篇文章介紹了基于 MiniO 存儲的 RAGFlow+Dfiy 圖片處理方案,之后有幾個知識星球內(nèi)的星友提問,如何優(yōu)化上一版方案中不能流式輸出的問題。
這篇試圖說清楚,三種 RAG 圖片問答的方案迭代過程,油猴腳本 (Tampermonkey)的具體實現(xiàn)方式,以及項目架構(gòu)梳理。
以下,enjoy:
1、多階段方案演進
在現(xiàn)有開源框架基礎(chǔ)上,不改動核心代碼,又要達到較好的流式圖文體驗,這自然會引入一些“變通方案”。本著“規(guī)避 LLM 弱點”、“模塊化處理”、“逐步優(yōu)化體驗”的工程實踐思路,我前后進行了以下三種方案的探索,其中前兩個歷史文章已經(jīng)詳細介紹過,這篇先大致回顧下前兩種的核心理念,后續(xù)主要就第三種方案展開。
1.1直接嵌入 HTML 的局限性
為了能在 RAGFlow、Dify 這類封裝度較高的框架中,回答顯示原文相關(guān)圖片,富文本處理 (圖片轉(zhuǎn) URL)是基礎(chǔ),也是讓圖片有被瀏覽器渲染的可能。我在最開始的工程化嘗試里,采用了最簡單直接的方式,就是通過圖片服務(wù)器容器化方案,把圖片直接使用 <img> 標簽和其公開的 HTTP URL 放處理后的富文本中,這樣只要在回答中輸出圖片 URL,瀏覽器就可以直接渲染。
但 LLM 在處理包含復(fù)雜 HTML 結(jié)構(gòu)或 URL 的文本時,可能會“創(chuàng)造性地”修改它們。尤其明顯的就是 LLM 會想當然的按照 Next token prediction 的套路修改 URL 中的圖片名稱,從而導(dǎo)致加載失敗。
1.2后端占位符替換
為了解決“LLM 可能會改動富文本中的 URL”這個問題,把不穩(wěn)定的 URL 替換為 LLM 更難篡改的、有特定格式的占位符,并把替換邏輯后置,這樣可以盡可能的確保數(shù)據(jù)在經(jīng)過 LLM 后的完整性和可解析性。
具體來說,本地 python 腳本針對原文檔進行預(yù)處理后,提取圖片和 map.json 存入 MinIO,文本中插入占位符上傳至 RAGFlow 的知識庫。緊接著 Dify 工作流中,通過 HTTP 節(jié)點獲取 map.json,Code 節(jié)點進行后端替換。
這個迭代方案的優(yōu)點是最大限度的解決了 LLM 修改圖片鏈接的問題,保證了圖片引用的準確性。但也帶來了新的痛點,就是 Dify Code 節(jié)點的批處理特性,導(dǎo)致圖片無法隨 LLM 的流式回答實時顯示,用戶體驗有明顯的延遲,尤其是針對選擇了帶顯示思維鏈的 LLM 而言,提問后的等待就變得更加反人性。
1.3前端實時渲染
先做下定義掃盲,簡單來說油猴腳本就是個瀏覽器擴展插件,在授權(quán)前提下允許用戶運行自定義 JavaScript 腳本來修改網(wǎng)頁。通俗解釋就是給網(wǎng)頁打“補丁”,增強或改變其功能。選擇這種做法的核心原因是可以非侵入式的修改 Dify 前端行為,免去了改動 Dify/RAGFlow 源碼的風(fēng)險。(直接增強后端流式處理能力或前端渲染邏輯。成本高,且影響框架升級。)
經(jīng)過了前兩個階段的試錯之后,就很自然的過渡到這個前端實時渲染的方案。引入油猴腳本后,就是把圖片占位符的替換工作從 Dify 后端轉(zhuǎn)移到用戶瀏覽器前端,從而實現(xiàn)圖片隨 LLM 文本流式輸出同步顯示,優(yōu)化用戶的使用體驗。
總結(jié)來說,目前主流的、主要基于文本處理的 LLM(即使是某些聲稱支持多模態(tài)的,在處理嵌入式 HTML 時也不完美),都不擅長精確保持和輸出復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)如 HTML。上述后兩種方案討論的“占位符+映射替換”策略,本質(zhì)上是將“結(jié)構(gòu)化內(nèi)容渲染”這個任務(wù)從 LLM 的職責(zé)中剝離出來,交給了后續(xù)更可控的程序(Code 節(jié)點或前端腳本),這是一種有效的規(guī)避 LLM 短板的思路。
如果使用 Langchain、LlamaIndex 這些自主開發(fā) RAG 系統(tǒng),在回答中顯示圖片的效果會簡化很多,但圖片提取、存儲和 URL 管理的復(fù)雜性依然存在,而且需要編寫更多的代碼來搭建整個應(yīng)用。
2、項目架構(gòu)圖
為了讓大家看的清楚些,整了張流程圖再還原下整個數(shù)據(jù)流和處理環(huán)節(jié):從 PDF 到 MinIO,再到 RAGFlow 知識庫,Dify 調(diào)用,LLM 處理,最終油猴腳本渲染。
3、油猴腳本的具體實現(xiàn)
3.1@match 指令
這個指令位于油猴腳本的元數(shù)據(jù)頭部(// ==UserScript== 塊內(nèi)),它告訴 Tampermonkey 擴展只有當用戶訪問的網(wǎng)址符合我列出的這些模式時,才需要激活并運行我這個腳本。
在這個本項目中, 需要將 @match 指令精確配置為 Dify 應(yīng)用的聊天頁面 URL 模式,例如 http://localhost/chat/* 或 ://<你的 Dify 域名>/app//chat/*。這樣,只有當用戶進入 Dify 聊天界面時,圖片替換腳本才會被激活。
3.2map.json 的 URL 配置
我們的腳本需要知道哪個圖片占位符(如 [IMG::image1.png])對應(yīng)哪張真實的圖片(如 http://minio-server/bucket/image1.png)。 這個對應(yīng)關(guān)系存儲在由 process_pdf.py 腳本生成并上傳到 MinIO 的 map.json 文件中。腳本內(nèi)部通常會有一個變量(例如 mappingFileUrl)來存儲這個 map.json 文件的完整 HTTP URL。腳本在初始化時,會通過這個 URL 異步下載并解析 map.json 的內(nèi)容。
在這個本項目中: 這個 URL(例如 http://localhost:9000/ragflow-public-assets/mappings/demo_map.json) 是在腳本首次運行時通過提示框讓用戶輸入,或者直接在腳本中配置一個默認值。正確配置此 URL 是腳本能否找到正確圖片鏈接的關(guān)鍵。它是連接占位符與實際圖片的橋梁。]
3.3CSS 選擇器
為了在網(wǎng)頁上準確地找到需要處理的內(nèi)容(即機器人回復(fù)的聊天氣泡,以及包含這些氣泡的總?cè)萜鳎?,需要使?CSS 選擇器幫助腳本在復(fù)雜的 HTML 結(jié)構(gòu)中定位到目標元素。
注:通過瀏覽器開發(fā)者工具(F12)仔細檢查 Dify 聊天頁面的 HTML 代碼,找到能夠穩(wěn)定標識機器人消息(例如,通過特定的 class 名如 div.markdown-body 或更復(fù)雜的組合選擇器)和聊天總?cè)萜鞯?CSS 選擇器,并將其配置在腳本中。
BOT_MESSAGE_SELECTOR:用于定位每一條由機器人發(fā)出的消息的 HTML 元素。腳本將在這個元素內(nèi)部查找并替換圖片占位符。CHAT_MESSAGE_CONTAINER_SELECTOR:用于定位包裹所有聊天消息(用戶和機器人的)的父容器。MutationObserver 會監(jiān)控這個容器的內(nèi)容變化。
這是腳本能否正確工作的核心中的核心。如果這些選擇器不正確或不夠精確,腳本可能根本找不到機器人回復(fù),或者錯誤地修改了頁面的其他部分。由于 Dify(或其他任何 Web 應(yīng)用)的前端 HTML 結(jié)構(gòu)可能更新,這些選擇器是最需要根據(jù)實際情況進行細致調(diào)試和配置的部分。
3.4油猴腳本的局限性
首先是用戶端依賴問題,因為不向后端服務(wù)是面向所有用戶修改的,油猴腳本的使用需要每個用戶在自己的瀏覽器上安裝 Tampermonkey 擴展并安裝該特定腳本,這個雖然也不復(fù)雜,但確實增加了一定的用戶側(cè)操作成本。
其次是前端結(jié)構(gòu)依賴問題,因為腳本強依賴于 Dify 前端頁面的 HTML 結(jié)構(gòu)(CSS 選擇器),如果 Dify 前端更新導(dǎo)致結(jié)構(gòu)變化,腳本可能失效,所以可能需要不定期維護。
4、寫在最后
4.1RAGFlow+Dify 組合的必要性
在當前這個方案中,RAGFlow 負責(zé)知識庫處理,Dify 負責(zé)應(yīng)用層和交互,油猴腳本負責(zé)前端體驗增強,三者協(xié)同工作。即使圖片渲染由油猴腳本在前端完成,Dify 作為應(yīng)用編排和用戶交互界面的價值依然存在,尤其是在面對更復(fù)雜工作流編排的需求時。
4.2油猴腳本的一些其他玩法
對于沒有接觸過油猴腳本的盆友,這里再列舉兩個經(jīng)典的用法,感興趣的可以試試看:
API 接口調(diào)試與 Mock 數(shù)據(jù)注入
在前端開發(fā)或與后端進行接口聯(lián)調(diào)時,經(jīng)常會遇到后端接口尚未開發(fā)完成、接口不穩(wěn)定、或者需要特定返回數(shù)據(jù)才能觸發(fā)某些前端邏輯的情況。油猴腳本可以用來攔截前端發(fā)起的 API 請求(通常是 XMLHttpRequest 或 fetch),并對其進行修改或直接返回一個預(yù)設(shè)的(Mock)響應(yīng)。
網(wǎng)頁自動化測試與表單填充
對于一些需要頻繁進行回歸測試的 Web 應(yīng)用,或者需要重復(fù)填寫大量表單的場景,油猴腳本可以編寫自動化腳本來模擬用戶操作,如點擊按鈕、輸入文本、選擇下拉框、提交表單等。
4.3“雕花”的階段性選擇
從步驟數(shù)量和涉及的組件來看,這個項目流程顯得有些復(fù)雜。但這種復(fù)雜性也往往源于我們試圖在現(xiàn)有框架(Dify/RAGFlow等)的限制下,實現(xiàn)一個它們并未原生完美支持的流式圖文混合輸出。
隨著多模態(tài) LLM 能力的增強,也許能更直接地處理和引用上下文中的圖片,減少對占位符的依賴,或者輸出更結(jié)構(gòu)化的指令來顯示圖片。 引導(dǎo) LLM 輸出更結(jié)構(gòu)化的數(shù)據(jù)(如 JSON 對象列表,明確指示文本和圖片),而不是自由文本中夾雜占位符,能讓后續(xù)處理更簡單。
Anyway,圖片回答更多是錦上添花,文檔預(yù)處理和分塊等基礎(chǔ)環(huán)節(jié)的優(yōu)先級依然是最高的。