☰
Agent执行轨迹变训练数据:SFT与DPO后训练闭环实战
2026/9/26 10:55:30 网站建设 项目流程

上周我把线上客服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 第一道闸门:按结果信号过滤

清洗的第一个动作是拿结果信号做粗过滤。我的经验是先把明显低质量的轨迹干掉,保留候选集,再抽人工审。粗过滤规则可以做成一张表:

信号类型具体判定建议处理
任务状态success=false 且用户明确投诉直接排除,或者按失败样本进入DPO负样本池
执行步数步数超过正常范围N倍,且没有成功大概率死循环,排除
工具错误率同一任务中工具调用失败超过3次多半是模型没学会工具用法,进入纠错池
回复长度最终LLM输出小于10个字且success=true可疑,转人工
超时中断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_ascii=False)}" ) }) 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 epoch

DPO阶段在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才会越用越聪明。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询