# AI Agent 上线前,先把 5 类失败演练跑一遍

很多 Agent demo 看起来很顺,是因为环境太干净:用户输入完整,工具接口稳定,权限边界清楚,模型每一步都能拿到足够证据。

生产环境不一样。真正出问题的地方,经常不是模型完全不会做,而是链路里某一个小失败没有被设计好:工具超时后重试了两次,队列消息重复投递,外部系统返回半成功,人工接管时看不到上下文,审计日志里只剩一行“执行失败”。

所以我不建议把“能跑通主流程”当作上线标准。Agent 上线前,至少要把下面 5 类失败演练跑一遍。

## 1. 工具超时:Agent 能不能停下来

最常见的失败不是接口直接报错,而是超时。

例如 Agent 要读取 CRM 里的客户状态,再决定是否生成回复。CRM 接口 8 秒没有返回时,系统应该怎么处理?

- 继续等待;
- 换一个工具;
- 使用缓存;
- 请求用户补充;
- 转人工;
- 直接停止。

这些策略不能交给模型临场猜。生产系统里更稳的做法,是给工具调用配置明确的超时、重试和降级策略。

一个简化的配置可以长这样:

```yaml
tools:
  crm.get_customer_status:
    timeout_ms: 8000
    retry:
      max_attempts: 1
      backoff_ms: 500
    fallback:
      mode: ask_human
      message: "CRM 状态暂时不可用,请人工确认客户状态后继续。"
    audit:
      log_request_id: true
      log_timeout: true
```

这里的关键不是 YAML,而是规则前置。Agent 可以参与判断,但不能拥有无限等待和无限重试的自由。

## 2. 重复执行:写入动作有没有幂等

Agent 系统很容易重复执行。

用户刷新页面、队列超时重投、网络中断后重试、模型重新规划路径,都可能让同一个动作被触发两次。

只读查询重复几次通常问题不大,但写入动作不一样:

- 重复创建工单;
- 重复发送客户通知;
- 重复添加扣费记录;
- 重复触发设备控制;
- 重复把同一条线索推进到下一阶段。

上线前必须演练“同一个写入动作被提交两次”。

可以用一个很简单的测试:

```typescript
const command = {
  idempotencyKey: "lead-1842-stage-update-20260624",
  action: "crm.updateLeadStage",
  leadId: "lead-1842",
  targetStage: "qualified",
};

await agent.execute(command);
await agent.execute(command);

// 期望:只产生一次状态变更,审计日志记录第二次为 duplicate_ignored。
```

如果系统没有 idempotency key,只靠“模型应该不会重复调用”,那上线风险会很高。

## 3. 证据缺失:模型会不会硬猜

很多 Agent 事故的根因,是证据不足时系统仍然给出了确定动作。

比如用户问:“这个客户是不是该进入续约流程?”

Agent 可能需要看合同到期日、最近工单、付款状态、客户分层、销售备注。任何一项缺失,都可能改变判断。

失败演练要故意拿掉关键证据,观察 Agent 是怎么表现的:

- 是否明确说明缺了什么;
- 是否降低置信度;
- 是否停止高风险动作;
- 是否请求人工补充;
- 是否把缺失证据写进审计日志。

我更希望看到这样的行为:

```json
{
  "decision": "needs_human_review",
  "reason": "contract_end_date_missing",
  "missingEvidence": ["contract.endDate", "latestInvoice.status"],
  "allowedActions": ["draft_summary"],
  "blockedActions": ["send_renewal_email", "advance_pipeline_stage"]
}
```

这比一个看似流畅的回答更有价值。生产 Agent 不怕承认“不够判断”,怕的是在证据不足时继续执行。

## 4. 半成功状态:外部系统返回不一致时怎么办

最麻烦的失败,往往不是成功或失败,而是半成功。

例如 Agent 调用外部系统发通知,请求超时了。但通知服务实际上已经发出,只是回调没有回来。Agent 如果直接重试,客户可能收到两条通知。

类似情况也会出现在支付、合同、设备控制、审批流里。

上线前可以设计一组半成功演练:

- 外部接口超时,但实际写入成功;
- 工具返回成功,但下游异步任务失败;
- DB 写入成功,但消息队列发布失败;
- Agent 记录失败,但业务系统已经发生状态变化。

这种场景需要“查证再决定”,而不是盲目重试。

```mermaid
flowchart TD
  A[Agent 发起写入动作] --> B{工具返回状态}
  B -->|成功| C[记录 action_id 和结果]
  B -->|失败| D[记录失败并停止]
  B -->|超时/未知| E[查询外部系统状态]
  E -->|已生效| F[标记为 succeeded_after_timeout]
  E -->|未生效| G[按幂等 key 安全重试]
  E -->|仍未知| H[转人工确认]
```

如果没有这条查证链路,Agent 的“自动修复”很容易变成自动制造重复动作。

## 5. 人工接管:人能不能看懂现场

很多团队说自己有 human-in-the-loop,但实际只是弹一个确认按钮。

真正的人工接管,至少要让接管人看到:

- 用户原始请求;
- Agent 当前计划;
- 已调用的工具;
- 每次工具调用的证据;
- 风险等级;
- 已经执行和未执行的动作;
- 推荐的下一步;
- 可回滚动作。

如果接管人只能看到一句“Agent 执行失败,请处理”,那不叫接管,只是把上下文丢给人工重做。

可以用一个简单演练来检查:

1. 让 Agent 在第三步工具调用失败;
2. 强制进入人工接管;
3. 让一个没有参与开发的人查看接管页;
4. 看他能不能在 3 分钟内判断下一步。

如果不能,说明问题不在人工,而在系统没有留下足够上下文。

## 上线前可以用这张清单

把 Agent 接入真实业务前,可以至少检查这些项:

- 工具调用是否有 timeout、retry、fallback;
- 写入动作是否有 idempotency key;
- 高风险动作是否需要人工确认;
- 证据缺失时是否停止执行;
- 半成功状态是否能查证;
- 失败是否写入结构化审计日志;
- 人工接管页是否显示完整上下文;
- 回滚动作是否演练过;
- 每类失败是否有可观测指标;
- 失败演练是否纳入上线验收。

这些检查不一定复杂,但要真的跑一遍。只在文档里写“支持重试、支持人工接管”,和在生产链路里验证过,是两回事。

## AgentKick 的判断

Agent 的生产化,不是把模型能力继续堆高,而是把失败路径补齐。

AgentKick 做 AI Agent Production-Readiness Review 时,会把工具调用、证据链、审计日志、模型路由、人工确认、回滚和可观测性放在一起看。因为生产系统真正要回答的问题不是“正常时能不能跑”,而是“失败时能不能稳住”。

更多推荐