Prerequisites
Install the cookbook
Install the Loops cookbook from PyPI into a uv project. The cookbook needs Python 3.12 or later, installs thebaseten-loops SDK, and provides the loops command. loops runs both recipes; each training config names its recipe with recipe = "rl" or recipe = "sft". Create a project first if you don’t have one:
examples/counting/train.toml, from your project’s directory. To change an example, copy its directory into your project at the same path. A config in your project wins over the installed one, and loops check prints the directory of the config it used.
Create a .env file in your project’s directory and set BASETEN_API_KEY to a workspace API key. The commands load .env from the training config’s directory or the directory you run them from. Never commit .env.
A run uses the serverless availability mode when serverless supports the model and the run’s LoRA rank and sequence length, and the dedicated mode otherwise. To choose a mode, add BASETEN_LOOPS_SCOPE=serverless or BASETEN_LOOPS_SCOPE=deployment to .env.
Build the dataset
examples/sft_toucan/make_data.py downloads part of Toucan and rewrites each conversation into the chat JSONL format described in Format conversations as chat JSONL. It splits by tool set, so the validation conversations use tool combinations the training data never saw. That keeps near-duplicate conversations from landing on both sides of the split.
make_data.py writes the dataset beside itself, and evaluate.py writes its results there too, so copy the example into your project first. To copy it, run the following command:
irrelevant conversations are requests no offered tool can serve, where the right answer calls no tool. They’re 15% of each split, so the model learns when not to call a tool as well as when to call one.
Check what trains
examples/sft_toucan/sft.toml sets the model, data, and schedule:
examples/sft_toucan/sft.toml
Config: in the report names your copy in examples/sft_toucan/, not the installed one. The rest reports what trains:
max_seq_len = 4096. The trained spans of the first row end with <|im_end|>, so each turn learns to stop.
Train
To train for one epoch, run the following command. With serverless Loops, Baseten runs the trainer and sampler and bills for the tokens you train, as long as serverless supports the model, the LoRA (low-rank adaptation) rank, and the sequence length. Otherwise, the run uses dedicated capacity, which bills for GPU time.run.json in the run directory holds the run ID. The last update’s sampler weights are always kept, as step-<N>.
The cookbook’s recorded run of this config took 125 updates, about 12 minutes of training once the trainer was up. val/nll fell from 0.914 before the first update to 0.795, mostly in the first 20 updates, and was flat over the last 20.
Compare the adapter with the base model
Validation loss shows the adapter fits the data. To check that it does the task better, sample both models on the validation conversations.examples/sft_toucan/evaluate.py takes each conversation’s first assistant turn, samples it greedily from the base model and from the adapter, and scores each reply against the data:
decision: calls a tool exactly when the data does.tools: calls the same tools.arguments: calls the same tools with the same arguments.well_formed: stops, and every call parses.
The data’s own replies score 98.5%
well_formed under Qwen3.6’s parser, so the adapter reached that ceiling. The largest change is on requests no tool can serve: the base model reaches for an unrelated tool anyway, and the adapter answers directly.
evaluate.py measures agreement with the data on first turns. It doesn’t run tools or score later turns. Before you ship a model trained this way, build an evaluation that covers your own tools and the rules your task needs. For more information, see Evaluate a model.
Resume a stopped run
If the run stops before its last update, continue it with--resume. The command finds the run of this config that trained most recently, and continues from the last checkpoint it saved:
max_steps counts the updates of the whole run, so the resumed run makes only the updates that are left. To check the resumed run first, run uv run loops check examples/sft_toucan/sft.toml --resume. If no run has trained, or the run saved no checkpoint, the command exits with code 2 and starts nothing.
Shut down the run
loops run leaves the Loops run active. On serverless, deactivation is optional because idle runs incur no token charges. On dedicated capacity, deactivate the run when you finish to stop GPU billing without waiting for automatic shutdown. To end the run and its paired sampler in either mode, run the following command with your run directory:
Deploy the adapter
The sampler checkpoint is ready to serve. The deployment loads the adapter on the base weights, downloads the base weights with thehf_access_token in workspace secrets, and bills for its GPU while it’s live. With the Baseten CLI installed, list the run’s checkpoints and deploy the one you want:
Next steps
- Deploy a checkpoint: Serve the adapter as an OpenAI-compatible endpoint.
- Prepare data for supervised fine-tuning: Build a dataset from your own traces.
- Train a tool-using agent with RL: Train on outcomes instead of fixed answers.