2026 年再看 Agent 开发,LangChain 和 LangGraph 已经不只是开源社区里的热门关键词,而是大量企业级智能体项目的实际底座。很多人最早是从 RAG 问答开始接触 LangChain 的,跑通线性 Chain 之后,下一步就会遇到一个更麻烦的问题:真实业务里的流程不可能永远是一路向下。分支判断、多轮工具调用、中间需要人工审批、失败之后要重试,这些用原来的 Chain 表达起来非常别扭。LangGraph 就是在这个背景下出现的。它没有取代 LangChain,而是在 LangChain 的组件体系之上,增加了一套基于状态图的编排能力。这里说的图,不是图数据库里的图,而是由节点、边和状态组成的流程状态机。下面会从零开始,按一条可复现的路线完成四件事:搭好 LangChain 与 LangGraph 开发环境、理解状态图的核心概念、手写一个带工具调用和记忆的 Agent、补齐企业级应用需要的稳定性、可观测性和排错方法。这篇文章的目录结构,也可以直接当作一条 Agent 开发学习主线来用。
1. 先分清边界:LangChain 是组件库,LangGraph 是流程引擎,Agent 是决策主体
把三者混在一起,是入门阶段最常见的错误。不少代码把提示词、模型封装、工具定义和流程判断全部放在一个文件里,跑通之后,一旦要加分支或记忆,代码就会迅速失控。先搞清楚三个概念的正确边界,后面每一步都会更顺。
1.1 LangChain:围绕大模型组件的一整套组件体系
LangChain 不是一个大模型,也不是一个单一的库。它把模型调用、提示词模板、输出解析、文档加载、向量检索、记忆管理等能力,封装成可以自由组合的组件。它的核心价值是统一了这些组件的接口,让开发者可以用相似的方式组合出 RAG、问答、摘要、分类等应用。
在 LangChain 体系里,最常用的组件包括ChatOpenAI这类模型封装、ChatPromptTemplate这类提示词工具、StrOutputParser这类输出解析器,以及DocumentLoader、VectorStore、Retriever等检索组件。把这些组件用 Chain 串起来,可以实现线性流程:组装提示词,调用模型,解析输出。
问题在于,Chain 默认是一条直线。它适合“先做 A,再做 B,最后做 C”的场景。真实 Agent 需要判断要不要调工具、调完工具要不要继续问模型、出错要不要重试,这些在 Chain 里只能靠外部代码硬编码,流程稍微复杂就难以维护。
1.2 LangGraph:把流程从线性 Chain 升级为状态图
LangGraph 是 LangChain 团队推出的另一套库,定位是构建有状态、可编排的 Agent 应用。它的核心不是组件复用,而是流程控制。你用StateGraph定义一张图,图上有节点、边和条件边,运行时由状态在节点之间流转。
和 Chain 相比,LangGraph 有三个关键差异。第一,支持循环。工具调用后可以回到模型节点,这正是 Agent 循环的本质。第二,状态集中管理。所有节点共享同一个 State,不依赖函数层层传参。第三,支持检查点机制。它可以持久化每一步执行结果,中断之后还能从断点恢复。
| 维度 | LangChain | LangGraph |
|---|---|---|
| 核心抽象 | Chain、Runnable | StateGraph、节点、边 |
| 典型场景 | RAG 流水线、线性调用 | 多轮工具调用、分支、人工审批 |
| 状态管理 | 调用链内部传参 | 集中式 State |
| 循环支持 | 不擅长 | 原生支持 |
| 持久化 | 需要自行处理 | checkpointer 内置 |
在实际项目里,LangChain 的组件仍然是基础,LangGraph 负责把这些组件放进可控流程。二者是配合关系,不是替代关系。
1.3 Agent:由模型决策,由图执行,由工具落地
Agent 在这套体系里的定位可以用一句话概括:由模型决策,由图执行,由工具落地。模型根据当前状态判断下一步做什么,图负责把决策转换为节点流转,工具负责真正访问外部系统。没有图的 Agent 是一个只会反复调 API 的脚本,没有模型的图是一套没有灵魂的流程模板。
不少教程里还会出现“技能”这个词。可以这样区分:工具是最小的能力单元,比如查订单、发消息;技能是把多个工具和步骤组合成一种解决问题的套路;Agent 则是根据当前状态决定调用哪个技能的执行主体。在 LangGraph 里,技能通常体现为一段可复用的子图,或一组预置的工具集合。
1.4 LangGraph 与流程引擎的边界
搜索站点上经常能看到“LangGraph 能不能代替 Flowable”这类讨论。结论不需要绝对化。Flowable 这类流程引擎用 BPMN 建模,流程定义清晰、执行确定性强,适合审批流、人工任务流这种需要强合规的场景。LangGraph 的优势在 AI 决策驱动的流程:下一步走哪里由模型判断,天然支持循环和动态分支。
| 维度 | 传统流程引擎(BPMN/Flowable) | LangGraph |
|---|---|---|
| 流程定义 | XML、模型设计器 | Python 代码 |
| 分支依据 | 规则和人预先定义 | 模型在运行时动态判断 |
| 执行确定性 | 高,适合审计 | 有随机性,需要兜底 |
| 适用场景 | 审批流、人工任务流 | AI 决策、多轮工具调用 |
| 学习成本 | 图形化配置 | 代码和图论概念 |
固定流程上,传统流程引擎仍然有优势;动态决策流程里,LangGraph 更合适。两者也可以共存,由流程引擎负责主线,LangGraph 负责 AI 子流程。
2. 环境准备:版本、虚拟环境与模型接入要一次到位
教程里最常见的高频踩坑是环境问题。很多人照着旧文章安装,结果 langchain 和 langgraph 版本不匹配,导入时报错,然后在错误的版本上反复调试。环境这一步值得一次做对。
2.1 版本选择:不要盲追最新版,也不要固定在旧版
LangChain、langchain-openai、langgraph 是三个独立发布的包,版本迭代非常快。文章不写死具体版本号,因为在你看到这篇文章时版本可能又变了。建议安装时直接安装当前最新稳定版,并记录到requirements.txt。Python 建议使用 3.10 及以上版本,安装前先确认基础环境。
python --version pip --version如果机器上有多个 Python 版本,python之后要确认指向的是同一个解释器,否则可能出现“包装上了但 import 不到”的问题。
2.2 用虚拟环境隔离依赖
为什么要建虚拟环境:第一,避免多个项目互相污染,尤其是一个项目要 LangChain 4.x、另一个还要 LangChain 3.x 时;第二,团队成员可以用同一份依赖清单还原一致环境;第三,升级依赖出错时,重建虚拟环境就能快速回滚。
mkdir langgraph-agent && cd langgraph-agent python -m venv .venv source .venv/bin/activate # Windows PowerShell 用户执行: .venv\Scripts\Activate.ps1 pip install --upgrade pip pip install --upgrade langchain langchain-openai langgraph如果你使用 uv 这类工具,安装速度会更快:
uv init langgraph-agent uv add langchain langchain-openai langgraph安装后验证版本,并保存依赖清单:
pip show langgraph | grep -E "Name|Version" python -c "import langgraph; print(langgraph.__version__)" pip freeze > requirements.txt2.3 模型接入:云端 API 和本地模型的两种跑法
模型服务是 Agent 的唯一外部依赖,先把它跑通,后面所有调试都会轻松。接入方式有两种。
第一种,使用云端模型 API。把密钥写入环境变量,而不是写进代码:
export OPENAI_API_KEY="你的密钥" export OPENAI_BASE_URL="https://api.openai.com/v1"如果你的项目使用的是兼容 OpenAI 协议的其他模型服务,把base_url换成服务商提供的地址即可,代码不需要改。
第二种,使用本地模型。本机装好 Ollama,拉取一个中小模型:
ollama pull qwen2.5:7b然后在代码里把base_url指向本地地址,api_key填一个占位符即可。
| 方式 | 优点 | 缺点 | 适用阶段 |
|---|---|---|---|
| 云端模型 API | 效果稳定、接入简单 | 需要申请密钥、按量计费 | 开发、测试、生产 |
| 本地模型 | 无外部网络依赖、成本可控 | 依赖本机算力、效果有差距 | 学习、离线场景 |
注意:生产环境的前端不要直接持有模型密钥。常见做法是企业内部网关对模型服务做代理,前端只拿短期凭证或统一走服务端转发。
2.4 环境自检脚本:先确认模型能通再写代码
不要等 Agent 写完再排查模型连通性。先跑一个最小脚本:
from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="gpt-4o-mini", temperature=0, ) resp = llm.invoke("请直接回复:模型连通正常") print(resp.content)如果输出是“模型连通正常”,说明模型接入正常。如果出现 401、404 之类错误,先检查密钥、模型名和base_url,不要急着改图逻辑。
本地调试时还会遇到两种启动方式的区分。uvicorn app:app启动的是你自己写的 FastAPI 服务,面向外部 HTTP 接口;langgraph dev启动的是 LangGraph 官方本地调试服务,更关注图本身,可以在浏览器里查看每个节点的状态变化。前者是业务服务,后者是调试工具,用途不同,不需要二选一。
3. 核心概念:状态、节点、边和条件分支就是全部语法
LangGraph 的语法不算复杂,核心只有四个词:State、Node、Edge、Conditional Edge。把这几个概念理解透,后面写任何 Agent 都是一样的套路。
3.1 用一条生产线理解状态图
可以把 LangGraph 想象成一条生产线。工件在传送带上移动,每个工位处理工件并可能修改它,传送带决定工件下一个去哪。这里的工件就是 State,工位就是 Node,传送带就是 Edge。
与真实生产线不同的是,状态图允许循环和条件分支,工件可以被送回上一个工位重新加工。Agent 的典型循环正是这样:模型决定要不要调用工具,调用就进入工具节点,工具返回后又回到模型节点,直到模型认为任务完成并输出最终答复。
3.2 State:所有节点共享的数据契约
State 是一份集中管理的可变数据,所有节点都能读取和修改。在代码里它通常定义成一个TypedDict。一个容易踩坑的地方是messages字段:每次模型或工具返回消息时,不能简单覆盖,否则之前的历史全部丢失。LangGraph 提供了add_messages这个 reducer,它决定新消息如何合并进旧状态。
from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] user_id: str pending_order_id: str这段定义的语义是:messages按追加方式合并,每次节点返回的新消息会叠加到历史列表尾部;user_id和pending_order_id没有 reducer,默认是覆盖语义,后写入的值会替换旧值。
如果不加add_messages,messages字段默认覆盖,每轮节点返回的新消息会把旧历史冲掉,Agent 会变成一个失忆机器人。这是新手最常见的错误之一。
3.3 节点和边:最小执行单元与路径
节点就是一个普通 Python 函数。它接收当前 State,返回一个字典,字典里的内容会按 State 定义的合并规则更新状态。
def agent_node(state): response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]}边就是节点之间的连接关系。START是图的入口,END是图的出口。所有节点必须从START出发,最终能到达END,否则图会永远停在某个中间节点。
3.4 条件边:Agent 自主决策的关键
条件边让图在运行时根据状态决定下一步去哪个节点。这是 Agent 和普通流水线最大的差别:走哪条路不是预先写死的,而是由模型在运行时决定的。
def route_after_agent(state): last_message = state["messages"][-1] if getattr(last_message, "tool_calls", None): return "tools" return END这段逻辑是:模型最后一条消息里带有tool_calls,说明它想调用工具,下一步进入tools节点;否则说明模型想直接回复用户,走向END。
工具调用完成后,必须有一条边回到模型节点。缺少这条边,Agent 只能执行一轮工具就无法继续组织语言回复。
3.5 一张图对应一份代码骨架
把上面的概念组合起来,就是下面这份代码骨架:
from langgraph.graph import StateGraph, START, END builder = StateGraph(AgentState) builder.add_node("agent", agent_node) builder.add_node("tools", tool_node) builder.add_edge(START, "agent") builder.add_conditional_edges("agent", route_after_agent) builder.add_edge("tools", "agent") app = builder.compile()每一行的作用都很明确:创建图、注册节点、指定入口、设置条件分支、让工具回流到模型。最终调用compile()得到一个可执行对象app,后续所有invoke和stream都通过它完成。
旧项目里你可能见过AgentExecutor。它是前一代实现,现在新项目推荐直接用 LangGraph,或者用 prebuilt 里的create_react_agent快速生成标准 ReAct Agent。学习时建议先手写一遍图,理解之后再使用预置实现。
4. 手写第一个可运行 Agent:查订单小助手
下面用一个订单查询 Agent 作为最小案例。场景很贴近生产:用户输入一句话,Agent 判断需要查订单时调用工具,拿到结果后组织语言回复。
4.1 先规划项目目录,避免代码全部堆在一个文件
langgraph-agent/ ├── requirements.txt ├── .env └── agent_demo/ ├── __init__.py ├── state.py ├── tools.py ├── nodes.py └── graph.py目录虽小,职责已经分开:state.py管数据契约,tools.py管工具注册,nodes.py管节点实现,graph.py管图组装。真实项目会在这一层之上再加config、services、tests等目录。
4.2 定义状态:消息如何累积
# agent_demo/state.py from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] order_id: str这里额外加了一个order_id字段,后面做人工确认时可以在节点之间传递待处理订单。
4.3 定义工具:能力最小单元
# agent_demo/tools.py from langchain_core.tools import tool @tool def get_order_status(order_id: str) -> str: """根据订单号查询订单当前状态,订单号形如 20260101001。""" # 示例实现,实际项目应查询订单服务或数据库 return f"订单 {order_id} 当前状态:已发货,预计 3 天内送达。" tools = [get_order_status]工具函数的 docstring 非常重要。模型靠它判断何时调用这个工具,以及应该传什么参数。工具名和参数名要语义化,描述要写清楚边界,比如订单号格式。工具描述不准确,模型就会在错误场景反复调用或完全不用它。
4.4 编写节点并绑定模型
# agent_demo/nodes.py from langchain_openai import ChatOpenAI from langgraph.prebuilt import ToolNode from agent_demo.tools import tools llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) llm_with_tools = llm.bind_tools(tools) def agent_node(state): response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} tool_node = ToolNode(tools)bind_tools(tools)的作用是把工具的结构化定义传给模型,让模型知道有哪些工具可用,以及每个工具的入参格式。ToolNode(tools)的作用是根据模型返回的tool_calls自动执行对应工具,不需要手写一遍工具分发逻辑。
这里要强调一个细节:tools列表被两处引用,一处绑定给模型,一处交给ToolNode。所以新增工具时只需要改一个列表,两处都会生效。
4.5 组装图:把节点串成可控流程
# agent_demo/graph.py from langgraph.graph import StateGraph, START, END from agent_demo.state import AgentState from agent_demo.nodes import agent_node, tool_node def route_after_agent(state): last_message = state["messages"][-1] if getattr(last_message, "tool_calls", None): return "tools" return END def build_graph(): builder = StateGraph(AgentState) builder.add_node("agent", agent_node) builder.add_node("tools", tool_node) builder.add_edge(START, "agent") builder.add_conditional_edges("agent", route_after_agent) builder.add_edge("tools", "agent") return builder.compile() app = build_graph()这个 Agent 的执行路径是:用户输入进入agent节点,模型决定要查订单就跳到tools,工具返回结果后再回到agent,模型根据工具结果给出最终回答,然后走到END。整个过程允许循环,但不会无限循环,因为模型只要不再返回tool_calls,条件边就会把它带向END。
4.6 运行验证与给 Agent 增加新技能
# app.py from agent_demo.graph import build_graph app = build_graph() def main(): while True: user_input = input("用户:") if user_input.strip().lower() in ("exit", "quit"): break result = app.invoke({"messages": [("user", user_input)]}) print("Agent:", result["messages"][-1].content) if __name__ == "__main__": main()invoke的入参直接就是 State 初始内容。输入消息写成("user", 文本)这种形式,add_messages会把它转换成正确消息对象。预期输出如下:
用户:你好 Agent:你好,有什么可以帮你? 用户:帮我查一下订单 20260101001 Agent:订单 20260101001 当前状态:已发货,预计 3 天内送达。给 Agent 增加技能也很直观。比如再增加一个天气查询工具:
@tool def get_weather(city: str) -> str: """查询指定城市今天的天气。""" return f"{city} 今天晴,气温 5~15 摄氏度。" tools = [get_order_status, get_weather]因为tools列表同时被bind_tools和ToolNode引用,加完工具后模型自然就能发现它。但要注意,工具数量变多后模型的选择难度也会上升,建议按业务域分组,避免把所有工具塞进同一个列表。
5. 让 Agent 可用起来:多轮记忆、人工确认和流式输出
前面案例每次运行完状态就丢了。真实产品需要多轮记忆、关键操作审批和流式反馈,这一节把这些能力补齐。
5.1 用 checkpointer 保存会话状态
默认情况下,每次invoke结束,State 就被丢弃。要让 Agent 记住上一轮聊了什么,需要给图配置 checkpointer,也就是检查点存储器。
from langgraph.checkpoint.memory import MemorySaver app = build_graph().compile(checkpointer=MemorySaver()) config = {"configurable": {"thread_id": "demo-user-001"}}之后每次调用都传入这个config:
app.invoke({"messages": [("user", "我上一轮查的订单是多少?")]}, config=config)MemorySaver把状态存在内存里,适合本地开发和测试。生产环境建议使用 PostgreSQL、Redis 等持久化 checkpointer,这样服务重启后会话还能恢复。
5.2 thread_id:多用户多会话的数据隔离
thread_id是会话隔离的关键。它决定了哪些消息属于同一条对话线,谁也不能读取别人的状态。
生产环境的thread_id建议按user_id + session_id组合生成,并在服务端校验当前登录用户和 thread_id 的归属关系。如果直接把用户可随意篡改的字符串当作 thread_id,很容易出现越权读取他人会话的风险。
5.3 人工确认:关键操作不能完全交给模型
企业级 Agent 里,发邮件、转账、删除数据、对外发送消息这类操作不能只靠模型判断,需要人工确认。LangGraph 的interrupt机制让图执行到关键节点时暂停,等待外部审批结果再继续。
以下示例基于 langgraph 0.3 之后的interruptAPI,具体导入路径和参数以你安装版本的官方文档为准:
from langgraph.types import interrupt def human_review_node(state): last_message = state["messages"][-1] decision = interrupt({ "question": "是否允许执行该操作?", "tool_calls": getattr(last_message, "tool_calls", []), }) return {"approved": bool(decision)}把human_review_node插到 Agent 节点和工具节点之间后,图执行到这里会暂停并返回一个中断状态。外部系统做审批后,用下面方式恢复:
from langgraph.types import Command app.invoke(Command(resume=True), config=config)resume的值会成为interrupt的返回值,也就会成为decision。生产落地时,可以把审批动作接到工单系统、IM 通知或管理后台,人工点击通过后再调用resume。
5.4 流式输出:先让用户体验变好
长任务如果一直让用户等待最终结果,体验会很差。LangGraph 支持按节点流式输出:
for chunk in app.stream( {"messages": [("user", "查一下订单 20260101001")]}, config=config, ): print(chunk)stream默认输出节点粒度的结果,可以看到agent、tools谁先执行、谁后执行。如果希望做成打字机效果,需要进一步使用astream_events或模型原生的流式输出接口。建议先把节点级流式跑通,再深入 token 级。
6. 面向生产:错误处理、超时、日志和成本控制
演示能跑和上线能扛是两回事。下面按稳定性优先级整理一份生产化清单。
6.1 节点级异常处理
节点内部抛出异常会导致整个图中断。生产环境至少要保证:单次模型超时不会让整个请求崩掉,工具内部报错能回到 Agent 并转成可读提示。
def safe_agent_node(state): try: response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} except Exception as exc: logger.error("agent node failed: %s", exc, exc_info=True) return {"messages": [AIMessage(content=f"模型服务暂时不可用,请稍后重试。错误原因:{exc}")]}要点是:既要把异常转成可读消息让流程继续,又要把完整异常记录到日志,不能静默吞掉。工具内部同样要做异常防护:
@tool def get_order_status(order_id: str) -> str: try: return query_order_service(order_id) except Exception as exc: return f"查询订单失败:{exc}"这样工具执行失败时,返回结果会作为消息回到模型,由模型组织成用户能看懂的回复。
6.2 超时、重试和递归上限
模型服务的网络抖动无法完全避免。模型封装层可以配置超时和重试:
llm = ChatOpenAI( model="gpt-4o-mini", temperature=0, timeout=30, max_retries=2, )图本身也要设置递归上限,防止 Agent 陷入死循环:
config = { "recursion_limit": 25, "configurable": {"thread_id": "order-demo-001"}, }| 参数 | 常见取值 | 作用 | 调大 | 调小 |
|---|---|---|---|---|
| temperature | 事实类任务 0,创意类 0.7+ | 控制采样的随机性 | 更发散,可能编造 | 更稳定,可能缺少变化 |
| timeout | 30 到 60 秒 | 模型响应超时 | 减少误判超时 | 更快失败 |
| max_retries | 1 到 2 次 | 网络抖动时重试 | 更稳 | 失败更快 |
| recursion_limit | 默认 25 左右 | 图执行的最大步数 | 支持更复杂任务 | 死循环能更快暴露 |
recursion_limit调得过小,复杂任务会被截断;调得过大,死循环时成本和时延都会失控。推荐做法是在路由函数里打印每次分支返回值,确认逻辑无误后再放开上限。
6.3 日志与链路追踪
生产排错离不开日志。不要用一堆print,至少配置标准 logging:
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s", ) logger = logging.getLogger("agent") logger.info("user=%s, thread=%s, message_count=%d", user_id, thread_id, len(messages))每个请求最好能关联到:用户、thread_id、模型名、token 消耗、每个节点耗时、每次工具调用的入参和出参。如果团队已经接入 LangSmith、Langfuse 或自建链路追踪系统,开发期就要接好,不要等上线出问题再补。
6.4 学习环境与生产环境的配置差异
| 维度 | 本地学习环境 | 生产环境 |
|---|---|---|
| 模型 | 本地 Ollama 或小模型 | 正式大模型 API、内部网关 |
| 状态存储 | MemorySaver | PostgreSQL、Redis checkpointer |
| 密钥 | 环境变量 | 密钥管理平台,服务端持有 |
| 日志 | print 或简单 logging | 结构化日志、指标、追踪 |
| 错误处理 | 不崩溃即可 | 重试、降级、告警 |
| 人工确认 | 手动跳过 | 审批工单、IM 通知 |
| 成本 | 不敏感 | 模型选型、token 估算、限流 |
7. 常见报错与排查:从现象定位到根因
7.1 看报错的正确姿势
报错信息很长时,先看最后一段堆栈,不要从第一行开始读。比如执行时看到agent execution terminated due to error.,这行只告诉你运行终止,真正原因在它之前的堆栈里,通常指向某个工具函数内部抛出的异常。
排查顺序建议固定下来:先确认输入是否正确,再确认文件路径和命名,接着检查依赖版本、配置是否生效、权限和网络,然后看异常日志,最后再看是不是框架版本限制。
7.2 安装和依赖相关
现象:pip install时出现依赖解析错误,或代码里导入langgraph报ModuleNotFoundError。
原因:项目里同时存在新老版 LangChain API 包,或者装到了错误的 Python 环境。
处理:新建干净虚拟环境,统一升级到最新稳定版后重新安装;确认pip和python指向同一个解释器;把依赖冻结到requirements.txt。
7.3 模型接入相关
现象:调用时报401 AuthenticationError、404 ModelNotFoundError。
原因:密钥错误、模型名在当前服务商不可用、base_url配置错误。
检查:打印环境变量确认OPENAI_API_KEY和OPENAI_BASE_URL已生效;用 curl 直接调用一次模型接口;确认模型名和账号权限匹配。换成正确的密钥和模型名后重试。
7.4 工具调用相关
现象:模型返回内容里出现了工具调用意图,但工具没有真正执行;或者整个流程直接报错。
原因:路由函数没有正确判断tool_calls;ToolNode里注册的工具名和模型绑定的工具名不一致;工具内部抛出了未捕获异常。
检查:在路由函数里打印最后一条消息的tool_calls;在工具函数里加打印或用 try/except 包住业务调用。先修正路由返回值,再给工具加异常兜底。
7.5 图运行相关
现象:图一直跑不停;多轮对话后历史丢失;每次更换进程后状态不在。
原因:条件边循环路径写错;messages字段没有使用add_messagesreducer;没有配置 checkpointer;thread_id不一致。
处理:修正路由分支;State 里给messages加Annotated[list, add_messages];配置持久化 checkpointer;统一thread_id生成规则。
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 安装时依赖冲突 | 新旧 LangChain API 包混用 | pip list对比版本 | 新虚拟环境统一升级再安装 |
| invoke 报 401/404 | 密钥、模型名、base_url 错误 | 打印环境变量、curl 调用 | 修正模型配置 |
| 模型要调工具但工具没执行 | 路由判断或工具名不一致 | 打印tool_calls | 修正条件边和工具注册 |
| 出现 agent execution terminated due to error | 工具内部异常未捕获 | 查看完整堆栈定位工具代码 | 工具内加异常防护 |
| 多轮对话没记忆 | 缺 checkpointer 或 thread_id 不一致 | 打印消息长度 | 配置 checkpointer,统一 thread_id |
| 图运行不结束 | 条件边死循环 | 在路由函数打印返回值 | 加 recursion_limit 和轮数上限 |
| 历史消息被覆盖 | messages 没用 add_messages | 检查 State 定义 | 用Annotated[list, add_messages] |
8. 企业级 Agent 落地清单与后续学习路线
8.1 从课程到实战的三种练手项目
第一种,资料问答 RAG 助手。做文档加载、向量化、检索,把检索结果封装成工具,Agent 根据问题决定是否检索,回答时带资料引用。
第二种,客服工单 Agent。沿用订单助手的思路,加数据库查询、工单创建、状态变更等工具,再结合多轮记忆和人工审批,已经能覆盖真实客服系统的大部分需求。
第三种,数据分析 Agent。提供查库工具、执行代码工具,Agent 根据用户的自然语言问题拆解步骤,查询数据、计算结果、输出结论。
这三个项目依次覆盖了 RAG、工具调用、记忆、人工确认和生产化,正好把前面所有知识点串起来。
8.2 发布前检查清单
- 依赖版本是否锁定在 requirements.txt 或 lock 文件。
- 模型密钥是否走环境变量或密钥管理平台,代码仓库里没有明文密钥。
- 每次请求是否都有统一的 thread_id,并在服务端校验归属权限。
- 每个工具是否有超时、异常兜底,返回结果是否适合转成用户可读内容。
- 图是否配置了 recursion_limit,避免单次请求成本失控。
- 是否接入结构化日志、链路追踪,能回溯到用户、thread_id、模型名、token 消耗。
- 写操作、删除操作、对外发送操作是否有人工确认。
- 是否做了模型选型和 token 估算,防止长对话和高频调用导致成本超预算。
- 是否准备了一组评测用例,每次升级模型或改流程后能回归对比。
- 是否考虑了多租户数据隔离,一个用户不能读到另一个用户的会话和工具数据。
8.3 后续学习路线:RAG、多 Agent 与评测
第一阶段,LangChain 基础。掌握模型封装、提示词模板、输出解析,能写线性调用。
第二阶段,RAG 应用。掌握文档加载、分块、向量化、检索、引用来源,能跑通一个带知识库的问答系统。
第三阶段,LangGraph 核心。掌握 State、节点、边、条件边、checkpointer,能手写 ReAct Agent。
第四阶段,工具调用与多 Agent。掌握工具注册、人工确认、流式输出、子图拆分。
第五阶段,评测与生产化。建立评测集,监控 token 成本,完善日志告警,再逐步扩大业务范围。
如果只看一句话:LangChain 提供零件,LangGraph 提供图纸,模型是决策者,工具是手脚。课程和文档能带你认识每个零件,但真正把图纸变成产品的,是在一个业务场景里反复打磨。拿到订单助手之后,可以按这个顺序改造它:先接数据库,再加人工审批,然后补日志和评测,最后把它包成 API 服务。每改一遍,对企业级 Agent 的理解就会深一层。