学习笔记 · Obsidian
LangGraph TypeScript 基础、Pregel 运行时与 API 选择
核心定位
LangGraph 是面向长时间运行、有状态工作流与 agent 的低层编排框架和运行时。它解决的是状态、分支、循环、并行、持久化、恢复、流式输出与人工暂停,而不是替开发者决定提示词、模型或固定 agent 架构。
它适合把确定性业务规则和不确定的模型决策放进同一个可恢复流程:
- 权限、金额阈值、审批顺序和审计步骤由代码控制;
- 分类、规划、工具选择和内容生成可以由模型决定;
- checkpoint、thread、interrupt 和 stream 作为运行时能力,而不是应用层临时补丁;
- 节点边界让错误、重试、观测和测试有明确归属。
LangGraph 不要求使用 LangChain。若需求只是常规“模型调用工具直到回答”,优先使用 LangChain 的 createAgent;当需要自定义状态机、复杂恢复、动态并发或严格业务控制时,再直接使用 LangGraph。
路由与版本边界
/oss/javascript/langgraph是可点击入口,但会以 307 跳转到/oss/javascript/langgraph/overview;两条路由属于同一份正文知识。- 官方安装页给出的最小包是
@langchain/langgraph与@langchain/core,模型提供商包需要另装。 - 文档当前以 v1 系列 API 为主;2026-01 的 v1.1 更新引入推荐的 StateSchema 与 Standard Schema 支持。
- 2026-08-11 的 npm registry 快照为:
@langchain/langgraph 1.4.9、langchain 1.5.5、@langchain/core 1.2.5;前端@langchain/react与@langchain/angular为 1.0.30。它们是核验日快照,不是永久版本建议,项目仍应使用 lockfile 和兼容测试。 - 官方安装页没有声明统一 Node.js 最低版本。核验时 npm 元数据中,独立
@langchain/langgraph包要求 Node 18+;当前langchain与@langchain/core组合要求 Node 20+。因此项目基线应按实际依赖树的最高要求锁定,而不是把 Node 18 当作所有示例的通用保证。 - 图定义、checkpoint adapter 和大部分服务端执行属于 Node/服务端边界;浏览器应通过 Agent Server、SDK 或前端包消费协议,不应把数据库凭据、checkpointer 或模型密钥打包进客户端。
用 LangGraph 思考
1. 从真实流程出发
先把待自动化过程拆为可验证步骤,再决定哪些步骤需要 LLM。常见节点类型包括:
- 模型节点:分类、推理、生成、结构化判断;
- 数据节点:搜索、数据库读取、资料加载;
- 动作节点:写库、发邮件、创建工单;
- 人工节点:审批、补充输入、纠正结果。
每个节点都应明确输入、输出、失败模式、重试条件以及副作用是否幂等。
2. State 保存原始业务事实
State 应存后续节点需要且无法廉价重建的数据,不应存拼好的 prompt。提示词在调用模型时临时格式化。这样可避免提示词改版破坏旧 checkpoint,也便于调试和迁移。
3. 按错误责任拆节点
| 错误 | 责任方 | 典型处理 |
|---|---|---|
| 网络抖动、限流、暂时性 5xx | 系统 | 有界重试、退避、超时 |
| 工具参数或输出可由模型修正 | 模型 | 把错误反馈给模型重新规划 |
| 缺少授权、审批或业务信息 | 用户 | interrupt 暂停 |
| 重试耗尽但可补偿 | 工作流 | errorHandler 路由到补偿节点 |
| 未知程序错误 | 开发者 | 向外抛出并保留 trace/checkpoint |
节点越小,恢复重复的工作越少;但过度拆分会增加 State 接口和图复杂度。不同外部系统、不同重试策略、独立审计或独立人工审批通常值得拆开。
Graph API 与 Functional API
两套 API 都编译到 Pregel,均可使用持久化、流式、memory 和 HITL,也可以互相调用。选择依据是控制流的表达方式,不是能力高低。
| 维度 | Graph API | Functional API |
|---|---|---|
| 控制流 | 显式 node、edge、shared State | 普通 TypeScript if、for、Promise |
| 状态 | schema、reducer、channel | 函数局部值与 previous |
| 并行 | fan-out、Send、reducer | Promise.all 等原生并发 |
| 可视化 | 静态结构清晰 | 动态控制流难以完整静态展示 |
| 团队边界 | 节点接口适合多人协作 | 适合线性流程与既有过程式代码 |
| 迁移成本 | 需要显式图建模 | 对原函数改动较小 |
优先 Graph API 的场景:
- 分支、循环和并行汇合较多;
- 多组件共享显式 State;
- 需要按节点配置重试、超时、缓存和补偿;
- 图结构本身要用于审计、展示或团队协作。
优先 Functional API 的场景:
- 给现有 TypeScript 流程增加 durable execution;
- 流程线性,局部状态自然存在函数作用域;
- 希望使用原生 Promise、条件语句和循环;
- 快速原型暂时不需要完整图可视化。
TypeScript Functional API 的真实形态
JavaScript/TypeScript 并没有 Python 装饰器语法。官方正文偶尔沿用 “decorator” 或 @task、@entrypoint 的 Python 表述,但实际 API 是函数工厂:
const fetchData = task("fetchData", async (input) => { ... });
const workflow = entrypoint( { name: "workflow", checkpointer }, async (input) => { ... }, );
需要掌握的执行契约:
- entrypoint 只接受一个输入参数;多字段应包装为对象。
- entrypoint 和 task 的输入输出必须可序列化,优先使用 JSON 兼容值。
- task 只能从 entrypoint、另一个 task 或 StateGraph node 中调用,不能在任意应用代码中期待其 checkpoint 语义。
- task 结果写入所属 entrypoint 的 checkpoint;恢复时 entrypoint 从函数开头 replay,但已完成 task 的结果会复用。
- 时间、随机数、网络调用、数据库写入等非确定性或副作用工作必须封装为 task。
- task 调用顺序与 interrupt 调用顺序是 replay 协议的一部分;在有未完成运行时重排会使旧结果与新位置错配。
- Promise.all 可并行启动多个 task;外部服务并发上限仍要由应用控制。
entrypoint.final({ value, save })可把本次返回值与保存给下一轮的 previous 值分开。
Functional API 的 durable execution 不是恢复 JavaScript 调用栈。恢复会重新执行入口函数,依靠已 checkpoint 的 task 结果跳过已完成工作。因此 task 外的日志、计数器、随机值和写操作都会再发生。
Pregel 运行时
StateGraph.compile 与 entrypoint 最终都生成 Pregel。Pregel 使用 Bulk Synchronous Parallel 模型,每个 superstep 有三个阶段:
- Plan:根据当前 channel 值选择本轮 actor;
- Execution:本轮 actor 并行执行;同一轮的写入彼此不可见;
- Update:把写入提交到 channel,再决定下一轮。
没有 actor 被选中或达到限制时结束。
这一模型直接解释了几个生产行为:
- 同一 superstep 的两个节点不能依赖彼此刚写入的值;
- 并行分支写同一个普通 State key 会冲突,需要 reducer;
- retry 或 interrupt 会从节点/入口开头重放,不能假设语言栈原地恢复;
- checkpoint 通常在 superstep 边界形成,而不是每行代码形成。
常见 channel:
- LastValue:每轮保留一个值,适合普通覆盖型 State;
- Topic:发布/订阅式多值 channel,可用于累积事件;
- BinaryOperatorAggregate:用二元操作聚合多次写入;
- EphemeralValue:低层临时值,只对下一步有效。
应用层通常不直接使用 Pregel;只有在需要自定义 actor/channel、理解调度细节或诊断并发写入时才下沉。
Quickstart 的两种 agent 结构
官方计算器示例分别展示 Graph 与 Functional 实现,其共同循环是:
- 模型读取 messages;
- 若生成 tool_calls,则执行工具;
- ToolMessage 追加回 messages;
- 再次调用模型,直到没有工具调用。
Graph 版本使用 MessagesValue、模型节点、ToolNode 和 conditional edge;Functional 版本使用 task、entrypoint 与 while 循环。前者结构可见,后者接近普通 TypeScript。
无论哪种实现,都必须确保每个 AI tool call 有匹配的 ToolMessage,否则会形成无效聊天历史。
Python 与 TypeScript 的主要差异
| 主题 | TypeScript | Python |
|---|---|---|
| Functional 声明 | task/entrypoint 函数工厂 | 装饰器 |
| 并发 | Promise.all、async iterator | asyncio.gather、future |
| State 类型 | StateSchema + Standard Schema,编译期推断 | TypedDict、dataclass、Pydantic 等 |
| schema 生态 | Zod v3/v4、Valibot、ArkType 等 | Python 类型与 Pydantic |
| stream 消费 | AsyncIterable、for await | async for / sync iterator |
| 前端 | 同语言 SDK 可共享类型 | 通常通过生成 schema 或远程类型连接 |
| 包管理 | npm/pnpm/yarn/bun,多包版本需对齐 | pip/uv/poetry |
Python 文档中的 snake_case 参数、True/False、None、装饰器示例不能直接抄到 TypeScript。TypeScript 实现应以当前类型定义、reference 和编译器为准。
选型清单
- 先决定是否真的需要 LangGraph;普通工具 agent 优先 LangChain。
- 根据实际依赖树锁定 Node 版本,当前全栈组合建议至少 Node 20。
- 复杂共享 State、fan-out 或可视化选 Graph API;线性 durable function 选 Functional API。
- 把所有副作用和非确定性工作放到可 checkpoint 的 task 或幂等节点。
- 对循环设置业务终止条件,并保留 recursionLimit 作为安全网。
- 浏览器只消费受认证的 Agent Server/SDK,不持有模型和持久化凭据。
- 升级前同时看 changelog、migration guide、类型检查和已有 checkpoint 兼容性。