本文聚焦高风险操作的人工审批场景提供 React/Vue/Svelte/Angular 全框架适配代码包含审批卡片、决策流转、多动作处理等核心功能。文章目录1. HITL 工作原理2. 环境搭建useStream 配置2.1 类型定义TypeScript2.2 基础流式组件React 示例3. 核心概念中断负载Interrupt Payload3.1 类型结构3.2 字段说明4. 三大决策类型批准/拒绝/编辑4.1 批准Approve4.2 拒绝Reject4.3 编辑Edit5. 核心组件审批卡片ApprovalCard实现组件特性6. 工作流中断恢复流程7. 常见应用场景8. 多待办动作处理方案8.1 多动作审批组件8.2 单个动作子卡片SingleActionCard9. 生产环境最佳实践1. HITL 工作原理Human-in-the-Loop人工介入是针对高风险操作的审批机制适用于邮件发送、数据删除、资金转账等不可逆操作。核心流程如下Agent 触发中断当 Agent 要执行高风险操作时主动暂停执行并发送「中断请求」前端接收中断useStream钩子通过stream.interrupt暴露中断信息用户决策前端渲染审批卡片提供「批准/拒绝/编辑」选项提交决策用户操作后前端调用stream.submit()提交决策结果Agent 恢复执行Agent 接收决策后继续执行批准、调整编辑或终止拒绝操作核心价值在 Agent 自动化流程中插入人工校验降低操作风险提升系统可靠性2. 环境搭建useStream 配置首先安装核心依赖以 npm 为例# 核心依赖npminstalllangchain/core langchain/react# 类型支持TypeScript 项目npminstall-Dtypes/langchain__core2.1 类型定义TypeScriptimporttype{BaseMessage}fromlangchain/core/messages;// 匹配 Agent 状态结构的接口interfaceAgentState{messages:BaseMessage[];// 消息列表包含 AI 消息、人类消息}// HITL 核心类型从 LangChain 导入或自定义importtype{HITLRequest,HITLResponse}fromlangchain/react/dist/types;2.2 基础流式组件React 示例import { useStream } from langchain/react; import { BaseMessage } from langchain/core/messages; import { Message } from ./Message; // 自定义消息组件 import { ApprovalCard } from ./ApprovalCard; // 后续实现的审批卡片 import type { HITLRequest, HITLResponse } from langchain/react/dist/types; // Agent 服务地址替换为你的实际地址 const AGENT_URL http://localhost:2024; export function HITLChat() { // 初始化流式连接指定 HITL 专属 Agent ID const stream useStreamAgentState({ apiUrl: AGENT_URL, assistantId: human_in_the_loop, // 替换为你的 HITL Agent ID }); // 获取当前中断请求Agent 暂停时非 null const interrupt stream.interrupt as { value: HITLRequest } | null; // 提交决策结果恢复 Agent 执行 const handleRespond (response: HITLResponse) { stream.submit(null, { command: { resume: response } }); }; return ( div classNamechat-container max-w-3xl mx-auto p-4 {/* 渲染历史消息 */} {stream.messages.map((msg: BaseMessage) ( Message key{msg.id} message{msg} / ))} {/* 有中断时渲染审批卡片 */} {interrupt ( ApprovalCard interrupt{interrupt.value} onRespond{handleRespond} / )} /div ); }3. 核心概念中断负载Interrupt Payload当 Agent 暂停时stream.interrupt.value包含HITLRequest类型的完整中断信息定义如下3.1 类型结构interfaceHITLRequest{actionRequests:ActionRequest[];// 待审批动作列表reviewConfigs:ReviewConfig[];// 审批配置允许的决策类型}interfaceActionRequest{action:string;// 动作名称如 send_email、delete_recordargs:Recordstring,unknown;// 动作参数结构化数据description?:string;// 动作描述人类可读}interfaceReviewConfig{allowedDecisions:(approve|reject|edit)[];// 允许的决策类型}3.2 字段说明字段描述actionRequests待审批的动作数组支持单个或多个动作actionRequests[].action动作唯一标识如 “transfer_funds” 对应资金转账actionRequests[].args动作的结构化参数如转账的{ amount: 1000, to: user123 }actionRequests[].description可选动作的自然语言描述如 “向用户 user123 转账 1000 元”reviewConfigs每个动作的审批配置与 actionRequests 一一对应reviewConfigs[].allowedDecisions该动作支持的决策类型如仅允许 “approve”/“reject”不允许编辑4. 三大决策类型批准/拒绝/编辑HITL 支持三种核心决策前端需根据reviewConfigs.allowedDecisions动态渲染对应按钮4.1 批准Approve用户确认动作按原参数执行// 决策格式constapproveResponse:HITLResponse{decision:approve,// 决策类型};// 提交决策触发 Agent 继续执行stream.submit(null,{command:{resume:approveResponse}});4.2 拒绝Reject用户拒绝执行动作可附带拒绝原因Agent 可根据原因调整流程// 决策格式constrejectResponse:HITLResponse{decision:reject,reason:转账金额超出限额请核实后重新提交,// 可选拒绝原因};// 提交决策stream.submit(null,{command:{resume:rejectResponse}});4.3 编辑Edit用户修改动作参数后批准执行// 原始参数从 action.args 获取constoriginalArgs{amount:1000,to:user123};// 编辑后的参数consteditedArgs{...originalArgs,amount:800,// 修改金额remark:月度补贴,// 新增参数};// 决策格式consteditResponse:HITLResponse{decision:edit,args:editedArgs,// 编辑后的参数};// 提交决策stream.submit(null,{command:{resume:editResponse}});5. 核心组件审批卡片ApprovalCard实现审批卡片是 HITL 交互的核心需支持「查看动作详情、选择决策、编辑参数、提交结果」全流程。以下是完整 React 实现import { useState } from react; import type { HITLRequest, HITLResponse } from langchain/react/dist/types; import { FaCheckCircle, FaTimesCircle, FaEdit, FaSave } from react-icons/fa; interface ApprovalCardProps { interrupt: HITLRequest; // 中断请求数据 onRespond: (response: HITLResponse) void; // 决策提交回调 } export function ApprovalCard({ interrupt, onRespond }: ApprovalCardProps) { // 取第一个待审批动作单动作场景多动作场景见 8 节 const [action] interrupt.actionRequests; const [config] interrupt.reviewConfigs; // 状态管理 const [mode, setMode] useStatereview | reject | edit(review); const [rejectReason, setRejectReason] useState(); const [editedArgs, setEditedArgs] useState( JSON.stringify(action.args, null, 2) // 格式化参数为 JSON 字符串方便编辑 ); const [isInvalidJson, setIsInvalidJson] useState(false); // 验证编辑后的 JSON 格式 const validateEditedArgs (): Recordstring, unknown | null { try { const parsed JSON.parse(editedArgs); setIsInvalidJson(false); return parsed; } catch (err) { setIsInvalidJson(true); return null; } }; // 提交批准决策 const handleApprove () { onRespond({ decision: approve }); }; // 提交拒绝决策 const handleReject () { onRespond({ decision: reject, reason: rejectReason.trim() || 用户拒绝执行该动作, }); }; // 提交编辑后的决策 const handleEditSubmit () { const parsedArgs validateEditedArgs(); if (parsedArgs) { onRespond({ decision: edit, args: parsedArgs, }); } }; if (!action || !config) return null; return ( div classNamerounded-lg border-2 border-amber-400 bg-amber-50 p-5 mb-4 shadow-md {/* 卡片标题 */} div classNameflex items-center gap-2 mb-4 span classNameinline-block w-3 h-3 rounded-full bg-amber-500 animate-pulse/span h3 classNametext-lg font-semibold text-amber-800需要人工审核/h3 /div {/* 动作描述 */} div classNamemb-4 text-gray-700 p classNamefont-medium动作{action.action}/p {action.description ( p classNamemt-1 text-sm text-gray-600 描述{action.description} /p )} /div {/* 动作参数JSON 格式化展示 */} div classNamemb-4 rounded-lg bg-white p-3 font-mono text-sm overflow-x-auto {mode edit ? ( // 编辑模式文本域允许修改参数 textarea className{w-full h-40 p-2 border rounded ${ isInvalidJson ? border-red-500 : border-gray-300 }} value{editedArgs} onChange{(e) setEditedArgs(e.target.value)} placeholder输入 JSON 格式的参数... / ) : ( // 查看模式展示格式化 JSON pre{JSON.stringify(action.args, null, 2)}/pre )} {isInvalidJson ( p classNamemt-1 text-xs text-red-500JSON 格式无效请检查/p )} /div {/* 决策操作区 */} {mode review ( div classNameflex gap-3 {/* 批准按钮 */} {config.allowedDecisions.includes(approve) ( button classNameflex items-center gap-1 rounded bg-green-600 px-4 py-2 text-white hover:bg-green-700 transition-colors onClick{handleApprove} FaCheckCircle size{16} / 批准 /button )} {/* 拒绝按钮 */} {config.allowedDecisions.includes(reject) ( button classNameflex items-center gap-1 rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700 transition-colors onClick{() setMode(reject)} FaTimesCircle size{16} / 拒绝 /button )} {/* 编辑按钮 */} {config.allowedDecisions.includes(edit) ( button classNameflex items-center gap-1 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 transition-colors onClick{() setMode(edit)} FaEdit size{16} / 编辑 /button )} /div )} {/* 拒绝模式输入拒绝原因 */} {mode reject ( div classNamespace-y-3 textarea classNamew-full h-24 p-2 border rounded border-gray-300 value{rejectReason} onChange{(e) setRejectReason(e.target.value)} placeholder请输入拒绝原因可选... / div classNameflex gap-3 button classNameflex items-center gap-1 rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700 transition-colors onClick{handleReject} FaTimesCircle size{16} / 确认拒绝 /button button classNamerounded bg-gray-200 px-4 py-2 text-gray-700 hover:bg-gray-300 transition-colors onClick{() setMode(review)} 取消 /button /div /div )} {/* 编辑模式提交/取消 */} {mode edit ( div classNameflex gap-3 button classNameflex items-center gap-1 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 transition-colors onClick{handleEditSubmit} FaSave size{16} / 提交编辑 /button button classNamerounded bg-gray-200 px-4 py-2 text-gray-700 hover:bg-gray-300 transition-colors onClick{() { setMode(review); setEditedArgs(JSON.stringify(action.args, null, 2)); // 重置编辑内容 }} 取消 /button /div )} /div ); }组件特性动态适配根据allowedDecisions只渲染允许的决策按钮格式校验编辑参数时自动校验 JSON 格式避免无效输入用户体验分「查看/拒绝/编辑」三种模式操作流程清晰样式友好使用语义化颜色绿色批准、红色拒绝、蓝色编辑适配聊天场景6. 工作流中断恢复流程完整的 HITL 工作流包含「Agent 中断 → 前端展示 → 用户决策 → Agent 恢复」四个阶段具体步骤如下Agent 触发中断执行高风险操作前Agent 发送HITLRequest并暂停前端接收中断stream.interrupt变为非 null触发审批卡片渲染用户决策用户查看动作详情选择「批准/拒绝/编辑」并提交前端提交决策调用stream.submit(null, { command: { resume: response } })Agent 接收决策LangGraph 后端传递决策结果给 AgentAgent 恢复执行批准按原参数执行动作拒绝接收拒绝原因调整流程如重新询问用户编辑按修改后的参数执行动作前端重置状态stream.interrupt变为 null审批卡片消失继续流式渲染后续内容支持多轮中断一个 Agent 流程中可插入多个 HITL 检查点如先审批搜索权限再审批邮件发送7. 常见应用场景HITL 适用于所有需要人工校验的高风险操作以下是典型场景及配置应用场景动作名称action允许的决策类型allowedDecisions核心参数args邮件发送send_email[approve, reject, edit]{ to: string[], subject: string, body: string }数据库记录修改update_record[approve, reject]{ table: string, id: string, data: Recordstring, unknown }资金转账transfer_funds[approve, reject]{ amount: number, to: string, currency: string }文件删除delete_files[approve, reject]{ filePaths: string[], force: boolean }外部 API 调用call_external_api[approve, reject, edit]{ url: string, method: string, params: Recordstring, unknown }8. 多待办动作处理方案Agent 可能同时触发多个待审批动作如批量删除多个文件此时需渲染多个审批卡片收集所有决策后统一提交8.1 多动作审批组件import { useState } from react; import type { HITLRequest, HITLResponse } from langchain/react/dist/types; import { SingleActionCard } from ./SingleActionCard; // 单个动作审批卡片复用 5 节逻辑 interface MultiActionReviewProps { interrupt: HITLRequest; onRespond: (responses: HITLResponse[]) void; // 接收多个决策结果 } export function MultiActionReview({ interrupt, onRespond }: MultiActionReviewProps) { // 存储每个动作的决策结果key: 动作索引value: 决策 const [decisions, setDecisions] useStateRecordnumber, HITLResponse({}); const { actionRequests, reviewConfigs } interrupt; // 检查是否所有动作都已决策 const isAllDecided Object.keys(decisions).length actionRequests.length; // 处理单个动作的决策 const handleActionDecide (index: number, response: HITLResponse) { setDecisions((prev) ({ ...prev, [index]: response })); }; // 提交所有决策 const handleSubmitAll () { // 按动作顺序整理决策结果 const responses actionRequests.map((_, index) decisions[index]); onRespond(responses); }; return ( div classNamerounded-lg border-2 border-amber-400 bg-amber-50 p-5 mb-4 shadow-md div classNameflex items-center gap-2 mb-4 span classNameinline-block w-3 h-3 rounded-full bg-amber-500 animate-pulse/span h3 classNametext-lg font-semibold text-amber-800 批量动作审核共 {actionRequests.length} 个动作 /h3 /div {/* 渲染每个动作的审批卡片 */} div classNamespace-y-4 mb-4 {actionRequests.map((action, index) ( SingleActionCard key{index} action{action} config{reviewConfigs[index]} onDecide{(response) handleActionDecide(index, response)} isDecided{!!decisions[index]} // 标记是否已决策 / ))} /div {/* 所有动作决策完成后显示提交按钮 */} {isAllDecided ( button classNamerounded bg-green-600 px-6 py-2 text-white hover:bg-green-700 transition-colors onClick{handleSubmitAll} 提交所有决策 /button )} /div ); }8.2 单个动作子卡片SingleActionCardimport { useState } from react; import type { ActionRequest, ReviewConfig, HITLResponse } from langchain/react/dist/types; interface SingleActionCardProps { action: ActionRequest; config: ReviewConfig; onDecide: (response: HITLResponse) void; isDecided: boolean; // 是否已决策 } export function SingleActionCard({ action, config, onDecide, isDecided }: SingleActionCardProps) { const [mode, setMode] useStatereview | reject | edit(review); const [editedArgs, setEditedArgs] useState(JSON.stringify(action.args, null, 2)); const [rejectReason, setRejectReason] useState(); const [isInvalidJson, setIsInvalidJson] useState(false); // 验证 JSON 格式复用 5 节逻辑 const validateEditedArgs () { /* ... */ }; // 决策处理函数复用 5 节逻辑 const handleApprove () { /* ... */ }; const handleReject () { /* ... */ }; const handleEditSubmit () { /* ... */ }; return ( div className{rounded-lg bg-white p-4 border ${isDecided ? border-green-300 : border-gray-300}} {/* 动作标题 已决策标记 */} div classNameflex items-center justify-between mb-2 h4 classNamefont-medium{action.action}/h4 {isDecided ( span classNametext-xs text-green-600已决策/span )} /div {/* 参数展示/编辑 决策按钮 */} {/* 复用 ApprovalCard 中的参数展示、模式切换、按钮逻辑 */} {/* ... 此处省略重复代码直接复用 5 节中的对应逻辑 ... */} /div ); }9. 生产环境最佳实践清晰展示上下文必须显示动作名称、描述、完整参数让用户明确「Agent 要做什么」简化批准流程批准操作应一步完成复杂流程拒绝/编辑放在次要位置参数校验不可少编辑参数时必须校验 JSON 格式显示明确的错误提示持久化中断状态用户刷新页面后中断状态应保留useStream已通过线程 checkpoint 实现审计日志记录所有决策谁、何时、批准/拒绝/编辑了哪个动作用于合规审计超时处理避免 Agent 无限期等待人工决策设置超时时间如 24 小时超时后自动拒绝权限控制高敏感操作如资金转账应限制审批人权限仅授权用户可审批批量动作优化多动作审批时支持「全选批准/全选拒绝」提升操作效率错误降级Agent 中断失败时前端应显示友好提示支持手动重试或取消响应式设计审批卡片需适配移动端确保参数展示和操作按钮不溢出