为什么不能把 AI 调用
和业务逻辑混在一起?
直觉上,"把所有功能写在一起"是最快的。但对于 Agent 系统来说, 这个选择会让你在第一次迭代之后,就陷入"改哪里都会出问题"的泥潭。
想象你们公司做了一个出行 Agent,用户说"帮我订明天北京到上海的高铁", Agent 调用 GPT-4o 理解意图,查询余票,完成支付,发确认短信。 整个逻辑写在一个大函数里,200 行代码,干净利落,功能跑通了。
三个月后,公司决定把 GPT-4o 换成 Claude——性价比更好。 工程师打开代码一看:AI 调用的代码和业务判断、数据库操作全搅在一起, 根本没办法只换模型,必须把整个函数重写一遍。 同时,测试团队说他们没法单独测"意图理解"那一步,因为跑一次测试就要完整走一遍订票流程,太慢了。
这就是"没有解耦"的代价——系统变成了一块板砖:整体换,或者不换。
问题到底出在哪里?
根源在于:这个系统把三件性质完全不同的事混在了一起—— "让 AI 理解意图"、"执行业务规则"、"操作数据库和外部服务"。 这三件事各有各的变化节奏:
AI 推理层
模型会换、prompt 会改、工具调用方式会迭代。这一层变化最频繁,几乎每个月都在动。
业务逻辑层
订票规则、退改签策略、定价逻辑。跟随业务节奏变化,通常按季度或功能上线来调整。
数据与通道层
数据库结构、推送服务、支付接口。相对稳定,但一旦要改,影响面最大。
当这三层混在一起时,任何一层的改动都会"惊动"其他两层。 这不是工程师不够谨慎,而是结构本身决定了它必然变得越来越脆。
分层之后,系统长什么样?
分层之后,每一层都可以独立修改、独立测试、独立替换。 换模型只改 AI 层;调整退改签政策只改业务层;更换数据库只改数据层。 其他层完全不知道有变化,也不会受影响。
实际上能带来哪些变化?
- 换模型需要改多处业务代码,容易引入新 bug
- 单测无法单独跑 AI 部分,每次测试要走完整流程
- 定位 bug 时不确定问题出在 AI 还是业务规则
- 多人协作时,工程师互相踩脚
- 换模型只改一个文件,改完跑 AI 层的单测即可
- 可以 mock 掉 AI 层,专门测业务逻辑
- 报错日志直接告诉你是哪一层出了问题
- AI 工程师和业务工程师并行工作,互不干扰
关于解耦的三个决策
- 我们的 AI 调用和业务逻辑有没有在代码层面物理隔离?(不是注释,是真正的模块分离)
- 如果明天需要换一个模型,工程师能不能在不改业务代码的前提下完成?
- 测试团队能不能在不真正调用 AI 的情况下测业务逻辑?
Agent 出错后
系统怎么"软着陆"?
最大的陷阱是:设计系统时只想"成功路径", 没想清楚失败的时候会发生什么。 对于 Agent 系统,这个问题比传统系统严峻得多。
传统系统:你写了一个计算器,输入两个数字相加。 如果输入的是字母,程序会报一个明确的错误——每次都一样,可以复现,可以修。
Agent 系统:用户说"帮我查一下明天北京到上海最快的车"。 这句话 99% 的情况下 Agent 都能正确理解。 但有 1% 的情况——可能因为这次用词有点含糊,可能因为当天模型负载高了点—— Agent 把"最快"理解成了"最便宜",帮用户查出来的是最低价车次。 下次用同样的话再问?可能又正确了。
这就是概率性失败的核心特点:不稳定、难复现、单元测试覆盖不了。 你需要一套专门针对这种失败的容错设计。
Agent 系统的失败有哪几种?
把 Agent 可能出的问题分类,大概是三种。搞清楚分类,才知道每种该怎么处理:
| 失败类型 | 具体表现 | 可以重试吗? | 推荐处理方式 |
|---|---|---|---|
| 工具调用失败 | 调用查票接口超时、网络断了、第三方服务挂了 | ✓ 通常可以 | 指数退避重试,超过次数后降级 |
| 模型输出格式错误 | 返回的 JSON 格式不对、缺少必要字段 | ✓ 有限次数 | 格式校验 + 自动修正或重试 |
| 模型理解偏差 | 把"最快"理解成"最便宜",任务方向跑偏 | ⚠ 重试可能没用 | 人工确认节点 + 结果验证 |
容错的三个层次
好的容错设计不是"出错了报个错就完事",而是像飞机的应急系统一样, 有层层的备选方案——只有所有备选都失效了,才最终告知用户"我做不到"。
重试策略里有个细节经常被忽视
"失败了就重试"听起来简单,但有一个经常被忽略的问题: 如果同时有 1000 个 Agent 实例在运行,它们同时失败,然后同时在第 1 秒重试—— 这就像 1000 人同时涌进一扇门,反而把服务压垮了。
重试等待时间 = 基础等待时间 + 随机的一小段延迟(比如 0~500ms)。 这样 1000 个实例不会在同一时刻重试,请求被打散,下游服务的压力大幅降低。 这个细节听起来小,但在高并发场景下,它是系统能不能扛住的关键。
出错时,用户应该看到什么?
这是产品设计里容易被工程视角忽视的部分。容错设计的终点不是"系统不崩溃", 而是"用户不失控"。用户体验的好坏,取决于出错时给用户看到的是什么。
"发生了未知错误,请稍后重试。"
用户完全不知道发生了什么,也不知道"稍后"是多久,更不知道下一步该怎么办。
"查询列车信息时连接超时,已为您保存搜索条件。您可以:① 等 30 秒后点击重试 ② 联系客服 400-xxx 人工处理"
用户知道出了什么问题,知道下一步能做什么,感觉自己还在掌控中。
容错设计需要在上线前想清楚的事
- 每个关键 Agent 步骤都有明确的超时时间吗?(不设超时 = 用户可能永远等待)
- 超时或失败后有降级方案吗?还是直接报错?
- 重试会不会造成重复操作(比如重复扣款、重复发短信)?
- 出错时给用户的提示,是否包含了下一步行动?
重试的时候
怎么保证不会做重复的事?
"幂等性"这个词听起来很学术,但它解决的问题每个人都懂: 网购时支付超时,你点了两次"付款",会不会扣两次钱? 这就是幂等性问题。在 Agent 系统里,这个问题出现的频率会比你想象的高得多。
出行 Agent 帮用户完成了高铁订票。订单提交成功,但调用支付接口时网络超时—— Agent 不知道钱有没有扣成功。按照容错逻辑,Agent 触发了重试,又调用了一次支付接口。
如果支付系统没有做幂等设计:用户被扣了两次钱,投诉热线爆炸。
如果支付系统做了幂等设计:第二次支付请求进来,系统识别出"这和第一次是同一笔交易", 直接返回第一次的结果,不重复扣款。
幂等性的核心机制是什么?
理解起来其实很简单:给每一次有副作用的操作,分配一个全局唯一的 ID。 下游系统用这个 ID 来判断"这件事我处理过没有"——处理过了直接返回之前的结果,没处理过才真正执行。
这个 ID 通常叫 idempotency_key(幂等键)。
就像你在超市结账时,同一张小票只能结一次——
收银台看到小票上的号,知道这是哪一笔,不会因为你多扫了一次就多收钱。
哪些操作需要做幂等设计,哪些不用?
判断标准很简单:这个操作执行两次,结果会不会和执行一次不一样? 如果会——就需要幂等设计。
| 操作类型 | 例子 | 天然幂等? | 需要额外设计? |
|---|---|---|---|
| 查询类 | 查余票、查路况、查订单状态 | ✓ 是 | 不需要,放心重试 |
| 支付类 | 扣款、退款 | ✗ 否 | 必须加 idempotency_key |
| 创建类 | 创建订单、创建用户 | ✗ 否 | 必须加唯一键避免重复创建 |
| 通知类 | 发短信、发推送、发邮件 | ✗ 否 | 必须做去重,避免用户收到多条相同消息 |
| 删除类 | 删除数据、取消订单 | ⚠ 通常是 | 删除同一条记录多次结果相同,但要确认业务逻辑 |
怎么知道 Agent
在任意时刻"在做什么"?
可观测性(Observability)解决的问题是:系统出了问题,你能不能搞清楚"到底发生了什么"。 对 Agent 系统来说,这个能力比传统系统重要 10 倍——因为 Agent 的失败可能在任何一个决策步骤里悄悄发生。
用户投诉:Agent 帮他订了一趟停靠站很多的普速列车,但他明明说的是"最快的"。 你打开后台,只看到两条日志:
14:32:01 [INFO] 用户请求:查询北京→上海列车
14:32:04 [INFO] 订单创建成功:T104次
中间发生了什么?完全不知道。 模型是怎么理解"最快"的?它调用了什么工具?工具返回了什么数据? 没有任何记录,你只能猜。这就是可观测性缺失的代价——出了问题只能靠猜。
Agent 系统需要记录"五个维度"
传统系统的日志只需要记录"做了什么"。但 Agent 系统的决策链路更长, 每一步都需要记录五个维度,合在一起才能还原事故现场:
收到了什么
这一步的输入是什么?用户原始请求、上下文状态、历史对话摘要。
怎么想的
模型的推理过程——它认为用户的意图是什么、为什么选择调用这个工具。
做了什么
调用了哪个工具、传入的参数是什么、完整的请求内容。
得到了什么
工具的返回结果、是否成功、原始响应内容。
花了多久
这一步的耗时、消耗的 token 数、API 调用费用(用于成本追踪)。
一条好的 Agent 日志,长什么样?
有了这条记录,再回到用户投诉那个场景:只需要搜 trace_id,
就能看到 Agent 的推理是什么、传入查询的参数是否正确、工具返回了什么。
从"大海捞针"变成"按图索骥",定位问题的时间从几小时缩短到几分钟。
它是出了问题时,你唯一能依赖的救命稻草。
一个容易被忽视的需求:成本追踪
Agent 系统的每次 LLM 调用都会产生费用。如果没有精细的 token 使用记录, 你会在某个月底突然发现 API 账单暴涨,却根本不知道是哪个功能、哪类用户造成的。 把 token 消耗和费用写进每一条 trace,是日后控成本的基础。
哪些事
Agent 不能自己做主?
"让 AI 全程自动化"是一个很有诱惑力的目标,但在真实产品里, 盲目追求"完全自动"往往会在最关键的地方出问题—— 用户对 AI 的信任,是一点一点建立起来的,不是一下子给出去的。
用户随口说了句"帮我看看有没有更便宜的机票",Agent 不仅查到了, 还直接帮用户退了原来的票、订了新的。 用户惊了:我只是让你查一下,没让你真的换啊! 而且新票的时间不太方便,退票手续费也扣了……
这不是 Agent 做错了——从技术上它"完成了任务"。 但它做了用户没有授权的操作,这种体验会让用户永远不再信任它。
用两个维度来判断:谁来做这个决定?
判断一个操作该不该交给 Agent 自主执行,用两个维度: 这件事影响有多大? 以及 出错了能不能撤回? 交叉来看,就得到了一张决策矩阵:
人工确认节点怎么设计,才不让用户觉得烦?
这里有一个设计上的平衡:确认节点太少,用户感到失控;确认节点太多,用户觉得 Agent 没用。 关键是让确认节点出现在对的位置,而且确认的交互要做到让用户一眼看懂、一秒做决定。
"我将执行以下操作,是否确认?
操作类型:预订服务
状态:待确认"
用户完全不知道"预订服务"是什么,要点"取消"还是"确认"?
"为您预订:G1次 · 5月28日 · 北京南→上海虹桥 · 二等座靠窗 · ¥553"
[确认购票] [换一班] [取消]
用户一眼看清楚要订什么,有明确的行动选项,不只是"确认/取消"。
信任是一步步建立起来的
出行场景里用户对失误的容忍度极低——错一次关键操作(比如退了不该退的票), 用户可能永远不再用这个产品。所以信任的建立,应该按阶段来:
第一阶段:Agent 只做"建议者"
给用户推荐选项,用户来决定和操作。Agent 不直接执行任何写入操作。让用户慢慢体验到 Agent 的推荐是靠谱的。
第二阶段:Agent 执行,事前确认
Agent 规划好操作方案,告诉用户"我要做 X、Y、Z",用户确认后统一执行。信任感建立后,这一步确认摩擦会显著降低。
第三阶段:Agent 自主完成,事后通知
用户主动授权特定类型的操作(比如"帮我自动续订月票"),Agent 完成后发通知。这是高信任状态下的最终形态。
这五个设计原则,看起来是技术问题,本质上都是工程风险管理。
解耦,是把变化的代价控制在一个可预期的范围内。 容错,是承认"系统一定会出错",并提前设计好出错后的路径。 幂等性,是保证"重试不会造成新的伤害"。 可观测性,是在出了问题时,你有能力搞清楚发生了什么。 人在回路,是在信任还没有建立起来之前,给用户保留掌控感。
这五件事,不是可以事后再补的优化项——它们是 Agent 产品从 Demo 走向生产的门票。 所有没做这些就上线的 Agent 产品,都是在用用户的信任赌博。