本页面使用默认的批量中断模式。对于每次调用中断(
interrupt_mode="per_call"),请参阅每次工具调用暂停。本页面使用默认的批量中断模式。对于每次调用中断(
interruptMode: "per_call"),请参阅每次工具调用暂停。中断的工作原理
LangGraph 智能体支持中断,这是智能体明确将控制权交还给客户端的暂停点。当智能体遇到中断时:- 智能体停止执行并发出中断有效负载
- The @[
useStream] 钩子通过stream.interrupt提交中断 - 您的 UI 渲染带有批准/拒绝/编辑选项的审核卡片
- 用户做出决定
- 您的代码调用
stream.submit()并带有恢复命令 - 智能体从停止的地方继续
设置 useStream
将 @[useStream] 连接到您的 Human-in-the-Loop 智能体。当图遇到中断时,钩子会在 stream.interrupt 上暴露待处理的负载。在设置该值时渲染一个审批卡片,然后在用户批准、拒绝或编辑操作后使用 stream.submit(null, { command: { resume: response } }) 恢复运行。
代码示例使用
useStream<typeof myAgent> 进行类型安全的流状态管理。请参考 Python 或 JavaScript 后端的类型推断说明。中断有效负载
当智能体暂停时,stream.interrupt 包含一个 @[HITLRequest],其结构如下:
决策类型
HITL 模式支持四种决策类型:批准
用户确认操作应按原样进行:拒绝
用户拒绝操作并提供可选的原因。工具不会执行:当操作被拒绝时,智能体会收到拒绝原因并决定如何继续。如果您省略
message,后端会使用默认消息告知模型工具未执行,并且除非用户请求,否则不要重试相同的工具调用。对于有副作用的工具,传递一个清晰的消息,告诉智能体是否放弃操作、询问后续问题或尝试更安全的替代方案。编辑
用户在批准前修改操作的参数:响应
用户为“询问用户”风格的工具提供直接回复。message 成为工具结果,而工具本身不会执行:
当工具有意作为人类输入的占位符时使用
respond,例如一个 ask_user 工具,提示智能体从用户那里收集信息。不要使用 respond 来拒绝建议的操作,因为它会作为成功的工具结果返回给模型。构建审批卡片
这是审批卡片使用的决策接线。UI 可以将每个操作拆分为自己的卡片,但恢复负载是一个HITLResponse,每个待处理操作都有一个决策:
恢复流程
在用户做出决定后,完整周期如下:- 调用
stream.submit(null, { command: { resume: hitlResponse } }) - The @[
useStream] 钩子将恢复命令发送到 LangGraph 后端 - 智能体收到
HITLResponse并继续执行。decisions中的每个条目可能是:{ type: "approve" }:智能体继续执行操作{ type: "reject", message }:工具不会执行,智能体在决定下一步之前会收到拒绝消息{ type: "edit", editedAction }:智能体使用编辑后的参数运行工具{ type: "respond", message }:人类的消息直接作为工具结果返回,而不会执行工具
interrupt属性在智能体恢复流式传输时重置为null
处理多个待处理操作
当中断包含多个actionRequests 时,智能体希望一次性执行多个操作。为每个渲染卡片并收集所有决策后再恢复:
自定义中断表单
恢复流程 使用humanInTheLoopMiddleware,它将工具包装在一个通用的批准/拒绝/编辑/响应卡片中。有时一组按钮是不够的:预订航班、批准退款和审核社交帖子都需要一个不同的表单,具有自己的字段、验证和文案。为此,从工具内部提升 interrupt(),并让负载描述 UI 应渲染的确切表单。每个工具可以显示完全不同的界面。
在中断有效负载中描述表单
interrupt() 接受任何 JSON 可序列化的值,这允许你提供一个前端知道如何渲染的“卡片”,例如,表单类型、标题、人类正在审查的上下文以及收集的字段。interrupt() 对其输入和返回类型(interrupt<I, R>(value: I): R)是泛型的,因此你可以为发送的卡片(InterruptCard)和用户解析的值(ReviewDecision)进行类型注解。导出这些类型,以便前端可以导入它们并保持同步:
formType(例如 "refund-approval"、"content-review"),以便前端可以基于它切换并渲染匹配的表单。
每个工具渲染不同的表单
在客户端,卡片作为stream.interrupt.value 到达。从你的智能体模块中导入 InterruptCard 和 ReviewDecision 类型,以便表单和有效负载保持同步,基于 formType 选择正确的表单,并将 fields 输入到你的输入中:
使用 respond(decision, { update }) 在屏幕上保持卡片
当你解析一个普通的中断时,卡片会在中断清除的瞬间消失,并且只返回工具结果。这意味着一个丰富的审查卡片会在运行中途消失。为了在屏幕上保持它,解析中断 并且 提交一个携带卡片的消息到 相同 超步中的状态,使用 @[useStream] 的 respond:
respond(response, { update }) 乐观地应用 update:卡片立即渲染,一旦恢复的运行回显相同消息,就会通过 ID 进行协调。后端不会重新发出卡片,因此它在(可能较慢的)工具运行时保持渲染,没有任何闪烁。通过从消息中读取来渲染解析的卡片:
最佳实践
在实现 HITL 工作流时,请记住以下指南:- 显示清晰的上下文。始终显示智能体想要做什么以及 为什么。包括操作描述和完整参数。
- 使批准是最简单的路径。如果操作看起来是正确的,批准应该是单击操作。为拒绝/编辑保留多步流程。
- 验证编辑的参数。当用户编辑操作参数时,在发送之前验证 JSON 结构。对于格式不正确的输入,显示内联错误。
- 持久化中断状态。如果用户刷新页面,中断仍然应该可见。@[
useStream] 通过线程的检查点处理此问题。 - 记录所有决定。对于审计跟踪,记录每个批准/拒绝/编辑决定,包括时间戳和做出决定的用户。
- 谨慎设置超时。长时间运行的智能体不应无限期地阻塞人类审查。考虑显示智能体等待了多长时间。
官方原文:src/oss/langchain/frontend/human-in-the-loop.mdx · 非官方翻译,译文由 AI 生成,以英文原文为准。