
1. 從RAG到RIG為什么先生成再檢索救不了多跳問題上個月我們在內(nèi)部知識庫上線的RAG問答系統(tǒng)被業(yè)務(wù)方連續(xù)問倒了三次。三次都栽在同一類問題上A方案和B方案沖突時合同模板里哪一條優(yōu)先這個報錯在舊版本文檔里有沒有對應(yīng)處理——單次檢索抓回來的文檔里根本沒有完整答案。我當時的第一反應(yīng)是加大top_k、擴窗口長度結(jié)果只是把更多噪音塞進了上下文答得更差。后來我換了個思路與其讓系統(tǒng)替模型把檢索提前做完不如讓模型自己在生成過程中隨時喊我需要查資料。這就是RIGRetrieval-Interleaved Generation檢索交織生成的核心想法而我用來落地這個想法的開源框架就是OpenRig。1.1 標準RAG的三個死穴標準的RAG pipeline大家都熟問題進來先向量檢索Top-K文檔塞進上下文再讓模型生成。這個流程對單點事實查詢很管用比如公司的報銷上限是多少。但對稍微復(fù)雜的場景它有三個天生缺陷。第一個是檢索時機問題。檢索發(fā)生在生成之前模型沒有后悔藥。問題本身表述模糊的時候模型在生成中途需要補充的信息一次性檢索根本覆蓋不到。第二個是多跳問題。很多真實業(yè)務(wù)問題要先查事實A再根據(jù)A查事實B最后才能推出結(jié)論。單次檢索沒辦法做到查完A之后帶著A去查B。第三個是上下文污染。為了覆蓋可能性我們把top_k調(diào)大結(jié)果不相關(guān)文檔一起進來模型的注意力被稀釋反而把正確信息淹沒掉。1.2 Agentic路線靈活但不可控既然單次檢索不夠很自然的想法就是上Agent讓模型自己決定調(diào)用檢索工具用ReAct式的循環(huán)拿到結(jié)果再繼續(xù)。這個方向的能力上限確實高多跳、總結(jié)、推理都能做。但落地之后你會發(fā)現(xiàn)另一個麻煩不可控。模型會為了一個只需要查一次的問題連續(xù)調(diào)用三次工具會在兩個證據(jù)矛盾時反復(fù)橫跳不收斂會把上下文窗口塞滿歷史步驟導致token爆炸。每一步都要設(shè)計終止條件、防重入機制、時間限制調(diào)優(yōu)成本高得嚇人。我見過不少團隊在這個路子上投入兩三個月最后產(chǎn)出的系統(tǒng)在評測集上還行一上真實流量就各種超時和循環(huán)。RIG走的是中間路線。它不像RAG那樣檢索一次就拉倒也不像Agent那樣把完整的工具調(diào)用權(quán)交給模型。它的設(shè)計原則很樸素模型在生成時如果發(fā)現(xiàn)自己缺信息就發(fā)出一個檢索信號框架收到信號后去檢索把結(jié)果追加回上下文模型繼續(xù)生成。檢索的決定權(quán)在模型手里但檢索的執(zhí)行是確定性的、單次的、可觀測的不會像Agent那樣產(chǎn)生自由發(fā)揮的工具調(diào)用序列。1.3 OpenRig到底解決什么問題OpenRig這個名字Open是開源Rig在這里不是機器配置的意思而是RIG模式的動詞化——它就是一個把檢索交織生成這套模式工程化的框架。它解決的是一類很具體的問題你的業(yè)務(wù)需要多跳知識推理但又承受不了Agent系統(tǒng)的復(fù)雜度和不確定性。我把它用在合同條款問答和故障診斷兩個場景上效果比預(yù)想好。后面我會從原理、部署、調(diào)優(yōu)、踩坑四個角度展開把我在生產(chǎn)環(huán)境里跑OpenRig的完整經(jīng)驗整理出來。如果你正在做知識庫問答、智能客服、文檔輔助決策這類系統(tǒng)這篇文章應(yīng)該能幫你少走不少彎路。2. OpenRig的核心設(shè)計與工作原理OpenRig的整體架構(gòu)不復(fù)雜但設(shè)計上有個關(guān)鍵選擇它怎么知道模型什么時候需要檢索。這一節(jié)把它的執(zhí)行鏈路拆開講清楚。2.1 一次完整的RIG執(zhí)行鏈路一次OpenRig請求完整走下來是這樣的用戶query進入會話管理器初始上下文 system prompt 歷史對話 query。模型開始流式生成逐token輸出。模型在某個位置輸出一個檢索標記retrieval marker。這個標記可以是特殊token也可以是約定的文本格式OpenRig默認支持|retrieve|這種特殊token也支持函數(shù)調(diào)用風格的JSON輸出。解析器捕獲標記暫停生成提取標記中攜帶的檢索query模型自己生成的查詢詞。檢索器執(zhí)行檢索返回Top-K文檔。結(jié)果按固定模板格式化追加到上下文中模型繼續(xù)生成。重復(fù)步驟2-6直到模型輸出結(jié)束標記或達到max_iterations上限。值得注意的是模型發(fā)出的檢索query不是用戶原話。模型在生成過程中已經(jīng)產(chǎn)生了對問題的中間理解所以它生成的檢索詞往往比原始query更精準。比如用戶問這個錯誤在哪個版本被修復(fù)模型生成到一半發(fā)現(xiàn)需要版本號它會自己生成v2.3.1 changelog bug fix這樣的檢索query。2.2 檢索標記與生成循環(huán)的實現(xiàn)細節(jié)標記機制是整個框架的靈魂。OpenRig采用了一個很務(wù)實的方案不用特殊token做訓練而是用提示詞約束 生成時解析。也就是說框架在system prompt里明確告訴模型如果你需要補充信息輸出|retrieve|需要查詢的內(nèi)容|endofretrieve|推理時解析器去匹配這個結(jié)構(gòu)。這樣做的好處是不需要對模型做任何微調(diào)任何支持復(fù)雜system prompt的模型都能跑。代價是模型偶爾不遵守格式或者在不該檢索的時候亂發(fā)信號。這個坑我后面專門講。底層循環(huán)的偽代碼邏輯大致是這樣def run(question, config): context build_initial_context(question) iterations 0 while iterations config.max_iterations: # 流式生成并監(jiān)聽檢索標記 for chunk in llm.stream(context, stop_markersconfig.stop_markers): if parser.detect_retrieve(chunk.text): retrieve_query parser.extract_query(chunk.text) docs retriever.search(retrieve_query, top_kconfig.top_k) context append_docs(context, docs, chunk.prefix_text) break else: # 沒有檢索標記說明生成正常結(jié)束 return context iterations 1 return context # 達到迭代上限強制返回這個循環(huán)最關(guān)鍵的參數(shù)是max_iterations。設(shè)大了模型可以多跳幾次但延遲和token開銷跟著漲設(shè)小了多跳問題又解決不了。我生產(chǎn)環(huán)境里一般設(shè)3極少有查詢需要超過3次檢索。2.3 與Agent框架相比OpenRig省掉了什么用過LangGraph或自研ReAct框架的人應(yīng)該有同感Agent方案的復(fù)雜度大頭在工具注冊、狀態(tài)管理、循環(huán)控制、終止條件。OpenRig把這些都砍掉了換來的是一個更窄的邊界。維度單次RAGOpenRig (RIG)Agent (ReAct)檢索觸發(fā)方式生成前一次性生成中按需多次工具調(diào)用循環(huán)模型自由度低中高是否有多跳能力弱強強上下文增長固定每次檢索新增步驟日志累積調(diào)試難度低中高可預(yù)測性高中高低這個表格不是想說Agent不好而是想說明它們解決的問題不一樣。如果你的需求是讓模型自主決定怎么完成任務(wù)Agent是對的如果你的需求是讓模型在回答知識類問題時自己補檢索OpenRig這種RIG框架更劃算。我團隊里現(xiàn)在有部分簡單需求直接先用OpenRig跑跑不動了再升級到Agent這個成本曲線平緩很多。3. 本地部署OpenRig從零跑通一個多跳問答服務(wù)3.1 環(huán)境準備與依賴選型OpenRig對運行環(huán)境的要求不算苛刻。我的部署機器是一臺32核CPU、128GB內(nèi)存、單張RTX 4090的服務(wù)器LLM推理用vLLM起了一個Qwen2.5-32B-Instruct服務(wù)向量庫用Milvus檢索器混用了向量檢索和BM25。依賴清單大致如下Python 3.10一個OpenAI兼容的LLM推理服務(wù)vLLM、TGI、Ollama都行向量數(shù)據(jù)庫Milvus / Qdrant / Chroma均可小規(guī)模用Chroma最省事OpenRig本體pip install openrig安裝完成之后先確認LLM服務(wù)能用OpenAI SDK調(diào)用curl http://localhost:8000/v1/models \ -H Authorization: Bearer EMPTY如果返回模型列表說明推理服務(wù)就緒可以進入下一步。3.2 配置向量庫和檢索器OpenRig的檢索接口設(shè)計成可插拔。你只需要實現(xiàn)一個search(query, top_k)返回文檔列表的函數(shù)或者直接用內(nèi)置的Milvus適配器。我習慣把配置寫在一個YAML里統(tǒng)一管理# openrig.yaml model: base_url: http://localhost:8000/v1 name: Qwen2.5-32B-Instruct max_tokens: 512 retriever: type: milvus host: localhost port: 19530 collection: contracts embedding_model: BAAI/bge-large-zh-v1.5 top_k: 5 score_threshold: 0.35 rig: max_iterations: 3 marker_style: special_token # 可選 special_token / json append_format: evidence這里有個我踩過坑的參數(shù)score_threshold。設(shè)置太低垃圾文檔大量進入上下文設(shè)置太高正確的檢索被過濾掉。我建議先跑一批真實查詢把相似度分數(shù)分布打出來再定閾值不要憑感覺拍腦袋。3.3 啟動服務(wù)并驗證檢索觸發(fā)配置寫好后啟動服務(wù)只需要一個Python腳本。OpenRig對外暴露的核心對象是RigSessionfrom openrig import RigSession, RigConfig config RigConfig.from_yaml(openrig.yaml) session RigSession(config) question 供應(yīng)商延遲交付合同里有沒有自動順延交貨期的條款順延期間的質(zhì)量責任怎么算 answer, trace session.ask(question) print(answer) print(檢索過程) for step in trace.retrieval_steps: print(f第{step.round}輪檢索: {step.query}) print(f命中文檔: {[d.title for d in step.docs]})跑完之后如果一切正常日志里能看到多次檢索事件每次檢索的query都是模型在生成過程中自動產(chǎn)出的。第一次跑通的時候我還挺興奮因為它確實做到了模型查到一半主動說我還需要看另一個條款。不過這里有個現(xiàn)象值得注意模型第一次生成的檢索query往往和用戶原話高度重合。真正顯示RIG價值的是第二輪、第三輪檢索那時候的query會帶著上一輪結(jié)果的上下文信息比如順延條款中沒有質(zhì)量責任說明查找違約條款中關(guān)于交付延期責任的規(guī)定。這種帶有推理痕跡的檢索詞是普通RAG給不了的。4. 關(guān)鍵參數(shù)調(diào)優(yōu)與評測方法4.1 迭代次數(shù)和檢索閾值的權(quán)衡max_iterations是最需要認真調(diào)的參數(shù)。從直覺上說多跳問題自然需要多次檢索但每一輪檢索都會帶來三個成本延遲、token、上下文污染風險。我做過一組實驗用合同問答的50條測試問題在max_iterations分別為1、2、3、5時評測max_iterations正確率平均延遲平均token消耗148%2.1s870271%3.4s1350379%4.7s2050578%8.2s4100數(shù)據(jù)看得很清楚3輪之后正確率不再上升成本和延遲卻幾乎翻倍。這就是典型的收益遞減曲線。我現(xiàn)在的建議是新業(yè)務(wù)一律從3起跳用評測數(shù)據(jù)說話不要為了省延遲砍到2除非你的業(yè)務(wù)確認沒有多跳需求。score_threshold的調(diào)法前面提到過另一種更高效的方案是加一個reranker。OpenRig支持在檢索器后面掛rerank層用bge-reranker-v2-m3把Top-K初篩結(jié)果再排一遍只保留排序前2。加上reranker之后我的正確率從79%提到86%延遲增加不到0.5秒性價比很高。4.2 延遲預(yù)算一次多跳查詢到底花了多少時間很多團隊最終不是被正確率勸退的是被延遲勸退的。這里給一個真實的耗時拆解讓你心里有底LLM首token生成1.5s32B模型40904-bit量化第一輪檢索和嵌入0.3s第二輪生成1.8s第二輪檢索0.3s最終生成1.2s總計約5.1s對一個內(nèi)部知識庫問答來說5秒是可接受的但如果你要做面向C端的實時客服就得想辦法壓。我試過幾條路徑用更小的模型14B做前三跳生成最后用32B匯總把embedding模型換成更快的小模型把Milvus索引換成內(nèi)存態(tài)。綜合下來延遲能壓到3秒左右正確率只降2%。4.3 用評測腳本量化收益OpenRig自稱是面向檢索增強生成的工程框架所以它很重視評測閉環(huán)。項目里帶了一個openrig eval命令可以用起來。它支持兩種模式一是跑公開多跳數(shù)據(jù)集比如HotpotQA的子集二是跑你自己的JSONL標注集。我強烈建議用后者因為公開數(shù)據(jù)集和業(yè)務(wù)分布差異太大。評估數(shù)據(jù)格式很簡單JSONL里每行一個問題、標準答案、以及可選的支持文檔列表openrig eval \ --config openrig.yaml \ --dataset ./data/contract_eval.jsonl \ --metric accuracy retrieval_precision token_overhead輸出是一張表格除了最終答案正確率還會報告每次檢索的有效率——也就是這次檢索到的文檔是否真的支持了最終答案。這個指標很有用它能把模型亂檢索但碰巧答對的情況暴露出來。我在初版配置上跑出來retrieval_precision只有42%說明模型有一半的檢索是沒必要的后面通過修改system prompt才把它拉到75%。5. 實測中踩過的坑與規(guī)避方案5.1 模型不配合檢索標記時有時無用OpenRig第一個星期最讓我頭疼的問題是模型經(jīng)常不輸出檢索標記。明明上下文里沒有答案它還是硬編一個。排查之后發(fā)現(xiàn)原因在system prompt的寫法上。OpenRig默認的prompt只寫了如果需要更多信息請使用檢索標記。但指令微調(diào)模型對如果需要這種條件型指令理解得不夠堅決。我把提示詞改成強約束版本效果立竿見影提示 檢索觸發(fā)條件要寫硬規(guī)則不要寫建議。例如你必須檢查當前上下文是否包含足夠信息回答問題。如果信息不充分你必須先輸出檢索標記再生成最終回答。禁止在信息不充分時直接回答。另外不同模型的格式偏好差別很大。Qwen和Yi對特殊token風格接受度高GPT系和Claude系更習慣JSON函數(shù)調(diào)用。如果發(fā)現(xiàn)模型經(jīng)常格式錯誤把marker_style從special_token換成json解析穩(wěn)定性和生成遵守率都會好不少。5.2 上下文膨脹失控多輪檢索意味著多輪文檔追加上下文長度是線性甚至超線性增長的。第一輪檢索塞5個文檔第二輪又塞5個加上前一輪的文檔還在上下文很快就幾千token了。上下文一長模型注意力開始渙散后面檢索的結(jié)果反而覆蓋了前面有用的證據(jù)。我的對策是控制每輪追加的文檔數(shù)量和對舊證據(jù)做壓縮。具體做法top_k設(shè)5但經(jīng)reranker后只保留2個結(jié)果第二輪開始把第一輪的原始文檔替換成模型生成的簡短摘要。這樣既保留了證據(jù)鏈又不讓上下文無限膨脹。OpenRig提供了append_format參數(shù)可以選full或summarysummary模式下框架會自動要求模型對上一輪證據(jù)做三句話以內(nèi)的總結(jié)。5.3 檢索到壞文檔導致越繞越遠RIG一個隱蔽的失敗模式是模型在錯誤證據(jù)的引導下生成一個同樣錯誤的檢索query然后一路錯下去。比如最初檢索到一份已廢止的合同條款模型基于它繼續(xù)追問檢索出的更多文檔都是圍繞舊版的最終答案完全跑偏。這個問題單靠OpenRig參數(shù)解決不了得從數(shù)據(jù)側(cè)動手。我做了三件事第一在向量庫里給文檔加上生效/失效元數(shù)據(jù)檢索時過濾失效文檔第二對高沖突域比如合同版本單獨建索引避免新舊版本混在一起第三在提示詞里加一條如果發(fā)現(xiàn)檢索結(jié)果與當前上下文證據(jù)沖突優(yōu)先信任最新的生效文檔并說明沖突。第三點尤其重要——它讓模型對自己產(chǎn)生的證據(jù)鏈有批判意識而不是被上一輪結(jié)果牽著走。除了以上三個大坑還有個評測期容易踩的隱蔽問題模型在缺少檢索標記的情況下偶爾會假裝用了檢索。它生成的內(nèi)容里直接寫根據(jù)相關(guān)資料顯示但OpenRig的trace里根本沒有檢索事件。這本質(zhì)上是模型幻覺的變體。排查方法也很簡單檢查trace里每一步的證據(jù)來源凡是最終答案引用了不存在于證據(jù)鏈的內(nèi)容都算失敗樣例。6. 什么樣的業(yè)務(wù)適合上OpenRig跑了大半年之后我對這個框架的邊界有了比較清晰的認識。OpenRig不是萬金油但也不是小眾玩具。最適合它的場景有幾類內(nèi)部知識庫問答用戶問題經(jīng)常需要連續(xù)查多個知識點才能回答比如合同、規(guī)章制度、技術(shù)文檔。診斷式客服用戶報一個現(xiàn)象系統(tǒng)需要先查現(xiàn)象對應(yīng)的模塊再查該模塊的常見故障再查修復(fù)步驟。文檔輔助決策比如某個操作是否合規(guī)需要對比多個制度條款后得出結(jié)論。這幾類業(yè)務(wù)的共同點是問題本身有結(jié)構(gòu)答案需要跨文檔拼接同時用戶能接受3到5秒的延遲。反過來如果你的業(yè)務(wù)是高頻簡單問答、低延遲強需求或者你的語料非常單一、一次檢索就有九成把握那老老實實用經(jīng)典RAG反而更好。OpenRig的價值恰好就是讓夠用和過度聰明之間多了一個中間檔。最后分享一個我個人的運維習慣OpenRig跑起來之后一定要把它的trace日志接進監(jiān)控。每次檢索事件、每輪query、每一跳的延遲都記錄下來。我后來發(fā)現(xiàn)很多系統(tǒng)變蠢的case都不是框架壞了而是embedding模型那側(cè)的數(shù)據(jù)沒更新或者新文檔進了向量庫但score_threshold把它們?nèi)^濾掉了。有了trace日志這些問題十分鐘就能定位。工具再智能也不如你手里有一條完整的證據(jù)鏈可查。