全文链接:tecdat.cn/?p=45181
原文出处:拓端数据部落公众号
关于分析师
在此对YouMing Zhang对本文所作的贡献表示诚挚感谢,他在东北大学完成了信息与计算科学专业的学士学位,专注机器学习与深度学习算法领域。擅长Python、MATLAB、神经网络架构设计、数据挖掘与智能系统开发。
引言
在企业级AI应用中,如何让大语言模型(LLM)既能利用内部知识库,又能实时获取最新信息,一直是技术落地的核心挑战。传统检索增强生成(RAG)通过外挂知识库让模型“边查边答”,但当检索到的文档质量不佳时,答案便会出现偏差。这好比一位学者只凭几本旧书回答问题,遇到新问题要么答非所问,要么承认无知。
在近期为一家跨国保险集团设计智能理赔助手的咨询项目中,我们直面了这一痛点。客户要求系统不仅要准确回答基于保单条款的问题,还要能实时应对理赔政策变更、法院判例更新等动态信息。传统RAG在静态文档上表现尚可,但面对需要联网核实或跨文档推理的场景,便力不从心。于是,我们引入了智能体(Agent)的概念——让大模型自己决定何时检索、何时上网、何时直接回答,并通过LangGraph构建了一个可编排、可回溯的状态化流程。本文将这一项目中的技术沉淀提炼成文,从零开始,逐步构建一个具备纠正性检索(Corrective RAG)能力的多智能体系统。
本文内容改编自过往客户咨询项目的技术沉淀并且已通过实际业务校验,该项目3实例完整代码与数据已分享至交流社群。阅读原文进群获取完整代码数据及更多最新AI见解、行业洞察,可与900+行业人士交流成长;还提供人工答疑,拆解核心原理、代码逻辑与业务适配思路,帮大家既懂“怎么做”,也懂“为什么这么做”;遇代码运行问题,更能享24小时调试支持。
文章脉络可用以下流程图概括:
用户输入
|
v
+-----+-----+
| 检索节点 | <---+ 向量数据库
+-----+-----+
|
v
+-----+-----+
| 文档评分节点 | (LLM判断相关性)
+-----+-----+
|
v
+-----+------+
| 条件分支 |
+-----+------+
|
+-----v-----+ +-----------+
| 全部相关 | | 部分/不相关 |
+-----------+ +-----+-----+
| |
v v
+-----+-----+ +-----+-----+
| 生成答案 | | 查询重写 |
+-----------+ +-----+-----+
| |
| v
| +-----+-----+
| | Web搜索 | (Tavily API)
| +-----+-----+
| |
| v
| +-----+-----+
| | 生成答案 |
| +-----+-----+
| |
+----------+-----------+
|
v
最终输出
基础:大模型调用与工具绑定
一切始于最基础的LLM调用。
# 结构化对话
from langchain_core.messages import HumanMessage, SystemMessage
dialogue = [
SystemMessage(content="你是一位能用简单语言解释复杂概念的助手。"),
HumanMessage(content="请用两句话解释机器学习。")
]
reply = my_llm.invoke(dialogue)
print(reply.content)
为了让模型具备工具使用能力,我们需要定义工具并绑定。下面我们创建了两个工具:一个简单的计算器和一个网络搜索工具(此处为示意,实际使用时需配置Tavily或DuckDuckGo)。
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
状态化聊天机器人:LangGraph初探
LangGraph的核心是构建一个状态图(StateGraph),其中节点处理消息并更新状态。我们先定义一个包含消息列表的状态类。
测试发现,这个简单图不保存跨轮记忆。为此,我们引入MemorySaver检查点。
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
chat_with_mem = builder.compile(checkpointer=memory)
def talk(message: str, thread_id: str):
config = {"configurable": {"thread_id": thread_id}}
initial = {"msgs": [HumanMessage(content=message)]}
result = chat_with_mem.invoke(initial, config)
print("助手:", result["msgs"][-1].content)
talk("你好,我叫小明", "thread-1")
talk("我叫什么名字?", "thread-1") # 现在能记住了
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
相关文章
DeepSeek、LangGraph和Python融合LSTM、RF、XGBoost、LR多模型预测NFLX股票涨跌|附完整代码数据
原文链接:tecdat.cn/?p=44060
让智能体自主调用工具
为了构建真正的智能体,我们需要两个节点:一个负责决定是否调用工具(agent),另一个负责执行工具(tools)。同时需要一个条件边来路由。
from langgraph.prebuilt import ToolNode
# 绑定工具后的模型
model_with_tools = my_llm.bind_tools([simple_calculator]) # 实际可绑定多个工具
def agent_node(state: DialogState) -> DialogState:
sys_msg = "你是一个智能助手,需要时可使用工具。"
messages = [SystemMessage(content=sys_msg)] + state["msgs"]
response = model_with_tools.invoke(messages)
return {"msgs": [response]}
tools_node = ToolNode([simple_calculator]) # 自动执行工具调用
def route_condition(state: DialogState):
last_msg = state["msgs"][-1]
return "tools" if hasattr(last_msg, 'tool_calls') else "end"
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
集成RAG:让智能体拥有私有知识
现在,我们为智能体添加文档检索能力。首先需要构建一个向量数据库,这里以Markdown简历或Wikipedia数据为例。
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# 加载文档(此处省略具体加载代码)
# docs = ...
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(docs)
embed_model = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = Chroma.from_documents(chunks, embed_model, persist_directory="./kb_store")
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
然后将检索器封装为工具,供智能体使用。
@tool
def knowledge_retriever(query: str) -> str:
"""在内部知识库中检索相关信息。"""
docs = retriever.invoke(query)
return "\n\n".join([d.page_content for d in docs])
tools = [simple_calculator, knowledge_retriever] # 可继续添加web搜索工具
model_with_rag = my_llm.bind_tools(tools)
为了演示纠正性RAG,我们需要引入评估节点:检查检索到的文档是否真的与问题相关。如果不相关,则触发查询重写和Web搜索。
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
传统RAG的局限与纠正性RAG的提出
传统RAG系统虽然通过外挂知识库增强了LLM的事实性,但仍存在几个固有缺陷:
- 无法访问实时数据:知识库一旦建立便静止不动,无法反映最新信息。
- 检索质量依赖索引:如果向量检索召回的是不相关文档,生成阶段就会产生错误答案。
- 缺乏自我评估:系统无法判断检索到的文档是否真的能回答问题。
这些问题在实际应用中屡见不鲜。例如,当用户询问“2024年欧冠冠军是谁?”时,如果知识库只有2023年以前的数据,传统RAG只能回答“不知道”。又或者,当检索到的文档部分相关但不足以完整回答时,模型可能会拼凑出错误结论。
针对这些痛点,Yan等人在2024年提出了纠正性检索增强生成(Corrective RAG, CRAG) 。其核心思想是在检索后增加一个轻量级评估器,对检索文档的相关性打分,并根据分数决定后续行动:如果文档足够相关,直接生成答案;如果不够相关,则通过Web搜索补充信息或提供更全面的上下文。
数据准备与基础RAG链
我们以一份Markdown格式的个人简历作为知识源,构建一个能够回答简历相关问题的智能体。首先,我们演示传统RAG链的表现。
# 读取Markdown文件
with open("source.md", "r") as f:
full_text = f.read()
# 按标题分割
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split = [("#", "H1"), ("##", "H2"), ("###", "H3")]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split, strip_headers=False)
chunks = splitter.split_text(full_text)
# 构建向量存储
embeddings = OpenAIEmbeddings()
vector_db = Chroma.from_documents(chunks, embeddings)
retriever = vector_db.as_retriever()
定义标准RAG提示模板并构建链。
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
rag_prompt = PromptTemplate.from_template("""
你是一个AI助手,根据提供的上下文回答问题。如果不知道答案,就说不知道。
问题:{question}
上下文:{context}
答案:""")
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| rag_prompt
| ChatOpenAI(model="gpt-4.1-mini", temperature=0)
| StrOutputParser()
)
测试几个问题,发现当答案直接存在于某个块中时,回答正确;但当问题需要跨块汇总信息时,传统RAG表现不佳。
提问:“S在哪些国家工作过?”
传统RAG输出:“提供的文档未明确说明S工作过的国家数量……”
实际上,简历中隐含着工作地点信息(新加坡、菲律宾、印度),但分散在不同部分,传统RAG无法整合。
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
从传统RAG到智能体RAG:以LangGraph实现CRAG
我们采用LangGraph构建一个包含检索、评估、重写、Web搜索和生成节点的图。评估节点使用LLM判断文档相关性,如果发现文档不足,则触发查询重写和Web搜索。
定义状态
检索节点
文档评分节点
使用带结构化输出的LLM进行打分。
查询重写节点
当需要Web搜索时,先用LLM优化查询。
rewriter_llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0.3)
def rewrite(state: CRAGState) -> CRAGState:
print("---重写查询---")
new_q = rewriter_llm.invoke(f"将以下问题改写成更适合搜索引擎的表述:{state['query']}")
return {
"query": new_q.content,
"docs": state["docs"],
"web_needed": state["web_needed"]
}
Web搜索节点
使用Tavily API搜索,结果转为Document对象。
生成答案节点
条件路由函数
构建LangGraph
测试同样的问题:
提问:“S在哪些国家工作过?”
CRAG输出:“S至少在新加坡、菲律宾和印度工作过。他在新加坡的OneZero和Splo工作,为菲律宾一家大银行开发了概念验证”
系统成功从多个文档块中提取信息,并整合出完整答案。
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
智能体纠正性RAG:更精细的决策与多源融合
在CRAG基础上,我们可以进一步引入智能体(Agent)概念,让LLM不仅评估文档,还能自主决定使用哪个工具(知识库检索、Web搜索、直接回答),并管理多轮对话状态。这构成了“智能体纠正性RAG”(Agentic CRAG)。
其架构包含以下节点:
- 路由器节点:根据用户问题,决定走“直接回答”、“知识库检索”还是“结束”。
- RAG节点:执行知识库检索,并让一个“裁判”LLM判断检索块是否足够,若不足则标记需Web搜索。
- Web搜索节点:调用Tavily API获取实时信息。
- 回答节点:综合所有上下文生成最终答案。
LangGraph使得这些节点的编排变得直观。
实战:基于Wikipedia数据的智能问答
我们使用Wikipedia的子集(约500篇文档)构建知识库,然后测试系统在实时事件(如2024年欧冠)上的表现。
# 创建文档评分器(使用结构化输出)
class GradeDocs(BaseModel):
score: str = Field(description="'yes' 或 'no'")
grader = ChatOpenAI(model="gpt-4.1-mini", temperature=0).with_structured_output(GradeDocs)
定义智能体状态和节点后,编译图并测试。
提问:“2024年欧冠冠军是谁?”
输出:“2024年欧冠冠军是皇家马德里,他们在决赛中2-0击败多特蒙德,卡瓦哈尔和维尼修斯建功。”
由于本地知识库无此信息,系统自动触发Web搜索,获得了正确答案。
阅读原文进群获取完整内容及更多AI见解、行业洞察,与900+行业人士交流成长。
总结与展望
本文从最基础的LLM调用开始,逐步构建了具有记忆、工具使用能力的LangGraph智能体,进而集成了RAG,并最终实现了纠正性RAG(CRAG)和智能体纠正性RAG(Agentic CRAG)。通过实际代码和案例,我们展示了如何让LLM自主评估检索质量、动态决定是否补充实时信息,从而大幅提升问答系统的准确性和时效性。
这套方法已在多家企业的智能客服、文档分析项目中落地,证明了其在处理动态知识、多源融合方面的有效性。未来,我们还可以引入自我反思、多智能体协作等机制,让系统更加智能。希望本文能为读者在构建企业级RAG系统时提供有价值的参考。
参考文献
- Yan, S.-Q., Gu, J.-C., Zhu, Y., & Ling, Z.-H. (2024). Corrective Retrieval Augmented Generation. arXiv. doi.org/10.48550/ar…
- LangGraph Documentation. langchain-ai.github.io/langgraph/