最近一直在折腾 Agentic SFT(智能体监督微调)的东西,踩了不少坑,也推翻了不少之前的想法。团队内部交流的时候发现,很多人对 Agentic SFT 的理解还停留在“把多步对话数据拼一起做 SFT”这个层面,实际跑过之后会发现完全不是这么回事。这篇就当个人记录,持续更新,主要聊聊我从数据构建、训练设到评估观测的一些浅薄认知和实操教训,希望能给正在搞 Agent 训练的同学一点参考。
如果说传统 SFT 是“考前背题”,那 Agentic SFT 更像是“模拟面试”。背题只需要记住标准答案,而模拟面试要求你在一个动态环境里,根据对方反馈不断调整自己的回答。这两者的训练数据、训练目标和评估方式,本质上就是两套逻辑。
1. 先理清概念:Agentic SFT 到底改了什么东西
1.1 传统 SFT 的假设与瓶颈
传统 SFT 的训练样本是一个静态的“问题-答案”对。模型要学的映射是:给定用户指令,直接生成最终回答。这条路在对话、文案生成这类单轮任务上非常成熟,但一旦放到 Agent 场景就开始暴露问题——真实环境里一个任务往往需要多轮交互,模型要调用工具、读取返回结果、纠错重试,最终才能完成目标。
传统 SFT 训练出来的模型有两个典型毛病:第一,它把“问题”和“最终答案”强行绑定,中间那些关键决策过程(比如调用哪个工具、如何解析返回结果)它根本没学过;第二,训练时模型看不到“动作之后的反馈”,比如工具报错后该如何恢复,这种临场应对能力在静态问答对里学不到。
还有一个更隐蔽的问题:传统 SFT 数据通常把一段多轮对话压成单轮问答来构造,模型的上下文窗口在训练时被强行填充上用户消息和最终回答,中间的工具调用痕迹被抹掉了。模型学到的认知是“用户提问,我直接给结果”,一旦推理时真实存在工具返回阶段,模型就不知道怎么接话,经常出现格式崩溃。
1.2 Agentic SFT 的核心差异:轨迹级监督
Agentic SFT 的训练单元不再是单轮问答,而是一条完整的轨迹(trajectory)。这条轨迹长这样:用户指令、模型的思考与动作(比如工具调用的结构化文本)、环境返回的结果(observation)、模型下一步思考,直到最终给出答案。
这里最关键的差异,是模型要学习“根据环境反馈调整决策”这个循环能力。每一步模型看到的都是当前可观察的状态,包括用户原始需求、历史动作、最新工具返回,它需要输出一个合理的 next action。这个模式和强化学习有些类似,但 Agentic SFT 仍然属于监督学习的范畴——所有动作都是我们用标签数据直接指定的,而非通过奖励探索获得的。
用一个不严谨但好理解的类比:传统 SFT 教模型“知道答案”,Agentic SFT 教模型“会干活,并且干到一半遇到意外知道怎么处理”。
1.3 它和 RLVR、DPO 的关系
很多同学问,做了 Agentic SFT 还有必要做 RLVR(基于规则的强化学习)吗?我的理解是:SFT 是让模型“会”,RL 是让模型“精”。Agentic SFT 的目标是把模型从“完全不会 Agent 交互”拉到“基本流程能走通”,但它有一个天花板——监督信号来自固定标签,模型只能模仿标注轨迹里的行为,无法超越数据分布。
如果只用 Agentic SFT,模型遇到数据里没出现过的边缘情况(比如工具超时、返回格式异常)大概率还是崩。这时需要 RLVR 这类在线探索机制,让模型自己尝试不同的恢复策略,根据环境结果获得奖励信号。Agentic SFT 的价值在于给 RLVR 提供了一个不崩的初始化策略——跳过了“连格式都不对”的阶段,RL 探索时不会频繁触发截断。
至于 DPO(直接偏好优化),我在实践中的定位是“事后修正”。SFT 训练完以后,模型可能在某些决策点(比如该重试还是该放弃)产生偏好偏移。这时可以收集一些好轨迹和坏轨迹做偏好对,用 DPO 微调一下。但 DPO 的偏好对如果构造不严谨,很容易让模型只是调整了措辞,而不是真正改进行为。所以这个组合顺序是:Agentic SFT 打底、RLVR 提上限、DPO 做策略微调——千万别颠倒。
2. 数据构建:Agentic SFT 的灵魂所在
2.1 轨迹数据从哪里来
这部分我踩的坑最多。刚开始我们尝试直接用真实用户日志里的多轮会话拼轨迹,结果惨不忍睹。真实会话里大量无意义试探、重复操作和最终未完成的半截任务,模型学这些只会变成“低效的模仿者”。
目前我们用的可靠方案是“模拟环境采样 + 结果过滤 + 人工抽检”三件套:
- 在可控的模拟环境里(比如一个测试用电商搜索系统),把任务清单灌进去,让一个能力较强的通用模型自主完成。
- 只保留最终任务状态为“成功”的轨迹,且核对每一步工具调用的参数合法、返回结果被正确解析。
- 人工抽检 20%-30% 的轨迹,主要看中间推理是否合理,剔除“歪打正着”的成功案例。
这个流程的核心是“环境可控”。模拟环境里的工具接口、报错规则都是我们自己定义的,因此能精确控制轨迹分布,想增加“重试后成功”的样本就故意注入工具报错。
2.2 配比策略:单步数据和轨迹数据的比例
实验中我们发现,如果把训练数据全部换成轨迹数据,模型的单轮对话能力会出现明显退化,尤其在通用问答上变得啰嗦、喜欢强行调用工具。最有效的做法是拿一定比例的常规指令数据做混合。
目前我们比较稳的比例是:单轮指令数据占 40%-50%,多步轨迹数据占 40%,剩余的 10%-20% 保留纯 reasoning 数据(比如数学解题链)。这个比例不是拍脑袋定的,我分别试过 20% 和 70% 的轨迹占比,结果 20% 导致 agent 能力基本没提升,70% 导致通用能力崩掉。40% 左右是一个甜区。
还有个细节:同一个任务的多条变体轨迹要控制重复度。模拟环境里同一个任务查同样的商品,轨迹会很相似,如果不去重,模型会过拟合到某几种固定的搜索路径上,一旦真实环境里的搜索词分布稍微偏移,表现立刻下滑。我们做了轨迹相似度去重,按关键词重叠率超过 70% 的就归成一簇,每簇最多保留 3 条。
2.3 数据字段与序列格式设计
Agentic 轨迹数据格式直接影响训练时的 mask 设计和模型是否能学会“何时停止”。我们内部用的格式大致如下:
{ "instruction": "用户想买一款性价比高的降噪耳机,预算500-800元", "messages": [ {"role": "user", "content": "帮我在商城找一款降噪耳机,预算500-800"}, {"role": "assistant", "content": "[SEARCH] 降噪耳机 500-800元"}, {"role": "observation", "content": "返回6条商品,前3条为:...,价格区间...,平均评分..."}, {"role": "assistant", "content": "[SELECT] 第2款 型号A 价格699 评分4.8"}, {"role": "observation", "content": "商品详情:... 库存充足,符合预算"}, {"role": "assistant", "content": "我推荐你选择这款耳机,价格699元... 理由:..."} ] }这里的关键点是把工具调用和 observation 都显式建模成消息角色。训练时对 observation 角色的 token 做 mask,模型看不到 loss 信号,避免模型去“预测环境返回什么”。它只需要学会根据 observation 内容生成下一轮决策,这个机制保证了模型不会把环境和自身行为混为一谈。
2.4 轨迹过滤的隐性标准
只看最终成功还不够。要强制过滤下面几类轨迹,否则训练效果大打折扣:
- 步骤过长的高 token 轨迹:超过 20 步的轨迹往往包含大量无效尝试,模型学多了会产生“长时间不收敛也正常”的错觉。
- 模型中途自问自答的轨迹:有些模型会在无 observation 的时候自己“猜测”工具返回,这种轨迹要斩断。
- 单步收益为负但不影响最终结果的轨迹:比如先查了一轮无关内容再切回正轨,这类轨迹里的子决策是错误的,却在成功标签下被保留下来,污染很大。
这一块目前我们还没有一个全自动的完美解法,相对靠谱的组合是规则过滤 + 奖励模型打分排序。对轨迹中每一步动作算一个过程奖励分,最后把高分轨迹和低分轨迹都保留一部分——保留低分是为了模拟真实环境的多样性,但比例要控制在 15% 以内,且人工确认这些低分轨迹不是“逻辑混乱”。
3. 训练设置:比传统 SFT 敏感得多
3.1 长序列打包与位置编码
Agentic 轨迹天然是长序列,几百到几千 token 都有。直接按完整序列训练会导致 batch 里的样本长度差距巨大,短样本拖慢训练、长样本占用大量显存。我们采用了 packing(序列打包)策略,把多条短轨迹拼进同一个训练序列,由一个特殊的端符分隔,并重新分配位置 id。
具体做法是:每条轨迹内部保持 token 之间的位置 id 连续,跨样本的部分位置 id 重新归零。注意力 mask 采用块状 mask,让不同样本之间不能互相 attend。这个设计如果不做,模型会学到跨样本的不存在的语义关联,推理时出现幻觉。
另外一个坑是超过了模型上下文上限的轨迹。我们最初的基线模型是 8k 上下文,于是把超过 8k 的轨迹直接删了。后来发现这是个错误,因为很多真实任务本来就需要更长的历史,把长轨迹全删掉等于训练时阉割了模型的长程决策能力。解决方式是把基线模型的 RoPE 基频调大,在训练时渐进式扩展到 16k。实验数据上,长轨迹的保留让最终线上任务成功率提升了接近 7 个点。
3.2 Loss 掩码逻辑:什么该算,什么不该算
训练时 loss 掩码可能是最容易被忽略、影响最大的设置。很多开源代码直接对 messages 里的每一条做 next token prediction loss,这意味着模型会去学习预测 observation 和 tool 调用格式里的固定字段。结果就是模型确实学会了“打印工具调用”,但根本没有理解“用返回结果来推理”。
我们的掩码设置非常严格:
- observation 角色的内容:无条件 mask,一条 token 都不算 loss。
- assistant 消息里非语义部分(比如工具调用的结构化 schema 字段、参数名的固定字符):mask。
- 真正需要模型学会的内容:工具调用时的参数值(动态部分)、模型自身的推理思考、对用户回复的自然语言:保留 loss。
- 对“思考”和“最终回答”两种 token 设置不同的 loss 权重:通常思考部分权重为 1.0,最终回答上调到 1.2-1.5。原因是最终回答直接决定任务是否完成,优先级更高。
这里补充一点,很多团队会把整个 [SEARCH] 这类工具调用 token 全部 mask。我试过这个方案,模型在推理的时候工具调用格式经常出错,输出一堆不符合 schema 的垃圾。所以工具调用的“动作名称”和“参数名”要保留 loss,让模型把这些固定格式背下来,只 mask 与预测无关的“参数值”是一种更优的折中。参数值不用预测,选型可以由复杂逻辑决定。
3.3 训练超参:学习率与 Epoch 的怪圈
Agentic SFT 比普通 SFT 更容易过拟合。我们第一版训练时用了通用的 2e-5 学习率和 3 个 epoch,结果模型严重退化,生成的内容几乎全是训练轨迹里高频出现的同款话术。
后续调参经验是:学习率降低到 5e-6 到 1e-5 之间,epoch 控制在 1-2 个。注意这说的是轨迹数据的 epoch,混入的通用指令数据可以多训几轮,因为它更抗过拟合。另一个被低估的参数是 weight decay,我们加到 0.1 以后,格式崩溃的问题明显变少。
如果想偷懒,可以直接采用 warmup 比例 3%、学习率 8e-6、batch size 128、bf16 混合精度这套起步配置。注意如果 base model 是之前做过 chat 指令微调的模型,学习率要再打折到 5e-6 左右,对已对齐模型的破坏能小一些。
3.4 基座选型:直接用 chat 模型还是一般预训练模型?
这是一个很关键的选型问题。我们一开始图省事,直接拿了一个现成的 chat 模型做 Agentic SFT。结果发现两个问题:一是 chat 模型本身已经具有强烈的“回答用户问题”的惯性,很难学会“先调用工具再回答”;二是它自带的安全对齐策略会在工具调用场景频繁触发拒绝。
后来改成用“base 模型 + 轻度通用 SFT”作为起点,训练更顺滑。具体是拿 base 模型先跑少量高质量的通用指令数据(大概几千条),让它具备基本的对话能力和格式遵循能力,然后在这个基础上做 Agentic SFT 轨迹训练。这个中间步骤至关重要,直接从 base 模型跳到轨迹训练,模型连基本的 instruction following 都做不到,轨迹训练根本上不了手。
如果有条件的话,推荐选基座的时候看一眼模型在长文本 retrieval 类任务上的表现。agent 场景对“从长 observation 中提取关键信息”的要求很高,有些基座在这块很弱,靠 SFT 硬拉效果很差。
4. 评估与观测:别只盯着成功率
4.1 离线评估集的设计思路
Agent 模型的离线评估很难做。最终成功率当然要看,但它容易掩盖问题——很多失败发生在中间步骤,最后一步失败导致成功率是 0,你看不出模型到底哪一步崩了。
所以我们的离线评估集设计成“逐步标注”的模式。每一条测试任务标注了关键决策点,比如“是否选择了正确的搜索关键词”、“工具返回后是否提取了正确的信息”、“在第一次搜索无结果时是否执行了合理的改写重试”。这样模型跑完以后,既统计最终成功率,也统计每个决策点的通过率。
这个做法的价值非常直接。有一版模型最终成功率没太大变化,但“重试决策点”通过率从 30% 涨到了 65%——虽然任务没有全部成功,但模型已经学会了遇到空结果时换一种说法继续尝试,这就是 Agentic SFT 带来的实质进步。也让我意识到,单看最终成功率会严重低估中间能力提升。
4.2 过拟合信号怎么识别
Agentic SFT 的过拟合和普通过拟合不太一样,普通模型的过拟合信号是验证集 loss 开始上升,而 agent 模型过拟合的表现更隐蔽:loss 还在下降,但评估集里的格式错误率开始上升,模型越来越爱生成训练集里出现过的固定短语,对新鲜指令的泛化变差。
我用的一个简易检测方法是:在训练过程中每隔一定步数加载 checkpoint,跑一个固定的 20 条测试集,看每条轨迹的“平均动作多样性”。如果发现模型的工具参数选择分布变得特别集中(比如总是查同一个关键词同义词),哪怕最终成功率看起来没问题,也要警惕过拟合。另外可以做一个扰动测试——把测试问题里的关键词替换成同义词,如果成功率掉得特别厉害,说明模型是背了关键词映射,而不是真理解了工具逻辑。
4.3 和 RL 阶段的衔接评估
Agentic SFT 做出来的模型最终还是要交给 RLVR 继续提效果。因此评估时一定要关注“这个模型是否适合做 RL 的初始化”,这个指标单独看。
有两个观测角度:一是 RL 阶段早期(几百步)的成功率提升速度,如果 SFT 模型质量高,RL 起步提升会很快;二是探索死区,SFT 模型如果在某个动作上很固执(比如总是不愿意调用某个工具),RL 阶段很难把它纠正过来。所以我们在 SFT 评估的 checklist 里加了一条:是否所有工具都被覆盖调用过,如果某个工具在测试时候从未被触发,就要检查是不是训练数据里这个工具的比例太低。
5. 常见问题与排查技巧实录
5.1 模型学会了格式,但没有学会推理
这个现象最典型。训练后模型很会输出 [SEARCH] ... 之类的格式,但传入的 observation 内容明明是空列表,它还会继续假装有结果并接着往下推。原因是训练数据里大量 observation mask 导致模型根本没学到“observation 是外部强约束”这一事实。
排查手段:把测试轨迹的 observation 改成一个明显矛盾的内容(比如搜索结果为空但商品标题写着有货),看模型是否坚持原来的回答。如果它完全不受影响,说明观测信息没有进入决策链。我们在数据层面做了两个修正:一是保留少量“早期无结果后重试成功”的轨迹,让模型看到 observation 为空时正确的动作是改写搜索词;二是训练时偶尔把 observation 区域的 token 故意粘到模型的下文里计算一小部分 loss,强制它根据观测信息做下一个动作预测。
5.2 长上下文的灾难性遗忘
Agentic SFT 训练大量几千 token 的轨迹,会导致模型在短文本任务上的表现显著下降。不是那种“回复变空”的大崩,而是更细碎的——比如总结、分类这类任务变得拖泥带水,总是试图把任务拆成多步。
这个问题的根源在于训练序列长度分布和真实使用场景的分布不一致。解决思路是做一个“序列长度混合采样”:每个 batch 里同时包含短任务(128-512 token)、中等任务(512-2048)和长轨迹任务,比例可以按 2:3:5 来。只训练长轨迹会让模型适应了超长输入,在短输入上注意力分配失衡。
另一个补救手段是在 SFT 后做一个短指令的“回声训练”,拿几千条纯短指令快速回放一遍,恢复短文本能力。这个成本不高,但对线上体验帮助很大。
5.3 中间步数爆炸:模型收不住尾
有一版模型训练完后,在测试时每一步都正确,但永远不肯给出最终答案,无限循环地调用工具、查看结果、再调用工具。原因是我们混合数据里“长轨迹”的占比过高,模型形成了“只有不停调用工具才算负责”的行为惯性。
解决办法分数据层面和算法层面。数据上,我们强制要求所有训练轨迹都必须收敛到一个显式的最终回答,并且在数据里加入一些“单步就能得出结论”的负样本,告诉模型不是所有问题都需要穷举搜索。算法上,引入一个“最大步数”的硬性截断辅助 loss——训练时如果模型在若干轮后还没收敛,用一个独立的 token 强制它给出决定。这种 token 可以插在指令里,比如“你已经搜索了 2 轮,现在请基于已有信息给出推荐”,让模型把这种约束也学成一种条件行为。
5.4 多 Agent 协同场景的 SFT 数据怎么搞
上面聊的基本是一个模型独立决策的场景。如果你训练的是多 Agent 协同系统(比如一个 planner、一个 executor、一个 validator 配合),数据构建复杂一倍。我们尝试过各自独立做 SFT,效果很差——planner 生成的计划到了 executor 那里永远是空返回,因为两个模型是分开训练的,语义空间不匹配。
经验做法是:用同一个基础模型初始化所有角色模型,然后拿“多角色完整轨迹”预先做一轮联合 SFT,让各角色的输入输出格式对齐。之后再针对每个角色用各自职责的轨迹数据做增量微调。这个过程类似先“统一语言”再“专业分工”,配合 loss mask 把非本角色承担的序列段舍掉。目前我们这套流程跑出来的多 Agent 成功率,比完全独立角色微调高十几个百分点。
5.5 训练数据覆盖不足:冷启动工具调用
有些工具在训练轨迹里出现次数极少,模型大概率不会主动调用它们。我们发现工具调用频率分布符合严重的长尾现象——80% 的调用集中在 3 个高频工具上,剩余 10 多个工具几乎没被触发过。
对策是有针对性地合成一批“单工具强制任务”:每一个任务只允许使用一个指定工具,让模型把低频工具的调用方式背下来。然后再混合一些需要组合多个工具的任务,逐步把调用能力串起来。这个过程要有耐心,低频工具 SFT 之后要专门评估一次对应工具的调用率,确认确实达到阈值再加高难度任务,否则最终模型会把多个工具互相调用混乱。
6. 持续记录:一些还没想清楚的问题
写到这里,Agentic SFT 的很多细节还谈不上吃透。比如过程奖励到底应该精细到动作级,还是保持在 trajectory 级?我们尝试过给每一步动作打分再筛选数据,但打分器本身的噪声变成了新的瓶颈。又比如“可验证任务”和“开放任务”在 SFT 数据配比上的最优比例,目前还只能靠实验试。再比如真正决定模型能否稳定收敛的,是增加的轨迹数据本身,还是精心维护的观测掩码顺序,这些都要通过隔离实验才能有结论。
目前我们在持续做的一件事是建立一套从“轨迹数据生成到过拟合报警”的自动化流水线,把这篇文章里提到的硬规则落到代码里。后续有新的进展会继续更新这个记录。最后给正在入坑的同学一个真诚的建议:不要一上来就搭复杂框架,先用 100 条高质量轨迹跑通一个最小闭环,观察模型的 loss 曲线、格式稳定性和任务成功率这三件事,再决定要不要投入资源扩大数据规模。