ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agentic SFT:从“对话微调”到“智能体决策训练”的实战指南

Agentic SFT:从“对话微调”到“智能体决策训练”的实战指南 1. 从一次微调翻车说起Agent场景下SFT为什么不够用了前几天我往项目里加Agent能力模型要能自己决定调哪个工具、传什么参数、看结果之后决定下一步做什么。一开始我很天真想着这不就是指令跟随的升级版吗把工具调用的输入输出整理成对话样本跑一轮SFT就完事。结果模型确实学会了输出“调用搜索工具”这几个字但参数经常传错搜索完拿到结果之后完全不知道该怎么用有时候甚至在一个死循环里反复调同一个接口失败一次就再来一次像是被困住了一样。这个现象逼着我重新审视一个基础问题我们平时说的SFT在Agent场景下到底在训练什么传统SFT的样本长这样用户问“北京的天气怎么样”模型答“北京今天晴22度”。一个输入一个输出答案就结束了。但在Agent场景里一个样本是一条完整的轨迹用户提出目标模型决定先搜索搜索返回结果模型分析结果发现不够还要再调用一个天气API拿到数据后汇总出最终答案。中间每一步都是决策每一步都可能错而且错误会像滚雪球一样累积到最终结果里。所以Agentic SFT给我的第一课就是它是监督微调但监督的不再是“最终的答案”而是“达成答案的整条决策路径”。我们要教模型的不只是“该说什么”更是“该在什么时候做什么、做完之后怎么看结果、结果不对劲怎么改”。这篇文章就是我目前对这个话题的全部认知积累持续迭代持续记录。我尽量写得直白一点把我踩过的坑、观察到的现象、还没想明白的问题都说清楚。适合正在做Agent微调、或者准备把模型从“对话模型”改造成“干活模型”的同行参考。2. Agentic SFT到底在“学”什么从单轮问答到多步决策2.1 输出空间的变化模型从“写答案”变成“做决策”传统SFT的核心假设是给定输入最优输出是唯一的或者至少是高度确定的。SFT要做的就是让模型去拟合这些输入输出对学一个从指令到回复的映射。Agentic SFT彻底打破了这一点。模型的输出不再是一段最终回复而是一个行动序列。它先要判断“我现在要不要调用工具”然后决定“调哪个工具”再生成“符合接口规范的参数”接着阅读“工具返回结果”最后综合所有信息继续下一步。这本质上是一个决策过程而不仅仅是文本生成过程。举个例子一个典型的Agent轨迹样本里模型输出通常长这样{ thought: 用户想知道A股大盘走势我需要先获取指数数据, action: call_tool, tool_name: get_stock_index, params: {index: sh000001, period: today} }关键在于模型要把“调用工具”这件事当成一种正常的文本输出。它并不真的去调用任何东西它只是在“生成”一段符合工具接口协议的结构化文本然后由外层框架去实际执行。这就有意思了我们训练的目标本质上是让模型学会“说人话”之外的另一种语言——工具协议语言。这就带来一个直接后果SFT的数据不能再是简单的“问题-答案”对而必须是“目标-行动-观察-决策”的完整闭环。少了任何一环模型都学不会真正的Agent行为。2.2 监督信号的粒度每一小步都要单独负责传统SFT里一个样本只有一个监督信号就是最终答案。哪怕模型在中间推理时胡说八道只要最后答案对了loss就是小的。但Agent场景不一样错误会传导。我后来在训练里用了“逐点监督”per-step supervision的思路对轨迹里的每一步分别计算loss而不是只在最终答案上算loss。具体来说一条轨迹可以拆成多个训练单元单元一用户说目标期望模型输出“思考调用工具A”单元二工具A返回结果期望模型输出“分析结果调用工具B”单元三工具B返回结果期望模型输出“汇总最终答案”每一步都是独立的监督目标。这样做的好处是哪怕最终答案因为外部工具的不确定性失败了模型也能学到“在这个状态下正确的下一步应该是做什么”。这在Agent场景里非常重要因为工具执行可能失败但模型对于“该调用哪个工具”的判断仍然是有监督价值的。2.3 为什么说“会背答案”不等于“会用工具”刚跑完第一轮Agentic SFT的时候我测了几个训练集里的用例表现非常好工具调用规范、参数准确、多步规划也有模有样。但换到全新的任务上立马露馅。后来我意识到一个本质问题模型在传统SFT里学了太多“从问题直接到答案”的捷径。对Chat模型来说这是优点因为它能高效回答。但对Agent来说这个先验反而是阻力。模型倾向于自以为是地直接输出答案而不是先想一想“我需要哪些信息、哪些信息我没有、我需要调用什么工具来获取”。我一度以为是数据量不够加了几千条样例之后问题依然存在。直到我对比了训练集和评测集的分布才发现模型学到的是“看到相似的问法就模仿相似的轨迹”它并没有真正学到“工具调用是一个为了弥补自身知识不足而采取的手段”。这个认知让我后来在设计数据时刻意加入了大量“模型不调用工具就会答错”的样例逼迫模型学会依赖工具。效果有提升但离真正的泛化还有距离。3. 数据为王Agentic SFT的训练数据到底怎么造3.1 轨迹数据的三种形态Agentic SFT的数据不是天上掉下来的。目前常见的获取方式有三种我分别试过感受很不一样。第一种人工构造轨迹。找几个核心业务场景自己模拟Agent的行为手工写完整的多轮轨迹。优点是质量可控每条数据都符合我们想要的Agent行为范式缺点也明显慢、贵、覆盖场景有限。适合冷启动不适合撑起大规模训练。第二种从真实系统日志里扒轨迹。如果你的Agent已经上线跑了一段时间日志里本身就是大量真实轨迹。把这部分日志清洗、过滤挑出高质量的会话记录下来直接当作训练数据。这个数据源最贴近真实分布但问题在于日志里优质轨迹的比例往往不高需要大量过滤和重写。而且日志里很多轨迹是带有外部状态依赖的清洗的时候要额外小心。第三种用更强的模型蒸馏。把任务描述扔给一个能力更强的模型让它扮演Agent生成轨迹然后拿这些轨迹来训练小模型。这是目前实践中最常用、也最容易规模化的一条路。但本质上蒸馏是在“压缩教师模型的行为模式”如果教师模型本身就是错的学生模型只会错得更稳定。3.2 数据里的信号密度比总量更重要的事我在实验里发现一个反直觉的结论Agentic SFT的数据量提升带来的收益会迅速进入平台期但数据里的“信号密度”却始终显著影响效果。什么叫信号密度就是一条轨迹里包含的“关键决策点”的数量和多样性。同样是3000条数据如果每条都只有一个工具调用、一个观察、一个最终答案模型能学到的东西非常有限。但如果里面有大量需要三次以上工具调用、或者中间出现工具失败和恢复的轨迹模型的Agent能力会有质的提升。所以我后来造数据时重点盯这几个信号多步依赖第二个工具的调用参数依赖第一个工具的输出而不是独立的两次调用拼在一起。这类数据教会模型“信息流转”。失败恢复工具返回了错误码模型需要判断是换参数重试还是换工具。这类数据教会模型“容错”。中途改计划拿到工具返回后发现和最初的设想不一致模型需要基于新信息调整后续步骤。这类数据教会模型“plan revision”。拒绝调用的场景用户问了一个模型已能回答的问题期望模型直接回答而不是非要去调工具。这类数据教会模型“何时不应该调用工具”。最后这一点经常被忽略。很多团队造数据时无限强调工具调用结果模型变成了一个“调用工具强迫症”连“今天星期几”这种问题都要先调一下日历API再回答。效率和成本都绷不住。3.3 构造数据时我用到的具体配方以我目前在做的客服Agent为例我整理了一套比较实用的数据构造流程从历史工单里挑出100个高频场景作为数据构造的“骨架”对每个场景设计2到3种不同路径一条最短路径、一条带纠错的路径、一条需要换工具的路径人工编写前50条作为质量锚点确保风格一致把剩余场景交给教师模型批量蒸馏生成后逐条人工审核在最终数据集里控制比例多步轨迹不低于40%失败恢复轨迹不低于20%单步轨迹控制在30%以下所有工具调用的参数值都做随机池化防止模型过拟合具体的参数字面量这套流程跑下来的数据集规模也就一万多条但训练出来的Agent在评测集上的表现比我之前用五万条粗糙数据训练出来的结果要好不少。信号密度确实比总量更重要。4. 训练细节里藏着魔鬼Loss设计与序列建模的取舍4.1 不是所有token都值得同等的监督第一版训练我直接把整条对话历史拼成一个序列当成普通SFT去fine-tune了。结果是loss掉得很漂亮但模型行为完全不对。后来我才意识到在Agent轨迹里不同类型的文本监督价值完全不同。用户指令、工具返回结果、历史观察记录——这些是模型需要“阅读”的上下文不应该参与loss计算。模型真正需要学习生成的是它的思考、工具调用指令和最终回复。如果这部分输入文本也参与了loss模型会把大量容量浪费在“模仿工具返回格式”这种无用功上真正要被训练的部分反而被稀释了。所以第一件事就是加mask。在训练时把用户消息、工具返回、系统提示这些部分全部mask掉只对模型自身生成的token计算loss。def build_agent_sample(conversation): conversation: List[Dict], 每条记录包含 role 和 content role 包括 system / user / assistant / tool_result 只对 assistant 的文本计算 loss其余部分 masked input_ids, labels [], [] for msg in conversation: tokens tokenizer.encode(msg[content]) input_ids.extend(tokens) if msg[role] assistant: labels.extend(tokens) # 参与 loss else: labels.extend([-100] * len(tokens)) # 不参与 loss return input_ids, labels4.2 对“动作关键帧”施加更高的权重即使是在assistant生成的文本内部不同内容的重要性也不一样。思考过程是软性的就算措辞不太对意思到了就行。但工具调用的参数是硬性的参数值一旦错了后面全盘崩溃。我在实验里做过一个调整对工具调用相关的token特别是参数值、函数名额外提高loss权重比如乘以2.0。这样模型会把更多学习容量分配到“准确生成工具指令”这个关键行为上。从效果来看工具调用的格式正确率有明显提升而思考部分的质量并没有因此变差。顺带说一句有人会纠结要不要给“思考”部分也降低权重我觉得不用太极端。思考措辞的质量高低对后续行为的影响是隐性的保持正常的训练权重即可重点还是要把工具调用部分突出出来。4.3 序列打包的细节坑Agent轨迹天然比普通对话要长。一条多轮工具调用轨迹算上所有工具返回结果动辄三四千token。训练时如果处理不当会遇到两个典型问题。第一个问题是超长序列截断。粗暴截断会把中间的决策上下文切断模型看到的是前后文不搭的输入能学到个啥。我的做法是滑动窗口切分把长轨迹切成多个有重叠的窗口保证每个窗口里都至少包含一个完整的工具调用决策段。第二个问题是短轨迹拼接。为了提升训练效率很多框架会把多段短文本packing到一起。但如果packing不当可能会导致模型跨样本学习本来不相干的两段轨迹被模型强行关联。我的处理方式是在packing时加入分隔符并在计算attention时把跨样本的部分mask掉。如果框架不支持这种mask就干脆不packing单条样本训练虽然慢一点但稳。4.4 超参数配置的一个参考基准我目前的Agentic SFT跑得比较顺的超参配置大致是这样的超参数值说明基础学习率5e-6Agent轨迹训练容易过拟合务必用低学习率warmup比例3%太少容易前期震荡太多会拖慢收敛epoch2超过2轮loss还在降但评测效果开始倒退batch size32每条样本较长显存受限时先用梯度累积工具调用token权重2.0突出关键动作帧对话历史mask开启输入侧全部不限参与loss这个配置不一定通用但可以作为起步基准。我个人强烈建议从低学习率开始因为Agent轨迹数据通常是高度重复的同样的工具调用格式会反复出现学习率稍微高一点模型就会开始原地打转训练集上的行为非常华丽测试集上一塌糊涂。5. 评测Agentic SFT只看成功率远远不够5.1 你测的到底是“模型的能力”还是“任务的运气”我一开始评测Agent效果就盯着一个指标端到端任务成功率。后来发现这个指标单独看非常骗人。举一个具体的现象。给模型布置一个“查询订单状态”的任务如果最终答对了我以为是Agent能力好。但翻日志发现模型第一次调工具就把订单ID传错了工具返回错误模型居然把错误信息原样搬进了最终回复里而用户恰好能从那段错误信息里看到订单状态——外部工具的错误返回“凑巧”包含了正确信息。这种情况下标注“成功”会严重高估模型的真实Agent能力。所以我现在评测时把成功率拆成多个维度来观察格式合规率模型输出的工具调用是否符合接口规范是不是每次都能解析出合法的工具名和参数动作正确率在当前状态下模型选择的工具是否是合理的工具参数是否准确地表达了用户意图信息利用率模型在生成最终回复时是否真的用上了工具返回结果中的关键字段失败恢复率工具调用失败后模型是选择了合理的重试/替代方案还是直接摆烂最终任务成功率整个流程最终是否解决了问题只有这五个指标一起看才能定位所谓的“成功”到底靠的是模型能力还是运气。5.2 构建评测集的坑不要让训练数据污染评测结果这个坑我印象太深了。有一次我从同样的业务场景里分别抽训练集和评测集只是换了些具体的参数值、改了点措辞结果评测效果出奇地好。后来我把训练集和评测集的语义相似度算了一下发现好多评测用例和训练用例重叠度很高模型根本就是在模拟训练数据里的轨迹评测效果自然虚高。正确的做法是评测场景必须和训练场景在任务类型、工具组合、交互模式上有可见的差异。如果训练时只见过搜索类工具的调用评测时就应该补充一些调用计算器、调用数据库查询接口的场景。另外不要用同一个Agent框架里的同一条轨迹生成器既产训练数据又产评测数据否则模型的“轨迹模仿”能力会被误认为Agent规划能力。5.3 一个更细的做法行为轨迹对齐我现在在尝试一种更细的评测方案不再只看结果而是把模型在评测任务里的完整轨迹和标注人员预写的“专家轨迹”做对齐。对齐的计算方式是序列级别的编辑距离看模型需要多少步增删改才能走到专家轨迹的路径上。这个指标的好处是能区分两种看似相同的结果一种是模型直接完成任务的路径和专家路径一致另一种是模型绕了一大圈、东错西错、最后碰巧完成。两者的编辑距离差异非常大。前者才是我们想要的行为模式后者说明Agent决策质量还有很大问题。不过在实操中要小心一个点Agent任务通常有多条合理路径专家轨迹不能只有一条。我现在的做法是每条评测任务预写2到3条不同类型的合理路径只要模型的轨迹和其中任何一条的编辑距离较小就认为该步行为合理。6. Agentic SFT和Agentic RAG训练与检索的分工边界6.1 Agentic RAG是什么“Agentic RAG”最近被讨论得很多简单说就是让模型自主决定何时检索、检索什么、从哪个来源检索而不是像传统RAG那样在每条用户请求前面都无脑做一次向量检索。区别于传统RAG的“固定流程”Agentic RAG是模型根据问题判断“现在有没有足够信息来回答”没有就去检索检索结果不够再检索一轮直到信息充足为止。我在实践中的体会是Agentic RAG里的“Agentic决策”能力恰好就是Agentic SFT需要训练的那部分能力。SFT负责把“在信息不足时主动检索”这件事变成模型的固有能力RAG负责提供检索的基础设施和知识来源。6.2 SFT和RAG的同与不同能力底座与外部知识的边界有人可能会觉得既然有了RAG模型不需要再通过SFT去学“事实知识”SFT就没那么重要了。这个观点对传统RAG场景有一定道理但对Agent场景完全行不通。需要想清楚一个分工RAG解决的是“知识从哪里来”SFT解决的是“模型如何组织行动”。即使查询都用RAG来做模型依然需要通过SFT学会“如何格式化一个检索查询”“如何判断当前检索结果是否足够”“如何在检索结果不足时换一种查询思路”。这些行为模式不是RAG组件能给的而是模型自身需要具备的能力。我做过一个对比实验在完全相同的RAG检索链路下分别用普通SFT模型和Agentic SFT模型作为控制器前者的检索行为非常僵化只会在固定的字段位置做一次检索而后者的多轮检索成功率有明显提升。所以SFT和RAG不是替代关系而是底座和插槽的关系。SFT先把“会检索”的能力训练出来RAG再把这个能力接到真正的知识源上。6.3 组合使用时的训练建议我在把SFT和RAG组合到同一个Agent系统里的时候有几个心得。第一Agentic SFT训练时不要脱离检索工具去造数据。有些团队为了简化训练把RAG检索封装成一个纯文本输入输出的黑盒接口只在接口里塞一段检索结果。这样训练出来的模型对“检索结果可能不完整”“检索结果需要二次加工”这类真实状态缺乏感知。建议训练数据里的工具返回内容尽量保留真实检索系统可能产生的不完美状态比如返回空结果、返回低相关片段、返回超时错误让模型见到真实世界的噪声。第二RAG的检索策略本身也需要SFT数据来覆盖。我的数据里专门有一类样本触发场景是“第一轮检索结果太泛、没有命中具体信息”期望模型的行为是“基于第一次的结果改写检索词进行第二轮更精确的检索”。这类行为模型靠提示词也能写出来但提示词控制不稳定的情况很多把它作为SFT训练样本的效果稳定得多。第三注意控制“检索过快”的行为。我在评测时发现经过Agentic SFT训练的模型有一种倾向是太容易触发RAG检索什么都想查一下。原因很简单训练数据里检索的密度太高了模型的先验变成“看到任务就先检索”。所以现在造数据时我会刻意加入一批“直接回答才是最优策略”的样本告诉模型有些问题是可以用自身知识直接回答的。7. 踩坑实录四类典型翻车场景的排查链路7.1 翻车一SFT之后模型变“笨”了这是很多做Agent微调的人都会遇到的事通用能力跑到Agent能力然后模型在通用任务上明显退步。我排查下来的原因有两个方向。第一个方向是数据配比失衡。Agentic SFT数据量过大压缩了通用数据的比例模型把所有容量都花在了拟合工具调用上把原先学会的推理、写作、常识问答技巧冲淡了。解决思路是控制Agentic SFT训练数据的配比上限并在训练时混合一批高质量通用指令数据。我的做法是7比3的比例七成Agent轨迹数据、三成通用对话数据能压住退化又不影响Agent能力的学习。第二个方向是学习率过高。Agent轨迹里工具调用格式是高度重复的高学习率会在极短的步数内把“工具调用”的模式刻进模型同时把其他能力全部覆盖掉。把学习率降到5e-6甚至更低之后退化现象会明显缓解。建议每次微调前先拿极小的验证集跑一个50步的实验观察通用能力在训练过程中的变化曲线再做全量训练。7.2 翻车二工具调用格式像“家族遗传病”一个让我很头疼的现象当训练数据里的工具调用格式统一为“调用某个搜索API后将结果纹丝不动粘贴到回答中”时模型学到的是“工具调用就是为了照抄结果”。换了业务场景之后模型的格式完全混乱甚至在没有任何工具定义的场景里也随机输出一段形似工具调用的文本。排查下来问题出在训练数据缺乏“对工具结果做分析”的样本。模型只见过“调用后原样搬运结果”没学过“调用之后结合自身理解重新组织答案”。解决方法是造数据时在同一批训练集里增加一类样本其目标行动是“阅读工具返回结果后提炼关键信息忽略无关字段再整合生成回答”。这类数据能教会模型将工具当作信息来源而不是当作复制粘贴的对象。7.3 翻车三Loss在降评测不动怀疑人生有一段时间我的训练loss曲线非常漂亮但评测集上的成功率一点没变。后来我把模型在评测集上的输出数据和训练集数据做了一个序列相似度对比发现模型的高频输出轨迹和训练集里的若干条轨迹高度雷同。这个现象的本质是模型把“高频轨迹”背下来了而不是把“决策规则”学会了。尤其在数据量不多、轨迹模式单一的情况下模型很容易把训练集里有代表性的一条轨迹当成标准答案背下来。于是换一个场景就崩。解决方法是扩充轨迹多样性。造数据时同一个任务多写几条完全不同的合理路径训练时每条轨迹做参数随机化让相同语义的表达有不同的措辞。这些手段都能有效压缩模型“背轨迹”的空间逼它抽象出通用的决策规则。7.4 翻车四多轮对话里的上下文污染Agent系统通常要维护多轮对话历史。微调时如果没有设计好跨轮样本模型很容易被前几轮会话里的工具调用历史干扰导致行为漂移。我遇到的具体问题是第二轮的对话里模型不该去访问一个只和第一轮相关的工具但它还是去了。因为训练数据里很多样本是从一条完整轨迹直接切出来的而切分后的第二条样本的对话历史里还残留着第一轮的“大量工具调用痕迹”模型就把“看到工具就调用”的错误规则学回去了。解决方法是构造训练样本时明确区分“当前任务的目标工具集”和“历史会话中已经用过的工具集”。我给每条样本标注了哪些工具在当前轮次是可用的哪些是历史遗留的不可用工具并刻意构造一些“历史工具调用记录和当前目标冲突”的场景。这样模型才能在真实的多轮环境中学会正确判断上下文边界。8. 持续记录的下一步方向目前这套Agentic SFT流程已经在我的两个项目里跑通了效果稳定但远谈不上终极方案。我还没想明白但准备接下来重点实验的问题还有几个一个是“轨迹长度对泛化的影响”。现有数据里决策步数大多在3到8步之间如果任务需要12步以上的长程决策模型表现依旧不稳定。我在考虑是否要构造更长链条的数据但长轨迹不仅标注成本高训练时也更容易被前面的决策错误带偏需要单独设计对“偏离修正”的监督方式。另一个是“外部状态是否应该作为模型输入的一部分”。比如工具返回的实时数据、当前时间、用户所在城市这些外部状态变量在训练数据里很多时候是缺位的真实部署时又必需。把外部状态注入训练样本理论上能提升模型的泛化能力但具体以什么格式注入、注入哪些状态、状态缺失时怎么办我还在摸索。还要想的是“如何让SFT和强化学习阶段配合得更好”。SFT训练出来的Agent决策稳定性直接影响后续RL阶段的探索效率。目前我的思路是把SFT阶段做“保守训练”只教会模型做出正确决策的最低必要能力把更激进的探索空间留给RL。但这个边界到底划在哪里还需要更多实验来验证。这次关于Agentic SFT的记录就先写到这里。
返回列表