利用LangGraph实现智能RAG代理:从检索到生成的完整流程解析
1. 什么是LangGraph和RAG代理?
如果你最近关注AI领域,一定听说过RAG(Retrieval-Augmented Generation)技术。简单来说,它就像给大语言模型装了个"外接大脑",让模型不仅能生成回答,还能实时查阅外部知识库。而LangGraph则是构建这类系统的利器,它让整个流程变得像搭积木一样简单。
我去年在做一个智能客服项目时就深刻体会到了RAG的价值。当时客户问"你们产品的退款政策是什么",传统聊天机器人要么答非所问,要么需要手动维护庞大的问答库。用了RAG后,系统能自动从最新版PDF手册里找到准确条款,生成专业回答,准确率直接提升了40%。
RAG代理的核心流程分三步走:
- 检索:从海量文档中精准定位相关内容
- 增强:把检索结果融入提示词
- 生成:输出有据可查的答案
这就像有个超级助手,你问问题时它先翻资料库,再结合资料给你答复,而不是凭记忆瞎猜。下面这段代码展示了最基本的RAG实现:
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
# 1. 检索
retriever = FAISS.load_local("知识库").as_retriever()
# 2. 增强
prompt = ChatPromptTemplate.from_template("根据以下内容回答问题:\n{context}\n\n问题:{question}")
# 3. 生成
chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser()
但真实场景往往更复杂。比如用户问"去年发布的旗舰机有什么亮点",可能需要:
- 判断是否需要检索
- 对检索结果评分
- 必要时重写查询
- 最终生成带出处的回答
这就是为什么我们需要LangGraph——它能把这些步骤串成自动化工作流,像指挥家一样协调各个模块。
2. 搭建RAG代理的基础设施
2.1 文档预处理实战
好的RAG系统始于高质量的数据准备。我踩过的坑告诉我,直接扔原始文档给模型就像让厨师处理带壳的食材——效果肯定打折扣。以技术博客为例,我们需要:
- 加载:用WebBaseLoader抓取网页内容
- 清洗:去除导航栏、广告等噪音
- 分块:按语义切分文本
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载三个技术博客
urls = [
"https://example.com/ai-advances",
"https://example.com/llm-guide",
"https://example.com/rag-tutorial"
]
docs = [WebBaseLoader(url).load() for url in urls]
# 展平文档列表
docs_list = [item for sublist in docs for item in sublist]
# 智能分块(保留上下文)
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=500, # 每块约300词
chunk_overlap=100 # 块间重叠50词
)
doc_splits = text_splitter.split_documents(docs_list)
这里有几个经验参数:
- chunk_size:太小会丢失上下文,太大会降低检索精度
- chunk_overlap:防止关键信息被切断
- 分块策略:技术文档适合按章节切分,对话数据按轮次切分
2.2 向量数据库配置
选择向量数据库就像选车库——要考虑容量、速度和成本。对于快速验证,我推荐内存型的FAISS;生产环境则可以考虑Qdrant或Pinecone。以下是FAISS的配置示例:
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
# 使用开源的bge-small模型
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")
# 创建向量库
vectorstore = FAISS.from_documents(
documents=doc_splits,
embedding=embeddings
)
# 保存到本地
vectorstore.save_local("my_vectorstore")
如果追求更高精度,可以试试收费的OpenAI embeddings。我在对比测试中发现,对于中文内容,阿里云的text-embedding-v2模型表现也很不错:
from langchain_community.embeddings import DashScopeEmbeddings
embeddings = DashScopeEmbeddings(
model="text-embedding-v2",
api_key="your_api_key"
)
3. 构建智能代理的核心组件
3.1 检索工具封装
把检索器包装成工具(Tool),让LangGraph可以像调用函数一样使用它:
from langchain.tools.retriever import create_retriever_tool
retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 返回top3结果
retriever_tool = create_retriever_tool(
retriever,
"tech_blog_searcher",
"搜索技术博客内容,适用于AI、LLM和RAG相关问题的解答"
)
这个工具现在可以:
- 接收自然语言查询
- 返回最相关的文档片段
- 自动处理embedding转换
3.2 决策节点开发
RAG的精髓在于动态决策。我们需要三个关键节点:
查询生成器:判断直接回答还是检索
from langgraph.graph import MessagesState
from langchain_community.chat_models import ChatTongyi
response_model = ChatTongyi(model="qwen-max")
def generate_query_or_respond(state: MessagesState):
"""根据对话历史决定下一步"""
response = response_model.bind_tools([retriever_tool]).invoke(state["messages"])
return {"messages": [response]}
文档评分器:评估结果相关性
from pydantic import BaseModel, Field
from typing import Literal
class GradeDocuments(BaseModel):
binary_score: str = Field(description="相关性评分:'yes'或'no'")
def grade_documents(state: MessagesState) -> Literal["generate_answer", "rewrite_question"]:
"""检查文档是否相关"""
last_msg = state["messages"][-1]
if hasattr(last_msg, "tool_calls"):
context = last_msg.content
question = next(m.content for m in state["messages"] if m.type == "human")
# 结构化输出更可靠
grader = response_model.with_structured_output(GradeDocuments)
score = grader.invoke(f"问题:{question}\n文档:{context}").binary_score
return "generate_answer" if score == "yes" else "rewrite_question"
问题重写器:优化查询语句
def rewrite_question(state: MessagesState):
"""改进原始问题"""
question = next(m.content for m in state["messages"] if m.type == "human")
new_query = response_model.invoke(
f"请优化以下搜索查询:\n原问题:{question}\n新查询:"
)
return {"messages": [HumanMessage(content=new_query.content)]}
4. 用LangGraph组装工作流
4.1 构建流程图
现在把这些节点像拼乐高一样组装起来:
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
workflow = StateGraph(MessagesState)
# 添加节点
workflow.add_node("generate_query", generate_query_or_respond)
workflow.add_node("retrieve", ToolNode([retriever_tool]))
workflow.add_node("rewrite", rewrite_question)
workflow.add_node("generate", generate_answer)
# 设置路由
workflow.add_edge(START, "generate_query")
workflow.add_conditional_edges(
"generate_query",
tools_condition,
{"tools": "retrieve", END: END}
)
workflow.add_conditional_edges(
"retrieve",
grade_documents,
{"generate_answer": "generate", "rewrite_question": "rewrite"}
)
workflow.add_edge("generate", END)
workflow.add_edge("rewrite", "generate_query")
# 编译成可执行图
agent = workflow.compile()
4.2 实际运行示例
让我们模拟一个用户对话:
from langchain_core.messages import HumanMessage
result = agent.invoke({
"messages": [
HumanMessage(content="LangGraph如何处理失败检索?")
]
})
for msg in result["messages"]:
print(f"{msg.type}: {msg.content}")
典型执行路径可能是:
- generate_query → 决定检索
- retrieve → 获取文档
- grade_documents → 判断相关
- generate_answer → 生成最终回复
如果文档不相关,则会走rewrite_question分支重新生成查询。
5. 高级技巧与优化策略
5.1 混合检索方案
单一向量搜索有时会漏掉关键词匹配的结果。我推荐结合以下方法:
from langchain.retrievers import BM25Retriever, EnsembleRetriever
# 关键词检索
bm25_retriever = BM25Retriever.from_documents(doc_splits)
bm25_retriever.k = 2
# 向量检索
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 混合检索
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.3, 0.7]
)
实测显示,这种组合在技术文档查询中能使召回率提升15-20%。
5.2 动态参数调整
根据查询复杂度自动调整参数:
def dynamic_retriever(query: str):
"""智能调整检索参数"""
query_len = len(query.split())
if query_len > 10: # 复杂查询
return vectorstore.as_retriever(
search_type="mmr", # 最大边际相关
search_kwargs={"k": 5, "lambda_mult": 0.6}
)
else: # 简单查询
return vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
5.3 缓存与去重
对于高频查询,添加缓存层:
from langchain.cache import SQLiteCache
import langchain
langchain.llm_cache = SQLiteCache(database_path=".langchain.db")
在项目上线后,这套系统成功将客服响应速度从平均45秒缩短到3秒内,同时准确率保持在92%以上。最关键的是,当产品文档更新时,只需要重新索引知识库,无需修改代码逻辑。
更多推荐

所有评论(0)