Choose targets you trust
Every assistant turn you train on is a target. Know where each one came from, because the source sets the ceiling:- Production outputs: the model learns to reproduce what production does today, mistakes included. Training on them distills the current system; it doesn’t improve on it. Filter them with your evaluation rules first.
- Human-written or human-corrected outputs: the strongest targets, and the most expensive. A verified slice is also what you calibrate an LLM judge against.
- Outputs from a stronger model: a common way to train a smaller, cheaper model on a task. Score them with your evaluation and keep only the ones that pass.
- Public datasets: useful for general skills such as tool use. Check that the format and behavior match what you serve.
Check the label balance
For classification and extraction tasks, count the distinct target values before training. When one target dominates, typically “no issue” or an empty extraction, the cheapest way to lower the loss is to output it every time. The training loss looks excellent and the model flags nothing. Rebalance at the source by oversampling the rarer targets, and evaluate with per-class false-positive and false-negative rates rather than overall accuracy. On a dataset where 95% of rows are “no issue,” a model that never flags anything is 95% accurate.Format conversations as chat JSONL
Write one conversation per line in OpenAI’s chat format. OpenAI fine-tuning files, Hugging Face conversational datasets, and most trace exports from tools such as LangSmith and Langfuse already use it or convert in a few lines:train.jsonl
toolslists the tools offered in that conversation, in OpenAI’s function format. Rows can offer different tools.tool_callsandtoolmessages follow OpenAI’s format. Answer every call exactly once.weighton an assistant message sets whether that turn trains:1trains it,0keeps it as context only.reasoning_contenton an assistant message carries that turn’s reasoning, when your data has it.
messages and tools columns, run the following script. Reshape the columns inside the loop when your dataset names them differently:
make_data.py
Decide which turns train
User, system, and tool-result messages are never targets. Among assistant turns, choose deliberately, because the choice changes what the model learns:
When a conversation repeats a mistake that a later turn corrects, train the correction and set
weight to 0 on the mistake.
Match how you serve
Train on the exact inputs the model sees in production. The system prompt, the tool definitions, and the chat template settings in training must match the deployment. A model trained with one system prompt and served with another is being evaluated on a task it didn’t train on. Treat reasoning as a setting to test, not a default. Reasoning tends to help with multi-step planning, tool ordering, and calculation. It tends to cost latency and tokens without benefit on direct extraction, classification, and formatting. Score the base model both ways on your evaluation before you choose. Turning reasoning on in the template doesn’t create reasoning supervision. If your data has no reasoning, train with thinking off, for exampleenable_thinking = false for Qwen3.6. With thinking on, the model learns to write an empty reasoning block. Don’t generate reasoning text after the fact to fill the gap: the model learns to imitate traces that never produced the answers.
Render every row before you train
Render the whole dataset with the model’s chat template before you provision a trainer. Rendering finds rows that are too long, tool calls whose arguments don’t parse, and turns the template can’t end. It also shows exactly which tokens train. The Loops cookbook does this withloops check, which reads a training config, renders every row, and launches nothing:
Install the Loops cookbook from PyPI into a uv project. The cookbook needs Python 3.12 or later, installs the baseten-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 your workspace has serverless access to the model, and the dedicated mode otherwise. To choose a mode, add BASETEN_LOOPS_SCOPE=serverless or BASETEN_LOOPS_SCOPE=deployment to .env.
A minimal config names the recipe, the model, the data, and the training settings:
sft.toml
- The share of tokens that train. Here about a third of each conversation is assistant output. A share near 0% means the weights or roles are wrong; near 100% means user or tool text is being trained.
- Sequence lengths against
max_seq_len. Setmax_seq_lenabove the longest sequence. Rows over it are refused and listed, not truncated. - The trained text of the first row. Confirm the spans are the assistant turns you meant to train, that each ends with the model’s end-of-turn token, and that tool calls render in the model’s own format.
Qwen/Qwen3.6-35B-A3B trains in either mode; the model catalog shows which models do.
Next steps
- Fine-tune tool calling with SFT: Train on this data and evaluate the adapter against the base model.
- Train on a chat dataset: Write the SFT loop yourself with the Loops SDK.
- Evaluate a model: Score the result on your held-out set.