Skip to main content
7 public names. import whileai.simulations as wai, then wai.name.

export_dataset

Defined in whileai/simulations/export.py. Write training_rows as JSONL, gated so a broken row never reaches the trainer. Reach for it when graded rows are ready for SFT. It builds the rows with training_rows (system prompt in, tool schemas on, tool calls in the wire format, <think> blocks stripped), checks them, and writes one JSON object per line. It returns the report: path, n and n_written, rewards, with_system, with_tools, format, mask_mode, tool_call_roundtrip, privileged_leaks, warnings, and what was cut or unrolled. export_dataset and export_training are one function object under two names (export_dataset is export_training): same arguments, same file, same report. Write export_dataset in new code (it exports a dataset, not a training run); the older spelling is kept so nothing written today breaks.
  • source: a SimulationData (system prompt and tools come from its profile), a row list, or a JSONL path; for the last two pass system_prompt and tools.
  • output: the file to write. With a path source and no output it writes <name>.train.jsonl next to it. It never overwrites the source.
  • validate (True): refuse 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); False exports anyway and leaves the report to read. 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. "trl" writes what trl can actually load: conversational SFT rows (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.
  • mask_mode with format="trl": the TRL rows carry no loss_mask, because trl 0.19.1’s SFTTrainer never reads one. Its collator (DataCollatorForLanguageModeling) labels every token and unlabels only from two token-level columns the trainer builds itself: completion_mask from prompt/completion rows, and assistant_masks from assistant_only_loss=True, which needs a {% generation %} block in the chat template (Qwen2.5-Instruct has none). So mask_mode="final" and unroll=True write prompt/completion rows (prompt up to the last assistant turn, that turn as the completion), and TRL trains on exactly what the mask asked. mask_mode="assistant" (the default) writes messages rows, and TRL trains on every token of them unless assistant_only_loss=True is set. The report’s mask_mode states which of the two TRL will do, and trained_messages / masked_messages count what TRL trains, not what the SDK intended; a format="trl" export on 94 rows was measured at 9x the tokens its loss_mask marked (#507).
  • push_to: a Hub repo ("me/my-set") to upload the written file to, with your own token: HF_TOKEN from the environment or the login hf auth login cached, through huggingface_hub (pip install 'whileai[hf]'). Private by default; no platform call. wai.hub.push is the same upload for a file, a directory or rows you already hold, with token= and private=. The report gains hub (repo_id, url, commit).
  • strip_think, mask_mode, unroll, max_tool_output_chars: passed through to training_rows, which explains each.

export_preference

Defined in whileai/simulations/export.py. 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="fireworks" each line is Fireworks’ one-turn DPO shape (input.messages is the prefix both sides share, then one assistant message each as preferred_output and non_preferred_output); pairs that lost later turns are counted as fireworks_turns_cut. With format="trl" each line is TRL’s conversational preference triple, the same one-turn preference Fireworks takes: prompt is the prefix both sides share (tool turns and later asks included) and chosen/rejected are each the one assistant turn where the sides diverge, with arguments as dicts. DPOTrainer masks the prompt and sums log-probabilities over every completion token, so a tool result or a user turn on a side would be scored as the policy’s own words; the turns after the preference are cut, and pairs that lost some are counted as trl_turns_cut. 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 with no one-turn contrast (the sides never differ, or diverge on a tool result) 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

Defined in whileai/simulations/export.py. Write training_rows as JSONL, gated so a broken row never reaches the trainer. Reach for it when graded rows are ready for SFT. It builds the rows with training_rows (system prompt in, tool schemas on, tool calls in the wire format, <think> blocks stripped), checks them, and writes one JSON object per line. It returns the report: path, n and n_written, rewards, with_system, with_tools, format, mask_mode, tool_call_roundtrip, privileged_leaks, warnings, and what was cut or unrolled. export_dataset and export_training are one function object under two names (export_dataset is export_training): same arguments, same file, same report. Write export_dataset in new code (it exports a dataset, not a training run); the older spelling is kept so nothing written today breaks.
  • source: a SimulationData (system prompt and tools come from its profile), a row list, or a JSONL path; for the last two pass system_prompt and tools.
  • output: the file to write. With a path source and no output it writes <name>.train.jsonl next to it. It never overwrites the source.
  • validate (True): refuse 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); False exports anyway and leaves the report to read. 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. "trl" writes what trl can actually load: conversational SFT rows (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.
  • mask_mode with format="trl": the TRL rows carry no loss_mask, because trl 0.19.1’s SFTTrainer never reads one. Its collator (DataCollatorForLanguageModeling) labels every token and unlabels only from two token-level columns the trainer builds itself: completion_mask from prompt/completion rows, and assistant_masks from assistant_only_loss=True, which needs a {% generation %} block in the chat template (Qwen2.5-Instruct has none). So mask_mode="final" and unroll=True write prompt/completion rows (prompt up to the last assistant turn, that turn as the completion), and TRL trains on exactly what the mask asked. mask_mode="assistant" (the default) writes messages rows, and TRL trains on every token of them unless assistant_only_loss=True is set. The report’s mask_mode states which of the two TRL will do, and trained_messages / masked_messages count what TRL trains, not what the SDK intended; a format="trl" export on 94 rows was measured at 9x the tokens its loss_mask marked (#507).
  • push_to: a Hub repo ("me/my-set") to upload the written file to, with your own token: HF_TOKEN from the environment or the login hf auth login cached, through huggingface_hub (pip install 'whileai[hf]'). Private by default; no platform call. wai.hub.push is the same upload for a file, a directory or rows you already hold, with token= and private=. The report gains hub (repo_id, url, commit).
  • strip_think, mask_mode, unroll, max_tool_output_chars: passed through to training_rows, which explains each.

loss_mask

Defined in whileai/simulations/export.py. 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 (Lambert 2025, chapter Instruction Tuning, 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 (Lambert 2025, chapter Tool Use).

to_trl

Defined in whileai/simulations/export.py. Rows from training_rows / export_preference in TRL’s shape. kind="training" reshapes SFT rows as conversational messages (no loss_mask: trl 0.19.1 trains on every token of them unless assistant_only_loss=True), kind="completion" as conversational prompt/completion with the last assistant turn as the completion (the mask_mode="final" mask, which the trainer honors through the completion_mask it builds), kind="preference" DPO pairs with the shared prefix as prompt and the one assistant turn where the sides diverge as each of chosen/rejected; 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. Completion rows with no assistant turn and preference pairs with no one-turn contrast (the sides never differ, or diverge on a tool result) are dropped; export_preference reports the counts.

tool_call_roundtrip

Defined in whileai/simulations/export.py. 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

Defined in whileai/simulations/export.py. Build the rows a trainer can consume directly: system prompt in, tool schemas on, wire format fixed. Reach for it when you want the rows in memory rather than in a file (export_dataset writes these same rows as JSONL, with the gates). A simulated row stores the conversation without the agent’s own system prompt or tool schemas; the run knows them, the row does not. This call returns a list of dicts, one per source row, each with messages (the system message prepended, tool calls in the OpenAI wire format with id, type, function.name and function.arguments as a JSON string, each tool result linked by tool_call_id), tools (the schemas), loss_mask (which assistant turns carry loss), the ask as prompt, and the row’s grade and lineage. The module docstring has the two wire shapes.
  • source: 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.
  • strip_think (True): remove <think> blocks from the assistant turns, so a thinking rollout model never teaches a non-thinking student to emit them. 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. Pass strip_think=False when the student should keep reasoning, and set thinking= the same on both arms of the eval either way.
  • mask_mode ("assistant"): which assistant turns carry loss (see loss_mask): all of them, or "final" for the last turn only.
  • unroll (False): 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 (Lambert 2025, chapter Instruction Tuning). 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 (None, cut nothing): cap 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 (Lambert 2025, chapter Tool Use).
Last modified on September 22, 2026