← 返回博客 XFENG · INSIGHTS

为什么分数上涨的 Agent 策略,我们仍然不让它上线

一次离线自进化实验里,训练集分数上涨、验证集下跌。本文写下验证集不可见、工具故障出分母、回归集否决权,以及搜索证据不等于可上线提案,解释分数变好为什么仍不能直接发布。

左侧显示训练集分数逐代上升,右侧显示同一候选在验证集上分数下降

2026 年 7 月,我们拿自己客服系统的真实对话做了一次离线进化实验。想验证的事情很直接,让系统自己改自己的话术和流程策略,一代一代往上走。

先把"进化"这个词的范围定下来。本文说的进化,指对回答策略、流程策略、工具路由配置做离线的、受约束的候选搜索。它不更新模型权重,也不自动修改生产系统。搜索跑在一份独立的归档里,产出的是候选配置和它们的评测记录。

材料从 2870 次真实客服对话中抽取,脱敏后取 60 条,按接口分页偏移取,不是随机抽样。首轮只用了前 30 条。10 条当训练集,接下来 20 条当验证集,进化环节看不到它们。剩下的 30 条在首轮没有用上,后面的轮次也一直没用到它们。外部对照我们另抽了三批样本,各 30 条,取自更靠后的分页,后文说的"三组数据"就是这三批各自跑的一轮。回归集是另外挑的 8 条,从基线在训练集和验证集上的结果里选出"看起来还能改"的坏案例,它在流程里只有否决权。

进化对象是一份结构化配置,里面装着回答策略、流程策略和工具路由策略。每一代由模型按白名单算子生成候选,改动是受限补丁,不许整体重写。评测用规则打分加模型裁判。

验证集的位置要先说清楚。它对生成候选的模型不可见,变异环节读不到它的案例,也读不到它的失败原因。但它的分数决定候选能不能进档案、决定下一代从谁身上继续改,也就是说它参与了优化。它能防住训练集上的过拟合,不能当成独立的最终测试集。这轮实验里没有一份从头到尾不参与任何选择的数据,这是它的边界,也是后面所有结论的适用范围。

第一轮跑了三代,每代两个候选。训练集从 44.7 一路涨到 75.6,加了 30.9 分。曲线一代比一代高,中间没有回头。

我们当时确实高兴了一会儿。然后把最好的那个候选拿到那 20 条验证集上,基线 77.4,候选 65.3。反跌 12 分。

左边是训练集上逐代走高的分数,右边是同一个候选在验证集上的结果

训练集在涨,验证集在跌

回看候选到底改了什么,答案很难看。它学会了多转人工。

我们的打分里,把该转人工的案例正确转出去是加分项。候选找到了这条捷径,于是把越来越多的案例往人工推,其中包括那些本该由工具故障来解释的案例。训练集只有 10 条,这个动作在 10 条里稳赚。到了 20 条验证集上,它把大量本来可以正常回答的对话也推给了人工,分数立刻掉下来。

两条规则由此定下。

第一,验证集对生成环节永远不可见。变异用不到它,它的失败原因也不许回流给模型。一旦把验证集上的坏案例喂回去,它就变成了第二个训练集,泛化信号当场失效。这个失败模式不报错,它只让你对一个已经污染的数字越来越有信心。被反复用于选择的数据不能再当泛化证明,这一条在后面每一轮里都要重新确认一次。

第二,工具故障、后端错误、缺少转人工配置这类案例,剔出分母。它们不是话术能解决的问题。把它们留在分母里,优化器就会想办法绕过去,而多转人工正是最省事的绕法。这些案例照旧要输出,只是走另一条口子,交给工程和配置去处理,不进反馈。

这条口子有两个前提。分类规则在第一轮之后就固定下来,后面不再按候选的表现调整,否则"剔出分母"本身会变成一种刷分手段。另外,"基础设施是不是正常"和"故障发生的时候处理得好不好"是两件事,只有前一件出分母。后一件仍然在优化范围里,后来唯一一批达标的补丁,改的正是工具返回为空的时候怎么答。

搜索证据不等于可上线提案

加上门控重跑,分数还在涨,把训练集、验证集、回归集三个分数加权之后的综合选择分从 55.2 走到 65.1。合格候选是零。每一个候选都倒在硬失败门上,格式或者路由出了错。当时的记录写的是,这一轮产出了有用的搜索证据,没有产出可上线的进化提案。

这句话后来一直管着我们怎么读实验结果。搜索会源源不断给你东西,那些东西看上去都像成果。判断它是不是成果,需要另外一套制度。

制度里最直接的一道门是验证集地板。有个候选训练集涨了 8.10 分,全场最高,验证集 −0.35。按"跑 N 轮取最高分"的朴素规则,它会上线。门控把它淘汰了,那一轮选中的是基线,什么都不改。

一次什么都没改的运行也是有产出的。它说明在本次数据、搜索范围、预算与评测规则下,没有发现达到晋级要求的候选,同时说明那个看起来最好的候选不该上线。这两条结论都能拿去做决定。

回归集有否决权

再往后加的一道门叫逐案例配对,它是被一次很像成功的结果逼出来的。

同一批数据上有四个候选,验证集的平均提升都是正的,从 +1.03 到 +1.56。逐案例看也站得住,四个候选改好的案例都多于弄坏的,赢输分别是 4 比 0、4 比 1、5 比 1、5 比 1。按均值排序随便挑一个上线,都说得过去。

然后我们把 8 条固定的历史坏案例拿出来逐条对比。每个候选,改好的案例数都少于弄坏的。四个候选的赢输分别是 2 比 4、2 比 3、1 比 2、0 比 2。最后那一个,一个案例都没改好,弄坏了两个。

两件事同时为真,因为它们落在两批样本上。验证集是当期的新对话,回归集是历史上已经出过问题的坏案例。候选在新对话上普遍变好,在老问题上普遍变差,这两句话之间没有矛盾。

逐案例的判定用单个案例的分差。案例分在 0 到 1 之间,涨过 0.01 记一次改好,跌过 0.01 记一次弄坏,中间的一律算持平。规则很短,同一批案例上弄坏的比改好的多,这个候选就不能进档案,验证集涨得再多也没用。回归集在这里只有否决权,它不能让一个候选晋级,只能把它拦下来。

这道门在后来每一轮里都在拦人,它最容易被忽略,也最值钱。要补的一句是,只比数量是我们当时选的保守规则。改善和退化的幅度没有分级,案例的严重程度和业务权重也没有分级,一个把客户劝走的退化和一个措辞变啰嗦的退化算同一票。这是下一步的事。

四个候选在两批样本上的逐案例结果

搜索里赢过,只证明在那批样本、那版评测器下赢过

这套搜索有过一次达标记录。

把代数加到两代,每代三个候选,回答、流程、工具路由三个补丁家族各出一个,在三组数据上各跑一遍。这一轮有两组跑出了达标候选,验证集 +1.15 和 +1.10,回归集 +2.13 和 +3.04。两个候选改的是同一类东西,工具返回为空或者信息不全的时候,明确告诉用户信息不完整并给出下一步,不要硬编一个答案。改动很朴素。总结里当时写的是,框架现在看起来真的有用。

这句话要按它成立的范围来读。我们在这次实验、这批样本、这版评测器下观察到了正向提升,仅此而已。它是不是稳定有效,要靠重复验证,那一步我们没有做到。

然后我们把同一份配置换成并行执行,重跑了一次。达标候选从 2 个变成 0 个,选中候选的验证集平均提升只剩 +0.056。

配置一个字没改,只是三组数据从排队跑变成各跑各的。结果对执行条件敏感,这一点确认了。至于其中有多少来自变异采样的随机性、多少来自并发下的资源竞争、多少来自评测本身的波动,我们没有把这几个因素分开。一次跑出来的结果不能当结论用。

更硬的一课在固化阶段。几轮下来各个家族都出过赢家,看上去可以把它们的思路蒸馏成一份通用补丁。我们按主题手写合并了一版,回答、流程、路由三个家族的改动都装进去,三组数据上重放,验证集全部为负。原因也清楚,合并之后补丁的作用范围变宽,宽的补丁更容易过拟合。

于是改用精确重放。重放的是配置和执行条件,从归档里取出赢家确切的每一步补丁操作,连它父代的操作一起,在当前评测器下按同样的参数再跑一遍。模型的输出本身不会逐字复现,能对齐的只有输入这一侧。最强的那个赢家,验证集 +1.51 的那个,没有复现出来。

往下排查发现,重放时的基线分数已经和当初那次实验对不上了。这几周里评测器本身改过好几处。

旧的实验结果没有作废,它们仍然是旧评测条件下的证据。作废的是拿旧数字直接做新决定这件事。提升是相对基线量出来的,尺子换了,基线要重新量,候选也要重新量,两边都在新尺子下比过才算数。评测器每改一次还得交代它在改什么,是在纠正一处误判,还是在改变优化目标。前者旧结论多半还站得住,后者等于换了一道题。

这里我们做了一个区分,后来一直没动过。在搜索里赢过,只证明在那批采样、那版评测器下赢过,这叫搜索证据。能上线,必须把确切的操作抽出来,在当前评测器下精确重放,多组数据都过,这叫晋级证据。早期赢家保留为搜索证据,不算晋级证据。

稳定与有效是两级判定

到这一步,需要两个词来分别描述两件事。

稳定说的是这次运行有合格候选,泛化差距没有超标,验证集和回归集都没有实质倒退。它描述的是这套机器在正常工作。

有效说的是选中的候选不是基线本身,验证集的提升达到了事先定死的门槛,回归集不跌,硬失败数量不增加。它描述的是这一次真的变好了。

三组数据可以全部稳定,同时零有效。绝大多数诚实的实验结果就是这个形状。实验记录里那句话说得更狠,"稳定说明框架在正常工作,有效才说明这次真的变好了。把稳定说成有效是最常见的谎报。"

门槛必须在看到结果之前定死。看完结果再调门槛去迁就它,门控就没有意义了。

四道闸门,以及逼出每一道的那次失败

没有赢家是合格输出

最后一次运行,用当前评测器重跑完整搜索,三组数据全部选回基线。把这次运行的归档喂给晋级门,它返回了一个字段,无候选。

这条结果被我们记录为一次成功的输出。原话是这样,"这个'没找到'本身是正确输出,不是失败。门控挡住中性和过拟合补丁,就是它在干活。"

这句话要收着说。门控拒绝一个候选,说明的只是这个候选没过这次的标准。它不证明标准本身定得合理,也不证明搜索有能力找到值得上线的东西。要判断一道门可不可信,两个方向的对照都得有。负向对照我们做过一次,那个训练集涨 8.10、验证集 −0.35 的候选是已知的坏候选,门控确实把它拦住了。正向对照没有做过,也就是拿一个经过独立确认确实有效的改动送进去,看门控会不会放行。缺了这一半,"门控在干活"目前只有一半的证据。

还有个诚实的边界要划一下。门控挡住候选时,"没找到"是正确输出。如果每次都没找到,那要先怀疑评测器和搜索强度,不能拿"门控在干活"自我安慰。两种情况的区别在于,你手里有没有过至少一次真的赢过的记录,以及这次的基线分数和上次对不对得上。

另外四条规则是走到这一步才写死的,它们决定循环会不会自己把自己带偏。被淘汰的候选永远不能成为父代。漏掉这条,一个训练集大涨、验证集下跌的候选会成为整个家族的祖先,后面每一代都在它的基础上继续改,分数一路走高,血缘已经烂了。卫冕者始终留在选择池里,不然会出现这一代全都更差却仍然选出新冠军。变异反馈只取训练集。新颖性要对比全部历史,包括被拒绝的那些,不然搜索会反复重提已经被否决的改动,预算烧在原地打转。

实验条件

上面每个数字的适用范围都由下面这张表限定。

项目 取值
数据划分 训练集 10 条、验证集 20 条,来自同一批 60 条脱敏对话的前 30 条,剩余 30 条未使用;回归集 8 条固定历史坏案例,选自基线在训练集与验证集上的坏结果;外部对照三批各 30 条,取自更靠后的分页偏移,后期每批各跑一轮,即正文中的"三组数据"
候选对象 一份结构化策略配置,含回答策略、流程策略、工具路由策略三个补丁家族
变异方式 模型按白名单算子提出受限补丁,列表追加、少量固定字段的字符串设置、单条意图路由合并;运行器校验后应用,每个候选默认最多一步操作。后期按家族分岛,每个家族从自己的最好可用父代继续
模型与裁判 变异由一个外部大模型接口生成;早期评测为规则打分加模型裁判,后期多数轮次改为纯规则裁判以保证可重放
搜索预算 首轮 3 代 × 每代 2 候选;后期主力形状 2 代 × 每代 3 候选,三个家族在每一代各占一个位置
重复次数 三批数据各跑一次算一组;同一形状最多重复过 3 组
逐案例判定阈值 单案例得分(0 到 1)变化超过 0.01 记为改好或弄坏,其余记为持平
晋级门槛 选中候选不是基线本身;验证集提升不低于 +1.0;回归集不为负;硬失败数量不超过基线。四条同时成立才记为有效
泛化差距上限 训练集提升减验证集提升超过 10 分即判为过拟合

验证环不能是装饰

把这套裁判做进产品的时候,我们先审了自己已有的一条提案流水线。它四个环节齐全,生成、评测、审核、应用,看上去是完整的。

审下来发现评测那一环没有把候选的改动注入进去,它评的是基线的健康度。应用那一步也不读评测结果。门控的默认设置会放行零改进的候选。四个环节确实都在转,验证这件事是装饰性的。

于是定了三条。

第一条,验证环不能是装饰。评测必须把候选的改动注入之后,与基线在同一批案例上成对比较。应用之前必须读门。零改进不放行。

第二条,收据由服务端绑定。模型的输出不能证明某次调用真的发生过,执行凭证要在接口边界上生成,不让模型自己填。子代必须与父代不同,未变更的子代也要计入成本,否则血缘和成本这两份证据都会失真。

第三条,探索离线,落地人审。离线实验室只负责产候选和归档,永远不写生产。落地那一层做成对评测和门控,风险按绿黄红分级审批,自动应用默认关闭。先跑一段影子模式,把"系统会怎么决定"和"人怎么决定"两份记录摆在一起看一致率,一致率稳住了再谈开关。

离线实验室与落地流水线

裁判制度是不随模型换代的部分

生成候选这件事正在飞快地变便宜。判断候选值不值得上线这件事没有跟着变便宜,它的成本几乎全落在数据隔离和重放复现这类笨功夫上,换更强的模型省不掉这些。

我们前后十几轮实验里换过评测器、换过搜索形状、换过执行方式,每次换完都得重新验一遍。留下来没动过的只有那几条判定规则。

这套东西现在走到哪一步,也可以交代清楚。离线实验做完了上面这些轮次,结论是搜索有过一次达标记录,没有可重复的有效性证据。产品那一侧把这套门控做成了提案流水线,按影子模式设计,自动应用默认关闭。到今天为止,生产环境里还没有产出过一条经审核应用的提案。所以真要问一个自进化系统可不可信,先看它的裁判是怎么立的,再看它的记录里有没有"没有赢家"这一类结果,以及那些结果是怎么来的。

注:本文为 2026 年 7 月的项目实践记录。