章节11 / 14
本文目录12 节
Agent 工作流要像状态机一样可恢复
本指南介绍如何将多步 Agent 拆解为具有确定性状态、Token 预算、人工审核拦截、失败恢复和详尽日志的可控状态机工作流。通过对比主流模型 API(OpenAI, Anthropic, Gemini)的工具调用规范与 Model Context Protocol (MCP) 边界,帮助开发者构建具备灾备能力、可暂停且可恢复的生产级 AI 代理应用。
- 理解 OpenAI 与 Anthropic 的工具调用机制
- 具备 TypeScript 或 Node.js 异步编程基础
- 设计并绘制出一个符合生产标准的 Agent 状态机草图
- 实现一套包含重试、退避和人工审核挂起的中间件策略
- 制定一份针对模型超时、工具异常和超额预算的故障恢复清单
把 Agent 当作一个“输入 Prompt 就能自动搞定一切”的神秘黑盒,是应用走向生产环境时的最大隐患。模型会幻觉、网络会超时、外部 API 会限流,如果将整个复杂任务的控制权完全交由大模型自由发挥,一旦中途某个步骤出错,整个进程就会崩溃,用户只能从头开始,不仅体验糟糕,还会白白浪费大量的 Token 预算。
可靠的 Agent 系统在本质上不应该是一个“自由思考的代理”,而应该是一个由应用层控制、可中断、可持久化、可恢复的有状态状态机。本指南将带你拆解 Agent 的不可信边界,设计出一套能够防范风险、允许人工干预并具备灾备恢复能力的 Agent 工作流。
1. 为什么“黑盒 Agent”在生产中必然崩溃
本节操作锚点:围绕“1.为什么“黑盒Agent”在生产中必然崩溃”记录步骤、样例、诊断、风险、检查清单和验收结果。
在开发原型时,我们常常习惯写一个循环:大模型输出 tool_calls -> 代码执行工具 -> 结果丢回给模型 -> 循环往复直到模型给出最终答复。这种模式被称为自主循环(Autonomous Loop)。
然而,当应用上线并面对真实用户时,这种设计会遭遇以下致命问题:
- 状态丢失:如果网络连接在第 4 步工具执行时断开,由于所有会话上下文和执行进度都只保存在内存中,应用无法得知当前的精确状态,只能引导用户从头运行,重复消耗 Token 并重复执行已经成功的外部操作(例如重复划扣资金)。
- 失控循环(Token 暴涨):当模型遇到无法解析的工具返回值时,它可能会陷入逻辑死循环,不断尝试调用同一个工具,在几秒钟内烧光单次会话的输入上限。
- 缺乏审计与回滚:当业务运营人员询问“为什么这个订单被自动取消了”时,开发人员无法还原当时大模型做出决策那一瞬间的确切系统状态、工具入参与上下文快照。
为了解决这些问题,我们必须将“智能体”的决策过程降维。模型不负责控制程序的生命周期,它只负责提供状态转移的建议,而真正的状态维护、分支走向和控制权必须牢牢掌握在应用层。
2. 边界划分:模型提出意图与应用执行工具
本节操作锚点:围绕“2.边界划分:模型提出意图与应用执行工具”记录步骤、样例、诊断、风险、检查清单和验收结果。
要构建可控的状态机,首先要厘清模型与应用层之间的交互边界。
OpenAI 的 Function calling 文档说明,工具执行权实际上是在应用层,模型只负责产生 tool_calls。模型接收到用户的输入后,返回一个结构化的 JSON,指明它“想要调用哪个函数”以及“参数是什么”,随后模型便挂起并等待。应用层负责解析这个 JSON,执行真实的本地代码或调用第三方 API,再将结果封装成 tool 角色消息回传给模型。
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ AI Model │ │ Application │ │ External World │
│ │ │ (State Machine) │ (APIs/DB) │
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
│ 1. Prompt │ │
│◄─────────────────────────────┤ │
│ │ │
│ 2. Tool Call Intent │ │
├─────────────────────────────►│ │
│ (Suspended) │ 3. Execute Tool │
│ ├─────────────────────────────►│
│ │◄─────────────────────────────┤
│ │ 4. Tool Result │
│ 5. Context + Result │ │
│◄─────────────────────────────┤ │
同样的,Anthropic Tool use 文档也强调,tool_use 与 tool_result 构成了一个明确的往返协议。应用层在接收到 tool_use 意图后,完全拥有拦截、修改甚至拒绝执行该工具的最高权力。Google Gemini API 的 function calling 机制同样遵循这一标准。这表明,模型的智能体能力只是一个“意图解析器”,而应用层才是“执行器”与“看门人”。
如果工具调用涉及到真实资金划拨、敏感数据删除或外部群发消息,应用层必须在状态机中加入“人工审批暂停”状态,否则一旦模型发生幻觉或遭遇 Prompt 注入,将产生无法撤销的实际业务损失。
3. 状态机的核心要素:持久化状态、运行上下文与 Token 预算
本节操作锚点:围绕“3.状态机的核心要素:持久化状态、运行上下文与T”记录步骤、样例、诊断、风险、检查清单和验收结果。
一个高可靠的 Agent 状态机,其核心是维持一个随时可以序列化为 JSON 并存入数据库的 State 结构体。每次状态转移(无论是模型决策、工具执行还是人工干预)都必须原子化地更新该状态。
以下是一个典型的 Agent 状态数据结构设计:
interface AgentState {
// 会话基本信息
sessionId: string;
status: 'idle' | 'model_deciding' | 'awaiting_tool' | 'awaiting_human' | 'failed' | 'completed';
// 消息历史(必须持久化,用于恢复给模型看)
messageHistory: Array<{
role: 'user' | 'assistant' | 'tool' | 'system';
content: string;
tool_calls?: any[];
tool_call_id?: string;
}>;
// 当前待执行的任务与上下文
pendingToolCalls: Array<{
id: string;
name: string;
arguments: Record<string, any>;
}>;
// 预算与控制阀门
budget: {
maxSteps: number;
currentSteps: number;
maxCostUSD: number;
accumulatedCostUSD: number;
};
// 恢复锚点
retryCount: Record<string, number>; // 记录每个工具或状态的重试次数
lastUpdated: number;
}
在执行循环中,每当调用一次大模型 API,应用层应当立即计算本次请求消耗的 Token,并折算成金额累加到 accumulatedCostUSD。根据 NIST AI Risk Management Framework (AI RMF) 中关于风险控制与资源管理的度量要求,系统必须对 AI 代理的资源消耗设立强硬的上限阈值(Budget Guardrails)。
如果单次任务的累积成本 accumulatedCostUSD 超过了 maxCostUSD,或者迭代步数 currentSteps 超过了 maxSteps,应用层必须强行将状态修改为 failed 或 awaiting_human 并发出告警,而不是继续任由模型调用 API。
4. 人类在环(Human-in-the-Loop):断点暂停与安全审核设计
本节操作锚点:围绕“4.人类在环(HumanintheLoop):断”记录步骤、样例、诊断、风险、检查清单和验收结果。
并非所有的工具调用都可以静默执行。在涉及敏感操作时,我们需要实现“人类在环”(Human-in-the-Loop, HITL)机制。由于我们已经将 Agent 设计成了状态机,引入 HITL 将变得非常自然:它不过是状态机中的一个“挂起”状态。
挂起与恢复的运行流程
- 模型输出意图:Claude 或 GPT 返回了一个敏感工具调用(例如
send_invoice_email)。 - 应用层识别并挂起:应用层检测到该工具属于敏感工具,不执行真实代码,而是将
status变更为awaiting_human,把待执行的参数写入pendingToolCalls,并保存状态机到数据库。 - 通知人类:应用层通过 Webhook、Slack 消息或前端 UI 提示审批人员,并提供“同意执行”或“拒绝并修改参数”的选项。
- 恢复执行:审批人员点击“同意”后,后台读取
sessionId对应的状态,将status改回model_deciding,使用原本暂存的参数执行真实工具,并将工具执行结果拼装成tool_result,最后继续触发下一次模型迭代。
async function handleAgentStep(state: AgentState, userApproved?: boolean, modifiedArgs?: any): Promise<AgentState> {
// 如果当前状态是等待审批,且收到了用户审批信号
if (state.status === 'awaiting_human') {
if (userApproved === false) {
// 用户拒绝执行该工具,我们向模型回传一条用户拒绝的错误信息,引导模型换种方案
state.messageHistory.push({
role: 'tool',
tool_call_id: state.pendingToolCalls[0].id,
content: 'Error: User rejected the execution of this tool.'
});
state.pendingToolCalls = [];
state.status = 'model_deciding';
return await saveAndTriggerNextModelStep(state);
}
// 用户同意或修改了参数
const toolToRun = state.pendingToolCalls[0];
const args = modifiedArgs || toolToRun.arguments;
const result = await runToolExecutor(toolToRun.name, args);
state.messageHistory.push({
role: 'tool',
tool_call_id: toolToRun.id,
content: JSON.stringify(result)
});
state.pendingToolCalls = [];
state.status = 'model_deciding';
return await saveAndTriggerNextModelStep(state);
}
// ... 其他常规状态处理
}
通过这种显式的状态持久化,整个审批过程可以跨越数小时甚至数天,而服务器在此期间可以随时重启,完全不会丢失执行上下文。
5. 异常容错机制:如何优雅处理网络抖动与工具执行失败
本节操作锚点:围绕“5.异常容错机制:如何优雅处理网络抖动与工具执行”记录步骤、样例、诊断、风险、检查清单和验收结果。
在分布式系统中,网络抖动或第三方接口暂时不可用是家常便饭。如果模型连续返回格式错误的 tool_calls 参数且重试 3 次依然失败,应该立即降级回传一个包含具体格式错误描述的模拟 tool_result 或者直接切换到人工客服节点,因为继续盲目调用大模型只会白白消耗 Token 预算。
我们可以为工具执行和模型调用设计一套分层的退避与重试策略(Exponential Backoff):
| 失败场景 | 表现形式 | 应用层应对策略 | 恢复手段 |
|---|---|---|---|
| 大模型 API 超时 | 接口请求无响应或返回 503 | 应用层进行指数退避重试(如 2s, 4s, 8s) | 若达到 3 次上限,将任务标记为 awaiting_recovery,允许用户手动点击重试 |
| 模型输出格式错误 | JSON 损坏,无法解析参数 | 回传特定 System 提示:“你提供的 JSON 格式有误,请重新生成” | 限制最多纠错 2 次,超出则降级为人审或终止任务 |
| 工具执行发生业务异常 | API 返回 400 或业务错误码 | 将错误包装为 tool_result 传回模型,让模型理解并自主调整参数 | 模型通常能根据错误信息(如“余额不足”)自行改变后续规划 |
| 工具执行遭遇系统崩溃 | 数据库连接失败或网络彻底断开 | 不要直接把原始代码 Crash 堆栈丢给模型 | 状态机捕获异常,将状态置为 failed,保留现场,并触发告警邮件 |
6. 多代理交接(Handoff)与不可信边界处理
本节操作锚点:围绕“6.多代理交接(Handoff)与不可信边界处理”记录步骤、样例、诊断、风险、检查清单和验收结果。
现代 Agent 系统正逐步从单一代理向多代理网络演进。OpenAI 的 Agents SDK 演示了通过 handoff 机制在不同的专业代理(如:客服代理、退款代理、技术支持代理)之间流转控制权。同时,Model Context Protocol (MCP) 正在成为连接各种外部工具和数据源的新标准。
然而,从安全和工程鲁棒性角度来看,每一个外部代理(特别是通过第三方 MCP Server 接入的工具)都应该被视为不可信边界。根据 MCP 规范设计,MCP 协议虽然标准化了工具发现与调用,但 MCP server 本身并不默认可信。如果我们在状态机中接入了不受控的外部 MCP 节点,就必须在状态机的流转中设立沙箱与严格的输入/输出校验。
除非 MCP 服务器处于完全隔离的本地私有 VPC 环境中,否则应用层在执行其返回的任何工具调用前,必须对输入参数进行严格的模型无关校验(Schema Validation),因为外部 MCP 服务可能已被篡改或注入了恶意指令。
当进行多代理交接(Handoff)时,状态机必须显式地记录这一转移事件:
// 模拟 OpenAI Agents SDK 的 handoff 状态转移
interface AgentHandoff {
fromAgent: string;
toAgent: string;
reason: string;
transferredContext: Record<string, any>;
}
function handleHandoff(state: AgentState, handoff: AgentHandoff): AgentState {
// 1. 验证目标 Agent 是否在系统白名单中
if (!isAuthorizedAgent(handoff.toAgent)) {
throw new Error(`Unauthorized handoff target: ${handoff.toAgent}`);
}
// 2. 写入审计日志
console.info(`[Handoff] Session ${state.sessionId}: ${handoff.fromAgent} -> ${handoff.toAgent}. Reason: ${handoff.reason}`);
// 3. 隔离上下文,防止敏感信息污染。仅传递目标 Agent 声明需要的上下文子集
state.messageHistory.push({
role: 'system',
content: `System: Control transferred to [${handoff.toAgent}]. Relevant context: ${JSON.stringify(handoff.transferredContext)}`
});
return state;
}
7. 交付实践:设计你的第一个高可靠状态机草图与失败恢复清单
本节操作锚点:围绕“7.交付实践:设计你的第一个高可靠状态机草图与失”记录步骤、样例、诊断、风险、检查清单和验收结果。
本课的学习产物是为你自己的 AI 应用设计一套高可靠状态机草图、对应的重试/拦截策略和失败恢复清单。以下是为你准备的交付物规范与验收模板。
交付物 1:状态机转换图(Mermaid 格式草图)
在你的技术文档中,使用以下结构清晰定义你的 Agent 会话状态流转:
stateDiagram-v2
[*] --> Idle
Idle --> ModelDeciding : 用户输入 / 唤醒
ModelDeciding --> AwaitingTool : 模型要求调用工具
ModelDeciding --> AwaitingHuman : 触发敏感操作审核
ModelDeciding --> Completed : 模型输出最终答复
ModelDeciding --> Failed : 预算超限 / 逻辑异常
AwaitingTool --> ModelDeciding : 工具执行成功,回传结果
AwaitingTool --> Failed : 工具执行崩溃且重试超限
AwaitingHuman --> ModelDeciding : 人工批准 / 修改参数并执行
AwaitingHuman --> ModelDeciding : 人工拒绝,回传拒绝信号
Failed --> [*]
Completed --> [*]
交付物 2:生产级故障恢复清单(Checklist)
在上线你的 Agent 应用前,逐一核对并回答以下问题,确保系统不具备“黑盒特征”:
- 1. 状态落地检查:我们的 Agent 状态(包括完整的 Chat History、 pending_tools 和当前 Step 计数)是否在每次模型 API 返回、以及每次工具调用结束后,都立即写入了 Redis、PostgreSQL 或 DynamoDB?(如果是纯内存状态,一律不予上线)
- 2. 悲观 Token 预算:系统是否设定了单次会话的硬性美元预算限制(如单次会话最多消耗 $0.5 USD)?当达到此限制时,系统是否能主动拦截并平滑回退,而不是任其继续计费?
- 3. 敏感工具隔离:涉及写操作、转账、发送外部通知的工具,是否在其执行函数前注入了
require_approval装饰器或拦截器? - 4. 幂等性设计:如果状态机在“工具执行中”意外断电重启,再次拉起时,我们的外部写入工具是否支持幂等性(例如带上 unique_token 防止重复下单)?
- 5. 日志与可观测性:我们是否记录了模型发出
tool_calls那一瞬间的完整 Prompt、Temperature 以及对应的 Raw JSON?当客户投诉时,我们是否能 100% 还原当时的现场?
8. 来源、复核与时效说明
本节操作锚点:围绕“8.来源、复核与时效说明”记录步骤、样例、诊断、风险、检查清单和验收结果。
本单元的技术设计原则与安全规范基于以下业界标准及官方文档构建,读者在后续维护和版本迭代时需关注其更新:
- 模型工具调用行为:参考了 OpenAI Function calling 与 Anthropic Tool use overview(访问日期:2026-05-28)。当前业界共识是将工具执行权留在应用端,通过往返协议驱动状态演进。若未来模型引入了“端到端全托管自主代理(Fully Hosted Agent)”功能,本指南中的“执行层沙箱”逻辑应转移至云端安全沙箱中执行。
- 多 Agent 编排与接入:参考了 OpenAI Agents SDK 与 Model Context Protocol (MCP) 规范(访问日期:2026-05-28)。由于 MCP 协议发展迅速,若你使用的 MCP SDK 进行了大版本升级,请重点检查其对安全沙箱、白名单授权(Authentication/Authorization)以及输入 Payload 校验(JSON Schema Validation)的支持。
- 风险管理框架:遵循 NIST AI Risk Management Framework 的治理与管理建议(访问日期:2026-05-28)。在设计多 Agent 交互和人类在环流程时,组织应参考其“映射(Map)”与“测量(Measure)”方法论,对 Agent 的自主权进行分级管辖。
9. 练习与验收
本节操作锚点:围绕“9.练习与验收”记录步骤、样例、诊断、风险、检查清单和验收结果。
实践作业:编写一个带人工干预的“优惠券发放”状态机
场景描述:
你要设计一个客服 Agent。当用户抱怨物流慢时,Agent 可以调用 grant_coupon 工具给用户补偿优惠券。但是,如果优惠券金额大于 $10,必须经过人工审核;$10 及以下可以直接发放。
验收要求:
- 写出该状态机的伪代码或 TypeScript 实现,核心需包含
AgentState结构。 - 模拟以下两种执行路径的控制台输出(用
console.log打印状态变化):- 路径 A:模型尝试发放 $5 优惠券 -> 自动执行成功 -> 回传给模型 -> 给出最终回复。
- 路径 B:模型尝试发放 $20 优惠券 -> 状态变为
awaiting_human挂起 -> 模拟接收到外部批准信号 -> 执行成功 -> 回传给模型 -> 给出最终回复。
- 提供你针对该场景补充的“失败恢复清单”。
Agent工作流要像状态机一样可恢复:把判断写成可复查证据
这一课的取舍应当写成可讨论的决策,而不是写成口号。本课交付物是 一个 Agent 状态机草图、重试/暂停/人审策略和失败恢复清单,它要服务于 AI 应用 的下一步,而不是只证明你读过这一节。
如果你现在还没有真实输入,先用一个最小样例完成 Agent工作流要像状态机一样可恢复,因为没有输入就无法判断产物是否可用。 如果本课产物会影响后续交付,应该写明适用条件和不适用条件,否则下一课会把不确定性误当成基础。 如果检查结果只剩一句“看起来可以”,必须补一条反例或失败样例,只有能解释失败原因时,这个判断才站得住。
如果同一个错误第二次出现,说明上一轮修复没有沉淀成流程或模板。围绕 Agent工作流要像状态机一样可恢复 做检查时,至少保留步骤、样例、风险、修复和验收五项。
OpenAI 的《Function calling》说明:支撑工具定义、模型提出调用、应用执行工具和结果回传的边界。;这意味着 Agent工作流要像状态机一样可 不能只写经验结论,要把来源变成检查动作。OpenAI 的《OpenAI Agents SDK documentation》提醒:支撑 Agent 编排、工具、handoff、guardrail、tracing 等生产化 Agent 组件。;因此本课方案必须写清边界。Anthropic 的《Anthropic Tool use overview》提供的证据是:支撑 tool_use / tool_result 流程、工具执行权留在应用层和代理边界。;所以当前结论按 2026-05-28 的来源状态使用。
复核触发条件要写具体:如果官方 API/SDK、软件工具版本、版权政策、安全合规要求、行业规范、平台发布规则或关键来源页面发生变化,需要重新检查 Agent工作流要像状态机一样可 的步骤、样例、风险和验收清单。
练习验收:把 Agent工作流要像状态机一样可恢复 应用到一个自己的任务,交付一份包含输入、步骤、样例、失败诊断、来源证据和复测结果的记录。缺少其中任何一项,都先标记为 needs_review。