上周我把线上客服Agent连续跑了三天导出两千多条执行轨迹洗掉噪音后剩下六百条高质量数据用这批数据做了一次SFT加DPO后训练模型在评测集上的工具调用成功率从71%提高到了84%平均任务步数也少了1.3轮。整个过程从采集、清洗、训练到上线评测跑了一周今天就把这条“执行轨迹变训练数据”的后训练闭环完整拆开讲一遍。先给没接触过的朋友划个重点Agent每执行一次任务就会留下一串“决策足迹”包括它看到什么、想了什么、调用了哪个工具、工具返回了什么、它下一步怎么修正这些内容远比普通聊天记录信息量大因为它们包含了状态变化和决策过程。把这些足迹整理成SFT样本、DPO偏好对再送回模型做后训练Agent就变成了一个能自我迭代的系统。这篇内容也不是绑定某个框架的教程而是把通用的数据格式、清洗规则、训练样本构造方法和踩坑经验讲清楚正在做Agent框架、智能体编排、工具调用、记忆系统或模型微调的工程师可以直接照搬思路。1. 为什么要把执行轨迹变成训练数据1.1 一次LLM调用只是日志完整轨迹才是样本普通LLM应用是“一句进一句出”一条Prompt对应一次Completion这就是一条样本。但Agent不是这样的结构任务是多步决策任务进来后模型要自己决定先做什么、调哪个工具、参数怎么填看到工具返回结果后还要决定继续执行还是结束任务。一次任务下来会产生十几次甚至几十次模型调用每两次调用之间还夹着工具的执行结果。所以Agent的基本数据单元应该是“轨迹”而不是“问答对”。把轨迹变成训练数据收益非常直接。第一它能优化每一步的决策。基座模型虽然会聊天但不会自动知道你开发的get_weather接口需要什么参数格式不知道公司内部工具返回结构长什么样也不清楚哪些错误要重试、哪些错误应该换方案。这些知识只存在于真实工具调用的轨迹里。第二它能形成演进闭环。模型上线后本身就在源源不断产生轨迹每周导出轨迹、清洗、注入训练、重新上线这个循环能让Agent的使用表现持续变好而不是越用越僵。很多团队在第一步就卡住了。他们把Agent日志打印出来看了一眼发现全是时间戳和零散的prompt片段根本拼不出完整决策过程于是放弃了继续往下做。实际上轨迹数据从一开始就要以“任务为中心”去设计不是以“日志为中心”。日志是给别人排查问题看的轨迹是给模型当教材学的两者的组织方式完全不一样。1.2 后训练闭环的五个环节一次完整的闭环可以拆成五个环节执行、采集、清洗、训练、评测回归。执行由Agent系统本身完成不需要额外做事。采集是在Agent框架里打点把每次LLM输出、工具调用及返回值、内部记忆变化全部落盘。清洗是把没完成任务、死循环、参数填错、包含敏感信息的低质量轨迹滤掉再做抽样和去重。训练是把高质量轨迹转换成SFT或DPO数据集对基座模型做轻量后训练。评测回归是拿固定的任务集跑前后对比看成功率、步数、成本变化达标后开放上线。五个环节里面执行和评测大家相对熟悉真正的瓶颈往往在采集、清洗和样本转换这三个环节。数据没有结构化采集后面的清洗和训练都无从谈起清洗规则太粗模型会把坏习惯一起学走训练样本转换方式不对模型可能不仅没变聪明反而连基础对话能力都退化了。后面几章我会按顺序把每一步掰开讲。1.3 哪些场景最值得先做这个闭环也不是所有Agent都需要立刻做闭环。如果你的Agent只是单轮RAG问答轨迹信息量很小优先做好检索和Prompt提示就够了。但下面这几类场景收益会特别明显工具数量多、接口格式复杂模型经常填错参数轨迹里能看到典型错误样本是纠错的最佳素材。多步规划型任务任务需要五步以上才能完成中间容易跑偏轨迹能明确告诉你“跑偏”是如何发生的。线上策略频繁调整业务接口升级、Prompt模板调整旧轨迹可以筛选出受影响的问题针对性做后训练。有稳定评测集每一次闭环的胜负都能被量化否则迭代就是盲人摸象练了半天不知道效果。我自己实际做下来一次闭环周期大概一周是比较舒服的节奏。周一到周三采集线上轨迹周三周四做清洗和人工抽审周五跑训练下周一评测上线。数据量不需要特别大后面会详细说质和量的平衡。2. 轨迹数据长什么样从一次执行到一条样本2.1 以客服Agent为例看轨迹结构拿一个熟悉的场景举例用户问“帮我查一下深圳本周的天气并给出行建议”。一次完整执行会产生大约四步模型先判断需要查天气这是LLM输出然后调用get_weather工具这是ToolCall工具返回七天的天气数组这是Observation最后模型整理成回答文本又是LLM输出。如果第一遍工具参数填错了还会多一个“修正参数再调一次”的步骤。我建议统一用下面的结构来存轨迹JSON Lines格式一行一条。每个字段的含义我都写在注释里{ trajectory_id: traj_20241212_001, task: 帮我查一下深圳本周的天气并给出行建议, domain: customer_service, steps: [ { step_index: 0, type: llm, content: { role: assistant, content: 我来看一下深圳本周的天气情况需要先查天气数据。 } }, { step_index: 1, type: tool_call, content: { tool_name: get_weather, parameters: {city: 深圳, date_range: 本周} } }, { step_index: 2, type: observation, content: { tool_name: get_weather, status: success, result: [{date: 12-12, weather: 多云, temp: 18~25}, {date: 12-13, weather: 阵雨, temp: 16~22}] } }, { step_index: 3, type: llm, content: { role: assistant, content: 深圳本周以多云和阵雨为主温度在16到25度之间建议出门带伞早晚加一件薄外套。 } } ], success: true, reward: 0.95, human_feedback: 5, meta: { model: base-agent-v0.3, temperature: 0.3, timestamp: 2024-12-12 10:23:45, user_id: anonymous_001 } }这里最关键的是把类型区分清楚。LLM输出、工具调用、工具返回观察值这三类事件不能混在一个字段里否则后续做训练样本时无法准确切分“模型说了什么”和“模型看到了什么”。Agent轨迹数据的质量很大程度上取决于这个Type设计。2.2 设计轨迹Schema的三个关键点第一个关键点是按步骤顺序存储每个步骤要有全局递增的step_index。不要用嵌套对象的方式去表达循环Agent执行过程中经常会有重试和循环嵌套表达会让回放和清洗逻辑非常痛苦。拍平成一个数组是最省事的。第二个关键点是保留完整工具调用参数和返回状态。工具调用至少要包含tool_name、parameters、status、result四个字段。不少团队只记录result不记录parameters后面想做工具调用纠错训练时才发现没有坏样本得返工重采白白浪费几周时间。status字段尤其重要它标记了这次调用是成功、超时还是异常。失败样本是你训练的重要财富绝不能因为“数据不干净”就直接丢掉。第三个关键点是元信息不能丢但要做脱敏。模型版本、采样温度、时间戳、用户反馈这些信息看似和训练无关实际上清洗的时候有大用。比如某个模型版本跑出来的失败率特别高说明那个版本可能有系统性缺陷这类轨迹在训练时要特殊处理。user_id这类身份信息要匿名化这是底线后面安全章会详细说。2.3 成功信号和反馈信号必须同步收集轨迹里最容易被忽视的字段是success和reward这类结果信号。一条轨迹如果不知道最终是成功还是失败它在训练里就很难定位价值——你不知道该让模型学习这条路径还是远离这条路径。结果信号的来源可以是任务规则判定、评测集打分、用户显式反馈、模型产出与标准答案的相似度也可以是人工抽审。我工作中常见的问题是测试环境里能清楚判断成功失败但上了生产环境任务是否有明确对错都很难定义。比如客服Agent帮用户查了个订单状态用户回了个“好的”这条任务算成功还是算失败这时候我建议定义三级标签success表示明确完成partial表示做了部分动作但结果存疑failure表示明确失败或用户投诉。清洗时partial单独抽出来人工审宁可少不用也不能学坏。3. 数据清洗与筛选不是所有轨迹都值得学3.1 第一道闸门按结果信号过滤清洗的第一个动作是拿结果信号做粗过滤。我的经验是先把明显低质量的轨迹干掉保留候选集再抽人工审。粗过滤规则可以做成一张表信号类型具体判定建议处理任务状态successfalse 且用户明确投诉直接排除或者按失败样本进入DPO负样本池执行步数步数超过正常范围N倍且没有成功大概率死循环排除工具错误率同一任务中工具调用失败超过3次多半是模型没学会工具用法进入纠错池回复长度最终LLM输出小于10个字且successtrue可疑转人工超时中断execution terminated due to error截取前缀不整条使用敏感内容轨迹中出现手机号、身份证、Token密钥脱敏后保留或直接排除粗过滤的目标不是把数据洗到完美而是去掉大部分噪音让后续人工审查能把精力花在真正难判断的样本上。我第一轮清洗通常能滤掉30%到40%的轨迹剩下里面还有不少是partial状态需要抽审。3.2 第二道闸门过程质量判断结果信号过滤解决“成没成”的问题但解决不了“过程对不对”的问题。有些轨迹虽然成功了但过程里全是坑连续三次用同一个错误参数重试同一个工具、思考链自相矛盾、明明有更合适的工具却绕了远路、或者被工具返回里的恶意指令带跑了。这类轨迹就是典型的“坏成功”拿去做SFT正向样本模型会强化绕路和死磕的错误行为。过程质量检查我建议用规则加模型打分结合。规则层面可以判断是否存在连续相同tool_call是则降权observation返回错误后是否更换了工具或修正参数没有则降权是否出现了与任务无关的工具调用是则降权。模型打分层面可以拿一个较强的模型当评审对轨迹的每一步决策给合理性评分评分区间1到5分低于3分的样本不要进正样本池。再补充一个很实用的做法把“坏成功”和“好失败”分开保存。坏成功是过程糟糕但结果碰巧对好失败是任务没完成但过程决策合理、策略正确只是最后一步工具挂了。这两种样本用在不同地方坏成功用来做DPO负样本好失败的前缀步骤可以用来做SFT正向训练激励模型保持正确策略。3.3 多样性去重与数量配比清洗完的轨迹还有一个隐藏风险高度同质化。线上流量集中在少数热门问题上如果你的清洗后数据里70%都是查天气、查订单训练出来的模型会在这些小任务上表现很好但换个冷门场景能力就塌了。去重不能只看任务文本完全相等要看语义相似度和决策路径相似度。同一个任务模型分别走A工具和B工具完成这两条轨迹要保留同一个任务两条轨迹用相同的工具、相同的参数、只差几个字只留一条就行。关于数量配比我实际操作下来比较稳的配方是正向SFT样本占六到七成来自成功且过程质量好的轨迹DPO偏好对占两到三成其中既有成功对失败、也有好过程对坏过程剩余一部分是人工构造的困难case。整体数据量看模型规模和任务复杂度LoRA训练的话五十条高质量轨迹起步就能出效果三百到五百条是一个很舒服的量级。别一上来就追求上万条轨迹数据的信息密度比普通文本数据高得多。3.4 清洗之后的抽审闭环千万不要只依赖规则清洗就结束。每批数据清洗完至少要抽10%到20%做人工复核复核的维度包括结果信号标注是否正确、过程是否存在规则没抓到的问题、工具参数是否有被错误清洗掉。抽审不是单纯排查而是校准规则。如果抽审发现“工具连续失败次数超过5次”这个规则误杀了大量高质量轨迹就要调整阈值。我把抽审查出来的误杀样本单独存一个目录下一轮清洗直接优先通过避免好的样本被反复误杀。这套抽审校准机制是我认为整个清洗环节里最容易被忽视却最能提升数据质量的细节。4. 从轨迹到训练样本SFT、DPO和轻量替代方案4.1 步骤级SFT样本怎么构造把轨迹转成SFT样本有两种粒度整条轨迹级和步骤级。整条轨迹级就是把任务描述和完整轨迹文本拼成问答对让模型学习完整套路缺点是样本短则几百字、长则几千字训练时容易超过长度限制而且一条轨迹只贡献一条样本数据利用率低。步骤级则是把一条轨迹按决策点拆成多个样本每个样本学习“在某个历史状态下模型下一步应该输出什么”。一条十步的轨迹可以拆出好几条有效样本数据量瞬间放大而且每一步的监督信号都对齐得非常明确。我用一段伪代码展示步骤级SFT样本的构造思路def build_sft_samples(trajectory: dict, max_obs_len: int 800): samples [] history [{role: system, content: SYSTEM_PROMPT}] history.append({role: user, content: trajectory[task]}) for step in trajectory[steps]: if step[type] llm: # 当前步的模型输出就是要学的目标 target step[content][content] samples.append({ conversation: history[-k:], # 保留最近K轮上下文 response: target, }) elif step[type] tool_call: history.append({ role: assistant, content: ( f[调用工具] {step[content][tool_name]}\n f参数: {json.dumps(step[content][parameters], ensure_asciiFalse)} ) }) elif step[type] observation: history.append({ role: user, content: f[工具返回] {truncate(step[content][result], max_obs_len)} }) return samples这个构造里最影响训练效果的是“上下文窗口”的选择。我建议只把最近K轮历史作为输入而不是从头到尾全塞进去。工具调用轨迹有一个特点决定当前步骤的关键信息往往都在最近几轮里越久远的信息对当前影响越小。K取4到8是个常见选择具体要看你任务的记忆强度任务特别长的可以适当加大。全部历史塞进去会让输入太长模型反而不容易聚焦。4.2 DPO偏好对怎么构建SFT解决“让模型学会正确的动作”DPO解决“让模型偏爱正确动作、远离错误动作”。轨迹数据天然适合构造偏好对让同一个模型对同一任务跑多次可能有的轨迹成功有的失败或者同一决策点一个分支走了正确的工具另一个分支走了错误参数重试。这两条轨迹拼在一起就构成了一组chosen和rejected。构造DPO样本的最关键点是正负样本的差异要尽可能边界清晰。如果正样本是“一次调用就成功”负样本是“重试了三次最终成功”模型能学会少走弯路如果负样本本身就是这次任务注定无解模型学到的更多是“这个任务不该做”而不是“这个动作不该做”。我的建议是DPO偏好对优先做“同起点、不同分支”的样本用同一段历史上下文一个走了正确策略一个走了错误策略。两条轨迹的输出文本要转成完整回答文本注意要保持prompt前缀一致否则DPO的loss对比就失真了。DPO样本的伪代码大概长这样positive build_response_text(success_trajectory) negative build_response_text(fail_trajectory) sample { prompt: SYSTEM_PROMPT \n任务: task, chosen: positive, rejected: negative, }如果觉得整条轨迹级的DPO对太长训练内存吃不消可以退一步做步骤级偏好对同一历史上下文下正样本是模型“本轮该输出的正确决策”负样本是模型“本轮实际输出的错误决策”同样有效而且整体长度可控。两种方案我都在实际训练里验证过效果没本质区别优先选长度更短、更稳定的方案。4.3 比微调更轻的做法示例库与思维前缀如果手里没有训练资源或者模型是纯闭源API也可以先把轨迹价值利用起来。一种做法是把清洗后的优良轨迹沉淀成示例库在线推理时按任务相似度检索出两条优秀样例塞进few-shot上下文模型跟随样例也能达到不错的改善。另一种做法是把轨迹里提炼出的成功策略压缩成几句“思维前缀”比如“先确认参数再调用工具”“工具返回错误时先尝试修正参数不要原样重试”“同一个工具连续失败两次就换路径”。这些前缀写进系统提示词里短期效果也能看到成本几乎为零。我自己喜欢把这种轻量做法作为“训练前验证”。先通过示例库和前缀验证哪些策略真的能提升效果再决定要不要花算力做训练。因为轨迹数据清洗到位之后大概率能提炼出几条稳定有效的策略这些策略直接用提示词注入已经能解决一部分问题微调子弹留到瓶颈期再打。4.4 训练参数选择的几条经验模型选择上我首选基座能力本身强一点的模型一个会规划但不会用工具的模型比一个啥都听不明白的模型更容易通过后训练补齐工具使用能力。训练方式优先LoRA不是全参微调。轨迹数据本质上是教模型“适应你的工具环境”不需要大幅改变模型的基本能力LoRA的参数量级恰好匹配这种需求还省显存迭代速度快。数据配比方面我常用LoRA rank 32到64学习率在1e-5到3e-5之间SFT两个epoch以内DPO一个epoch就够了。数据量少的时候epoch多一些容易过拟合500条优质轨迹跑3个epoch问题不大如果数据量上千条建议控制在1到2个epoch。训练时要注意把通用对话数据混入一部分比例在20%到30%比较稳。后面避坑部分我会讲如果不混通用数据模型会开始“满口工具调用”连用户纯闲聊都想要调个函数非常头疼。5. 一次性跑通闭环采集、训练、评测的实操清单5.1 采集端怎么打点采集要在四个位置打点LLM调用前记录原始Prompt和完整上下文LLM调用后记录Completion、token数和延迟工具执行前记录工具名、参数工具执行后记录返回状态、返回结果摘要和耗时Agent内部状态流转时记录记忆更新、重试次数、当前步骤目标最后是结果汇总生成整个任务的success、reward、用户反馈等元信息。最简单可靠的落盘方案是JSON Lines文件按天轮转每条轨迹一行。要不要用消息队列看规模。日采集量在几万条轨迹以内直接文件加个锁就行量大再上队列和列式存储。这里我的实际经验是采集要异步并设置队列上限千万别让打点阻塞Agent主链路。曾经有团队因为在工具调用链路里加了同步的日志写入接口延迟直接从100毫秒涨到800毫秒最后被迫整体回滚这个坑踩得毫无价值。5.2 存储、版本与回放轨迹数据建议分开存轨迹正文用JSONL轨迹的索引和元信息进SQLite或PostgreSQL。清洗时先查元信息过滤出候选集再按id去读轨迹正文。每做一次清洗或训练都要建一个数据版本号。版本号可以很简单就是“日期_清洗规则版本_基数”比如20261212_r2_v1。后训练开跑之前把版本号和训练参数一起记录到训练日志里这样以后任何一次模型行为变化都能追溯到究竟是哪批数据哪个环节引入的。另外强烈建议做一个简单的轨迹回放工具。我一直觉得不回放轨迹就清洗等于闭眼开车。回放就是把轨迹按step_index逐条渲染成“可读剧本”人眼能顺着看明白模型经历了什么。很多规则清洗抓不到的问题回放一遍就能发现。回放工具不用做得多精致能渲染时间线、能高亮工具调用的错误状态、能展示每个决策点的具体文本就够了。5.3 用开源框架完成后训练训练直接用开源框架不要自己写训练循环。以LLaMA-Factory为例SFT阶段可以用下面这类CLI命令llamafactory-cli train \ --model_name_or_path Qwen2.5-7B-Instruct \ --stage sft \ --dataset agent_traj_sft \ --template qwen \ --cutoff_len 8192 \ --output_dir ./output/agent-qwen-sft \ --num_train_epochs 2 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --lora_rank 64 \ --save_strategy epochDPO阶段在SFT产出的checkpoint上继续llamafactory-cli train \ --model_name_or_path ./output/agent-qwen-sft \ --stage dpo \ --dataset agent_traj_dpo \ --template qwen \ --cutoff_len 8192 \ --output_dir ./output/agent-qwen-dpo这里的cutoff_len建议设置到能覆盖九成以上的步骤级样本。根据你的工具返回长度来定如果observation已经做了800字截断那8192基本够用如果保留完整工具返回8192很容易爆掉。数据集格式要去熟悉你所选框架要求的对话格式通常是一个JSON数组中间过程用的对话模板和推理时保持完全一致这个一致性我是在一次训练后模型行为突然跳变时才深刻体会到的。5.4 评测与回归指标后训练完成不是终点评测才是决定能不能上线的关口。我建议维护一个固定评测集混合三种来源线上采样的真实任务、人工构造的困难case、老版本跑失败但新版本应该修复的任务。每条评测任务提前配好标准打分规则。常用的回归指标有下面几个指标计算方式说明任务成功率评测集中任务成功完成的比例核心指标工具调用准确率工具名与参数全部正确的调用次数 / 总调用次数反映工具使用熟练度平均任务步数总步数 / 任务数反映决策效率平均token消耗总token / 任务数成本和效率的直接体现用户侧反馈分人工或另一个强模型按5分制评分补充客观成功率的盲区我每次做闭环评测时都会把评测集分成“简单、中等、困难”三档只统计总数会掩盖模型偏科。经常出现的情况是整体成功率上涨了三个点点开明细发现简单任务涨了、困难任务反而跌了这时候需要把困难任务的样本单独抽出来分析是不是模型被简单样本带偏了。6. 常见问题与避坑记录做这个闭环一年下来踩过的坑写出来能列满满几页这里挑几个最典型、影响最大的记录一下。6.1 工具返回结果太长导致训练样本溢出工具返回常常是“喂不饱”的源头数据库查询结果几百行、网页抓取全文几千字、图片识别输出一串base64。直接把这些内容塞进训练样本cutoff_len再大也不够用。我的做法是在采集端就对Observation做摘要化处理保留与任务相关的关键字段去掉冗余每条Observation限制在800字以内。注意是在存储轨迹前就截断而不是训练时再截断。训练时截断会让模型看到“不完整的前缀”学出来的输出往往也是残缺的。6.2 轨迹中断与半截数据怎么处理Agent在执行过程中因为API超时、工具异常或上下文溢出而中断这种情况线上非常常见。轨迹录了一半就停在中间算失败样本还是丢弃我的经验是分情况看。如果中断前模型已经完成了主要操作只剩最后一步总结没输出可以把已完成的动作部分保留为正向样本同时把“中段失败”的标签单独标出来不进成功池也不进失败池。如果中断发生在任务刚开头说明模型连第一步都没走对直接丢弃。处理半截轨迹时一定要打特殊标记否则很容易把“不完整的输出”当作正确输出教给模型。6.3 成功失败标反了自动判定成功失败看似简单真实场景里非常容易出问题。用户说“好的”“谢谢”不代表任务完成只是会话礼貌性终止Agent输出了一个模板回答但内容完全错误也被判定成了success。这类标注错误会让训练数据出现严重噪音。我的做法是定义结构化成功判据比如查天气任务必须同时满足“调用了天气工具”且“返回结果未被截断”且“最终回答包含具体天气数据”才算成功三条任何一条缺了都转partial。判据落到代码里能顶住80%以上的误标情况剩下20%靠抽审兜底。6.4 训练后模型变笨了这是最常见的翻车现场训练完模型工具调用能力上来了但用户随便聊两句模型也非要调个工具或者回答变得机械僵硬。根本原因是训练数据里Agent专用样本比例过高把模型的基本对话分布冲垮了。解决方案是在训练集里混入20%到30%的通用指令数据保留基础模型的聊天和推理能力同时严格控制epoch次数轨迹数据量越大epoch越要收敛。我第一版训练就是全量轨迹数据直接练了三个epoch结果模型变成了“工具复读机”只能重新跑一版带通用数据的训练。6.5 模型学会了错误重试模式轨迹数据里有一类坏味道模型遇到工具报错后不换参数不换工具原封不动地再重试一次。如果这类轨迹在正样本里没被清洗掉模型就会把“重试”当成万能药。更隐蔽的是有些轨迹里模型重试了三次最终碰巧成功规则清洗没判它失败就进了正样本池等于告诉模型“同样的错误再来三次就能成”。针对这类问题清洗规则里要加一条连续相同tool_call次数等于或超过两次的轨迹过程质量分直接减半DPO阶段专门构造“错误重试”作为负样本和“更换策略”作为正样本的偏好对。6.6 敏感信息与提示注入的过滤线上轨迹里几乎必然包含用户隐私、内部系统返回数据甚至可能有攻击者故意塞进的反问和注入指令。这部分清洗是红线。我的处理分两层第一层脱敏手机号、身份证号、邮箱、密钥等正则匹配后替换成占位符用户ID做哈希第二层是注入检测如果Observation字段里出现了“忽略之前的指令”“你现在是另一个模型”这类明显注入写法要打标记不能让模型在训练后学到“执行外部文本里的指令”。原样保留敏感轨迹进入训练集不仅是合规问题更是把样本变成定时炸弹迟早炸在线上。写到这里最后再分享一个我自己体会最深的小技巧清洗和训练规则不要一次性定死每个闭环结束后都回头看看那些被过滤掉的数据里有没有被冤枉的好样本。有一次我因为“工具连续失败超过三次就降权”这条规则把一条非常高质量的多步规划轨迹给误杀了那条轨迹虽然中间工具失败了三回但每次失败后都换了新思路最终成功完成了任务。后来我把规则改成“连续相同工具失败超过两次才降权”同时把这条轨迹手动捞回来作为困难case的SFT样本。数据闭环做久了你会发现真正让模型能力突飞猛进的往往不是数量最大的那批样本而是那些在失败边缘不断尝试、最终成功突破的困难轨迹。把它们单独建库每次训练都带上Agent才会越用越聪明。