English interface selected · this article is currently written in Chinese.
怎样用 SFT 学会工具接口:格式、参数与任务完成要分开检查
English overview →具备清晰骨架、初步数学推导或工程验证,持续补充论据与实测。
数字花园
🌿 什么是数字花园与思想演进?
遵循 Maggie Appleton 与 Andy Matuschak 的常青笔记理念:这里不是按时间沉底的流水账,而是持续修剪打磨的知识生态。文章按成熟度经历:
- 🌱 萌芽 (Seedling): 发散灵感、读书摘录、开放猜想
- 🌿 演进 (Budding): 结构渐明、初步代码推导、积极修正
- 🌳 常青 (Evergreen): 逻辑闭环、持久经得起检验的基石
- 🔬 实验待证 (Speculative): 结合沙盒与仿真探针的理论探索
上一篇已经有一条完整示范:用户需要 3 件 A17,先查杭州;杭州只有 2 件,再查苏州;苏州有 5 件,最后报告苏州满足需求,没有预订。
现在想让模型学会这件事。最容易想到的办法,是准备很多类似记录,做监督微调。但“记录放进去了”和“正确行为被监督了”之间,还有几处容易漏掉的细节。
比如,工具调用可能存放在 tool_calls 字段里,而预处理只读取 content。训练仍然能跑,损失也能下降,可模型根本没有见到需要学的调用内容。又比如,模型学会了合法 JSON,却总把仓库写成苏州;接口能执行,用户要求的顺序却错了。
本篇沿用只读库存任务,把数据、损失和评测逐层拆开。数字与样本都是教学设定;配套脚本只核对监督范围和算例,没有执行 SFT 训练。
先把三个“正确”分开
格式正确,表示生成结果能被接口解析。例如它包含工具名、参数对象和必要字段。
参数正确,表示调用符合当前请求与已知历史。warehouse="SZ" 是合法值,但在本例的第一步就查询苏州,违反了“先杭州”的约定。sku="A18" 同样可能是合法商品,却不是用户要的 A17。
任务完成,还要把一连串动作和最终回答放在一起看。两个查询都正确,最后却说“已为你预订”,仍然失败。上一篇的执行器没有任何预订操作。
这三个层次不能只用“调用准确率”一个名字带过。本文在评测时先定义检查项目,再报告分母,不把解析通过的样本筛出来以后,就忘记其余失败。
一条轨迹实际提供了三次决策示范
不要先从训练框架参数开始。先问:在每个时刻,模型能够看到什么,应该生成什么?
| 决策 | 当时可见的内容 | 本次监督目标 |
|---|---|---|
| 第一次调用 | 用户请求、工具说明 | 查询 A17 的杭州库存 |
| 第二次调用 | 上述内容,加第一次调用和“杭州 2 件” | 查询 A17 的苏州库存 |
| 最终回答 | 上述内容,加第二次调用和“苏州 5 件” | 报告苏州满足需求且未预订 |
这里的监督目标是模型应输出的动作或回答,工具结果则提供条件。训练第二次调用时可以看见杭州数量,不能看见还没查询到的苏州数量。
也不要把最终答案写进一个“辅助说明”字段,再无意中拼到每一步输入里。字段名字叫 metadata,并不保证它不会被模型看到;只有实际渲染后的输入能回答这个问题。
两种组织方式,都要明确损失算在哪里
第一种是保留完整对话,让模型在因果注意力下预测各个 assistant 片段。序列中虽然存在后续内容,正常的因果掩码会阻止当前位置看到未来。工具返回片段保留在输入中,但在本篇选定的训练目标下不计预测损失。
第二种是把每次决策单独拆成“历史前缀→下一条 assistant 消息”。三个训练样本各自只监督最后那一次决策;前缀里以前的 assistant 消息也只是条件。
这两种方式并不天然等价。拆分会重复读取历史;如果每个样本等权,短调用与长回答的相对权重也可能改变。更常见的错误是:拆成前缀样本后,仍然监督前缀里所有 assistant 消息。第一次调用就会在三份样本中被算三次,第二次调用算两次,最终回答只算一次。
TRL 0.23.1 文档分别说明了 completion-only 与 assistant-only 的损失设置,并要求相应模板支持 assistant token 掩码。工具训练样本还需要保存调用、结果和可用工具列表。选项名称不能代替实际检查。SFT Trainer 固定版本文档
本文不提供一个未经运行的“通用训练配置”。同一个开关遇到不同数据形态、模板和版本,监督范围可能不同。真正训练前,应把一条样本的 token、标签和被计分区间一起导出。
用一组小数字看清重复监督
假设三段需要学习的内容,分别有 4、6、10 个目标 token,平均负对数似然分别为 0.5、1.0、0.2。这里只是人为设定的长度与损失,不是对实际中文或 JSON 的分词结果。
若完整轨迹中的每个目标 token 只计一次,总共 20 个目标 token,总损失和为 10,平均损失为 0.5。
若拆成三个前缀样本,却错误地再次监督历史 assistant 片段,计数会变成:第一段 4×3,第二段 6×2,第三段 10×1,共 34 个目标 token。三段加权后的损失和分别是 6、12、2,因此:
这不是模型突然退步,而是你换了样本权重。早期调用被重复强调了。
即便没有重复监督,把三次决策的平均损失直接等权平均,也得到约 0.5667,而不是 0.5。前者让每次决策等权,后者让每个目标 token 等权。两者可以有各自的用途,但要写出选择,不能把它们当成同一个数比较。
更一般地,token 平均目标可以写成:
这里的前缀包含已经渲染的请求、工具说明和历史;m_t 为 1 表示这个目标位置计分,为 0 表示不计分。没有任何目标位置的样本不能直接拿零分母计算。结束标记是否纳入监督,也要按模板明确,不能把停止行为漏掉后再期待模型自然停止。
真正应该查看的是渲染后的输入
数据表里字段齐全,还不足以证明模型能读到它们。聊天模板决定这些字段怎样变成实际 token 序列。
Transformers 的模板文档说明了工具定义、调用与结果需要由模板处理。某些模型使用特定边界标记,工具调用也不一定以普通回答的形式渲染。应检查与目标模型配套的模板,而不是只看原始 JSON。聊天模板编写文档
对第一条样本,至少手工核对下面四处:工具名和参数是否还在;杭州返回值是否位于第二次调用之前;工具结果有没有被误标为 assistant;调用片段与结束位置是否确实获得了训练标签。
再做一个简单变化:把杭州库存从 2 改成 3。正确示范应在第一次查询后结束,不能继续保留查询苏州的旧目标。只改输入、不改标签,会制造相互矛盾的数据。
长轨迹还会遇到截断。若截掉第二次调用,却保留它的结果,历史就失去了配对;若截掉工具定义,剩下的请求可能无法解释。先检查样本在真实长度限制下还剩什么,再决定按完整决策边界切分、缩短环境输出,还是舍弃样本。把最大长度调小并不是纯粹的性能优化。
数据要覆盖分支,不能只把成功句子换个说法
库存任务至少有三类正常分支:杭州足够,杭州不足但苏州足够,两个仓库都不足。还应有不需要调用的说明性问题,以及信息缺失时需要澄清的请求。
| 样本变化 | 想教会的行为 | 容易学到的捷径 |
|---|---|---|
| 杭州刚好 3 件 | 达标后停止 | 只记“杭州通常不足” |
| 两地都不足 | 如实报告不足 | 每次都推荐苏州 |
| 商品编号改变 | 从用户请求提取参数 | 永远填 A17 |
| 只问字段含义 | 直接解释已有说明 | 每次都查库存 |
| 缺少需求数量 | 询问数量 | 默认猜 3 件 |
这些是覆盖要求,不是建议把所有类型机械地平均分配。部署请求的分布、错误代价与稀有分支的学习难度,会影响配比。记录每类数量和抽样方式,比声称“数据很多样”更有用。
把商品名随机替换几百次,也不自动得到几百个独立测试问题。同一轨迹骨架的近重复样本应该按家族划分,避免一部分进训练、一部分进测试后,夸大对新场景的适应能力。
错误示例不能不加区别地当成正确目标
假设旧模型先查了苏州,后来被指出顺序错了,才重新查杭州。整段轨迹里既有错误动作,也有恢复动作。
如果对所有 assistant 消息照常计算 SFT 损失,早先的错误调用也会被鼓励。想学习恢复,可以把错误历史保留为输入,只监督经过核对的修正动作;想学习首次就做对,则另外构造正确初始决策。两种目的应该分开标注。
更不能为了让错误轨迹“看起来成功”,手工把工具返回值改成能支持原动作的数字。修正示范要在一致的环境里重新核对,而不是修改证据来迎合答案。本卷后面会专门讨论失败轨迹的利用。
离线预测正确,不等于实际执行成功
离线检查时,可以给模型一段正确历史,让它预测下一步。这能定位模型会不会在“杭州 2 件”之后查苏州,但它绕过了一个问题:模型自己生成的第一步是否就已经错了?
闭环评测则让模型从用户问题开始,自己生成调用,交给环境执行,再读取真实结果继续。错误会影响后续历史,不再由标准答案替它铺路。
设想一个人为构造的 100 次首步调用检查:90 次满足接口格式,其中 72 次参数与当前请求正确。在另一个覆盖这些任务的完整交互评测里,最终有 54 个任务成功。这里可以报告格式通过 90/100、首步语义正确 72/100、条件语义正确 72/90,以及任务成功 54/100;不能只说“准确率 80%”。
这组数字只是说明分母,不是实测。真实系统里还可能有多次调用、允许重试和无需工具的任务,因此调用级与任务级指标不能随意相乘。一次错误能否恢复,也由任务协议决定。
怎样设计一轮有解释力的小实验
先固定基础模型、数据划分、模板、环境和解码设置。保留未微调模型作为基线,再比较“只监督最终回答”与“监督调用及最终回答”的版本。工具结果在两种训练条件里都保留为上下文,避免同时改变可见信息。
但前者无法直接学到调用片段;闭环差异也可能来自基础模型原本会不会调用。因此同时报告有标准历史的逐步测试与从头执行的任务测试。训练预算还应注明按总读取 token、目标 token 还是更新步数对齐,这些口径不总能同时一致。
这是一项待运行的实验设计。本文实际完成的是小算例复算:20 与 34 个目标位置的计数、两种损失平均、历史重复监督的次数,以及示范前缀不包含未来事件的检查。它帮助排除数据管线错误,不能支持“某种 SFT 配方提高了多少成功率”的结论。
开始训练前,应当能拿出一条渲染输入、一份标签范围、一份分支覆盖表,以及一条从请求到结束的评测日志。如果这四样还解释不清,先扩大数据或提高训练步数,往往只是把同一个错误重复更多次。
文章概念网络
从章节进入正文概念。实线表示所属层级,虚线连接跨章节重复出现的概念,不代表因果关系。
50 / 50 个节点 · 正文标记术语与关键词概念
怎样用 SFT 学会工具接口:格式、参数与任务完成要分开检查
回到原文 ↗最后更新于 2026-10-23
引用本文
@article{2026-10-23-llm-training-tool-02-supervision,
author = {枫语},
title = {怎样用 SFT 学会工具接口:格式、参数与任务完成要分开检查},
journal = {枫语},
year = {2026},
month = {oct},
url = {https://meiluosi.github.io/posts/2026-10-23-llm-training-tool-02-supervision/}
}