Write down what a good answer is
Turn quality into rules a reviewer can apply to one output. Write each rule as a trigger and a required behavior, not an adjective:
Include output-format requirements in these rules. For JSON, check that the reply parses, required fields have the right types, and enum values are allowed. For tool calls, check the function name and argument schema. For prose, check requirements such as sections or length. Score format and task correctness separately: a reply can follow the schema and still give the wrong answer.
Use structured outputs when you need to constrain JSON generation, then evaluate the content against your task’s rules.
Collect rules from material that already encodes them:
- System prompts and docs: every must or must-not instruction is a rule someone wrote down.
- Templates and schemas: required sections, field formats, and enum values.
- Production outputs: choices the outputs make consistently. These describe what production does, which isn’t necessarily correct.
- Escalations and corrections: complaints and “don’t do this again” notes. A rule broken once in an escalation can outrank a pattern seen in hundreds of rows.
Freeze a held-out set
Hold back examples that never enter training data or prompt examples, and score every model on that same set. Split with a fixed seed so the set doesn’t change between runs, and split by group so related rows stay together. A group is whatever makes rows near-duplicates: the same customer, document, or tool set. If one group lands on both sides, the evaluation measures recall of training data instead of the task. Store the evaluation records in JSON Lines (JSONL), a UTF-8 text format with one complete JSON value per line. This script expects one JSON object per line. Keep the object on one line, without a surrounding array or commas between records. Don’t insert blank lines. Represent line breaks inside strings as\n.
string
required
Identifier for the customer, document, or tool set that related records share. Records with the same value stay in the same split. The script preserves every other field in the record.
data.jsonl. The two records from customer-001 must stay together. prompt and reference are example fields for an evaluation record.
data.jsonl
split.py. It reads data.jsonl, assigns about 10% of the groups to the held-out set with a fixed seed, and writes train.jsonl and holdout.jsonl. Groups can have different sizes, so the fraction of held-out rows can differ from 10%.
split.py
data.jsonl:
Output
holdout.jsonl with your results. A score is comparable to another only when both came from the same file. While you design the evaluation, iterate on rows from the training side. Score the held-out set when you compare finished candidates, so the design isn’t tuned to the rows that later decide the release.
Score with code first
Use code for any rule a person wouldn’t need to reason about: valid JSON, a required field, a length limit, an exact label, the tool a model called. Code is free to run, gives the same answer every time, and can’t be argued with. For the tool-calling assistant, four checks compare the model’s first reply with the reference reply:decision: the model calls a tool exactly when the reference does.tools: it calls the same tools.arguments: it calls the same tools with the same arguments.well_formed: the reply finished and every tool call parsed.
checks.py
decision on requests no tool can serve and lowers it everywhere else.
Judge what code can’t decide
Use an LLM judge only for rules that need reading and reasoning, such as whether a direct answer addresses the request. Design each judged rule as its own axis:- One prompt per axis. A prompt that scores several rules at once lets one obvious failure bleed into unrelated scores.
- Pass or fail by default. A binary verdict varies less between repeated judgments than a 1 to 10 scale. If you use a scale, set the passing threshold yourself rather than asking the judge whether an output passes.
- Reasoning before the verdict. Ask for the judge’s reasoning and quoted evidence first, then the verdict, so the verdict follows from the evidence.
- As few axes as the rules need. Two axes that never disagree on the same rows measure one thing. Each extra axis is another judge call per row.
- Data boundary first: if the task’s data can’t leave your infrastructure, the judge must be a model you host, such as one deployed on Baseten.
- A different model family from the one under test: models tend to prefer outputs that resemble their own.
- Cost at your data size: a judge that scores every training row as well as the held-out set is called many times. A cheaper judge that passes the checks below is the better choice.
reasoning and evidence before verdict, so the model writes its evidence before it decides:
judge.py
judge() only on rows where the code checks found a direct answer, and score each other judged axis with its own rule and call. Kimi K3 reasons before it answers by default; to lower the cost of each call, set a lower reasoning_effort.
Check the judge before you trust it
A judge is a measuring instrument. Measure it before it decides anything:- Three known cases. On real rows, confirm a known-good output passes, a known-bad output fails, and an empty or errored output neither crashes your parser nor scores as a pass.
- Stability. Judge the same outputs several times and compute agreement per axis. Report a chance-corrected statistic such as Cohen’s kappa. On an axis where 95% of outputs pass, a judge that always says pass reaches 95% raw agreement and a kappa near 0.
- Calibration. Score outputs a person verified as good, and outputs with violations you planted on purpose. A judge that fails verified outputs is miscalibrated; a judge that misses planted violations is too lenient to gate a release or filter training data.
Score a baseline
Before you train, score models you already have access to: the model that serves the task today, the base model you plan to train, and the strongest model you can call. Score them with the same prompts, tools, and sampling settings you use for every later run. To score an open model without training it, give it an inference endpoint. Model APIs serve a curated set of models behind an OpenAI-compatible API, with nothing to deploy. Dedicated Inference deploys any open model on hardware you choose, for example from a model recipe. The comparison script takes any OpenAI-compatible base URL, so for a Model API, usehttps://inference.baseten.co/v1 and the model’s slug.
If an open model already meets your bar, you don’t need to post-train. See Decide whether to post-train.
Read the spread before you read the ranking. If clearly different models all land within the judge’s run-to-run variation, the evaluation is saturated: it can’t show a training gain, so make the task harder or the rules sharper first. If a smaller model beats a larger one, read the rows where it won before you decide whether the evaluation or your expectation is wrong.
Compare a checkpoint with its baseline
Deploy the checkpoint, then send the held-out prompts to both deployments with the same request settings. Scoring through the deployment measures what you would serve, including its chat template and tool handling. To score two OpenAI-compatible deployments on the held-out set, configure the endpoints and run the script. The model and deployment IDs come from each deployment, andmodel is the served name: for a deployed Loops checkpoint, the checkpoint name such as step-124.
compare.py
Break each check down by the slices that matter to your task before you decide. In that run, the largest change was on requests no offered tool could serve:
decision rose from 73.3% to 100% on those 30 conversations, because the base model called an unrelated tool and the adapter answered directly. Read the worst regressions row by row as well as the averages.
Release thresholds are a product decision. Set them from what the task needs, such as “no check drops by more than one point and decision on unservable requests is at least 98%”, not from the baseline’s numbers. Keep the held-out file, the checks, the judge prompts, and the judge model fixed across candidates; changing any of them makes earlier scores incomparable.
Next steps
- Prepare SFT data: Use the same rules to choose training examples.
- Check whether an RL run is learning: Read reward and rollout metrics during training.
- Deploy a checkpoint: Serve the checkpoint you evaluate.