
在智能体Agent训练里评估结果很漂亮一换网络环境、任务描述或工具返回格式就失灵是最常见也最隐蔽的问题。AgentMercury 的项目实践给出了一条更值得复用的思路把“训练环境”和“评测集”脱钩。训练时不让模型接触与评测同分布的样例评测时也不让任何训练数据、缓存或已保存日志污染结果。这个思路的核心不是单纯多做几个测试用例而是把训练分布和评测分布从源头上分开用分布外能力代替同分布记忆。文章会围绕“为什么耦合会让泛化失效”“脱钩需要在数据、环境、流程哪几层落地”“如何搭一个最小脱钩评测框架”展开并用 YOLOv5 这类视觉任务的 train/val/test 拆分做对照帮助理解脱钩的真正含义。1. 训练环境与评测集耦合泛化能力从哪一步开始失效1.1 智能体训练里的三个角色策略、环境、评测集任何智能体训练项目发展到一定阶段都会拆出三个核心对象。第一个是策略也就是我们要训练的模型。它接收观察输出动作。在对话型智能体里观察可能是用户消息、历史记录和工具返回结果动作可能是回复文本或工具调用在具身智能体或游戏智能体里观察可能是图像、状态向量动作可能是移动、抓取、点击。第二个是训练环境它负责给策略提供交互场所。环境里定义了状态空间、动作空间、奖励函数、回合结束条件和成功判定方式。训练环境通常是可随机化的每次 episode 会采样不同的初始状态、实体、任务描述和干扰噪声。第三个是评测集它和训练环境是两回事。评测集通常包含两个组成部分一部分是输入样例例如任务描述、用户问题、目标状态另一部分是评测环境例如固定的模拟器版本、固定的工具返回格式、固定的评测场景。评测集存在的意义是对模型进行标准化的、可复现的能力测量而不是给训练过程提供额外样本。在 AgentMercury 这类智能体项目中最容易被忽视的就是评测集和训练环境到底共享了什么。很多项目表面上把评测集放在独立目录里实际构建逻辑却复用了同一套模板、同一个采样器、同一份缓存数据。于是训练过程中模型已经间接见过评测分布评测结果被高估。高估不是模型能力强而是模型记住了评测分布下的表面规律。1.2 记住评测分布不等于学会决策当一个智能体在训练环境里拿到高分但在评测集上表现不稳定时第一反应通常是继续加数据、调奖励权重或换更强的基座模型。这些调整可能让分数短期上涨但真正的问题往往是模型学会了“背题”而不是学会了“做题”。举个对话智能体的例子。训练环境里的用户问题是按固定模板生成的用户我想查询订单 {order_id} 的状态。评测集如果也用同一模板只是换一个 order_id模型很容易通过记忆规则完成。真正有价值的评测应该在措辞上变成“我前两天买的东西怎么还没送到”在状态上变成“订单已签收但用户以为未送达”在工具返回上变成“接口返回了多个疑似的订单”。这种情况下模型的工具调用、置信度判断、追问策略都会被真正检验。这就是“训练环境与评测集耦合”的典型结果训练环境通过数据生成器、模板库、实体表、成功判定函数把评测分布提前泄漏给了模型。模型不需要理解任务只需要拟合训练时见过的统计规律。另一个常见场景是评测集被用于调参。训练过程中开发者在评测集上反复观察得分并据此调整学习率、奖励函数或提示词。这等于把评测集变成了训练集的隐式扩展。即便评测集没有被显式拼进训练数据模型和开发者也已经间接记住了它的脾气。最终得分反映的是“开发者对评测集的适应程度”而不是“模型对真实任务的泛化程度”。1.3 脱钩到底是哪一层脱钩“脱钩”不是指不再使用评测集而是指三件事分别独立数据来源脱钩评测集的采样逻辑、模板库、实体表不能复用训练数据生成器的全部参数更不能直接从训练数据里抽样。环境行为脱钩评测环境的模拟器版本、工具返回格式、状态转移规则应该独立于训练环境至少不能使用同一套随机种子和缓存文件。流程节奏脱钩训练脚本不能访问评测集超参调试不能反复基于评测集得分做选择评测集要等到模型“冻结”后才被使用。要用一句话说清就是训练过程不允许看到任何能让模型提前写出评测答案的信息评测过程不允许对模型和代码做任何面向评测集的修修补补。2. 训练环境与评测集脱钩的核心机制和工程做法2.1 从同分布评测走向分布外评测很多团队对评测的理解停留在“同分布拆分”。也就是把一批数据按 8:1:1 切出训练、验证、测试集。这种做法的好处是方便对比模型效果缺点是测试集和训练集来自同一个生成流程分布偏移非常小泛化能力被高估。分布外评测是脱钩设计的自然延伸。评测集应该刻意包含训练分布之外的情况例如用户说得更口语化不包含标准实体名称。任务需要连续调用三个以上工具训练时最多见过两个。环境返回格式符合协议但有额外字段、空字段、异常值。目标状态与训练阶段的目标状态在物理或逻辑上存在冲突。分布外评测不能无限追求“完全不同”否则模型永远无法完成。好的做法是给分布偏移设定梯度。先做轻度偏移例如换措辞、换实体、换同类任务再做中度偏移例如换环境版本、换工具返回格式最后做重度偏移例如跨任务迁移、长尾场景。每一级的评测结果都单独统计这样能精确判断模型泛化能力在哪一级断裂。2.2 数据、环境、流程三层脱钩的具体做法脱钩不是靠自觉而是靠工程结构强制。数据层、环境层、流程层要分别做约束。数据层脱钩要求评测集构建脚本独立。常见做法是训练数据生成器使用 train_config.yaml评测集生成器使用 eval_config.yaml。两个配置文件里的模板路径、实体库路径、随机种子列表、生成规则都不共享。评测集生成完成后生成 content hash并在训练代码里禁止直接读取该文件。这样即使有人在训练脚本里多写了一个 glob也会因为路径和哈希不对而报错。环境层脱钩要求评测环境的执行结果稳定可复现。训练环境可以随机引入干扰、调整奖励、改变长度评测环境必须锁定模拟器版本、模型版本、随机种子、温度参数和最大步数。评测环境建议做成独立的 Python 包或 Docker 镜像不能直接复用训练环境目录下的同一个源文件。否则训练环境一改评测结果就会悄悄变化。流程层脱钩要求训练和评测在时间上隔离。训练完成前评测脚本只能跑在预留的 dev 集上模型冻结后评测脚本一次性运行在最终评测集上。dev 集和最终评测集也要完全独立不是同一批数据打乱重分。最终评测结果要记录评测集版本、模型文件哈希、运行时间、随机种子、环境版本方便事后回溯。2.3 三层脱钩的对比速查表实际搭建时可以用这张表检查每一个环节是否真正独立。脱钩层次要解决的问题典型错误做法推荐做法数据层训练数据泄漏评测分布评测集从训练数据里抽取评测集独立采样、独立生成、独立 hash数据层模板和实体重复共用同一个模板库训练和评测使用不同模板库环境层评测行为不稳定直接复用训练环境源码评测环境版本锁定独立镜像或包环境层随机性不可控不固定随机种子锁定 seed、温度、最大步数流程层评测集被反复调参用评测集选择超参模型冻结后再跑最终评测流程层缓存污染结果保留训练缓存评测前清空缓存禁用评测集外文件3. 设计一个最小可复现的脱钩评测框架3.1 先定义评测目标和指标在设计代码之前先明确“脱钩评测”要测量什么。对 AgentMercury 这类智能体最常用的指标有五个。指标计算方式含义注意点Success Rate成功回合数 / 总回合数任务完成比例成功判定必须独立于训练奖励Average Episode Length总步数 / 总回合数完成任务的效率需要区分成功回合和失败回合Goal Progress状态接近目标的程度部分完成能力需要环境提供进度信号Normalized Score标准化到 0 到 1 的得分综合效果需要提前规定分数映射Reject Rate主动拒绝或无法执行的回合占比安全边界某些场景拒绝比乱做更好指标要在评测运行前写进评测配置不能跑完再挑一个更好看的指标。否则就变成了“面向评测得分的过拟合”。3.2 目录结构和配置示例脱钩的第一个工程标志是目录结构。训练和评测必须从物理路径上分开。agent_project/ ├── train/ │ ├── config.yaml │ ├── data_generator.py │ ├── train_pipeline.py │ └── outputs/ ├── eval/ │ ├── config.json │ ├── dataset_v1.json │ ├── evaluate.py │ ├── env/ │ │ ├── eval_env.py │ │ └── success_check.py │ └── output/ └── models/ ├── checkpoint_final.pt └── checkpoint_manifest.jsontrain 目录和 eval 目录互不 import。评测脚本只依赖 eval/env 下的代码不依赖 train/data_generator.py。这是一个很简单的约定但很多人做不到。评测配置用 JSON 比用代码更容易锁版本。示例{ model_path: ../models/checkpoint_final.pt, model_hash: sha256:8f4a1c..., seed: 20241101, temperature: 0.0, max_steps: 30, environment: { package_name: eval_env, version: v1.2.0 }, output_path: ./output/results_v1.json }这里 model_hash 很重要。如果模型文件有变化但标签不变评测结果可能不可复现。记录哈希后任何结果都能追溯到具体模型。3.3 评测 runner 的最小实现下面是一个简化版评测脚本重点不是完整框架而是体现脱钩约束评测集路径只属于 eval 层训练相关依赖不出现在 import 中。# eval/evaluate.py import hashlib import json from pathlib import Path from agent import build_agent from env import build_env EVAL_SET_PATH Path(./eval/dataset_v1.json) CONFIG_PATH Path(./eval/config.json) OUTPUT_PATH Path(./eval/output/results_v1.json) def load_config(path: Path) - dict: with open(path, r, encodingutf-8) as f: return json.load(f) def load_dataset(path: Path) - list: with open(path, r, encodingutf-8) as f: return json.load(f) def model_hash(path: str) - str: h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(8192), b): h.update(chunk) return h.hexdigest() def run_one_item(agent, config, item): env build_env( env_specitem[env_spec], seedconfig[seed], success_check_typeitem[success_check_type], max_stepsconfig[max_steps], ) obs env.reset(task_promptitem[task_prompt]) done False step_count 0 last_info {success: False, message: not_finished} while not done and step_count config[max_steps]: action agent.act(obs) obs, reward, done, info env.step(action) step_count 1 last_info info return { id: item[id], success: last_info.get(success, False), steps: step_count, message: last_info.get(message, ), } def main(): config load_config(CONFIG_PATH) items load_dataset(EVAL_SET_PATH) model_file config[model_path] actual_hash model_hash(model_file) if actual_hash ! config[model_hash].split(:)[-1]: raise RuntimeError(model hash mismatch, check checkpoint) agent build_model(model_pathmodel_file, temperatureconfig[temperature]) results [run_one_item(agent, config, item) for item in items] output_dir OUTPUT_PATH.parent output_dir.mkdir(parentsTrue, exist_okTrue) with open(OUTPUT_PATH, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) success_count sum(1 for r in results if r[success]) print(fsuccess_rate{success_count / len(results):.2f}) if __name__ __main__: main()代码本身不难但有几个关键点脚本只从 eval 目录读取数据train 目录没有被 import。运行前校验模型 hash防止用错模型。每条评测都有独立 seed避免不同任务共享状态。结果写到 eval/output 下不会覆盖训练日志。实际项目中还要加日志、异常捕获、超时控制、并发隔离和资源限制但最小框架已经能保证脱钩的骨架正确。4. 如何构建高质量的脱钩评测集4.1 用“代表性”取代“覆盖面”评测集不是越大越好也不是越难越好。它的价值取决于对真实使用场景的代表性。一个只有 50 条但覆盖多种分布的评测集往往比 5000 条同模板数据更能说明问题。构建评测集的第一步是列出“模型上线后可能遇到的分布维度”。常见维度包括用户的表达风格包括口语、书面语、中英混杂、错别字。任务的组合复杂度例如单工具调用、多工具协同、条件判断、异常恢复。环境返回的格式差异例如字段顺序、空值、重复值、异常文本。目标状态的定义方式例如成功条件是否唯一、是否存在多个可接受结果。对每个维度刻意构造“训练阶段大概率没见过”的样例而不是从训练数据里随机抽样。随机抽样只能验证记忆刻意构造才能验证泛化。4.2 对话评测集构建的基本流程结合 AgentMercury 这类对话智能体推荐按六步构建脱钩评测集。第一步是定需求。明确评测面向哪类任务例如订单查询、售后处理、多轮信息收集。不要一上来就想覆盖全部业务。第二步是采样真实输入。从线上日志里收集用户问题去掉无法脱敏的数据保留多样性。这一步是保证代表性最重要的一环。第三步是生成变体。把真实输入改写为指令式、口语式、负面式、隐含式等变体。改写时避免复用训练模板否则又变回同分布。第四步是人工校验和消歧。每条评测必须有明确的目标状态、成功判定、可接受回答范围。如果一条评测有多种合理解释必须补充约束否则评测结果会不稳定。第五步是版本锁定。评测集文件生成后计算 hash并把这个值记录在评测配置中。后续增加或删除条目都要发布新版本不能静默修改。第六步是发布和归档。评测集发布后立即进入只读状态训练团队只能使用 dev 集做开发调试不能直接阅读最终评测集的答案。4.3 评测集条目字段和模板评测集条目建议使用 JSON 格式字段尽量结构化。下面是一条例子{ id: eval_ticket_0123, task_type: ticket_summary, task_prompt: 用户反馈订单重复扣款请先查清失败原因再给出处理方案。, env_spec: { provider: mock_crm_v2, lang: zh, entities: [order_20240101, user_7788] }, success_check_type: match_fields, required_fields: [order_id, reason_code, action], tags: [unseen_entity, mixed_intent] }各字段含义id唯一标识便于统计。task_type任务分类分析不同任务上的泛化差异。task_prompt用户输入。env_spec评测环境的具体配置包括模拟器类型和已有实体。success_check_type成功判定方式例如 match_fields、rule_based、llm_judge。required_fields判定时需要出现的字段。tags标签用于分组统计。success_check_type 尤其重要。如果成功判定函数和训练环境里的奖励函数完全一致脱钩就会打折。建议评测成功判定独立实现并且对每个判定条件都做人工校验。5. 从 YOLOv5 训练环境搭建看“脱钩”的工程含义5.1 视觉模型的数据拆分并不是真正脱钩很多读者搭建过 YOLOv5 模型训练环境熟悉 train、val、test 三个数据目录。刚开始训练时大多会按比例随机划分数据。这种划分对快速验证算法有效但它和 AgentMercury 强调的脱钩并不等价。在 YOLOv5 流程里test 集虽然不参与权重更新但它和 train 集来自同一批标注数据。标签人员、标注风格、类别分布、图片拍摄条件都高度相似所以 test 集更像“同分布验证集”而不是“分布外评测集”。真正的脱钩评测要求在训练之前额外保留一组完全独立来源的图片或场景。如果训练图片来自白天、晴天、车头视角评测图片就要包含夜间、雨天、车尾视角如果训练目标只包含车辆评测就要准备训练时没有出现过的危险品运输车、三轮车等长尾目标。这种评测才能暴露模型是否只是记住了常见外观。5.2 从视觉训练到智能体训练的迁移对照把 YOLOv5 的实践迁移到 AgentMercury 时可以直接对照下面这张表。对比维度YOLOv5 常规流程智能体脱钩评测数据来源从单一数据集随机切分评测集独立采样或独立生成环境版本图片尺寸、增强策略一致评测环境版本和训练环境分开锁定分布偏移数据同分布刻意制造措辞、工具、目标状态偏移成功判定使用 Ground Truth 框独立实现成功判定函数调参约束容易在 test 集反复实验模型冻结后再跑最终评测缓存影响缓存增强结果可能影响对比评测前清空训练缓存可以看到脱钩不是某个环节的精细优化而是对“评测信息是否流向训练”这条链路做的整体约束。视觉项目里很多人会在 test 集上反复调 confidence 阈值、NMS 参数、anchor 参数这本质上已经污染了评测。智能体项目里的等价行为就是根据评测集中失败案例反复修改奖励函数和系统提示词。6. 脱钩评测时的常见坑和排查链路6.1 至少躲开这四个坑第一个坑是评测集泄漏进训练数据。现象是训练损失正常下降评测得分很高上线后效果明显变差。原因是某些评测条目被直接或间接混入训练数据池。检查方式是在训练数据文件夹里搜索评测集 id 字符串或评测 prompt 的片段。解决方式是训练前做 minhash 或向量去重并且把评测集 hash 写进 CI 检查脚本。第二个坑是随机种子不一致。训练环境用固定 seed评测环境忘记固定 seed或者评测脚本内部每次重新初始化随机状态。结果是同一模型多次评测得分波动很大。检查方式是连续跑三次评测比较得分标准差。解决方式是评测配置里写明 seed并在评测代码开头设置随机种子。第三个坑是成功判定函数与训练奖励函数复用。训练时使用reward 0.5判定成功评测时也复制同一段逻辑。如果训练奖励函数本身已经针对模型弱点调整过评测结果就会失真。解决方式是评测成功判定单独实现并由非训练人员审核。尤其要注意不能只在“明显成功”和“明显失败”上校准判定函数还要对半成功、拒绝回答等边界情况定义清楚。第四个坑是评测环境与训练环境共享缓存。训练时保存了大量对话缓存、工具返回缓存或特征缓存评测时直接复用导致模型在评测环境中遇到的状态和训练完全一致。现象是本地评测稳定换到干净环境后分数骤降。解决方式是评测运行前使用独立临时目录并清空环境变量里的缓存路径。6.2 从“评测得分高但上线失败”开始的排查链路当出现“离线评测分数很高线上表现却很差”时不要第一时间调整模型先按下面顺序排查。确认评测集是否被训练脚本访问过。检查训练代码中的文件路径、日志中的读取记录、训练数据采样器是否包含评测集目录。确认评测集是否被用于超参选择。翻看实验记录如果模型最终版本是在评测集上反复试出来的脱钩已经失效得分没有参考价值。确认分布偏移程度。对比评测集和线上实际输入的语义相似度、实体类型、对话轮数、工具调用次数。如果评测集本身偏简单分数高不代表能力强。确认评测环境复现性。固定 seed 后重跑三次看结果是否一致。不一致说明环境或模型仍有随机性。确认成功判定函数。找一条线上失败但评测判为成功的 case人工核对模型输出是否真的符合业务要求。确认缓存和状态污染。检查评测运行时的临时目录、环境变量、并行 worker 之间是否共享了不该共享的状态。排查时建议把每一步的结论记在表格里例如检查项检查方式结论处理建议评测集泄漏搜索训练数据中的评测 id未泄漏持续保持超参污染查看实验记录曾用评测集调过温度重新冻结模型分布偏移对比线上日志与评测 prompt评测分布偏简单补充分布外评测复现性固定 seed 跑 3 次标准差 0.8%可接受成功判定人工抽检失败 case判定过宽重写判定函数缓存污染干净环境重跑分数下降 5%评测隔离缓存排查的真正目的不是找到单一 bug而是看整条信息链路里评测分布有没有以任何形式流回训练侧。只要有分数就要打折扣。7. 脱钩评测的最佳实践与扩展方向7.1 生产环境脱钩评测检查清单固化一套清单比临时争论“谁动了评测集”更高效。以下 20 项可以作为发布前检查依据。评测集生成脚本与训练生成脚本是否完全独立。评测集是否记录了版本号和内容 hash。训练脚本是否禁止读取评测集目录。CI 是否检查训练数据与评测集之间的重复片段。评测环境版本是否锁定为独立包或独立镜像。评测配置是否固定 seed、温度、最大步数。评测运行前是否清空训练缓存和中间结果。模型文件是否记录 hash并在评测前校验。成功判定函数是否由非训练人员评审。评测结果是否写入独立输出目录。训练阶段是否只允许使用 dev 集。dev 集与最终评测集是否完全独立。模型是否冻结后才运行最终评测。是否记录了评测耗时、运行环境、并发数。是否对每条评测结果保留原始日志。是否按标签分组统计成功率而不只看总分。是否包含分布外样例且比例在配置中写明。是否有人工抽检机制避免自动判定过宽或过严。评测集一旦发布是否进入只读状态。是否定期用独立来源的新评测集替换或扩充旧评测。清单不是用来收藏的而是要在训练开始前、模型冻结后、评测发布后分别执行一次。7.2 下一步扩展方向脱钩评测解决了“分数是否可信”的问题之后还能继续扩展。第一个方向是自动生成评测集。可以用大模型批量改写任务描述再用规则筛选掉与训练数据重复的条目。但自动生成后的评测集必须经过人工抽样校验不能直接上线。第二个方向是对抗式评测。训练一个专门“挑错”的攻击型 evaluator持续寻找模型会失败但当前评测集没覆盖的场景。这些失败场景进入评测集后比单纯增加数量更有价值。第三个方向是跨环境迁移评测。把同一个模型放到不同模拟器、不同工具返回格式、不同业务域的评测环境中观察分数变化。这一步更接近真实上线环境也更接近“训练环境与评测集脱钩”的最终目标。第四个方向是在线评测。离线评测集解决不了实时变化的长尾分布。可以在生产环境设置小流量的影子评测把真实请求记录为匿名评测样本再离线评估模型输出。这样评测集可以持续反映真实分布而不是永远停留在创建那一刻。回到 AgentMercury 的核心结论训练环境与评测集脱钩本质上是在用工程手段维护一条信息隔离边界。如果下次再遇到训练得分高但换环境失败的情况先不要继续调参先把评测集路径、训练集路径、随机种子和成功判定函数四张表列出来。脱钩不是一次性的文件划分而是一条持续执行的规则。把这条规则固化到脚本和 CI 里泛化能力才不会只停留在项目标题上。