团队做 AI Agent 应用的时候,几乎都会撞上一个很微妙的墙:同一个问题,上午问和下午问,Agent 给的答案不一样。有时候只是措辞变了,有时候连结论都翻车。甚至同一次会话里,你把同一个 request 发给同一个模型,拿回来的结果都可能长得不一样。
这不是 bug,而是大模型应用的底层属性。但“属性”不代表“没法治”。这篇文章我会从一次真实的事故讲起,拆一下 Agent 一致性的病根到底在哪,再给出我落地的一套组合方案——从采样参数、结构化约束到系统级的评测与兜底。
1. 场景重现:我在生产环境里撞上的“变脸”事故
1.1 一次典型的线上故障始末
去年年中,我做了一个面向企业客户的智能客服 Agent。它的核心任务很简单:根据知识库内容,回答“发票报销流程是什么”“保修政策覆盖哪些范围”这类高频问题。老板对它的要求也很简单:同样的问题,今天答的和明天答的要基本一致。
结果上线第二周,就有客户投诉了——客户上午问“笔记本进水了可以保修吗”,Agent 说“液体损坏不在保修范围”,下午又问了一遍同样的问题,这次 Agent 答“进水属于意外损坏,建议联系售后评估”。
问题不在知识库出错,而是同样检索到的几段文档,两次推理选择了不同的侧重点。我觉得这是小事,但客户不这么看,他们把这种“说话不算数”的体验直接上升到“系统不可靠”。这时候我才真正意识到,对于 To B 场景,确定性的重要性可能比准确性还高。因为准确性是你的上限,而一致性是用户每天都在感知的下限。
1.2 一致性问题的三种日常表现
那次事故之后,我特意在多个项目里做了回归观察。Agent 的不一致基本集中在三个层面:
- 语义不一致:同一个问题,两轮回答的核心结论相反,或者一个肯定一个含糊。最严重,往往出现在多个知识片段互相有重叠时,模型每次采信的片段不同。
- 格式不一致:第一轮给你一个 bullet list,第二轮给你一段纯文本。用户感知不明显,但下游系统做解析时容易直接报错。
- 过程不一致:同一个任务,Agent 的 tool 调用路径每次都不同——有时先检索后计算,有时先计算后检索,甚至这次调了两次检索,下次直接跳过了。过程不一致导致你很难调试,因为每次复现问题的链路都不一样。
这三种不一致,根源在模型的生成机制,也被工程架构放大。往下拆。
2. 拆解病根:为什么同一个输入会跑出不同输出
2.1 罪魁祸首是解码器的采样策略
大模型不是查表,而是在每一步生成时,先计算出所有候选词的概率分布,再按策略选一个。当你把 temperature 调到大于 0,模型就不是“选概率最高的那个”,而是“按概率分布随手摸一个”。温度越高,低概率词被摸中的机会越大,输出花样越多。同理,top_p(核采样)会动态圈定一个“摸彩票范围”,只从累积概率超过 p 的那些词里抽,范围越窄越稳定。
这里有个很多教程没点透的地方:即使 temperature 设为 0,你也不一定拿到确定性的输出。一方面,部分模型服务端有 beam search、长度惩罚等参数参与决策;另一方面,GPU 推理时的浮点计算顺序、batch 内并发、kernel 实现差异,都会造成极微小但可观察的结果漂移。我自己实测过某个主流模型服务,temperature 调 0 后同一请求跑 20 次,仍有大约 5% 的请求在措辞或分点顺序上有细微出入。所以“温度设 0 就万事大吉”是完全不现实的。
2.2 你所谓的“同一输入”,其实每次都不同
我们总以为“同一问题=同一输入”,但在 Agent 架构里,真正送进模型的上下文远不止用户的一句话,它还包括:系统提示词、历史消息、检索返回的文档片段、工具调用结果、时间戳、随机 ID、甚至动态拼进去的当天日期。
我在排查上面那个“进水保修”案例时,发现罪魁祸首是 RAG 检索环节——同一个问题的向量检索结果,两次召回的 top 文档片段有细微的排序差异,模型因此采信了不同段落。这种变量藏得最深:输入端看起来一字未变,实际语义上下文已经换了内容。上下文里任何一段检索文本的增删,都会导致后续生成结果的分布明显改变。
另外一个容易被忽略的变量是上下文窗口的截断策略。当历史消息越来越长、逐渐逼近窗口上限时,Agent 框架会做“剪枝”——有时删最早的消息,有时按重要性压缩摘要。剪枝策略不同,模型“看到”的上下文其实就不同了,输出自然跟着变。这类问题在长会话 Agent 里几乎是必然出现的。
2.3 模型版本的“静默换血”是隐形杀手
还有一类更隐蔽的漂移,很多团队踩了坑才后知后觉:某个模型供应商今天给你用的权重,和三个月前给你用的权重,根本不是同一套。有些服务会渐进式灰度更新模型,API 的版本号没变,但实际在跑的 checkpoint 已经换了。
这类“静默换血”的破坏力我体会很深。某次升级后,Agent 在“拒答边界”上的表现发生了明显变化——原本能回答的问题开始变得保守,原本拒绝的问题又开始给了模糊建议。代码一行没动,行为却变了。所以后续我在系统里强制锁定了具体的模型版本(通过日期后缀、版本参数等方式),并做了一致性回归测试,才把这类风险控制住。
2.4 先定义清楚:我们到底在修哪种一致性
在动手治理之前,我建议团队先坐下来把“一致性”这个词拆开。在我看来至少有两类目标,它们的技术手段完全不同:
| 目标类型 | 关注点 | 典型问题 | 核心手段 |
|---|---|---|---|
| 结果一致性 | 每次输出的结论、数据是否相同 | 保修结论翻车 | 确定性解码、结构化约束 |
| 过程一致性 | 每次推理步骤、工具调用是否稳定 | 调试时无法复现 | 思维链固定、路由规划锁定 |
还有一个更细分的“格式一致性”——它下游系统能否稳定解析,我一般把它归到“结果一致性”里用结构化输出约束去解决。
如果连自己到底要哪个一致性都没想清楚,就很容易陷入“这轮修好了,下轮又冒出来”的怪圈。例如你花了两天把输出格式稳定成 JSON,结果第二天发现在部分请求里结论本身错了——这是两种病,得分开治。
3. 怎么治:从源头压制随机性的工程手段
3.1 代价最低的一招:把解码参数往“铁板一块”上调
第一道防线是采样参数。我在多数 Agent 生产任务里这样设置:
temperature = 0(或者服务端允许的最低值,如 0.1)top_p = 1——有些实现里 top_p 和 temperature 联动,干脆关掉核采样- 关闭服务端的自动抽样开关(如果 API 有暴露 seed 或 best-of 参数,优先使用)
- 关闭“自动重试”。我见过很多 SDK 默认在请求超时会静默重试,这个重试可能被路由到不同的推理节点,导致同一请求实际执行了两次、返回不同结果,而业务层却只看到了其中一次
以 OpenAI API 为例,设置seed参数能在多数情况下让输出更接近确定(但不能绝对保证)。Anthropic 系列没有提供 seed 时,就只能靠 temperature 最低值 + 后置校验来逼近。
不过我必须泼一盆冷水:参数调法只能缩窄波动范围,压不到零。它适合“本来就不需要创造性发挥”的任务,比如信息查询、单据提取、SQL 生成。而对于需要发散的任务,强压温度反而会让输出变得呆板、重复,甚至更早陷入“复读机”式循环。后面我讲的系统性方案才是主菜。
3.2 给模型装上“JSON 枷锁”:结构化输出的威力
如果你只想花一小时改善一致性,我最推荐先做结构化输出约束。
原理说穿了很直接:不一致的来源是自由空间太大。模型有一万个词可以选,你给它一百万个可能的方向,它当然会飘。结构化输出把自由空间压缩成一个明确的模板——比如“你必须输出 JSON,字段只有 two 个,一曰 result,类型 boolean,二曰 reason,类型 string,长度不超过 50 字”——模型的“变脸”空间就变得极其有限。
具体落地方式有三档:
- Prompt 层约束:在系统提示里写死格式要求,并给出一个 few-shot 范例。成本最低,但约束力最弱,模型偶尔还是会不听话。
- API 结构化模式:使用 OpenAI 的
response_format或同类功能,让服务端强制生成合法 JSON,甚至能定义 JSON Schema,字段级约束更严格。 - 解码层硬约束:通过开源推理框架里的约束解码,在每一步生成时把非法 token 的概率直接置零,从根本上让模型“说不出格式以外的话”。
我常用的组合是 2+3:API 的 structured output 做第一层,应用层的 Pydantic 校验做第二层。一旦解析失败,直接触发一次带“上轮错误信息”的重试,而不是把原样回复交给用户。实测下来,格式一致性可以从“看运气”提升到 99% 以上。
3.3 把思维链变成“固定流水线”:过程一致性的工程解
对于复杂任务 Agent,光锁输出格式不够,因为你还要保证工具调用链稳定。这个问题的本质是:模型在每一步“自由想下一步做什么”时,随机性就被放大了。
我的做法是“把自由推理改成流水线作业”。举个例子,我做一个“产品对比咨询 Agent”时,把任务流程拆成固定的四步:
- 先调检索工具,找出所有相关产品文档;
- 基于检索结果做参数对比,输出对比表格;
- 根据用户预算执行过滤;
- 生成最终建议。
每一步都写成一个独立的 system 指令块或一个自定义 tool,Agent 必须按顺序调用。如果模型试图跳步,外层控制器直接拦截。再配合工具调用前的“计划复述”——让模型先输出一句“我打算先做什么、再做什么”——既方便我审计,也让模型在“说出计划”这个行为中自我约束了执行路径。
它的代价是灵活性下降:当用户的问题超出了流水线的覆盖范围,Agent 可能处理不了。所以我会设置一个意图路由,普通问题走固定流水线,非常规问题才放行到自由模式。这个折中在工程上是划算的——用户 90% 的常规诉求都被稳定覆盖了,剩下 10% 的不确定问题本来就不该追求一致。
3.4 缓存不是缓存:让高频问题的答案“焊死”在数据库里
这是我最想强调的一点:对于高频且答案相对静态的问题,最好的一致性方案是彻底不经过模型生成。
我在这套智能客服里加了一个“确定性应答层”——当用户问题击中知识库的“高置信匹配”区间(比如语义相似度超过某个阈值),系统直接把预先审核过的标准答案返回,模型只负责对特殊情况做补充解释。
实现时有两点经验:
- 别只做字面缓存。用户问“发票咋报”和“报销发票流程是什么”,字面完全不一样,但语义几乎一致。我当时的方案是对用户 query 做 embedding,再跟库里所有“已审核问答对”做向量检索,命中高相似度就直接返回预置答案,没有再丢给模型。
- 给缓存答案带上版本号和维护时间。知识库一旦更新,旧缓存要能自动失效。我在数据库里给每条标准答案存了生效时间区间,查询时顺带判断时效性,否则会出现“上个月的标准答案现在还挂着”的尴尬。
这一层给系统带来的收益不只是稳定性,还顺带把 token 成本压下来一大截。高频问题原本每次都要跑一次完整的大模型推理链路,现在直接走向量检索+数据库读取,延迟和成本都大幅下降。
4. 系统级治理:路由、版本锁定与一致性评测
4.1 先问一句“这个问题需要一致性吗”——路由分流的设计
不是所有问题都值得追求强一致。创造性写作、头脑风暴类任务,反而需要一定的随机性来保持新鲜感。如果一刀切地把 temperature 全设 0,你会得到一个“又一个呆板的大模型”。
所以我在系统入口做了一个意图路由器(意图路由),参考下面这个决策过程:
- 信息查询类(查政策、查流程、查数据)→ 确定性链路:低温度 + 标准答案缓存 + 严格输出模板
- 任务执行类(写邮件、做总结、生成代码)→ 半确定性链路:固定思维链 + 结构化输出,但内部允许适度灵活
- 创意生成类(写文案、头脑风暴、取名)→ 创造性链路:保持合理温度(如 0.8),不锁上下文顺序
三个链路共用同一个模型底座,但走的是完全不同的参数配置和后处理逻辑。这个分层是治本的总纲,后面对一致性诉求最高的第一类任务才会被上全套“约束”。
4.2 模型版本要怎么能钉死就怎么钉死
针对前面提到的“静默换血”,我要单独划一个章节强调。
落地时我做了三件事:
- 在环境变量和配置中心里明确记录
model字段,使用服务商提供的“只读版本别名”而非浮动别名。比如有些平台支持带日期后缀的快照版本,优先用这类。 - 在 API 网关层记录每次实际请求用的模型版本号。所有线上日志里都能查到“这条回复是哪个模型版本生成的”,出了事故可以快速回溯是“模型变了”还是“输入变了”。
- 重要版本的模型更新要走灰度流程。先在预览环境跑一遍一致性回归测试,再分批切流量,而不是躺平等服务商后台偷偷升级。
这个习惯会帮你省掉大量“线上为什么变了”的排查苦工。我见过某团队花了整整一天查 Agent 的一个行为漂移,最后发现是服务商一个月前灰度了新权重,而他们根本没有任何版本回滚的开关。那种无力感,经历过一次就懂了。
4.3 一致性回归测试:把“感觉不稳”变成“分数不合格”
治理一致性不是一锤子买卖,要靠评测持续校准。我在每个 Agent 项目里都会维护一份“一致性回归集”,它是一批固定输入-输出对的集合,作用类似于传统软件工程的自动化测试套件。
具体落地框架如下:
- 测试样本:从线上日志里挑出 200 条左右的高频问题,覆盖各类典型场景,尽量保证涉及不同任务类型的均衡。
- 执行策略:每个样本在“同一环境、同一版本”下跑 5 次,收集输出。
- 评测指标:
- 结论一致性:用语义相似度判断两次输出是否表达同一核心结论(我常用余弦相似度加人工抽检校准);
- 格式命中率:输出是否符合预定 JSON 结构;
- 字段准确率:结构化字段(如金额、日期、型号)是否全对;
- 过程稳定率:工具调用序列是否完全一致。
- 阈值设定:对我来说,一个版本要上线,结论一致性至少 95% 以上(即 100 个样本重复跑 5 次,同一结论的比例),格式命中率 98% 以上。没有达到的分支,直接打回迭代。
这一步很花时间,但它是通往可维护性的必经之路。早期我也试图跳过评测直接上线,直到一次又一次被线上用户“教育”,才老老实实把回归集建了起来。
4.4 兜底机制:模型不可信,最后一道防线交给程序
即使做了全套约束,你也没法保证模型 100% 不犯错。所以在严肃场景里,我还会加一道“程序兜底”:
- 校验层:对结构化字段做程序级验证。例如,Agent 给出了一个“发货日期”,程序必须检查它是否是合法日期、是否不早于今天。关键业务数字要对账,可以用确定性规则算出来的,就别让模型自由发挥。
- 回退策略:当模型输出被校验程序打回两次以上,直接切换为预置兜底话术——“这个我需要转人工确认一下”。不丢人,这是负责任的工程行为。
- 人工审计台:低置信度输出(比如模型自己给的
confidence打分低于阈值)自动转去人工复核队列。看似多了一步,但在 To B 场景里,这一步能挡住大部分最严重的“变脸”事故。
我在设计兜底逻辑时,一直是按“模型的输出只是候选,程序才是最终决策者”的思路来压责任。模型可以给候选方案,但“用还是不用”“用那个版本”应该由确定性代码裁决。这样最终的一致性,是由“会话裁决规则”保证的,而不是由一个概率模型保证的。
5. 治标与治本的边界:一致性不是越高越好
5.1 哪些场景铁了心要强一致
我接触过的项目里,这几类任务对一致性诉求最强,要毫不犹豫地上全套方案:
- 金融与政务问答:结论不一致不是体验问题,是合规问题。
- 客服售后决策:客户会因为两次答案不同而投诉,甚至产生纠纷。
- 数据提取与结构化录入:每次提取的结果要能对账,字段要完全稳定。
- 代码与配置生成:同一需求生成的代码,不能这次用轮询下次用随机数。
这些场景有一个共同点:输出要作为下一步决策或记录的依据。一旦不稳定,下游就会出连环事故,用户会直接判定这个系统“不可用”。
5.2 哪些场景别把一致性当KPI
反过来,有些场景你逼着 Agent 当“复读机”,反而会毁掉体验:
- 营销文案生成:同一产品每次都要有新说法,用户才会觉得内容鲜活。你把温度设 0,一周后 AI 和 AI 自己生成的内容都能撞文案。
- 头脑风暴与创意发散:随机性就是价值本身。一致性越高,陷入模式化、同质化的风险越大。
- 个性化陪伴与对话:用户每次进来,期待的是“这一秒的回应”而不是“模板的重复”。这些场景强行追求一致性,用户反而会觉得“这 AI 好机械”。
我在跟很多同行交流时,最想提醒的就是一句:一致性是为了建立“可靠感”,而“可靠感”不等于“每次都一样”。在创意类任务里,稳定的风格比稳定的内容更重要;在事实类任务里,稳定的结论比流畅的措辞更重要。治标或治本的前提,是先分清“哪个维度需要一致”。
5.3 这轮落地之后,我的一些体会和快速上手清单
总结一下我在多个 Agent 项目里反复使用的治理清单,按投入产出比排序:
- 先给所有 Agent 请求统一设置低温度和确定性的采样参数(半小时见效)。
- 给结果类任务做结构化输出约束,外加程序级校验兜底(一下午见效)。
- 在入口加意图路由,三类任务分流,不同任务走不同链路(一天见效)。
- 建一个 100-200 条的高频问题回归集,每次发版前跑一遍一致性评测(长期坚持)。
- 给模型 API 固定版本并做网关层记录,灰度升级前必须过评测(功在千秋)。
- 最后才考虑思维链固定、过程审计,因为它的开发成本最高,适用场景反而更窄。
这套方案并不能抹平大模型的概率本性——把随机性关进笼子,比试图杀死随机性,要现实得多。关键是分清哪个环节可以靠工程手段“焊死”,哪个环节必须靠评测去“监控”。我在实际项目中试过的经验是:只要把结构化约束、确定性链路和回归测试这三板斧一起落地,绝大多数 Agent 的一致性都能从“哪天心情好”变成“上线见真章”,用户对系统的信任度也会有质的提升。