芥末
发布于 2026-02-03 / 0 阅读
0
0

FunctionGemma 工具调用微调实战:让模型正确选择业务工具

FunctionGemma 是面向函数调用优化的 Gemma 3 270M 模型版本,适合用在智能体场景里,把用户的自然语言请求转换成可执行的 API(应用程序编程接口)调用。

普通聊天模型擅长回答问题,但智能体需要更进一步:它不仅要理解用户想做什么,还要判断应该调用哪个工具、传什么参数、什么时候不该闲聊而应该执行动作。FunctionGemma 的价值就在这里,它把“说话”推进到“行动”。

一个典型工具调用链路大致是这样:

flowchart LR
    A[用户输入自然语言] --> B[FunctionGemma 理解意图]
    B --> C{选择工具}
    C --> D[生成函数名]
    C --> E[生成函数参数]
    D --> F[业务系统执行 API]
    E --> F
    F --> G[返回执行结果]

不过,基础模型并不会天然理解每个公司的业务规则。比如用户问“差旅餐饮的报销限额是多少?”,从语言表面看,这是一个信息查询问题;但在企业环境里,它不应该去公开搜索,而应该查内部知识库。要让模型学会这种规则,就需要对工具调用行为做微调。

为什么工具调用还需要微调

FunctionGemma 已经具备函数调用能力,但“能调用函数”和“能按业务策略正确调用函数”不是一回事。

通用模型通常从公开数据中学习语言规律,它可以推断“这个问题像搜索问题”,却不知道某些问题必须走内部系统,也不知道某些工具之间的优先级。微调的目的,就是把这些业务语境和策略写进模型行为里。

常见需求可以分成三类:

需求例子微调要解决的问题
消除工具选择歧义“差旅策略是什么?”应该查内部知识库,而不是搜索互联网让模型在相似工具之间选择正确工具
适配专有任务控制移动设备功能、解析内部 API、生成监管报告让模型学习公开数据里没有的工具格式和参数规则
模型蒸馏用大模型生成训练样本,再微调小模型用更小、更快的模型承载固定业务流程

工具调用微调不是为了让模型“知道更多知识”,而是让模型在给定工具集合中学会稳定路由。

案例:在内部知识库和 Google 搜索之间做选择

假设系统里有两个搜索工具:

  1. search_knowledge_base:查询企业内部文档
  2. search_google:查询公开互联网信息

用户输入不同,模型应该选择不同工具:

用户问题正确工具原因
使用 Python 编写简单递归函数的最佳实践是什么?search_google这是公开技术知识
差旅餐饮的报销限额是多少?search_knowledge_base这是内部政策
如何创建一个新的 Jira 项目?search_knowledge_base这通常取决于企业内部流程
最新的 Python 版本有什么新特性?search_google需要查询公开信息

这个任务看起来简单,但对模型来说有歧义。因为这些问题都像“搜索请求”,区别不在语法,而在业务语境。

工具路由逻辑可以画成这样:

flowchart TD
    A[用户问题] --> B{是否涉及内部制度、流程、系统权限、企业工具}
    B -- 是 --> C[调用 search_knowledge_base]
    B -- 否 --> D{是否属于公开知识或互联网信息}
    D -- 是 --> E[调用 search_google]
    D -- 否 --> F[根据工具定义和上下文继续判断]

微调要做的事情,就是用足够多的样本让模型学会这条分界线。

数据集准备:让模型看到“问题”和“正确调用”

这个案例可以使用 bebechien/SimpleToolCalling 数据集。数据集中包含用户问题、工具名称和工具参数,适合训练模型在 search_knowledge_basesearch_google 之间做选择。

训练样本需要被整理成对话格式。简化后的结构可以理解为:

{
  "messages": [
    {
      "role": "user",
      "content": "如何创建一个新的 Jira 项目?"
    },
    {
      "role": "assistant",
      "content": "<start_function_call>call:search_knowledge_base{query:<escape>Jira project creation process<escape>}<end_function_call>"
    }
  ]
}

模型要学习的不是“Jira 是什么”,而是看到这类企业流程问题时,输出函数调用:

<start_function_call>call:search_knowledge_base{query:<escape>Jira project creation process<escape>}<end_function_call>

训练数据的处理流程如下:

flowchart LR
    A[加载 SimpleToolCalling 数据集] --> B[转换为对话格式]
    B --> C[划分训练集和测试集]
    C --> D[训练集用于 SFT 微调]
    C --> E[测试集用于评估未见样本]
    D --> F[得到微调后的 FunctionGemma]
    F --> E

示例代码:

from datasets import load_dataset

dataset = load_dataset("bebechien/SimpleToolCalling", split="train")

# 将原始样本转换成 FunctionGemma 所需的对话格式
dataset = dataset.map(
    create_conversation,
    remove_columns=dataset.features,
    batched=False,
)

# 训练集和测试集各占 50%
dataset = dataset.train_test_split(
    test_size=0.5,
    shuffle=False,
)

这里使用 50/50 的训练集和测试集划分,是为了更明显地观察模型在大量未见样本上的变化。实际业务里更常见的是 80/20 或 90/10,具体比例取决于数据量和评估需求。

数据划分的坑:shuffle=False 不是永远安全

shuffle=False 只有在源数据已经充分混合时才适合使用。

如果数据本身已经随机分布,直接按顺序切分可以避免额外随机性,方便复现实验。但如果源数据是按类别排列的,比如前半部分全是 search_google,后半部分全是 search_knowledge_base,直接 shuffle=False 会造成严重问题:

flowchart LR
    A[原始数据按类别排序] --> B[前半段 search_google]
    A --> C[后半段 search_knowledge_base]
    B --> D[训练集只看到 Google 搜索]
    C --> E[测试集主要是内部知识库]
    D --> F[模型没有学过如何区分两类工具]
    F --> G[测试结果很差]

这种情况下,模型训练时没有见过完整分布,自然无法学会工具选择边界。

更稳妥的做法是:

dataset = dataset.train_test_split(
    test_size=0.2,
    shuffle=True,
    seed=42,
)

选择是否打乱时,可以按这个表判断:

数据状态推荐设置原因
已确认预先随机混合shuffle=Falseshuffle=True 都可以不容易出现类别偏置
不知道数据是否混合shuffle=True更安全
明确按标签、工具、时间排序shuffle=True避免训练集和测试集分布不一致
时间序列预测任务谨慎使用 shuffle=True时间顺序可能是任务的一部分

工具调用任务通常不是时间序列任务,优先保证每个工具类别都能出现在训练集中。

使用 TRL 做监督式微调

Hugging Face TRL 提供了 SFTTrainer,可以用来做 SFT(监督式微调,Supervised Fine-Tuning)。在这个任务里,监督信号非常明确:用户输入对应哪个函数调用。

训练时,模型会反复看到这样的配对:

用户:差旅餐饮的报销限额是多少?
助手:<start_function_call>call:search_knowledge_base{query:<escape>meal reimbursement limit for business travel<escape>}<end_function_call>

经过若干轮训练后,模型会调整参数,使类似问题更倾向于输出内部知识库调用,而不是泛化成普通搜索或聊天回答。

训练逻辑可以概括为:

sequenceDiagram
    participant Data as 训练样本
    participant Trainer as SFTTrainer
    participant Model as FunctionGemma
    participant Eval as 测试集评估

    Data->>Trainer: 提供用户问题和目标函数调用
    Trainer->>Model: 执行监督式微调
    Model-->>Trainer: 返回训练损失
    Trainer->>Eval: 在未见样本上测试工具选择
    Eval-->>Trainer: 返回准确率和错误样本

在案例中,模型训练了 8 个周期。训练过程中的损失曲线可以用来判断模型是否正在学习新任务。

FunctionGemma 微调训练中的损失曲线

损失值在初期快速下降,说明模型很快捕捉到了新的工具路由规律。后续下降变慢则比较常见,因为模型已经学到主要模式,剩下的是对边界样本和格式细节的调整。

微调前,基础模型可能出现两类错误:

错误类型表现问题
选错工具内部政策问题调用 search_google没有理解业务边界
不调用工具直接解释“可以查看公司政策”没有遵守工具调用任务格式

微调后,当用户问:

如何创建一个新的 Jira 项目?

模型可以输出:

<start_function_call>call:search_knowledge_base{query:<escape>Jira project creation process<escape>}<end_function_call>

这说明模型学到的不是某个固定问题的答案,而是“企业内部流程类问题应该走内部知识库”的路由策略。

评估时不要只看训练损失

损失曲线下降只能说明模型在训练样本上学得更好了,不能单独证明它在真实场景里可靠。工具调用模型至少要检查三类指标:

指标检查内容示例
工具选择准确率函数名是否正确search_knowledge_base 是否被误选成 search_google
参数生成质量参数字段是否完整、语义是否合理query 是否准确概括用户意图
格式合法性输出是否能被解析器识别是否包含正确的 <start_function_call><end_function_call>

评估集必须和训练集分离。否则模型可能只是记住了训练样本,而不是真正学会路由规则。

实际落地时还可以加入更细的测试集:

测试集类型用途
常规样本检查基本工具选择能力
边界样本检查模糊问题,比如“Python 报销脚本怎么写”
对抗样本检查用户诱导模型绕开内部工具的情况
格式样本检查函数调用字符串是否稳定可解析

FunctionGemma Tuning Lab:不用写训练脚本的微调界面

如果不想手写 Python 训练脚本,可以使用 FunctionGemma Tuning Lab。它托管在 Hugging Face Spaces 上,提供了一个可视化界面,用来完成函数架构定义、数据上传、训练配置和评估。

FunctionGemma Tuning Lab 界面

界面把微调流程拆成几个操作区:定义函数 schema、上传 CSV(逗号分隔值)训练数据、设置学习率和训练周期、启动训练、观察日志和损失曲线。对只想快速验证工具路由效果的场景来说,它可以省掉依赖安装、训练循环编写和评估脚本搭建的时间。

Tuning Lab 的核心能力如下:

功能作用
无代码函数定义直接用 JSON(JavaScript Object Notation)描述工具名称、参数和说明
CSV 数据导入上传用户提示词、目标工具名、工具参数
可视化训练配置用界面设置学习率、训练周期等参数
实时训练日志查看训练是否正常收敛
自动前后评估对比微调前后的工具调用效果

一个工具 schema 可以写成类似结构:

[
  {
    "name": "search_knowledge_base",
    "description": "Search internal company documents, policies, and procedures.",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "description": "The internal knowledge base search query."
        }
      },
      "required": ["query"]
    }
  },
  {
    "name": "search_google",
    "description": "Search public web information.",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "description": "The public web search query."
        }
      },
      "required": ["query"]
    }
  }
]

CSV 数据可以按这种形式准备:

prompt,tool_name,arguments
"差旅餐饮的报销限额是多少?","search_knowledge_base","{""query"":""business travel meal reimbursement limit""}"
"使用 Python 编写递归函数有哪些最佳实践?","search_google","{""query"":""Python recursive function best practices""}"
"如何创建一个新的 Jira 项目?","search_knowledge_base","{""query"":""Jira project creation process""}"

这类数据不需要写成长篇回答,因为目标不是训练模型聊天,而是训练模型输出正确函数调用。

本地运行 FunctionGemma Tuning Lab

想在本地运行 Tuning Lab,可以使用 Hugging Face 的 hf CLI(命令行界面)下载 Space 代码。

安装并登录 Hugging Face CLI 后,执行:

hf download google/functiongemma-tuning-lab \
  --repo-type=space \
  --local-dir=functiongemma-tuning-lab

cd functiongemma-tuning-lab

pip install -r requirements.txt

python app.py

运行后,就可以在浏览器里打开本地界面,完成函数定义、数据上传和训练。

什么时候用 TRL,什么时候用 Tuning Lab

两种方式都能完成微调,但适合的场景不一样。

方式适合场景优点代价
TRL 训练脚本需要接入现有训练流水线、做复杂数据处理、定制评估逻辑灵活,可自动化,适合生产流程需要写代码和管理依赖
FunctionGemma Tuning Lab快速验证工具路由效果、演示、少量数据试验上手快,界面直观,自带可视化定制能力有限,不适合复杂训练系统

如果只是验证“内部知识库 vs Google 搜索”这类路由策略,Tuning Lab 足够快。
如果要接入持续训练、自动评估、版本管理和灰度发布,TRL 脚本更适合。

微调工具调用模型时需要注意的细节

1. 工具描述要写清楚边界

工具 schema 里的 description 会影响模型理解工具用途。描述不能只写“搜索”,否则两个工具看起来几乎一样。

不推荐:

{
  "name": "search_knowledge_base",
  "description": "Search documents."
}

更好的写法:

{
  "name": "search_knowledge_base",
  "description": "Search internal company documents, including policies, procedures, employee guides, and internal system instructions."
}

工具描述越能体现边界,模型越容易学会选择。

2. 训练样本要覆盖相似但答案不同的问题

如果训练集中只有明显样本,模型遇到模糊问题时仍然会不稳定。应该主动构造“长得像但工具不同”的样本。

问题正确工具
Python 递归函数怎么写?search_google
公司内部 Python 项目代码规范在哪里?search_knowledge_base
Jira 是什么?search_google
如何在公司 Jira 里创建新项目?search_knowledge_base

这种对比样本比单纯堆数量更有用,因为它直接教模型区分边界。

3. 不要让测试集泄漏到训练集

如果训练集和测试集有重复样本,评估结果会虚高。工具调用任务尤其容易出现相似模板,需要去重或至少检查高度相似样本。

可以做三层检查:

flowchart TD
    A[原始数据] --> B[按完全相同文本去重]
    B --> C[检查近似重复问题]
    C --> D[按工具类别分层划分]
    D --> E[训练集]
    D --> F[测试集]

4. 关注格式稳定性

函数调用结果通常要被程序解析。即使工具选对了,只要格式错了,业务系统仍然无法执行。

需要重点检查:

  • 函数名是否存在;
  • 参数名是否符合 schema;
  • 字符串转义是否正确;
  • 是否多输出了无法解析的解释文本;
  • 是否遗漏起止标记。

5. 小模型更依赖高质量样本

FunctionGemma 基于小规模模型,优势是推理速度快、部署成本低,但它对训练数据质量更敏感。样本里如果工具名称混乱、参数格式不统一、同类问题标签不一致,模型很容易学到错误模式。

关键实践清单

FunctionGemma 工具调用微调可以按这个顺序落地:

flowchart TD
    A[定义工具 schema] --> B[准备用户问题、工具名、参数]
    B --> C[清洗和混合数据]
    C --> D[划分训练集和测试集]
    D --> E{选择训练方式}
    E --> F[TRL 脚本微调]
    E --> G[Tuning Lab 可视化微调]
    F --> H[评估工具选择和格式合法性]
    G --> H
    H --> I[分析错误样本]
    I --> J[补充边界样本继续训练]

微调的核心不是让模型背答案,而是让模型学会稳定执行业务规则。对于智能体系统来说,这一步决定了自然语言请求能否被准确路由到正确工具。FunctionGemma 适合承担这种轻量、快速、成本可控的工具调用任务;配合高质量数据和清晰工具 schema,就能把通用函数调用模型改造成面向具体业务流程的执行组件。


评论