📌 本文重點
- 多輪 Agent Loop RAG 可讓 QA 準確率大幅提升
- 關鍵是「決策 +多輪檢索」而非一味加 reranker
- 實作重點在 decision head、記憶管理與 citation 驗證
Google FRAMES 的多跳 QA 實驗顯示:最佳傳統 RAG pipeline 78.9%,Agent Loop 式 RAG 92.7%,幾乎等同「直接給模型正確文件」。對工程團隊來說,這代表一件很直接的事:
你不一定需要更大的模型或更複雜的 reranker,而是需要讓模型有“多輪檢索與決策能力”的 RAG 架構。
💡 關鍵: 在相同文件與 embedding 條件下,Agent Loop 式 RAG 能比傳統 RAG 多拿約 14 個百分點的準確率,效益遠勝只換大模型或小 reranker。
下面從工程視角拆解:Agent Loop 式 RAG 到底多了什麼、怎麼實作一個最小可行版本、以及部署時要小心哪些坑。
重點說明:Agent Loop 式 RAG 的關鍵差異
1. 一次性 Top‑k vs. 多輪自我檢索
傳統 RAG:
1. 使用者 query
2. 向量庫 top_k 檢索
3. 把 chunks 拼進 context
4. 一次性回答
Agent Loop RAG(FRAMES 的 Agent Loop 類型):
1. 使用者 query
2. 模型判斷:需要檢索嗎?→ 呼叫 檢索工具
3. 讀結果後:
– 若資訊不足:改寫 / 分解 query 再查一次
– 若資訊足夠:生成答案
4. 持續迭代,直到滿足「可以回答」的停止條件
對多跳問題(典型企業知識庫 QA)來說:
– 傳統 RAG 常卡在「第一跳檢索就打偏」
– Agent Loop 允許模型根據前一輪讀到的內容,修正檢索方向
這也是為什麼同一組 embedding/文件,Agent Loop 在 FRAMES 能拉高約 14 個百分點的原因。
2. Agent 如何決定「要不要再查」?
實務上會做成一個小的 decision head,而不是完全靠 prompt。常見做法:
- 用一個 工具選擇 schema,讓模型每輪輸出:
- action:
"search" | "answer" - reason:決策理由(方便 debug)
- query:若
action是search,要查的內容
範例(以 OpenAI / OpenRouter 類 API 為例):
// tool schema(簡化版)
{
"type": "function",
"function": {
"name": "agent_decide",
"description": "決定下一步是再次檢索還是回答",
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["search", "answer"]
},
"reason": {"type": "string"},
"query": {
"type": "string",
"description": "如果需要檢索,這是更新後的查詢語句"
}
},
"required": ["action", "reason"]
}
}
}
在 loop 中實作邏輯:
- 若
action == "search": - 呼叫你的 檢索 API(向量庫、混合搜尋皆可)
- 把檢索結果 append 到對話狀態
- 若
action == "answer": - 要求模型在嚴格引用現有 context 的前提下生成最終答案
這種方式比「用 system prompt 告訴模型:資訊不夠就再查」穩定得多,因為 decision 被結構化,容易觀測與評估。
3. 為何小 reranker 反而拖垮效果?
FRAMES 實驗的結果很不直覺:
– 小 reranker:讓最佳 pipeline 準確率 掉了 9 個百分點
– 大 reranker:只有輕微提升
💡 關鍵: 弱 reranker 容易錯殺原本已在 top_k 的關鍵 chunk,在多跳 QA 中反而降低整體答對率。
工程上的原因通常是:
1. 弱 reranker = 引入噪音排序
– 原本 top_k 裡其實已有足夠訊息
– 小模型 rerank 反而把關鍵 chunk 往下排
2. 多跳場景不適合一次性排序整包文件
– FRAMES 類題目常需要「先找到中間 entity,再查下個文件」
– 一次性把所有文件塞進 context,再怎麼 rerank 都很難
對多跳問題來說,檢索策略(多輪) > 排序策略(rerank)。實務結論:
– 先把心力放在 Agent Loop + query 改寫/分解
– reranker 真要上,從 大一點的模型 + 明確場景評估 開始
實作範例:最小可行 Agent Loop RAG
以下是一個可以直接改成你專案版本的「最小可行」架構:
1. 介面與狀態設計
核心組件:
– 檢索 API:search_docs(query, top_k) -> [Doc]
– 對話狀態:messages + memory
– 工具 schema:agent_decide + search_tool
# 假設已有向量庫 search_docs
def search_docs(query: str, top_k: int = 5):
# return list of {"id", "title", "content"}
...
# 對話記憶(簡化)
class AgentMemory:
def __init__(self):
self.history = [] # user / assistant turns
self.retrieved = [] # 已讀過的 docs meta
def add_retrieval(self, query, docs):
self.retrieved.append({"query": query, "docs": docs})
2. 工具定義(決策 + 檢索)
TOOLS = [
{
"type": "function",
"function": {
"name": "search_tool",
"description": "用關鍵字搜尋文件庫",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"top_k": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "agent_decide",
"description": "決定接下來是檢索還是回答",
"parameters": {
"type": "object",
"properties": {
"action": {"type": "string", "enum": ["search", "answer"]},
"reason": {"type": "string"},
"query": {"type": "string"}
},
"required": ["action", "reason"]
}
}
}
]
3. Agent Loop Pseudo-code
MAX_STEPS = 4
def agent_loop(user_query: str, llm_client) -> str:
memory = AgentMemory()
messages = [
{"role": "system", "content": "你是一個嚴格依據檢索文件回答的助理。"},
{"role": "user", "content": user_query},
]
for step in range(MAX_STEPS):
# 1) 請模型用 agent_decide
decision = call_llm_decide(messages, llm_client)
if decision["action"] == "search":
q = decision.get("query") or user_query
docs = search_docs(q, top_k=5)
# 避免重複查同批文件(一個常見坑)
if is_duplicate_retrieval(memory, q, docs):
# 強制模型換策略:更新 prompt 或降低溫度
messages.append({"role": "assistant", "content": "目前檢索結果重複,請改寫查詢或嘗試總結。"})
continue
memory.add_retrieval(q, docs)
# 把摘要版 docs 塞回 messages
context_txt = summarize_docs_for_context(docs)
messages.append({
"role": "assistant",
"content": f"[檢索結果]\n{context_txt}"
})
elif decision["action"] == "answer":
# 2) 最終回答:強調不得虛構引用
messages.append({
"role": "system",
"content": "只允許引用上述[檢索結果]中出現的資訊,若沒有就回答無法判斷。"
})
return call_llm_answer(messages, llm_client)
# 超過 MAX_STEPS 仍未收斂
return "目前檢索仍不足以可靠回答這個問題。"
這樣的框架可以直接套到既有向量庫專案,只需要:
– 把原本「一次性檢索 + 回答」拆成 decision → 檢索 → decision → 回答 loop
– 把 檢索結果縮摘要(避免 context 爆掉)
建議與注意事項:部署時的實務考量
1. 延遲與成本:多輪檢索怎麼控
Agent Loop 必然:
– 更多 LLM call(decision + answer)
– 更多檢索 call
控制策略:
– 設定 MAX_STEPS(通常 3–5 輪就夠,多了收益遞減)
– decision 用較小模型、最後回答用大模型(類似 routing)
– 對「簡單問題」走捷徑:
– 先讓模型判斷 need_search: bool
– 為 False 時走 direct answer 或 cached FAQ
2. 限制 hallucination 與 citation 造假
FRAMES 實驗也發現,即使明講「只能根據文件」,模型仍會:
– 憑記憶補完內容,硬塞 citation 看起來很合理
實務防禦:
- 結構化 citation:
- 要求回答時返回:
{"answer": ..., "citations": [doc_id, ...]} -
像「citation receipt」一樣,保存:哪個句子對應哪個 chunk
-
離線自動驗證:
-
對高風險場景(法務、醫療),可加一個 verification pass:
- 把
answer + citations再丟給 LLM,問: - 這些句子是否都能在
citiedchunks 中找到明確證據? - 若否,標記為需人工審查
- 把
-
明確拒答路徑:
- 在 prompt 中允許、甚至鼓勵模型回答「依據現有檢索結果無法判斷」,比亂猜好
3. Loop 不收斂、重複查同一批文件、context 爆掉
這是 Agent Loop RAG 的三大工程坑:
- loop 不收斂:
- 加 MAX_STEPS 上限
-
decision prompt 中加入:「若多次檢索仍無新訊息,請選擇
answer並說明無法回答」 -
重複查同一批文件:
- 在
AgentMemory中存檢索 query + top 文檔id -
若新一輪檢索與前一輪 query 相似度高且 top ids 近似,視為重複,強制換策略
-
context 爆掉:
- 對每次檢索結果先做 chunk‑level 摘要 再拼進 prompt
- 對話歷史採 滑動視窗,只保留「最近幾輪檢索摘要 + 關鍵決策理由」
4. 從既有 RAG 平滑遷移到 Agent Loop
推薦遷移路徑:
- Phase 1:保留現有 RAG,只加一層 decision head
- 先讓模型判斷
need_extra_search(布林值) -
若是:再跑一次檢索 + answer
-
Phase 2:引入多輪 query 改寫
- 在 decision 中加入
refined_query欄位 -
觀察實際 query 改寫對召回率的提升
-
Phase 3:完全 Agent Loop
- 將檢索、決策、回答視為三個工具,以 loop 驅動
- 為每一輪紀錄 log,方便做 自動打標 / 事後評估
觀測上,建議:
– 對一批固定測試集(可自建,也可用 FRAMES 類題目)
– 比較:
– 傳統 RAG pipeline vs Agent Loop RAG 的答對率 / citation 正確率
– 並記錄平均回合數與成本
總結:
– 多跳與複雜 QA 場景下,Agent Loop 式 RAG 的收益實際可觀(以 FRAMES 為證)
– 與其疊更多「hybrid search + 小 reranker」,不如先讓模型具備:
– 多輪檢索決策能力
– 對話狀態 / 檢索記憶管理
– 可觀測、可驗證的 citation 流程
如果你已有一個傳統 RAG QA 服務,本文的最小 Agent Loop 範例幾乎可以直接改成你自己的 API 呼叫,把 loop 跑起來,會比換更大模型更划算。
🚀 你現在可以做的事
- 在現有專案中,先加上一層
agent_decidedecision head,觀察多一次檢索帶來的答對率變化- 用自家資料集或 FRAMES 類題目,對比「傳統 RAG vs Agent Loop RAG」的準確率與平均步數
- 為現有回答格式加入結構化
citations欄位,並設計一個簡單的離線驗證流程







