简单RAG能解决80%的问题,但剩下的20%才是生产环境的噩梦——多文档对比、复杂推理,这些场景简单RAG根本扛不住。
调研了两个月,踩了不少坑,记录一下。
结论先行
| 场景 | 方案 |
|---|---|
| FAQ机器人 | 简单RAG |
| 单文档问答 | 简单RAG + reranker |
| 多文档综合 | Agentic RAG |
| 对比类问题 | Agentic RAG |
| 延迟敏感 | 简单RAG |
| 质量优先 | Agentic RAG |
延迟敏感用简单RAG,质量优先用Agentic RAG。
简单RAG vs Agentic RAG
简单RAG:
用户问题 → 检索 → 上下文 → LLM生成
Agentic RAG:
用户问题 → Agent规划 → 多次检索 → 反思 → 可能重试 → 综合答案
Agentic RAG会"思考":当前结果够不够?要不要换个角度再查?
代价:延迟和成本。Agentic RAG一次查询可能触发3-5次LLM调用。
简单RAG的瓶颈
把简单RAG调优到83%准确率后,遇到三个瓶颈:
1. 多文档对比
问"比较A产品和B产品",简单RAG各检索5条塞给LLM,结果经常混乱漏维度。
Agentic RAG做法:先规划"从哪些维度比较",分头查,再汇总。
2. 模糊查询
问"那个去年Q3的性能优化文档",简单RAG搜"性能优化"返回一堆结果。
Agentic RAG做法:分析为"性能优化 + 2025Q3",精准定位。
3. 复杂推理
问"切换架构需要哪些准备",需要多跳推理,简单RAG很难处理。
框架选型
| 框架 | 特点 |
|---|---|
| LangChain LCEL | 生态成熟 |
| LlamaIndex Workflows | 索引能力强 |
| LangGraph | 状态流清晰,可视化 |
| OpenAI Assistants | 托管快速 |
选了LangGraph——状态管理清晰,支持可视化调试。
LangGraph实现
from langgraph.graph import StateGraph, END
from typing import TypedDict
class RAGState(TypedDict):
query: str
plan: list[str]
current_step: int
retrieved_docs: list
response: str
iteration: int
def planning_node(state: RAGState) -> RAGState:
"""规划:决定检索什么"""
plan = llm.predict(f"""
分析这个问题,决定需要检索的关键词:
{state['query']}
输出JSON数组。
""")
state["plan"] = json.loads(plan)
state["current_step"] = 0
return state
def retrieval_node(state: RAGState) -> RAGState:
"""检索:根据plan执行"""
keyword = state["plan"][state["current_step"]]
docs = vector_store.similarity_search(keyword, k=5)
state["retrieved_docs"].extend(docs)
state["current_step"] += 1
return state
def evaluation_node(state: RAGState) -> RAGState:
"""评估:判断是否继续检索"""
is_enough = llm.predict(f"""
问题:{state['query']}
已检索:{state['retrieved_docs']}
是否足够?(是/否)
""")
state["needs_more"] = "否" not in is_enough
state["iteration"] += 1
return state
def generation_node(state: RAGState) -> RAGState:
"""生成:综合结果"""
docs = deduplicate_and_rank(state["retrieved_docs"])
context = "\n".join([d.content for d in docs[:5]])
response = llm.predict(f"""
问题:{state['query']}
内容:{context}
""")
state["response"] = response
return state
# 构建图
workflow = StateGraph(RAGState)
workflow.add_node("planning", planning_node)
workflow.add_node("retrieval", retrieval_node)
workflow.add_node("evaluation", evaluation_node)
workflow.add_node("generation", generation_node)
workflow.set_entry_point("planning")
workflow.add_edge("planning", "retrieval")
workflow.add_edge("retrieval", "evaluation")
# 条件边:评估结果决定下一步
workflow.add_conditional_edges(
"evaluation",
lambda s: "retrieval" if s["needs_more"] and s["iteration"] < 3 else "generation",
{"retrieval": "retrieval", "generation": "generation"}
)
workflow.add_edge("generation", END)
app = workflow.compile()
踩坑实录
坑一:延迟失控
P99延迟飙到几秒。
解法:降级策略
async def query_with_fallback(query: str, timeout: float = 3.0):
try:
return await asyncio.wait_for(agentic_rag(query), timeout=timeout)
except asyncio.TimeoutError:
return await simple_rag(query) # 超时降级
坑二:成本爆炸
成本是简单RAG的3-5倍。
解法:路由层
def route(query: str) -> str:
# 简单问句走简单RAG
if len(query) < 20 and is_simple(query):
return "simple"
# 复杂问句走Agentic RAG
if any(k in query for k in ["比较", "哪些", "为什么"]):
return "agentic"
return "simple"
现在30%走Agentic RAG,70%走简单RAG,成本可控。
坑三:调试困难
Agent行为不透明。
解法:执行日志
def log_step(step: str, state: dict):
logger.info({
"step": step,
"query": state["query"],
"docs": len(state.get("retrieved_docs", [])),
"needs_more": state.get("needs_more"),
})
配合LangGraph可视化,排查效率高很多。
踩坑小结
- 简单RAG调优到极致再上Agentic——80%的问题简单RAG能解决
- 加路由层——别所有query都走Agentic,延迟敏感场景受不了
- 限制重试次数——最多3次,避免无限循环
- 加降级策略——超时自动切简单RAG
- 日志要详细——Agent行为不透明,排查全靠日志
选型代码
def select_rag_approach(query: str, latency_sensitive: bool) -> str:
"""
query: 用户问题
latency_sensitive: 是否延迟敏感
"""
if latency_sensitive:
return "simple_rag"
is_complex = any(k in query for k in ["比较", "哪些", "为什么", "如何", "多久"])
if is_complex:
return "agentic_rag"
return "simple_rag"