[LangGraph] Functional API
在 langgraph 中,其实有两套写流程的方式:
- Graph API
- Functional API
Functional API 把工作流从画流程图变成写业务函数,但依然保留可恢复、可中断、可追踪的能力。
- 传统 Graph API 的思路:先设计一张图,再往图里塞节点
- Function API 的思路:先写业务逻辑函数,LangGraph 帮我托管执行状态
看起来像普通 async 函数,但背后具备:
- 可中断(interrupt)
- 可恢复(resume)
- 有 checkpoint
- 支持时间旅行
它存在的意义就是:降低建模成本,让你先把业务跑通,再逐步演进成更复杂的图。
核心模块
要使用 Functional API,需要了解构建工作流的两个核心模块:
- entrypoint:封装工作流逻辑并负责管理执行流程,包括处理长时间运行的任务和中断。
- task:表示一个离散的工作单元,例如一次 API 调用或数据处理步骤,可以在 entrypoint 中异步执行。一般用于有副作用、耗时或者外部调用的部分。
1. entrypoint
在 Functional API 中,entrypoint 是整个工作流的入口。
它的作用不是“执行一个函数”,而是将一个普通函数封装为一个可被 langgraph 管理的工作流,由 langgraph 负责执行过程中的状态保存、中断、恢复以及多次调用之间的衔接。
entrypoint 的基本使用规则
在定义 entrypoint 时,有几条非常重要的规则需要提前明确:
-
entrypoint 必须基于一个函数定义
entrypoint({ // 配置对象 }, async ()=>{}) -
该函数只能接收一个参数作为输入
官方文档原话:
An entrypoint is defined by calling the
entrypointfunction with configuration and a function.The function must accept a single positional argument, which serves as the workflow input. If you need to pass multiple pieces of data, use an object as the input type for the first argument. -
输入和输出必须是可 JSON 序列化的数据
-
如果需要传多个参数,应将它们封装为一个对象
-
实际使用中通常需要配合
checkpointer,否则无法启用恢复与记忆能力
这些规则的本质原因是:langgraph 需要将函数的输入、输出和执行状态持久化到检查点中。
定义一个 entrypoint
使用 entrypoint 函数即可创建一个工作流入口。
import { entrypoint } from "@langchain/langgraph";
const workflow = entrypoint(
// 配置对象
{ checkpointer, name: "workflow" },
async (input) => {
// 这里编写你的工作流逻辑
return result;
}
);
await workflow.invoke();
通过这种方式定义后,得到的并不是一个普通函数,而是一个 workflow 实例,后续所有的执行、恢复、流式处理,都是通过这个实例完成的。
entrypoint 的执行方式
entrypoint 返回的 workflow 实例,主要有两种执行方式。
invoke:一次性执行并返回结果
await workflow.invoke(input, config);
stream:流式执行
for await (const chunk of workflow.stream(input, config)) {
console.log(chunk);
}
这种方式会在执行过程中不断产出结果,适合:
entrypoint 与中断和恢复
当在执行过程中触发 interrupt 时:
- 工作流会立即暂停
- 当前状态会被写入检查点
- 执行权返回给调用方
恢复执行时,并不是“从中断的下一行继续执行”,而是:使用相同的 thread_id,再次调用 entrypoint,langgraph 会自动恢复到上一次的状态。
换句话说:恢复执行的时候,是重新执行整个 entrypoint 所对应的函数,不是从中断的开始。
通过使用 Command ,可以恢复中断,并且从外界将数据传递到中断处。
await workflow.invoke(
new Command({ resume: value }),
config
);
恢复执行本质上是一次新的调用(entrypoint的那个函数),通过 checkpoint 能够拿出中断前所执行的 task 的缓存结果。
在 entrypoint 中,中断前的副作用操作同样必须具备幂等性。
2. task
在 Functional API 中,task 用来表示工作流中的一个独立步骤。
你可以把 task 理解为:被 langgraph 接管执行的一小段业务逻辑,比如一次接口请求、一次数据库查询,或者一次明确的数据处理过程。
task特点
-
task 是一个独立的工作单元,它代表一件具体要做的事,比如:
-
调一次 API
-
处理一段数据
-
执行一次计算
每个 task 都是一个“原子步骤”,不关心整个流程,只关心自己这一步。
-
-
task 是异步执行的,task 不会阻塞主流程:
-
可以并发跑多个 task
-
某个 Task 在等网络、等 I/O 时,不会卡住整个系统
本质上就是:适合长耗时、I/O 型操作
-
-
task 会自动做检查点,Task 执行完成后:
-
结果会被保存到 checkpoint 检查点
-
如果流程中断、崩溃或被暂停
-
下次可以从这个 task 执行完的地方继续,而不是从头来
-
定义 task 的方式也非常简单,只需要使用 task 函数包裹一个普通函数即可。被包装后的函数不再是“随便调用的工具函数”,而是一个只能在工作流内部运行的受管执行单元。
import { task } from "@langchain/langgraph";
const slowComputation = task("slowComputation", async (inputValue: any) => {
// 模拟一个长时间运行的操作
return result;
});
task 只能在三种地方被调用:
- entrypoint 内部
- 另一个 task 内部
- StateGraph 的节点中
它不能直接在主应用代码中执行,这是因为 task 的执行依赖于 LangGraph 的上下文与检查点机制。
在使用时,调用 task 会返回一个 Promise,可以像调用普通异步函数一样使用 await 获取结果。
const myWorkflow = entrypoint(
{ checkpointer, name: "workflow" },
async (someInput: number): Promise<number> => {
return await slowComputation(someInput);
}
);
但与普通异步函数不同的是,task 的执行过程和结果都会被 langgraph 纳入工作流管理体系中,这是它存在的核心价值。
import "dotenv/config";
import {
MemorySaver,
entrypoint,
task,
interrupt,
Command,
} from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import readline from "readline-sync";
const model = new ChatOpenAI({
model: "gpt-4o-mini",
temperature: 0.7,
});
// 1. 定义一个任务
const writeEssay = task("writeEssay", async (topic: string) => {
console.log(`\n正在生成关于 "${topic}" 的文章...\n`);
// 通过模型生成文章
const stream = await model.stream([
new HumanMessage(
`请写一篇50字左右的,关于以下主题的短文:${topic}。请用中文回答。`,
),
]);
// 将文章的内容显示出来
let content = ""; // 拼接文章内容
// 流式输出
for await (const chunk of stream) {
process.stdout.write(chunk.content as string);
content += chunk.content as string;
}
process.stdout.write("\n\n");
return content; // 对外返回最终的内容
});
const checkpointer = new MemorySaver();
// 2. 定义一个工作流
const workflow = entrypoint(
{
name: "writeEssayWorkflow",
checkpointer,
},
async (topic: string) => {
let currentTopic = topic;
while (true) {
// 因为我们需要不断的让用户确认,生成的文章是否符合要求
// 2-1. 执行任务
const essay = await writeEssay(currentTopic); // 当这个任务执行完了之后,结果就会缓存到 checkpoint 里面
// 2-2. 中断,代码回到外界,让用户决定是否通过审核
const { approved, newTopic } = interrupt({
// 传递给外界的
essay, // 生成的文章
action: "请审核该文章", // 给外界提供的一个提示语句
});
if (approved) {
// 审核通过了,返回最终结果
return {
essay,
approved,
};
}
// 代码来到这儿,说明审核没通过
// 更新主题
if (newTopic) currentTopic = newTopic;
}
},
);
const config = {
configurable: {
thread_id: "generate_essay",
},
};
async function main() {
console.log("--- 启动工作流 ---");
// 让用户输入主题
const topic = readline.question("请输入文章主题:");
// 启动工作流
await workflow.invoke(topic, config);
const finalState = await handleHumanReview();
console.log("\n--- 工作流结束 ---");
console.log("最终结果:", finalState.values);
}
// 该方法专门是用于处理中断相关的逻辑的
async function handleHumanReview() {
while (true) {
// 拿到最新的状态
const state = await workflow.getState(config);
// console.log("state>>>", state);
// 从这个状态上面获取中断
const interruptTask = state.tasks?.find((t) => t.interrupts?.length > 0);
// console.log("interruptTask>>>>", interruptTask);
if (!interruptTask) return state;
// 代码来到这里,说明需要处理中断
// console.log(
// "interruptTask.interrupts[0].value>>>",
// interruptTask.interrupts[0].value
// );
const { action } = interruptTask.interrupts[0].value;
console.log("\n!!! 收到中断请求 !!!");
console.log("操作提示:", action);
// 再次询问用户是否审批通过这篇文章
const answer = readline.question("\n是否批准这篇文章?(yes/no): ");
// 根据用户的输入决定返回给中断的值
// approved 是一个布尔值
const approved = ["yes", "y", "ok", "批准"].includes(
answer.trim().toLowerCase(),
);
let newTopic;
if (!approved) {
// 如果进入此分支,说明审核不通过,需要外界的用户输入新的主题
newTopic = readline.question("审核不通过,请输入新的文章主题:");
}
// 代码来到这里,需要恢复中断
console.log(
`\n--- 恢复工作流 (用户输入: ${approved ? "批准" : "重写"}) ---`,
);
// 如何恢复工作流?
await workflow.invoke(
new Command({
resume: {
approved, // 审批是否通过
newTopic, // 可能是undefined、也可能是新主题
},
}),
config,
);
}
}
main();
核心区别
下面是两者之间的一些关键区别:
1. 控制流层面
Functional API 不要求你先“画图”。你就按正常的思路写函数,顺序执行、条件判断都用原生语法即可,更像在写普通业务代码,代码量也更少。
2. 短期记忆管理
- Graph API 中,状态是“全局共享的”,需要你提前声明 State,甚至还要写 reducer 来控制状态怎么合并。
- 而 Functional API 中,state 只存在于当前函数调用上下文,函数之间不共享,不用你手动管状态结构,理解成本更低。
Function API 根本“没有全局 state 这个概念”
在 Function API 里:
- 没有
StateSchema - 没有
state对象在 task 之间自动流转 - 没有 reducer
写的 entrypoint,本质上就是一个普通的 async 函数:
const workflow = entrypoint(
{ checkpointer: new MemorySaver(), name: "wf" },
async (input) => {
// 这里的一切,都是“函数内部的局部变量”
}
);
Function API 的 “state” ≈ 普通函数的局部变量
看一个最直观的例子:
const step1 = task("step1", async (x: number) => x + 1);
const step2 = task("step2", async (x: number) => x * 2);
const workflow = entrypoint(
{ checkpointer: new MemorySaver(), name: "wf" },
async (input: number) => {
const a = await step1(input); // a 是局部变量
const b = await step2(a); // b 也是局部变量
// 两个任务的执行结果,会缓存到checkpoint里面
return b;
}
);
这里的 a、b 就是 Function API 里的“短期记忆”,它们:
- 只存在于这一次
entrypoint执行过程中 - 不会自动暴露给别的 workflow
- 不会在 entrypoint 之间共享
一旦这次调用结束:所有这些“state”都会自然消失
那中断 / 恢复时,这些“state”去哪了?
你可能会问:a、b 这些变量在 interrupt 之后还能用吗?
答案是:不能直接用,但可以“被重算出来”。
看这个例子:
const workflow = entrypoint(
{ checkpointer: new MemorySaver(), name: "wf" },
async (input: number) => {
const a = await step1(input);
const b = await step2(a);
const ok = interrupt({ b });
return b;
}
);
发生 interrupt 后,JS 层面的 a、b 都没了。下次恢复执行时:
- entrypoint 从头执行
a = await step1(input)再算一次- 但如果
step1有 checkpoint,那就直接取缓存结果,如果没有才真正执行
所以 Function API 的“state 持久性”不是靠 state 对象,而是靠 task 的结果缓存。
3. 检查点机制
两种 API 都支持检查点。
- Graph API 的特点是:每走完一个 superstep,就会生成一个新的检查点。也就是说,在Graph API中,是有多个检查点的。
- Functional API 则不同:task 的执行结果会写回到 entrypoint 对应的检查点中,而不是不断生成新的检查点,整体更“轻量”。
Function API 里,checkpoint 是 entrypoint 执行状态的快照,每当某个 task 成功完成或发生 interrupt,langgraph 会更新这一个 checkpoint,把「已完成的 task 结果」写进去,注意是“更新”,不是“新增”。
checkpoint 里实际存的是什么?
在 Function API 的 checkpoint 中,核心存的是三类信息:
- entrypoint 的输入
- 已经完成的 task 及其结果
- interrupt 的 payload(如果有)
它不关心:
- JS 执行到哪一行
- 中间变量的值
- 调用栈状态
所以 checkpoint 的语义是:“entrypoint 重跑时,哪些 task 可以直接复用结果”
恢复执行时,两种 API 的行为差异
Graph API 恢复时:
- 从 checkpoint 记录的 node 位置
- 继续向后调度节点
- 不会重新走已经完成的节点
恢复像是:流程接着走
Function API 恢复时:
- 整个 entrypoint 从第一行重新执行
- 但遇到已经有 checkpoint 结果的 task,直接“命中缓存”,不再真正执行
- 只执行还没有结果的 task
恢复像是:流程重放 + task 级缓存
4. 可视化能力
- Graph API 天然就是“图”,可以直接可视化流程,非常适合分析复杂结构。
- Functional API 的流程是在运行时动态展开的,本身不提供图形化展示,更偏向执行而非展示。

浙公网安备 33010602011771号