
如果你是从传统 LLM 应用开发转过来的最近一定有一个很强烈的感受LangChain 的教程变了代码写法也变了过去把几个 Prompt、一个模型、一个 Python 函数串起来的 Chain 方式正在被一种叫 LangGraph 的图结构替代。真正容易让人困惑的点在于既然已经学了 LangChain为什么还要再学 LangGraphChain 和 Graph 到底有什么区别新手到底应该从哪套开始这些问题如果只看碎片化资料很容易越看越乱。这篇文章不会从概念堆砌开始而是从一条主线展开带你从零构建一个能调用工具、能做条件判断、能记住多轮对话上下文的 AI-Agent。在这个过程中我会把 LangChain 与 LangGraph 的关系讲清楚解释 MCP 协议在真实项目里解决什么问题也会拆解智能体记忆 Memory 的落地方式。读完这篇文章你能跑通一个最小可用的 Agent 项目并且知道再往生产环境走真正的坑在哪里。1. 为什么现在必须重新理解 LangChain 与 LangGraph1.1 从“管线时代”到“状态机时代”LangChain 早期最核心的用法是把 LLM 调用封装成PromptTemplate LLM OutputParser然后用Chain把它们按顺序串起来。这种模式非常适合固定流程的应用比如一个简单的 RAG 问答、一个文本摘要服务。但到了 Agent 时代应用需求发生了变化。Agent 不再只是“按顺序执行”它需要根据模型的输出决定下一步做什么是调用工具查天气还是直接给出最终答案是继续追问用户还是结束对话。这种“下一步执行什么”是运行时动态决定的固定写死的 Chain 处理不了这种分支和循环。所以 LangGraph 做的事情很直接把执行流程变成一张有向图图的节点是函数图的边是连接关系。通过add_conditional_edges条件边节点之间可以形成循环、分支、跳转这才符合 Agent 的真实运行逻辑。1.2 LangGraph 不是替代 LangChain而是替代 Chain 编排很多初学者问一个问题LangGraph 出来了是不是就不用学 LangChain 了这个理解不准确。LangGraph 并没有替换掉 LangChain 里的模型调用接口、工具封装、提示词模板这些基础能力它替换的是“编排层”。你在 LangGraph 的节点里依然会使用ChatOpenAI、bind_tools、tool装饰器这些 LangChain 经典组件。换句话说LangChain 提供的是“零件”模型、工具、解析器、向量库封装。LangGraph 提供的是“组装逻辑”把零件放在一个状态机里由状态和条件决定执行路径。Agent 则是这套组合想实现的高级形态让模型作为“大脑”在循环中决定调用哪些工具、什么时候给出结果。1.3 一个比较明确的判断如果你现在要新起一个项目尤其是涉及工具调用、多轮决策、MCP、复杂记忆的项目直接选 LangGraph不要再用旧 Chain 方式堆流水线。如果项目只是一个没有分支的固定流程Chain 也够用但从长期维护和扩展角度看LangGraph 的改造成本并不高。后面我会用一个非常小的人事流程示例来演示 LangGraph 的直觉模型你会发现它并没有想象中复杂。2. 核心概念扫盲State、Node、Edge、Agent、MCP、Memory在开始写代码之前先把几个高频概念一次性说清楚。理解这些概念后面看代码会轻松很多。2.1 State图中流转的状态对象State 是整个 LangGraph 运行期间保存数据的对象在 Python 里通常是一个TypedDict。每个节点函数接收当前 State然后返回一个字典这个字典里的字段会合并到 State 中。这是 LangGraph 最重要的设计之一节点函数不直接修改外部变量而是通过返回值更新状态。这样做的好处是流程可追踪、可回放也更容易排查问题。2.2 Node 与 Edge节点与边节点就是普通的 Python 函数。一个节点做一件事比如调用一次模型、执行一个工具、写一段摘要。边表示节点之间的执行顺序。普通边用add_edge表示“执行完 A 一定执行 B”。条件边用add_conditional_edges表示“根据函数返回值动态选择下一步去哪个节点”。Agent 的决策循环本质上就是条件边的应用。2.3 Agent具备决策循环的智能体可以这样理解 Agent它不是一个单独的 Python 类而是“模型 工具 循环”的组合。最经典的实现模式是 ReAct模型观察当前问题决定需要调用哪个工具工具返回结果模型继续推理直到模型认为可以给出最终答案这种循环在 LangGraph 里非常自然Agent 节点调用模型如果模型返回了工具调用请求就走工具节点执行完工具再回到 Agent 节点如果没有工具调用就走向结束节点。2.4 MCP连接 Agent 与外部系统的开放协议MCP 全称 Model Context Protocol是一个把工具、资源、提示词统一成标准接口的开放协议。它的目的是解决一个实际问题以前每接入一个外部系统就要写一套适配代码现在只要对方提供 MCP ServerAgent 就能通过 MCP Client 加载暴露出来的工具。可以把 MCP 理解为“USB-C 接口”。以前你要为不同设备准备不同充电线现在设备都提供统一接口只要线缆支持这个标准就能直接连上。2.5 Memory短期记忆与长期记忆Memory 在 Agent 系统里不是一个单一组件而是分层的概念短期记忆在一个会话内让 Agent 记住上下文。LangGraph 里通过 State 中的消息列表实现如果加上 Checkpointer还能把状态持久化下来实现跨多轮、跨请求的记忆。长期记忆跨会话记住用户偏好、历史信息。通常需要借助外部存储比如 Redis、MySQL、向量数据库。这两个概念非常容易被混在一起后面第 7 章会结合代码演示。下表可以快速对照这些概念概念解决什么问题典型实现State图运行期间的状态共享TypedDict、AnnotatedNode图中的一个执行单元普通 Python 函数Edge节点之间的执行顺序add_edge、add_conditional_edgesAgent模型决策 工具调用的循环ReAct 模式、条件边MCP工具与外部系统的标准化连接MCP Server / MCP ClientCheckpointer状态持久化与多轮会话记忆MemorySaver、SqliteSaver3. 环境准备与前置条件3.1 版本与环境要求LangGraph 目前支持 Python 3.9 以上版本推荐使用 Python 3.11 或 3.12。演示代码依赖 LangChain 和 LangGraph安装的时候最好在虚拟环境里进行避免污染系统 Python。3.2 创建虚拟环境python -m venv .venv source .venv/bin/activate # Windows 环境执行.venv\Scripts\activate pip install --upgrade pip3.3 安装依赖pip install langgraph langchain langchain-openai langchain-core如果后面要做 MCP 接入还需要额外安装 MCP 相关包pip install mcp langchain-mcp-adapters版本说明LangChain 生态迭代速度很快本文示例基于当前主流 API 写法。建议你安装后先运行代码验证如果发现 API 有变化以官方文档和代码提示为准。具体版本不建议盲追最新生产环境更要锁定版本。3.4 模型配置本文示例使用 OpenAI 兼容接口。你可以在环境变量中配置 API Keyexport OPENAI_API_KEYsk-xxxx如果你本地部署了 Ollama 或 DeepSeek 这类支持 OpenAI 接口的模型也可以通过base_url参数接入不绑定具体厂商。4. 从 Chain 到 GraphLangGraph 基础构建4.1 理解 State 的合并机制先解决一个很常见的问题在 LangGraph 的节点函数里怎么修改 State 的值答案不是原地修改而是返回一个字典。LangGraph 会自动把返回值按 key 合并到 State。例如当前 State 有topic字段节点函数返回{outline: xxx}运行后 State 会同时包含原字段和新字段。4.2 最小示例两个顺序节点下面这个例子非常小但包含了 LangGraph 的全部核心要素定义 State、添加节点、连接边、编译、调用。# file: simple_graph.py from typing import TypedDict from langgraph.graph import StateGraph, START, END class BlogState(TypedDict): topic: str outline: str content: str def write_outline(state: BlogState): # 根据 topic 生成大纲 return {outline: f{state[topic]} 的完整大纲} def write_content(state: BlogState): # 根据 outline 生成正文 return {content: f{state[outline]}\n\n这里是正文内容……} g StateGraph(BlogState) g.add_node(write_outline, write_outline) g.add_node(write_content, write_content) g.add_edge(START, write_outline) g.add_edge(write_outline, write_content) g.add_edge(write_content, END) app g.compile() result app.invoke({topic: LangGraph入门}) print(result)运行命令python simple_graph.py预期输出{topic: LangGraph入门, outline: LangGraph入门 的完整大纲, content: LangGraph入门 的完整大纲\n\n这里是正文内容……}这个示例说明了一个关键点LangGraph 的执行流程就是“节点函数依次被调用每个节点返回的字段不断累积到 State 中”。不需要中间变量满天飞所有数据都从 State 里拿。4.3 条件边让 Graph 拥有决策能力上面这个例子还是顺序执行和 Chain 相比优势不大。LangGraph 真正的能力来自条件边。基本用法是g.add_conditional_edges( agent, should_continue, { tools: tools, end: END, }, )should_continue是一个函数接收当前 State返回tools或endLangGraph 根据返回值选中对应的目标节点。这个机制就是后面 Agent 循环的核心。4.4 子图、并行分支与循环检测LangGraph 还支持子图Subgraph、并行分支和循环检测。子图适合把一段可复用的流程抽出来并行分支适合同时执行多个工具调用。实际项目里经常会出现“一个节点里并行调用多个工具”的需求新建子图的场景更多是为了复用基础流程。不过对初学者来说先专注把状态、节点、条件边掌握好已经能解决 80% 的 Agent 场景。子图和并行会在后面的进阶文章中单独展开。5. AI-Agent 实战让 LLM 使用工具做决策循环5.1 Agent 的最简结构一个最小可用的 Agent 需要四部分一个支持工具调用的模型。一组工具函数。一个 Agent 节点负责调用模型并判断下一步。一个工具执行节点负责执行模型指定的工具。模型通过bind_tools(tools)感知到工具的存在返回结构化工具调用指令。LangGraph 根据模型返回内容决定走工具节点还是直接结束。5.2 定义一个天气查询工具这里先定义一个简单的工具方便演示机制。from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市当前天气输入城市名返回天气描述。 # 真实项目这里应该调用天气 API 或数据库 return f{city}今天晴25摄氏度注意tool装饰器会把函数变成 LangChain Tool 对象函数名、参数、docstring 都会作为模型理解的元数据。5.3 完整 Agent 示例代码下面这个例子实现了一个 ReAct 风格的 Agent模型判断需要天气时调用get_weather工具返回结果后模型继续推理最后给出答案。# file: basic_agent.py from typing import TypedDict, Literal from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END tool def get_weather(city: str) - str: 查询指定城市当前天气输入城市名返回天气描述。 return f{city}今天晴25摄氏度 class AgentState(TypedDict): messages: list model ChatOpenAI(modelgpt-4o-mini, temperature0) tools [get_weather] model_with_tools model.bind_tools(tools) def call_agent(state: AgentState): response model_with_tools.invoke(state[messages]) return {messages: state[messages] [response]} def call_tool(state: AgentState): last_message state[messages][-1] new_messages [] for tool_call in last_message.tool_calls: tool_name tool_call[name] tool_args tool_call[args] if tool_name get_weather: result get_weather.invoke(tool_args) else: result f未知工具: {tool_name} new_messages.append({ role: tool, tool_call_id: tool_call[id], content: result, }) return {messages: state[messages] new_messages} def should_continue(state: AgentState) - Literal[tools, end]: last_message state[messages][-1] if last_message.tool_calls: return tools return end g StateGraph(AgentState) g.add_node(agent, call_agent) g.add_node(tools, call_tool) g.add_edge(START, agent) g.add_conditional_edges(agent, should_continue, { tools: tools, end: END, }) g.add_edge(tools, agent) app g.compile() result app.invoke({ messages: [{role: user, content: 帮我查一下杭州天气}] }) print(result[messages][-1].content)5.4 运行与验证运行前确保已经配置好模型 API Keypython basic_agent.py如果一切正常控制台会输出类似杭州今天晴25摄氏度。这个例子虽然简单但已经把 Agent 的完整循环跑通了。你可以改成别的工具比如查数据库、调用 HTTP API、读文件机制都一样。5.5 对新手最重要的三个认知第一bind_tools(tools)这一步决定了模型是否知道工具的存在。如果模型返回结果中没有tool_calls说明它没有识别到需要调用工具问题可能出在 prompt 或工具描述上。第二条件边是 Agent 循环的核心。should_continue判断最后一次模型消息是否包含工具调用包含就走工具节点不包含就结束。第三节点函数修改 State 的正确姿势是返回新字典、拼接新列表而不是直接原地修改旧列表。这样能避免并行执行时出现数据竞争。6. 接入 MCP让 Agent 连接外部系统6.1 MCP 解决的核心问题在真实项目中Agent 不会只调用一个天气函数。它可能需要查数据库、读文件、操作网页、调用内部 API。如果没有统一标准每接一个系统就要写一套工具适配代码维护成本很高。MCP 的思路是把外部能力封装成标准化的 MCP ServerAgent 通过 MCP Client 连接协议、发现工具、调用工具。对 Agent 开发者来说工具来源变了以前是自己写函数现在可以加载远端 MCP Server 暴露出来的工具。6.2 Agent Skill 与 MCP 的区别搜索热词里经常有人问“Agent Skill 和 MCP 有什么区别”。这里做个明确区分Skill 是 Agent 侧的“能力包”它包含提示词、调用模板、预设操作流程。它解决的是 Agent “会做什么、按什么方式做”的问题。MCP 是工具通信协议解决的是 Agent “怎么访问外部系统”的问题。它更偏底层通信标准化。可以理解为Skill 更像人的工作经验总结MCP 更像拿在手里的标准接口插头。两者不是替代关系实际项目中常常一起出现。6.3 MCP Server 配置示例MCP 客户端通常通过一个配置文件来定义如何启动 Server。下面是一个常见格式{ mcpServers: { sqlite: { command: uvx, args: [mcp-server-sqlite, --db-path, ./test.db] } } }这个配置的含义是客户端会启动command指定的命令通过标准输入输出与这个进程通信。mcpServers下面可以有多个 Server每个 Server 暴露自己的工具集。这种command args的启动方式本质上让 Agent 团队可以“即插即用”各种外部能力不需要为每个系统单独开发一套长连接服务。6.4 在 LangGraph Agent 中加载 MCP 工具下面的代码演示了如何用 MCP SDK 启动一个本地 Server并加载它暴露出来的工具。# file: load_mcp_tools_example.py import asyncio from langchain_mcp_adapters.tools import load_mcp_tools from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def load_tools(): server_params StdioServerParameters( commandpython, args[my_mcp_server.py], ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: return await load_mcp_tools(session) if __name__ __main__: tools asyncio.run(load_tools()) print(tools)这些返回的工具可以直接作为 LangChain Tool 使用绑定给模型后前面第 5 章的 Agent 循环不必改动太多工具来源从本地函数变成了 MCP Server。需要提醒的是langchain-mcp-adapters的具体导入路径和 API 可能会随版本变化。安装后建议先运行一次官方示例确认接口再集成到项目里。6.5 MCP 使用的安全边界使用 MCP 时要特别注意一点一个 MCP Server 可能暴露文件读取、命令执行、网络请求等高危工具。生产环境中必须只连接可信的 Server并且要做权限控制。Agent 在无人监督时自动执行工具带来的风险比代码调用大得多因为 Agent 可能基于异常输入触发危险操作。建议在工具层增加白名单、参数校验和审计日志。7. 智能体记忆 Memory让 Agent 记住上下文7.1 短期记忆Memory Channel 与 CheckpointerLangGraph 里经常会看到Memory Channel这个说法。它并不神秘本质就是 State 中用于保存消息列表的通道节点之间通过这个通道传递对话历史。没有这个 ChannelAgent 每轮之间就是孤立的。但仅靠 State 还不够默认情况下一次invoke结束状态就丢了。要实现“下一次调用还能记住上一次对话”需要 Checkpointer。它负责把每一步状态持久化可以用内存版也可以用 SQLite、Redis 等外部存储。7.2 用 MemorySaver 实现多轮记忆在之前的basic_agent.py基础上只需要改编译部分# 在 basic_agent.py 的基础上将 app 编译部分改为 from langgraph.checkpoint.memory import MemorySaver memory MemorySaver() app g.compile(checkpointermemory) config {configurable: {thread_id: user-123}} first app.invoke( {messages: [{role: user, content: 我叫张三请记住}]}, config, ) second app.invoke( {messages: [{role: user, content: 我叫什么名字}]}, config, ) print(second[messages][-1].content)这里关键点是thread_id。相同thread_id的多次调用共享同一个状态相当于同一个会话不同thread_id之间互相隔离。如果运行时发现模型不记得上一轮内容优先排查是不是没有传config或者thread_id是否一致。7.3 长期记忆外部存储短期记忆解决的是会话内上下文。长期记忆需要把信息放到外部系统比如 Redis、MySQL、向量数据库。典型做法是用户第一次使用时Agent 提取关键信息偏好、称呼、禁止事项写入存储。下次会话开始时先根据user_id加载用户画像放进 State 的初始字段。Agent 生成回答时可以参考这些长期信息。# 伪代码示意 def load_user_profile(user_id: str) - str: # 从 Redis / MySQL / 向量库读取用户画像 return 用户偏好喜欢简洁回答对价格敏感 def save_user_profile(user_id: str, summary: str) - None: # 写入外部存储 pass state { messages: [], user_profile: load_user_profile(user-123), } # 调用 Agent 时有意识地把 user_profile 拼进 system prompt长期记忆的难点不在于写代码而在于设计“什么时候写入、什么时候更新、什么时候删除”。不是所有对话内容都值得长期保存盲目存储反而会导致隐私合规风险。7.4 记忆设计的三条建议第一短期记忆使用 LangGraph 的 Checkpointer 就够了不要自己造轮子。第二长期记忆要从业务需求出发明确哪些信息需要跨会话保留。第三长期保存的信息必须提供用户查看和删除的入口这是工程底线不只是功能问题。8. 常见问题与排查思路LangGraph 项目最常见的错误集中在依赖版本、工具调用、状态隔离、MCP 连接这几类。问题现象可能原因排查方式解决方案安装时依赖冲突langgraph 与 langchain 版本不匹配执行pip check查看依赖树使用虚拟环境统一锁版本模型完全不调用工具没有执行 bind_tools或工具描述不清打印模型原始返回内容绑定工具改进工具描述报错 last_message.tool_calls 不存在模型返回普通文本没有结构化工具调用检查模型是否支持 function calling更换支持工具调用的模型多轮对话中模型不记得上文每次 invoke 没有传 thread_id检查 config 是否传递使用相同 thread_idMCP 工具加载为空Server 启动失败或命令路径错误先单独启动 Server 看日志检查 command、args、工作目录并发调用时状态互相污染多个请求共用了同一个 MemorySaver 实例检查是否复用了变量按会话隔离图实例或合理使用线程隔离本地模型工具返回格式不对模型厂商实现不兼容 tool_calls 标准查看工具返回消息格式增加适配层或换模型这里特别强调第一条LangChain 生态版本更新很快很多“网上看着能跑本地跑不起来”的问题根因都是依赖版本不一致。建议创建项目时使用虚拟环境写一个requirements.txt把关键包版本固定下来。9. 最佳实践与工程建议9.1 版本锁定LangChain、LangGraph、LangChain OpenAI 适配器、MCP SDK这些包的 API 都可能小版本升级后发生变化。生产环境不要依赖“最新版”要使用锁文件或固定版本号。pip freeze requirements.txt9.2 State 设计要小State 里的字段越多LangGraph 每一步持久化的成本越高也越容易出错。不要把大段文件内容、完整原始响应都塞进 State。能存 ID 就存 ID能存摘要就存摘要需要细节时再通过工具获取。9.3 超时与重试Agent 是多步调用不是一次 HTTP 请求。任何一个工具调用都可能慢、失败、卡住。建议在工具节点加上超时控制在关键节点加上重试逻辑。9.4 人类介入生产级别的 Agent 不应完全无人值守。LangGraph 支持中断和恢复机制可以在执行到关键步骤前“暂停”等人工确认后再继续。涉及支付、删除、外发消息等操作时这个能力几乎必备。9.5 可观测性Agent 系统比普通后端系统更难排查因为最终结果由模型的多步决策决定。建议把每一轮的输入输出、工具调用参数、返回结果、耗时全部记录下来。调试时回放完整决策路径比只看最终输出有用的多。9.6 安全边界涉及外部工具调用时先问三个问题这个工具会不会修改数据会不会发起对外请求参数是不是用户可控如果都是“否”可以交给无人值守流程只要有一个“是”都要加权限和人工确认。10. 总结与后续学习方向这篇文章从 Chain 到 Graph 的演进讲起通过一个最小示例让你理解了 State、Node、Edge 和条件边然后实现了一个真正的 ReAct Agent最后讲解了 MCP 和记忆 Memory 的落地方式。如果你能把第 5 章的示例代码完整跑通再自己换一个其他类型工具基本上就算入门了 LLM Agent 开发。接下来可以按这个顺序深入先自己扩展工具集比如加一个 HTTP 请求工具让 Agent 能查真实 API然后给 Agent 接入 Checkpointer实现多轮记忆之后再了解并行分支和子图最后再研究生产部署包括权限、监控、人工介入和模型降级。LangGraph 的优势不在于一步到位解决所有问题而在于它把复杂的 Agent 流程变成了一张清晰可见的图。你在图上加节点、加边、加状态逻辑始终是可控的。这也是我建议新项目直接选择 LangGraph 的原因。建议把示例代码保存下来之后写自己的 Agent 时直接在上面修改能少走很多弯路。