如果你正在学习大语言模型应用开发,可能会遇到这样的困惑:看了很多教程,每个概念都懂,但一到实际项目就无从下手。特别是面对 LangChain、LangGraph、Agent、RAG 这些热门技术时,感觉它们各自为战,不知道如何将它们组合成一个真正能跑起来的、有实际价值的智能体系统。
更具体地说,你可能:
- 跟着教程跑通了 LangChain 的 Hello World,但不知道如何用它处理复杂的、多步骤的业务逻辑。
- 听说过 Agent 能“思考”和“使用工具”,但自己写的 Agent 总是逻辑混乱,无法完成预定任务。
- 搭建了 RAG 系统,但回答质量不稳定,不知道如何引入更强大的推理和控制能力。
- 看到 LangGraph 这个词,感觉它很强大,但官方文档概念抽象,缺少一个从零到一的完整项目串联。
这篇文章要解决的,正是这个“从孤立知识点到完整项目”的断层问题。我的核心判断是:LangGraph 不是 LangChain 的替代品,而是其“大脑”和“调度中心”。它将 LangChain 提供的各种工具(Tools)、记忆(Memory)和模型(LLMs)组织成可控的、有状态的工作流,是构建复杂、可靠 AI 智能体的关键框架。
本文将围绕一个核心目标展开:带你从零开始,用 LangChain + LangGraph 构建一个具备长期记忆、能调用工具、并可进行复杂推理的智能体(Agent)。我们会融入 RAG 作为知识库,并初步探讨 MCP 协议如何扩展工具能力。这不是简单的 API 调用演示,而是一个贴近真实开发场景的实战项目,你会看到清晰的架构设计、每一步的代码实现、常见的“坑”以及生产环境的注意事项。
读完本文,你将能:
- 清晰理解 LangChain、LangGraph、Agent、RAG 在技术栈中的定位与协作关系。
- 掌握使用 LangGraph 构建有状态、可循环、带条件判断的智能体工作流。
- 实现一个具备对话记忆、能查询知识库(RAG)、能执行代码(工具调用)的实用智能体。
- 了解 MCP 协议的概念及其在工具生态中的价值。
- 获得一套可运行、可扩展的完整项目代码,并知道如何将其适配到自己的业务中。
1. 为什么你需要关注 LangChain + LangGraph 这套组合?
在 AI 应用开发领域,我们正从一个“单次问答”的简单模式,走向“多轮交互、自主规划、使用工具”的智能体时代。LangChain 早期解决了“连接”问题,把大模型、向量数据库、各种工具链接在一起。但当业务流程变得复杂时,单纯链式调用就显得力不从心了。
想象一下,你要开发一个智能数据分析助手,用户可能说:“帮我分析上个月的销售数据,找出增长最快的三个产品,然后为每个产品生成一段市场推广文案。”这个任务包含了多个步骤:理解意图、查询数据库、执行分析、排序筛选、调用文案生成模型。步骤之间有依赖关系(必须先拿到数据才能分析),有状态传递(分析结果要传给文案生成),还可能循环(为每个产品生成文案)。
LangChain 本身不擅长描述这种复杂、有状态的工作流。这就是 LangGraph 登场的原因。它本质上是一个基于图(Graph)的工作流编排框架,专门为构建有状态的、多智能体协作的应用而设计。它的核心价值在于:
- 显式的工作流定义:用节点(Node)和边(Edge)清晰地描绘出应用的执行路径,代码即架构图,可读性和可维护性极大提升。
- 内置的状态管理:自动在节点间传递和更新一个共享的“状态”对象,省去手动管理中间变量的麻烦。
- 灵活的循环与路由:轻松实现“思考-行动-观察”的 ReAct 循环,或根据条件跳转到不同分支。
- 对多智能体的原生支持:可以方便地定义多个具有不同角色的智能体,并编排它们之间的协作。
所以,LangChain + LangGraph 的组合,构成了当前开发复杂 AI 智能体最主流、最工程化的技术栈之一。LangChain 提供丰富的组件(模型 I/O、检索器、工具),LangGraph 提供强大的编排能力。学习它,意味着你掌握了构建下一代 AI 应用的核心方法论。
2. 核心概念拆解:LangChain, LangGraph, Agent, RAG, MCP 分别是什么?
在深入代码之前,我们必须统一语言。这些术语经常被混用,但它们在架构中扮演着截然不同的角色。
| 概念 | 通俗理解 | 在项目中的角色 | 常见误区 |
|---|---|---|---|
| LangChain | “连接器”与“工具箱” | 提供与大模型对话的接口(LLM)、从文档中提取和检索知识的能力(RAG)、以及封装好的各种工具函数(Tools,如计算器、搜索引擎API)。 | 以为 LangChain 本身就是一个完整的应用框架。实际上,它更偏向于提供标准化、可复用的底层模块。 |
| LangGraph | “大脑”与“调度中心” | 定义智能体的“思考”流程。它决定先做什么、后做什么、遇到条件如何判断、如何循环。它调用 LangChain 提供的工具和模型。 | 以为 LangGraph 是 LangChain 的替代品。实际上,它依赖于 LangChain 的组件,并赋予它们“智能”的协作能力。 |
| Agent | “执行者” | 一个能理解目标、规划步骤、调用工具(来自 LangChain)来完成任务的大模型驱动程序。在 LangGraph 中,一个或多个节点可以承担 Agent 的职能。 | 以为 Agent 是一个神秘的、不可控的黑盒。实际上,它的行为完全由 LangGraph 定义的工作流和提供的工具所约束。 |
| RAG | “外部知识库” | 一种为大模型提供非参数化知识的技术。通过将文档切片、向量化存储,在问答时快速检索相关片段注入上下文,让模型能回答训练数据之外的问题。 | 以为 RAG 能100%解决幻觉问题。实际上,检索质量、上下文长度、模型指令遵循能力共同决定最终效果。 |
| MCP | “工具插拔协议” | Model Context Protocol,一个新兴的开放协议。它旨在标准化 AI 应用(如智能体)与外部工具、数据源之间的连接方式,让工具可以像插件一样即插即用。 | 以为 MCP 是某个特定工具。实际上,它是一个协议标准,未来可能改变 Agent 工具生态的集成方式。 |
它们之间的关系:你可以把LangGraph看作导演,LangChain看作道具组和演员资源库,Agent是执行具体动作的演员,RAG是演员可以随时查阅的剧本库,而MCP则是一种新的、更标准的道具接入规范。导演(LangGraph)根据剧情(用户输入),指挥演员(Agent)利用道具(LangChain Tools)和剧本(RAG),完成一场演出(任务)。
3. 环境准备:构建你的智能体开发环境
我们的目标是构建一个本地可运行的开发环境。为了兼顾效果和本地部署的便利性,我们选择以下方案:
- 大模型:使用Ollama本地运行DeepSeek-V3模型。Ollama 极大简化了本地大模型的部署和管理。
- 向量数据库:使用ChromaDB,因为它轻量、易用,且与 LangChain 集成良好。
- 开发框架:LangChain和LangGraph。
- Python环境:建议使用 Python 3.10 或以上版本。
3.1 基础环境安装
首先,确保你的系统已安装 Python 和 pip。然后,创建一个新的虚拟环境并安装核心依赖。
# 创建并激活虚拟环境 (Windows 用户请使用 `python -m venv venv` 和 `venv\Scripts\activate`) python3 -m venv langgraph-agent-env source langgraph-agent-env/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 升级pip pip install --upgrade pip # 安装 LangChain 全家桶和 ChromaDB pip install langchain langchain-community langgraph langchain-chroma # 安装 Ollama 的 LangChain 集成包 pip install langchain-ollama # 安装其他可能用到的工具包 pip install pypdf # 用于读取PDF文档 pip install tiktoken # 用于token计数(非必须,但推荐)3.2 安装并配置 Ollama
Ollama 的安装请参考其 官方文档 。安装完成后,在终端拉取我们需要的模型。
# 拉取 DeepSeek-V3 模型 (请根据你的硬件选择合适版本,如 deepseek-r1:7b) ollama pull deepseek-r1:7b # 启动 Ollama 服务(通常安装后会自动运行) # 检查服务是否运行 ollama list如果看到deepseek-r1:7b在列表中,说明模型已就绪。
3.3 验证基础环境
创建一个简单的 Python 脚本test_env.py来验证 LangChain 和 Ollama 是否能正常工作。
# test_env.py from langchain_ollama import OllamaLLM from langchain_core.prompts import ChatPromptTemplate # 1. 初始化本地LLM llm = OllamaLLM(model="deepseek-r1:7b") # 2. 创建一个简单的提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个乐于助人的助手。"), ("user", "{input}") ]) # 3. 创建链并调用 chain = prompt | llm response = chain.invoke({"input": "你好,请用一句话介绍你自己。"}) print("模型回复:", response)运行这个脚本:
python test_env.py如果看到模型返回了一段自我介绍,恭喜你,基础环境搭建成功!如果遇到连接错误,请检查 Ollama 服务是否正在运行(ollama serve)。
4. 项目架构设计:我们要构建一个什么样的智能体?
在写代码前,先进行设计。我们将构建一个“多功能研究助手”智能体,它具备以下能力:
- 对话记忆:能记住同一会话中的历史对话。
- 知识库问答(RAG):当用户问题涉及我们提供的专用知识(如公司文档、技术手册)时,能优先从知识库中查找答案。
- 工具调用:能执行一些实用功能,例如:
search_web: 搜索网络信息(此处我们用模拟函数代替真实API)。execute_python: 执行简单的 Python 代码并返回结果(在安全沙箱中)。
- 自主规划与执行:根据用户问题,自主决定是否需要使用知识库、是否需要调用工具、以及调用哪个工具。
技术架构图(文字描述):
用户输入 | v [LangGraph 工作流入口] | v [路由节点] --(是知识库问题?)--> [RAG 检索节点] --(检索结果)--> [回答生成节点] | | |--(需要工具?)--> [工具调用节点] --(工具结果)--| | v [最终回答节点] --> 输出给用户这个工作流的核心是一个“路由”逻辑,由一个大模型(LLM)根据用户问题和历史来判断下一步该走哪条分支。LangGraph 完美支持这种模式。
5. 分步实现:从知识库搭建到智能体工作流
5.1 第一步:构建本地知识库(RAG)
假设我们有一份关于“LangGraph 最佳实践”的 PDF 文档。我们将其加载、切分、向量化并存入 ChromaDB。
# rag_setup.py import os from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings # 1. 加载文档 loader = PyPDFLoader("./docs/langgraph_best_practices.pdf") # 请准备你的PDF文件 documents = loader.load() # 2. 分割文档 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每个片段500字符 chunk_overlap=50, # 重叠50字符以保证上下文 separators=["\n\n", "\n", "。", "!", "?", ",", "、", " ", ""] ) splits = text_splitter.split_documents(documents) print(f"文档被分割成 {len(splits)} 个片段。") # 3. 初始化嵌入模型和向量数据库 # 使用 Ollama 的嵌入模型,例如 nomic-embed-text embeddings = OllamaEmbeddings(model="nomic-embed-text") # 指定持久化目录 persist_directory = "./chroma_db" # 4. 创建向量库 vectorstore = Chroma.from_documents( documents=splits, embedding=embeddings, persist_directory=persist_directory ) vectorstore.persist() # 持久化到磁盘 print("知识库构建完成,已保存至:", persist_directory)运行此脚本后,./chroma_db目录下会保存向量数据库。后续我们可以直接加载它。
5.2 第二步:定义智能体可用的工具
工具是智能体与外界交互的“手”。我们定义两个示例工具。
# tools.py import subprocess import sys from langchain.tools import tool from typing import Optional @tool def search_web(query: str) -> str: """在互联网上搜索信息。对于需要最新、非本地知识的问题非常有用。""" # 注意:这是一个模拟函数。真实场景应接入 SerperAPI、Google Search API 等。 # 此处返回模拟结果。 print(f"[工具调用] 正在搜索: {query}") # 模拟网络延迟 import time time.sleep(0.5) return f"关于 '{query}' 的模拟搜索结果:LangGraph 是一个用于构建有状态、多智能体应用的工作流库。" @tool def execute_python(code: str) -> str: """执行一段 Python 代码并返回结果。用于计算、数据处理或测试代码片段。""" print(f"[工具调用] 正在执行 Python 代码:\n```python\n{code}\n```") try: # 使用 subprocess 在隔离环境中运行代码,更安全 result = subprocess.run( [sys.executable, "-c", code], capture_output=True, text=True, timeout=10 ) if result.returncode == 0: output = result.stdout.strip() return f"代码执行成功。输出:\n{output}" if output else "代码执行成功,无输出。" else: return f"代码执行出错(返回码 {result.returncode}):\n{result.stderr}" except subprocess.TimeoutExpired: return "错误:代码执行超时(10秒)。" except Exception as e: return f"执行过程中发生未知错误:{str(e)}" # 将工具放入列表,供后续使用 AGENT_TOOLS = [search_web, execute_python]安全提醒:execute_python工具在生产环境中存在极大安全风险。此处仅为演示,真实场景必须使用严格的沙箱环境(如 Docker 容器)或禁用此类危险工具。
5.3 第三步:定义 LangGraph 工作流的状态(State)
状态是 LangGraph 工作流中流动的“血液”,它包含了所有节点需要共享的信息。
# state.py from typing import TypedDict, List, Annotated import operator class AgentState(TypedDict): """智能体工作流的状态定义。""" # 用户当前输入的问题 input: str # 对话历史 chat_history: List[str] # 从知识库检索到的上下文 context: str # 大模型生成的中间“思考”过程 reasoning: str # 需要调用的工具名称 tool_to_call: str # 调用工具时的参数 tool_input: str # 工具返回的结果 tool_output: str # 最终给用户的回答 final_output: str # 定义一个特殊的“归约”类型,用于让 chat_history 能自动追加消息。 # 这是 LangGraph 处理列表状态更新的推荐方式。 class GraphState(TypedDict): input: str chat_history: Annotated[List[str], operator.add] # 关键:自动追加 context: str reasoning: str tool_to_call: str tool_input: str tool_output: str final_output: strAnnotated[List[str], operator.add]是 LangGraph 的一个魔法,它告诉框架,当多个节点修改chat_history时,应该用+操作符(即追加)来合并它们的修改,而不是覆盖。
5.4 第四步:构建核心工作流图
这是最核心的部分。我们将创建多个节点函数,并用StateGraph将它们连接起来。
# graph.py from langgraph.graph import StateGraph, END from .state import GraphState from .tools import AGENT_TOOLS from langchain_ollama import OllamaLLM from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.agents import create_react_agent from langchain.agents.format_scratchpad import format_log_to_str from langchain.agents.output_parsers import ReActSingleInputOutputParser from langchain.schema import AIMessage, HumanMessage # 初始化 LLM llm = OllamaLLM(model="deepseek-r1:7b", temperature=0.1) # 加载之前创建的向量数据库 from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings embeddings = OllamaEmbeddings(model="nomic-embed-text") vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings) retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 检索最相关的3个片段 def retrieve_context(state: GraphState): """节点函数:从知识库中检索相关内容。""" print(f"[节点:检索] 正在检索与问题相关的知识...") docs = retriever.invoke(state["input"]) context = "\n\n".join([doc.page_content for doc in docs]) return {"context": context} def route_question(state: GraphState): """节点函数:路由决策。判断问题类型,决定下一步。""" print(f"[节点:路由] 分析问题类型...") # 这是一个简化的路由逻辑。实际应用中,可以用一个LLM来做更复杂的判断。 question = state["input"].lower() # 规则1:如果问题明显是关于我们知识库主题的(例如包含“langgraph”) if "langgraph" in question: return "retrieve" # 规则2:如果问题要求计算、搜索或执行代码 elif any(keyword in question for keyword in ["计算", "搜索", "python", "代码", "执行"]): return "call_tool" # 规则3:其他情况,直接回答 else: return "generate_answer" def call_tool(state: GraphState): """节点函数:调用工具。""" tool_name = state.get("tool_to_call") tool_input = state.get("tool_input") if not tool_name or not tool_input: # 如果没有指定工具,则让LLM决定 return {"final_output": "错误:未指定要调用的工具或输入参数。"} print(f"[节点:调用工具] 调用工具 '{tool_name}',输入:{tool_input}") # 查找工具 tool_map = {tool.name: tool for tool in AGENT_TOOLS} if tool_name not in tool_map: return {"final_output": f"错误:未知工具 '{tool_name}'。"} # 执行工具 try: tool_result = tool_map[tool_name].invoke(tool_input) return {"tool_output": tool_result} except Exception as e: return {"tool_output": f"工具执行失败:{str(e)}"} def generate_answer(state: GraphState): """节点函数:生成最终回答。""" print(f"[节点:生成回答] 综合信息生成回答...") # 构建提示词 prompt_template = ChatPromptTemplate.from_messages([ ("system", """你是一个AI研究助手。请根据以下信息,结合你的知识,给用户一个准确、有帮助的回答。 相关上下文(来自知识库): {context} 工具执行结果(如果有): {tool_output} 请用中文回答。如果信息不足,可以说明。"""), MessagesPlaceholder(variable_name="chat_history"), ("user", "{input}") ]) # 准备输入 messages = prompt_template.format_messages( context=state.get("context", "无"), tool_output=state.get("tool_output", "无"), chat_history=state["chat_history"], input=state["input"] ) # 调用LLM response = llm.invoke(messages) # 更新对话历史 new_history = state["chat_history"] + [ HumanMessage(content=state["input"]), AIMessage(content=response.content) ] return {"final_output": response.content, "chat_history": new_history} def update_history(state: GraphState): """节点函数:将本轮对话更新到历史中(在最终输出后调用)。""" # 此节点主要演示状态更新。generate_answer 中已经更新了历史。 # 这里可以做一些清理或格式化工作。 print(f"[节点:更新历史] 本轮对话结束。") return {} # 构建图 workflow = StateGraph(GraphState) # 添加节点 workflow.add_node("retrieve", retrieve_context) # 检索知识库 workflow.add_node("call_tool", call_tool) # 调用工具 workflow.add_node("generate_answer", generate_answer) # 生成回答 workflow.add_node("update_history", update_history) # 更新历史 # 设置入口点:首先进行路由决策 workflow.set_conditional_entry_point( route_question, { "retrieve": "retrieve", # 去检索 "call_tool": "call_tool", # 去调用工具 "generate_answer": "generate_answer" # 直接回答 } ) # 定义边(连接节点) workflow.add_edge("retrieve", "generate_answer") # 检索完后生成回答 workflow.add_edge("call_tool", "generate_answer") # 调用工具后生成回答 workflow.add_edge("generate_answer", "update_history") # 生成回答后更新历史 workflow.add_edge("update_history", END) # 最后结束 # 编译图 app = workflow.compile()这个图定义了完整的工作流:入口根据问题类型路由,分别走向检索、调用工具或直接生成回答,最后汇聚到生成回答节点,更新历史后结束。
5.5 第五步:创建并运行智能体
现在,我们将所有部分组合起来,并创建一个简单的交互循环。
# main.py import sys sys.path.append('.') # 确保可以导入自定义模块 from graph import app from state import GraphState def run_agent(): print("=== 多功能研究助手智能体已启动 ===") print("输入 'quit' 或 'exit' 退出程序。") print("-" * 40) # 初始化状态 initial_state = GraphState( input="", chat_history=[], context="", reasoning="", tool_to_call="", tool_input="", tool_output="", final_output="" ) while True: try: user_input = input("\n你: ").strip() if user_input.lower() in ['quit', 'exit', 'q']: print("再见!") break if not user_input: continue # 更新状态中的输入 initial_state["input"] = user_input # 执行工作流 print("\n[智能体正在思考...]") final_state = app.invoke(initial_state) # 输出结果 print(f"\n助手: {final_state['final_output']}") print("-" * 40) # 为下一轮对话更新初始状态(保留历史,清空其他中间状态) initial_state = GraphState( input="", chat_history=final_state["chat_history"], # 保留历史 context="", reasoning="", tool_to_call="", tool_input="", tool_output="", final_output="" ) except KeyboardInterrupt: print("\n\n程序被中断。") break except Exception as e: print(f"\n发生错误:{e}") # 可以选择重置状态或继续 initial_state = GraphState( input="", chat_history=[], context="", reasoning="", tool_to_call="", tool_input="", tool_output="", final_output="" ) if __name__ == "__main__": run_agent()6. 运行与效果验证
现在,让我们运行这个智能体,并进行多轮对话测试。
启动智能体:
python main.py测试对话记忆:
你: 你好,我叫小明。 [智能体正在思考...] 助手: 你好小明!很高兴认识你。我是你的AI研究助手,可以帮你查询知识、搜索信息或执行简单的计算。有什么我可以帮你的吗? --- 你: 我刚才说我叫什么名字? [智能体正在思考...] 助手: 你刚才说你叫小明。- 验证点:智能体正确回忆了历史对话。
测试知识库问答(RAG):
你: LangGraph 有什么优势? [节点:路由] 分析问题类型... [节点:检索] 正在检索与问题相关的知识... [节点:生成回答] 综合信息生成回答... 助手: 根据知识库内容,LangGraph 的主要优势在于它提供了清晰的工作流编排能力,支持有状态的多步骤任务处理,便于构建复杂的多智能体应用...(此处应结合你的PDF内容返回)- 验证点:智能体识别出这是知识库问题,触发了检索节点,并基于检索到的上下文生成回答。
测试工具调用:
你: 请计算一下 2 的 10 次方是多少。 [节点:路由] 分析问题类型... [节点:调用工具] 调用工具 'execute_python',输入:2**10 [工具调用] 正在执行 Python 代码: ```python 2**10[节点:生成回答] 综合信息生成回答... 助手: 代码执行成功。输出:1024。所以,2 的 10 次方是 1024。
* **验证点**:智能体识别出计算需求,正确调用了 `execute_python` 工具,并整合结果生成回答。测试综合场景:
你: 我想了解一下 LangGraph,然后帮我用 Python 写一个简单的 hello world。 [节点:路由] 分析问题类型...(这里我们的简单路由可能只会触发一个分支,实际更复杂的Agent会用LLM规划多个步骤)- 验证点:这个复杂请求暴露了我们当前简单路由的局限性。一个更强大的智能体应该能将其分解为“检索 LangGraph 信息”和“调用代码生成工具”两个子任务。这需要通过更复杂的“规划”节点或使用 LangChain 的
create_react_agent来实现。
- 验证点:这个复杂请求暴露了我们当前简单路由的局限性。一个更强大的智能体应该能将其分解为“检索 LangGraph 信息”和“调用代码生成工具”两个子任务。这需要通过更复杂的“规划”节点或使用 LangChain 的
7. 常见问题与排查思路
在构建和运行此类智能体时,你一定会遇到各种问题。下表列出了最常见的问题及其解决方法。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Ollama 连接错误 | Ollama 服务未启动;模型未下载;端口被占用。 | 1. 运行ollama list检查模型。2. 运行 ollama serve查看服务日志。3. 检查 OllamaLLM初始化时指定的模型名是否正确。 | 1. 确保 Ollama 服务运行 (ollama serve)。2. 拉取正确模型 ( ollama pull deepseek-r1:7b)。3. 确认代码中 model=参数与拉取的模型名一致。 |
ChromaDB 报错No such file or directory | 向量数据库持久化目录不存在或路径错误。 | 检查persist_directory路径。运行rag_setup.py的目录是否与主程序一致? | 使用绝对路径,或确保工作目录正确。先运行rag_setup.py生成数据库。 |
| 工具调用失败或无效 | 工具函数定义错误;工具未正确传递给 Agent;工具执行环境问题。 | 1. 在tools.py中单独测试工具函数。2. 检查 AGENT_TOOLS列表是否正确定义并导入。3. 查看工具函数的 @tool装饰器和参数。 | 1. 确保工具函数有明确的文档字符串(docstring),LangChain 依赖它。2. 检查工具导入路径。 3. 对于 execute_python,确保系统有 Python 环境且 subprocess 可用。 |
| 智能体不调用工具,总是直接回答 | 路由逻辑 (route_question) 太简单或错误;LLM 在 ReAct 模式下的提示词不佳。 | 1. 打印route_question函数的返回值。2. 检查触发工具调用的关键词是否匹配用户输入。 3. 如果使用 ReAct Agent,检查其提示词。 | 1. 强化路由逻辑,或引入一个 LLM 作为“规划器”来动态决定下一步。 2. 使用 LangChain 的 create_react_agent,它能更好地让 LLM 自主决定何时调用工具。 |
| 对话历史丢失或混乱 | GraphState中chat_history的定义或更新方式错误。 | 1. 检查state.py中Annotated[List[str], operator.add]的定义。2. 在 generate_answer节点中,检查是如何更新chat_history的。 | 1. 确保状态定义正确。 2. 在更新历史的节点,返回 {"chat_history": new_history},LangGraph 会自动执行追加操作。 |
| 程序运行缓慢 | 本地模型推理慢;检索器返回片段过多;网络工具调用慢。 | 1. 观察是哪个环节慢(模型响应、检索、工具)。 2. 检查检索的 k值是否过大。3. 检查模拟工具中的 time.sleep。 | 1. 考虑使用更小的模型或量化版本。 2. 调整检索参数 k(如从 5 降到 3)。3. 优化工具实现,或对耗时工具进行异步调用。 |
RuntimeError: ...图编译错误 | 节点名拼写错误;边连接了不存在的节点;条件路由返回值不在映射中。 | 仔细检查workflow.add_node和workflow.add_edge使用的字符串名称是否完全一致。 | 使用常量或枚举来定义节点名,避免拼写错误。仔细核对set_conditional_entry_point的映射字典。 |
8. 进阶优化与最佳实践
上面的示例是一个教学原型。要用于实际项目,你需要考虑以下优化点:
8.1 使用更强大的“规划器”替代简单路由
当前的route_question函数基于关键词,非常脆弱。应该用一个 LLM 作为规划器,分析用户意图并生成一个任务执行计划(Plan)。LangChain 的Plan-and-Execute模式或 LangGraph 的StateGraph本身都支持更复杂的规划逻辑。
8.2 实现真正的 ReAct 循环
我们的工具调用是“一次性”的。真正的 ReAct(Reasoning + Acting)模式要求智能体能够根据工具返回的结果,进行再次思考,决定是继续调用工具还是给出最终答案。这需要在图中添加一个从generate_answer或call_tool回到“路由/规划”节点的循环边,并设置合适的停止条件。
8.3 集成更真实的工具
- 网络搜索:注册 SerperDev、Google Search API 等服务,替换
search_web模拟函数。 - 知识库更新:增加一个工具或后台进程,定期或手动更新向量数据库。
- 安全沙箱:对于
execute_python,务必使用 Docker 容器等隔离环境,并限制资源(CPU、内存、运行时间、网络访问)。
8.4 引入 MCP 协议(前瞻性)
MCP 旨在让工具集成标准化。你可以探索将一些工具改造成 MCP Server,然后通过 LangChain 的 MCP 集成来调用。这虽然增加了初期复杂度,但长期来看有利于工具的管理和复用。关注langchain-mcp等社区包的发展。
8.5 生产环境部署注意事项
- 配置管理:将模型名称、API密钥、数据库路径等抽离到环境变量或配置文件中。
- 错误处理与重试:在图中的关键节点(如 LLM 调用、工具调用)添加完善的
try...except,并考虑实现指数退避重试。 - 日志与监控:使用
logging模块替代print,记录详细的运行日志,便于排查问题。监控智能体的耗时、工具调用成功率等指标。 - 状态持久化:当前的
chat_history在内存中,服务重启会丢失。需要将其持久化到数据库(如 Redis、SQLite)中,并以session_id区分不同对话。 - 异步优化:如果智能体需要调用多个外部 API,考虑使用
asyncio进行异步调用以提升吞吐量。
9. 总结:从项目实战中获得的真正洞察
通过这个从零搭建的 LangChain + LangGraph 智能体项目,我们得到的远不止一段可运行的代码。最关键的是理解了复杂 AI 应用的构建范式:
- LangChain 是基石:它提供了与大模型、数据、工具交互的标准化接口。学 LangChain,重点是学它的抽象(
LLM、PromptTemplate、Retriever、Tool),而不是死记硬背 API。 - LangGraph 是灵魂:当你需要处理多步骤、有状态、带循环或条件判断的任务时,就应该想到 LangGraph。它用“图”这个直观的概念,让你能把复杂的业务逻辑清晰地画出来、写出来。
- Agent 是模式,不是具体实现:“智能体”是一种设计模式,它由感知(输入)、规划(大脑/LangGraph)、执行(工具/LangChain)、记忆(状态)组成。我们这个项目就是这种模式的一个具体实现。
- RAG 和工具是能力的延伸:它们让智能体突破了模型本身的知识和功能限制。RAG 注入领域知识,工具赋予行动能力。它们的质量直接决定了智能体的上限。
- 从简单开始,迭代复杂:不要一开始就设计一个巨无霸智能体。像本文一样,从一个清晰的核心工作流(路由->检索/工具->回答)开始,验证可行性,然后逐步加入规划、多轮工具调用、多智能体协作等高级特性。
下一步你可以做什么?
- 深化 LangGraph:研究
StateGraph的add_conditional_edges来实现更动态的路由,尝试MessageGraph来处理纯消息流。 - 探索多智能体:用 LangGraph 定义两个具有不同角色(如“研究员”和“写作者”)的智能体,让它们协作完成一篇报告。
- 接入真实数据与工具:用公司的 Confluence、Notion 或代码仓库构建真正的 RAG 系统,接入 Jira、GitHub API 等真实工具。
- 关注 MCP 生态:随着 MCP 协议支持度的提高,尝试将一两个工具改造成 MCP Server,体验标准化工具集成的便利。
构建 AI 智能体的过程,就像教一个实习生:你先要告诉他工作流程(LangGraph),给他提供资料和工具(LangChain + RAG + Tools),然后通过不断的对话和反馈(记忆与迭代)让他变得更熟练。希望这个项目能成为你那位“实习生”的第一份清晰的工作手册。