Ga naar hoofdinhoud

FunctionGemma fine-tunen: een stapsgewijze handleiding

Leer hoe je FunctionGemma in minder dan 10 minuten fine-tunet met de gratis GPU’s van Kaggle, van datasetvoorbereiding en baseline-evaluatie tot training en validatie na het fine-tunen.
· 13 min lezen

Verken met AI

In deze tutorial duiken we in FunctionGemma, een lichtgewicht taalmiddel voor function calling van Google DeepMind, en leg ik uit waarom fine-tunen essentieel is om betrouwbare en schema-conforme tooluse te bereiken. 

We beginnen met het opzetten van een GPU-omgeving in Kaggle en laden en bereiden vervolgens zowel de dataset als het basis FunctionGemma-model voor.

Daarna voeren we pre-fine-tuning-evaluaties uit om een baseline vast te stellen voor toolselectie en de nauwkeurigheid van function calls. Vervolgens fine-tunen we FunctionGemma met supervised training en evalueren we de prestaties na afloop om te verifiëren dat het fine-tunen correct is toegepast.

Zoek je praktische oefeningen om fine-tunen te leren? Bekijk dan de cursus Fine-Tuning met Llama 3. 

Wat is FunctionGemma? 

FunctionGemma is een gespecialiseerde versie van Google’s Gemma 3 270M open model, specifiek ontworpen voor function calling en tooluse in plaats van algemene conversatie.

Het gebruikt dezelfde architectuur als Gemma 3, maar heeft een speciaal formaat en trainingsfocus waarmee het gestructureerde outputs kan genereren die function calls representeren.

FunctionGemma wordt uitgebracht als een basismodel dat ontwikkelaars kunnen fine-tunen voor specifieke use-cases. Door de grootte en het ontwerp is het lichtgewicht, efficiënt en inzetbaar op apparaten met beperkte middelen, zoals laptops en edge-hardware.

Waarom FunctionGemma fine-tunen?

Hoewel FunctionGemma is getraind voor function calling, presteren modellen van deze omvang het best wanneer ze worden gespecialiseerd via fine-tuning op taak-specifieke data. 

Fine-tunen helpt het model stabiele patronen te leren voor het kiezen van de juiste functie uit een set tools en het correct formatteren van de bijbehorende argumenten voor realistische use-cases. 

Dit leidt tot meer consistente en voorspelbare gestructureerde outputs, wat op zijn beurt de betrouwbaarheid verbetert in praktische workflows.

FunctionGemma fine-tunen: stapsgewijze handleiding

Laten we de stappen doorlopen die nodig zijn om FunctionGemma te fine-tunen. 

1. Een Kaggle-omgeving opzetten

Begin op Kaggle en maak een nieuw notebook aan. Zodra het notebook geopend is, zoek je in het rechterpaneel naar Session options en stel je de Accelerator in op GPU (T4 ×2). 

Dit schakelt GPU-versnelling in voor de sessie en stelt je in staat het model te fine-tunen met de gratis rekencapaciteit van Kaggle.

GPU selecteren voor een Kaggle-sessie.

Nadat je de GPU hebt ingeschakeld, maak je een nieuwe codecel aan en voer je het volgende commando uit om alle vereiste Python-dependencies te installeren. Het kan even duren voordat de kernel start; daarna worden de pakketten automatisch geïnstalleerd.

%pip -q install -U datasets accelerate trl kagglehub sentencepiece huggingface_hub tqdm evaluate jiwer

Voeg vervolgens je Hugging Face-toegangstoken op een veilige manier toe. 

Open in het bovenste menu Add-ons → Secrets en klik vervolgens op Add Secret. Stel de naam van de secret in op HUGGINGFACE_TOKEN en plak je Hugging Face API-sleutel als waarde. 

Kaggle Secrets gebruiken is veiliger dan omgevingsvariabelen of tokens rechtstreeks in het notebook hardcoden.

De Hugging Face-secret instellen in Kaggle.

Zodra de secret is opgeslagen, geeft Kaggle een snippet om deze programmatisch op te halen. 

Gebruik de volgende code om je aan te melden bij de Hugging Face Hub:

from kaggle_secrets import UserSecretsClient
from huggingface_hub import login

hf_token = UserSecretsClient().get_secret("HUGGINGFACE_TOKEN")
if not hf_token:
    raise ValueError("HUGGINGFACE_TOKEN not found in Kaggle Secrets.")
login(token=hf_token)

Na een geslaagde authenticatie kun je toegang krijgen tot afgeschermde modellen en datasets, en je gefinetunede FunctionGemma-model uploaden naar de Hugging Face Hub om te delen en te hergebruiken.

2. De Hermes Reasoning Tool-Use-dataset laden

De Hermes Reasoning Tool-Use-dataset is een open Engelstalige dataset in een tooluse-reasoningformaat met gestructureerde voorbeelden, ontworpen voor het trainen en evalueren van modellen op toolselectie- en JSON-achtige function-calling-taken. 

De dataset bevat tienduizenden tooluse-reasoningvoorbeelden met natuurlijke taalprompts en toolschemas in een formaat dat geschikt is voor supervised fine-tuning.

In deze tutorial gebruiken we een subset van de dataset voor snellere experimenten. 

Definieer eerst een willekeurige seed en het totale aantal voorbeelden dat je wilt gebruiken. Splits de data daarna in trainings- en evaluatiesets:

from datasets import load_dataset

SEED = 40
N_TOTAL = 3000
N_EVAL  = 300
N_TRAIN = N_TOTAL - N_EVAL

Laad vervolgens de dataset, shuffle deze en maak de train- en eval-subsets:

raw = load_dataset("interstellarninja/hermes_reasoning_tool_use", split="train")
raw = raw.shuffle(seed=SEED).select(range(N_TOTAL))
train_ds = raw.select(range(N_TRAIN))
eval_ds  = raw.select(range(N_TRAIN, N_TOTAL))

print("train:", len(train_ds), "eval:", len(eval_ds))

De dataset downloaden van Hugging Face

Dit levert 2.700 trainingsvoorbeelden en 300 evaluatievoorbeelden op, voldoende om fine-tuning te demonstreren terwijl de rekenvereisten beheersbaar blijven.

train: 2700 eval: 300

3. Het FunctionGemma-model en de processor laden

In deze tutorial gebruiken we KaggleHub om het FunctionGemma-model rechtstreeks in het Kaggle-notebook te laden. 

KaggleHub bepaalt automatisch de modellocatie en maakt het model beschikbaar in de notebookomgeving, zodat je het niet handmatig van Hugging Face hoeft te downloaden.

import kagglehub
from transformers import AutoProcessor, AutoModelForCausalLM

model_path = kagglehub.model_download("google/functiongemma/transformers/functiongemma-270m-it")

Na het uitvoeren van de cel hierboven verschijnen de modelbestanden in de sectie Input van het Kaggle-notebook. Dat bevestigt dat het model succesvol is geladen en lokaal beschikbaar is.

Toegang tot het functiongemma-model met de Kaggle Hub-bibliotheek.

Zodra het model beschikbaar is, laden we zowel de processor als het model in het GPU-geheugen. 

De processor behandelt chattemplates en toolschemaformattering, terwijl het model verantwoordelijk is voor het genereren van gestructureerde function calls. 

Met device_map="auto" plaats je het model op de beschikbare GPU, en dtype="auto" kiest een efficiënte precisie die door de hardware wordt ondersteund.

processor = AutoProcessor.from_pretrained(model_path, device_map="auto")
model = AutoModelForCausalLM.from_pretrained(model_path, dtype="auto", device_map="auto")
tokenizer = processor.tokenizer if hasattr(processor, "tokenizer") else processor

print("dtype:", model.dtype, "| device:", model.device)

Dit bevestigt dat FunctionGemma correct is geladen, op de GPU staat en klaar is voor evaluatie en fine-tunen.

dtype: torch.bfloat16 | device: cuda:1

4. Tooldefinities normaliseren en gold function calls extraheren

Voordat we FunctionGemma fine-tunen, moeten we standaardiseren hoe tools worden weergegeven en de juiste (gold) function calls betrouwbaar uit de dataset halen. 

De Hermes Reasoning Tool-Use-dataset kan tooldefinities in meerdere formaten opslaan, dus deze stap zet alles om naar een consistente structuur die we kunnen gebruiken voor supervised fine-tuning.

Tools-veld normaliseren

We beginnen met het normaliseren van het veld tools. In de dataset kan tools voorkomen als een Python-lijst, een JSON-string of volledig ontbreken. 

De hulpfunctie hieronder zet alle geldige representaties om in een nette Python-lijst en negeert veilig misvormde of lege items.

import re, json

def normalize_tools_field(tools):
    if tools is None:
        return []
    if isinstance(tools, list):
        return tools
    if isinstance(tools, str):
        s = tools.strip()
        if not s:
            return []
        try:
            parsed = json.loads(s)
            return parsed if isinstance(parsed, list) else []
        except Exception:
            return []
    return []

Parametertypes normaliseren

Vervolgens normaliseren we parametertypes. Hermes-tooldefinities gebruiken vaak informele typeannotaties zoals str,int of containertypes zoals List[str]. De onderstaande functie zet deze om naar geldige JSON Schema-typen die compatibel zijn met Hugging Face function calling.

def _parse_hermes_type(t) -> dict:
    if t is None:
        return {"type": "string"}
    if isinstance(t, dict):
        return t if "type" in t else {"type": "object"}
    if isinstance(t, list):
        return {"type": "array"}
    if not isinstance(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"}

Hermes-stijl tool omzetten naar Hugging Face-functionschemas

Met deze utilities zetten we Hermes-achtige tooldefinities om naar Hugging Face-{"type": "function", "function": {...}}-schemas. Deze functie ondersteunt twee formaten:

  • Hermes per-argument-stijl (waarbij elke parameter een eigen kleine specificatie heeft)
  • Tools die al op JSON Schema lijken

Voor het per-argument-formaat behandelt deze implementatie de opgesomde parameters als verplicht, tenzij er expliciet een required-lijst is opgegeven.

def hermes_tools_to_hf_schema(tools_field):
    """
    Handles both:
    - Hermes per-arg style: {"parameters": {"x":{"type":"str"}, ...}}
    - Already-JSON-schema style: {"parameters":{"type":"object","properties":...,"required":[...]}}
    """
    hermes_tools = normalize_tools_field(tools_field)
    out = []

    for tool in hermes_tools:
        if not isinstance(tool, dict):
            continue

        name = tool.get("name")
        desc = tool.get("description", "")
        params = tool.get("parameters", {}) or {}

        # If params already look like JSON schema (best case)
        if isinstance(params, dict) and "type" in params and "properties" in params:
            json_schema_params = params
            if "required" not in json_schema_params:
                json_schema_params["required"] = []
        else:
            props, req = {}, []
            if isinstance(params, dict):
                for p_name, p_spec in params.items():
                    p_spec = p_spec or {}
                    if isinstance(p_spec, dict):
                        p_desc = p_spec.get("description", "")
                        p_type = p_spec.get("type", "str")
                    else:
                        p_desc, p_type = "", "str"

                    frag = dict(_parse_hermes_type(p_type))
                    if p_desc:
                        frag["description"] = p_desc
                    props[p_name] = frag
                    req.append(p_name)

            json_schema_params = {"type": "object", "properties": props, "required": req}

        out.append({
            "type": "function",
            "function": {
                "name": name,
                "description": desc,
                "parameters": json_schema_params,
            }
        })

    return out

Hulpfunctie

We definiëren een kleine helper om de geconverteerde toolschemas voor elk datasetvoorbeeld op te halen:

def get_tools_hf(ex):
    return hermes_tools_to_hf_schema(ex.get("tools"))

De gold function call extraheren

Daarna halen we de gold-toolcall uit de conversatie. Hermes-conversaties kunnen meerdere beurten bevatten, dus we parsen <tool_call>{...}</tool_call>-blokken en extraheren het eerste geldige toolcall-object (naam + argumenten). Zo blijft de supervisie eenvoudig en consistent.

TOOL_CALL_RE = re.compile(r"<tool_call>\s*(\{.*?\})\s*</tool_call>", re.DOTALL)

def extract_first_tool_call_obj(text: str):
    if not text:
        return None
    m = TOOL_CALL_RE.search(text)
    if not m:
        return None
    try:
        obj = json.loads(m.group(1))
        if isinstance(obj, dict) and "name" in obj and "arguments" in obj:
            return obj
    except Exception:
        return None
    return None

De toolcall afstemmen op de gebruikersopdracht

Om de toolcall af te stemmen op de juiste gebruikersopdracht scannen we de conversatiegeschiedenis, zoeken we het gebruikersbericht dat overeenkomt met de dataset-task en nemen we vervolgens het assistentantwoord er direct na. Als dat antwoord een toolcall bevat, geven we die terug als gold-label.

def _role(turn):
    return (turn.get("from") or "").lower().strip()

def get_gold_tool_call_task_aligned(ex):
    task = (ex.get("task") or "").strip()
    conv = ex.get("conversations") or []
    if not conv:
        return None

    idx = None
    if task:
        for i, t in enumerate(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
                    break
    if idx is None:
        for i in range(len(conv)-1, -1, -1):
            if _role(conv[i]) in ["human", "user"]:
                idx = i
                break
    if idx is None:
        return None

    for j in range(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"]:
            break
    return None

Aan het einde van deze stap heeft elk bruikbaar datasetvoorbeeld:

  • Een genormaliseerde set toolschemas (Hugging Face-formaat)
  • Een schone, taak-uitgelijnde gold function call
  • Een consistente structuur die klaar is om supervised fine-tuning-samples van te maken.

6. Supervised fine-tuning-samples bouwen voor function calling

Nu moeten we die omzetten naar Supervised Fine-Tuning (SFT)-trainingssamples in precies het formaat dat FunctionGemma verwacht, met gebruik van de bovenstaande hulpfuncties. 

Het doel is om voorbeelden te maken waarbij de input bevat:

  • Een developer-bericht dat function calling activeert
  • De gebruikersopdracht
  • De tooldeclaraties (schemas)

En de target is één enkele, gestructureerde FunctionGemma-call in dit formaat:

<start_function_call>call:TOOL_NAME{args:<escape>{...}<escape>}<end_function_call>

Stap 1: Schone, bruikbare rijen bouwen

We itereren eerst door de dataset en filteren voorbeelden weg die we niet kunnen gebruiken voor training.

Deze functie behoudt alleen voorbeelden waarbij:

  • Er een gold-toolcall bestaat
  • Er een geldige tools-lijst is
  • De gold-toolnaam in de toollijst voorkomt
  • De argumenten een geldige dictionary zijn
  • Als de tool geen vereiste velden heeft, worden de argumenten geforceerd naar {} om ruis of inconsistente targets te voorkomen
from datasets import Dataset

def build_simple_rows(ds, max_rows=None):
    rows = []
    for ex in ds:
        # 1) task-aligned gold tool call
        gold = get_gold_tool_call_task_aligned(ex)
        if not gold:
            continue

        # 2) tools -> HF schema
        hf_tools = get_tools_hf(ex)
        if not hf_tools:
            continue

        # 3) build required_map: tool_name -> required fields
        required_map = {}
        for t in hf_tools:
            if t.get("type") == "function":
                fn = t.get("function", {})
                name = fn.get("name")
                req = (fn.get("parameters", {}) or {}).get("required", []) or []
                if name:
                    required_map[name] = req

        # 4) guard: gold tool must be in tool list
        tool_names = set(required_map.keys())
        if gold["name"] not in tool_names:
            continue

        # 5) force {} when no required params
        gold_args = gold.get("arguments", {})
        if not isinstance(gold_args, dict):
            gold_args = {}

        req = required_map.get(gold["name"], [])
        if len(req) == 0:
            gold_args = {}  # key fix

        rows.append({
            "user_content": ex.get("task", ""),
            "tool_name": gold["name"],
            "tool_arguments": json.dumps(gold_args, ensure_ascii=False),
            "hf_tools": hf_tools,
        })

        if max_rows and len(rows) >= max_rows:
            break

    return rows

Nu genereren we trainings- en evaluatierijen:

simple_train = build_simple_rows(train_ds, max_rows=N_TRAIN)
simple_eval  = build_simple_rows(eval_ds,  max_rows=N_EVAL)
print("usable train:", len(simple_train), "usable eval:", len(simple_eval))

In ons geval houden we 961 trainingsvoorbeelden en 109 evaluatievoorbeelden over van de oorspronkelijke 3.000. Deze daling is te verwachten omdat de filtering streng is. Voorbeelden met ontbrekende toolcalls, ongeldige tooldefinities, niet-overeenkomende toolnamen of misvormde argumenten worden verwijderd.

usable train: 961 usable eval: 109

Deze opschoningsstap is heel belangrijk. Vooraf bevatte de dataset veel ruis en misuitgelijnde voorbeelden, wat leidde tot instabiel function-callinggedrag en matige resultaten, zelfs na fine-tunen. Na het terugbrengen tot alleen hoogwaardige, taak-uitgelijnde voorbeelden wordt FunctionGemma veel consistenter en verbeteren de fine-tuningresultaten aanzienlijk.

Stap 2: Elke rij omzetten in één trainingssample met tekst

Vervolgens formatteren we elke rij in de exacte prompt-plus-target-structuur die FunctionGemma nodig heeft. We gebruiken apply_chat_template van de processor om tooldeclaraties correct in te voegen en plakken daarna de gold function call als target eraan vast.

def format_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

Nu zetten we de rijen om naar Hugging Face Dataset-objecten:

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])

Stap 3: Zeker stellen dat elke sample een geldige string is

Sommige TRL-versies vereisen dat formatting_func een string teruggeeft, en training kan stuklopen als een sample een lijst of niet-stringwaarde wordt. Deze helper forceert text altijd naar een schone string.

# ensure text is always a string
def force_text_string(ds):
    def fix(ex):
        t = ex.get("text", "")
        if isinstance(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)

Print tot slot één sample om de formattering te bevestigen:

print(train_text_ds[0]["text"])

Je zou een volledig gerenderde prompt moeten zien met tooldeclaraties, gevolgd door een gold function call vergelijkbaar met:

<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>

7. FunctionGemma evalueren vóór fine-tunen

Voor de training moet je meten hoe goed het basis FunctionGemma-model presteert op function calling. Deze baseline helpt te bevestigen dat fine-tunen de toolselectie en function-call-formattering echt verbetert.

In deze sectie evalueren we twee zaken:

  • Toolnaam-nauwkeurigheid: of het model de juiste functienaam kiest
  • TC-CER (Tool-Call Character Error Rate): hoe dicht het gegenereerde function-call-blok bij het gold-call-blok ligt; lager is beter

We gebruiken twee reguliere expressies om de voorspelde toolnaam en het volledige function-call-blok uit de modeloutput te extraheren en vergelijken die met de gold-labels.

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)

def extract_tool_name(gen: str):
    gen = gen or ""
    m = FG_NAME_RE.search(gen)
    return m.group(1) if m else None

def extract_call_block(gen: str):
    gen = gen or ""
    m = FG_BLOCK_RE.search(gen)
    return m.group(0) if m else ""

def gold_call_block(r):
    return (
        f"<start_function_call>call:{r['tool_name']}"
        f"{{args:<escape>{r['tool_arguments']}<escape>}}"
        f"<end_function_call>"
    )

Baseline-evaluatielus

De onderstaande functie voert inferentie uit op een subset van de evaluatievoorbeelden en berekent beide metrics. We houden sampling uitgeschakeld (do_sample=False) zodat de resultaten deterministisch zijn en makkelijker te vergelijken voor en na het fine-tunen.

@torch.inference_mode()
def eval_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),
    }

Voer de baseline-evaluatie uit:

pre_metrics = eval_tool_and_cer(processor, model, simple_eval, n=50, max_new_tokens=128)
print("PRE metrics:", pre_metrics)

Zelfs vóór fine-tunen laat FunctionGemma sterke baselineprestaties zien op deze taak. 

Op een subset van 50 evaluatievoorbeelden behaalt het model 88% toolnaam-nauwkeurigheid, wat betekent dat het in de meeste gevallen de juiste functie kiest. 

Daarnaast is de Tool-Call Character Error Rate (TC-CER) ongeveer 33% voor het volledige function-call-blok. Dat geeft aan dat, hoewel vaak de juiste tool wordt gekozen, de gegenereerde argumenten en de formattering nog afwijken van de gold-target.

PRE metrics: {'n_eval': 50, 'tool_accuracy': 0.88, 'TC-CER (lower is better)': 0.33399307273626916}

Deze resultaten bevestigen dat het basis FunctionGemma-model de mechaniek van function calling al begrijpt. 

De relatief hoge character error rate onderstreept echter de noodzaak van fine-tunen om de volledigheid van argumenten, de consistentie van de formattering en de algehele structurele correctheid van de gegenereerde function calls te verbeteren.

Eén voorspelling inspecteren

Metrics zijn nuttig, maar het is ook belangrijk om naar kwalitatieve outputs te kijken. De helper hieronder voert inferentie uit voor één voorbeeld en print de voorspelde function call naast de gold-referentie.

import torch

@torch.inference_mode()
def infer_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),
    }

Voer het uit op één voorbeeld:

pre = infer_one(processor, model, simple_eval, idx=15)


print(f"""
TASK:
{pre['task']}

TOOL MATCH: {pre['tool_match']}

--- PREDICTED ---
{pre['predicted']}

--- GOLD ---
{pre['gold']}
""")

Output:

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>

In dit voorbeeld kiest het model correct de juiste functie, daarom is de tool match True. 

De gegenereerde call bevat echter extra argumenten en volgt niet exact de targetstructuur die in de training is gebruikt. 

Hoewel deze argumenten semantisch logisch zijn, komen ze niet overeen met het gold-formaat, wat bijdraagt aan een hogere character error rate. Dit illustreert waarom fine-tunen nodig is om de structurele consistentie en schema-compliance te verbeteren, niet alleen de toolselectie.

8. De modeltrainer instellen voor fine-tunen 

Nu we schone SFT-datasets hebben, is de volgende stap het configureren van de trainer die FunctionGemma gaat fine-tunen. We gebruiken TRL’s SFTTrainer, een lichtgewicht en betrouwbare manier om chatstijlsmodellen te fine-tunen op tekstvoorbeelden.

We stellen ook een outputmap in zodat Kaggle het gefinetunede checkpoint opslaat op een persistente locatie.

from trl import SFTConfig, SFTTrainer

OUT_DIR = "/kaggle/working/functiongemma-hermes-ft"

Om VRAM-gebruik tijdens training te verminderen, schakelen we gradient checkpointing in en zetten we de KV-cache uit.

# VRAM savings
model.gradient_checkpointing_enable()
model.config.use_cache = False

Trainerconfiguratie

We definiëren de trainingsconfiguratie met SFTConfig. Deze instellingen balanceren stabiliteit en efficiëntie op Kaggle-GPU’s en houden de trainingstijd kort.

cfg = SFTConfig(
    output_dir=OUT_DIR,
    max_length=512,
    packing=False,
    num_train_epochs=1,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    learning_rate=5e-5,
    lr_scheduler_type="cosine",
    warmup_ratio=0.03,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=10,
    report_to="none",
    fp16=(model.dtype == torch.float16),
    bf16=(model.dtype == torch.bfloat16),
    optim="adamw_torch_fused",
)

De formatting-functie zorgt ervoor dat elk trainingsvoorbeeld als één string wordt teruggegeven, wat vereist is door de huidige TRL-versie.

def formatting_func(example):
    # MUST return a STRING (not list) for your TRL version
    t = example.get("text", "")
    if isinstance(t, list):
        t = "\n".join(map(str, t))
    return str(t)

De trainer initialiseren

Tot slot initialiseren we de SFTTrainer met het model, de configuratie, de datasets en de tokenizer.

trainer = SFTTrainer(
    model=model,
    args=cfg,
    train_dataset=train_text_ds,
    eval_dataset=eval_text_ds,
    processing_class=tokenizer,
    formatting_func=formatting_func,
)

9. FunctionGemma fine-tunen

Met de geconfigureerde trainer kunnen we het model nu fine-tunen. Deze stap voert supervised training uit op de voorbereide dataset en evalueert het model periodiek tijdens de training.

trainer.train()
trainer.save_model(OUT_DIR)
processor.save_pretrained(OUT_DIR)
print("Saved to:", OUT_DIR)

Na afloop slaan we zowel het gefinetunede model als de processor op in de outputmap, zodat ze herbruikbaar zijn voor inferentie of upload naar de Hugging Face Hub.

Modeltraining en validatieverlies

Tijdens de training dalen zowel de training- als validatie-loss gestaag. De gemiddelde token-accuratesse verbetert ook in de tijd, wat aangeeft dat het model beter en consistenter gestructureerde function-call-outputs leert produceren.

Het gefinetunede FunctionGemma-model publiceren op Hugging Face

Zodra het fine-tunen klaar is, kunnen we het model publiceren zodat het eenvoudig kan worden hergebruikt, gedeeld of ingezet. Door het naar de Hugging Face Hub te pushen, kunnen anderen het direct laden via standaard Transformers-API’s.

HF_REPO_ID = "kingabzpro/functiongemma-hermes-3k-ft"
model.push_to_hub(HF_REPO_ID)
processor.push_to_hub(HF_REPO_ID)
print("Pushed to:", HF_REPO_ID)

Na het uploaden is het gefinetunede model publiek beschikbaar op Hugging Face:

Pushed to: kingabzpro/functiongemma-hermes-3k-ft

Gefinetunede model gepubliceerd op de Hugging Face Hub.

Bron: kingabzpro/functiongemma-hermes-3k-ft · Hugging Face

Evaluatie en foutanalyse na het fine-tunen

Na het fine-tunen laden we het opgeslagen model en de processor opnieuw vanaf schijf en draaien we dezelfde evaluatiepipeline als vóór de training. Zo is de vergelijking tussen pre- en post-fine-tuning eerlijk en consistent.

from transformers import AutoProcessor, AutoModelForCausalLM

ft_processor = AutoProcessor.from_pretrained(OUT_DIR, device_map="auto")
ft_model = AutoModelForCausalLM.from_pretrained(OUT_DIR, dtype="auto", device_map="auto")

Daarna evalueren we het gefinetunede model op de evaluatieset met toolnaam-nauwkeurigheid en TC-CER.

post_metrics = eval_tool_and_cer(ft_processor, ft_model, simple_eval, n=50, max_new_tokens=64)
print("POST metrics:", post_metrics)

De resultaten tonen een duidelijke verbetering ten opzichte van de baseline:

POST metrics: {'n_eval': 50, 'tool_accuracy': 0.98, 'TC-CER (lower is better)': 0.1454725383473528}

De toolnaam-nauwkeurigheid stijgt van 88 procent naar 98 procent, en de character error rate voor het volledige function-call-blok halveert ruim. Dit geeft aan dat het model niet alleen betrouwbaarder de juiste tool kiest, maar ook outputs produceert die nauwer aansluiten op het doelschema.

Om de verbeteringen beter te begrijpen, inspecteren we individuele voorspellingen. In het onderstaande voorbeeld kiest het model de juiste functie en produceert het een goed gestructureerde call met vollediger argumenten.

post = infer_one(ft_processor, ft_model, simple_eval, idx=15)

print(f"""
TASK:
{post['task']}

TOOL MATCH: {pre['tool_match']}

--- PREDICTED ---
{post['predicted']}

--- GOLD ---
{post['gold']}
""")

Hoewel de voorspelde call extra argumenten kan bevatten die verder gaan dan de gold-target, zijn de algehele structuur en argumentformattering aanzienlijk consistenter dan vóór het fine-tunen. Dat weerspiegelt een beter schema-bewustzijn en sterkere afstemming tussen gebruikersintentie en gegenereerde function calls.

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>

Laten we nog een voorbeeld bekijken.

post = infer_one(ft_processor, ft_model, simple_eval, idx=25)

print(f"""
TASK:
{post['task']}

TOOL MATCH: {pre['tool_match']}

--- PREDICTED ---
{post['predicted']}

--- GOLD ---
{post['gold']}
""")

Een tweede voorbeeld toont een schone en correcte function call voor een URL-extractietaak, wat laat zien dat het gefinetunede model goed generaliseert over verschillende tools.

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>

Al met al bevestigen deze resultaten dat fine-tunen zowel de nauwkeurigheid als de structurele correctheid voor function-calling-taken verbetert.

Het volledige Kaggle-notebook bij deze tutorial vind je op:https://www.kaggle.com/code/kingabzpro/finetuning-functiongemma

Loop je ergens tegenaan? Clone dan het notebook, voeg je Hugging Face-secret toe en draai het end-to-end.

Tot slot

Als je probeert FunctionGemma te trainen op generieke tekstgeneratiedatasets, kun je daar beter vroegtijdig mee stoppen. FunctionGemma is niet ontworpen om zich te gedragen als een algemeen doel-LLM. 

Voor open-ended tekstgeneratie is de Gemma 3-modelfamilie een betere keuze. 

FunctionGemma is purpose-built voor function calling, waar het doel is om de juiste tool te selecteren, schema-conforme argumenten te produceren en betrouwbaar te interageren met externe systemen zoals API’s, databases en services.

De kleine omvang van FunctionGemma is een bewuste ontwerpkeuze. Met 270 miljoen parameters is het geoptimaliseerd voor low-latency, resource-efficiënte deployments, waaronder lokale machines, edge-devices en private infrastructuur. Daardoor is het zeer geschikt voor realtime function calling en agentische workflows, waar correctheid en structuur zwaarder wegen dan vloeiende tekst.

In deze tutorial hebben we geleerd hoe je FunctionGemma fine-tunet op de Hermes Reasoning Tool-Use-dataset. Een belangrijke les is dat datavoorbereiding en evaluatie zwaarder wegen dan pure trainingstijd. 

Door de dataset zorgvuldig op te schonen, taken uit te lijnen met gold-toolcalls en strikte schemaconsistentie af te dwingen, konden we het modelgedrag drastisch verbeteren met een relatief klein aantal hoogwaardige samples.

De resultaten laten duidelijk de impact van fine-tunen zien. De toolnaam-nauwkeurigheid steeg van 88 procent naar 98 procent en de character error rate voor volledige function-call-blokken werd meer dan gehalveerd. Belangrijker nog, het model werd veel consistenter in het produceren van goed gestructureerde, voorspelbare function calls die aansluiten bij gebruik in de praktijk.

FunctionGemma FAQ's

Wat is FunctionGemma en waarin verschilt het van Gemma 3?

FunctionGemma is een gespecialiseerde versie van het open model Gemma 3 270M. Waar standaard Gemma 3 is ontworpen voor tekstgeneratie, is FunctionGemma expliciet getraind om natuurlijke taal te vertalen naar gestructureerde function calls (API-verzoeken). Het is daarmee een dedicated "router" voor agentische workflows in plaats van een conversatie-chatbot.

hat hardware do I need to run FunctionGemma?

Omdat het een model met 270M parameters is, is FunctionGemma bijzonder licht (~300MB gequantiseerd). Het kan efficiënt draaien op edge-apparaten zoals laptops, mobiele telefoons en embedded hardware (bijv. NVIDIA Jetson Nano) zonder een dedicated cloud-GPU nodig te hebben.

Waarom moet ik FunctionGemma fine-tunen als het al gespecialiseerd is?

Hoewel het basismodel het concept van tooluse begrijpt, is fine-tunen nodig om jouw specifieke API-schemas en bedrijfslogica aan te leren. Volgens Google’s benchmarks kan fine-tunen de nauwkeurigheid van function calling verhogen van ~58% (basis) naar meer dan 85% op domeinspecifieke taken.

Kan FunctionGemma multi-turn conversaties aan?

FunctionGemma is primair geoptimaliseerd voor single-turn commando-uitvoering (User Instruction → Tool Call). Voor complexe, multi-turn redenering of het bewaren van lange gespreksgeschiedenis gebruik je het model het best als een "verkeersregelaar" die moeilijke vragen delegeert aan een groter model (zoals Gemma 3 27B), terwijl het eenvoudige acties lokaal afhandelt.

Abid Ali Awan

Auteur

Abid Ali Awan

Als gecertificeerd data scientist haal ik met passie het maximale uit de nieuwste technologie om innovatieve machinelearning-toepassingen te bouwen. Met een sterke achtergrond in spraakherkenning, data-analyse en -rapportage, MLOps, conversationele AI en NLP heb ik mijn vaardigheden aangescherpt in het ontwikkelen van intelligente systemen die echt impact maken. Naast mijn technische expertise ben ik ook een sterke communicator met een talent om complexe concepten terug te brengen tot heldere, beknopte taal. Daardoor ben ik uitgegroeid tot een veelgelezen blogger over data science, waar ik mijn inzichten en ervaringen deel met een groeiende community van data-professionals. Op dit moment richt ik me op contentcreatie en redactie, waarbij ik met large language models werk aan krachtige en aansprekende content die zowel bedrijven als individuen helpt het beste uit hun data te halen.

Onderwerpen