阅读本文您将获得:
- 函数式API的基础构成
- 入口点@entrypoint的定义及执行
- 任务@task的定义及执行
- 函数式API的使用约束
- 函数式API与图API的核心差异
一、函数式API概述
函数式API(Functional API)允许你在对现有代码改动最小的前提下,为应用添加LangGraph的核心功能:持久化(persistence)、记忆(memory)、人机协同(human-in-the-loop)和流式处理(streaming)。 该API的设计目标是将这些功能集成到现有代码中,即便这些代码可能使用 if 语句、for 循环、函数调用等标准编程语言原语来实现分支和控制流。与许多数据编排框架不同(这类框架要求将代码重构为显式的流水线或有向无环图(DAG)),函数式API无需强制采用固定的执行模型,就能让你融入上述能力。
函数式API基于两个核心构件实现智能体构建:
- @entrypoint:将函数标记为工作流的起点,封装业务逻辑并管理执行流,包括处理长时间运行的任务和中断(interrupt)。
- @task:代表一个独立的工作单元(例如一次 API 调用或一个数据处理步骤),可在入口点(entrypoint)内异步执行。任务会返回一个类future对象,该对象支持异步等待或同步解析。
通过这种方式,函数式API为构建具备状态管理和流式处理能力的工作流提供了极简的抽象层。
一个简单的示例:
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.func import entrypoint, task
from langgraph.types import interrupt
@task
def write_essay(topic: str) -> str:
"""Write an essay about the given topic."""
time.sleep(1) # A placeholder for a long-running task.
return f"An essay about topic: {topic}"
@entrypoint(checkpointer=InMemorySaver())
def workflow(topic: str) -> dict:
"""A simple workflow that writes an essay and asks for a review."""
essay = write_essay("cat").result()
is_approved = interrupt({
# Any json-serializable payload provided to interrupt as argument.
# It will be surfaced on the client side as an Interrupt when streaming data
# from the workflow.
"essay": essay, # The essay we want reviewed.
# We can add any additional information that we need.
# For example, introduce a key called "action" with some instructions.
"action": "Please approve/reject the essay",
})
return {
"essay": essay, # The essay that was generated
"is_approved": is_approved, # Response from HIL
}
该示例展示了一个基于函数式API实现的简单工作流,可撰写文章,并会中断执行以请求人工审核。 其中函数write_essay使用@task装饰器装饰,实现基于给定主题的文章写作功能;函数workflow使用@entrypoint装饰,实现文章写作和人工审核的流程编排。接下来我们详细介绍一下Functional API的这两个基础构件。
二、入口点(Entrypoint)
@entrypoint装饰器可用于从一个函数创建工作流。它会封装工作流逻辑并管理执行流程,包括处理长时间运行的任务和中断。
2.1 定义方式
入口点通过给函数添加 @entrypoint 装饰器来定义。
该函数必须接收一个位置参数,该参数用作工作流的输入。如果需要传递多个数据,可将第一个参数的输入类型设为字典。
给函数添加 @entrypoint 装饰器后,会生成一个Pregel实例 —— 该实例用于帮助管理工作流的执行(例如,处理流式传输、恢复执行和检查点相关操作)。
通常,需要向 @entrypoint 装饰器传入一个检查点工具(checkpointer),以启用持久化功能,并使用人机协同(human-in-the-loop)等特性。
from langgraph.func import entrypoint
@entrypoint(checkpointer=checkpointer)
def my_workflow(some_input: dict) -> int:
# some logic that may involve long-running tasks like API calls,
# and may be interrupted for human-in-the-loop.
...
return result
2.2 可注入参数
声明入口点(entrypoint)时,可以访问将在运行时自动注入的额外参数。这些参数包括:
| 参数(Parameter) | 说明(Description) |
|---|---|
| previous | 访问与指定线程(thread)的上一个检查点(checkpoint)相关联的状态。 |
| store | [BaseStore][langgraph.store.base.BaseStore] 的实例,适用于长期记忆(long-term memory)场景。 |
| writer | 在使用 Python 3.11 之前的异步(Async)版本时,用于访问 StreamWriter。 |
| config | 用于访问运行时配置(run time configuration)。 |
2.3 执行
使用 @entrypoint 装饰器会生成一个 Pregel 对象,该对象可通过 invoke、ainvoke、stream 和 astream 方法执行。
config = {
"configurable": {
"thread_id": "some_thread_id"
}
}
my_workflow.invoke(some_input, config) # Wait for the result synchronously
'''
#stream output
for chunk in my_workflow.stream(some_input, config):
print(chunk)
'''
2.4 恢复执行
在执行被中断后恢复执行,可通过向Command基础组件传递恢复值来实现。
from langgraph.types import Command
config = {
"configurable": {
"thread_id": "some_thread_id"
}
}
my_workflow.invoke(Command(resume=some_resume_value), config)
错误后的恢复执行
若要在发生错误后恢复执行,需使用 None 作为参数,并搭配相同的线程ID(config 中配置)来运行入口点。此操作的前提是:底层错误已解决,且执行流程能够成功继续。
config = {
"configurable": {
"thread_id": "some_thread_id"
}
}
my_workflow.invoke(None, config)
2.5 短期记忆
当入口点(entrypoint)通过检查点工具(checkpointer)定义时,它会将同一线程ID下连续调用之间的信息存储在检查点(checkpoint)中。 这使得我们可以通过 previous 参数访问上一次调用的状态。 默认情况下,previous 参数的值就是上一次调用的返回值。
@entrypoint(checkpointer=checkpointer)
def my_workflow(number: int, *, previous: Any = None) -> int:
previous = previous or 0
return number + previous
config = {
"configurable": {
"thread_id": "some_thread_id"
}
}
my_workflow.invoke(1, config) # 1 (previous was None)
my_workflow.invoke(2, config) # 3 (previous was 1 from the previous invocation)
entrypoint.final 是一种特殊组件,可从入口点返回,它能将检查点(checkpoint)中保存的值与入口点的返回值解耦。 其中,第一个值是入口点的返回值,第二个值是将保存到检查点中的值。其类型标注格式为 entrypoint.final[return_type, save_type](return_type 表示入口点返回值类型,save_type 表示检查点保存值类型)。
@entrypoint(checkpointer=checkpointer)
def my_workflow(number: int, *, previous: Any = None) -> entrypoint.final[int, int]:
previous = previous or 0
# This will return the previous value to the caller, saving
# 2 * number to the checkpoint, which will be used in the next invocation
# for the `previous` parameter.
return entrypoint.final(value=previous, save=2 * number)
config = {
"configurable": {
"thread_id": "1"
}
}
my_workflow.invoke(3, config) # 0 (previous was None)
my_workflow.invoke(1, config) # 6 (previous was 3 * 2 from the previous invocation)
三、任务(Task)
任务(Task)代表一个独立的工作单元,可以是一次API调用或一个数据处理步骤。它具有两个核心特性:
- 异步执行(Asynchronous Execution):任务设计为异步执行模式,支持多个操作并发运行且不会造成阻塞。
- 检查点记录(Checkpointing):任务结果会保存到检查点(checkpoint)中,使得工作流能够从最近一次保存的状态恢复执行。
3.1 定义方式
任务(Task)通过 @task 装饰器定义,该装饰器会对一个普通的 Python 函数进行包装。
from langgraph.func import task
@task()
def slow_computation(input_value):
# Simulate a long-running operation
...
return result
3.2 执行
任务(Task)仅能从以下位置调用:
- 入口点(entrypoint)内部
- 另一个任务(task)内部
- 状态图节点(state graph node)内部
任务无法从主应用代码中直接调用。
调用任务时,它会立即返回一个 future 对象。future 对象是一个 “占位符”,用于指代后续才会生成的结果。若要获取任务的结果,有两种方式:
- 同步等待(使用 result() 方法)
- 异步等待(使用 await 关键字)
# 同步方式获取任务结果
@entrypoint(checkpointer=checkpointer)
def my_workflow(some_input: int) -> int:
future = slow_computation(some_input)
return future.result() # Wait for the result synchronously
# 异步方式获取任务结果
@entrypoint(checkpointer=checkpointer)
async def my_workflow(some_input: int) -> int:
return await slow_computation(some_input) # Await result asynchronously
3.3 何时使用任务
任务(Task)在以下场景中十分实用:
- 检查点记录(Checkpointing):当你需要将长时间运行操作的结果保存到检查点(checkpoint)时,使用任务可避免在工作流恢复执行时重新计算该结果。
- 人机协同(Human-in-the-loop):若你构建的工作流需要人工干预,则必须使用任务来封装所有随机性操作(例如 API 调用),以确保工作流能正确恢复执行。
- 并行执行(Parallel Execution):对于 I/O 密集型任务,任务支持并行执行,可让多个操作并发运行且不会造成阻塞(例如调用多个 API)。
- 可观测性(Observability):将操作包装在任务中,可通过 LangSmith 追踪工作流进度,并监控单个操作的执行情况。
- 可重试工作(Retryable Work):当某项工作需要通过重试来处理失败或数据不一致问题时,任务可提供一种封装和管理重试逻辑的方式。
四、使用函数式API的约束
4.1 序列化(Serialization)
在使用LangGraph Functional API时,序列化包含两个核心要点:
- 入口点(entrypoint)的输入和输出必须可 JSON 序列化。
- 任务(task)的输出必须可 JSON 序列化。
这些要求是实现检查点记录(checkpointing)和工作流恢复执行的必要条件。请使用字典(dict)、列表(list)、字符串(string)、数字(number)和布尔值(boolean)等 Python 原生数据类型,确保你的输入和输出可被序列化。 通过序列化,工作流状态(如任务结果、中间值等)能够被可靠地保存和恢复。这对于实现人机协同(human-in-the-loop)交互、容错(fault tolerance)以及并行执行(parallel execution)至关重要。
若工作流已配置检查点工具(checkpointer),提供不可序列化的输入或输出会导致运行时错误。
4.2 确定性(Determinism)
所有随机性操作都应封装在任务(task)内部。这能确保当执行被暂停(例如,为了进行人机协同交互)并随后恢复时,工作流仍会遵循相同的步骤序列 —— 即便任务结果具有非确定性(non-deterministic)。 (task) LangGraph 通过在任务(task)和子图(subgraph)执行过程中持久化其结果,实现了上述特性。一个设计良好的工作流会确保恢复执行时遵循相同的步骤序列,从而能够正确获取之前计算的结果,无需重新执行。这对于长时间运行的任务或结果具有非确定性的任务尤为实用,因为它可避免重复已完成的工作,并能从与中断前基本一致的状态恢复执行。 尽管同一工作流的不同运行实例可能产生不同结果,但恢复某个特定运行实例时,始终应遵循已记录的相同步骤序列。这使得 LangGraph 能够高效查询在图(graph)中断前已执行的任务和子图结果,避免重新计算。
4.3 幂等性(Idempotency)
幂等性可确保同一操作多次执行后产生相同结果。若某个步骤因故障需重新运行,这一特性有助于防止重复的 API 调用和冗余处理。 请始终将 API 调用置于任务(task)函数内部以实现检查点记录(checkpointing),并在设计时确保这些 API 调用具备幂等性,以防出现重新执行的情况。当任务已启动但未成功完成时,就可能发生重新执行;此时若工作流恢复运行,该任务会再次执行。可使用幂等键(idempotency keys)或验证现有结果的方式,避免重复操作。
五、Functional API与Graph API的差异
对于更偏好声明式方法的用户,LangGraph的Graph API允许你使用图(Graph)范式定义工作流。两种 API 共享相同的底层运行时,因此你可以在同一应用中同时使用它们。
两者的核心差异在于:
- 控制流(Control Flow):函数式 API 无需考虑图结构,你可以使用标准的 Python 语法结构(如 if、for 等)定义工作流。这通常能减少你需要编写的代码量。
- 短期记忆(Short-term Memory):图 API(Graph API)需要声明一个状态(State),且可能需要定义归约函数(reducers)来管理图状态的更新。而 @entrypoint 和 @task 无需显式的状态管理 —— 它们的状态作用域仅限于自身函数,不会在多个函数间共享。
- 检查点(Checkpointing):两种 API 都会生成并使用检查点。在图 API 中,每完成一个超步(superstep)就会生成一个新的检查点;在函数式 API 中,执行任务时,任务结果会保存到与指定入口点(entrypoint)关联的现有检查点中,而非创建新的检查点。
- 可视化(Visualization):图 API 能轻松将工作流可视化为图结构,这对调试、理解工作流以及与他人共享都很有帮助。函数式 API 不支持可视化,因为其图结构是在运行时动态生成的。