大家好,我是程序員魚皮。
最近這兩年,只要你接觸過 AI 編程,大概率聽過一個詞,RAG(Retrieval-Augmented Generation)。
注意,是 RAG,不是 RAP!
很多小伙伴對 RAG 都只是一知半解,導致面試的時候只能說出 “檢索增強生成” 這六個字,面試官再多問一點,就只能 “阿巴阿巴”。
而這篇文章,是對 RAG 技術的一個全景科普,從最初的 Naive RAG、到現在最主流的 Agentic RAG,總共 16 種主流 RAG 方案,我會一次性給大家講清楚。以后開發 AI 應用的過程中,無論遇到什么場景,都能選擇最合適的 RAG 方案。
![]()
干貨比較多,建議先點贊收藏,再慢慢往下看~
什么是 RAG?
AI 大模型有一些硬傷,比如:
知識有截止日期
會一本正經地胡說八道,也就是我們常說的幻覺
缺乏私有知識,了解不到內部的文檔寫了什么
比如問 DeepSeek:程序員魚皮的最新項目是什么?
結果它給我扯了個兩年半以前的項目出來,技術棧也完全不對!
![]()
解決這個問題就可以用 RAG。RAG 的核心思想是 先搜再答,讓大模型在回答之前先去搜一遍相關資料,再基于搜到的知識來組織答案。
就跟考試的時候偷偷翻書一樣,遇到不會的先翻一翻書,再根據書里的知識答題。
![]()
還是問 AI 同樣的問題,我們主動給 AI 一些參考資料,他的回答就會準確一些:
![]()
這個思路聽起來簡單,但在實際工程上 RAG 已經演化出了很多種不同的實現方法,從最初的「切塊 → 搜索 → 生成」,到讓 AI Agent 自主決策檢索策略的 Agentic RAG,復雜度和能力天差地別。
有朋友可能會問:現在的大模型不是已經支持百萬 token 的上下文窗口了,還需要 RAG 嗎?
答案是:需要,而且用得比以前更多了!
因為把所有文檔塞進上下文窗口,既貴又不靠譜。上下文越長 token 費用越高,而且大模型普遍存在 “Lost in the Middle” 問題,顧名思義,就是對超長上下文中間部分的注意力會明顯下降。
這個也不難理解,就像聽別人說話一樣,我們對開頭和結尾的印象會相對深刻一些,中間的總是容易忘記。
不過呢,RAG 和長上下文也不是互斥的關系,現在一般的最佳實踐是先用 RAG 給 AI 提供相對精確的資料,再利用長上下文窗口進行有針對性的分析推理,兩者互補。
好,背景交代完了。下面我們正式進入主題,挨個講講每種 RAG 方案。
我們先從最基礎的 RAG 講起,如果你之前完全沒接觸過 RAG,看完這一節就能理解它的核心原理。
主流 RAG 方案 標準 RAG 及變體 Naive RAG
Naive RAG 這個詞聽起來就很牛對不對?
但其實,Naive 是樸素的意思,Naive RAG 是最基本的 RAG 實現方案。
假設你有一份 200 頁的公司員工手冊,怎么讓 AI 基于里面的內容回答員工的提問呢?
最簡單粗暴的做法就是每次提問都把整本手冊塞給 AI 大模型。
但是一本手冊可能幾十萬字,全塞進去又貴又慢,而且上下文一長,大模型還容易犯前面提到的 “Lost in the Middle” 的毛病,導致回答質量下降。
所以更合理的思路是:員工問什么,我們就只把相關的幾段內容塞給 AI。
那問題又來了,怎么定位到相關的那幾段呢?
如果用關鍵詞匹配,很容易出現問題和文檔里的關鍵詞不一致的問題,比如員工問 “老板不批假怎么辦”,文檔里寫的是 “請假審批流程”,關鍵詞對不上,就搜不到。
這就需要用到 向量 了。
所謂向量,就是把一段文字用一串數字表示出來,讓計算機可以比較語義上的相似度。
舉幾個例子感受一下:
文本
向量(簡化示意)
我喜歡吃魚
[0.21, 0.85, 0.13, ...]
我愛吃海鮮
[0.23, 0.82, 0.15, ...]
今天天氣真好
[0.88, 0.12, 0.41, ...]
語義越接近的句子,它們的向量在數學空間里離得越近。
負責把文字轉成向量的模型,叫 Embedding 模型;存儲這些向量并支持快速相似度搜索的數據庫,叫向量數據庫,比如 Milvus、Chroma、Qdrant 等。
理解了向量,Naive RAG 的做法就很好理解了,主要分為兩步。
第一步是離線索引:
把文檔切成小塊(chunk),每塊幾百字
用 Embedding 模型把每個小塊轉成向量
把向量和對應原文都存進向量數據庫
第二步是在線查詢問答:
把用戶問題也用 Embedding 模型轉成向量
去向量庫里搜最相似的幾個文檔塊(比如 Top 5)
把這幾個塊和用戶問題拼成 Prompt,交給大模型生成回答
回到開頭那份 200 頁的員工手冊。我們先把它切成幾百個小塊并向量化入庫,當員工問 “年假有多少天?” 的時候,RAG 的執行流程是這樣的:
系統把問題轉成向量
在向量庫里找到最相似的 5 個文檔塊(比如某塊寫著 “入職滿一年享有 10 天年假,滿三年 15 天…”)
把這 5 個塊連同問題一起交給大模型
大模型回答:“入職滿一年享有 10 天年假”
不過 Naive RAG 也有一些比較明顯的問題:
切塊方式粗暴,可能把一段完整的語義從中間截斷
檢索質量完全依賴 embedding 模型,搜不到就沒轍
搜到了垃圾文檔也不管,導致輸出錯誤答案
這些局限,就是后面所有進階方法要解決的問題。
下面我用偽代碼來幫助大家理解,不熟悉編程的同學可以跳過,不影響后續的學習~
1)離線索引階段:
# 1. 把文檔切成小塊,chunk_size=500 表示每塊 500 字,
# chunk_overlap=50 表示相鄰塊之間重疊 50 字,避免關鍵信息被切斷
chunks = split_into_chunks(documents, chunk_size=500, chunk_overlap=50)# 2. 把每個小塊轉成向量,連同原文一起存入向量數據庫
for each chunk in chunks:
vector = embedding_model.encode(chunk) # 調用 Embedding 模型編碼
vector_store.insert(vector, chunk) # 向量和原文一起入庫
2)在線查詢階段:
Multi-Query RAG# 1. 用同一個 Embedding 模型把用戶問題也轉成向量
query_vector = embedding_model.encode(user_query)
# 2. 在向量庫里搜最相似的 Top 5 文檔塊
top_k_chunks = vector_store.search(query_vector, k=5)
# 3. 把檢索到的文檔塊拼進 Prompt,作為參考資料
prompt = "基于以下參考資料回答問題:\n" + join(top_k_chunks) + "\n問題:" + user_query# 4. 交給大模型生成最終回答
answer = LLM.generate(prompt)
用戶提問的方式千奇百怪。比如同樣是想知道公司報銷流程,有人會問怎么報銷,有人會問費用審批流程是什么,還有人會問花了錢怎么找公司要回來。
但文檔里可能只寫了 “報銷申請流程” 這個表述,如果用戶的措辭和文檔差距太大,向量檢索就可能搜不到正確的內容。
Multi-Query 的思路就是:既然一種問法搜不全,那就讓大模型把原始問題改成多種不同的表述,分別去搜,最后把結果合并去重。
![]()
這種方法的代價就是每次提問要多調用一次 LLM 做改寫,再多跑 N 次向量檢索,延遲和成本都會增加。
而且如果 LLM 改寫出的問題方向跑偏,會把無關文檔也帶進來,影響答案質量。
所以它比較適合 面向普通用戶的客服、電商等場景,用戶表述口語化、和文檔術語差距大,多花這點成本還是有必要的。但是在術語規范的專業領域,Multi-Query 的收益就很有限了。
Multi-Query RAG 的代碼實現如下:
HyDE# 1. 讓 LLM 把原問題改寫成多個表述
queries = LLM.generate("請將以下問題改寫成 3 個不同的表述:" + user_query)
// 例如 AI 返回: ["報銷流程是什么", "費用審批怎么操作", "如何提交報銷申請"]
# 2. 每個表述分別走一次向量檢索
all_results = []
for each query in queries:
results = vector_store.search(embed(query), k=5)
all_results.append(results)# 3. 合并去重,得到覆蓋面更廣的候選文檔
merged_chunks = deduplicate(all_results)
# 4. 把合并后的文檔 + 原始問題交給大模型生成回答
answer = LLM.generate(merged_chunks + user_query)
AI 回復的效果不好,可能不是因為用戶的問法不好,而是用戶的問題和文檔的語義空間不一致。
用戶的提問往往很短,比如 “KV Cache 是什么?”,就這么幾個字。
但文檔里關于 KV Cache 的描述可能是一大段技術解釋。一短一長,在 embedding 空間中可能離得很遠,就檢索不到了。
HyDE 的做法就是讓大模型憑空寫一個答案(不必完全準確),然后用這段假答案的向量去檢索。因為假答案和真文檔的文體更接近,兩者在向量空間中離得也更近。
還是上面的例子,用戶問:“KV Cache 是什么?”
LLM 先編一段假答案:“KV Cache 是一種在 Transformer 推理過程中緩存 Key 和 Value 矩陣的優化技術,可以避免重復計算…”
這段假答案雖然不一定完全準確,但它的向量和真實文檔里關于 KV Cache 的那段描述非常接近,所以能精準命中正確的文檔。
![]()
不過 HyDE 也有一個風險,如果 LLM 編的假答案方向完全跑偏了(比如把 KV Cache 理解成了 Redis 緩存),那檢索結果就會更差。所以它比較適合 LLM 對問題領域有基本認知的場景,冷門領域或企業私有術語慎用。
HyDE 的代碼示例如下:
# 1. 讓 LLM 先憑空生成一段“假答案”(不必完全準確)
hypothetical_answer = LLM.generate("請回答:" + user_query)
# 2. 用假答案的向量去檢索,因為它的文體更接近真實文檔
hyp_vector = embedding_model.encode(hypothetical_answer)
top_k_chunks = vector_store.search(hyp_vector, k=5)# 3. 把檢索到的真實文檔 + 原始問題交給大模型生成最終回答
answer = LLM.generate(top_k_chunks + user_query)
上面這三種方法,解決的是「能不能搜到」的問題。但搜到之后,資料的質量好不好呢?這就是下面要處理的了。
提升檢索質量 語義分塊(Semantic Chunking)
Naive RAG 里最粗暴的一步就是切塊,每 500 個字切一刀,管你是不是正好切在一句話中間。
比如某塊文本是 “員工請假需要提前 3 天申請,超過 5 天需要”
剛好在這里被截斷了,下一個塊從 “部門經理審批” 開始。
這兩個塊單獨看都不完整,檢索和 AI 的理解都會打折扣。
雖然可以通過 chunk_overlap 設置相鄰塊之間重復內容的長度,但是效果相對有限。
語義分塊的做法是先把文檔按句子拆開,計算相鄰句子的 embedding 相似度,當相似度突然下降時,說明話題變了,就在這里切一刀,盡量保證每個句子的語義都是完整的:
![]()
其余的步驟就跟 Naive RAG 一致了。
不過這種方式代價也不小,首先要為每一句話都算一次 embedding,成本和耗時比按字數切分要高。
而且相似度閾值非常難調,所以它比較適合結構松散、話題變化比較快的文檔,像會議紀要、訪談記錄這些。
對于本身就有清晰章節結構的技術手冊、產品說明文檔,直接按標題切效果也不差,還更便宜。
語義分塊的偽代碼示例如下:
層級索引(Parent-Child Retrieval)# 1. 把文檔拆成單句
sentences = split_into_sentences(document)
chunks = []
current_chunk = [sentences[0]]# 2. 遍歷相鄰句子,計算 embedding 相似度
for i from 1 to len(sentences) - 1:
similarity = cosine_similarity(embed(sentences[i-1]), embed(sentences[i]))
# 相似度驟降,話題切換
if similarity < threshold:
chunks.append(join(current_chunk))
current_chunk = []
current_chunk.append(sentences[i])
切塊這件事有一個天然矛盾。切得小吧,檢索精度高但上下文不足;切得大吧,上下文豐富但噪聲也大。
Parent-Child Retrieval 的策略則是 兩層都要。
先把文檔切成大塊,再把每個大塊細分成小塊。檢索時用小塊匹配,命中后返回它所屬的大塊。
相當于在書里搜到了某一句話時,讀的時候把它所在的整個章節都拿過來看:
![]()
學過數據庫的朋友們應該對這種思路不陌生吧?
這種方案就很適合長文檔場景,比如技術手冊、法律合同、產品文檔這些內容,往往需要連帶上下文一起看才能理解。
層級索引的偽代碼實現如下:
# 1. 離線階段:文檔先切大塊,再在大塊內切小塊
for each document:
parent_chunks = split_into_sections(document)
for each parent in parent_chunks:
child_chunks = split_into_paragraphs(parent)
# 只對小塊建向量索引,但保留它屬于哪個大塊
for each child in child_chunks:
index.insert(embed(child), child, parent_id=parent.id)
# 2. 在線階段:用小塊做精確匹配
matched_children = index.search(embed(query), k=5)
# 3. 但返回的是小塊所屬的大塊,保證上下文完整
parent_ids = unique([child.parent_id for child in matched_children])
context = [get_parent_chunk(pid) for pid in parent_ids]
# 4. 把大塊作為上下文交給大模型
answer = LLM.generate(context + query)
Hybrid Search 混合檢索純向量檢索有一個缺點,就是無法精確術語匹配。
比如用戶問:“ERROR_CODE_4012 是什么意思?”
向量檢索會去找語義相似的內容。
但這種編碼本身沒什么語義,向量搜索可能找到一堆講錯誤處理的段落,就是找不到那個精確提到 4012 的段落。
而傳統的關鍵詞搜索擅長精確匹配,但不理解語義。
比如搜 “如何退款”,就搜不到寫著 “退貨及返還貨款流程” 的文檔,類似數據庫里的 like 操作。
Hybrid Search 就是同時使用兩種搜索,然后合并排序。向量搜索負責語義理解,BM25 負責精確匹配,通過 RRF(Reciprocal Rank Fusion 倒數排序融合)算法,把兩邊的結果合并成一個排序。
![]()
幾乎所有生產環境都建議用 Hybrid Search 替代純向量搜索,尤其是像技術文檔、醫療、法律等術語密集的領域。
Hybrid Search 混合檢索的實現代碼如下:
Reranking 精排# 1. 同時跑兩路檢索,各自召回 Top 20
semantic_results = vector_store.search(embed(query), k=20)
keyword_results = bm25_index.search(query, k=20)
# 2. 用 RRF 算法融合兩路結果,公式:1 / (60 + 排名),分數越高越相關
for each doc in (semantic_results ∪ keyword_results):
score = 0
if doc in semantic_results: score += 1 / (60 + rank_in_semantic)
if doc in keyword_results: score += 1 / (60 + rank_in_keyword)# 3. 按融合后的分數排序,取 Top 5 交給大模型
final_results = sort_by_score(all_docs, top_k=5)
answer = LLM.generate(final_results + query)
不管是向量搜索還是 Hybrid Search,檢索回來的候選文檔里總會混著一些看起來相關但實際沒用的噪聲。
Reranking 的做法是在檢索和生成之間加一個精排步驟:用 Reranker 模型給每對 (query, doc) 重新打分。
前面提到的 embedding 模型是分別給 query 和 doc 算向量再比較距離,快但粗糙,Reranker 是把它們拼在一起送進模型打分,慢但精準。
在實際生產中,如果語料庫有十萬級以上的 chunk,一般會采用級聯檢索方案,分層篩選:
![]()
為什么分這么多層呢?
如果使用粗檢索,只撈 20 個內容,在大型語料庫里很容易漏掉關鍵文檔;但如果一次性撈 150 個,全送進 Cross-Encoder 交叉編碼器來精確計算兩個文本片段的相關性分數,算力又扛不住。
分層篩選則是在召回率和計算成本之間找平衡。當語料庫里的 chunk 比較多時,Reranking 的效果提升會非常顯著。
可以這么理解,粗檢索負責不遺漏,精排負責不摻假,跟推薦系統里的粗排和精排很像。
分層篩選的實現代碼如下:
# 第一層:粗檢索,保證召回率
candidates = hybrid_search(query, k=150)
# 第二層:輕量 Reranker 初篩
semi_final = lightweight_reranker.rank(query, candidates, top_k=20)
# 第三層:Cross-Encoder 精排,只處理 20 個
scored = []
for each doc in semi_final:
score = cross_encoder.score(query, doc)
scored.append((doc, score))top_docs = top_k(scored, k=5)
answer = LLM.generate(top_docs + query)
到這里,我們已經能搭出一個相當不錯的 RAG 系統了,語義分塊 + Hybrid Search + Reranking,這三板斧組合起來,就是大多數生產級 RAG 系統的基礎配置。
前面的方法都在優化怎么搜得更準,但有一個更根本的問題沒解決:如果搜到的全是垃圾,大模型還是會一本正經地基于這些垃圾內容生成答案。
這就像開卷考試帶錯了書,還照著抄了上去……
所以接下來我們要聊一聊,怎么讓 RAG 學會自我糾錯。
RAG 反思機制 Corrective RAG(CRAG)
不管我們怎么優化檢索,總會有搜不到或者搜歪了的情況,這時候大模型如果還硬著頭皮拿這些內容去回答,那就避免不了一本正經地胡說八道。
而 CRAG 就是 把搜到的資料過濾一遍 來解決這個問題的。
具體做法就是在檢索和生成之間插一個質檢員,逐個審查檢索到的文檔是否和問題相關,然后根據審查結果走不同的分支。
打分高的,說明資料靠譜,直接喂給大模型生成答案
打分低的,說明內部知識庫里壓根沒找著相關內容,干脆回退到 Web 搜索兜底
打分模糊的,就兩邊的結果合一起送進去
Corrective RAG 的示例實現代碼如下:
# 1. 先跑一遍常規檢索
docs = retriever.search(query, k=5)
# 2. 逐個讓 LLM 判斷文檔是否和問題相關
relevant_docs = []
for each doc in docs:
score = LLM.judge("這段內容和問題相關嗎?", query, doc)
if score > threshold:
relevant_docs.append(doc)# 3. 根據審查結果走不同分支
if len(relevant_docs) > 0:
answer = LLM.generate(relevant_docs + query)
else:
new_query = LLM.rewrite(query)
web_results = web_search(new_query)
answer = LLM.generate(web_results + query)
最關鍵的是那個 else 分支,如果內部知識庫完全搜不到有用信息,CRAG 會自動回退到 Web 搜索(當然也可以是其他策略)。
Self-RAG
CRAG 在檢索階段做了質檢,但是生成階段呢?
大模型完全有可能拿到了正確的參考資料,但回答的時候夾帶私貨,在答案里摻入了參考資料中根本沒提到的內容。這就是生成階段的幻覺。
Self-RAG 的思路是在整個流程中設置四個檢查點,每一步都讓模型自我審視:
這個問題需要檢索嗎(Retrieve)?
檢索到的文檔相關嗎(IsRel)?
我的回答有文檔支撐嗎(IsSup)?
這個答案對用戶有用嗎(IsUse)?
Self-RAG 的示例實現代碼如下:
# 1. 判斷這個問題需不需要檢索
need_retrieval = LLM.judge("這個問題需要外部知識嗎?", query)
ifnot need_retrieval:
return LLM.generate(query)
# 2. 檢索 + 逐個過濾相關文檔
docs = retriever.search(query, k=5)
relevant_docs = filter(docs, where LLM.judge("和問題相關嗎?") == true)
# 3. 生成初步答案
answer = LLM.generate(relevant_docs + query)# 4. 檢查答案的每個論斷是否都有文檔依據
is_supported = LLM.judge("答案中的每個論斷都能在文檔中找到依據嗎?", answer, relevant_docs)
ifnot is_supported:
answer = LLM.regenerate(relevant_docs + query + "請嚴格基于參考資料回答")
一般來說第三個檢查點的價值相對較高,能在一定程度上避免 AI 的幻覺。
Adaptive RAG
CRAG 和 Self-RAG 都在給 RAG 加流程來解決問題,但這樣多了好幾次 LLM 調用,增加了成本。
如果用戶問的是 “你好” 或者 “今天星期幾”,還要跑一遍完整的檢索 + 質檢 + 生成流程,有點開著坦克去買菜的意思,純屬浪費。
Adaptive RAG 在最前面加了一個路由器(分類器),先判斷問題的復雜度,然后決定走哪條路線:
![]()
Adaptive RAG 的實現代碼很簡單:
complexity = classifier.predict(query)# 簡單問題直接答
if complexity == "simple":
answer = LLM.generate(query)
# 一般問題檢索一次
else if complexity == "moderate":
docs = retriever.search(query)
answer = LLM.generate(docs + query)
# 復雜問題采用 crag
else:
answer = run_full_crag_pipeline(query)
這個分類器可以是一個微調的小模型,也可以用 LLM 的 few-shot 少樣本提示來實現,關鍵是讓簡單問題和復雜問題分別處理。
這種方案適合流量混雜的場景,比如既有 “公司地址在哪” 這種一句話能答的問題,又有 “對比 A 和 B 兩個方案的優缺點” 這種需要多文檔綜合分析的復雜問題。
上面這些方法處理的都是非結構化文本,但如果我們的數據是關系網絡或者表格呢?那就需要下面的方法了。
結構化知識增強 GraphRAG
前面這些方法都有一個共同的問題,就是答案必須落在某一個文檔塊里。
但現實里經常有一類問題:答案散落在多個文檔中,需要串起來推理。
舉個例子,假設文檔庫里有兩段話。
文檔 A 寫著 “張三是 AI 部門的負責人”
文檔 B 寫著 “AI 部門屬于技術中心”
用戶問:“張三屬于哪個中心?”
傳統向量檢索大概率只能搜到文檔 A,但要回答這個問題,必須把 A 和 B 連起來推理:張三 → AI 部門 → 技術中心。
這種跨文檔的多跳推理,純向量搜索就很難搞定了。
GraphRAG 就是用來解決這種問題的,這是 Microsoft Research 在 2024 年提出的方法。
![]()
它的思路是先把文檔變成知識圖譜,再基于圖譜來檢索和推理。
具體做法分為 3 步:
用 LLM 逐篇讀文檔,抽取里面的實體(人、部門、產品等)和關系,構建成一張圖譜
用 Leiden 算法對圖譜做社區劃分,把關聯緊密的實體聚成一團,然后讓 LLM 為每個社區生成一段摘要
提問時先定位到相關實體,沿著關系拿到子圖
根據微軟的評測,在全局語義理解類問題上,GraphRAG 答案的全面性和多樣性顯著優于傳統向量 RAG。
但對于簡單的事實查詢,兩者效果差不多,就沒必要用了。
需要注意的是,GraphRAG 要用 LLM 逐篇抽實體關系,圖譜構建成本比向量索引高得多,查詢延遲也更大,所以使用 GraphRAG 之前一定要評估是否必要!
GraphRAG 的偽代碼實現如下:
Text-to-SQL RAG# 1. 離線階段:用 LLM 從每篇文檔里抽取實體和關系,構建知識圖譜
for each document:
entities, relations = LLM.extract("請抽取文中的實體和關系", document)
knowledge_graph.add(entities, relations)
# 2. 用 Leiden 算法做社區劃分,并為每個社區生成摘要(用于回答全局性問題)
communities = leiden_algorithm(knowledge_graph)
for each community:
summary = LLM.summarize(community.entities, community.relations)
# 3. 在線階段:先定位相關實體,沿關系遍歷 2 跳拿子圖
relevant_entities = knowledge_graph.search("張三")
subgraph = knowledge_graph.traverse(relevant_entities, hops=2)# 4. 把子圖和社區摘要作為上下文交給大模型生成回答
answer = LLM.generate(subgraph + community_summaries + query)
如果我們的數據本身就是結構化的表格,比如銷售數據、用戶行為日志、財務報表這些,就不合適傳統的 RAG 了,因為對表格數據做 embedding 是非常低效的。
用戶問:“上個月銷售額最高的產品是哪個?”
這本質上就是一條 SQL,向量搜索對這種聚合、排序、篩選類的需求完全沒招。
Text-to-SQL RAG 的做法是讓 LLM 直接把自然語言翻譯成 SQL,執行查詢,再把查詢結果作為上下文來回答:
![]()
這個方案適合所有數據分析類需求,比如 BI 看板問答、數據庫運維助手、財務報表查詢等等,本質上是用 LLM 替代了手寫 SQL。
不過要特別提醒,生產環境中絕對不能讓 LLM 生成的 SQL 直接執行,必須配備只讀權限控制、SQL 語法審計、沙盒隔離等安全措施,防止 SQL 注入。
Text-to-SQL RAG 的偽代碼實現很簡單:
# 1. 準備表結構(schema)作為提示,讓 LLM 知道有哪些表、字段
schema = "表 sales: product(產品名), amount(金額), month(月份), region(地區)"
# 2. 讓 LLM 把自然語言問題翻譯成 SQL
sql = LLM.generate("根據以下表結構,將問題轉為 SQL:\n" + schema + "\n問題:" + query)# 3. 在數據庫中執行 SQL,拿到結構化結果
result = database.execute(sql)
# 4. 把查詢結果喂給 LLM,讓它用自然語言組織成最終回答
answer = LLM.generate("查詢結果:" + result + "\n請用自然語言回答:" + query)
到這里,我們已經講了很多種 RAG 方法了。有的小伙伴可能已經注意到一個問題:前面提到的大多數方法都是預定義好的 pipeline,流程是死的,不管什么問題進來,處理方式都差不多。
但現實世界的問題千變萬化。有的需要搜向量庫,有的該查數據庫,有的應該搜 Web,甚至有的根本不需要檢索。
好復雜啊…… 有沒有一種方法能讓系統自己判斷該怎么做?
答案就是 Agentic RAG。
智能體驅動 RAG Agentic RAG
前面每種方法都有自己擅長的場景:Hybrid Search 擅長術語密集的文檔,GraphRAG 擅長多跳推理,Text-to-SQL 擅長結構化數據。
但在一個真實的系統中,這些場景可能同時存在。
比如用戶問 “張三上個月的考勤記錄” 得查數據庫,問 “公司的遠程辦公政策” 得搜文檔,問 “張三屬于哪個部門的哪個中心” 得走知識圖譜,但是為每種問題硬編碼一條 pipeline 太麻煩了。
Agentic RAG 的做法是讓一個 AI Agent 來自動調度,根據問題自主決定每一步該怎么做。給這個 Agent 配備一組檢索工具,它會先搜搜看 → 看看結果夠不夠 → 不夠就換個方式 / 換個關鍵詞再搜 → 結果夠了就生成回答。
![]()
Agentic RAG 雖然是很靈活的方案,但是代碼實現很簡單,核心是 Agent Loop 循環:
# 1. 給 Agent 配一組工具(向量檢索、Web 搜索、SQL 查詢、圖譜遍歷……)
tools = {
"vector_search": query -> vector_store.search(embed(query)),
"web_search": query -> search_engine.search(query),
"sql_query": query -> database.execute(LLM.to_sql(query)),
"graph_traverse": query -> knowledge_graph.traverse(query),
}# 2. 進入 ReAct 循環:思考 → 行動 → 觀察,直到信息足夠為止
context = []
while true:
# 讓 LLM 基于已知信息,決定下一步用哪個工具,或者直接回答
thought = LLM.reason("問題:" + query + "\n已知信息:" + context +
"\n我應該使用哪個工具?還是信息已經足夠可以回答了?")
# 決定作答,跳出循環
if thought.action == "answer":
return LLM.generate(context + query)
# 否則調用對應工具,把結果追加到上下文,進入下一輪
result = tools[thought.tool](thought.tool_input)
context.append(result)
以前 Agent 的概念剛火起來的時候,大家還在爭論 “Agent 自主決策靠不靠譜”。
到了今天,Agentic RAG 已經是比較主流的生產范式了,知名的 AI 編程工具 Cursor 用的就是這種方式,AI 自主決定使用什么方式來搜集信息:
![]()
Multi-Agent RAG
單個 Agent 處理復雜任務時,可能有個問題:當它要同時兼顧理解意圖、選擇策略、驗證質量、生成答案時,Prompt 變得又長又復雜,決策質量就會下降。這就像一個人又當程序員、又教人打籃球、又當說唱歌手,忙不過來。
Multi-Agent RAG 的做法是拆分為多個專職 Agent,各自負責一些任務。
比如 Router Agent 負責分發、各 RAG Agent 負責對應領域的檢索和推理、Verification Agent 負責質檢、Generation Agent 負責潤色輸出。
![]()
這種方案適合數據源多、權限復雜、語言多樣的企業級知識庫。每個環節可以獨立優化和擴展,不會牽一發而動全身。
Multi-Agent RAG 的代碼示例如下:
RAG 能力擴展 多模態 RAG(Multimodal RAG)# 1. Router Agent 先識別問題意圖,決定分給哪個專職 Agent
intent = router_agent.analyze(query)
# 2. 各領域 Agent 各管一攤,獨立完成檢索和推理
if intent == "document_qa":
raw_answer = doc_agent.run(query)
elseif intent == "data_analysis":
raw_answer = sql_agent.run(query)
else:
raw_answer = graph_agent.run(query)
# 3. Verifier Agent 做質檢,發現問題則提出修改建議
verified = verifier_agent.check(query, raw_answer)
ifnot verified.passed:
raw_answer = verifier_agent.suggest_fix(raw_answer)# 4. Writer Agent 做最終潤色,統一輸出風格
final_answer = writer_agent.polish(query, raw_answer)
傳統 RAG 只處理文本,但現實中的企業文檔里面充斥著大量的圖表、流程圖、架構圖、產品照片等。
如果用純文本 RAG 去處理一份真實文檔,那些流程圖、架構圖里的信息就全丟了。
多模態 RAG 的做法是把圖片、表格和文本統一到一個向量空間里,這樣檢索時就能跨模態匹配:
最后一步生成階段,需要用視覺語言模型來處理混合模態的上下文,因為普通的純文本 LLM 看不懂圖片。
![]()
多模態 RAG 的代碼實現比較復雜:
Speculative RAG// 離線:文本和圖片統一編碼到同一個向量空間
for each page in document:
text_chunks = extract_text(page)
images = extract_images(page)
tables = extract_tables(page)
for each chunk in text_chunks:
index.insert(text_encoder.encode(chunk), chunk, type="text")
for each image in images:
index.insert(vision_encoder.encode(image), image, type="image")
for each table in tables:
index.insert(table_encoder.encode(table), table, type="table")
// 在線:統一檢索,跨模態匹配
results = index.search(text_encoder.encode(query), k=5)
// results 中可能混合了文本塊、圖片、表格answer = vision_LLM.generate(results + query) // 用視覺語言模型處理混合內容
普通的 RAG 還有一個問題:如果把檢索到的所有文檔都塞進同一個 Prompt,不僅增加了推理延遲、響應速度,而且如果某個文檔是噪聲,整個生成都會被帶偏。
Speculative RAG(假設性檢索增強生成)借鑒了推測性解碼(Speculative Decoding)的思想,核心目標是降低延遲,把檢索到的文檔分成多個子集,用多個專家小模型從每個子集 并行 生成候選草稿,最后由一個更強的大模型做一次驗證,選出最佳答案。
![]()
這就有點像團隊共同做個大項目,多位前端和后端開發一起干活和仔細驗證,最后產品經理只需要簡單驗證就好,能大幅縮短工作總時長,有問題也更容易發現。
Speculative RAG 的實現代碼如下:
方法選擇# 1. 檢索階段召回較多的候選文檔
docs = retriever.search(query, k=15)
# 2. 把候選文檔拆成 n 個子集,準備并行處理
subsets = split_into_subsets(docs, n=5)
# 3. 多個小模型并行生成候選草稿,每個草稿只看一部分文檔
drafts = parallel_run(
for each subset in subsets:
draft = small_LM.generate(subset + query)
return { draft, subset, confidence }
)# 4. 大模型做一次驗證,從多個草稿中選出最佳答案
best = large_LM.verify_and_select(drafts, query)
return best.answer
看到這里,你可能已經被這十幾種 RAG 方案搞暈了。
我到底該用哪一種啊啊啊啊?!
沒事,我給大家梳理了一張表格,直接根據自己的項目情況來選方案:
你的情況
推薦方案
標準文本知識庫,追求基本可用
Naive RAG
用戶提問風格多變、口語化
Multi-Query RAG 或 HyDE
生產環境,追求檢索質量
Hybrid Search + Reranking
對準確率要求高,不能容忍幻覺
Corrective RAG 或 Self-RAG
查詢復雜度差異大
Adaptive RAG 路由
需要跨文檔多跳推理
GraphRAG
數據以結構化表格為主
Text-to-SQL RAG
文檔包含大量圖表/圖片
多模態 RAG
多數據源、多類型混合
Agentic RAG / Multi-Agent RAG
延遲敏感
Speculative RAG
對于正在動手搭建 RAG 系統的初學者來說,我建議 從簡單開始,逐步完善。
先跑通 Naive RAG,發現哪個環節出了問題,就針對性地選用前面的 RAG 方案。
千萬別一上來就搞 Multi-Agent + GraphRAG + 多模態全家桶,不僅實現成本高,效果也不一定更好。
![]()
那怎么知道 RAG 系統效果好不好呢?
其實有個評估框架 RAGAS,就是用來做這個的。它有四個核心指標:
忠實度(回答有沒有瞎編)
答案相關性(答的是不是你問的)
上下文精確率(搜到的有多少是有用的)
上下文召回率(該搜到的搜到了嗎)
這樣你就能先評估效果再優化,而不是僅憑感覺調參數。
除此之外,RAG 相關的主流技術還有編排框架 LangChain / LangGraph、LlamaIndex、一體化平臺 Dify 、RAGFlow、向量數據庫 Chroma、Milvus、Qdrant 等,大家感興趣的話可以自行學習了解一下~
OK,這篇文章寫了快 1 萬字,把 16 種 RAG 的實現和優化方案都講完了,希望對大家有所幫助。
我是魚皮,持續分享 AI 編程干貨,這篇文章也會收錄到我免費開源的 ,GitHub Star 數已經破萬,從零開始帶你學會用 AI 開發上線自己的產品。
![]()
可以點我頭像,然后私信「AI編程」獲取:
學會的話歡迎點贊收藏關注哦,也歡迎評論區聊聊:你用過 RAG 么?最喜歡那種 RAG 方案?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.