Agent 工程体系 · 第 97/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。
Agent 故障应急:错误分类、止损、证据、回滚、补偿和复盘
Agent 进入生产环境后,故障不再只是“模型回答错了”。一次用户请求可能经过路由、模型回合、工具调用、权限校验、外部系统写入、记忆更新和异步任务投递。最终暴露给用户的,可能只是一个超时或一句错误提示,但系统内部已经发生了部分成功、重复执行、状态污染或不可逆副作用。
因此,Agent 故障应急的对象不是单个异常,而是一个正在演化的业务过程:
其中任何一步失败,都必须回答六个问题:
- 这是什么错误?
- 它是否仍在扩大影响?
- 哪些证据已经可靠保存?
- 能否通过回滚恢复?
- 不能回滚时,如何补偿?
- 如何证明问题已经修复,并防止再次发生?
这六个问题分别对应错误分类、止损、证据、回滚、补偿和复盘。它们不是事后工作的六个并列栏目,而是一条有因果关系的应急链路。
一、先定义故障:Agent 的“错误”不是一种东西
1. 错误、失败、异常和事故
在工程语境中,几个词需要区分。
异常是程序观察到的非正常事件,例如超时、网络连接失败、JSON 解析失败。异常通常是技术层面的。
失败是一次操作没有达到预期结果。例如工具调用返回 500,或者模型生成了无法解析的结构化参数。
错误是导致失败的原因或状态,例如权限配置错误、Prompt 约束不充分、工具幂等键缺失。
事故是已经影响用户、业务数据、合规边界或成本预算的生产事件。事故不一定伴随程序异常:模型生成一个语法正确但业务错误的退款指令,同样可能构成严重事故。
可以将一次 Agent 运行抽象成:
其中:
- :输入,包括用户请求和上下文;
- :Prompt、策略、路由和工具定义;
- :模型调用及模型回合;
- :工具调用;
- :外部状态变化;
- :最终输出。
如果只检查异常 ,只能发现:
但生产正确性应检查:
也就是:输入、推理过程、工具行为、外部状态和对用户的承诺是否共同满足业务不变量。
例如:
- 模型调用成功,但选错了工具:没有异常,却是错误;
- 工具返回超时,但实际已经扣款:表面失败,业务状态可能成功;
- 最终回复说“退款已完成”,但退款工具只创建了申请:输出错误;
- 第一次调用已成功,客户端因超时重试并产生第二笔订单:并发与幂等错误。
2. 六类故障分类
实际应急时,建议同时使用故障来源分类和业务结果分类。只按 HTTP 状态码分类,会掩盖 Agent 特有的问题。
2.1 输入与上下文错误
输入错误包括:
- 用户请求缺少必要字段;
- 文件、图片或消息格式无法解析;
- 用户身份、租户或权限上下文缺失;
- 记忆内容过期、冲突或被错误注入;
- 上游系统传入了错误的关联 ID。
这类错误通常可以安全地要求用户补充信息,但不能把所有输入错误都归咎于用户。例如,系统将上一位用户的会话记忆带入当前请求,属于上下文隔离故障。
2.2 模型与 Prompt 错误
模型与 Prompt 错误包括:
- 意图识别错误;
- 选择了不适合的工具;
- 违反业务规则;
- 输出格式不符合 schema;
- 在不确定时编造结果;
- 过度调用工具,形成循环;
- 在工具失败后错误地继续执行;
- 把工具的“已受理”理解为“已完成”。
这类错误常见的特点是:技术调用成功,但业务行为不正确。因此,不能用调用成功率替代业务正确率。
2.3 工具协议与实现错误
工具错误包括:
- 参数校验失败;
- schema 与实际接口不一致;
- 工具返回格式变化;
- 超时、限流、连接失败;
- 工具执行成功但响应丢失;
- 工具重复执行;
- 工具缺少权限、审计或幂等控制。
尤其要重视“响应丢失”:
这并不等价于:
支付、下单、发券、发消息等写操作,必须把“未知状态”作为独立状态处理,而不是简单重试。
2.4 外部系统与数据错误
外部系统错误包括:
- 数据库不可用;
- 依赖服务部分故障;
- 读写延迟导致旧数据;
- 事务只提交了一部分;
- 消息已投递但消费失败;
- 外部服务返回业务拒绝;
- 多个系统之间出现状态不一致。
Agent 通常是分布式流程的编排者,无法仅靠一个本地事务覆盖模型服务、工具服务和业务数据库。因此,系统必须显式记录外部操作的状态。
2.5 安全、权限与策略错误
包括:
- 未授权用户调用高风险工具;
- 工具参数越权;
- Prompt 注入导致违反工具边界;
- 敏感数据进入模型上下文或 Trace;
- 代理绕过人工审批;
- 记忆内容跨租户泄露。
这类故障的首要目标不是恢复吞吐,而是封锁权限、冻结风险操作并保留审计证据。
2.6 发布与配置错误
Agent 的有效版本通常不是一个代码版本,而是一个组合:
只回滚代码而不回滚 Prompt、模型路由或工具 schema,可能无法恢复原行为。
常见发布错误包括:
- 新 Prompt 让模型更频繁调用写工具;
- 新模型改变工具选择分布;
- 工具 schema 发布后,旧 Agent 仍使用旧参数;
- Memory 结构迁移不兼容;
- 灰度流量未正确隔离;
- 配置中心回滚延迟,造成新旧版本混跑。
二、用业务不变量判断影响,而不是只看异常日志
1. 什么是业务不变量
业务不变量是无论 Agent 如何规划、模型如何生成,都必须保持成立的事实。
以“退款 Agent”为例,可以定义:
- 一个订单最多存在一笔有效退款;
- 退款金额不超过可退款金额;
- 未通过身份校验不能执行退款;
- 回复“退款已完成”时,支付系统必须返回最终成功状态;
- 工具超时后不能直接再次创建退款;
- 每次退款操作都必须可关联到用户请求和审批记录。
令:
- :退款金额;
- :已退款金额;
- :订单实付金额;
- :身份校验是否通过;
- :支付系统最终状态。
一个安全执行条件可以写为:
而“可以向用户承诺完成”的条件是:
注意,退款申请已创建、退款处理中、退款成功不能合并成一个布尔值。否则 Agent 会把中间状态错误地解释成最终状态。
2. 完整算例:一次超时不等于一次失败
假设用户请求退款 100 元,流程如下:
| 时间 | 事件 | 外部状态 | 本地观察 |
|---|---|---|---|
| 10:00:00.100 | 创建 Trace | 无 | 成功 |
| 10:00:00.180 | 模型选择 refund_order |
无 | 成功 |
| 10:00:00.220 | 工具请求发出 | 未知 | 已发送 |
| 10:00:01.220 | 客户端超时 | 可能成功 | 超时 |
| 10:00:01.250 | Agent 重试 | 可能已成功 | 第二次请求 |
| 10:00:01.400 | 工具返回“重复退款” | 已有一笔 | 业务拒绝 |
| 10:00:01.500 | Agent 回复“退款失败” | 实际已成功 | 用户误解 |
如果工具没有幂等键,第二次请求可能真的造成重复退款。即使工具拒绝重复退款,最终回复“退款失败”仍然错误,因为第一次操作可能已经成功。
正确做法是把操作状态建模为:
NOT_STARTED
-> SENT
-> SUCCEEDED
-> FAILED
-> UNKNOWN
当发生超时时,状态应进入 UNKNOWN,而不是 FAILED:
之后通过查询接口、事件通知或人工核对确定最终状态:
UNKNOWN -> SUCCEEDED
UNKNOWN -> FAILED
UNKNOWN -> NEEDS_REVIEW
这一步是回滚和补偿的前提。如果没有状态确认,就无法知道应该撤销、重试还是等待。
三、止损:先限制故障增长,再追求恢复
1. 止损的定义
止损是通过降低流量、能力、权限或副作用,把事故的增长速率降到可控范围。
设每分钟产生的风险操作数为:
其中:
- :请求速率;
- :请求进入高风险工具的概率;
- :单次操作失败或状态未知的概率;
- :失败后再次执行的概率;
- :一次错误操作的平均影响。
止损可以降低任意一个变量:
- 限制流量,降低 ;
- 关闭高风险工具,降低 ;
- 切换健康依赖,降低 ;
- 禁止不安全重试,降低 ;
- 只读降级,降低 。
这解释了为什么事故处理中通常先执行“暂停写操作”,而不是先修改 Prompt。修改 Prompt 需要验证,暂停写操作可以立即阻止新副作用。
2. 止损动作的优先级
第一优先级:阻断不可逆副作用
例如临时禁用:
- 支付、退款、转账;
- 删除、覆盖、批量更新;
- 发券、发短信、发邮件;
- 创建工单、下单、提交审批;
- 会改变长期记忆或权限状态的工具。
将工具状态从 ENABLED 改为 READ_ONLY 或 DISABLED,并由工具服务端再次校验。不要只在 Prompt 中写“暂时不要调用”,因为模型指令不是安全边界。
第二优先级:冻结错误版本
发布治理至少要支持按版本冻结:
model_version
prompt_version
tool_schema_version
memory_schema_version
routing_config_version
policy_version
如果某个版本导致故障,应停止继续扩大灰度,并保留当前实例使用的完整版本组合。不能只记录“Agent v42”,却无法展开 v42 对应的模型、Prompt 和工具定义。
第三优先级:降低重试和并发
重试不是默认的恢复手段。对读操作,有限次指数退避通常风险较低;对写操作,只有满足幂等条件时才允许自动重试:
若写操作没有幂等键,超时后的自动重试应被禁止,转为查询或人工确认。
第四优先级:降级为人工或只读流程
降级不是简单返回“系统繁忙”,而是切换到已定义的安全路径:
高风险自动执行
-> 只读查询
-> 创建人工审核任务
-> 返回处理中状态
降级响应必须准确表达状态。例如:
- 正确:“请求已提交,当前正在确认支付状态。”
- 错误:“退款失败,请稍后重试。”——这可能诱导用户重复提交。
四、证据:没有完整上下文,就无法判断回滚和补偿
1. 证据不是日志堆积
证据是能够回答“谁在什么版本、基于什么输入、做了什么操作、外部系统最终发生了什么”的可关联记录。
一条普通错误日志:
refund failed: timeout
几乎不能支持事故分析。至少需要:
{
"trace_id": "trace_...",
"run_id": "run_...",
"request_id": "req_...",
"tenant_id": "tenant_123",
"user_id_hash": "hash_...",
"agent_version": "agent_2026_09_01_17",
"model_version": "model_x",
"prompt_version": "prompt_42",
"tool_name": "refund_order",
"tool_call_id": "call_...",
"idempotency_key": "refund:order_1001:request_abc",
"external_operation_id": "payop_...",
"attempt": 1,
"status": "UNKNOWN",
"error_class": "TRANSPORT_TIMEOUT",
"started_at": "2026-09-01T10:00:00.220Z",
"ended_at": "2026-09-01T10:00:01.220Z"
}
敏感输入和输出不应无条件写入日志。应根据数据分级决定是否保存原文、脱敏值、哈希、摘要或仅保存长度与 schema 校验结果。
2. Trace、Span 与关联 ID
在 Agent 可观测性中:
- Trace表示一次端到端工作流;
- Span表示工作流中的一个有开始和结束时间的操作;
- parent_id表示 Span 的父子关系;
- group_id可以把同一会话中的多次 Trace 关联起来。
OpenAI Agents SDK 的内置 Trace 会覆盖运行过程,并为任务、模型回合、Agent、模型生成、函数工具调用、Guardrail 和 Handoff 等操作建立 Span。(openai.github.io)
一个典型层级如下:
trace: refund_workflow
└── task_span: request
├── turn_span: turn_1
│ ├── agent_span: refund_agent
│ └── generation_span: model_decision
├── function_span: verify_identity
└── turn_span: turn_2
├── agent_span: refund_agent
├── generation_span: tool_selection
└── function_span: refund_order
诊断时需要把以下 ID 串起来:
request_id
-> trace_id
-> span_id
-> model_turn_id
-> tool_call_id
-> idempotency_key
-> external_operation_id
-> compensation_id
其中 trace_id回答“这次工作流发生了什么”,tool_call_id回答“Agent 发起了哪次调用”,idempotency_key回答“业务上哪些请求应被视为同一个操作”,external_operation_id回答“外部系统实际创建了哪一笔操作”。它们不能互相替代。
3. OpenAI Agents SDK 的证据边界
SDK Trace 默认会捕获模型生成和函数工具调用,但这些 Span 可能包含敏感输入输出;可以通过 RunConfig.trace_include_sensitive_data 控制是否记录这类数据。官方文档还指出,ZDR 组织不可使用该 Trace 能力。(openai.github.io)
长运行 Worker 还需要注意 Trace 导出时机。默认批处理处理器会在后台导出,但任务结束后不一定立即出现在仪表盘;如果需要一个工作单元结束时立即发送,可以在 Trace 上下文退出后调用 flush_traces()。该调用会阻塞到当前缓冲的 Trace 和 Span 导出完成,因此不能在 Trace 尚未关闭时调用。(openai.github.io)
示例:
from agents import Agent, Runner, RunConfig, flush_traces, trace
agent = Agent(
name="Refund agent",
instructions="Only create a refund after identity verification."
)
def handle_refund(user_text: str) -> str:
try:
with trace("refund_workflow") as current_trace:
result = Runner.run_sync(
agent,
user_text,
run_config=RunConfig(
trace_include_sensitive_data=False
),
)
return result.final_output
except Exception:
# 这里只负责记录并向上抛出;
# 不要把异常吞掉后假装业务失败
raise
finally:
# 适合短任务或需要及时看到证据的后台任务。
# 高频同步请求中应评估阻塞和导出成本。
flush_traces()
这里的 flush_traces() 只能保证 Trace 尽快导出,不能保证外部退款操作已成功,也不能替代业务操作记录。Trace 是诊断证据的一部分,不是业务事务日志。
4. 证据保存的并发问题
故障发生时,多个 Worker 可能同时处理同一请求或同一订单。证据记录必须具备:
- 追加写或版本化写入;
- 稳定的事件时间与接收时间;
- 唯一事件 ID;
- 去重规则;
- 不依赖单个进程内存;
- 与外部操作 ID 关联。
推荐记录事件,而不是只覆盖最终状态:
OperationRequested
OperationSent
OperationResponseReceived
OperationTimedOut
OperationStatusChecked
CompensationRequested
CompensationCompleted
因为“最终状态=失败”无法说明中间是否已经产生副作用。事件序列才能支持重建故障路径。
五、回滚:撤销系统版本,不等于撤销业务副作用
1. 回滚的两个含义
发布回滚是把 Agent 运行时切换到已知稳定的版本组合。
业务回滚是撤销已经发生的业务状态变化。
二者经常被混淆:
回滚 Prompt
≠ 撤销已经发出的退款
≠ 删除错误写入的记忆
≠ 自动恢复外部订单状态
发布回滚解决“继续产生错误”的问题;业务回滚解决“已经产生的错误结果”的问题。
2. 什么时候可以回滚
回滚适用于以下条件:
- 新版本是主要变化来源;
- 旧版本仍可运行;
- 旧版本的工具 schema、记忆 schema 和权限配置兼容;
- 回滚不会让正在执行的任务突然改变语义;
- 已产生的副作用有明确的业务处理方案。
可以把版本选择写成:
但对正在执行的 Run,通常应保持版本固定:
否则一次运行可能在第一个模型回合使用新 Prompt,在第二个回合使用旧 Prompt,导致难以重放和判断。
3. 回滚步骤
一个可验证的回滚流程如下:
发现异常
-> 冻结候选版本
-> 停止扩大灰度
-> 记录当前版本组合
-> 切换稳定版本
-> 验证新请求
-> 处理存量进行中任务
-> 评估已产生副作用
每一步都要有验证条件:
| 步骤 | 动作 | 验证 |
|---|---|---|
| 冻结 | 禁止新部署和配置变更 | 版本指纹不再增长 |
| 停止灰度 | 将流量权重设为 0 | 新 Trace 不再出现候选版本 |
| 切换 | 恢复稳定组合 | 新请求使用稳定版本 |
| 冒烟 | 执行只读和低风险用例 | 工具选择、权限和输出正常 |
| 观察 | 比较错误率和业务指标 | 没有继续扩大 |
| 存量处理 | 标记旧版本进行中的 Run | 不重复执行未知写操作 |
4. 回滚的反例
假设 Prompt v42 会把“查询退款进度”错误地路由到“创建退款”,事故期间将 Prompt 回滚到 v41。
如果某个 Run 已经执行了:
v42 -> model turn -> refund_order request sent -> timeout
此时直接让 Worker 使用 v41 继续跑,模型可能再次规划“查询订单”。如果查询接口返回旧数据,系统仍然可能把已创建的退款视为不存在,并重新发送退款请求。
因此,对状态未知的存量任务,正确动作不是“换旧 Prompt 再跑一次”,而是:
暂停自动继续
-> 根据 external_operation_id 查询
-> 确认最终状态
-> 进入成功、失败或人工复核
六、补偿:当回滚做不到时,建立业务等价的修复动作
1. 为什么需要补偿
分布式 Agent 流程中,很多动作不可逆:
- 已发送的短信不能真正撤回;
- 已发出的邮件可能已经被阅读;
- 已完成的支付无法靠恢复代码撤销;
- 已写入第三方系统的数据无法通过本地数据库回滚;
- 已进入模型上下文的敏感信息不能当作从未发生。
此时需要补偿事务:执行一个与错误操作相反、或在业务上抵消其影响的动作。
如果正向操作是 ,补偿操作是 ,理想关系为:
其中 是原始业务状态。但现实中通常只能做到业务近似恢复:
这里 表示状态差异,表示无法完全消除的影响,例如用户已经收到通知、产生了手续费或留下了审计记录。
2. 补偿不是重试
重试的目标是完成原操作:
失败 -> 再次执行 F
补偿的目标是修正已经发生的错误:
错误成功 -> 执行 C
如果操作状态未知,不能直接选择重试或补偿:
UNKNOWN
-> 查询最终状态
-> SUCCEEDED: 补偿或确认
-> FAILED: 可按策略重试
-> UNKNOWN: 人工复核
3. 补偿必须幂等
补偿本身也可能超时,因此补偿操作同样需要幂等键:
compensation_key = "refund-correction:{original_operation_id}"
伪代码:
def reconcile_refund(op):
status = payment.query(op.external_operation_id)
if status == "SUCCEEDED":
if op.amount > op.allowed_amount:
key = f"refund-correction:{op.external_operation_id}"
return payment.reverse_excess(
operation_id=op.external_operation_id,
amount=op.amount - op.allowed_amount,
idempotency_key=key,
)
return {"action": "confirm_success"}
if status == "FAILED":
return {"action": "mark_failed"}
return {"action": "manual_review"}
输入是原始业务操作记录,而不是模型重新生成的参数。补偿任务不应再次依赖模型决定退款金额、订单号或用户身份;这些值应来自经过校验的事件和数据库记录。
4. 补偿算例:错误发券
假设 Agent 因 Prompt 错误给不符合条件的用户发放 100 元优惠券。
正向操作:
理想补偿:
但如果优惠券已经被使用,补偿就不能简单删除。补偿流程可能变成:
- 查询优惠券是否已领取;
- 查询是否已使用;
- 未使用:撤销优惠券;
- 已使用:冻结后续权益并创建人工处理任务;
- 记录用户通知和审计原因;
- 评估是否需要退款或客服补救。
这说明补偿方案必须根据副作用的时间和可逆性设计,不能用统一的“把数据改回去”处理所有事故。
七、错误处理状态机:把“异常处理”变成可恢复流程
可以把一次高风险工具调用建模为状态机:
stateDiagram-v2
[*] --> NOT_STARTED
NOT_STARTED --> VALIDATING
VALIDATING --> REJECTED: 参数/权限不合法
VALIDATING --> READY: 校验通过
READY --> SENT
SENT --> SUCCEEDED: 明确成功响应
SENT --> FAILED: 明确业务失败
SENT --> UNKNOWN: 超时/连接断开/响应丢失
UNKNOWN --> SUCCEEDED: 查询确认成功
UNKNOWN --> FAILED: 查询确认失败
UNKNOWN --> NEEDS_REVIEW: 无法确认
SUCCEEDED --> COMPENSATING: 发现业务错误
COMPENSATING --> COMPENSATED: 补偿成功
COMPENSATING --> NEEDS_REVIEW: 补偿失败或部分成功
FAILED --> RETRYABLE: 满足安全重试条件
RETRYABLE --> SENT
REJECTED --> [*]
COMPENSATED --> [*]
NEEDS_REVIEW --> [*]
关键规则如下:
VALIDATING阶段解决参数、权限和业务前置条件;SENT表示请求已经离开本地系统;UNKNOWN表示系统不知道外部最终状态;FAILED只有在获得明确失败证据后才能进入;RETRYABLE不是所有失败的默认去向;SUCCEEDED后发现业务错误,必须进入COMPENSATING,不能把记录改成“从未成功”。
状态机的价值在于,它限制了错误路径。例如,禁止:
UNKNOWN -> RETRY
除非先证明操作具备幂等性,或者通过外部查询确认原操作没有成功。
八、模型故障与工具故障必须分开处理
1. 模型生成错误
模型生成错误通常没有可靠的“撤销模型输出”动作。处理重点是:
- 阻止模型输出直接成为高风险执行指令;
- 通过结构化 schema、业务校验和权限校验拦截;
- 将模型决定和工具实际执行分离;
- 保存模型回合、工具候选和最终选择;
- 用 Trace 评估路由、交接、Guardrail 和工具选择。
OpenAI 的 Agent Evals 文档将 Trace grading 定位为调试工作流级问题的入口:Trace 可以记录一次运行中的模型调用、工具调用、Guardrail 和 Handoff,再通过 Grader 对轨迹进行结构化评分。(developers.openai.com)
例如,不能只对最终答案打分:
最终答案:退款已完成
评分:正确
还应检查:
身份校验是否发生?
是否选对工具?
退款金额是否来自可信字段?
工具返回的是成功还是处理中?
是否在超时后重复调用?
是否违反了人工审批要求?
这些问题属于流程级正确性,单独评估最终文本无法发现。
2. 工具执行错误
工具执行错误要按“请求是否到达”和“外部状态是否确定”分类:
| 情况 | 是否到达工具 | 外部状态 | 动作 |
|---|---|---|---|
| 参数校验失败 | 否或未执行 | 未变化 | 修正参数或终止 |
| 连接建立失败 | 未知 | 未知 | 查询或人工确认 |
| 明确 4xx 业务拒绝 | 已到达 | 未变化 | 不重试 |
| 明确 5xx 且工具保证未执行 | 未执行 | 未变化 | 可有限重试 |
| 响应超时 | 可能已执行 | 未知 | 禁止盲目重试 |
| 成功响应但本地落库失败 | 已执行 | 已变化 | 通过查询和事件补齐本地状态 |
“5xx 可重试”也不能仅凭状态码判断。必须知道工具的执行语义:它是否可能在返回 5xx 前已经提交事务?如果不清楚,就应视为 UNKNOWN。
九、故障期间的用户回复也是系统行为
Agent 的最终回复不是装饰层,它可能改变用户下一步行为,进而扩大事故。
1. 三种状态必须对应三种表述
| 业务状态 | 正确表达 |
|---|---|
| 明确成功 | “退款已完成,金额为 100 元。” |
| 明确失败 | “退款未完成,原因是订单已超过可退款期限。” |
| 状态未知或处理中 | “退款请求已提交,系统正在确认最终状态,请勿重复提交。” |
禁止把技术异常直接映射成业务结论:
try:
result = call_refund()
return "退款成功"
except TimeoutError:
return "退款失败"
更安全的处理是:
try:
result = call_refund(idempotency_key=key)
except TimeoutError:
mark_operation_unknown(key)
enqueue_reconciliation(key)
return "请求已提交,正在确认状态,请勿重复操作。"
这里的关键不是措辞,而是用户被告知的动作必须与系统状态一致。否则用户可能重复点击、重复付款、重复提交工单。
2. 流式输出的特殊风险
流式 Agent 可能已经把部分文本发送给用户,然后才发现工具失败。例如:
正在为你办理退款……
退款已完成。
如果第二句在工具确认前就被发送,后续即使系统发现失败,也无法真正撤回用户看到的文本。
因此,高风险操作的流式输出应设置提交点:
规划阶段:可以流式输出进度
执行阶段:不输出最终成功承诺
确认阶段:只有拿到最终状态后输出结论
对于不可逆操作,最终承诺必须晚于业务确认,而不能早于工具调用。
十、复盘:从“谁改错了”转向“哪个控制面没有生效”
1. 复盘的对象
复盘不是重复时间线,而是验证控制面是否存在并生效:
- 输入校验是否阻止了非法请求?
- Prompt 是否允许模型越过工具边界?
- 工具是否执行了服务端权限校验?
- 写操作是否具有幂等键?
- 超时是否进入
UNKNOWN? - Trace 是否包含关键模型回合和工具调用?
- 版本是否可以精确定位和回滚?
- 灰度是否真正限制了影响范围?
- 补偿是否可以自动执行?
- 用户是否收到准确的中间状态?
2. 从 Trace 形成可重复评测
一次事故中,代表性 Trace 可以先用于定位单条故障路径;问题稳定后,再把脱敏后的输入、预期行为和故障轨迹整理为数据集,运行重复评测。
OpenAI 的评测文档建议:调试阶段先从单条 Trace 和 Trace grading 开始;当“什么是正确行为”已经明确后,再转向数据集和评测运行,以便重复比较 Prompt、路由和工作流变化。(developers.openai.com)
事故用例不应只保存最终文本,而应保存结构化断言:
{
"input": "请退还订单 1001 的 100 元",
"assertions": [
"identity_verification_before_refund",
"refund_amount_leq_refundable_amount",
"no_retry_after_unknown_without_reconciliation",
"final_success_claim_requires_payment_success",
"trace_contains_refund_operation_id"
]
}
这样才能验证“修复没有引入另一种错误”。例如,修复重复退款后,可能导致所有退款都转人工;最终错误率下降了,但自动化目标也被破坏了。
3. 复盘指标应覆盖结果和过程
建议至少区分以下指标:
结果指标
- 业务成功率;
- 错误操作率;
- 重复操作率;
- 用户受影响数;
- 补偿完成率;
- 未确认操作积压量。
过程指标
- 工具选择正确率;
- 参数校验拦截率;
UNKNOWN状态转最终状态的时间;- 自动重试次数;
- 人工接管比例;
- Trace 完整率;
- 版本可定位率;
- 从告警到止损的时间;
- 从止损到恢复的时间。
单看“Agent 请求成功率”会掩盖最危险的情况:请求都返回 HTTP 200,但模型持续调用错误工具。
4. 根因分析的反例
“模型太笨”不是可执行的根因,因为它不能直接导出修复动作。
更有用的根因表述是:
Prompt v42 将“退款进度查询”和“创建退款”描述为相邻能力;
工具层未执行二次意图校验;
写操作缺少幂等键;
超时被映射为失败并触发自动重试;
因此在 2026 年 9 月 1 日 10:00 至 10:08 期间,部分订单产生重复退款。
这个根因包含:
- 触发变化;
- 失效的控制;
- 故障传播路径;
- 时间范围;
- 可验证的修复方向。
十一、一个可落地的应急作业流程
阶段一:识别
通过以下信号发现事故:
- 业务不变量被违反;
- 高风险工具调用率异常;
UNKNOWN操作积压;- 用户投诉与 Trace 聚集;
- 模型版本、Prompt 版本发布后指标突变;
- Trace grading 发现工具选择或 Guardrail 回归。
阶段二:分类
先确定:
影响对象:文本 / 权限 / 数据 / 业务副作用 / 合规
状态:失败 / 成功 / 未知 / 部分成功
范围:单租户 / 单版本 / 单工具 / 全局
可逆性:可回滚 / 可补偿 / 不可逆
若无法确认是否成功,应宁可标记为 UNKNOWN,不要强行归类为失败。
阶段三:止损
按风险从高到低执行:
- 关闭或只读化高风险工具;
- 停止候选版本灰度;
- 禁止未知状态操作的自动重试;
- 降低并发和入口流量;
- 将请求转入查询、人工审核或处理中状态。
阶段四:保存证据
固定保存:
request_id
trace_id
run_id
span_id
model_turn_id
tool_call_id
idempotency_key
external_operation_id
完整版本指纹
错误分类
操作状态
时间线
用户可见输出
同时检查敏感信息保护和 Trace 导出是否完成。
阶段五:回滚或补偿
- 版本问题:回滚完整版本组合;
- 状态未知:先查询外部状态;
- 已成功但业务错误:执行幂等补偿;
- 不可逆副作用:创建人工任务、通知用户并记录审计;
- 失败且确认未执行:按安全条件重试。
阶段六:验证恢复
恢复不能只看服务健康检查。至少要验证:
- 新请求不再进入错误版本;
- 高风险工具恢复后仍有服务端校验;
- 未知状态积压正在下降;
- 业务不变量重新成立;
- 用户回复与实际状态一致;
- 代表性 Trace 和事故评测用例通过;
- 没有重复补偿或重复执行。
十二、常见误解与边界
误解一:模型没有抛异常,所以 Agent 没故障
模型生成通常可以成功返回,但内容可能违反业务规则。正确性必须由业务校验、工具约束和流程断言共同定义。
误解二:超时就重试
超时只说明调用方没有在期限内获得响应,不说明外部操作没有发生。对写操作,超时首先意味着状态不确定。
误解三:回滚版本就能恢复一切
版本回滚只能阻止新的错误行为,不能撤销已经发生的支付、发券、消息发送或第三方写入。
误解四:Trace 等于审计日志
Trace 适合观察工作流和诊断模型、工具、Guardrail、Handoff 等过程;业务审计还需要不可抵赖的操作事件、审批记录、外部操作 ID 和状态变更记录。
误解五:记录全部 Prompt 和工具输入就最有利于排查
完整记录有助于复现,但可能造成敏感数据泄露,也可能违反数据保留策略。应按字段分级、脱敏和限制访问。Agents SDK 文档明确提示,模型生成和函数调用 Span 可能包含敏感数据。(openai.github.io)
误解六:把所有失败都交给人工就是安全
过度人工化会造成积压和隐性超时;更重要的是,它不能替代幂等、状态查询和补偿设计。人工接管应是状态机中的明确分支,而不是异常处理的垃圾桶。
Agent 故障应急的核心不是让模型“永不犯错”,而是让错误在进入业务系统前可拦截,在产生副作用后可确认,在无法恢复时可补偿,在事后能够重放、评测和修复。
一个成熟的 Agent 运行链路应满足:
其中最重要的工程判断是:不要把未知状态伪装成失败,不要把版本回滚伪装成业务回滚,也不要把 Trace 记录伪装成业务审计。 只有把模型行为、工具调用、外部状态和用户承诺放进同一个可关联、可验证的状态机里,Agent 才具备真正的生产可恢复性。
系列导航与关联阅读
- 系列入口:Agent 工程完整路线:从运行循环、记忆与协议到安全、评测和生产交付
- 上一篇:Agent 发布与版本治理:模型、Prompt、Tool、Memory、灰度和回滚
- 下一篇:Agent 生产架构:网关、运行时、模型、工具、记忆、队列和观测
- 延伸:Agent 可观测性:Trace、Span、模型回合、工具调用、Token 和关联 ID
官方资料
本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。

评论
0 条讨论