if 语句、for 循环和函数调用)的现有代码中。与许多需要将代码重构为显式管道或 DAG 的数据编排框架不同,函数式 API 允许您在不强制执行严格执行模型的情况下,将这些功能集成到应用程序中。
函数式 API 使用两个关键构建块:
:::python
@entrypoint:将函数标记为工作流的起点,封装逻辑并管理执行流,包括处理长时间运行的任务和中断。- @[
@task]:表示一个离散的工作单元,例如 API 调用或数据处理步骤,可以在入口点内异步执行。任务返回一个类似未来的对象,可以异步等待或同步解析。 :::
entrypoint:一个入口点封装工作流逻辑并管理执行流,包括处理长时间运行的任务和中断。task:表示一个离散的工作单元,例如 API 调用或数据处理步骤,可以在入口点内异步执行。任务返回一个类似未来的对象,可以异步等待或同步解析。 :::
函数式 API 与图 API
对于更喜欢更声明式方法的用户,LangGraph 的 图 API 允许您使用图范式定义工作流。这两个 API 共享相同的底层运行时,因此您可以在同一应用程序中使用它们。 以下是一些关键差异:- 控制流:函数式 API 不需要考虑图结构。您可以使用标准的 Python 构造来定义工作流。这将通常会减少您需要编写的代码量。
- 短期记忆:图 API 需要声明一个 状态,并且可能需要定义 归约器 来管理图状态的更新。
@entrypoint和@tasks不需要显式状态管理,因为它们的状态仅限于函数内部,并且不会在函数之间共享。 - 检查点:这两个 API 都会生成和使用检查点。在 图 API 中,每个 超级步骤 后都会生成一个新的检查点。在 函数式 API 中,当任务执行时,其结果会保存到与给定入口点关联的现有检查点中,而不是创建新的检查点。
- 可视化:图 API 可以轻松地将工作流可视化为图,这对于调试、理解工作流以及与他人分享非常有用。函数式 API 不支持可视化,因为图是在运行时动态生成的。
示例
下面我们演示了一个简单的应用程序,该应用程序编写一篇论文并 中断 以请求人工审核。 :::pythonDetailed Explanation
Detailed Explanation
此工作流将编写关于“猫”主题的论文,然后暂停以获取人工审核。该工作流可以无限期地中断,直到提供审核。当工作流恢复执行时,它将从最开始执行,但由于 一篇论文已经编写完成,准备进行审核。一旦提供审核,我们可以恢复工作流:工作流已完成,审核已添加到论文中。
::::::js一篇论文已经编写完成,准备进行审核。一旦提供审核,我们可以恢复工作流:工作流已完成,审核已添加到论文中。
:::
writeEssay 任务的结果已经保存,任务结果将从检查点加载,而不是重新计算。:::python入口点
:::python 可以使用 @[@entrypoint] 装饰器从一个函数创建工作流。它封装工作流逻辑并管理执行流,包括处理 长时间运行的任务 和 中断。
:::
:::js
可以使用 @[entrypoint][entrypoint] 函数从一个函数创建工作流。它封装工作流逻辑并管理执行流,包括处理 长时间运行的任务 和 中断。
:::
定义
:::python 一个 入口点 是通过使用@entrypoint 装饰器来定义的。
函数 必须接受一个位置参数,该参数用作工作流输入。如果您需要传递多个数据,请使用字典作为第一个参数的输入类型。
使用 entrypoint 装饰函数将生成一个 @[Pregel][Pregel.stream] 实例,它有助于管理工作流的执行(例如,处理流式输出、恢复和检查点)。
通常,您需要将一个 检查点器 传递给 @entrypoint 装饰器,以启用持久化并使用 人机协作 等功能。
- Sync
- Async
entrypoint 函数并传入配置和一个函数来定义的。
该函数 必须接受一个位置参数,该参数用作工作流输入。如果你需要传递多份数据,请将第一个参数的类型设置为对象。
使用函数创建 entrypoint 会产生一个工作流实例,该实例有助于管理工作流的执行(例如,处理流式输出、恢复和检查点)。
你通常需要将 checkpointer 传递给 entrypoint 函数以启用持久化并使用 人机交互 等功能。
可注入参数
在声明一个entrypoint 时,你可以请求访问在运行时自动注入的附加参数。这些参数包括:
Requesting Injectable Parameters
Requesting Injectable Parameters
执行
:::python 使用@entrypoint 会生成一个 @[Pregel][Pregel.stream] 对象,可以使用 invoke、ainvoke、stream 和 astream 方法执行。
- Invoke
- Async Invoke
- Stream
- Async Stream
entrypoint 函数将返回一个可以使用 invoke 和 stream 方法执行的对象。
- Invoke
- Stream
恢复
:::python 在 @[interrupt][interrupt] 后恢复执行可以通过向 @[Command] 原语传递一个 resume 值来实现。
- Invoke
- Async Invoke
- Stream
- Async Stream
Command]原语可以恢复执行。
- Invoke
- Stream
entrypoint并使用None和相同的thread id(config)。
这假设底层的错误已解决,执行可以成功继续。
- Invoke
- Async Invoke
- Stream
- Async Stream
entrypoint并使用null和相同的thread id(config)。
这假设底层的错误已解决,执行可以成功继续。
- Invoke
- Stream
短期记忆
当entrypoint使用checkpointer定义时,它会在同一thread id的连续调用之间存储信息在检查点。
:::python
这允许使用previous参数访问前一次调用的状态。
默认情况下,previous参数是前一次调用的返回值。
getPreviousState函数访问前一次调用的状态。
默认情况下,getPreviousState函数返回前一次调用的返回值。
entrypoint.final
:::python
@[entrypoint.final][entrypoint.final]是一个特殊原语,可以从入口点返回并允许解耦存储在检查点中的值与入口点的返回值。
第一个值是入口点的返回值,第二个值是将在检查点中保存的值。类型注解是entrypoint.final[return_type, save_type]。
entrypoint.final][entrypoint.final]是一个特殊原语,可以从入口点返回并允许解耦存储在检查点中的值与入口点的返回值。
第一个值是入口点的返回值,第二个值是将在检查点中保存的值。
任务
任务代表一个离散的工作单元,例如API调用或数据处理步骤。它有两个关键特征:- 异步执行:任务设计为异步执行,允许多个操作并发运行而不阻塞。
- 检查点:任务结果保存到检查点,从而可以从上次保存的状态恢复工作流程。(参见persistence了解更多详情)。
定义
:::python 任务使用@task装饰器定义,该装饰器包装一个常规的Python函数。
task 函数定义,该函数包装了一个普通函数。
执行
任务 只能从 入口点、另一个 任务 或 状态图节点 中调用。 任务 不能 直接从主应用程序代码中调用。 :::python 调用 任务 时,它会立即返回一个 future 对象。Future 是一个占位符,用于表示稍后可用的结果。 要获取 任务 的结果,你可以同步等待它(使用result())或异步等待它(使用 await)。
- Synchronous Invocation
- Asynchronous Invocation
何时使用任务
任务 在以下场景中很有用:- 检查点:当你需要将长时间运行操作的结果保存到检查点,以便在恢复工作流时无需重新计算它。
- 人机交互:如果你正在构建一个需要人工干预的工作流,你必须使用 任务 封装任何随机性(例如,API 调用),以确保工作流可以正确恢复。有关更多详细信息,请参阅 确定性 部分。
- 并行执行:对于 I/O 密集型任务,任务 支持并行执行,允许多个操作并发运行而不会阻塞(例如,调用多个 API)。
- 可观察性:将操作包装在 任务 中提供了一种跟踪工作流进度和监控单个操作执行情况的方法,使用 LangSmith。
- 可重试工作:当工作需要重试以处理失败或不一致时,任务 提供了一种封装和管理重试逻辑的方法。
序列化
LangGraph 中的序列化有两个关键方面:entrypoint输入和输出必须是 JSON 可序列化的。task输出必须是 JSON 可序列化的。
确定性
当你恢复工作流运行时,代码 不 会从执行停止的 同一行代码 处恢复。执行返回到检查点边界,并且工作流 重放 前进,直到它再次到达暂停点。 对于 Functional API,重放从 入口点 的开头开始,而 LangGraph 从检查点恢复已完成的 任务 和 子图 结果,而不是重新计算它们。这保留了跨暂停的步骤记录顺序,包括对于长时间运行或非确定性 任务 输出。 要使用 人机交互 等功能,你必须将非确定性工作(例如,随机值)和副作用(例如,文件写入或 API 调用)放在 任务 中。 工作流的多次运行可能会产生不同的结果,但恢复 特定 线程应该重放相同的持久化 任务 和 子图 结果。 为确保你的工作流是确定性的并且可以一致地重放,请遵循以下指南:- 避免重复工作:在 入口点 中,如果你链式调用多个副作用(例如,日志记录、文件写入或网络调用),请为每个副作用分配一个 任务,以便恢复时从检查点恢复它们的输出,而不是再次运行它们。
- 封装非确定性操作:将可能在多次尝试中变化的值(例如,随机数或时钟读取)保留在 任务 中,以便重放与检查点记录对齐。
- 使用幂等操作:对于部分任务失败和重试,请参阅 幂等性。
幂等性
幂等性确保多次运行相同操作会产生相同的结果。这有助于防止重复 API 调用和冗余处理,如果步骤因失败而重跑。始终将 API 调用放在 任务 函数内部进行检查点,并设计它们在重新执行时保持幂等性。 这对于导致数据写入的操作尤其重要。 当工作流恢复时,LangGraph 从检查点重放已完成的 任务 结果。一个已开始但未完成的 任务 可能在恢复时再次运行,因此请设计副作用以保持幂等性。使用幂等性键或验证现有结果以避免意外重复。常见陷阱
处理副作用
将副作用(例如,写入文件、发送电子邮件)封装在任务中,以确保在恢复工作流时不会多次执行它们。- Incorrect
- Correct
在此示例中,副作用(写入文件)直接包含在工作流中,因此在工作流恢复时会再次执行它。:::python::::::js:::
非确定性控制流
每次可能产生不同结果的操作(如获取当前时间或随机数)应该封装在任务中,以确保在恢复时返回相同的结果。- 在任务中:获取随机数(5)→ 中断 → 恢复 → (再次返回5)→ …
- 不在任务中:获取随机数(5)→ 中断 → 恢复 → 获取新的随机数(7)→ …
interrupt]调用可能会与错误的resume值匹配,导致结果不正确。
请阅读关于determinism的章节以获取更多详细信息。
- Incorrect
- Correct
在此示例中,工作流使用当前时间来确定要执行哪个任务。这是非确定性的,因为工作流的结果取决于其执行的时间。:::python::::::js:::
了解更多
官方原文:src/oss/langgraph/functional-api.mdx · 非官方翻译,译文由 AI 生成,以英文原文为准。