把 Agent 从能 demo 推到能产线上用,这半年我踩的坑,比之前做三年后端加起来还多。今天想聊的不是那种一眼就看穿的报错、崩溃,而是五类“看起来解决了”的假修复——当时确实改了代码、调了 Prompt、评测也过了,但上线跑一段时间,问题换个马甲又回来了,甚至带着新问题一起回来。
我做的这个 AI Agent 应用,本质上是一个基于大模型的多步任务智能体,需要自己规划、调用工具、维护中间状态。2026年国内 AI Agent 产品已经不少,从智能体中台到垂直场景助手都有。如果你也在从 0 到 1 搭 AI Agent,大概率会经历和我一样的阶段:原型很快、demo 很爽、一到真实流程就抓瞎。这篇文章不讲架构选型,也不讲怎么把 Agent 跑得更快,就聊这半年里最值得复盘的五次“假解决”。
1. Prompt 调好了?其实是把错误转移了——AI Agent 最容易踩的第一个坑
1.1 当时那个 bad case:模型开始“复读机”了
我在做客服辅助 Agent 的时候,遇到一个很典型的问题:用户在对话里说“帮我查一下 7 月 3 号的订单”,Agent 正确调用了查询工具,返回了订单列表,然后它在回复里把订单信息一个字不差地复述了一遍,接着又问“请问您需要我做什么”。
用户当然很烦。我同事也吐槽:这 Agent 像个复读机。
我当时的第一反应就是改 Prompt。在 system prompt 里加了一句“不要复述用户的话或工具返回内容,直接给出结论”,又补了一条 few-shot 示例,告诉模型“正确做法是:确认订单状态 + 给出下一步选项”。
试了两个坏例,确实解决了。模型不再复述订单,而是直接说“您的订单 7 月 3 号已经发货,需要我帮您修改地址吗”。我挺满意,又随手拿十几个 case 过了一遍,没有明显异常,就部署了。
1.2 复盘:一条 prompt 补丁,换来了三个新问题
一周之后,新的 bad case 出现了,而且不止一个。
第一个问题:模型开始“假确认”。用户说“我要把收货地址改成XX”,Agent 直接回答“好的,已为您修改”。实际上它根本没调修改地址的工具,因为我在 Prompt 里加了“不要复述,直接给出结论”,它把“直接给出结论”理解成了“所有环节都别做多余确认”,于是跳过了关键的二次确认步骤,甚至在工具未成功执行的情况下,直接向用户宣告成功。
第二个问题:另一个场景的准确率掉了。我们有一个内部知识问答场景,原来跑得好好的。我在客服场景加的“不要复述”指令,被模型泛化到了所有场景,它开始把本应详细解释的内容压缩成一句话,用户问“这个产品的退货政策是什么”,它只回“请查看官网”,因为这个 Prompt 让它觉得“说得越少越好”。
第三个问题:bad case 越修越多。我每看到一个新的错误输出,就往 Prompt 里加一条“不要……”禁令。两个月后,system prompt 已经有了三千多字,模型行为开始变得四不像:既不像复读机,也不像能正常对话的助手,输出风格从“过于啰嗦”变成了“过于随机”。
复盘之后我才明白,这个坑的本质是:我当时以为“错误输出”是一个可以被文字规则直接碾平的问题,但大模型的输出是概率性的,Prompt 补丁只是把概率分布往某个方向挤压。压掉一个坏行为的同时,会顶起另一个坏行为。我修复的是“表面输出”,不是“行为逻辑”。
1.3 现在我会先做根因归类,再动手改 Prompt
再遇到类似的 bad case,我不会急着改 system prompt。先花半小时做根因归类,至少区分这几种情况:
- 理解层问题:模型没读懂用户意图,比如把“改地址”理解成“查订单”。
- 上下文问题:该有的信息没进上下文,比如没有把用户地址传进工具调用。
- 工具链路问题:工具真正执行了,但模型没拿到结果,或者拿到了结果没有适当使用。
- 行为规范问题:模型理解意图,也拿到信息,但不知道怎么组织输出。
每种根因的解法完全不同。理解层问题可能要重设 few-shot;上下文问题要看 RAG 或者会话摘要逻辑;工具链路问题要去查工具调用的返回处理;只有行为规范问题才适合用 Prompt 约束。
另外,我建议每次改 Prompt 之前,先给 bad case 建一个最小复现用例。这个用例要能稳定复现旧问题,改完 Prompt 之后重新跑一遍。如果旧问题消失了但产生了新问题,至少能通过回归集发现。
在我现在的项目里,任何 Prompt 变更都会先跑一遍大约 60 条的基础回归集,跑完再放上线。这个习惯帮我挡掉了至少三次上述的“补丁后遗症”。
2. Function Calling 看着通了,真正要命的是没人校验工具执行结果
2.1 看起来通了:JSON 解析正确、工具参数合法
很多做 AI Agent 的同学都有这个体验:一开始最兴奋的时刻,是看到模型准确地在用户说“帮我订明早九点去浦东机场的出租车”之后,输出了一串工整的 JSON,里面是{"time": "08:30", "destination": "浦东机场", "pickup": "用户默认地址"}。
到了这一步,大家通常会说“通了”。
我也一样。我一度以“模型能生成正确的 function calling 参数”作为“工具调用能力 OK”的标志。我们当时给 Agent 接了一个预约服务,用户说“帮我约后天下午三点的保洁”,模型正确输出了时间和服务类型,我验证了 JSON 格式,觉得链路没问题。
但实际上,这只验证了半个链路。工具调用要真正解决问题,至少要包含四步:模型决定调用哪个工具、生成参数、工具实际执行、把执行结果反馈给模型。我当时的验证只覆盖了前两步,完全忽略了后两步。而这后两步,才是真正决定用户会不会得到服务的环节。
2.2 真相比想象中可怕:重复下单和假成功
有个真实事故让我记忆特别深。
预约服务上线没多久,我们发现有用户订单列表里出现重复预约,同一个保洁订单出现两次,时间、地址、服务内容完全一样。一开始我怀疑是用户手误,但看了日志才发现:Agent 在对话过程中调用了两次创建订单工具。一次是用户说“帮我约保洁”后调用,另一次是用户说“好的,那我要确认这个时间”后,模型竟然又调了一次创建订单接口。
为什么会这样?因为模型没有判断“这个订单已经创建成功”,它看到用户说“确认”,就认为是新的动作意图,于是又调了一次工具。我们把“工具调用成功”这件事只写在了日志里,没有回传给模型状态机,模型自然不知道已经创建过了。
还有另一个“假成功”案例:工具实际执行成功了,但返回结构因为后端接口升级变了。模型拿到的工具返回里没有时间字段,它就开始自己编:“已为您预约,时间:7月5日下午4点。”实际上后端分配的是 3 点。用户到了 4 点才发现没人来。
这些问题的共同点是什么?都出在“没有人对工具执行结果做校验”。当时我们只校验了模型输出 JSON 的合法性,没有校验“工具是否按预期完成、返回数据是否被正确消费”。
2.3 工具调用层的三件事:校验、幂等、审计
这个坑踩完之后,我在 Agent 的工具接入层加了三道保险,现在基本成了我的标准做法。
第一道是返回结果校验。不能只看 JSON 合法,还要对关键业务字段做断言。比如预约场景必须断言:创建订单返回里存在订单 ID;查询场景必须断言:返回里有实际数据条目,而不是空数组被误判成“查询成功”。这个校验不是模型做的,是代码层显式做的。模型拿到的是校验之后的结果。
第二道是幂等设计。像下单、支付、发消息这类有副作用的工具,必须支持幂等键。我通常用一个request_id,同一个用户同一轮对话里的同一意图,只会执行一次。这个 request_id 可以来自对话 ID + 意图序列号,由 Agent 框架生成,而不是依赖模型自己保证。模型不会每次都自觉避免重复,但框架可以。
第三道是审计日志。记录每次工具调用的完整链路:模型输出的原始参数、校验结果、实际执行结果、返回给模型的数据快照。这个日志不是给用户看的,是给自己复盘用的。没有它,你很难定位“到底是模型调用错了,还是工具执行错了”。
另外,凡是涉及资金、预约、身份修改这类高影响操作,我建议必须在工具执行前加一道人工确认闸门。不要让模型独自决定“直接执行”。表面上多了一次交互,实际上省掉了大量客诉和返工。
3. 评测集自我感动:为什么 90% 通过率不能说明你的 Agent 真的能用
3.1 手工试 10 个 case,觉得“效果还行”
做 Agent 应用的人,几乎都经历过这种时刻:拿 10 个测试问题跑一遍,8 个结果满意,于是结论是“效果还行”。我当时也是这么干的。
后来发现,这个“效果还行”几乎没有参考价值。原因有三条。
第一,这 10 个 case 大概率是你自己写的,你心里早就知道期望输出是什么,你会下意识避开那些会让系统难堪的提问方式。第二,10 个样本太少,任何系统在 10 个样本上的表现都有极大偶然性。第三,手工跑意味着没有回归机制,你今天改了一个 Prompt,明天可能就把一周前修的 bug 又带回来了,但你不知道。
我做客服辅助 Agent 的时候走过一个大弯路:连续两周都在“修 bug—上线—发现新 bug—再修”的循环里,效率很低。后来我把历史 bad case 全部翻出来,发现其中至少有三分之一,是两周前我已经“修好并验证过”的问题。因为我没有评测集和回归机制,那些修复在一轮又一轮的 Prompt 调整中悄悄丢了。
3.2 LLM 自动评测的 90 分,怎么来的
被手工测试坑过之后,我去搭了一套大模型自动评测流程。简单说,就是用 GPT/Claude 这类模型当评委,给 Agent 的回复打分。第一次跑完,成绩很漂亮:90 分。团队一看都觉得效果达标了。
但我越用越觉得不对。因为同一个 case,我换了不同的评分 Prompt 之后,分数波动能达到 20 分以上。再仔细一看,那 90 分是怎么来的?评分 Prompt 里写了“根据准确性和完整性打分”,但没有规定“准确性”的具体含义。LLM 评委对模糊的评分维度,天然倾向于给高分。尤其是当 Agent 回复得很流畅、措辞很礼貌时,评委很容易忽略事实错误。
我还试过让同一批 case 跑两次评分,只有一次真正检查了工具调用结果有没有错,另一次只看语气是否友好。这就是 LLM 评估的“自嗨属性”:你告诉他什么重要,他就看什么;你没告诉他的,他自动忽略。
3.3 一套评估体系从 50 条高质量数据开始
我现在搭的评测体系,不再追求大而全,而是先用 50 到 80 条高质量数据把基线立住。这些数据全部来自真实会话,不是我自己编的。每条数据包含:用户输入、期望工具调用序列、期望最终回复、备注的边界条件。
评测维度分开看,不再只给一个总分。至少分成四个维度:
- 任务完成率:用户的目标是否真的被解决了,不只看了回复文本,还看中间的工具调用记录。
- 工具调用准确率:该调的工具调对了没有,该传的参数传对了没有,有没有多余调用、重复调用。
- 安全性:有没有幻觉承诺、有没有未经确认就执行高影响操作、有没有泄露不该泄露的信息。
- 成本效率:平均对话轮数、平均 token 消耗、平均工具调用次数。
这四个维度分开打分,你会发现之前那个 90 分根本站不住。可能任务完成率只有 70%,其中还有两次重复调用工具,成本效率很低。
评测跑完还要抽样人工复核,尤其是那些 LLM 评委打了高分的 case。我一般每周抽 20 条,自己看一遍真实日志,确认“高分不是假高分”。这套机制比一个大数字有价值得多。另外提醒一句:评测集也要持续更新,每产生一个真实 bad case,如果确认值得防回归,就把它加进评测集。评测集不是一次建完就不动的。
4. 记忆加上去,Agent 反而更糊涂了——上下文污染实战复盘
4.1 加了记忆,结果模型抱着过期的偏好不放
做多轮对话 Agent,早期的苦恼是“忘得快”。用户上一轮说“我预算 5000 左右”,下一轮问“帮我推荐一台笔记本”,Agent 完全不记得,又按默认预算推荐了一堆 3000 到 10000 的。
所以很自然地,我去加了“记忆”。方案也很朴素:把每轮对话都拼到 context 里,再把用户关键偏好抽出来存进向量库,后续对话先检索再拼装。听起来很合理,对吧?结果上线之后,出现了大问题。
有一个用户第一次咨询时说“预算 5000”,一周后再次咨询时明确说“预算提高到了 8000”。Agent 检索到了旧记忆,居然把 8000 预算的用户又推了一堆 5000 价位的产品。用户问“怎么还是给我看 5000 的,我说了预算已经调到 8000 了”,Agent 还振振有词:“根据您之前的预算偏好,我优先推荐了这个区间。”
这就是典型的“历史记忆污染当前决策”。我把记忆当成了一种只增不改的静态资料,忘了记忆是有时效性的,是会过期的。用户的偏好变化、状态重置、甚至是临时性需求,都需要被识别和更新。
4.2 长上下文是“信息过载”,不是“信息丰富”
还有一个隐蔽的问题:为了“不忘记”,我把所有历史都拼进 context。Session 长了以后,整个上下文达到几万 token。效果怎么样?模型确实记得这轮对话里自己说过什么了,但它也开始“过度关注”历史细节,经常拿早期对话里的次要信息来回答当前问题,甚至因为上下文太长,注意力被稀释,回答质量下降,响应时间也变长了。
我后来才意识到:对于 Agent 来说,上下文不是越丰富越好,而是越精确越好。大模型在长上下文中,不会自动区分“哪条信息最重要”。你把十条记忆都给它,它可能拿一条不相干的旧信息盖过当前的事实。这和人类一样,你手里拿着一堆资料,反而找不到现在需要的那一页。
所以长上下文真正要解决的问题不是“存够多”,而是“每次放进 context 的信息,是否都是当前决策所需的最小子集”。
4.3 记忆分层:工作记忆、事实存储、长期偏好
现在我的记忆方案分了三层,分别对待。
工作记忆:只保留当前任务闭环内的关键信息,比如正在进行的订单、用户刚说的紧急要求。任务结束就清零,不累积到下一轮无关对话。
事实存储:保存那些有明确时间戳、可验证的事实,比如“用户 6 月 1 日下单订单 A”“用户 7 月 10 日将预算改为 8000”。每条事实都带时间和来源。凡是新写入的事实和旧事实冲突,不是简单覆盖,而是把旧事实标记为“已过期”,并保留过期记录。这样模型检索时能知道“8000 是最新说法”。
长期偏好:存那些跨会话稳定成立的偏好,比如“用户偏好安静环境”“用户常用地址”。这类信息更新频率低,但写入时也要有置信度判断。不能因为用户随口一句“最近想省钱”,就把“偏好低价”写成长期偏好。
三层记忆写入和读取都有不同的策略。工作记忆随对话走,事实存储按时间排序、冲突标记,长期偏好需要多次确认或用户显式声明才写入。检索时也不是无脑全返回,而是限制返回条数(我通常只取 3 到 5 条最相关的),并且每条都带上时间上下文,让模型知道“这条是三个月前的,仅供参考”。
这个分层方案不能彻底解决记忆污染,但至少把“过期偏好干扰当前决策”这类问题挡在了外面。最重要的是,我开始用“记忆不是越多越好”这个视角去看问题,而不是一上来就往 context 里塞东西。
5. 重试兜底看似稳了,其实是把故障时间拉长了三倍
5.1 重试三连,用户等了三倍时间
做 Agent 应用,外部依赖非常多:大模型服务、业务 API、向量库。只要其中一个抖动,Agent 就可能出问题。我最开始的做法很粗暴:在 Agent 框架里给所有外部调用都加了重试,失败就重试 3 次,每次间隔 1 秒。
结果一次真实的故障让我印象非常深。那天下班后,业务 API 因为数据库连接池满开始报错。我设置的 3 次重试,让每个请求在失败之前都要等至少 3 秒到 5 秒;而 Agent 在一个任务里可能连续调用 4 到 5 个工具,每个工具都失败都重试,用户面对的就是一个持续卡顿、迟迟不回复的 Agent。
最后虽然没有一个请求直接崩溃,但用户侧体验是“Agent 死了”。没人会感谢一个“延迟 30 秒后告诉你请求失败”的助手。我意识到:重试看起来让系统更稳了,实际上只是把错误延迟了;错误并没有消失,用户体验反而更差了。
5.2 重试的三类错误姿势
后来我把重试逻辑单独拿出来复盘,发现有三类典型错误。
第一类是无差别重试。不管错误类型是什么,只要失败就重试。但很多错误是“确定性失败”,比如参数校验失败、用户权限不足、业务规则不允许。这类错误,你重试一百次结果都一样,只会白白消耗资源和时间。
第二类是无限次重试,或者重试次数太多。有些外部服务长时间不可用时,重试只是不断加剧压力。我记得有一次外部接口持续 500 报错,Agent 每轮都重试 3 次,再配合多轮任务编排,瞬间打上去几十个请求,把原本只是轻微故障的服务直接打挂了。
第三类是重试不带退避策略。失败后立刻重试,连续撞墙。正确做法是用线性或指数退避,比如第一次等 1 秒、第二次等 3 秒、第三次等 8 秒。但即便这样,也要有上限。
更严重的是,重试失败之后,大多时候我们只是返回一个模糊的“系统繁忙,请稍后再试”,然后把用户晾在那里。这等于把真正的故障事实藏了起来,用户不知道到底发生了什么,也无法决定要不要等待。
5.3 容错不是让系统永不失败,而是失败得明明白白
我现在处理容错的思路变了。核心目标是:让 Agent 在失败时尽快、明白地告诉用户,而不是通过重试把失败变隐形。
先区分可重试错误和不可重试错误。网络超时、HTTP 503、连接重置这类暂时性错误,可以重试,但要设上限和退避。参数校验失败、业务规则违反、模型连续输出非法 JSON 这类确定性错误,不重试,直接走失败分支。
再给 Agent 设计明确的失败降级路径。工具调用失败后,Agent 不能卡住,要输出一句用户可以行动的话,比如:“查询服务暂时不可用,我这边没法马上看到您的订单。您可以留下手机号,我会在恢复后短信通知您。”这句话不是模板套话,而是真的调用了一个“记录待办”的工具,把用户请求转成离线任务。这样用户至少知道自己的请求没有丢。
还要给 Agent 设置“最大努力”边界。比如整个任务最多执行 10 个工具调用、最长不能超过 60 秒。一旦超过,Agent 主动终止,向用户交代“已完成哪几步、还剩哪几步没有完成”。这种诚实反而比硬撑到底更让用户信任。
熔断也很重要。当一个外部服务在 30 秒内错误率超过 30%,我会触发熔断,后续请求直接快速失败走降级路径,不再重试。这相当于给下游服务一个喘息时间,也避免 Agent 在自己不知道的情况下连环调用一个已经不健康的服务。
容错这件事,本质上不是让系统永不失败,而是让系统在失败时,行为可预期、用户可感知、后果可控制。
6. 如何识别“看起来解决了”:来自半年的几个自我检查方法
6.1 “假解决”的三个明显信号
踩完这些坑,我开始反思一个通用问题:怎么在第一次被坑的时候就识别出“这是假解决”?
我总结了三个信号,一旦出现,就要警惕。
第一个信号:同一个问题反复出现,只是换了场景。比如你修好了客服场景的重复请求,一周后订单场景又出现重复请求。这不是模型变笨了,而是你上次的修复没有触及通用机制,只是针对单一场景打了个补丁。
第二个信号:你只验证了“输入输出”,没有验证“中间过程”。比如你只看了 Agent 的最终回复,没有看它到底调了几次工具、每次都干了什么。出现“回复看起来对,但实际没做该做的事”的情况,几乎可以断定是假解决。
第三个信号:你只跑了 happy path,没有跑异常路径。比如用户中断了对话、用户提供了模糊信息、工具返回了空数据、外部接口超时。如果你从来不主动测试这些路径,那你的 Agent 大概率只是“在理想条件下没问题”。
6.2 把“看起来解决了”变成“真的解决了”的四步检查
现在我在收尾一个修复任务之前,会先按四步过一遍。
第一步,写复现用例。这个 bad case 必须能稳定触发,写成一个固定的测试用例,存进项目里。如果连稳定复现都做不到,那你根本没有资格说“解决了”。
第二步,做根因判断。明确说出问题属于哪一层:Prompt 行为问题、工具执行问题、记忆检索问题、还是容错机制问题。说不清楚根因的修复,往往就是只修了表面。
第三步,做回归验证。拿一个基础回归集跑一遍,确认修复没有破坏其他场景。如果项目还没有回归集,那当务之急是建一个,哪怕只有 30 条。
第四步,上线后持续观察一段时间。不要在部署当天就宣布完成,至少观察一周的线上日志。因为很多问题只在真实流量分布下才会暴露。
6.3 团队协作里值得坚持的三个小习惯
最后聊几个团队层面的习惯,帮大家少走弯路。
第一个习惯:每次修完 bug,把 bad case 和修复说明一起提交到代码仓库。我们团队现在有一条不成文规则:没有附上最小复现用例的修复,不允许合入。这逼迫每个人把“为什么这样解决”落到纸面上。
第二个习惯:每人每月轮流当“评测集委员”,负责收集新的真实 bad case,更新回归集。这个活儿不能全靠一个人干,否则评测集很容易停在自己关心的那几个场景里。
第三个习惯:线上出问题之后,先别急着修,先花 10 分钟写一个“问题定位单”,写清楚现象、影响范围、可疑原因、验证方式。这个单子不一定要发给谁,就是让自己在动手之前把脑中的“想当然”清空一遍。我踩过的那些坑,几乎都在“没写定位单就直接加代码”的时候出现的。
做 AI Agent 应用,最有意思也最折磨人的地方,就是它没有标准答案。你永远不知道下一个 bad case 长什么样。但半年下来我最大的体会是:真正值钱的不是你能把一个错误修好,而是你能识别出自己是不是在“看起来解决了”的路上自嗨。希望这五个复盘,能帮你少走一点我们走过的弯路。