ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent应用一致性治理:从随机性根源到工程化落地方案

Agent应用一致性治理:从随机性根源到工程化落地方案

1. 一致性问题的本质:当"随机"成为系统故障

作为做 Agent 应用开发的人,第一次遇到"同一输入不同输出"的时候,我第一反应是"大模型本来就有随机性,这不是很正常吗"。直到问题被业务方上升到"系统不可靠"的高度,我才意识到,在 Agent 场景里,一致性不是一个可选项,而是一个基础工程指标。一致性问题,简单来说就是:相同或高度相似的用户请求、上下文、系统状态,经过 Agent 的多轮推理和工具调用之后,得到的最终结果却不可复现,甚至在关键结论上互相矛盾。

这种问题在纯对话机器人上还不算致命,用户顶多觉得"模型有点飘"。但当 Agent 开始承担数据查询、代码生成、报告撰写、订单状态修改这类实际业务动作时,不一致的后果会被直接放大。运营人员拿着同样的参数跑了两遍报表,第一遍和第二遍结论不同,你没法解释为什么;合规审计要求你说明某个决策的依据,你发现系统根本无法稳定复现当时的决策路径。这种"不可复现性"一旦存在,Agent 就很难被当作正式生产力工具来信任。

更麻烦的是,一致性问题往往不是单点原因造成的,而是模型采样、上下文构建、外部依赖、流程编排多个环节共同作用的结果。如果我们只会把 temperature 调到 0,那只能是治标不治本。这篇文章,我会从"为什么"出发,先拆清问题根源,再给出一套从工程上可落地的一致性治理方案,最后用一次真实的项目复盘来展示如何操作。主要针对的是正在做 Agent 应用开发、平台建设、或者负责 Agent 项目质量的工程师和产品同学。

2. 四层根源:从模型采样到系统设计

2.1 模型采样层的随机性:temperature 不是唯一变量

先从最容易被理解的模型层说起。大语言模型的生成过程,本质是在词表概率分布上逐步采样。给定相同的输入序列,模型内部会计算下一个 token 的概率,但在实际选择 token 时,采样策略里存在随机因素。temperature 控制的是概率分布的"锐利程度":温度越低,高概率 token 的优势越明显;温度越高,低概率 token 被选中的机会越大。

很多人把 temperature=0 当成"确定性开关",这个理解在工程上并不准确。且不说不同模型对 temperature 的实现细节不一样,很多推理框架内部还存在着 top_p、top_k 等采样参数,甚至某些框架在算子层面还会引入额外的随机性。即便你在 API 配置里写了 temperature=0,底层依然可能不是严格确定的。这里有一个非常实际的坑:我在某开源模型的推理服务里做过实验,同一 prompt 用相同参数调用两次,temperature 都配的 0,输出还是略有差异,后来排查发现是框架版本和 GPU 算子行为引起的。

所以在模型层,我的建议是:不要只依赖单一参数,而是把一组采样参数固化下来,并且对这些参数做全局治理。做法上,可以在团队内部封装一个模型网关,所有对大模型的调用都经过网关,在网关里强制设置 temperature、top_p、seed 等参数,并把实际生效的参数连同请求 ID 一起写入日志。这样至少把模型层的不确定因素收敛到可追踪、可复现的状态。

补充说明一下 seed 的作用。seed 的意义在于给随机数生成器一个固定起点,在"同一模型版本 + 同一输入 + 同一采样参数"的前提下,固定 seed 通常能得到大致一致的输出。但 seed 不能跨模型、跨版本使用,换了模型版本,哪怕输入一样,输出也会完全不同。所以 seed 更适合用于测试环境的复现和问题定位,而不是作为生产环境一致性的终极手段。

2.2 上下文构建的漂移:你以为的"同一输入"其实不同

第二个根源在上下文层面。Agent 系统在每次调用模型时,都会构建一个上下文,这个上下文可能包含 System Prompt、用户消息、历史对话、检索到的知识片段、工具返回值等等。如果两次请求在用户输入表面上相同,但上下文实际内容有细微差异,模型的输出自然就不同了。

我见到过的上下文漂移主要有这么几类。第一类是历史窗口滑动导致的漂移:每次对话轮数增加,早期信息被截掉,模型对整体语境的理解就变了。第二类是检索结果的顺序不一致:同一个查询,向量检索返回的 TopK 结果可能因为索引更新或评分微调而顺序不同,而顺序会影响模型对重点信息的感知。第三类是工具返回内容的规范化程度不够:同一个字段,有时候返回"1000",有时候返回"1,000",甚至格式、单位、时间时区都不统一,模型解析后得到的语义自然也不同。

针对上下文漂移,需要把"上下文构建"当作一个独立的工程环节来设计。具体可以做这么几件事:

  • 固定历史窗口策略:明确保留多少轮、如何截断、被截断的内容是否生成摘要。
  • 对检索结果做确定性排序:用固定的二次排序规则,避免直接依赖原始相关性分数的随机波动。
  • 对工具返回值做 schema 化规范:统一字段命名、单位、时间格式、数组排序规则,并在进入上下文前完成清洗。

这些工作不会让模型变得绝对确定,但能显著缩小小幅波动被放大的空间。

2.3 外部系统状态差异:动态环境下的合理"不一致"

第三个根源来自外部系统。Agent 要真正干活,就免不了查数据库、调 API、读文件。外部系统的状态是动态变化的,同一请求在不同时刻执行,拿到的基础数据可能就不同,输出不同是必然的。

这类"不一致"有些是合理的,比如查询实时库存,两次查询结果不同完全符合预期。但在很多 Agent 场景里,我们希望某些环节的数据是稳定的,尤其是当 Agent 的输出需要通过评审、审计或多人复核时,我们不能让每次运行都拿到不一样的数据快照。

处理这一层问题,关键是区分"需要固化的数据"和"需要容忍的数据"。在系统设计层面,可以在关键工具调用前增加快照或缓存机制:比如为一次任务生成一个统一的任务上下文 ID,并在该任务生命周期内,用缓存层固定基础数据的版本。实际操作中,很多团队会给数据查询类 Agent 增加"业务时间快照"参数,所有查询统一使用同一快照时间,而不是实时时间,这样至少能保证同一任务内的多个步骤看到的是同一份数据。

2.4 业务流程编排的非确定性:自己设计出来的不一致

最后这一层很有意思,很多时候不一致是我们自己写出来的。Agent 的编排逻辑里,经常会用到并行调用、条件分支、重试机制。并行调用的结果返回顺序不稳定;条件分支是由模型判断决定的,模型判断稍有波动,流程就走向不同路径;重试机制在首次失败后重新执行,可能触发不同分支或拿到不同结果。

举个例子。一个 Agent 流程里,第一步让模型判断"用户意图是否明确",如果明确就进入查询流程,如果不明确就追问。这个判断只要有一次输出不同,后续流程就完全不同。这种分支级的一致性问题,已经不是采样和上下文的问题,而是决策点设计的问题。

治理编排层的原则是:把决策尽可能前移,能使用规则判断的业务逻辑,就不要让模型做自由裁量;必须由模型判断的场景,也要把判断的输入输出格式收敛,比如让模型输出布尔值和置信度,而不是自由文本,然后在下游用阈值做硬规则。此外,并行的工具调用如果结果需要进入同一个上下文,最好固定执行顺序或者按结果键排序后再拼接,避免"顺序不稳定导致内容排列不稳定"。

3. 治理框架:分层治理的思路与实践

3.1 模型层治理:采样参数固化与结构化输出

前面对根源做了拆解,现在讲治理方案。为了方便操作,我把治理工作分成四层:模型层、上下文层、编排层、机制层。每一层解决不同的问题。

模型层要做两件事。第一件是采样参数的固化与追踪。除了把 temperature、top_p、top_k、seed 等参数统一设置之外,还要在日志里记录每次请求的实际采样参数,以便问题复现时定位。第二件是使用结构化输出约束。现在主流模型基本都支持 JSON Output Mode、Structured Output 或者函数调用(Function Calling),这些能力可以让模型输出遵循一个预定义的结构。

结构化输出的价值,不光是方便解析,更重要的是它给输出的"骨架"加了确定性约束。模型在字段值上仍然可能有轻微波动,但字段结构、枚举取值、类型都是被约束住的,下游业务逻辑只需要处理值域上的微小波动,难度大大降低。我在实践中发现,即使模型对某个字段的推理发生偏差,结构化约束也会让偏差集中在具体语义上,而不会让整段输出散架。这对后续做一致性比对和自动校验非常有帮助。

3.2 上下文层治理:快照化、规范化和确定性排序

上下文层的目标是让"同样的请求,尽量拿到同样的上下文"。这里的核心手段有三个:快照化、规范化、确定性排序。

快照化很好理解。在一次任务开始时,生成一个会话级或任务级的上下文快照 ID,并把该快照 ID 对应的系统状态(比如时间、用户信息、权限范围、基础配置)记录下来,后续所有模型调用都引用这个快照,避免环境变化导致的上下文漂移。

规范化是将所有进入上下文的文本按照统一规范进行清洗。无论是历史对话、检索片段还是工具返回,都要经过一个格式标准化模块,统一字段名、时间格式、数字格式、单位,甚至统一数组排序规则。这样模型每次读到的上下文在细节层面是一致的。

确定性排序,主要是针对检索和工具返回结果。向量检索的召回结果,不能直接按原始分数排序后拼接,因为分数可能在小数点后几位波动。建议在召回后增加一个固定规则的二次排序,比如按内容哈希或文档 ID 排序,确保同一批内容在不同次运行中的顺序一致。

3.3 编排层治理:减少自由裁量,固定流程结构

编排层的治理,一句话总结就是"把自由裁量权收回来"。Agent 的流程中,每一个由模型自由判断的分支都是一个潜在的不一致来源。因此,我们可以在流程设计上做减法。

优先将可以规则化的判断移到代码层。比如,判断一个请求是否在营业时间内、是否满足参数合法性,这些都是规则判断,完全没必要让模型来做。模型只负责那些真正需要语义理解的环节,比如理解用户意图、生成摘要、解释结论。

对于必须由模型判断的场景,尽量转化为"枚举 + 置信度"的形式。让模型从固定选项中选择,而不是写自由文本;要求模型给出置信度,再在下游用阈值做硬性裁断。这样做的好处是,即使模型的判断在两可之间略有波动,下游规则可以稳定地收敛到一个决策。

此外,对重试、并行、分支要有确定性策略。重试不能简单地"失败就重跑一遍",因为重跑可能触发不同的外部影响;并行调用的结果要排序后再合并;分支的进入条件尽量由状态机来控制,而不是由模型每次重新生成路径。

3.4 机制层治理:一致性基准、回归评测与幂等设计

机制层是工程保障。没有这一层,前面的治理措施很容易在迭代中退化。

一致性基准集是必需的。挑选一组代表性的输入,配上固定的外部 mock 数据,固定上下文构建方式,然后多次运行 Agent,计算输出差异度。差异度的度量可以用文本相似度、语义相似度、结构化字段差异度等。每次发布前,跑一轮一致性回归,如果差异度超过阈值,就需要人工介入检查。

另外,要在 Agent 的所有关键决策节点埋点,记录完整的 trace。这里不光是模型调用的输入输出,还包括上下文快照 ID、采样参数、工具调用顺序、每个中间判断的结果。有了完整 trace,排障的效率会大幅提升,一致性问题的定位从"靠猜"变成"靠查"。

幂等设计也是机制层的重点。Agent 只要执行写操作(比如发消息、改状态、创建订单),就必须引入幂等键。因为不管我们怎么治理,外部系统和小概率的模型波动始终存在,幂等键可以保证重复执行不会造成重复操作。

4. 实操复盘:给一个经营报表 Agent 治理"精神分裂"

4.1 场景与问题

我拿一个最近处理过的案例来说明整个治理过程。某公司的一个经营报表 Agent,输入是"日期范围 + 需要汇总的维度",它会查询订单、支付、退款等多个数据源,然后生成一份经营报表,并附上"关键结论"。问题反馈是:运营同事用同一个日期范围跑了多次,看到的报表结论不一样,有时差异还很离谱,比如第一次说"退款率环比上升 5%",第二次说"基本持平"。

我先做了问题复现。用完全相同的输入连续跑五次,并把五次的输出并排对比。结果显示,确实存在不一致:结构上,五次都生成了 Markdown 表格,但表格列的顺序、指标名称、结论措辞都有差别;数值上,部分聚合值和比例值存在轻微浮动;结论上,有一次"环比上升"、有一次"基本持平"。

这个复现过程非常关键。它让我确认了两件事:第一,问题不是偶发,而是系统性的;第二,不一致的幅度足以影响业务判断。

4.2 根因定位

接下来排查根因。我按照"模型层 → 上下文层 → 编排层 → 外部系统"的顺序,层层查看。

模型层:查看日志发现,该 Agent 的模型调用确实设置了温度,但多个外部调用点没有统一走网关,不同环节实际使用的采样参数不一致。有些环节还启用了 top_p,相当于给随机性打了二次折扣。

上下文层:报表 Agent 每次会检索一些"指标口径说明"文档。因为没有二次排序,TopK 结果的顺序在不同运行之间不稳定,导致模型侧重点漂移。工具返回值也没有做规范化,订单状态字段有的返回"已完成",有的返回"completed",混着来。

编排层:Agent 在查询多个数据源时使用了并行调用,返回结果按自然返回顺序拼接。两次运行之间,接口响应速度不同,导致拼接顺序不同,后续模型解读"重点指标"时就会侧重不同的数据源。

外部系统:报表查询本身依赖实时数据,虽然日期范围固定,但数据源中的退款状态在两次查询之间可能发生变化。这个属于合理动态,但因为没有快照机制,无法区分"模型波动"和"数据动态"的贡献。

4.3 改造措施

针对以上根因,我做了几项改造。

第一,模型层统一收敛。把该 Agent 的所有模型调用迁到模型网关,统一设置 temperature=0.2、top_p=1.0、固定 seed,并在日志中记录实际参数。这里没有直接设置 temperature=0,因为报表 Agent 还要生成结论和解释,温度太低时语言表达会僵化;0.2 是在"语言自然度"和"稳定性"之间的折中。

第二,上下文层做规范化和确定性排序。对检索结果,召回后先按文档 ID 排序,再进入上下文。对工具返回值,增加一层 schema 清洗,统一字段命名、枚举翻译、时间格式。对并行查询结果,在拼接前按数据源名称排序,保证顺序稳定。

第三,增加业务快照。为一次报表生成任务创建任务 ID,并在任务开始时记录"业务时间快照"(比如统一取任务开始时间往前 5 分钟的数据版本),后续所有查询都使用该快照时间。这样可以区分"同一个快照上的不稳定"和"数据更新导致的变化"。

第四,增加一致性回归。从历史请求中挑选 20 条典型输入,配上固定的 mock 数据,做成一致性基准集。发布前跑三轮,对比输出结构差异和关键数值差异,任何一轮超过阈值就要拦截。

4.4 改造效果观察

改造之后的对比效果比较明显。同样跑五遍,报表的结构固定了,表格列顺序和指标名称基本一致;关键聚合值在不同运行之间的浮动范围明显缩小;结论层面不再出现"上升 vs 持平"这种方向性冲突。下面用表格做一个简化对比:

观测维度改造前改造后
表格结构一致性列顺序、指标名称常变基本固定,偶发措辞轻微差异
关键数值稳定部分比例值波动超过 1%波动收敛在 0.1% 以内
结论方向稳定出现方向性冲突未再出现方向性冲突
问题可复现性难以稳定复现同参数同 seed 可稳定复现
排障效率靠猜,平均半天定位靠 trace 和日志,半小时内定位

这次改造还带来一个副产品:因为一致性提升,运营同学开始信任报表 Agent 的结论,愿意把它的输出直接用于内部周会材料,而不是每次人工核对一遍。这其实是一致性治理最核心的价值——它把系统从"看起来聪明"变成了"可以被信赖"。

5. 常见问题与排查技巧实录

5.1 一致性问题的常见情况速查

把我在多个项目里见过的高频问题整理成一张表,方便大家排障时对照:

症状可能原因快速排查方向
同等输入,结论方向相反温度/top_p 设置不当;上下文漂浮不定;关键分支判断不稳定检查采样参数;固定上下文快照;将分支判断规则化
结构化输出偶尔解析失败模型输出没有严格约束;约束格式与模型版本不兼容启用 JSON Schema;降低温度;升级模型版本
同一任务多次运行结果不同并行调用顺序不稳定;工具返回值未规范化;外部数据动态变化固定调用顺序;增加 schema 清洗;引入业务快照
发布后输出行为漂移模型版本变更;提示词版本变更;上下文模板变更未被记录对提示词和模型版本做版本管理;建立上线前后对比测试
部分环节确定,部分环节随机不同调用点参数不一致;某些环节没有走统一网关统一网关;统一参数记录

5.2 排障方法论:从"靠猜"到"靠查"

排障一致性问题的关键,是要建立一套可操作的方法论。

第一步是"复现"。用相同的输入、相同的上下文、相同的参数连续跑多次。如果能稳定复现不一致,问题大概率在系统内部;如果很难复现,可能来自外部系统。

第二步是"分层隔离"。不要试图一次定位全部根因,而是先用一个最简单的方式逐层隔离。比如先固定模型参数,看看不一致是否收敛;再固定上下文,看看是否进一步收敛;再固定外部数据,看看模型输出是否进入可接受范围。每一步记录"收敛幅度",就可以判断哪个层面对不一致的贡献最大。

第三步是"打点留痕"。在每个模型调用之前,记录上下文哈希;在每次工具调用之后,记录返回内容哈希;在关键决策节点,记录模型输出。有了这些哈希和 trace,当不一致发生时,可以直接对比两次运行的上下文和中间状态,快速定位是从哪一步开始分叉的。

第四步是"用语义相似度做量化"。纯文本 diff 对自然语言输出不够灵敏。可以用一个轻量的 embedding 模型对多次输出做语义相似度计算,或者对结构化字段做逐个比对。设定阈值,低于阈值直接告警。

第五步是"对阈值不断校准"。阈值设得太严会误报,设得太松会漏报。校准方法是用历史数据做分布统计,找出正常波动和异常波动的分界点,然后设置一个有纪律的阈值,并且持续观察。

5.3 几个实用的避坑技巧

最后补充几个我在实操中踩过坑之后总结的小技巧。

第一个技巧:不要在生产环境追求"绝对一致"。一致性治理的目标是把输出收敛在业务可接受的范围内,而不是消除所有的随机波动态。语言模型本质上是一个概率系统,强行追求每一步都完全确定,往往意味着牺牲表达能力、增加巨大成本,而且还会引入过度工程化的复杂度。你真正要保障的是关键字段、关键结论、关键流程的一致,而不是每个 token 的一致。

第二个技巧:对"结论类"输出要增加验证环节。如果 Agent 生成了"退款率上升"这类结论,最好在结论背后挂一个"依据链",让它把得出该结论所依赖的数据条目标记出来。后续如果结论与依据不对应,就可以自动识别并告警。这本质上是一种"自校验"机制,对提升可信度非常有效。

第三个技巧:一致性回归要纳入 CI/CD。很多团队会做功能测试、回归测试,但不会跑一致性测试。我的建议是把它加进发布流水线,用基准集跑一轮自动比对,成本不高,但能在早期拦截大量回归问题。

第四个技巧:关注模型版本升级。模型版本更换是 Agent 行为漂移的头号原因。每次升级模型前,最好用同样的基准集分别跑旧版本和新版本,对比输出差异。这个对比结果,经常能避免上线后才发现"Agent 变笨了"或者"Agent 变野了"。

6. 写在最后的体会

这套治理框架,我在不同项目里跑了多次,最大的体会是:一致性不是靠某一个神奇参数就解决的,它是一个"从模型到系统、从开发到发布"的系统性工程。越早把一致性当成一等公民来对待,后面的返工就越少。

如果你正被 Agent 的"精神分裂"问题困扰,不妨先从日志和 trace 入手,把一次不一致的完整链路拉出来,再对照本文的四层根源逐层排查。很多时候,你只要把模型层参数统一、把工具返回规范化、把关键分支规则化,问题就已经解决了一大半。

最后,再分享一个小建议:一致性治理不要只盯着技术侧。把业务方拉进来一起定义"哪些指标必须一致、哪些可以容忍浮动",你可能会有意外收获——一方面可以避免过度治理,另一方面也能让业务方理解你的努力方向。这个协同,比任何参数调优都更能为 Agent 项目的落地铺平道路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进