import whileai.simulations as wai, then wai.name.
otel
OTel GenAI spans to trajectory rows:simulate(traces=...) input.
rows_from_otel
prompt, tool spans in
time order become steps, later user turns become user steps, and
the last assistant output becomes final_text. reward_keys names
the span attributes a 0..1 reward is read from (REWARD_KEYS).
platform
While platform client: dataset upload, listing, download.agents
data.push(name, agent=...))
or a trace arrives with gen_ai.agent.name; register_agent is for
attaching the spec or a description ahead of that.
catalog
{"datasets": [card, ...], "agents": [...]}. No key needed.
cut
zeroproof.scenario_id.
kind="rl" keeps the prompts the agent passes some of the time and
not always (20% to 80% by default, RLHF book ch. 7); kind="sft"
keeps the best run of every prompt that ever passed. Either way the
prompts are split into a train set and a held-out set, so a prompt you
measure on is never a prompt you trained on.
since is the trace window ("all", "24h", "7d"); any
other keyword is a trace filter, the same ones the traces page has
(model=, tool=, evalSet=, measure=). band=(lo, hi)
moves the pass-rate window and holdout= the split fraction.
Returns the gate’s reply with the two sets pulled out::
made = wai.cut(agent=“my-agent”, kind=“rl”)
wai.pull(made[“train”][“datasetId”], “train.jsonl”)
made[“holdout”][“datasetId”] # measure on this, never train on it
cuts
prompts seen, rl (the ones worth training on, with the train and
holdout counts), sft, and more (prompts that need more runs).
Read it before cut() when you want to know what you would get.
format_cuts() prints it as the three lines the traces page shows.
datasets
delete_dataset
delete_empty_datasets
max_rows rows or
fewer when that is set (smoke runs). Permanent. Returns the ids.
format_cuts
cuts() says, as the sentence the traces page leads with.
The answer, the counts behind it, and the line to run next::
3 prompts are worth training on
9 runs · 3 train · none held out
next: wai.cut(agent=“my-agent”, kind=“rl”)
hf_publish
repo adds a second split. Every push is a
commit tagged zp-<dataset id> and the repo carries whileai.json
(split -> dataset, numbers, history). Defaults: your username and a
slug of the dataset name. With wait (the default) this returns the
finished state {"repo", "url", "commit", "tag", "split", ...};
otherwise the pushing stamp.
hf_publish_run
zp-<run id>.
hf_status
{"connected", "username", "namespaces", "scopes"}.
import_hf
wait (the default) the row is
ready (or this raises with the import error); otherwise it is
importing and wai.datasets() shows it settle.
issue_delegated_credential
clerk_token must be a valid Clerk session token or other authenticated
backend token. This helper sends that token as a bearer token to the auth
endpoint to mint the delegated credential.
preview
profile
force=True
recomputes.
publish
agent (a short name such as "airline-support").
The dataset must be finalized and hold rows. Returns the card. Anyone can
then pull it with no key.
pull
path and returns the path,
or returns the parsed rows when path is omitted. Public catalog
datasets need no key; your own need the usual one.
A dataset is stored as one or more parts, and the grant lists every one
of them. Datasets pushed with push_rows are a single part, which is
why reading only downloadUrl looked correct for so long; a dataset
filled by trace ingest is one part per trace, and that path returned the
first row of a 60-row dataset without saying so.
purge_agent
dry_run=True only counts.
Returns {"agent", "traces", "datasets", "deleted"}.
push_file
name defaults to the file name.
gate=True (default) parses the file, runs publish_gate (rows
get their calibration stamp; RL-shaped rows that are ungraded or
have no mixed group are refused), and uploads the stamped rows. The
report comes back as entry["gate"]. gate=False uploads the
bytes exactly as they are on disk.
push_rows
datasetId. Pass parent (a
ds_... id) when this dataset is an iteration of an existing one, so
lineage shows on the platform. purpose is what the set is for on the
Datasets page: "train" (the default), "holdout" or "eval";
mode is the simulation mode that made it, and is also recorded. gate=True runs publish_gate
first (calibration stamp; RL-shaped rows refused when ungraded or
without a mixed group) and returns its report as entry["gate"].
endorsed names what the reward should track for the gate’s
hack_scan; strict_hacks=True refuses a set whose reward is
best explained by something else.
SimulationData.push gates by default; this row-level entry point
does not, because the caller may already have run optimize. A
purpose="holdout" push warns when the set is too small to prove a
prove_effect gain (5 points) at 80% power.
refresh_delegated_credential
register_agent
send_score
cut() filters on::
wai.send_score(“4bf92f3577b34da6”, 1.0) # passed
wai.send_score(“4bf92f3577b34da6”, 0.0) # failed
wai.cut(agent=“my-agent”, kind=“rl”) # now there is something to cut
A run counts as a pass at 1.0 or above, which is what puts its prompt
in the 20-80% band cut(kind="rl") keeps. Send a 0-to-1 quality number
as the verdict and nothing will ever read as a pass; scale it, or send it
under its own name= and leave score for the verdict.
This is a call of its own because a judge answers after the run it is
judging has closed, and a human disagreeing with the judge answers a day
later. Re-sending the same name is a correction, not a duplicate::
wai.send_score(“4bf92f3577b34da6”, 0.82, name=“helpfulness”)
wai.send_score(scores=[{“traceId”: t, “value”: v} for t, v in graded])
Any other keyword rides along to every measurement sent: label=,
pass_at=, max=, description=, direction=, kind=.
Returns the gate’s reply — applied, plus unknown (trace ids this
account never sent) and rejected when a batch is partly good. Raises
when nothing landed at all, because a typo in a trace id is otherwise a
silent success.
unpublish
update_dataset
purpose moves it between the Train, Holdout, Eval and Raw sections of
the Training data page. Only the arguments you pass change.
traces
Production traces to a focused coverage grid. Simulation, version two.dimensions_from_traces
rows.
Starts from build_dimensions for this agent so every value is one
the writer and sandbox understand. The tool axis puts observed failing
tools first; broaden=False drops tools the traces never touched
(keeping the base specials such as unrelated), so a run spends its
budget near the flaws instead of boiling the ocean. Fault and world
axes always keep their clean value: contrast needs passing rows too.
fault_to_axis maps an observed fault chip to the axis value that
reproduces it (FAULT_TO_AXIS by default).
drop_leaky_rows
flaw_rows
simulate(traces=flaw_rows(evaluated)) so the next batch aims at
what the agent still gets wrong.
format_trace_report
infer_harness
leakage_report
threshold cosine
similarity to any source prompt is flagged (0.9 by default: the 8-gram
exact-overlap test of rlhfbook.com/c/16-evaluation.html with a small
paraphrase allowance). Exact matches always flag, whatever the embedder
thinks. leaky lists the first examples offenders; n_leaky is
the full count.
load_traces
source is a JSONL path or an iterable of dicts. Rows carrying
tool_trace/trace instead of steps, final/output/
response instead of final_text, or only OpenAI-style
messages are converted; reward is kept only when it coerces
cleanly to 0 or 1, and its absence is fine. Rows that are not dicts or
carry neither an ask nor any steps are dropped.
mine_traces
flaw_rows is any row with an observed fault or a 0 label. Those are
the behaviors worth simulating more of.
opening_share
messages field (kept by load_traces); rows
without messages count as user-opened. This is the evidence the
opening="auto" topology axis resolves against.
simulate_from_traces
simulate(agent, traces=...): same grid focus and
leakage gate, for callers who start from the traces.
With no agent, tools or policy, the tool surface is read from the traces
themselves, so handing over graded telemetry is enough to start.
split_pseudo_production
task_key
(the scenario_id, else the prompt) lands on the same side, so the
held-out slice is disjoint from the training side in the unit every
report groups by, not just prompt-disjoint. Splitting
by row is not enough — under mode="rl" with repeats=k each
prompt has k rows, and scattering siblings across the two sides trains
the student on every prompt it is then evaluated on.
Every unique flaw signature (fault name plus behavior shape) sends its
task to the production side first, so the held-out slice contains each
distinct failure at least once. fraction is still counted in rows,
but whole tasks are added, so the slice can overshoot it by up to the
size of one task. Deterministic in seed.
tools_from_traces
trace_report
simulate(traces=...). With tools (and optionally
policy) the report also computes the actual grid emphasis: which
axis values move forward in the coverage grid because of these traces.
Reward stays optional; ungraded counts are reported, never required.
advisory_labels counts rows carrying only a qwen_reward: those
labels do steer trace mining, so they are disclosed, not hidden under
“ungraded”. dropped counts input rows that carried no usable
signal and were discarded by normalization.