Skip to main content
JSONL, preference pairs, and RL environments out of graded rows. 7 public names. import whileai.simulations as wai, then wai.name.

export_dataset

Write training_rows as JSONL. Never overwrites the source. export_dataset is this same function object under the product name — export_dataset is export_training — not a second exporter. There is no behavioral difference to pick between: same arguments, same file, same report. export_dataset is the name to write in new code (it exports a dataset, not a training run); export_training is the older spelling and is kept so nothing written today breaks. With a path source and no output, writes <name>.train.jsonl next to it. validate=True refuses to write a dataset whose tool calls do not round-trip to structured arguments, or whose assistant turns quote the row’s own privileged block (the export scrubs the key, not the reply that recited it); pass validate=False to export anyway and read the report instead. The leak check reads the source before the scrub, so pass the SimulationData or its trajectories; rows that already came through rows(), save() or a file carry nothing to check, and report["privileged_leaks"] says so. format="openai" (the default) writes the OpenAI chat-completions wire row: the full messages list, function.arguments as a JSON string, and the ask carried alongside as prompt. format="trl" writes what trl can actually load: conversational SFT rows (messages only, arguments as dicts, the ask under prompt_text). TRL decides “is this conversational?” from the column set, so a prompt string beside messages makes it skip the chat template without an error and train on the bare ask — which is why the TRL rows do not carry one. The report says which format and which argument encoding the round-trip gate checked.

export_preference

Write chosen/rejected pairs as DPO-style JSONL. With format="openai" (the default) each line is \{"prompt": "&lt;the ask, as text>", "chosen": [...messages...], "rejected": [...messages...]\} in the same wire format as export_dataset: both sides are the whole conversation, prompt turns included, and tool-call arguments are JSON strings. With format="trl" each line is TRL’s conversational preference triple: prompt is the message list up to the first assistant turn and chosen/rejected are the completions only, with arguments as dicts. The default shape is not loadable by trl.data_utils.maybe_apply_chat_template — a prompt string with conversational sides raises TypeError: string indices must be integers — so pass format="trl" when a TRL trainer is the consumer. Pairs whose chosen or rejected side has no completion after the prompt prefix are dropped (no_completion_dropped). The roundtrip gate runs over BOTH sides and names the encoding it checked. Pairs come from ScoredData.select_for_preference() / build_preference_pairs. A pair judge_pairs marked tie carries no preference and is left out (ties_dropped in the report) unless drop_ties=False.

export_training

Write training_rows as JSONL. Never overwrites the source. export_dataset is this same function object under the product name — export_dataset is export_training — not a second exporter. There is no behavioral difference to pick between: same arguments, same file, same report. export_dataset is the name to write in new code (it exports a dataset, not a training run); export_training is the older spelling and is kept so nothing written today breaks. With a path source and no output, writes <name>.train.jsonl next to it. validate=True refuses to write a dataset whose tool calls do not round-trip to structured arguments, or whose assistant turns quote the row’s own privileged block (the export scrubs the key, not the reply that recited it); pass validate=False to export anyway and read the report instead. The leak check reads the source before the scrub, so pass the SimulationData or its trajectories; rows that already came through rows(), save() or a file carry nothing to check, and report["privileged_leaks"] says so. format="openai" (the default) writes the OpenAI chat-completions wire row: the full messages list, function.arguments as a JSON string, and the ask carried alongside as prompt. format="trl" writes what trl can actually load: conversational SFT rows (messages only, arguments as dicts, the ask under prompt_text). TRL decides “is this conversational?” from the column set, so a prompt string beside messages makes it skip the chat template without an error and train on the bare ask — which is why the TRL rows do not carry one. The report says which format and which argument encoding the round-trip gate checked.

loss_mask

One 0/1 per message: 1 carries loss, 0 is context only. "assistant" trains every assistant turn, the multi-turn default. "final" trains only the last assistant turn, for conversations whose earlier agent turns were scripted or came from another policy (rlhf-book ch. 4 “Implementation Details” describes both). System, user, and tool messages are always 0: tool output is the environment speaking, not the policy, and training on it teaches the model to invent tool results (ch. 13).

to_trl

Rows from training_rows / export_preference in TRL’s shape. kind="training" reshapes SFT rows, kind="preference" DPO pairs; see the module docstring for what each shape is and why it differs from the default OpenAI wire rows. Equivalent to passing format="trl" to the exporters, for callers that already hold rows. Preference pairs whose chosen or rejected side has no completion after the prompt prefix are dropped.

tool_call_roundtrip

Check every tool call in exported rows carries structured arguments. Guards the training run, not the export. Which check that is depends on where the rows are going, so the report names the encoding it validated:
  • format="openai" (encoding: "json_string"): arguments must parse back to a dict. Arguments that survive as un-parseable strings get re-quoted by chat templates and teach the model to emit string-wrapped arguments, which then spiral on tool rejections.
  • format="trl" (encoding: "dict"): arguments must already be dicts. A JSON string here is valid OpenAI wire and still wrong for a chat template, which would render it quoted twice, so it counts as invalid rather than passing on a technicality.

training_rows

Rows a trainer can consume directly. See the module docstring. source is a SimulationData (system prompt and tools come from its profile), a row list, or a JSONL path. For lists and paths, pass system_prompt= and tools= explicitly; a row exported without its policy trains an agent that never saw its rules. mask_mode picks which assistant turns carry loss (see loss_mask). strip_think=True (the default) removes <think> blocks from the assistant turns. On a reasoning base such as Qwen3 that teaches the adapter to emit an empty <think></think> and answer at once, so at eval it answers while the untrained base is still reasoning under the same max_tokens (#297). Pass strip_think=False when the student should keep reasoning, and set thinking= the same on both arms of the eval either way. unroll=True turns an N-turn conversation into N samples, the k-th ending at the k-th assistant turn with loss on that turn only (rlhf-book ch. 4, multi-turn masking). Every earlier agent turn then trains once with exactly the context it had, instead of only the last one (mask_mode="final") or all of them at once ("assistant", where later turns see context the policy never produced). Each sample carries unroll (turn, turns) and lineage.unrolled_from (the source row’s prompt hash and rollout index); mask_mode is ignored, and no group fields are stamped, since samples of one conversation are not a GRPO group. max_tool_output_chars caps each tool message at that many characters, appending [... N chars of tool output truncated] and counting the cut on the row as tool_output_truncated (messages) and tool_output_chars_cut. Tool output is masked from the loss anyway; what it costs is context, and the cut is explicit rather than silent (rlhf-book ch. 13). None cuts nothing.