☰
Dify学习笔记 02 · LangGraph代码级Agent:工具、状态与记忆(第17-39集)
2026/10/30 8:30:03 网站建设 项目流程

李沐深度学习191集课程全解析:模块拆解、学习路径

吴恩达《面向开发者的提示词工程》

吴恩达 MCP 教程(Model Context Protocol)(一)

多模态大模型教程学习笔记 — ViT · CLIP · SAM · GLIP · Stable Diffusion

一句话总结:当 Dify 的拖拽节点满足不了精细控制时,就下沉到LangGraph——一个独立、MIT 开源、底层基于 FastAPI 的 Agent 编排框架。本模块(全课最重,23 集约 6.1 小时)带你搭起本地服务、理解 agent loop,掌握工具的四种创建方式、AgentState 状态管理、Command 更新、短期 checkpointer 与长期 store 记忆,并用 PostgreSQL 落地生产级持久化,同时讲透 Qwen3 流式工具调用的经典坑。

一、可视化导览

本模块的 agent 循环、脚手架文件、工具四方式、状态与记忆体系、Qwen3 坑已整理成一张知识图谱:模块二 · LangGraph 代码级 Agent · 可视化图谱https://mcnj5s80gkfh.doubaoapps.com/app/app_17fkrqyfemghttps://mcnj5s80gkfh.doubaoapps.com/app/app_17fkrqyfemghttps://mcnj5s80gkfh.doubaoapps.com/app/app_17fkrqyfemghttps://mcnj5s80gkfh.doubaoapps.com/app/app_17fkrqyfemghttps://mcnj5s80gkfh.doubaoapps.com/app/app_17fkrqyfemg

1.1 为什么要从 LangChain 迁到 LangGraph

早期大家用 LangChain 搭 Agent,但它的核心是一条"链",节点之间顺序执行、很难表达循环和分支。真实的 Agent 偏偏是个循环:模型要不断"想—做—看—再想",还经常需要在某个节点停下来等人审批、或者根据中间结果走不同分支。用链来硬套这种动态流程,代码很快就变成一团绕来绕去的回调和状态判断。LangGraph 就是为了解决这个问题而生的:它把整个 Agent 抽象成一张图,节点是处理步骤,边是流转方向,状态在节点间显式传递。这样一来,循环、分支、暂停、回放都成了图的天然能力,而不是你要自己手写的逻辑。

理解这个动机,你就明白 LangGraph 里那些看似繁琐的概念到底为谁服务。为什么要显式定义状态?因为图的每一步都要读上一步的结果。为什么要有 reducer?因为多个节点可能同时往一个字段写东西,得规定怎么合并。为什么要 checkpointer?因为图可能跑一半就停在等人审批,下次要能从断点接着跑。这些都不是凭空设计,而是"把动态 Agent 流程工程化"的必然要求。

1.2 workflow 与 agent 的本质区别再辨析

这个区别是面试高频,值得彻底讲透。workflow 的意思是:先做什么、再做什么、什么条件下走哪条路,这些都是程序员在代码里写死的。大模型在其中只是一个"被调用的函数",在固定的节点上干活,它不能决定下一步去哪。这种方式可控、可预测,适合流程明确的自动化任务。agent 则反过来:下一步去哪由模型自己根据当前情况决定。你给它一批工具,它读了用户问题和工具说明后,自己判断要不要调工具、调哪个、传什么参数,做完再看结果决定下一步。

现实中绝大多数系统是两者的混合:主干流程是写死的 workflow,但在某个节点内部让 agent 去自由发挥。比如一个客服系统,先路由判断问题类型(写死),再在"查订单"这个节点里让 agent 自主决定调哪个查询接口(动态)。学 LangGraph 时,要有意识地把你的业务拆成"哪些步骤该写死、哪些该交给模型",这才是成熟的 Agent 设计思维。

记忆钩子:workflow 是人开车,路线固定;agent 是导航自己选路。LangGraph 既能画死路线,也能在路口交给模型决定。

二、视频清单(23 集 · 约 6.1 小时,全课最重)

集

标题

时长

核心内容

17

最新版 LangGraph 的介绍

12:09

新版智能体移植到 LangGraph,选它的三大理由

18

Agent 和 WorkFlow

18:00

workflow 固定路径 vs agent 自主动态,agent loop

19

安装 LangGraph 本地服务(一)

14:19

Python3.11+、virtualenv、安装 langgraph-cli

20

安装 LangGraph 本地服务(二)

15:06

langgraph new 脚手架、pyproject、pip install -e .

21

启动 LangGraph 服务器

25:40

langgraph dev,REST API / 文档 / Studio 三地址

22

调用 Agent 发布的 API 接口(一)

16:52

REST 调用、消息结构、thread_id

23

调用 Agent 发布的 API 接口(二)

06:25

流式调用与 stream_mode 处理

24

Tool 的定义(一)

16:18

工具四组件、@tool 装饰器

25

Tool 的定义(二)

21:19

📝 工具入参 schema、描述优化(本地无素材,大纲补全)

26

Tool 的定义(三)

14:29

📝 工具返回值与 return_direct(本地无素材,大纲补全)

27

Qwen3 大模型工具调用解析器的错误

21:09

vLLM hermes 解析器漏 b 参数导致 Recursion limit

28

解决 Qwen3 流式输出的问题

14:09

📝 换 SGLang(支持 qwen_ip),两台机器部署(本地无素材,大纲补全)

29

根据 Runnable 对象创建工具

15:06

Runnable.as_tool 把 LCEL 链包成工具

30

继承 BaseTool 创建工具

19:32

BaseTool 子类、Pydantic 字段 Annotated[type, Field()]

31

Configurable 静态配置(一)

21:01

不可变配置、thread_id/用户名、prompt 从 config 读

32

Configurable 静态配置(二)

08:47

📝 配置在节点/工具中的读取实践(有视频无字幕,大纲补全)

33

AgentState 状态详解(一)

31:09

TypedDict、messages + add_message reducer

34

AgentState 状态详解(二)

14:38

📝 自定义 reducer、状态字段设计(本地无素材,大纲补全)

35

AgentState 状态详解(三)

05:12

Command(update) 更新状态、InjectedToolCallId

36

记忆存储的介绍

11:57

短期 checkpointer 与长期 store 的分工

37

开发环境下的短期记忆案例

17:49

InMemorySaver、thread_id 多轮记忆

38

PostgreSQL 实现 Agent 短期存储

16:59

PostgresSaver、with 语句、首次 setup 建表

39

PostgreSQL 实现长期存储

10:10

store 跨会话记忆,新增 store/migrations 表

三、LangGraph 定位与 agent loop

新版 Dify 智能体已全面移植到 LangGraph。它是一个独立框架(不绑定 Dify)、MIT 开源、底层用 FastAPI 提供服务。选它有三个理由:

① 可靠性、可控性

执行路径确定,支持人工审批(human-in-the-loop),关键动作可暂停等人确认,不再是黑盒

② 低层级、可扩展

暴露图、节点、边、状态等底层原语,能手撕多智能体协作,任意定制

③ 一流的流式

stream_mode 灵活,流式输出体验好

LangGraph 的平台版(LangGraph Platform)不开源、要收费;配套可观测平台 LangSmith 在国内基本无法顺畅使用。本课程用的是开源框架 + 自建本地服务。

workflow vs agent

Anthropic 把 LLM 应用分成两类:workflow是代码预先规定好的固定路径(LLM 只在固定节点被调用);agent则由模型自主、动态地决定下一步。智能体的三要素是模型 + 工具 + 提示词,其运行核心是 agent loop:

模型读取消息并选择工具→ 运行时执行工具→ 把工具结果收回写进 messages → 模型判断:还需调工具就继续循环,信息足够就输出最终答案。课程强调"一切都是图",最简单的create_react_agent本质就是一个单节点图。

四、本地环境搭建与服务启动

  1. 准备Python 3.11+,用 virtualenv 建独立虚拟环境。

  2. 安装 CLI:pip install langgraph-cli。

  3. 用模板创建项目:langgraph new <路径> --template new-langgraph-project-python。

  4. 安装本包:pip install -e .(可编辑模式);在 PyCharm 中把src目录标记为"源代码根目录",否则导入报错。

  5. 启动:langgraph dev。

生成文件

作用

graph.py

定义 graph 对象,是应用入口;节点、边、工具都挂在这里

langgraph.json

配置文件,声明图的位置;本地服务默认端口2024

pyproject.toml

依赖与包元信息

启动后会同时给出三个地址:REST API端点、接口文档(Swagger)、LangGraph StudioWeb UI。课程模型用 AutoDL 上私有化部署的 Qwen3-8B,通过 SSH 隧道把远程端口映射到本地。建议智能体 / MCP 场景优先用 Qwen3 满血版。

五、调用 Agent 发布的 API

服务启动后,Agent 就是一个标准 HTTP 服务。调用时通过thread_id区分不同会话:同一个 thread_id 的请求共享上下文(依赖记忆配置,见第九节),不同 thread_id 互相隔离。流式调用时返回结构由stream_mode决定(messages-tuple / messages 等),处理第一个 chunk 时要用 isinstance(data, list) 做类型保护,因为不同模式首帧结构不同。

六、Tool 工具系统

6.1 工具的四个组件

name

工具名,模型据此选择要调用的工具

description

工具描述,写得越清楚,模型越能在正确时机正确调用

args_schema

用 Pydantic 模型定义入参,框架据此自动生成 function schema 并做校验

return_direct

是否把工具结果直接返回给用户而不再过模型;约 80% 场景不设 true

6.2 四种创建方式

  • @tool 装饰器(最常用):直接注解一个普通 Python 函数,从 docstring 与类型注解生成描述和 schema。

  • Runnable.as_tool:把一个已有的 LCEL Runnable / 链包装成工具。

  • 继承BaseTool(最灵活):定义 BaseTool 子类,Pydantic 字段需写成Annotated[type, Field()]形式并附带描述。

  • MCP工具:第四种方式,通过 MCP 协议接入外部标准工具。

6.3 Qwen3 流式 function call 经典坑(重点)

现象:Agent 一调用工具就触发 Recursion limit(递归上限),工具被反复调用无法结束;但非流式调用却正常。

根因:vLLM 启动需指定--tool-call-parser。官方对 Qwen 建议用 hermes,而 Qwen 自有的解析器叫 qwen_ip(对应 qwen2.5 时的解析器名)。vLLM 不支持 qwen_ip、只支持 hermes,导致 Qwen3 在流式输出 function call 时漏传某个参数(如 b 参数),工具入参不完整 → 模型反复重试 → 撞递归上限。

解决:改用SGLang部署(它支持 qwen_ip 解析器)。注意 vLLM 与 SGLang 装在同一台机器会产生 torch 依赖冲突,必须分两台机器部署。

6.4 工具描述怎么写,模型才会用对

很多人抱怨"模型乱调工具、不按预期调用",其实八成是工具描述写得太差。工具的 description 不是写给人看的注释,而是写给模型的"使用说明书"。写得好,要点有三个:第一,一句话说清这个工具是干什么的,让模型在众多工具里能正确地"认出来"该用它;第二,说清什么场景下该用、什么场景下不该用,边界越清楚越不会误用;第三,每个入参都写清含义和取值,尤其是枚举类参数要告诉模型有哪些合法值。

举个反例:一个查天气的工具,描述只写"查天气",那模型在用户问"今天适合出门吗"时可能也来调它,其实用户要的是建议而非天气数据。正确写法应该是"当用户明确询问某个城市的实时天气或预报时调用;不需要用于给出出行建议本身"。这种"该用/不该用"的边界,往往比工具本身的实现更影响调用准确率。

6.5 四种工具创建方式怎么选

方式

适用场景

取舍

@tool 装饰器

最常用,包装一个普通函数

简单快捷,从类型注解自动生成 schema

Runnable.as_tool

已有一条 LCEL 链想复用为工具

避免重复实现,把现成链变成可被调用的工具

继承 BaseTool

需要复杂初始化、自定义校验

最灵活但样板代码多

MCP 工具

接入外部标准化能力

跨进程、跨语言,生态最广

日常开发里九成场景用 @tool 就够了。只有当你需要在工具初始化时持有连接、配置等资源,或者需要更精细的字段描述时,才考虑继承 BaseTool。MCP 工具则是当这个能力不是你自己实现、而是别人已经按 MCP 标准提供时才用。

6.6 Recursion limit 之外的常见运行时错误

除了课程重点讲的 Qwen3 流式坑,自己跑 Agent 还常遇到几类错误。工具名冲突:两个工具起了一样的名字,模型调用时认错。解决办法是保证工具名全局唯一且语义清晰。入参校验失败:模型传的参数格式不对,Pydantic 直接抛错,这时候要看 args_schema 是否定义清楚、错误信息有没有回传给模型让它自我修正。工具执行异常:工具内部报错时,要把错误信息作为 observation 写回 messages,让模型知道"这个工具失败了、换个法子",而不是整个 Agent 崩掉。最后是死循环:模型反复调同一个工具得不到满足,这正是课程那个递归上限的本质——要在系统里设定最大步数兜底。

排障习惯:Agent 行为不对时,第一件事是把每一步的 messages 完整打印出来看——模型到底"看到"了什么、为什么做这个决定。黑盒调试只会瞎猜。

七、Configurable 静态配置

configurable用来放不可变的静态配置,例如 thread_id、当前用户名、模型名、环境开关等。它在一次运行中保持不变,与会被节点不断更新的 state 相区分。常见做法:

  • 调用时在 config 中传入configurable={"thread_id": "...", "user_id": "..."}。

  • 系统提示词可以写成一个函数,在运行时从 config 里读取用户名等信息动态拼装,实现同一套图对不同用户呈现不同人设。

八、AgentState 状态与 Command

状态用 TypedDict 定义,最核心的字段是消息列表:

from typing import Annotated, TypedDict, Sequence from langchain_core.messages import BaseMessage from langgraph.graph.message import add_message class AgentState(TypedDict): # add_message 是 reducer:新消息自动"追加/合并",而非整体覆盖 messages: Annotated[Sequence[BaseMessage], add_message]
  • reducer(如 add_message):决定多个节点返回同一字段时如何合并。消息字段用 add_message,模型/工具产出的新消息会自动追加。

  • Command(update={...}):工具或节点可以返回一个 Command 对象来更新状态,而不只是返回字符串。

  • InjectedToolCallId:工具需要回写对应那条 tool call 结果时,用InjectedToolCallId依赖注入自动拿到 tool_call_id,无需模型生成。

  • Command 还支持delete,用于删除状态中的某些字段。

九、记忆:短期 checkpointer 与长期 store

类型

短期记忆 checkpointer

长期记忆 store

粒度

会话级,靠客户端传 thread_id 区分

跨会话,记住用户长期偏好/事实

前提

必须先配置 checkpointer

必须在短期记忆已配置的基础上才能启用

开发态

InMemorySaver(内存,重启即丢)

对应内存版 store

生产态

PostgresSaver(持久化)

PostgreSQL store(持久化)

PostgreSQL 落地要点:用PostgresSaver.from_conn_string(...)配合with语句获取连接;首次运行要调 setup() 创建 4 张 checkpoint_ 开头的表,之后切勿再调 setup()(避免重复建表/清数据)。开启长期 store 后,数据库会额外新增 store / migrations 相关表。Redis、SQLite 后端同理。

发布限制:使用自定义 checkpointer 时无法直接用langgraph dev发布成标准服务;出路是自己用 FastAPI 包裹图对外提供接口,或部署私有化的 LangSmith。

9.1 状态设计的常见误区

AgentState 看着就是个 TypedDict,其实设计学问很大。第一个误区:所有字段都不加 reducer,导致后一个节点直接覆盖前一个节点的结果。记住,messages 之所以用 add_message,就是因为它需要"追加"语义;如果你自己加了个字段想让多个节点累计写入,就得自己写对应的 reducer,否则每次都会被覆盖。第二个误区:把不该放状态的东西塞进状态。状态是会被序列化、持久化的,放进去的数据都会存进 checkpoint;临时变量、数据库连接这类不该持久化的,就别放状态里。第三个误区:状态过于庞大。什么都往里塞会让每次更新都要搬运大量数据,既慢又难调试。原则是:只放"跨节点需要共享、且需要被记住"的信息。

9.2 短期记忆和长期记忆到底差在哪

这是另一个高频混淆点。短期记忆(checkpointer)记的是"这一次对话进行到哪了"——用户和你一来一回说了什么、Agent 刚才调了哪个工具、中间结果是什么。它靠 thread_id 区分会话,换一个 thread_id 就是全新对话。它解决的是"多轮对话不遗忘上文"。长期记忆(store)记的是"这个用户是谁、他有什么长期偏好"——比如他叫什么、他喜欢什么风格、他提过自己是个学生。这些信息不绑定单次会话,跨很多次对话都要能用。

举个例子:用户第一次说"我是学生,回答请简洁"。短期记忆会记住"这一次对话里他说过这话",但下次开新会话就忘了;长期记忆会把"他是学生、喜欢简洁"存下来,以后每次新会话都能自动用上。工程上,长期记忆往往是 Agent 在对话中自动提炼出关键事实,写进 store;下次启动时再从 store 读出来拼进系统提示词。这就是为什么课程强调"长期记忆必须建立在短期记忆已经配置好的基础上"——它们是两层叠加的关系,不是二选一。

9.3 生产化时的注意事项

从开发态的 InMemorySaver 切到 PostgresSaver,不只是换个后端名字这么简单。内存版一重启就丢数据,只适合本地调试;Postgres 版才能真正持久化。但要记住课程那个关键提醒:首次运行必须调一次 setup() 把表建好,之后绝不能再调,否则可能重复建表甚至清掉已有数据。另外,生产环境要考虑:连接池配置(不要每个请求都新建连接)、表的备份(checkpoint 里存着所有会话状态,丢了就全没了)、以及数据隔离(不同用户的 thread 要做好隔离,别串台)。课程最后提到一个现实限制:用了自定义 checkpointer 后,就不能简单地靠 langgraph dev 直接发布,往往需要自己用 FastAPI 把图包一层对外提供服务——这是从 demo 走向产品必须跨过的一步。

📝 补全提示:第 25、26、32、34 集本地无素材,其工具入参 schema、return_direct、配置读取、自定义 reducer 等细节,已分别融入第六、七、八节的补充说明;以你安装版本的官方文档为准。

十、核心概念速查表

概念

一句话理解

agent loop

选工具 → 执行 → 收结果 → 判断终止的循环

create_react_agent

预置好的 ReAct 单节点图,快速建 Agent

工具四组件

name / description / args_schema / return_direct

工具四方式

@tool、Runnable.as_tool、继承 BaseTool、MCP

configurable

不可变静态配置(thread_id、用户等)

AgentState

TypedDict 状态,messages 用 add_message reducer

Command

节点/工具更新状态的对象,支持 update / delete

InjectedToolCallId

依赖注入自动获取 tool_call_id

checkpointer

短期会话记忆,按 thread_id 存取

store

跨会话长期记忆,依赖短期记忆先配置

stream_mode

决定流式返回结构,首帧需类型保护

十一、学习打卡

  • 能说清 workflow 与 agent 的区别、agent loop 的每一步

  • 能用 langgraph new 脚手架建项目并 langgraph dev 启动,拿到三个地址

  • 能通过 REST API 调用自己发布的 Agent,并用 thread_id 区分会话

  • 能用 @tool 装饰器定义一个含 Pydantic 入参的工具并被模型正确调用

  • 能用继承 BaseTool 的方式定义工具,理解 Annotated[type, Field()]

  • 理解 return_direct 的作用与默认取值场景

  • 能复现/解释 Qwen3 流式漏参数导致 Recursion limit 的原因与 SGLang 解法

  • 能用 configurable 传入 thread_id/用户名,并让提示词函数读取配置

  • 能定义 AgentState,理解 add_message reducer 与 Command(update)

  • 能用 InMemorySaver 实现多轮短期记忆

  • 能用 PostgresSaver 落地短期记忆,且只在首次 setup()

  • 能用 PostgreSQL store 实现跨会话长期记忆

学习建议:本模块是全课最硬的部分,建议"视频 : 敲代码 ≈ 4 : 6",每一节都跟着把图跑起来。学习顺序上,先把 17-23 的脚手架与 API 跑通建立信心,再攻 24-35 的工具与状态(这是 Agent 的灵魂),最后做 36-39 的记忆持久化;Qwen3 坑(27-28)建议在自己模型上实际复现一次,印象最深。

十二、本模块常见问题与实战补充

12.1 环境搭建踩坑汇总

第一次搭 LangGraph 环境,最常见的问题集中在三处。第一,Python 版本不够:langgraph-cli 对版本有要求,老版本解释器会装不上或跑不起来,务必用 3.11 以上。第二,可编辑安装后导入报错:pip install -e . 之后,如果在 IDE 里跑,要把 src 目录标记为源代码根目录,否则解释器找不到你写的 graph 包。第三,远程模型连不上:课程用 AutoDL 上的 Qwen3,要通过 SSH 隧道把远程端口映射到本地,否则本地服务访问不到模型;而且容器或本地访问模型地址时要填对主机,别犯"把 localhost 当成模型服务器"的错。建议把这三条坑在第一次跑通时就记录下来,后面换环境还会再遇到。

12.2 调试 LangGraph 的几个利器

代码级 Agent 最怕"不知道它内部在干嘛"。好在 LangGraph 提供了几种调试手段。一是 Studio Web UI:启动 langgraph dev 后那个网页界面,能可视化地看到图的结构和每一步状态,强烈建议用起来。二是把状态打印出来:在关键节点里 print 一下当前 state,一眼看清数据怎么流的。三是利用流式:用 stream_mode 边跑边看中间产物,而不是等最后结果。四是善用时间旅行:有了 checkpointer,你可以回到某一步重新跑,这对调试"为什么这一步走错了"极其有用。养成边跑边看状态的习惯,比读十遍代码都管用。

12.3 面试高频:请讲一个你设计的 Agent

面试时这道题几乎必问,答题框架可以参考。先讲场景:你要解决什么业务问题。再讲架构:主干是 workflow 还是 agent,哪些节点写死、哪些交给模型。然后讲工具:给了它哪几个工具,工具描述怎么写的。接着讲状态:你在 state 里存了哪些字段,有没有用 reducer。再讲记忆:短期怎么用 thread_id 隔离会话,长期记了哪些用户偏好。最后讲踩坑:比如遇到过模型乱调工具、递归上限、流式格式不对,你是怎么定位和解决的。把课程里这些点串成一个你亲手做过的小项目,这个回答就非常完整了。

十三、延伸练习

学完本模块,建议做三个递进练习。练习一:给你的 Agent 加一个会查当前日期时间的工具,让它回答"今天星期几"这类需要实时信息的问题,体会工具如何补上模型知识截止的短板。练习二:用 configurable 传入不同用户名,让 Agent 在对话开头自动称呼对方,体会静态配置如何区分多用户。练习三:用 PostgresSaver 落地后,重启服务,确认同一 thread_id 的对话还能接着上下文继续,体会持久化记忆的实际效果。

  • 我能用一句话说清 LangGraph 相比旧式链的核心优势

  • 我能在白纸上画出一个 ReAct agent loop 的完整循环

  • 我能区分短期记忆与长期记忆,并举出各自该记什么

  • 我知道生产化时为什么要自己包 FastAPI、要注意哪些数据问题

  • 我亲手复现过至少一次工具调用,并把 messages 打印出来看过

本模块小结:你已经从"会拖节点"进阶到"能写图"。工具、状态、记忆这三件事是代码级 Agent 的灵魂,把它们吃透,你就能驾驭那些平台满足不了的复杂场景。下一站模块三,回到 RAG,看怎么让 Agent 用好私有知识。

十四、深入理解 Agent 的工程本质

14.1 为什么说"一切都是图"

课程反复强调"一切都是图",这句话值得展开。在 LangGraph 眼里,不管你的应用多复杂,最终都能画成一张由节点和边组成的图。最简单的 ReAct Agent 看起来是一个循环,但它其实是一张只有两个节点的图:一个节点调模型,一个节点执行工具,边在"模型决定继续调工具"和"模型决定输出答案"之间分叉。你以后要做的多 Agent 协作、人在审批、子流程嵌套,本质上都是在这张图上加节点、加边、加条件。

这种图式思维的好处是可视化和可调试。你可以把整张图画出来,一眼看清数据怎么流、在哪分叉、可能在哪卡住。相比之下,纯代码写的 Agent 往往是一堆函数互相调用,出了问题很难一眼看出执行路径。学 LangGraph 时,建议每写一个 Agent 都先在纸上或白板上把图草画一遍——节点有哪些、边怎么连、状态怎么传——画清楚了再写代码,事半功倍。

14.2 人在审批(human-in-the-loop)为什么重要

这是 LangGraph 相比普通 Agent 框架的一大杀手锏。很多真实场景里,Agent 不能一路跑到底,关键动作必须先停下来等人确认。比如它要帮你发一封邮件、要执行一个删除操作、要调用一个花钱的接口——这些动作前最好暂停,把它打算做什么展示给你,你说"确认"它才继续。在 LangGraph 里,这天然支持:图可以在某个节点暂停,把状态存进 checkpointer,等人审批后再从断点恢复。这就是为什么前面说"可靠性、可控性"是选它的核心理由——没有这种暂停与恢复能力,Agent 只能拿来玩玩,不敢用在真业务上。

14.3 流式输出为什么是刚需

用户体验上,让用户干等十几秒看 Agent 全部想完再出结果,是不可接受的。流式输出让模型一边生成一边把字吐出来,体验好得多。但 Agent 的流式比单轮对话复杂:它中间还要调工具、看结果,这些过程也要能流式地展示给用户——"它现在正在查天气""它正在计算"。LangGraph 的 stream_mode 就是为这件事设计的,不同的模式让你既能拿到最终文本流,也能拿到中间每个节点的执行事件。课程特别提醒首帧要做类型保护,就是因为不同模式返回的数据结构不一样,不判一下直接解就会报错。

实操要点:做流式时,不要只关心最终答案流出来,也要把"正在调用工具"这种中间状态透传给前端,用户才会觉得系统是活的、有掌控感。

十五、从课程到生产:还差什么

课程带你跑通了一个可调用的 Agent,但离真正上线还有几步。一是错误处理:工具失败、模型超时、限流,都要有兜底,不能整个崩掉。二是可观测:线上 Agent 行为不对时,你得能回溯当时它经历了哪些节点、状态长什么样,这就需要日志和轨迹记录。三是成本控制:Agent 会反复调模型和工具,token 消耗是普通应用的好几倍,要监控、要设上限。四是安全:工具能调外部接口,就得防注入、防越权,别让用户诱导 Agent 干坏事。这些课程没展开,但都是从 demo 到产品绕不开的功课。

  • 我能在纸上画出 ReAct 的图,并标出分叉条件

  • 我理解了人在审批为什么是 Agent 上生产的关键能力

  • 我知道流式不仅要吐答案,还要展示中间工具调用

  • 我能列出从 demo 到生产还差的至少四项工程工作

记住:课程让你"会写",生产化要你"写稳"。中间隔着的就是错误处理、可观测、成本与安全这四道工序。

十六、把课程概念串成一个完整例子

为了帮你把零散概念串起来,我们构造一个完整例子:做一个"个人日程助手"。用户用自然语言说"帮我记一下明天下午三点开会"。整个流程在 LangGraph 里是这样跑的。开始节点接收用户消息,写进 state 的 messages。模型节点读了消息和可用工具后,决定要调用"新增日程"这个工具——它生成工具调用,带上参数"明天下午三点、开会"。图流转到工具节点,执行工具,把"新增成功"的结果写回 messages。模型再读一遍:信息够了,不需要再调工具,于是生成一句确认话输出给用户。这就是一个最小 agent loop。

现在叠加本模块学的各个能力。多轮对话:用户接着问"那我明天一共几个会?",因为用了 thread_id,短期记忆里还存着上一条日程,模型据此回答。多用户:configurable 里传入了 user_id,每个用户只能看自己的日程,工具内部按 user_id 隔离数据。长期记忆:用户之前说过"我喜欢把日程时间写得具体到分钟",这个偏好存在 store 里,每次新会话自动带上。可靠性:新增日程这种关键动作前,图暂停一下问用户"确认新增吗",等人点头才执行。你看,课程里每个概念都在这个例子里各司其职——这就是把知识学活的样子。

16.1 学习这个模块的正确节奏

23 集、6.1 小时,是全课最重的部分,一口气看完只会更晕。建议分成三段。第一段(17-23 集)先把脚手架和 API 跑通,目标是"能启动、能调通",不追求理解每个细节,先建立"它是个 HTTP 服务"的体感。第二段(24-35 集)是核心中的核心,工具和状态,这部分一定要逐行跟着敲,每个概念都用 print 看一眼实际数据。第三段(36-39 集)做记忆持久化,把短期和长期都跑通。Qwen3 那个坑(27-28)如果你的模型环境复现不出来也没关系,理解它的原理——流式 function call 的解析器问题——就够了,不必强求一模一样的现象。

节奏建议:这个模块宁可慢,不要飘。看懂了不等于跑通了,跑通了不等于理解了。每一节都问自己:我能不看笔记复现吗?

十七、常见误区与澄清

误区一:"Agent 越聪明越好,工具挂得越多越好。"错。工具太多,模型在选择时反而容易迷惑,挂工具要精不要多。误区二:"状态里塞得越全越保险。"错,冗余状态只会拖慢、搞乱。误区三:"短期记忆和长期记忆是一回事。"前面已辨析,一个绑会话、一个跨会话。误区四:"用了 LangGraph 就不用写错误处理了。"错,框架管流程不管业务健壮性。误区五:"create_react_agent 是万能的。"它只是个单节点图,复杂流程还得自己画。

  • 我能复用"日程助手"这个例子讲清 agent loop 全流程

  • 我按三段节奏推进,没有一口气硬啃

  • 我已跳出"工具越多越好"等常见误区

模块二的主体补充到此。它是全课最硬的部分,慢一点、动手多一点,都是值得的。

十八、代码视角的关键细节补充

18.1 add_message reducer 到底做了什么

很多人知道 messages 要用 add_message,但说不清它具体怎么合并。普通字段如果你返回一个新值,它会直接覆盖旧值。而 add_message 是一个 reducer,它的合并逻辑是:把你这次返回的新消息,追加到已有消息列表后面。更细一点,它还会处理"同一条消息的更新"——比如工具结果要回写某条已有的工具调用消息,它能识别出来是更新而不是新增。理解这点,你就明白为什么模型每生成一条、工具每返回一条,messages 列表就自动变长,而不需要你手动 append。这就是 reducer 的价值:把"怎么合并"这件事从你的业务代码里抽出来,框架替你管。

18.2 Command 为什么比直接返回字符串强

节点最朴素的写法是返回一个字符串,框架默认把它当成对 messages 的更新。但有时候你要做的事更复杂:比如工具执行完,你不仅要写回结果,还想顺便更新状态里另一个字段,甚至想直接改变图接下来的走向。这时候返回字符串就不够了,得返回一个 Command。Command 可以同时做几件事:用 update 指定要更新哪些状态字段,用 goto 指定下一步去哪个节点,用 delete 删除某些字段。这种"一次返回里既改状态又控流转"的能力,让复杂节点写起来干净利落,不用把逻辑拆散到好几个节点里。

18.3 InjectedToolCallId 解决了什么痛点

工具被模型调用时,框架需要知道"这个返回值是对应哪一次工具调用",才能把结果正确贴回 messages。按理说这个对应 id 应该由模型生成,但让模型每次都吐出正确的 tool_call_id 既麻烦又容易错。InjectedToolCallId 的思路是依赖注入:你在工具函数参数里声明一下需要这个 id,框架在调用工具时自动把当前这次调用的 id 塞进来,你直接拿来用就行,模型完全不用管。这种"框架自动注入运行时上下文"的设计,是 LangGraph 让工具写起来更省心的典型手法。

18.4 configurable 和 state 怎么分工

新手最容易把这两个搞混。记住一句话:configurable 是"运行前就定好、运行中不变"的静态配置,比如这个会话属于哪个用户、用哪个模型;state 是"运行中不断变化"的动态数据,比如对话进行到哪、中间结果是什么。一个是常量,一个是变量。为什么要分开?因为静态配置不应该被节点误改,把它单独放在 config 里,既安全又方便在调试时一眼看清。系统提示词写成函数去读 config 里的用户名,就是利用了这种"运行时动态读静态配置"的模式。

一句话分工:configurable 放"这次是谁、用什么",state 放"聊到哪了、有什么中间结果"。

十九、模块二收尾

走到这里,你已经具备了代码级 Agent 的核心能力:能搭服务、能调 API、能写工具、能管状态、能落记忆。回头看,模块一让你会用平台,模块二让你能写底层。这两者不是替代关系,而是视野的两层——你既知道平台里那个节点在代码里对应什么,也知道什么时候必须离开平台自己写。

  • 我能解释 add_message reducer 与普通字段覆盖的区别

  • 我知道 Command 相比返回字符串多了哪些能力

  • 我能说清 configurable 与 state 的分工

  • 我理解了 InjectedToolCallId 依赖注入省去了什么

模块二(第17-39集)到此结束。这是全课最硬的一战,撑过来你已经超过大多数只会调平台的人。下一站模块三,RAG。

十三、补遗:状态管理与排错的实战经验

写代码级智能体,真正让人头大的往往不是把图跑通,而是状态为什么没按预期流动。这里补几条实战中特别有用的经验。第一,调试时一定要把每一步的状态打印出来看。智能体的执行是一条接一条节点的,你光看最终输出根本不知道哪一步偏了;把每个节点进出的状态分别打出来,一眼就能看出是在哪一步丢了字段、改了值。这个习惯能帮你少走大量弯路。

第二,关于记忆。很多新手一上来就想存所有对话,结果状态越堆越乱、还超出上下文长度。正确做法是想清楚:哪些是每次都要带的短期记忆,哪些是该提炼后长期留存的;该忘的就主动忘,别让历史对话把后面的判断带偏。第三,工具调用失败要有兜底。真实世界里工具随时可能报错,你的图不能一遇到失败就整个崩掉,而要设计好失败后是重试、换个工具、还是直接告诉用户。这种鲁棒性,才是玩具和能用产品的区别。

面试时若被问到你做过的智能体,能主动讲出"我怎么调试状态、怎么处理工具失败、怎么管理记忆",就说明你不是照抄教程,而是真正踩过坑、想过设计。

再补一句心态上的建议:刚接触代码级智能体时,别想着一口气把所有高级技巧都用上。先用最朴素的图把一个能跑的最小版本做出来,让它至少能调一次工具、记住一点上下文。然后在这个能跑的基础上,一次只加一个改进,每次都观察行为有没有变好。这种小步快跑的方式,比一上来就堆复杂流程要稳得多,也更符合真实工程的节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询