知识库 · 第三篇

Agent 系统设计思维
解耦 · 容错 · 可观测

为什么 AI Agent 系统比传统系统更难搭、更难调?

因为传统系统的 Bug 是确定性的——同样的输入,错误一定复现。 但 Agent 的失败是概率性的——有时候成功,有时候失败,甚至出了问题你都不知道它到底做了什么。

这篇文章不讲代码,讲的是:当你要把一个 AI Agent 产品从 Demo 推向真实用户, 你在系统设计层面需要想清楚的五件事。 每件事都会先用一个真实场景带你进入,再讲清楚背后的原理和解法。

难度:中级 适合:产品架构师 / 负责人 阅读:约 20 分钟 场景:出行 / 企业服务 / 通用 Agent
01
解耦:为什么不能把 AI 和业务逻辑混在一起
02
容错:Agent 出错后,系统怎么"软着陆"
03
幂等性:重试的时候怎么保证不做重复的事
04
可观测:怎么知道 Agent 在任意时刻"在做什么"
05
人在回路:哪些事 Agent 不能自己做主
01 · 解耦设计

为什么不能把 AI 调用
和业务逻辑混在一起?

直觉上,"把所有功能写在一起"是最快的。但对于 Agent 系统来说, 这个选择会让你在第一次迭代之后,就陷入"改哪里都会出问题"的泥潭。

想象你们公司做了一个出行 Agent,用户说"帮我订明天北京到上海的高铁", Agent 调用 GPT-4o 理解意图,查询余票,完成支付,发确认短信。 整个逻辑写在一个大函数里,200 行代码,干净利落,功能跑通了。

三个月后,公司决定把 GPT-4o 换成 Claude——性价比更好。 工程师打开代码一看:AI 调用的代码和业务判断、数据库操作全搅在一起, 根本没办法只换模型,必须把整个函数重写一遍。 同时,测试团队说他们没法单独测"意图理解"那一步,因为跑一次测试就要完整走一遍订票流程,太慢了。

这就是"没有解耦"的代价——系统变成了一块板砖:整体换,或者不换

问题到底出在哪里?

根源在于:这个系统把三件性质完全不同的事混在了一起—— "让 AI 理解意图"、"执行业务规则"、"操作数据库和外部服务"。 这三件事各有各的变化节奏:

变化频率 · 高

AI 推理层

模型会换、prompt 会改、工具调用方式会迭代。这一层变化最频繁,几乎每个月都在动。

变化频率 · 中

业务逻辑层

订票规则、退改签策略、定价逻辑。跟随业务节奏变化,通常按季度或功能上线来调整。

变化频率 · 低

数据与通道层

数据库结构、推送服务、支付接口。相对稳定,但一旦要改,影响面最大。

当这三层混在一起时,任何一层的改动都会"惊动"其他两层。 这不是工程师不够谨慎,而是结构本身决定了它必然变得越来越脆。

分层之后,系统长什么样?

系统分层架构对比
❌ 混合型(不推荐) LLM 调用 业务规则判断 数据库 / 推送 三者耦合,牵一发动全身 换模型 = 整体重写 ✓ 分层型(推荐) AI 推理层 模型调用 · Prompt 管理 · 工具调用 👆 换模型只改这里 业务逻辑层 任务调度 · 规则判断 · 状态管理 👆 业务策略变动只改这里 数据与通道层 数据库 · 支付 · 推送 · 外部 API 👆 数据结构变动只改这里

分层之后,每一层都可以独立修改、独立测试、独立替换。 换模型只改 AI 层;调整退改签政策只改业务层;更换数据库只改数据层。 其他层完全不知道有变化,也不会受影响。

实际上能带来哪些变化?

× 没有解耦时
  • 换模型需要改多处业务代码,容易引入新 bug
  • 单测无法单独跑 AI 部分,每次测试要走完整流程
  • 定位 bug 时不确定问题出在 AI 还是业务规则
  • 多人协作时,工程师互相踩脚
✓ 解耦后
  • 换模型只改一个文件,改完跑 AI 层的单测即可
  • 可以 mock 掉 AI 层,专门测业务逻辑
  • 报错日志直接告诉你是哪一层出了问题
  • AI 工程师和业务工程师并行工作,互不干扰
如果你是产品负责人,你需要问工程团队的问题

关于解耦的三个决策

  • 我们的 AI 调用和业务逻辑有没有在代码层面物理隔离?(不是注释,是真正的模块分离)
  • 如果明天需要换一个模型,工程师能不能在不改业务代码的前提下完成
  • 测试团队能不能在不真正调用 AI 的情况下测业务逻辑?
02 · 容错设计

Agent 出错后
系统怎么"软着陆"?

最大的陷阱是:设计系统时只想"成功路径", 没想清楚失败的时候会发生什么。 对于 Agent 系统,这个问题比传统系统严峻得多。

传统系统:你写了一个计算器,输入两个数字相加。 如果输入的是字母,程序会报一个明确的错误——每次都一样,可以复现,可以修。

Agent 系统:用户说"帮我查一下明天北京到上海最快的车"。 这句话 99% 的情况下 Agent 都能正确理解。 但有 1% 的情况——可能因为这次用词有点含糊,可能因为当天模型负载高了点—— Agent 把"最快"理解成了"最便宜",帮用户查出来的是最低价车次。 下次用同样的话再问?可能又正确了。

这就是概率性失败的核心特点:不稳定、难复现、单元测试覆盖不了。 你需要一套专门针对这种失败的容错设计。

Agent 系统的失败有哪几种?

把 Agent 可能出的问题分类,大概是三种。搞清楚分类,才知道每种该怎么处理:

失败类型 具体表现 可以重试吗? 推荐处理方式
工具调用失败 调用查票接口超时、网络断了、第三方服务挂了 ✓ 通常可以 指数退避重试,超过次数后降级
模型输出格式错误 返回的 JSON 格式不对、缺少必要字段 ✓ 有限次数 格式校验 + 自动修正或重试
模型理解偏差 把"最快"理解成"最便宜",任务方向跑偏 ⚠ 重试可能没用 人工确认节点 + 结果验证

容错的三个层次

好的容错设计不是"出错了报个错就完事",而是像飞机的应急系统一样, 有层层的备选方案——只有所有备选都失效了,才最终告知用户"我做不到"。

容错三层架构 · 从第一层开始,逐层降级
第一层 · 自动重试 指数退避策略:第1次等1s,第2次等2s,第3次等4s。最多重试3次。 仍然失败 ↓ 第二层 · 降级方案 启用备用接口 / 返回缓存结果 / 使用更简单的模型处理 仍然失败 ↓ 第三层 · 转人工 / 告知用户 说清楚发生了什么 + 给出用户可以采取的行动 + 必要时转到人工客服

重试策略里有个细节经常被忽视

"失败了就重试"听起来简单,但有一个经常被忽略的问题: 如果同时有 1000 个 Agent 实例在运行,它们同时失败,然后同时在第 1 秒重试—— 这就像 1000 人同时涌进一扇门,反而把服务压垮了。

解决方案:加随机抖动(Jitter)
重试等待时间 = 基础等待时间 + 随机的一小段延迟(比如 0~500ms)。 这样 1000 个实例不会在同一时刻重试,请求被打散,下游服务的压力大幅降低。 这个细节听起来小,但在高并发场景下,它是系统能不能扛住的关键。

出错时,用户应该看到什么?

这是产品设计里容易被工程视角忽视的部分。容错设计的终点不是"系统不崩溃", 而是"用户不失控"。用户体验的好坏,取决于出错时给用户看到的是什么。

× 差的错误提示

"发生了未知错误,请稍后重试。"

用户完全不知道发生了什么,也不知道"稍后"是多久,更不知道下一步该怎么办。

✓ 好的错误提示

"查询列车信息时连接超时,已为您保存搜索条件。您可以:① 等 30 秒后点击重试 ② 联系客服 400-xxx 人工处理"

用户知道出了什么问题,知道下一步能做什么,感觉自己还在掌控中。

产品负责人的决策清单

容错设计需要在上线前想清楚的事

  • 每个关键 Agent 步骤都有明确的超时时间吗?(不设超时 = 用户可能永远等待)
  • 超时或失败后有降级方案吗?还是直接报错?
  • 重试会不会造成重复操作(比如重复扣款、重复发短信)?
  • 出错时给用户的提示,是否包含了下一步行动
03 · 幂等性

重试的时候
怎么保证不会做重复的事?

"幂等性"这个词听起来很学术,但它解决的问题每个人都懂: 网购时支付超时,你点了两次"付款",会不会扣两次钱? 这就是幂等性问题。在 Agent 系统里,这个问题出现的频率会比你想象的高得多。

出行 Agent 帮用户完成了高铁订票。订单提交成功,但调用支付接口时网络超时—— Agent 不知道钱有没有扣成功。按照容错逻辑,Agent 触发了重试,又调用了一次支付接口。

如果支付系统没有做幂等设计:用户被扣了两次钱,投诉热线爆炸。

如果支付系统做了幂等设计:第二次支付请求进来,系统识别出"这和第一次是同一笔交易", 直接返回第一次的结果,不重复扣款

幂等性的核心机制是什么?

理解起来其实很简单:给每一次有副作用的操作,分配一个全局唯一的 ID。 下游系统用这个 ID 来判断"这件事我处理过没有"——处理过了直接返回之前的结果,没处理过才真正执行。

这个 ID 通常叫 idempotency_key(幂等键)。 就像你在超市结账时,同一张小票只能结一次—— 收银台看到小票上的号,知道这是哪一笔,不会因为你多扫了一次就多收钱。

幂等 Key 工作原理
Agent 生成唯一 key 第1次请求 key: pay_abc123 重试请求 key: pay_abc123(同一个) 支付系统 Key 存在? → 返回原结果 不存在 → 执行扣款 并保存 Key 两次请求 只扣一次钱 ✓

哪些操作需要做幂等设计,哪些不用?

判断标准很简单:这个操作执行两次,结果会不会和执行一次不一样? 如果会——就需要幂等设计。

操作类型 例子 天然幂等? 需要额外设计?
查询类 查余票、查路况、查订单状态 ✓ 是 不需要,放心重试
支付类 扣款、退款 ✗ 否 必须加 idempotency_key
创建类 创建订单、创建用户 ✗ 否 必须加唯一键避免重复创建
通知类 发短信、发推送、发邮件 ✗ 否 必须做去重,避免用户收到多条相同消息
删除类 删除数据、取消订单 ⚠ 通常是 删除同一条记录多次结果相同,但要确认业务逻辑
记住这条判断原则: 凡是 Agent 执行后会在真实世界产生"不可逆影响"的操作(扣钱、发消息、修改数据), 都需要幂等设计。你不能假设 Agent 永远只会执行一次。
04 · 可观测性

怎么知道 Agent
在任意时刻"在做什么"?

可观测性(Observability)解决的问题是:系统出了问题,你能不能搞清楚"到底发生了什么"。 对 Agent 系统来说,这个能力比传统系统重要 10 倍——因为 Agent 的失败可能在任何一个决策步骤里悄悄发生。

用户投诉:Agent 帮他订了一趟停靠站很多的普速列车,但他明明说的是"最快的"。 你打开后台,只看到两条日志:

14:32:01 [INFO] 用户请求:查询北京→上海列车
14:32:04 [INFO] 订单创建成功:T104次

中间发生了什么?完全不知道。 模型是怎么理解"最快"的?它调用了什么工具?工具返回了什么数据? 没有任何记录,你只能猜。这就是可观测性缺失的代价——出了问题只能靠猜

Agent 系统需要记录"五个维度"

传统系统的日志只需要记录"做了什么"。但 Agent 系统的决策链路更长, 每一步都需要记录五个维度,合在一起才能还原事故现场:

维度 01

收到了什么

这一步的输入是什么?用户原始请求、上下文状态、历史对话摘要。

维度 02

怎么想的

模型的推理过程——它认为用户的意图是什么、为什么选择调用这个工具。

维度 03

做了什么

调用了哪个工具、传入的参数是什么、完整的请求内容。

维度 04

得到了什么

工具的返回结果、是否成功、原始响应内容。

维度 05

花了多久

这一步的耗时、消耗的 token 数、API 调用费用(用于成本追踪)。

一条好的 Agent 日志,长什么样?

// 一个 Agent 执行步骤的完整 trace 记录 { "trace_id": "t_20240601_u007_task003", // 全局唯一追踪 ID "step": 3, "timestamp": "2024-06-01T14:32:02Z", // 维度1:收到了什么 "input": { "user_request": "明天北京去上海,最快的班次", "user_prefs": "靠窗座位,二等座" }, // 维度2:模型怎么想的 "reasoning": "用户强调'最快',应优先筛G字头高铁,按时长排序", // 维度3:做了什么 "tool_call": { "name": "search_trains", "args": { "from": "北京南", "to": "上海虹桥", "sort_by": "duration" } }, // 维度4:得到了什么 "tool_result": { "status": "success", "trains": ["G1(4h28m)", "G3(4h32m)"], "recommended": "G1" }, // 维度5:花了多久 "latency_ms": 432, "tokens_used": 386, "cost_usd": 0.0012 }

有了这条记录,再回到用户投诉那个场景:只需要搜 trace_id, 就能看到 Agent 的推理是什么、传入查询的参数是否正确、工具返回了什么。 从"大海捞针"变成"按图索骥",定位问题的时间从几小时缩短到几分钟。

可观测性不是"有了再说"的可选项。
它是出了问题时,你唯一能依赖的救命稻草。

一个容易被忽视的需求:成本追踪

Agent 系统的每次 LLM 调用都会产生费用。如果没有精细的 token 使用记录, 你会在某个月底突然发现 API 账单暴涨,却根本不知道是哪个功能、哪类用户造成的。 把 token 消耗和费用写进每一条 trace,是日后控成本的基础

05 · 人在回路

哪些事
Agent 不能自己做主?

"让 AI 全程自动化"是一个很有诱惑力的目标,但在真实产品里, 盲目追求"完全自动"往往会在最关键的地方出问题—— 用户对 AI 的信任,是一点一点建立起来的,不是一下子给出去的。

用户随口说了句"帮我看看有没有更便宜的机票",Agent 不仅查到了, 还直接帮用户退了原来的票、订了新的。 用户惊了:我只是让你查一下,没让你真的换啊! 而且新票的时间不太方便,退票手续费也扣了……

这不是 Agent 做错了——从技术上它"完成了任务"。 但它做了用户没有授权的操作,这种体验会让用户永远不再信任它。

用两个维度来判断:谁来做这个决定?

判断一个操作该不该交给 Agent 自主执行,用两个维度: 这件事影响有多大? 以及 出错了能不能撤回? 交叉来看,就得到了一张决策矩阵:

操作授权决策矩阵
影响范围(小 ← → 大) 可逆程度(可逆 ← → 不可逆) ✓ Agent 自主执行 查询类操作 查余票 · 查路况 · 查天气 信息整理 · 草稿生成 ⚠ 执行后通知用户 小额操作 · 发行程提醒 (可让用户设置阈值) 💡 Agent 推荐,用户决定 推荐座位 · 推荐路线 (用户可以一键接受) ⛔ 必须人工确认 支付 · 退票 · 改签 删除数据 · 发真实消息 大额操作 · 影响第三方

人工确认节点怎么设计,才不让用户觉得烦?

这里有一个设计上的平衡:确认节点太少,用户感到失控;确认节点太多,用户觉得 Agent 没用。 关键是让确认节点出现在对的位置,而且确认的交互要做到让用户一眼看懂、一秒做决定

× 糟糕的确认设计

"我将执行以下操作,是否确认?
操作类型:预订服务
状态:待确认"

用户完全不知道"预订服务"是什么,要点"取消"还是"确认"?

✓ 好的确认设计

"为您预订:G1次 · 5月28日 · 北京南→上海虹桥 · 二等座靠窗 · ¥553"

[确认购票]   [换一班]   [取消]

用户一眼看清楚要订什么,有明确的行动选项,不只是"确认/取消"。

信任是一步步建立起来的

出行场景里用户对失误的容忍度极低——错一次关键操作(比如退了不该退的票), 用户可能永远不再用这个产品。所以信任的建立,应该按阶段来:

1

第一阶段:Agent 只做"建议者"

给用户推荐选项,用户来决定和操作。Agent 不直接执行任何写入操作。让用户慢慢体验到 Agent 的推荐是靠谱的。

2

第二阶段:Agent 执行,事前确认

Agent 规划好操作方案,告诉用户"我要做 X、Y、Z",用户确认后统一执行。信任感建立后,这一步确认摩擦会显著降低。

3

第三阶段:Agent 自主完成,事后通知

用户主动授权特定类型的操作(比如"帮我自动续订月票"),Agent 完成后发通知。这是高信任状态下的最终形态。

核心原则: 不要从第三阶段开始。用户不认识你,凭什么一上来就让 Agent 自主做所有决定? 信任需要被挣来,不是被设计假设的。
ARCHITECT NOTE · 五个原则的本质

这五个设计原则,看起来是技术问题,本质上都是工程风险管理

解耦,是把变化的代价控制在一个可预期的范围内。 容错,是承认"系统一定会出错",并提前设计好出错后的路径。 幂等性,是保证"重试不会造成新的伤害"。 可观测性,是在出了问题时,你有能力搞清楚发生了什么。 人在回路,是在信任还没有建立起来之前,给用户保留掌控感。

这五件事,不是可以事后再补的优化项——它们是 Agent 产品从 Demo 走向生产的门票。 所有没做这些就上线的 Agent 产品,都是在用用户的信任赌博。