在顶部菜单中打开 Add-ons → Secrets,然后点击 Add Secret。将密钥名称设为 HUGGINGFACE_TOKEN,并将您的 Hugging Face API Key 粘贴为其值。
使用 Kaggle Secrets 比在笔记本中直接硬编码环境变量或令牌更安全。
密钥保存后,Kaggle 会提供一段代码片段,便于以编程方式访问。
使用以下代码在 Hugging Face Hub 上进行认证:
from kaggle_secrets import UserSecretsClient
from huggingface_hub import login
hf_token = UserSecretsClient().get_secret("HUGGINGFACE_TOKEN")ifnot hf_token:raise ValueError("HUGGINGFACE_TOKEN not found in Kaggle Secrets.")
login(token=hf_token)技术支持
认证成功后,您将能够访问受限模型与数据集,并可将微调后的 FunctionGemma 模型推送到 Hugging Face Hub 以供分享与复用。
def_parse_hermes_type(t)->dict:if t isNone:return{"type":"string"}ifisinstance(t,dict):return t if"type"in t else{"type":"object"}ifisinstance(t,list):return{"type":"array"}ifnotisinstance(t,str):return{"type":"string"}
t = t.strip()
prim ={"str":"string","string":"string","int":"integer","integer":"integer","float":"number","number":"number","bool":"boolean","boolean":"boolean","dict":"object","object":"object","list":"array","array":"array",}if t.lower()in prim:return{"type": prim[t.lower()]}
m = re.match(r"List\[(.+)\]$", t)if m:return{"type":"array","items": _parse_hermes_type(m.group(1).strip())}
m = re.match(r"Dict\[(.+),\s*(.+)\]$", t)if m:return{"type":"object","additionalProperties": _parse_hermes_type(m.group(2).strip())}return{"type":"string"}技术支持
def_role(turn):return(turn.get("from")or"").lower().strip()defget_gold_tool_call_task_aligned(ex):
task =(ex.get("task")or"").strip()
conv = ex.get("conversations")or[]ifnot conv:returnNone
idx =Noneif task:for i, t inenumerate(conv):if _role(t)in["human","user"]:
val =(t.get("value")or"").strip()if val == task or task in val or val in task:
idx = i
breakif idx isNone:for i inrange(len(conv)-1,-1,-1):if _role(conv[i])in["human","user"]:
idx = i
breakif idx isNone:returnNonefor j inrange(idx+1,len(conv)):if _role(conv[j])in["gpt","assistant","model"]:
gold = extract_first_tool_call_obj(conv[j].get("value",""))if gold:return gold
if _role(conv[j])in["human","user"]:breakreturnNone技术支持
defformat_row_as_text(row):
messages =[{"role":"developer","content":"You are a model that can do function calling with the following functions"},{"role":"user","content": row["user_content"]},]
prompt = processor.apply_chat_template(
messages,
tools=row["hf_tools"],
add_generation_prompt=True,
tokenize=False,)
target =(f"<start_function_call>call:{row['tool_name']}"f"{{args:<escape>{row['tool_arguments']}<escape>}}"f"<end_function_call>")return prompt + target技术支持
现在将这些行转换为 Hugging Face Dataset 对象:
train_text_ds = Dataset.from_list([{"text": format_row_as_text(r)}for r in simple_train])
eval_text_ds = Dataset.from_list([{"text": format_row_as_text(r)}for r in simple_eval])技术支持
步骤 3:确保每个样本均为有效字符串
某些 TRL 版本要求 formatting_func 返回字符串;若任何样本成为列表或非字符串,训练可能会出错。下面的辅助函数会强制 text 始终为干净的字符串。
# ensure text is always a stringdefforce_text_string(ds):deffix(ex):
t = ex.get("text","")ifisinstance(t,list):
t ="\n".join(map(str, t))return{"text":str(t)}return ds.map(fix)
train_text_ds = force_text_string(train_text_ds)
eval_text_ds = force_text_string(eval_text_ds)技术支持
<bos><start_of_turn>developer
You are a model that can do function calling with the following functions<start_function_declaration>declaration:get_pollution_levels{description:<escape>Retrieve pollution levels information<escape>,parameters:{properties:{location:{description:<escape>The location for which you want to get the pollution levels (e.g., Beijing, London, New York)<escape>,type:<escape>STRING<escape>},pollutant:{description:<escape>Specify a pollutant for pollution levels (e.g., PM2.5, PM10, ozone)<escape>,type:<escape>STRING<escape>}},required:[<escape>location<escape>],type:<escape>DICT<escape>}}<end_function_declaration><start_function_declaration>declaration:get_water_quality{description:<escape>Retrieve water quality information<escape>,parameters:{properties:{location:{description:<escape>The location for which you want to get the water quality (e.g., river, lake, beach)<escape>,type:<escape>STRING<escape>}},required:[<escape>location<escape>],type:<escape>DICT<escape>}}<end_function_declaration><end_of_turn><start_of_turn>user
I'm planning a beach cleanup at Zuma Beach this weekend and need to ensure safety. Can you provide the current water quality and if it's poor, check the pollution level of PM2.5 there?<end_of_turn><start_of_turn>model
<start_function_call>call:get_water_quality{args:<escape>{"location":"Zuma Beach"}<escape>}<end_function_call>技术支持
import re
import evaluate
import torch
from tqdm.auto import tqdm
cer_metric = evaluate.load("cer")# Gemma / FunctionGemma-style only
FG_BLOCK_RE = re.compile(r"<start_function_call>.*?<end_function_call>", re.DOTALL)
FG_NAME_RE = re.compile(r"call:([a-zA-Z0-9_]+)\{", re.DOTALL)defextract_tool_name(gen:str):
gen = gen or""
m = FG_NAME_RE.search(gen)return m.group(1)if m elseNonedefextract_call_block(gen:str):
gen = gen or""
m = FG_BLOCK_RE.search(gen)return m.group(0)if m else""defgold_call_block(r):return(f"<start_function_call>call:{r['tool_name']}"f"{{args:<escape>{r['tool_arguments']}<escape>}}"f"<end_function_call>")技术支持
@torch.inference_mode()defeval_tool_and_cer(proc, mdl, rows, n=50, max_new_tokens=128):
mdl.eval()
n =min(n,len(rows))
tool_ok =0
preds, refs =[],[]for i in tqdm(range(n)):
r = rows[i]
messages =[{"role":"developer","content":"You are a model that can do function calling with the following functions"},{"role":"user","content": r["user_content"]},]
inputs = proc.apply_chat_template(
messages,
tools=r["hf_tools"],
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",)
inputs ={k: v.to(mdl.device)for k, v in inputs.items()}
out = mdl.generate(**inputs,
do_sample=False,
max_new_tokens=max_new_tokens,
pad_token_id=proc.eos_token_id,)
gen = proc.decode(out[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True).strip()if extract_tool_name(gen)== r["tool_name"]:
tool_ok +=1
preds.append(extract_call_block(gen))
refs.append(gold_call_block(r))return{"n_eval": n,"tool_accuracy": tool_ok / n,"TC-CER (lower is better)": cer_metric.compute(predictions=preds, references=refs),}技术支持
import torch
@torch.inference_mode()definfer_one(proc, mdl, rows, idx=0, max_new_tokens=128):
r = rows[idx]
messages =[{"role":"developer","content":"You are a model that can do function calling with the following functions"},{"role":"user","content": r["user_content"]},]
inputs = proc.apply_chat_template(
messages,
tools=r["hf_tools"],
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",)
inputs ={k: v.to(mdl.device)for k, v in inputs.items()}
out = mdl.generate(**inputs,
do_sample=False,
max_new_tokens=max_new_tokens,
pad_token_id=proc.eos_token_id,)
gen = proc.decode(
out[0][inputs["input_ids"].shape[-1]:],
skip_special_tokens=True).strip()
pred_tool = extract_tool_name(gen)return{"task": r["user_content"],"tool_match":(pred_tool == r["tool_name"]),"predicted": extract_call_block(gen),"gold": gold_call_block(r),}技术支持
TASK:
I'm reviewing the schedule in the surgical unit. Can you fetch the surgical nursing details for patient ID 12345 undergoing a 'Cardiac Bypass' today? If the procedure is handled by Nurse Ratched, let's record a post-operative care task for this evening.
TOOL MATCH: True
--- PREDICTED ---
<start_function_call>call:get_surgical_nursing_information{nurse_id:<escape>Ratched<escape>,patient_id:<escape>12345<escape>,procedure_type:<escape>Cardiac Bypass<escape>}<end_function_call>
--- GOLD ---
<start_function_call>call:get_surgical_nursing_information{args:<escape>{"patient_id":"12345", "procedure_type":"Cardiac Bypass"}<escape>}<end_function_call>技术支持
defformatting_func(example):# MUST return a STRING (not list) for your TRL version
t = example.get("text","")ifisinstance(t,list):
t ="\n".join(map(str, t))returnstr(t)技术支持
TASK:
I'm reviewing the schedule in the surgical unit. Can you fetch the surgical nursing details for patient ID 12345 undergoing a 'Cardiac Bypass' today? If the procedure is handled by Nurse Ratched, let's record a post-operative care task for this evening.
TOOL MATCH: True
--- PREDICTED ---
<start_function_call>call:get_surgical_nursing_information{args:<escape>{"patient_id":"12345", "procedure_type":"Cardiac Bypass", "nurse_id":"Ratched", "task":"Post-operative care"}<escape>}<end_function_call>
--- GOLD ---
<start_function_call>call:get_surgical_nursing_information{args:<escape>{"patient_id":"12345", "procedure_type":"Cardiac Bypass"}<escape>}<end_function_call>技术支持
TASK:
I would like to extract details from a LinkedIn company page. Could you assist me in fetching the information from this URL: https://www.linkedin.com/company/abc-corporation?
TOOL MATCH: True
--- PREDICTED ---
<start_function_call>call:extract{args:<escape>{"url":"https://www.linkedin.com/company/abc-corporation"}<escape>}<end_function_call>
--- GOLD ---
<start_function_call>call:extract{args:<escape>{"url":"https://www.linkedin.com/company/abc-corporation", "html": true}<escape>}<end_function_call>技术支持
在本教程中,我们将探索来自 Google DeepMind 的轻量级函数调用语言模型 FunctionGemma,并解释为何微调对于实现可靠且符合模式(schema)的工具使用至关重要。
我们将先设置启用 GPU 的 Kaggle 环境,然后加载并准备数据集和基础 FunctionGemma 模型。
接下来,我们会在微调前进行评估,以建立工具选择与函数调用准确性的基线。随后使用监督训练对 FunctionGemma 进行微调,并在训练后评估其表现,以验证微调是否正确生效。
如果您想通过动手练习加深对微调的理解,建议查看 Fine-Tuning with Llama 3 课程。
什么是 FunctionGemma?
FunctionGemma 是 Google 的 Gemma 3 270M 开源模型的一个专用版本,专为函数调用与工具使用而设计,而非通用对话。
它采用与 Gemma 3 相同的架构,但加入了专门的格式与训练重点,使其能够生成表示函数调用的结构化输出。
FunctionGemma 以基础模型形式发布,开发者可针对特定用例进行微调。其规模与设计使之轻量高效,可部署在资源受限的设备上,如笔记本与边缘硬件。
为何要微调 FunctionGemma?
尽管 FunctionGemma 已针对函数调用进行训练,但这类规模的模型在通过任务特定数据进行微调、实现专门化后,表现更佳。
微调有助于模型学习稳定的模式,以便从一组工具中选择正确的函数,并为真实用例正确格式化相应参数。
这一过程会带来更一致、可预测的结构化输出,从而提高在实际工作流中的可靠性。
微调 FunctionGemma:分步指南
让我们逐步完成微调 FunctionGemma 所需的步骤。
1. 配置 Kaggle 环境
首先访问 Kaggle 并创建一个新笔记本。打开笔记本后,在右侧找到 Session options 面板,将 Accelerator 设置为 GPU (T4 ×2)。
这将为会话启用 GPU 加速,使您可以使用 Kaggle 的免费算力资源对模型进行微调。
启用 GPU 后,新建一个代码单元并运行以下命令安装所需的 Python 依赖。内核可能需要稍等片刻启动,随后将自动安装这些软件包。
接下来,安全地添加您的 Hugging Face 访问令牌。
在顶部菜单中打开 Add-ons → Secrets,然后点击 Add Secret。将密钥名称设为
HUGGINGFACE_TOKEN,并将您的 Hugging Face API Key 粘贴为其值。使用 Kaggle Secrets 比在笔记本中直接硬编码环境变量或令牌更安全。
密钥保存后,Kaggle 会提供一段代码片段,便于以编程方式访问。
使用以下代码在 Hugging Face Hub 上进行认证:
认证成功后,您将能够访问受限模型与数据集,并可将微调后的 FunctionGemma 模型推送到 Hugging Face Hub 以供分享与复用。
2. 加载 Hermes Reasoning Tool-Use 数据集
Hermes Reasoning Tool-Use 数据集是一个开源英文数据集,采用工具使用推理格式,提供结构化示例,用于训练与评估模型在工具选择与类 JSON 函数调用任务上的表现。
其中包含数以万计的工具使用推理样本,包含自然语言提示与工具模式(schema),格式适合用于监督微调。
本教程中,我们将使用该数据集的一个子集,以便更快速地进行实验。
首先,定义随机种子与使用的样本总数。然后将数据划分为训练与评估集:
接着,加载数据集、打乱顺序,并创建训练与评估子集:
这样我们得到 2,700 个训练样本和 300 个评估样本,足以演示微调流程,同时保持计算资源可控。
3. 加载 FunctionGemma 模型与处理器
在本教程中,我们使用 KaggleHub 将 FunctionGemma 模型直接加载到 Kaggle 笔记本中。
KaggleHub 会自动解析模型位置并在笔记本环境中提供模型文件,无需手动从 Hugging Face 下载。
运行上述单元后,模型文件会出现在 Kaggle 笔记本的 Input 区域,表示模型已成功加载且可在本地使用。
模型就绪后,我们将 processor(处理器) 与 model(模型) 一并加载到 GPU 内存。
处理器负责聊天模板与工具模式的格式化,模型负责生成结构化的函数调用。
设置
device_map="auto"可确保模型被放置在可用的 GPU 上,而dtype="auto"会选择硬件支持的高效精度。这确认了 FunctionGemma 已正确加载、放置于 GPU 上,并已准备好进行评估与微调。
4. 规范化工具定义并提取黄金函数调用
在微调 FunctionGemma 之前,我们需要标准化工具的表示方式,并可靠地从数据集中提取正确(黄金)的函数调用。
Hermes Reasoning Tool-Use 数据集可能以多种格式存储工具定义,因此此步骤会将所有内容转换为一致的结构,便于进行监督微调。
规范化 tools 字段
我们先规范化
tools字段。在数据集中,tools可能以 Python 列表、JSON 字符串出现,或完全缺失。下面的辅助函数将所有有效表示转换为干净的 Python 列表,并安全地忽略格式错误或空条目。
规范化参数类型
接下来规范化参数类型。Hermes 的工具定义常使用非正式类型标注,如
str、int,或如List[str]这类容器类型。下面的函数将这些类型转换为与 Hugging Face 函数调用兼容的有效 JSON Schema 类型。将 Hermes 风格工具转换为 Hugging Face 函数模式
利用这些工具函数,我们将 Hermes 风格的工具定义转换为 Hugging Face 的
{"type": "function", "function": {...}}模式。该函数支持两种格式:对于按参数粒度的格式,如果未显式提供 required 列表,则将列出的参数视为必填。
辅助函数
我们定义一个小型辅助函数,用于为每个数据集样本获取转换后的工具模式:
提取黄金函数调用
然后我们从对话中提取黄金工具调用。Hermes 对话可能包含多轮,我们通过解析
<tool_call>{...}</tool_call>块并提取第一个有效的工具调用对象(名称 + 参数)。这可使监督过程保持简单一致。将工具调用与用户任务对齐
为使工具调用与正确的用户任务对齐,我们扫描对话历史,找到与数据集
task匹配的用户消息,然后取其后的助手回复。如果该回复包含工具调用,则将其作为黄金标签返回。完成此步骤后,每个可用的数据集样本具备:
6. 构建用于函数调用的监督微调样本
现在我们需要使用上述辅助函数,将其转换为 监督微调(SFT) 训练样本,格式需完全符合 FunctionGemma 的预期。
目标是创建如下输入内容的示例:
而目标输出是如下格式的单个结构化 FunctionGemma 调用:
步骤 1:构建干净可用的行
我们从遍历数据集开始,过滤掉无法用于训练的示例。
该函数仅保留以下示例:
现在生成训练与评估用的行:
在我们的例子中,最终从最初的 3,000 条中得到 961 个训练样本与 109 个评估样本。这一降幅是预期的,因为过滤条件较为严格。它会移除缺少工具调用、工具定义无效、工具名不匹配或参数格式错误的示例。
清洗步骤至关重要。在应用该步骤前,数据集中存在嘈杂且未对齐的样本,即便微调后也会导致函数调用行为不稳定、结果不佳。过滤至仅保留高质量、任务对齐的示例后,FunctionGemma 的一致性显著提升,微调效果大幅改善。
步骤 2:将每一行转换为单个训练文本样本
接着,我们将每一行格式化为 FunctionGemma 所需的精确「提示 + 目标」结构。我们使用处理器的
apply_chat_template正确注入工具声明,然后附加黄金函数调用作为目标。现在将这些行转换为 Hugging Face Dataset 对象:
步骤 3:确保每个样本均为有效字符串
某些 TRL 版本要求
formatting_func返回字符串;若任何样本成为列表或非字符串,训练可能会出错。下面的辅助函数会强制text始终为干净的字符串。最后,打印一个样本以确认格式:
您应能看到包含工具声明的完整提示,随后是类似如下的黄金函数调用:
7. 微调前评估 FunctionGemma
在训练前,您应当测量基础 FunctionGemma 模型在函数调用任务上的表现。该基线有助于确认微调是否确实提升了工具选择与函数调用格式化能力。
本节我们评估两点:
我们使用两个正则表达式从模型输出中提取预测的工具名与完整的函数调用块,然后与黄金标签进行比较。
基线评估循环
下面的函数在评估子集上运行推理并计算两项指标。我们禁用采样 (
do_sample=False)以使结果确定,便于在微调前后对比。运行基线评估:
即使在微调前,FunctionGemma 在该任务上的基线表现也相当强劲。
在 50 个评估样本的子集上,模型的工具名称准确率达到 88%,即多数情况下能选对函数。
此外,完整函数调用块的字符错误率(TC-CER)约为 33%,说明尽管经常能选对工具,但生成的参数与格式仍与黄金目标存在偏差。
这些结果表明基础版 FunctionGemma 已理解函数调用的机制。
然而,较高的字符错误率凸显了微调的必要性,以改进参数完整性、格式一致性,以及生成函数调用的整体结构正确性。
查看单条预测
指标有用,但查看定性输出也很重要。下面的辅助函数会对一个示例运行推理,并将预测的函数调用与黄金参考并排打印。
对一个示例运行:
输出:
在此示例中,模型正确选择了合适的函数,因此工具匹配为 True。
但生成的调用包含额外参数,且未遵循训练目标要求的精确结构。
这些参数在语义上虽合理,但与黄金函数调用格式不一致,导致字符错误率更高。这说明微调不仅要改进工具选择,也需提升结构一致性与模式合规性。
8. 配置用于微调的训练器
既然我们已有干净的 SFT 数据集,下一步是配置用于微调 FunctionGemma 的训练器。我们使用 TRL 的
SFTTrainer,这是一种对聊天风格模型进行文本样本微调的轻量、可靠方式。我们还设置输出目录,使 Kaggle 将微调后的检查点保存至持久位置。
为降低训练期间的显存占用,我们启用梯度检查点并禁用 KV 缓存。
训练器配置
我们使用 SFTConfig 定义训练配置。这些设置在 Kaggle GPU 上兼顾稳定性与效率,同时保持训练时间较短。
格式化函数确保每个训练样本以单个字符串返回,这是当前 TRL 版本的要求。
初始化训练器
最后,我们用模型、配置、数据集与分词器初始化
SFTTrainer。9. 微调 FunctionGemma
训练器配置完成后,即可开始微调模型。该步骤会在准备好的数据集上进行监督训练,并在训练期间定期评估模型。
训练完成后,我们将微调后的模型与处理器保存到输出目录,以便后续推理或上传到 Hugging Face Hub。
训练过程中,训练与验证损失均稳步下降。平均 token 准确率也随时间提升,表明模型已学会更准确、一致地生成函数调用输出。
将微调后的 FunctionGemma 发布到 Hugging Face
微调完成后,我们可以发布模型,方便复用、分享或部署。将模型推送到 Hugging Face Hub 后,他人即可使用标准 Transformers API 直接加载。
上传完成后,微调模型将公开可用:
来源: kingabzpro/functiongemma-hermes-3k-ft · Hugging Face
微调后评估与误差分析
微调完成后,我们从磁盘重新加载保存的模型与处理器,并运行与训练前相同的评估流程。这样可确保微调前后对比公平、一致。
随后,我们在评估集上用工具名称准确率与 TC-CER 对微调模型进行评估。
结果显示相较基线有明显提升:
工具名称准确率从 88% 提升至 98%,完整函数调用块的字符错误率降低超过一半。这表明模型不仅更可靠地选择正确工具,还能生成更接近目标模式的输出。
为更好理解改进点,我们查看单条预测。下面的示例中,模型既选对了函数,也生成了结构良好、参数更完整的调用。
尽管预测可能包含超出黄金目标的额外参数,但总体结构与参数格式较微调前明显更一致。这反映出更好的模式感知与更强的用户意图—函数调用对齐能力。
我们再看另一个样本。
第二个示例展示了一个干净、正确的 URL 提取任务函数调用,说明微调后的模型能在不同工具间良好泛化。
总体而言,结果证实微调成功提升了函数调用任务的准确性与结构正确性。
本教程的完整 Kaggle 笔记本见:https://www.kaggle.com/code/kingabzpro/finetuning-functiongemma
如遇问题,您可克隆笔记本、添加 Hugging Face 密钥并端到端运行。
结语
如果您尝试用通用文本生成数据集来训练 FunctionGemma,最好尽早停止。FunctionGemma 并非为通用型语言模型行为而设计。
对于开放式文本生成,Gemma 3 模型家族是更佳选择。
FunctionGemma 为函数调用而生,其目标是选择正确工具、生成符合模式的参数,并与 API、数据库与服务等外部系统可靠交互。
FunctionGemma 的小尺寸是刻意设计之举。其 2.7 亿参数针对低时延、资源高效的部署进行了优化,包括本地机器、边缘设备与私有基础设施。这使其十分适合实时函数调用与智能体工作流,在这些场景中,正确性与结构比流畅表述更重要。
在本教程中,我们学习了如何在 Hermes Reasoning Tool-Use 数据集上微调 FunctionGemma。一个关键结论是:数据准备与评估比生硬堆叠训练时长更重要。
通过认真清洗数据集、将任务与黄金工具调用对齐,并严格执行模式一致性,我们仅用相对较少的高质量样本就能显著改善模型行为。
结果清晰表明微调的影响:工具名称准确率从 88% 提高至 98%,完整函数调用块的字符错误率降低超过一半。更重要的是,模型在生成结构良好、可预测且贴近真实使用的函数调用方面,一致性显著提升。