术语表(Glossary)
《LangGraph.js 中文入门》全书核心术语速查。中英对照 + 一句话解释 + 首次出现章节。
怎么用这份术语表
- 看名字发懵时来这儿查:读某章遇到一个没见过的 API,按类别扫一眼就能找到「它是什么、第一次出现在哪一章」。
- 以本书实际用到的 API 为准:本表只收正文里真正出现过的名字。个别不属于本书的「周边概念」(如第 8 章大纲里预告的重试、缓存、可观测性)会明确标注,不混进正文术语里。
- 中英对照帮你对接官方文档:LangGraph.js 的官方文档、报错信息、类型定义全是英文,左列给中文名、括号里给英文名,方便你去官方文档搜。
- 「首次出现」用相对章节指路:写「见第 N 章」即可,方便你回正文对照上下文。术语本身的解释尽量克制、准确;能类比的会用 JS 的说法帮你对上直觉。
术语一致性的底线:本表里的
Annotation.Root、MessagesAnnotation、MemorySaver、SqliteSaver、InMemoryStore、interrupt、Command、Send、thread_id、streamMode、createSupervisor、createSwarm、addConditionalEdges、getStateHistory、recursionLimit等,写法都与本书代码完全一致。
一、基础概念
| 术语 | 说明 |
|---|---|
| 图(Graph) | 把流程建模成「节点 + 边」的有向结构,可以分支、循环、并行、暂停。这是 LangGraph 的核心心智模型,见第 1 章。 |
| 链(Chain) | 一条直线式的流程(A → B → C),路径固定、无状态、不能回头。LangGraph 正是为了解决「链不够用」而生,见第 1 章。 |
| 节点(Node) | 一个普通(可异步)函数:读状态 → 干活 → 返回要更新的字段。用 .addNode("名字", fn) 注册,见第 1 章。 |
| 边(Edge) | 规定「下一步去哪」。固定边用 .addEdge,条件边用 .addConditionalEdges,见第 1 章。 |
| 状态(State) | 在节点之间传递的「共享表格」。节点读它、返回增量更新,框架负责合并,见第 1 章。 |
| 智能体(Agent) | 能自己决定「下一步做什么」的 AI 系统,本质是一张带循环的图(模型节点 + 工具节点 + 条件边),见第 1 章。 |
| ReAct | Reasoning + Acting。经典智能体循环:模型判断 → 执行工具 → 回传结果 → 再问一次「还要吗」,直到模型不再调用工具,见第 1 章。 |
StateGraph | 建图的类。new StateGraph(状态定义) 得到建造者,链式 addNode/addEdge 后 .compile() 才能执行,见第 1 章。 |
Annotation.Root({...}) | 定义状态形状的方式:每个字段用 Annotation<类型> 声明,见第 1 章。 |
Annotation<T> | 声明单个字段的类型。直接调用表示「新值覆盖旧值」;传配置对象可加 reducer / default,见第 1 章。 |
START / END | 两个特殊虚拟节点(图里显示为 __start__ / __end__),分别表示入口和出口,别用它们当普通节点名,见第 1 章。 |
| 建造者(Builder) | new StateGraph(...).addNode(...)... 返回的对象,只有 addNode / addEdge / compile 等方法,见第 1 章。 |
| 可运行对象(Runnable) | .compile() 之后得到的对象,可以 .invoke() / .stream()。注意 compile() 不是「编译成机器码」,更像「把图纸变成能开动的机器」,见第 1 章。 |
| 超步(Super-step) | 图的执行单位:同一个超步里的节点会并发执行,跑完才进入下一步。recursionLimit 数的就是它,见第 3 章。 |
| 低阶编排框架(Low-level orchestration framework) | LangGraph 的官方定位:不负责「怎么调大模型」(那是 LangChain 的事),只负责「步骤之间怎么连接、状态怎么传递」,见第 1 章。 |
| Mermaid | 一种流程图文本格式。LangGraph 能把图导出成 Mermaid 贴到 mermaid.live 查看,见第 1 章。 |
getGraphAsync() / drawMermaid() / drawMermaidPng() | 可视化三件套:拿到可绘制图对象、导出 Mermaid 文本、生成 PNG(后者会调用外部服务,需联网),见第 1 章。 |
二、状态与数据流
| 术语 | 说明 |
|---|---|
| 增量更新(Update) | 节点返回的是「只包含想改的字段」的对象,不是整体状态。框架负责把它合并进 state,见第 1 章。 |
| 覆盖(Overwrite) | 字段没有 reducer 时的默认语义:后写的把先写的整个顶掉。只返回部分字段不影响其它字段,同名同字段则最后一次生效,见第 2 章。 |
| 合并(Merge) | 框架把节点增量并入状态的过程。对普通字段是「保留未更新字段」,对带 reducer 的字段按 reducer 规则,见第 1、2 章。 |
| reducer(归约函数) | 定义字段「怎么合并」的函数:(left, right) => 新值。left 是当前值,right 是本次写入值。没写 reducer 就是覆盖,见第 1、2 章。 |
default(默认值) | Annotation<T>({ default: () => 初始值 }):字段第一次被写时 left 是 undefined,用 default 给个起点,避免 undefined + x 变 NaN,见第 2 章。 |
| 通道(Channel) | 字段底层的「合并规则承载者」。StateSchema 写法直接把通道选择写进字段定义,见第 2 章。 |
MessagesAnnotation | 官方预置状态:messages 字段自带「追加」reducer。等价于手写 Annotation<BaseMessage[]>({ reducer: (l, r) => l.concat(r), default: () => [] }),见第 1 章。 |
messagesStateReducer | 官方导出的消息 reducer,可直接用作自定义状态里 messages 字段的合并规则(行为同 MessagesAnnotation),见第 4 章。 |
StateSchema(new StateSchema({...})) | LangGraph v1 的新式状态写法:每个字段用类型 schema(如 zod)声明,把「通道」直接写进字段定义,见第 2 章。 |
ReducedValue | StateSchema 写法里「带 reducer 的字段」,等于旧写法 Annotation<T>({ reducer }),见第 2 章。 |
UntrackedValue | StateSchema 写法里的「瞬态字段」:值正常参与本次运行、节点能读到,但不进入 checkpoint,适合临时缓存,见第 2 章。 |
输入 / 输出 schema(state / input / output) | new StateGraph({ state, input, output }):state 是内部完整状态,input 限定 invoke 能传什么,output 限定只返回什么 —— 中间字段对外「隐身」,见第 2 章。 |
RemoveMessage | 删除指定消息的指令对象:new RemoveMessage({ id }),reducer 合并时会把对应 id 的消息剔除。必须带真实存在的 id,见第 2 章。 |
REMOVE_ALL_MESSAGES | 清空消息的特殊哨兵:new RemoveMessage({ id: REMOVE_ALL_MESSAGES }) 会把之前所有消息丢弃,只保留它之后的消息,见第 2 章。 |
| 消息裁剪 / 上下文膨胀(Message trimming / Context bloat) | 挂了 checkpointer 后 messages 每轮 +2、一路膨胀,迟早顶爆上下文窗口。做法是加一个裁剪节点,用 RemoveMessage 删掉最老的若干条,见第 2、4 章。 |
三、控制流
| 术语 | 说明 |
|---|---|
固定边(addEdge / Fixed edge) | 写死的「铁轨」,必走。可视化里是实线,见第 1 章。 |
条件边(addConditionalEdges / Conditional edge) | 「岔道口」:.addConditionalEdges("源节点", 路由函数, [可能节点列表]),去向由路由函数现场决定。可视化里是虚线,见第 1 章。 |
| 路由函数(Router / Routing function) | 一个普通函数:读状态、返回下一步的节点名或 END。它只「指路」,不干活,返回值必须是图上真实存在的节点名,见第 1、3 章。 |
| 循环(Loop) | 条件边指回已走过的上游节点。图比链强大的核心能力之一,但必须设退出条件,见第 3 章。 |
| fan-out(扇出) | 一个节点连出多条边,让多个下游节点在同一个超步里并发执行,见第 3 章。 |
| fan-in(扇入) | 多条边汇入同一个节点;框架自动「等齐」所有上游,只运行一次下游节点,见第 3 章。 |
| 屏障(Barrier) | fan-in 自动「等齐」的语义,LangGraph 内置,你什么都不用写,见第 3 章。 |
| 并发写入的字段必须带 reducer | 同一超步里多个节点写同一字段时,字段必须带 reducer,否则报 InvalidUpdateError。reducer 应做可交换的合并(concat / + / 取最大),因为并发顺序不确定,见第 3 章。 |
recursionLimit | invoke(input, { recursionLimit: N }),限制超步次数,默认 25。超限抛 GraphRecursionError,是兜住死循环的「刹车」,见第 3 章。 |
GraphRecursionError | 运行超过 recursionLimit 时抛出的错误,用 e instanceof GraphRecursionError 捕获,见第 3 章。 |
InvalidUpdateError | 无 reducer 的字段在一个超步里被写多个值(或条件边返回值非法)时抛出,见第 3 章。 |
UnreachableNodeError | 节点「不可达」时编译期抛错。典型触发:条件边第三个参数漏列了某个分支节点,或用了 Command.goto 却没在 addNode 声明 ends,见第 3 章。 |
Send | 动态派发:条件边函数返回一个 Send 数组,数组长度 = 并发任务数,每个 new Send("节点", 局部状态) 是独立的节点调用。这就是动态 map-reduce,见第 3 章。 |
Command | 节点返回值:new Command({ update, goto }),一步同时「更新状态 + 决定跳转」。update 等价于普通 return {...},goto 等价于条件边路由结果,见第 3 章。 |
ends | .addNode("名字", fn, { ends: ["a", "b"] }):声明该节点可能跳去哪些节点。用 Command.goto 时必须声明,否则编译期报 UnreachableNodeError,见第 3 章。 |
| map-reduce | 动态模式:用 Send 把列表 map 成 N 个并发任务,再用一个节点 reduce 汇总。结果要落到带 reducer 的字段上,见第 3 章。 |
| 动态边(Dynamic edge) | 由 Send 在运行时生成、数量随数据变化的边;静态图结构(getGraphAsync())里看不见它,见第 3 章。 |
Command.PARENT | new Command({ goto, graph: Command.PARENT }):让子图里的节点直接跳转到父图的某节点。多智能体移交的底层机制,见第 3、7 章。 |
四、持久化与记忆
| 术语 | 说明 |
|---|---|
| 持久化(Persistence) | 让图在每一步之后自动把状态存下来、并在下次调用时读回来,是「有状态智能体」的地基,见第 4 章。 |
| Checkpointer(检查点存储器) | 挂在 compile({ checkpointer }) 上的持久化组件,像「游戏存档器」。没挂它,图跑完即忘,见第 4 章。 |
| Checkpoint(存档 / 检查点) | 每一步之后保存的状态快照,按 thread 归档,是「时间旅行」和「断点续跑」的载体,见第 4 章。 |
MemorySaver | 内存版 checkpointer:存在进程内存里,进程一退出就清零,适合开发调试,见第 4 章。 |
SqliteSaver | SQLite 落盘版 checkpointer:SqliteSaver.fromConnString(path),把快照写进 .sqlite 文件,跨进程重启仍保留,见第 4 章。 |
BaseCheckpointSaver | checkpointer 的公共接口。换 saver(内存 / SQLite / Postgres…)不用改图,见第 4 章。 |
thread_id | 一次会话的「房间号」,通过 invoke(input, { configurable: { thread_id } }) 传入。同一 thread_id 共享记忆,换一个就全新失忆。记忆在 saver 里,不在图里,见第 4 章。 |
configurable | invoke 第二个参数里的配置对象,thread_id、checkpoint_id、checkpoint_ns 都放在里面,见第 4 章。 |
| 时间旅行(Time travel) | 借助存档回到过去某一步、改写状态、再从那里重跑后面的步骤,典型用途是调试,见第 4 章。 |
getState(config) | 取「当前」存档(最新状态 + next 待执行节点),见第 4 章。 |
getStateHistory(config) | 列出该 thread 上的所有存档(最新在前,返回 async iterator,想按时间顺序就 .reverse()),见第 4 章。 |
updateState(config, values) | 改写某个存档的状态。不是原地改,而是在旧存档上分叉出新存档,并返回那个新存档的 config,见第 4 章。 |
StateSnapshot | getState / getStateHistory 返回的存档对象类型,含 values、next、config 等字段,见第 4、5 章。 |
checkpoint_id / checkpoint_ns | 存档的标识。恢复 / 重放时应直接复用快照自带的 config(它含 thread_id + checkpoint_ns + checkpoint_id),别自己拼,否则易漏 checkpoint_ns 报错,见第 4、5 章。 |
| 分叉(Fork) | updateState 在旧存档上生成一条新分支,用返回的新 config 才有改动。invoke(null, 分叉config) 从分叉点续跑,见第 4、5 章。 |
| 短期记忆(Short-term memory) | 由 checkpointer + thread_id 承载,作用域是单个 thread,存整条线程的完整状态。回答「这段对话聊到哪了」,见第 4 章。 |
| 长期记忆(Long-term memory) | 由 store 承载,作用域跨所有 thread,按 { namespace, key } → value 存任意数据。回答「我关于这个用户 / 这个世界知道什么」,见第 4 章。 |
store | 长期记忆仓库。compile({ store }) 传入,节点里用 getStore(config) 取,见第 4 章。 |
InMemoryStore | 内存版 store(new InMemoryStore()):进程结束即失。生产要持久化就换落盘的 store 实现,见第 4 章。 |
getStore(config) | 在节点里取 store 的方法。节点的第二个参数就是 config,一定要把它传进去,否则拿不到,见第 4 章。 |
namespace | store 里数据的组织方式:字符串数组(像目录)。按「用户 / 业务」分 namespace,而不是按 thread —— 这正是长期记忆「跨 thread」的本质,见第 4 章。 |
BaseStore | store 的公共接口,对应 checkpointer 的 BaseCheckpointSaver。store 还支持 search() / listNamespaces(),见第 4 章。 |
五、人工介入(HITL)
| 术语 | 说明 |
|---|---|
| HITL(Human-in-the-Loop,人类在环) | 让流程在中间停下来、等一个人拍板再继续。典型场景:审批、修改内容、审阅工具调用、多轮澄清,见第 5 章。 |
interrupt(payload) | 在节点里调用的暂停函数:抛出特殊信号 → 图暂停 → 把 payload 交给人类。靠抛异常实现,别用 try/catch 吞掉;必须挂 checkpointer 才能用,见第 5 章。 |
__interrupt__ | 暂停时 invoke 正常返回的状态对象里多出来的字段,是个数组(一个超步里可能有多个节点同时中断),见第 5 章。 |
isInterrupted(result) | 判断「图是否被暂停」的规范方法,比手写 "__interrupt__" in result 更稳,见第 5 章。 |
Interrupt(类型) | __interrupt__ 数组里的元素,形如 { id?, value?, response_schema? },value 就是你传给 interrupt() 的 payload,见第 5 章。 |
resume | 恢复执行时传入的值:graph.invoke(new Command({ resume: 值 }), config)。这个值会成为被中断节点里 interrupt() 的返回值,见第 5 章。 |
Command({ resume }) | HITL 的恢复通道。人类答复或系统兜底值,都走这一条通道送回图里,见第 5 章。 |
responseSchema | interrupt(payload, { responseSchema: zodSchema }):给人类输入加类型 / 校验,避免五花八门的字符串误判,见第 5 章。 |
| 审批(Approval) | 危险工具(转账 / 删库 / 发邮件)执行前先 interrupt 问一次,人类回 approve / reject 再决定是否执行,见第 5 章。 |
| 副作用账本(Side-effect ledger) | 用一个数组记录「危险操作真正执行了几次」,用来证明工具到底有没有被执行(而不是「执行了又撤销」),见第 5 章。 |
| 幂等(Idempotent) | 恢复执行时,中断的那个节点会被整体重跑一遍(interrupt() 之前的代码会再执行一次),所以那些代码必须无副作用、可重复执行,见第 5 章。 |
| 超时兜底(Timeout fallback) | 审批加截止时间,人没按时响应就用「安全默认值」(通常 reject)恢复。超时逻辑写在调用方(你的应用 / 网关),不写在图里,见第 5 章。 |
六、流式输出
| 术语 | 说明 |
|---|---|
| 流式输出(Streaming) | 把「一大块结果」拆成「一小口一小口」实时送出。不改变总耗时,改变的是「用户什么时候开始觉得系统活着」,见第 6 章。 |
| TTFT(Time To First Token,首字延迟) | 从发请求到第一个 token 出现的耗时,是量化流式体感的核心指标,见第 6 章。 |
streamMode | graph.stream(input, { streamMode }) 里选择「流什么」的参数,取值 values / updates / messages / custom / debug / tasks / checkpoints,见第 6 章。 |
values(streamMode) | 每次吐完整状态快照(当前所有字段)。像定时拍全景照,字段只增不减,见第 6 章。 |
updates(streamMode) | 每次只吐增量:{ 节点名: 该节点返回值 },带节点名。像日志,用来知道「哪一步跑完了、写了什么」,见第 6 章。 |
messages(streamMode) | token 级增量 [消息增量(AIMessageChunk), 元数据]。只要节点里调了模型就会产,用来做打字机,见第 6 章。 |
custom(streamMode) | 你自己用 getWriter() 推出来的任意数据(进度条、阶段提示),框架和模型都猜不到,只有业务逻辑知道,见第 6 章。 |
debug(streamMode) | 框架内部的详细事件,排查问题用,见第 6 章。 |
tasks / checkpoints(streamMode) | 任务 / 检查点事件,属高级场景(与第 4 章持久化相关),见第 6 章。 |
getWriter() | 在节点内部拿到「上报函数」的方法(走 AsyncLocalStorage)。const write = getWriter(); write?.({...}) 就会以 custom 模式流出,见第 6 章。 |
writer | 官方文档里的另一种上报方式。注意:@langchain/langgraph 1.4.21 里裸 writer() 是「哑」的(静默不发),可靠写法用 getWriter(),见第 6 章。 |
多模式合并 / 二元组 [模式名, 数据] | streamMode 可以写成数组同时订阅多个模式;此时每个 chunk 从「裸数据」变成 [模式名, 数据] 二元组,靠第 0 个元素分流,见第 6 章。 |
metadata.langgraph_node | messages 模式里每个 chunk 的元数据字段,告诉你这个 token 来自哪个节点,见第 6 章。 |
| 打字机效果(Typewriter) | 逐 token 输出正文、边生成边显示。做时要把推理模型的「思考」(reasoning_content)与正文(content)分流,只对正文打字,见第 6 章。 |
| SSE(Server-Sent Events) | 一条 HTTP 长连接、服务器单向持续推文本事件的协议。一帧格式固定:event: + data: + 空行,见第 6 章。 |
EventSource | 浏览器原生的 SSE 客户端,es.addEventListener("token", ...) 零依赖接流,见第 6 章。 |
useStream() | @langchain/langgraph-sdk 提供的 React Hook,内部走 SSE,直接给你 messages / values / isLoading 等响应式状态,见第 6 章。 |
| Agent Chat UI | 官方聊天前端,配合 useStream() 可几分钟搭出一个聊天界面,见第 6 章。 |
七、多智能体
| 术语 | 说明 |
|---|---|
| 多智能体(Multi-Agent) | 把一个大任务拆给多个「专家」,每个专家一个人设、一个干净的上下文,解决单 agent 的「职责污染」,见第 7 章。 |
| 子图(Subgraph) | 一张 compile() 过的图,被 addNode("名字", 子图) 挂进父图 —— 「子图就是一个更大的节点」。父子字段不一致时包一个转换节点做映射,见第 7 章。 |
| Supervisor(调度者模式) | 有中心结构:一个总管 + 若干专家,总管自己不干活,只决定「下一个该谁上」,专家干完都回总管汇报,见第 7 章。 |
createSupervisor | 从 @langchain/langgraph-supervisor 导入,一行建出总管图。它会自动为每个专家生成 transfer_to_<专家名> 移交工具,返回的是 StateGraph,记得 .compile(),见第 7 章。 |
移交工具(Handoff tool)/ transfer_to_<专家名> | 一个「返回值是 Command 的工具」:调用它不再是写回消息,而是按 goto 把控制权交给目标专家。supervisor 与 swarm 的核心都是它,见第 7 章。 |
| Swarm(去中心化 / 蜂群模式) | 无中心结构:专家之间直接互相移交,没有总管。谁「当前活跃」记在状态字段 activeAgent 里,见第 7 章。 |
createSwarm | 从 @langchain/langgraph-swarm 导入。必须传 defaultActiveAgent,指定「第一个开口的是谁」,见第 7 章。 |
defaultActiveAgent | createSwarm 的必填参数,指定 swarm 启动时活跃的专家,见第 7 章。 |
activeAgent | swarm 状态里的字段,记录当前活跃的专家。专家调用移交工具就是把它改成别人,见第 7 章。 |
createHandoffTool | 从 @langchain/langgraph-swarm 导入,手造移交工具。因为没有总管替你准备,swarm 里每个专家得自己带「话筒」(把自己要用的移交工具 bindTools 进去),见第 7 章。 |
createHandoffBackMessages | 官方提供的移交时裁剪上下文的工具,避免下游专家看到不属于自己的工具调用记录,见第 7 章。 |
getCurrentTaskInput() | 在工具 / 节点里取当前任务状态的方法,手写移交工具时用来把状态取出来再拼进 Command.update,见第 7 章。 |
| 移交轨迹(Handoff trace) | 用 streamMode: "updates" 记录节点被访问的顺序,还原出「谁把活交给了谁」的路径,见第 7 章。 |
| 层级结构(Hierarchical) | 专家很多时,用「总管下面再挂分总管」代替平铺十几个专家,比平铺更稳。实践上专家数量建议控制在 2~5 个,见第 7 章。 |
八、工程化
| 术语 | 说明 |
|---|---|
环境变量与 .env | 密钥 / 接口地址 / 模型名统一走 .env(AI_API_KEY / AI_ENDPOINT / AI_MODEL),被 .gitignore 排除,永远不要把密钥写进代码,见第 0 章。 |
共享模型工厂(createModel() / banner()) | lib/model.ts 里的全书复用工具:统一创建模型、统一打印横幅。密钥统一走 .env,见第 0 章。 |
一键验证(npm run verify) | 逐个运行 scripts/verify-examples.sh 里登记的所有示例,确认全书示例都能跑通(会真实消耗 API 额度),见第 0 章。 |
| 三级判定(PASS / SUSPECT / FAIL) | 对示例运行的判定:PASS 正常退出、SUSPECT 没崩但输出明显不对、FAIL 非零退出或运行时故障。设 SUSPECT 是因为有些 bug「静悄悄地错」,只看退出码抓不到,见第 0 章。 |
类型检查(npm run typecheck) | 验证所有 .ts 的类型正确,见第 0 章。 |
| Node 版本要求 | LangGraph.js v1 依赖 Node 18+ 新特性,本书要求 Node >= 20(推荐 22 / 24),Node 16 会直接报错,见第 0 章。 |
重试策略(retryPolicy) | addNode(名字, 函数, { retryPolicy: {...} })。节点抛错时框架自动重试,常用字段 maxAttempts(最多几次)/ initialInterval(首次等待毫秒)/ backoffFactor(退避倍数,实现指数退避)。见第 8 章。 |
节点缓存(cachePolicy) | 两步走:compile({ cache }) 给整张图装缓存存储,再 addNode(名字, 函数, { cachePolicy: true }) 声明这个节点允许被缓存。相同输入第二次命中缓存,节点函数根本不会被调用。见第 8 章。 |
缓存存储(InMemoryCache) | 内存型缓存存储。注意:在本书锁定的版本里,它从 @langchain/langgraph-checkpoint 导入(不是在 @langchain/langgraph 或 @langchain/core/caches)。见第 8 章。 |
错误处理器(errorHandler) | addNode 的选项,签名 errorHandler(state, error),error 的类型是 NodeError。在 retryPolicy 重试用尽之后才触发,用来做优雅降级,而不是让整张图崩掉。见第 8 章。 |
节点超时(timeout) | addNode 的选项,限制单个节点的最长执行时间,超时按节点失败处理。见第 8 章。 |
| 图服务化(HTTP 服务) | 把编译好的图包成 HTTP 接口对外提供能力(例如 POST /chat + GET /health)。多轮会话靠请求里带 thread_id 实现。官方路线是用 langgraph.json 配一张图 + @langchain/langgraph-cli 起服务,见第 8 章。 |
| 可观测性(LangSmith) | 开启 LANGCHAIN_TRACING_V2 / LANGCHAIN_API_KEY / LANGCHAIN_PROJECT 三个环境变量后,图的每一次执行都会上报 LangSmith,可在网页上逐步查看完整轨迹(每个节点的输入输出、耗时、工具调用)。未配置时不影响本地运行。见第 8 章。 |
| 无依赖的本地追踪 | 不接外部服务的那条路:自己包一层节点函数,打印每步耗时与输入/输出摘要。适合不能把数据外发的场景。见第 8 章。 |
| 分层应用结构 | 一个能长大的 LangGraph 项目通常分四层:状态(state.ts)→ 工具(tools/)→ 节点(nodes/)→ 组装(graph.ts),再加一个入口。每层只干一件事,才能被单测、被复用。见第 10 章。 |
| 图只编译一次 | compile() 有真实开销,不要在每个请求里重建图。把编译结果放在模块级导出、全进程复用。见第 10 章。 |
运行时上下文(contextSchema / context) | 每次调用都变的值(用户 ID、语言、渠道)不应硬编码进图或状态。用 new StateGraph(State, ContextSchema) 声明,节点通过第二个参数 runtime.context 读出,调用时 invoke(input, { context }) 传入。见第 10 章。 |
| 节点单测 | 节点就是 (state) => update 的普通函数,所以可以脱离图和网络单测:喂构造好的假 state,断言返回值。见第 12 章。 |
| 假模型(fake model) | 返回固定内容的模型替身,用来测整张图而不花 API 钱、且结果确定。还能直接构造固定 tool_calls,从而断言「工具到底被调用了没有」。见第 12 章。 |
| 结构断言 | 集成测试里不要断言完整文本(模型输出天然随机),改为断言结构:消息条数、是否出现 tool 消息、最后一条是不是 AI、内容里是否含关键数字。见第 12 章。 |
| 报错速查手册 | 第 12 章把常见报错逐条刻意触发并给出三段式说明(触发条件 → 根因 → 修法),包括 InvalidUpdateError、UnreachableNodeError、GraphRecursionError、节点名与状态字段重名、缺 checkpoint_ns、maxTokens 导致正文为空等。见第 12 章。 |
九、大模型相关
| 术语 | 说明 |
|---|---|
| LLM(大语言模型) | 在海量文本上训练、能理解与生成自然语言的模型。本书不绑定任何一家服务商,只要兼容 OpenAI 接口即可,见第 0 章。 |
| 服务商(Provider) | 提供兼容 OpenAI 接口(/v1/chat/completions)的公司,如 DeepSeek、硅基流动、阿里云百炼、智谱、本地 Ollama。换服务商只改 .env,代码一行不动,见第 0 章。 |
| 工具调用(Function Calling / Tool Calling) | 模型生成「调用某工具 + 参数」的结构化请求的能力。模型只描述,不执行,执行由你的代码(或 ToolNode)完成。选模型时这是硬性要求,见第 0 章。 |
tool() | 从 @langchain/core/tools 导入,定义一个模型可调用的工具:给 name / description / schema(zod),见第 1 章。 |
bindTools() | 把工具绑定到模型上,让模型知道「可以调什么」,见第 1 章。 |
tool_calls | 模型返回的「要调用哪些工具、参数是什么」字段。条件边靠检查上一条 AI 消息里有没有 tool_calls 来决定去 tools 还是结束,见第 1、5 章。 |
ToolNode | 官方提供的「执行工具」节点:从最后一条 AI 消息读出 tool_calls、逐个执行、把结果包成 ToolMessage 返回。注意它会立刻执行,要「执行前先问人」就得自己写节点,见第 1、5 章。 |
createReactAgent() | 从 @langchain/langgraph/prebuilt 导入,一行得到标准 ReAct 图(模型 + 工具 + 条件边,等价于手工版),见第 1、7 章。 |
createAgent() | LangChain v1 里更上层的封装(从 "langchain" 导入),内部也是基于 LangGraph,额外加了中间件(middleware)、结构化输出等能力。分层关系:手工 StateGraph ← createReactAgent() ← createAgent(),见第 1 章。 |
| 消息类型 | HumanMessage(用户输入)、AIMessage(模型回复)、SystemMessage(身份 / 规则)、ToolMessage(工具结果,回传给模型)、AIMessageChunk(messages 流式模式的 token 增量)。基类是 BaseMessage,见第 1、5、6 章。 |
zod | TypeScript 优先的 schema 校验库。本书用它定义工具参数(schema: z.object({...}))和 StateSchema 的字段,同时提供编译期类型与运行期校验,见第 1、2 章。 |
maxTokens | 限制回复长度的参数。推理型模型的 maxTokens 包含思考消耗,设太小会把额度花在思考上、导致 content 变空字符串且不报错,见第 0、1、6 章。 |
temperature | 控制随机性的参数。本书示例多数用 temperature: 0 求稳定复现,见第 0、1 章。 |
| token(词元) | 模型处理文本的最小单位。既影响成本也影响流式粒度(messages 模式就是 token 级增量),见第 6 章。 |
| 推理型模型(Reasoning model) | 会先输出一大段「思考过程」的模型(如本书默认的 deepseek-flash)。思考不在 content 里,而在 additional_kwargs.reasoning_content,见第 1、6 章。 |
reasoning_content | 推理型模型思维链所在字段(chunk.additional_kwargs.reasoning_content)。做打字机时必须把它和正文 content 分流,见第 1、6、7 章。 |
| 上下文窗口(Context window) | 模型单次请求能处理的最大 token 数。聊久了 messages 会顶爆它,需要裁剪(第 4 章),见第 4 章。 |
十、编排设计模式
第 9 章把这些「该把机制拼成什么形状」的经验总结成五种模式。
| 术语 | 说明 |
|---|---|
| 提示链(Prompt Chaining) | 把任务拆成固定顺序的步骤,上一步的产出喂下一步。适合能清晰拆解、每步都可验证的任务,见第 9 章。 |
| 路由(Routing) | 先用模型对输入分类,再用条件边走到不同分支(每条分支有自己的提示词/工具)。适合输入类型差异大、混在一起会互相干扰的场景,见第 9 章。 |
| 并行化(Parallelization) | 两个子形态:**分片(Sectioning)**把活儿切开并行做;**投票(Voting)**同一问题跑多次取多数。用带 reducer 的字段接收并发结果,见第 9 章。 |
| 编排者-工作者(Orchestrator-Worker) | 由模型动态决定拆成几个子任务(数量不写死),用 Send 分发给 worker,再汇总。适合"任务边界事先说不清"的场景,见第 9 章。 |
| 评估者-优化者(Evaluator-Optimizer) | 生成 → 评审给具体意见 → 按意见重写,循环到达标或到上限。适合"有明确质量标准、但一次写不好"的任务,见第 9 章。 |
| 工作流 vs 智能体(Workflow vs Agent) | 核心判断题:路径能不能事先定死? 能 → 用工作流(可预测、便宜、好调试);不能 → 才上智能体(灵活但贵、路径不可预测)。先想工作流,不够用再放开自主权,见第 9 章。 |
十一、从 LangChain 迁移
| 术语 | 说明 |
|---|---|
| LCEL(LangChain Expression Language) | LangChain 的链式写法:用 .pipe() 把组件串成一条直线。迁到 LangGraph 时,RunnableSequence 对应「节点 + 固定边」。见第 11 章。 |
RunnableLambda | 把一个普通函数包装成可参与 LCEL 的组件。迁移时它通常直接变成一个图节点。见第 11 章。 |
RunnableBranch | LCEL 里的条件分支,对应 LangGraph 的 addConditionalEdges。注意 LCEL 的 RunnableBranch 多数场景仍先跑完前面所有步骤;换成图后才能真正"不走的边就不执行"。见第 11 章。 |
RunnablePassthrough | LCEL 里把输入原样传递下去的组件,对应 LangGraph 里的「状态字段自然传递」,迁到图里通常直接消失。见第 11 章。 |
| 把现有链当节点(最省力的迁移路径) | 不必重写老代码:.addNode("step", 现有Runnable) 或节点里 await 链.invoke(...),老链就变成图上的一个节点。见第 11 章。 |
| 迁移映射表 | 第 11 章给出的 LCEL → LangGraph 对照表(含"能直接对应"与"需要改造"的标注)。见第 11 章。 |
| 该不该迁 | 出现这四类信号才迁:需要循环、需要中途停下等人、需要跨请求记忆、需要并行分支再汇总。纯直线、无状态、无中断的链留在 LangChain 更划算。见第 11 章。 |
十二、函数式 API
LangGraph 的第二套写法:不画图,直接写代码。
| 术语 | 说明 |
|---|---|
entrypoint | 把一个 async 函数变成可运行对象(有 .invoke() / .stream(),可接 checkpointer),等价于一张图。函数最多接收 (input, config) 两个参数,见第 13 章。 |
task | 把一段逻辑包装成"任务",白捡五样能力:可命名、可并行、可重试(retry)、可缓存(cachePolicy)、可超时(timeout)。只能在 entrypoint 或图节点内部调用,见第 13 章。 |
getPreviousState | 函数式写法读"上一轮状态"的方式,对应图中的状态字段,见第 13 章。 |
entrypoint.final({ value, save }) | 函数式 API 独有的能力:把「返回给调用方的值」(value)和「存进 checkpoint、下一轮可读的值」(save)分开,见第 13 章。 |
| 两套写法的选型 | 图擅长结构(复杂编排、循环、可插入校验),函数式擅长自由(直线流程、大量可并行子步骤)。官方建议:简单处写函数、复杂处画图,可嵌套,见第 13 章。 |
十三、前端集成
| 术语 | 说明 |
|---|---|
| TTFT(Time To First Token,首字延迟) | 从发出请求到收到第一个 token 的时间。流式的价值就在它 —— 同样总耗时 1.9 秒,首帧可能 0.16 秒就出来了,体感完全不同。见第 6、14 章。 |
| SSE(Server-Sent Events) | 服务器单向持续推送文本事件的标准做法,帧格式形如 event: token\ndata: {...}\n\n。浏览器用 EventSource 消费。见第 14 章。 |
| 前后端事件协议 | 把图产生的 stream 事件(values / updates / messages / custom)整理成前端能照着写的 TypeScript 类型定义,作为前后端之间的契约。见第 14 章。 |
| 把审批传到前端 | 图 interrupt 停下后,把中断内容作为 HTTP 响应返回给前端 → 前端弹出审批卡片 → 用户点完,前端再 POST /resume 带 Command({ resume }) 续跑。见第 5、14 章。 |
useStream | 官方 @langchain/langgraph-sdk 提供的前端 hook,直接消费 LangGraph 的流式接口。见第 14 章。 |
| Agent Chat UI | 官方开源的通用聊天前端,能直接对接任意 LangGraph agent(JS / Python 都行),自己不用写界面。见第 14 章。 |
| 生成式 UI(Generative UI) | 让图返回"要渲染什么组件"的结构化描述,由前端动态渲染出卡片/表格/表单,而不是只有纯文本。官方示例仓库:langchain-ai/langgraphjs-gen-ui-examples。见第 14 章。 |
十四、易混淆概念对照
1. 节点(Node)vs 边(Edge)
- 节点是「干什么」——一个读状态、返回增量的函数。
- 边是「下一步去哪」——连接节点的关系。
- 记忆口诀:节点负责干活,边负责引路。一个节点可以没有任何业务逻辑(只转发),但一条边永远不干活。
2. 状态(State)vs 更新(Update)
- State 是完整的「共享表格」,节点读到的就是它。
- Update 是节点返回的增量对象,只包含想改的字段。
- 关键点:
return { greeting: ... }(增量)≠return { ...state, greeting: ... }(把整张表抄一遍,多余且有风险)。框架负责把 Update 合并进 State。
3. 两种 streamMode:values vs updates
values | updates | |
|---|---|---|
| 每次给你什么 | 完整状态快照 | 增量 { 节点名: 返回值 } |
| 产出条数(同一张图) | 初始 1 + 每步 1 | 只算节点执行 |
| 带节点名吗 | 不带 | 带 |
| 像什么 | 定时拍全景照 | 日志,每条标了来源 |
| 状态很大时 | 越来越重(每次传全量) | 更省 |
- 要「随时拿到最新完整状态」→
values;要「知道每一步是谁干的」→updates。
4. MemorySaver vs SqliteSaver(内存 vs 落盘)
MemorySaver | SqliteSaver | |
|---|---|---|
| 存在哪 | 进程内存 | .sqlite 文件 |
| 进程退出后 | 全部清零 | 仍在(跨进程 / 重启保留) |
| 适用 | 开发调试(零配置、跑得快) | 单机落盘、需要断点续跑 |
- 二者接口同为
BaseCheckpointSaver,换 saver 不用改图;生产多实例再换 Postgres 等更重的 saver。
5. 短期记忆 vs 长期记忆
| 短期记忆 | 长期记忆 | |
|---|---|---|
| 载体 | checkpointer | store |
| 作用域 | 单个 thread_id | 跨所有 thread |
| 存什么 | 整条线程的完整状态 | 自定义的 { namespace, key } → value |
| 像什么 | 聊天窗口的历史记录 | 用户档案 / 知识库 |
| 一句话 | 回答「这段对话聊到哪了」 | 回答「我关于这个用户知道什么」 |
- 记忆口诀:
thread_id管「哪段对话」,namespace管「哪个用户」。短期记忆跟着 thread 走,长期记忆跟着 namespace 走。
6. Supervisor vs Swarm
| Supervisor(有中心) | Swarm(无中心) | |
|---|---|---|
| 结构 | 一个总管 + 专家,专家干完回总管汇报 | 专家之间直接互相移交,没有总管 |
| 调用步数 / 成本 | 高(每轮都回中心) | 低(直达下一个专家) |
| 可控性 / 可审计 | 强(每步决策都经过总管) | 弱(专家自主移交) |
| 移交工具 | createSupervisor 自动生成 | 需自己用 createHandoffTool 造 |
| 适合 | 专家多、需管控 / 审计 | 专家少、追求最短移交链 |
- 默认从 Supervisor 开始(可控、可观测、好调试);确认成本是瓶颈、且专家间是「流水线接力」时再换 Swarm。
7. interrupt 与 Command.resume 的关系
interrupt(payload):把「一个问题」交出去 —— 图暂停,payload交给人类;invoke正常返回一个带__interrupt__的状态对象。new Command({ resume: 值 }):把「一个答案」送回来 —— 这个值会成为被中断节点里interrupt()的返回值。- 二者的关系:同一个暂停点的一进一出。中间隔着的可以是几秒,也可以是几天,因为状态存在 checkpointer 里,而连接它们的钥匙是
thread_id。 - 两个易错点:
- 恢复不是从
interrupt()那一行之后接着跑,而是把整个节点从头重跑一遍(这次interrupt()直接返回人类的值)——所以interrupt()之前的代码必须无副作用、可重复执行。 - 没有 checkpointer,
interrupt就无法工作。
- 恢复不是从
8. StateGraph vs entrypoint(画图 vs 写代码)
StateGraph(画图) | entrypoint(写代码) | |
|---|---|---|
| 表达方式 | 声明状态 → 注册节点 → 连边 → compile() | 直接写一个 async 函数 |
| 结构可见性 | 强,能 drawMermaid() 画出来 | 弱,结构藏在代码里 |
| 插入校验 / 审批 | 容易(加个节点就行) | 要自己在流程里插 |
| 直线流程的代码量 | 啰嗦(一堆样板) | 短 |
| 可并行子步骤 | 用并行节点 + reducer | Promise.all(task(...)),更直观 |
| ReAct 这类循环 | 天然擅长 | 要自己写 while,反而更长 |
- 口诀:图擅长「结构」,函数式擅长「自由」。 二者能混用(
entrypoint可以当节点挂进图),不必二选一。
9. 工作流 vs 智能体(Workflow vs Agent)
- 工作流:路径事先写死(谁在什么时候执行是确定的)。优:可预测、便宜、好调试、好测试。
- 智能体:路径由模型现场决定(包括"要不要再来一轮工具")。优:灵活,能处理事先说不清的任务;代价是不确定、更贵、更难测。
判断只有一句话:这件事的步骤,你现在能不能写出来?
- 能写出来 → 先做工作流。真遇到"某些输入确实需要额外步骤"时,只把那一小段放开给模型。
- 完全说不清 → 才上智能体;而且优先考虑"工作流骨架 + 局部智能体",而不是整个流程都交给模型自由发挥。
这也是第 9 章的结论:五种编排模式里,前四种都是「工作流」,只有编排者-工作者和评估者-优化者开始掺入自主决策 —— 而它们之所以可控,正是因为把自主权关在了节点内部。