前阵子我一直在折腾一件事:把公司那个7B的小模型调教到能稳定调用工具。一开始真是一把辛酸泪——让它查天气,它能一本正经地报出一串根本不存在的城市名;让它调数据库接口,它能给你编一个绝不存在的字段。旁边的同事调侃说,这哪是小模型,这是小骗子。可问题在于,大模型什么都能干,但推理慢、单价贵、部署成本也高。于是我就想了一个很朴素的方案:用大模型当老师,持续去喂小模型,把工具使用和问答能力一点点教出来。这篇文章就是我把这套方案从0到1跑通后的完整复盘。
如果你也在做智能体、私有化问答、或者想把大模型的能力压缩进一个能跑在普通服务器上的小模型里,这篇应该能帮你省掉不少试错时间。我会把数据怎么造、训练怎么训、评估怎么评、线上怎么回流整个闭环一次讲透,顺便把我在整个过程中踩过的坑也抖出来。
1. 为什么偏要用大模型去喂小模型
1.1 小模型的瓶颈不是参数量,而是没见过高质量工具调用
很多人以为小模型做不好工具使用,是因为参数少、记忆差。这个说法对了一半。参数量确实限制了它能塞进去的知识上限,但工具调用这件事,本质上不是“知识”,而是“行为”:给定一个用户问题,判断该不该调工具、调哪个工具、参数怎么填、返回后怎么组织答案。这种能力更依赖“见过足够多的高质量样例”,而不是单纯依赖参数量。
我做过一个很直观的对比:同一个7B模型,在通用聊天语料上效果还行,但一旦进入工具调用场景,输出就崩。最典型的表现是它知道要输出JSON,却把字段名写错,或者在一个需要调用多个工具的复杂任务里,只完成第一步就直接给结论。背后的原因并不玄妙——它的训练数据里,“工具调用轨迹”太少了。大模型之所以工具调用能力强,是因为训练阶段见过海量结构化API调用样例和思维链轨迹,小模型没这个条件。
所以问题的本质就变成了:我们能不能制造大量高质量的工具调用和问答轨迹给小模型学?这就是大模型的用武之地。
1.2 大模型当“老师”的三种主流姿态
在落地过程中,我总结了大模型“教”小模型的三种主要方式,它们的侧重点各不相同。
| 方式 | 核心思路 | 主要应用阶段 | 成本敏感点 |
|---|---|---|---|
| 行为蒸馏 | 用大模型生成“用户问题-思维链-工具调用-最终答案”的完整轨迹 | SFT阶段 | 生成轨迹的API费用和过滤成本 |
| 合成数据扩充 | 大模型根据种子工具和场景批量生成新任务与新对话 | 数据准备阶段 | 多样性设计和质量清洗成本 |
| 偏好对齐 | 针对同一问题,生成“正确工具调用”和“错误工具调用”作为正负样本 | DPO/RLHF阶段 | 偏好标注的准确性和一致性 |
行为蒸馏是基础。我最早的做法就是拿一个大模型API,给出一堆工具定义和用户问题,让它生成完整调用过程。这里有个容易被忽略的细节:大模型生成的轨迹不一定好用,经常有幻觉,参数值瞎编、工具名张冠李戴,所以过滤这一关绝对不能省。合成数据扩充是在有了基础种子之后,让大模型在工具列表里随机组合出更复杂的场景,制造“需要多步调用”的任务。偏好对齐则是用来解决“模型虽然会写JSON,但不会选对工具”的问题,后面会详细展开。
一句话总结:大模型不是用来替代小模型的,而是用来给小模型“出题、给答案、批改作业”的。
2. 工具使用能力的数据飞轮怎么搭
2.1 工具调用的数据表示:从ReAct到JSON function calling
开始造数据之前,必须先定数据格式。目前主流两种:一种是很早的ReAct,把推理和动作都写在文本里;另一种是更结构化的JSON function calling,模型输出一个带函数名和参数的对象。我的建议是直接用JSON格式。原因很简单:文本式ReAct在小模型上容易“跑偏”,从Thought到Action再到Observation,稍微写错一个词,后面的解析就全乱;而JSON是强结构,天然方便代码解析和校验,也方便后续接入工具网关。
一个典型的训练样本长这样:
{
"instruction": "帮我把北京明天下午三点到五点的空闲会议室订一下,人数大概八人。",
"tools": [
{
"name": "search_meeting_rooms",
"description": "根据时间和人数查询空闲会议室",
"parameters": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "日期,YYYY-MM-DD格式"},
"time_start": {"type": "string"},
"time_end": {"type": "string"},
"capacity": {"type": "integer"}
}
}
}
],
"thought": "用户需要预订会议室,需要先查询空闲会议室。日期是明天,时间是下午3点到5点,人数8人。",
"call": {
"name": "search_meeting_rooms",
"arguments": {
"date": "2025-06-12",
"time_start": "15:00",
"time_end": "17:00",
"capacity": 8
}
}
}
别小看这个结构。我踩过最惨的坑就是早期样本里“thought”和“call”没有分离,小模型经常把thought里的内容也折进arguments里,导致JSON解析失败。后来强制要求:thought是隐含推理,不参与执行;call是最终输出,只保留结构和参数。这一步的一致性比什么都重要。
2.2 用大模型批量合成工具调用任务
有了格式,接下来就是批量造任务。我的做法是分三步:
- 种子工具库先建好 :把真实业务里会用到的工具按功能分类,至少准备20到30个。每个工具必须有结构化的schema,包含工具名、描述、参数类型、参数说明、必填项。描述尤其重要,小模型主要靠描述来匹配工具。
- 构造用户意图池 :针对每个工具,人工写5到10个典型用户问题作为种子,然后把这些问题丢给大模型,让它扩展出更多变体。比如“查天气”可以变成“明天去杭州出差,需要知道穿什么衣服”“周六想带娃去公园,适合户外活动吗”等等。大模型擅长这种同义改写和场景衍生。
- 让大模型完整走一遍调用链 :给大模型看当前用户问题、可用工具集合,让它自己决定调哪个工具、填什么参数、拿到结果后怎么回答。如果工具结果字段是虚构的,就让大模型先造一个符合schema的返回结果,再基于结果回答。
这里有一个非常关键的点: 一定要生成“需要组合多个工具”的复杂任务 。单工具调用模型很容易学会,但真实业务里经常是“先查空闲会议室,再订会议室,再通知参会人”,这种多跳任务如果不覆盖,小模型上线就会断链。
2.3 问答能力的数据怎么扩
工具调用之外的另一半,是问答能力。这一块我推荐的组合是“检索增强问答 + 多轮对话”一起做。纯让大模型凭空生成一堆QA对,效果会偏“大模型腔”,而且很容易犯事实错误。更好的做法是:先准备一批业务文档或知识库片段,再让大模型基于这些片段构造问题,同时要求答案必须能在片段里找到依据。
一个具体的流程:
1. 从知识库中抽取段落,保留标题、正文和元信息。
2. 用大模型为每个段落生成3-5个问题,覆盖细节抽取、归纳总结、对比分析等类型。
3. 让大模型先决定“是否需要调用检索工具”,再生成查询语句。
4. 把检索结果拼进上下文,生成最终答案。
这样训练出来的小模型,就不只是在“背答案”,而是学会了一个完整链路:先判断该不该检索、怎么检索、拿到结果后怎么组织答案。这套逻辑和工具调用是同构的,所以你可以把它们放进同一个训练集,不要让模型以为“只有工具类任务才需要结构化输出”。
2.4 数据筛选与去重:别让脏数据进训练集
合成数据最大的问题是“量大但脏”。大模型生成1000条数据,真正能用的可能只有600条。所以我在数据进入训练集之前,加了四道过滤:
- 硬性规则过滤 :JSON能否解析、工具名是否在工具库中、参数是否通过schema校验。
- 逻辑一致性校验 :用户问题问的是“明天”,生成的arguments日期是不是明天;“人数八人”,capacity是不是8。
- 大模型质量打分 :让一个较强的大模型按“用户意图理解准确、工具选择合理、参数填写正确、答案自然度”四个维度打分,低于阈值的直接丢掉。
- 语义去重 :用Embedding模型把指令转成向量,聚类后只保留每个簇中最具代表性的样本。这一步能有效避免大量同质化数据把模型“带偏”。
这套流水线跑下来,最终能用的数据大概只有原始生成量的五到六成,但训练效果好了不止一个档次。数据量不是越大越好,干净且多样才是关键。
3. 持续改进的闭环:训练、评估、回流
3.1 SFT阶段:让模型先“会说工具话”
数据准备好之后,进入训练环节。在SFT阶段,我的核心目标是让模型学会“格式化地说话”——看到用户问题,知道先想一下,再输出一个结构化的工具调用。这个阶段不需要追求模型多么聪明,先确保它输出稳定、字段正确、风格统一。
实际操作中,我用的是LLaMA-Factory,因为它对SFT和后面要做的DPO都支持得比较好。一个典型的SFT配置里,需要关注几个点:
- 指令模板 :尽量和推理阶段的prompt保持一致。比如训练时用“你是一个智能助手,可以使用以下工具...”,那推理时也要用相同的前缀,模板不一致会导致效果暴跌。
- Loss计算掩码 :只对模型需要生成的部分计算loss,工具定义、系统提示这些“输入”部分要mask掉。早期我图省事没做mask,结果模型学会了复读工具描述而不是真正调用。
- 学习率与轮数 :小模型懒,这种结构化任务不需要训太多轮,我通常2到3个epoch就够。轮数过多模型会记住训练集,导致泛化变差。
llamafactory-cli train \
--model_name_or_path Qwen/Qwen2.5-7B-Instruct \
--stage sft \
--dataset tool_qa_v1 \
--cutoff_len 4096 \
--learning_rate 1e-5 \
--num_train_epochs 3 \
--lora_rank 32 \
--output_dir ./checkpoints/sft_v1
SFT后先做一轮快速冒烟测试,重点关注格式错误率。如果100条测试里还有超过5条JSON解析失败,就先不要往下走,回头查数据模板和mask配置。
3.2 工具对齐阶段:从“会调用”到“会选对”
SFT只能让模型“会说工具话”,但“选对工具”是另一个能力层面。有时候模型格式完全正确,却调了一个明显不对的工具,比如用户问“天气”,它却调了“查日历”。这属于意图理解与工具选择的错位,需要在偏好对齐阶段解决。
我实际用的是DPO。构造偏好数据的方式是:给大模型同一个用户问题,让它分别生成“正例”和“负例”,正例是正确选择工具并给出合理参数的轨迹,负例则是故意写一个“格式正确但工具选错”的轨迹。注意负例不能胡编到离谱,它最好是那种“很像正确答案但实际不行”的样本,这样模型才能真正学会区分。
这部分的训练数据通常不需要太多,我大概用了3000到5000条偏好对,就看到了明显改善。DPO本身不复杂,核心在于偏好对的构造质量。我踩过的坑是:正负例差异过大,比如正例是完美调用,负例是完全没有调用,模型学到的只是“必须调用工具”这个表层规律,而不是“正确选择工具”。后来我把负例改成“调用了工具,但搞错了工具名或参数语义”,效果才上来。
3.3 自动化评估:不能只看文本相似度
评估是整个闭环里最容易自欺欺人的环节。如果你只拿BERTScore或者Rouge去算答案相似度,你会发现分数高得离谱,但模型一上线就露馅。原因是工具调用是强约束结构,一步错步步错,单纯的文本重叠率根本反映不了执行结果。
我搭建了一个两层评估体系:
| 评估维度 | 方法 | 通过标准 |
|---|---|---|
| 结构有效性 | 解析模型输出,检查JSON、工具名、参数schema | 解析成功率 ≥ 98% |
| 执行正确性 | 真实调用工具网关,模拟执行,对比最终结果 | 任务成功率 ≥ 85% |
| 语义质量 | 把模型输出和大模型参考答案交给LLM-as-judge打分 | 单轮打分 ≥ 4.5/5 |
这里最有价值的其实是“执行正确性”。我们在工具网关里加了一个mock模式,每个工具都有对应的模拟返回,这样每次模型改动后,可以自动化跑几百条真实链路,看它是不是真的按预期完成了任务。这个mock模式就像一个沙盒,能在不影响线上服务的前提下,把问题都暴露出来。
我还会每周存一份评测样本,防止某次数据改动导致回归。这个版本隔离是很多团队会忽略的,后面专门讲。
3.4 线上失败样本回流:持续改进的最后一块拼图
评估只能发现问题,真正让模型变强的,是线上失败样本的回流。我设计了一个很简单的回流管道:
- 实时采集 :线上服务把每一次请求的输入、输出、用户反馈、异常日志写入日志系统。
- 失败识别 :通过规则(比如JSON解析失败、调用了不存在的工具、用户点了“踩”)和模型分类器筛选出失败样本。
- 聚类归因 :把失败原因分成几类——格式错误、工具选择错误、参数缺失、答案不准确等。
- 修复并入库 :把失败样本抽出来,用大模型重新生成正确轨迹,再经过过滤规则,进入下一轮训练集。
这个流程看起来简单,但真正难的是坚持做。很多团队一开始兴致勃勃,跑两周就停了,然后模型就在原地踏步。我个人的做法是每周固定跑一次“回流+重训+评估”的小迭代,即使效果没有明显提升,也能保证模型不会越用越差。
4. 我的技术选型和开源参考
4.1 小模型底座怎么选
我在这套方案里试过不少开源底座,简单列个对比供参考:
| 模型 | 参数量 | 工具调用表现 | 中文问答表现 | 部署成本 |
|---|---|---|---|---|
| Qwen2.5-7B-Instruct | 7B | 优秀,原生支持function calling | 优秀 | 单卡A10可跑 |
| Qwen2.5-3B-Instruct | 3B | 良好,经过训练后可用 | 良好 | CPU也能跑,速度一般 |
| Llama-3.2-3B-Instruct | 3B | 中等,需要较多数据微调 | 一般 | 低 |
| Mistral-7B-Instruct | 7B | 中等偏上 | 中等,中文稍弱 | 较高 |
| DeepSeek-R1-Distill-Qwen-7B | 7B | 推理更强,但输出冗长 | 优秀 | 中等 |
如果让我推荐一个“性价比之王”,我会选Qwen2.5-3B-Instruct。它的底子不错,配合生成的高质量数据,能在工具调用场景达到接近7B模型八成以上的效果,但部署和推理成本低很多。如果你对效果要求更高,Qwen2.5-7B是更稳妥的选择。但如果业务知识量很大、复杂推理多,预算又允许,直接上14B甚至32B也不是不行,无非是成本换效果。
4.2 训练、推理、评测工具链
工具链方面,我只推荐我自己实际用着顺手的:
- LLaMA-Factory :数据预处理、SFT、DPO一站式搞定,适合快速迭代。
- TRL :如果只想用Hugging Face生态,TRL的DPOTrainer也很成熟。
- Unsloth :在资源紧张的时候微调用,能省不少显存,训练速度也更快。
- vLLM :小模型部署和推理用vLLM,吞吐量高,兼容OpenAI接口,后面接工具网关很方便。
- Ollama :本地快速试模型时用,胜在省心,适合验证想法。
- LangSmith / Langfuse :我拿Langfuse做链路追踪,看线上“问题→工具调用→最终答案”的完整链路,定位失败节点很快。
其中vLLM有一个点我特别推荐:它支持函数调用格式的输出约束。在部署阶段,可以直接用它把输出限制成合法的JSON结构,能省掉不少解析错误。虽然训练时还是要让模型本身学会正确输出,但推理时加一道约束,能明显降低线上格式崩溃率。
4.3 值得反复看的开源项目和论文
这波“大模型教小模型”的做法,开源社区其实已经积累了不少好东西。几个对我启发最大的:
- Toolformer :最早让我认识到“模型可以通过自监督方式学会调用工具”的工作,虽然是老论文,但思路很经典。
- Gorilla :专门做API调用的大模型,它的数据构造方法值得细读,里面有很多关于“如何把API文档和调用样例组织成训练数据”的细节。
- DeepSeek-R1的蒸馏思路 :虽然R1主要是推理模型,但它证明了“用大模型生成链式推理,蒸馏到小模型”这条路是通吃的,工具调用本质上也是链式推理的一种。
- AnyTool :在大模型工具调用方面做了大量数据组合的工作,尤其是“多工具协同”的构造逻辑,我上面提到的组合任务,很大程度上受了它的启发。
看这些工作的时候,重点不是复现代码,而是理解他们“数据从哪来”“格式怎么定”“失败样本怎么处理”。这些才是决定一个工具调用模型能不能落地的核心。
5. 实测中的坑与应对
5.1 格式崩溃:小模型真的会“说漏嘴”
工具调用训练最磨人的坑就是格式崩溃。小模型经常会出现这些状况:
用户:帮我查下北京明天天气
模型:好的,我来帮你查询北京的天气。调用 search_weather 工具。
{"city": "北京", "date": "明天"}
看,它在调用工具之前先输出了一段自然语言,而且“明天”没有转换成年月日。这种输出在文本聊天里看起来很自然,但解析引擎拿到它,要么识别不出工具调用,要么日期字段类型不对。我的解决办法是在训练数据里强制加入大量“直接调用工具,不加多余寒暄”的样本,同时在推理侧配合vLLM的结构化约束。双管齐下之后,格式崩溃率才从最初的20%降到了1%以内。
5.2 蒸馏过头的退化:大模型错觉被放大
另一个坑是大模型的错误被小模型放大了。大模型生成轨迹时,偶尔会一本正经地给参数填一个看似合理但不存在的值,比如用户问“我附近有什么川菜馆”,大模型可能直接填一个“lat: 39.9, lng: 116.4”的坐标,而不是去调定位服务。这种错误在单条数据里不显眼,但合成数据一多,小模型就会把这种短期行为当成规律,甚至学会“编坐标”而不是“请求权限”。
针对这类问题,我后来定了一条铁律: 所有参数必须能从用户输入或知识库中明确推导,否则一律标记为缺失并生成“询问澄清”的回复 。还有一招是对大模型生成的轨迹做“逆向验证”:拿着生成的参数重新去调mock工具,看返回是否和上下文吻合,对不上的直接废弃。这一步能把大量隐性噪音挡在训练门外。
5.3 评估集污染:持续改进最大的隐形杀手
持续改进做得越久,越要小心评估集污染。早期我把线上失败样本修复后直接丢进训练集,同时也把其中几条放进评估集,结果每次迭代评估分数都在涨,上线却没有实感。后来一查,原来是部分评估样本已经在训练集里出现过,模型记住答案了。
从那以后,我强制做了两件事:一是训练集和评估集按时间窗口隔离,比如6月之前的数据进训练集,6月之后的数据进评估集;二是每个评估样本绑定一个“唯一来源ID”,通过脚本检查它是否在训练数据中出现过。这两条规则执行之后,评估分数的“虚高”问题基本消失,模型改进方向也清晰多了。
5.4 工具数量扩展与上下文压力
工具数量少的时候,把所有工具定义都塞进系统提示词没问题。但当工具数量到三四十个以上,两个问题就出现了:一是token占用越来越大,挤占上下文空间;二是模型面对一堆工具描述容易“选择困难”,选错率上升。
我的方案是做一个轻量级的工具路由模块:先根据用户问题做一次粗分类,只把可能相关的5到8个工具定义放进提示词。这个路由不一定要用大模型,可以用Embeding相似度检索,成本低、速度快。实测下来,工具选择准确率反而涨了,因为模型不再被无关工具干扰。等未来工具数量继续增长,还可以引入多级路由,但那是另一个故事了。
最后再分享一个实际操作中的心得:这套“大模型教小模型”的方法,本质上是把大模型的智能“压缩”成规则和样例,再刻进小模型里。它不会让7B模型一夜之间变成GPT-4,但能让它在特定任务上做到“够用、稳定、干净”。如果你也在做类似的事情,建议先不要追求一步到位,把数据管道和评估体系建好,再慢慢迭代。这两个地基打牢,模型能力会自己往上长。
671



被折叠的 条评论
为什么被折叠?



