LangGraph实战:构建智能决策的Agentic RAG系统
最近在帮一个团队重构他们的问答系统时我遇到了一个典型问题他们用传统RAG处理用户提问但当用户问“Lilian Weng对奖励黑客的分类有什么看法”时系统要么直接回答“不知道”要么返回一堆不相关的文档片段。这让我意识到很多团队把RAG理解成了简单的“检索-生成”流水线却忽略了最关键的一环让AI自己判断什么时候该检索什么时候该直接回答以及检索到的内容是否真的相关。这正是LangGraph的价值所在——它不只是另一个框架而是重新定义了AI应用的工作方式。通过将决策权交还给AI本身我们能够构建出真正理解用户意图的智能系统。1. 为什么传统的RAG不够用而Agentic RAG才是出路传统RAG系统有一个根本性缺陷它们假设每次用户提问都需要检索外部知识。但在实际应用中很多问题根本不需要检索——比如“你好吗”或者“谢谢你的帮助”。更糟糕的是即使检索到了内容传统RAG也无法判断这些内容是否真的相关。如果用户问“奖励黑客的分类”但系统检索到的是关于“视频扩散模型”的文档它仍然会试图基于不相关内容生成答案导致输出质量下降。1.1 Agentic RAG的核心突破让AI掌握决策权Agentic RAG的核心思想是引入决策机制。系统会先让AI判断这个问题需要检索外部知识吗如果需要应该用什么关键词检索检索到的内容真的相关吗如果不相关是重新表述问题还是直接承认不知道这种决策能力来自于LangGraph的图结构设计。每个节点代表一个决策点边代表可能的流转路径。这种设计让RAG从静态流水线变成了动态决策系统。1.2 实际场景中的决策价值考虑这样一个场景用户问“Lilian Weng最近在研究什么”这是一个开放性问题直接检索可能返回大量无关信息。在Agentic RAG中系统会先判断是否需要细化问题生成更具体的检索查询比如“Lilian Weng 2024年研究主题”对检索结果进行相关性评估只在确认内容相关时才生成最终答案这种工作流显著提升了答案的准确性和实用性。2. LangGraph实战构建完整的Agentic RAG系统让我们从零开始构建一个完整的Agentic RAG系统。我会重点讲解每个环节的设计思路和实际考量。2.1 环境准备与依赖管理首先我们需要安装核心依赖。这里的关键是版本兼容性——LangGraph生态更新较快固定版本可以避免意外问题。pip install langgraph0.0.40 langchain-anthropic0.0.11 langchain-text-splitters0.0.1 bs4 requests环境变量配置是第一个容易踩坑的地方。很多教程只简单提到设置API密钥但实际生产中还需要考虑超时、重试策略等。import os import getpass def setup_environment(): 安全的环境变量配置 required_vars { OPENAI_API_KEY: 你的OpenAI API密钥, ANTHROPIC_API_KEY: 可选的Claude API密钥 # 用于多模型备选 } for var, description in required_vars.items(): if var not in os.environ: print(f请输入{description}:) os.environ[var] getpass.getpass(f{var}: ) # 初始化环境 setup_environment()2.2 文档预处理质量决定上限文档预处理是RAG系统的基石。很多团队在这里投入不足导致后续环节效果大打折扣。import bs4 import requests from langchain_core.documents import Document from langchain_text_splitters import RecursiveCharacterTextSplitter def load_web_page(url: str, timeout: int 30) - list[Document]: 稳健的网页内容提取 try: response requests.get(url, timeouttimeout) response.raise_for_status() # 使用更精确的解析策略 soup bs4.BeautifulSoup(response.text, html.parser) # 移除脚本和样式标签 for script in soup([script, style]): script.decompose() # 提取主要文本内容 text soup.get_text(separator\n, stripTrue) return [Document(page_contenttext, metadata{source: url})] except Exception as e: print(f加载URL {url} 时出错: {e}) return [] # 选择高质量的知识源 urls [ https://lilianweng.github.io/posts/2024-11-28-reward-hacking/, https://lilianweng.github.io/posts/2024-07-07-hallucination/, https://lilianweng.github.io/posts/2024-04-12-diffusion-video/, ] # 并行加载提升效率 docs [] for url in urls: page_docs load_web_page(url) docs.extend(page_docs) print(f成功加载 {len(docs)} 个文档)文本分割策略需要根据内容特点调整。技术博客通常需要保留完整的代码块和论证逻辑因此分块大小可以适当放大。# 针对技术博客的优化分块策略 text_splitter RecursiveCharacterTextSplitter( chunk_size800, # 适当增大以保持上下文完整 chunk_overlap100, # 重叠确保关键信息不丢失 separators[\n\n, \n, . , ! , ? , , ] # 自然语言分隔符 ) doc_splits text_splitter.split_documents(docs) print(f分割后得到 {len(doc_splits)} 个文本块)2.3 构建智能检索工具检索工具的设计直接影响Agent的决策质量。我们需要考虑缓存、相似度阈值等工程细节。from langchain_core.vectorstores import InMemoryVectorStore from langchain_openai import OpenAIEmbeddings from functools import lru_cache from langchain.tools import tool lru_cache(maxsize1) # 单例模式避免重复初始化 def get_retriever(): 创建带缓存的检索器 vectorstore InMemoryVectorStore.from_documents( documentsdoc_splits, embeddingOpenAIEmbeddings(modeltext-embedding-3-small), ) return vectorstore.as_retriever(search_kwargs{k: 3}) # 控制返回结果数量 tool def retrieve_blog_posts(query: str) - str: 智能检索工具带有质量控制的文档检索 try: retriever get_retriever() retrieved_docs retriever.invoke(query) # 质量控制只返回内容丰富的文档 meaningful_docs [ doc for doc in retrieved_docs if len(doc.page_content.strip()) 50 # 过滤过短内容 ] if not meaningful_docs: return 未找到相关文档 return \n\n.join([doc.page_content for doc in meaningful_docs]) except Exception as e: return f检索过程中出错: {e} # 测试检索工具 test_result retrieve_blog_posts.invoke({query: 奖励黑客类型}) print(检索测试结果:, len(test_result), 字符)3. LangGraph核心架构理解状态流转机制LangGraph的强大之处在于其基于状态机的设计。理解状态流转是掌握LangGraph的关键。3.1 定义图状态MessagesState的设计思路MessagesState是LangGraph的核心抽象它维护了对话的完整历史。from langgraph.graph import MessagesState from langchain.chat_models import init_chat_model # 初始化响应模型 response_model init_chat_model(openai:gpt-4o-mini, temperature0) def generate_query_or_respond(state: MessagesState): 决策节点判断是否需要检索 # 绑定工具让模型知道可以调用检索功能 model_with_tools response_model.bind_tools([retrieve_blog_posts]) # 基于当前对话状态生成响应 response model_with_tools.invoke(state[messages]) return {messages: [response]}3.2 条件边实现智能路由条件边是LangGraph实现决策逻辑的核心机制。from typing import Literal from pydantic import BaseModel, Field class GradeDocuments(BaseModel): 文档评分模型结构化输出确保可靠性 binary_score: str Field( description相关性评分: yes表示相关, no表示不相关 ) def grade_documents(state: MessagesState) - Literal[generate_answer, rewrite_question]: 文档评估节点判断检索结果的相关性 if len(state[messages]) 3: return rewrite_question # 消息不完整时重新提问 question state[messages][0].content context state[messages][-1].content # 使用结构化输出确保评估一致性 grader_model init_chat_model(openai:gpt-4o-mini, temperature0) prompt f 你是一个评估检索文档相关性的专家。 用户问题: {question} 检索到的文档内容: {context} 请判断文档内容是否与用户问题相关。只返回yes或no。 try: response grader_model.with_structured_output(GradeDocuments).invoke( [{role: user, content: prompt}] ) return generate_answer if response.binary_score yes else rewrite_question except Exception: return rewrite_question # 出错时保守策略3.3 问题重写机制提升检索精度当检索结果不相关时问题重写是提升效果的关键策略。def rewrite_question(state: MessagesState): 问题重写节点优化检索查询 question state[messages][0].content prompt f 原始问题: {question} 这个问题的检索结果不理想。请分析问题的语义意图并重新表述一个更精确的检索查询。 考虑以下优化方向: 1. 添加具体的上下文信息 2. 使用更专业的关键词 3. 明确查询的具体范围 只返回优化后的问题不要添加解释。 response response_model.invoke([{role: user, content: prompt}]) return {messages: [{role: user, content: response.content}]}4. 组装完整的工作流从节点到智能体有了各个组件后我们需要将它们组装成完整的工作流。4.1 构建图结构定义节点和边from langgraph.graph import END, START, StateGraph from langgraph.prebuilt import ToolNode # 初始化图 workflow StateGraph(MessagesState) # 添加节点 workflow.add_node(generate_query_or_respond, generate_query_or_respond) workflow.add_node(retrieve, ToolNode([retrieve_blog_posts])) workflow.add_node(rewrite_question, rewrite_question) workflow.add_node(generate_answer, generate_answer) # 定义起始点 workflow.add_edge(START, generate_query_or_respond) def route_on_tool_calls(state: MessagesState): 路由逻辑根据工具调用决定下一步 last_message state[messages][-1] if hasattr(last_message, tool_calls) and last_message.tool_calls: return retrieve return END # 条件边判断是否需要检索 workflow.add_conditional_edges( generate_query_or_respond, route_on_tool_calls, { retrieve: retrieve, END: END, } ) # 条件边评估检索结果 workflow.add_conditional_edges( retrieve, grade_documents, { generate_answer: generate_answer, rewrite_question: rewrite_question, } ) # 重写问题后回到决策节点 workflow.add_edge(rewrite_question, generate_query_or_respond) workflow.add_edge(generate_answer, END) # 编译图 graph workflow.compile()4.2 答案生成节点基于上下文的精准回答def generate_answer(state: MessagesState): 答案生成节点基于检索内容生成最终回答 question state[messages][0].content context state[messages][-1].content prompt f 你是一个专业的技术问答助手。基于以下上下文回答问题。 问题: {question} 相关上下文: {context} 要求: 1. 只基于提供的上下文回答 2. 如果上下文不包含答案明确说明不知道 3. 回答要简洁准确最多3句话 4. 不要添加上下文之外的信息 response response_model.invoke([{role: user, content: prompt}]) return {messages: [response]}5. 运行与优化让智能体真正工作起来构建完图之后我们需要测试和优化整个系统。5.1 测试工作流验证决策逻辑def test_agentic_rag(question: str): 测试Agentic RAG系统 print(f测试问题: {question}) print(- * 50) try: # 流式输出便于观察决策过程 events graph.stream( {messages: [{role: user, content: question}]}, stream_modevalues ) for event in events: messages event[messages] if messages: last_msg messages[-1] if hasattr(last_msg, content): print(fAI: {last_msg.content}) if hasattr(last_msg, tool_calls): print(f工具调用: {last_msg.tool_calls}) except Exception as e: print(f执行出错: {e}) # 测试不同场景 test_cases [ Lilian Weng对奖励黑客的分类有什么看法, # 需要检索 你好最近怎么样, # 直接回答 请总结一下幻觉问题的研究进展 # 需要精确检索 ] for i, question in enumerate(test_cases, 1): print(f\n测试案例 {i}:) test_agentic_rag(question) print()5.2 性能优化提升响应速度和质量在实际部署中我们需要考虑性能优化from functools import lru_cache import time class OptimizedRAGAgent: 带性能优化的RAG智能体 def __init__(self): self.graph graph self.response_cache {} lru_cache(maxsize1000) def get_cached_response(self, question: str) - str: 带缓存的响应生成 start_time time.time() result self.graph.invoke( {messages: [{role: user, content: question}]} ) response_time time.time() - start_time print(f响应时间: {response_time:.2f}秒) return result[messages][-1].content def ask(self, question: str, use_cache: bool True) - str: 优化的问答接口 if use_cache and question in self.response_cache: return self.response_cache[question] response self.get_cached_response(question) if use_cache: self.response_cache[question] response return response # 使用优化后的智能体 agent OptimizedRAGAgent() response agent.ask(奖励黑客的主要类型有哪些) print(优化后响应:, response)6. 生产环境部署从原型到实战将LangGraph智能体部署到生产环境需要考虑更多工程因素。6.1 错误处理与容错机制class ProductionRAGAgent: 生产环境级的RAG智能体 def __init__(self, max_retries: int 3): self.graph graph self.max_retries max_retries def invoke_with_retry(self, input_data: dict) - dict: 带重试的调用机制 for attempt in range(self.max_retries): try: return self.graph.invoke(input_data) except Exception as e: if attempt self.max_retries - 1: raise e print(f第{attempt 1}次尝试失败重试中...) time.sleep(1) # 指数退避可以更复杂 def safe_ask(self, question: str) - dict: 安全的问答接口返回完整执行信息 try: start_time time.time() result self.invoke_with_retry( {messages: [{role: user, content: question}]} ) execution_time time.time() - start_time return { success: True, answer: result[messages][-1].content, execution_time: execution_time, message_count: len(result[messages]) } except Exception as e: return { success: False, error: str(e), answer: 抱歉暂时无法回答这个问题 } # 生产环境使用 production_agent ProductionRAGAgent() result production_agent.safe_ask(奖励黑客的研究现状) print(生产环境响应:, result)6.2 监控与日志记录import logging from datetime import datetime class MonitoredRAGAgent: 带监控的RAG智能体 def __init__(self): self.graph graph self.logger self._setup_logging() def _setup_logging(self): 设置结构化日志 logger logging.getLogger(rag_agent) logger.setLevel(logging.INFO) if not logger.handlers: handler logging.FileHandler(rag_agent.log, encodingutf-8) formatter logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(message)s ) handler.setFormatter(formatter) logger.addHandler(handler) return logger def ask_with_monitoring(self, question: str, user_id: str anonymous) - str: 带监控的问答 start_time datetime.now() self.logger.info(f用户 {user_id} 提问: {question}) try: result self.graph.invoke( {messages: [{role: user, content: question}]} ) answer result[messages][-1].content duration (datetime.now() - start_time).total_seconds() self.logger.info( f问题回答成功 - 用户: {user_id}, f时长: {duration:.2f}s, 答案长度: {len(answer)} ) return answer except Exception as e: self.logger.error(f回答失败 - 用户: {user_id}, 错误: {str(e)}) return 系统暂时无法处理这个问题请稍后重试 # 使用监控版本 monitored_agent MonitoredRAGAgent() response monitored_agent.ask_with_monitoring(幻觉问题的最新研究, user_123)通过这个完整的LangGraph Agentic RAG实战指南你应该能够理解如何将传统的检索增强生成升级为真正的智能决策系统。关键是要记住LangGraph的价值不在于提供新的API而在于改变了我们构建AI应用的基本思路——从线性流水线到动态决策图。在实际项目中建议先从简单的决策逻辑开始逐步增加复杂度。同时要密切关注每个节点的性能和质量因为整个系统的效果取决于最弱的那一环。