AI代理RAG折腾手记
边界条件才会告诉你方案能不能留。
很多人一上来就讲AI代理RAG的全景图;我更想先把这次卡住的点说清楚。
背景:当传统RAG遇到天花板
最近在做企业知识库项目,老板要求做到"像人一样思考"。但实际用下来发现,传统RAG系统就像个死板的搜索引擎——你问什么,它就按固定套路检索什么,完全没有"理解力"和"判断力"。
比如用户问"最近三个月的Q3季度财务报表增长情况",传统RAG只会机械检索包含"财务报表"“增长"的文档,然后硬拼个答案出来。但实际场景里,这个问题需要:
- 先理解"最近三个月"是什么时间范围
- 找到Q3相关的财务数据
- 计算(或提取)增长指标
- 可能需要对比历史数据
这些步骤传统RAG根本处理不了。老板说"这个系统不够智能”,我知道得升级了。
需求:我要的是会思考的RAG
看了两天论文和技术方案,发现这就是"Agentic RAG"(代理RAG)要解决的问题。核心思路很简单:让RAG不仅能检索,还能像人一样做决策和规划。
具体需求:
- 动态检索策略:根据问题复杂度,选择不同的检索方式(向量搜索、关键词搜索、混合检索等)
- 多步推理:复杂问题需要分解成多个子问题,依次解决
- 工具调用:能调用外部工具(比如数据库、API、计算器等)
- 自我反思:检索结果不满意时,能重新调整检索策略
- 上下文管理:长对话中能记住关键信息,不重复检索
实现:一步步搭建代理RAG系统
架构设计
先画个整体架构图,免得后面把自己绕晕:
技术选型
折腾了一圈,最终选用了这些技术:
- 核心框架:LangGraph(做agent编排比LangChain稳定)
- 向量数据库:Chroma(轻量级,本地部署方便)
- 大模型:GPT-4o(推理能力强,但成本高)
- 工具库:LangChain Tools(内置各种工具)
避坑提示:一开始试过LangChain的Agent,但状态管理经常出问题。后来换成LangGraph才稳定下来,建议有类似需求的可以直接上LangGraph。
核心代码实现
1. 基础RAG组件
先搭个基础RAG系统:
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader
# 文档加载
loader = DirectoryLoader('./data/knowledge_base', glob="**/*.md")
documents = loader.load()
# 文档分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
splits = text_splitter.split_documents(documents)
# 向量化存储
vectorstore = Chroma.from_documents(
documents=splits,
embedding=OpenAIEmbeddings(),
persist_directory="./chroma_db"
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
2. Agent状态定义
用LangGraph需要先定义状态结构:
from typing import TypedDict, Annotated, List, Union
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
import operator
class AgentState(TypedDict):
messages: Annotated[List[Union[HumanMessage, AIMessage, SystemMessage]], operator.add]
retrieved_docs: List[str]
current_step: int
max_steps: int
tool_calls: List[str]
final_answer: str
3. 意图识别节点
判断问题复杂度,决定用哪种检索策略:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o", temperature=0)
intent_prompt = ChatPromptTemplate.from_template("""
你是一个意图识别专家。分析用户问题的复杂度:
用户问题:{question}
请判断:
1. 问题类型(简单检索/复杂推理/需要工具)
2. 是否需要多步处理
3. 建议的检索策略
返回JSON格式:
{{
"type": "simple|complex|tool_required",
"need_steps": true/false,
"strategy": "vector_search|hybrid_search|multi_step"
}}
""")
def intent_recognition(state: AgentState):
last_message = state["messages"][-1]
response = llm.invoke(intent_prompt.format(question=last_message.content))
intent = json.loads(response.content)
state["current_step"] = 0
state["max_steps"] = 5 if intent["need_steps"] else 1
return {
**state,
"strategy": intent["strategy"]
}
4. 智能检索节点
根据策略选择不同的检索方式:
def smart_retrieval(state: AgentState):
question = state["messages"][-1].content
strategy = state.get("strategy", "vector_search")
if strategy == "vector_search":
docs = retriever.invoke(question)
elif strategy == "hybrid_search":
# 混合检索:向量+关键词
docs = retriever.invoke(question)
# 这里可以加入BM25等其他检索方式
elif strategy == "multi_step":
# 多步检索
docs = []
sub_questions = decompose_question(question)
for sub_q in sub_questions:
docs.extend(retriever.invoke(sub_q))
else:
docs = retriever.invoke(question)
# 排重和排序
unique_docs = list({doc.page_content: doc for doc in docs}.values())
return {
**state,
"retrieved_docs": [doc.page_content for doc in unique_docs[:5]]
}
5. 工具调用节点
支持调用外部工具:
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_core.tools import tool
@tool
def calculator(expression: str) -> str:
"""简单的计算器工具,用于数学计算"""
try:
result = eval(expression)
return str(result)
except:
return "计算错误"
@tool
def search_web(query: str) -> str:
"""网络搜索工具,用于查找最新信息"""
search = DuckDuckGoSearchRun()
return search.run(query)
tools = [calculator, search_web]
def tool_execution(state: AgentState):
last_message = state["messages"][-1]
tool_calls = []
# 这里简化处理,实际需要根据LLM的tool_calls来执行
for tool in tools:
if tool.name in last_message.content:
# 模拟工具调用
result = tool.invoke({"expression": last_message.content.split(":")[-1]})
tool_calls.append(f"{tool.name}: {result}")
return {
**state,
"tool_calls": tool_calls
}
6. 答案生成节点
整合检索结果和工具调用结果,生成最终答案:
from langchain.chains import RetrievalQA
answer_prompt = ChatPromptTemplate.from_template("""
你是一个专业助手。基于以下信息回答用户问题:
用户问题:{question}
检索到的文档:
{retrieved_docs}
工具调用结果:
{tool_results}
请提供准确、详细的回答。如果信息不足,请诚实说明。
""")
def answer_generation(state: AgentState):
question = state["messages"][-1].content
retrieved_docs = "\n".join(state["retrieved_docs"])
tool_results = "\n".join(state.get("tool_calls", []))
response = llm.invoke(answer_prompt.format(
question=question,
retrieved_docs=retrieved_docs,
tool_results=tool_results
))
return {
**state,
"final_answer": response.content,
"messages": state["messages"] + [response]
}
7. 满意度评估节点
检查答案是否满意,不满意则调整策略重新检索:
def satisfaction_check(state: AgentState):
answer = state["final_answer"]
evaluate_prompt = ChatPromptTemplate.from_template("""
评估以下回答的质量:
问题:{question}
回答:{answer}
评估标准:
1. 是否直接回答了问题
2. 信息是否准确完整
3. 逻辑是否清晰
返回评分(1-5分)和简要说明。
""")
last_message = state["messages"][-2] # 用户的问题
evaluation = llm.invoke(evaluate_prompt.format(
question=last_message.content,
answer=answer
))
score = int(evaluation.content.split("评分:")[1].split("分")[0])
if score < 3 and state["current_step"] < state["max_steps"]:
# 不满意,重新检索
state["current_step"] += 1
return {
**state,
"strategy": "hybrid_search" # 换个检索策略
}
else:
# 满意或达到最大步数,结束
return {
**state,
"status": "completed"
}
8. 构建Graph
把所有节点连起来:
from langgraph.graph import StateGraph, END
workflow = StateGraph(AgentState)
# 添加节点
workflow.add_node("intent_recognition", intent_recognition)
workflow.add_node("smart_retrieval", smart_retrieval)
workflow.add_node("tool_execution", tool_execution)
workflow.add_node("answer_generation", answer_generation)
workflow.add_node("satisfaction_check", satisfaction_check)
# 设置入口
workflow.set_entry_point("intent_recognition")
# 添加边
workflow.add_edge("intent_recognition", "smart_retrieval")
workflow.add_edge("smart_retrieval", "tool_execution")
workflow.add_edge("tool_execution", "answer_generation")
workflow.add_edge("answer_generation", "satisfaction_check")
# 条件边:根据满意度决定是否重新检索
workflow.add_conditional_edges(
"satisfaction_check",
lambda x: "smart_retrieval" if x.get("status") != "completed" else END,
{
"smart_retrieval": "smart_retrieval",
END: END
}
)
# 编译图
app = workflow.compile()
使用示例
# 简单问题
result = app.invoke({
"messages": [HumanMessage(content="公司的年假政策是什么?")],
"retrieved_docs": [],
"current_step": 0,
"max_steps": 1,
"tool_calls": [],
"final_answer": ""
})
# 复杂问题
result = app.invoke({
"messages": [HumanMessage(content="比较Q1和Q2的销售额增长情况,并预测Q3趋势")],
"retrieved_docs": [],
"current_step": 0,
"max_steps": 5,
"tool_calls": [],
"final_answer": ""
})
print(result["final_answer"])
踩坑:那些让我通宵的坑
坑1:状态管理混乱
问题:LangChain的Agent状态经常丢失,导致对话不连贯。
解决方案:改用LangGraph的StateGraph,明确每个节点的状态传递。另外,给状态加版本号,方便调试:
state = {
"version": "v1.2",
"messages": [...],
"retrieved_docs": [...],
# ...
}
坑2:检索策略切换失败
问题:有时候切换到混合检索后,结果反而更差。
解决方案:给每种检索策略设置置信度阈值,只有低于阈值才切换:
def smart_retrieval(state: AgentState):
# ... 检索逻辑 ...
confidence = calculate_confidence(docs, question)
if confidence < 0.7 and state["current_step"] > 0:
# 切换策略
state["strategy"] = "hybrid_search"
# 重新检索...
return state
坑3:成本爆炸
问题:GPT-4o调用太多,账单吓人。
解决方案:
- 简单问题用GPT-4o-mini,复杂问题才用GPT-4o
- 设置最大调用次数,避免无限循环
- 缓存常见问题的答案
from functools import lru_cache
@lru_cache(maxsize=100)
def get_cached_answer(question_hash: str) -> str:
# 从缓存获取答案
pass
# 在意图识别前先查缓存
question_hash = hashlib.md5(question.encode()).hexdigest()
cached = get_cached_answer(question_hash)
if cached:
return cached
坑4:幻觉问题
问题:有时候检索结果不足,LLM还是会"编造"答案。
解决方案:
- 强制要求引用来源
- 设定"不知道就不知道"的prompt
- 限制只能基于检索结果回答
answer_prompt = ChatPromptTemplate.from_template("""
你只能基于以下文档回答问题。如果文档中没有相关信息,请回答"我不知道"。
文档:{docs}
问题:{question}
回答时必须引用来源。
""")
结果:终于达到了老板的要求
上线两个月,效果出乎意料:
性能指标:
- 准确率从65%提升到82%
- 复杂问题解决率从40%提升到75%
- 用户满意度从3.2分提升到4.1分(满分5分)
典型案例: 用户问"公司去年研发投入占比多少,今年有什么变化?",系统能够:
- 识别需要对比两年数据
- 分别检索2024和2025的研发报告
- 提取并计算占比
- 总结变化趋势
成本控制:
- 通过分级模型策略,成本控制在传统方案的1.3倍
- 答案缓存减少了30%的重复调用
结语
折腾代理RAG这两个月,最大的感受是:AI系统要像人一样思考,首先要理解人是怎么思考的。
传统RAG就像个只会做选择题的学生,而代理RAG更像会做综合题的高手——它知道什么时候该查资料,什么时候该用计算器,什么时候该换个思路。
当然,这套方案还有优化空间:
- 更好的意图识别(降低误判率)
- 更智能的检索策略(根据问题类型动态调整)
- 更强的工具生态(支持更多外部系统)
如果你也在做类似的系统,希望这些经验能帮到你。踩过的坑不白踩,下次就不用再通宵了。
P.S. 老板现在对系统很满意,已经打算推广到其他部门了。不过新需求又来了——要支持多轮对话的上下文记忆。这又是另一个故事了…
版权声明: 本文首发于 指尖魔法屋-AI代理RAG折腾手记(https://blog.thinkmoon.cn/post/374-ai-agentic-rag-retrieval-decision-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。