从你想要自动化的过程开始
假设你需要构建一个处理客户支持邮件的 AI 代理。你的产品团队给出了以下要求:步骤 1:将你的工作流映射为离散步骤
首先识别你流程中的不同步骤。每个步骤将成为一个节点(一个执行特定功能的函数)。然后,草拟这些步骤如何相互连接。 此图中的箭头显示了可能的路径,但实际选择哪条路径的决策发生在每个节点内部。 现在我们已经识别了工作流中的组件,让我们了解每个节点需要做什么:读取邮件:提取和解析邮件内容分类意图:使用 LLM 对紧急程度和主题进行分类,然后路由到适当的操作文档搜索:查询你的知识库以获取相关信息错误跟踪:在跟踪系统中创建或更新问题起草回复:生成适当的回复人工审核:升级给人工代理以进行批准或处理发送回复:发送邮件回复
步骤 2:识别每个步骤需要做什么
对于图中的每个节点,确定它代表的操作类型以及它正常工作所需的上下文。LLM 步骤
当你需要理解、分析、生成文本或进行推理决策时使用
数据步骤
当你需要从外部源检索信息时使用
操作步骤
当你需要执行外部操作时使用
用户输入步骤
当你需要人工干预时使用
LLM 步骤
当一个步骤需要理解、分析、生成文本或进行推理决策时:分类意图
分类意图
- 静态上下文(提示):分类类别、紧急程度定义、回复格式
- 动态上下文(来自状态):邮件内容、发件人信息
- 期望结果:决定路由的结构化分类
起草回复
起草回复
- 静态上下文(提示):语气指南、公司政策、回复模板
- 动态上下文(来自状态):分类结果、搜索结果、客户历史
- 期望结果:可供审核的专业邮件回复
数据步骤
当一个步骤需要从外部源检索信息时:文档搜索
文档搜索
- 参数:根据意图和主题构建的查询
- 重试策略:是,对于瞬时故障使用指数退避
- 缓存:可以缓存常见查询以减少 API 调用
客户历史查询
客户历史查询
- 参数:来自状态的客户电子邮件或 ID
- 重试策略:是,但如果不可用则回退到基本信息
- 缓存:是,使用生存时间来平衡新鲜度和性能
操作步骤
当一个步骤需要执行外部操作时:发送回复
发送回复
- 何时执行节点:批准后(人工或自动)
- 重试策略:是,对于网络问题使用指数退避
- 不应缓存:每次发送都是一个独特的操作
错误跟踪
错误跟踪
- 何时执行节点:当意图为“错误”时总是执行
- 重试策略:是,关键在于不丢失错误报告
- 返回:包含在回复中的工单 ID
用户输入步骤
当一个步骤需要人工干预时:人工审核节点
人工审核节点
- 决策上下文:原始邮件、草稿回复、紧急程度、分类
- 期望输入格式:批准布尔值加上可选的编辑后回复
- 何时触发:高紧急程度、复杂问题或质量问题
步骤 3:设计你的状态
状态是代理中所有节点可访问的共享内存。可以将其视为你的代理用来跟踪其在处理过程中学习和决定的所有内容的笔记本。什么应该属于状态?
对每条数据问自己这些问题:包含在状态中
它需要跨步骤持久化吗?如果是,就放入状态中。
不要存储
你能从其他数据推导出它吗?如果是,在需要时计算它,而不是存储在状态中。
- 原始邮件和发件人信息(以后无法重建)
- 分类结果(多个后续/下游节点需要)
- 搜索结果和客户数据(重新获取成本高昂)
- 草稿回复(需要在审核过程中持久化)
- 执行元数据(用于调试和恢复)
保持状态原始,按需格式化提示
这种分离意味着:- 不同的节点可以根据自己的需求以不同方式格式化相同的数据
- 你可以更改提示模板而无需修改状态模式
- 调试更清晰——你可以准确看到每个节点接收了什么数据
- 你的代理可以在不破坏现有状态的情况下演进
步骤 4:构建你的节点
现在我们将每个步骤实现为一个函数。LangGraph 中的节点只是一个 JavaScript 函数,它接受当前状态并返回对其的更新。适当地处理错误
不同的错误需要不同的处理策略:- 瞬时错误
- LLM 可恢复
- 用户可修复
- 意外
- Saga / 补偿
添加重试策略以自动重试网络问题和速率限制。
实现我们的邮件代理节点
我们将每个节点实现为一个简单的函数。记住:节点接受状态,执行工作,并返回更新。读取和分类节点
读取和分类节点
回复节点
回复节点
步骤 5:将它们连接在一起
现在我们将节点连接成一个工作图。由于我们的节点处理自己的路由决策,我们只需要一些基本的边。 要启用带有interrupt() 的人在环中,我们需要使用检查点进行编译,以在运行之间保存状态:
图编译代码
图编译代码
Command 对象在节点内部发生。每个节点声明它可以去哪里,使流程明确且可追溯。
试用你的代理
让我们用一个需要人工审核的紧急计费问题来运行我们的代理:测试代理
测试代理
interrupt() 时暂停,将所有内容保存到检查点,并等待。它可以在几天后恢复,准确地从它停止的地方继续。thread_id 确保此对话的所有状态都一起保存。
总结和后续步骤
关键见解
构建这个邮件代理向我们展示了 LangGraph 的思维方式:分解为离散步骤
每个节点都做好一件事。这种分解支持流式进度更新、可以暂停和恢复的持久执行,以及清晰的调试,因为你可以检查步骤之间的状态。
状态是共享内存
存储原始数据,而不是格式化的文本。这允许不同的节点以不同的方式使用相同的信息。
节点是函数
它们接受状态,执行工作,并返回更新。当它们需要做出路由决策时,它们会指定状态更新和下一个目的地。
错误是流程的一部分
瞬时故障获得重试,LLM 可恢复错误带着上下文循环回去,用户可修复问题暂停以获取输入,意外错误冒泡以便调试。
人工输入是一等公民
interrupt() 函数无限期暂停执行,保存所有状态,并在你提供输入时准确地从它停止的地方恢复。当与节点中的其他操作结合使用时,它必须首先出现。图结构自然涌现
你定义基本的连接,你的节点处理自己的路由逻辑。这使控制流明确且可追溯——你总是可以通过查看当前节点来理解你的代理下一步将做什么。
高级考虑因素
节点粒度权衡
节点粒度权衡
本节探讨节点粒度设计中的权衡。大多数应用程序可以跳过此部分并使用上面显示的模式。
读取邮件 和 分类意图 合并为一个节点?或者为什么将文档搜索与起草回复分开?答案涉及弹性和可观察性之间的权衡。弹性考虑: LangGraph 的持久执行在节点边界创建检查点。当工作流在中断或故障后恢复时,它从执行停止的节点的开头开始。较小的节点意味着更频繁的检查点,这意味着如果出现问题需要重复的工作更少。如果你将多个操作合并到一个大节点中,接近末尾的故障意味着从该节点的开头重新执行所有内容。为什么我们为邮件代理选择这种分解:- 外部服务隔离: 文档搜索和错误跟踪是单独的节点,因为它们调用外部 API。如果搜索服务缓慢或失败,我们希望将其与 LLM 调用隔离。我们可以为这些特定节点添加重试策略,而不影响其他节点。
-
中间可见性: 将
分类意图作为其自己的节点,让我们可以在采取行动之前检查 LLM 决定了什么。这对于调试和监控很有价值——你可以准确看到代理何时以及为何路由到人工审核。 - 不同的故障模式: LLM 调用、数据库查询和邮件发送具有不同的重试策略。单独的节点允许你独立配置这些。
- 可重用性和测试: 较小的节点更容易单独测试并在其他工作流中重用。
读取邮件 和 分类意图 合并为一个节点。你将失去在分类前检查原始邮件的能力,并且在该节点中的任何故障时都会重复这两个操作。对于大多数应用程序,单独节点的可观察性和调试好处值得这种权衡。应用程序级别的考虑:步骤 2 中的缓存讨论(是否缓存搜索结果)是应用程序级别的决策,而不是 LangGraph 框架功能。你根据特定需求在节点函数内实现缓存——LangGraph 不规定这一点。性能考虑:更多的节点并不意味着更慢的执行。LangGraph 默认在后台写入检查点(异步持久性模式),因此你的图继续运行而无需等待检查点完成。这意味着你以最小的性能影响获得频繁的检查点。如果需要,你可以调整此行为——使用 "exit" 模式仅在完成时检查点,或使用 "sync" 模式在写入每个检查点之前阻止执行。从这里去哪里
这是关于如何思考使用 LangGraph 构建代理的介绍。你可以用以下内容扩展这个基础:人在环中模式
学习如何在执行前添加工具批准、批量批准和其他模式
子图
为复杂的多步骤操作创建子图
流式传输
添加流式传输以向用户显示实时进度
可观察性
使用 LangSmith 添加可观察性以进行调试和监控
工具集成
集成更多工具以进行网络搜索、数据库查询和 API 调用
重试逻辑
为失败的操作实现带有指数退避的重试逻辑
将这些文档通过 MCP 连接到 Claude、VSCode 等,以获取实时答案。

