whileai 0.67__tar.gz → 0.69__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {whileai-0.67/whileai.egg-info → whileai-0.69}/PKG-INFO +98 -58
- {whileai-0.67 → whileai-0.69}/README.md +96 -57
- {whileai-0.67 → whileai-0.69}/pyproject.toml +7 -3
- {whileai-0.67 → whileai-0.69/whileai.egg-info}/PKG-INFO +98 -58
- {whileai-0.67 → whileai-0.69}/whileai.egg-info/requires.txt +1 -0
- {whileai-0.67 → whileai-0.69}/LICENSE +0 -0
- {whileai-0.67 → whileai-0.69}/MANIFEST.in +0 -0
- {whileai-0.67 → whileai-0.69}/setup.cfg +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/_env.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/auth.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/cli.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/ingest.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/init_evals.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/platform.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/py.typed +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/__main__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/data.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/environment.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/export.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/actionspace.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/adapters.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/agents.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/anthropic_backend.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/coverage.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/diversity.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/embeddings.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/explore.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/generator.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/offline_agent.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/scenarios.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/generate/usage_meter.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/ingest/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/ingest/otel.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/ingest/platform.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/ingest/traces.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/monitor.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/py.typed +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/run/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/run/config.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/run/engine.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/run/rows.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/run/spec.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/schema.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/schemas/row-v1.json +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/agreement.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/audit.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/checklist.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/curriculum.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/delta.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/grade_llm.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/grading.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/grounding.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/hack_scan.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/hygiene.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/judge_trust.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/judging.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/labels.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/llm_judge.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/logprobs.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/markers.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/optimize.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/pairwise.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/passat.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/preflight.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/privileged.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/publish_gate.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/quality.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/reference.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/rubric.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/spec.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/stage.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/stats.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/style.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/score/trace.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/simulation.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/text.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/training.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/base.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/code.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/math.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/structured.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/verify/text.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/world/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/simulations/world/sandbox.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/templates/__init__.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai/templates/evals.py +0 -0
- {whileai-0.67 → whileai-0.69}/whileai.egg-info/SOURCES.txt +0 -0
- {whileai-0.67 → whileai-0.69}/whileai.egg-info/dependency_links.txt +0 -0
- {whileai-0.67 → whileai-0.69}/whileai.egg-info/entry_points.txt +0 -0
- {whileai-0.67 → whileai-0.69}/whileai.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whileai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.69
|
|
4
4
|
Summary: Post-training data and evaluation for tool-using LLM agents: simulate, grade, measure, select, train, export, with the checks from rlhfbook.com.
|
|
5
5
|
Author: Jacob Weiss
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pydantic>=2.0
|
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
30
|
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-xdist>=3.6; extra == "dev"
|
|
31
32
|
Requires-Dist: ruff>=0.12; extra == "dev"
|
|
32
33
|
Requires-Dist: mypy>=1.16; extra == "dev"
|
|
33
34
|
Requires-Dist: types-requests; extra == "dev"
|
|
@@ -50,39 +51,50 @@ Dynamic: license-file
|
|
|
50
51
|
<a href="https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/whilehq/whileai-sdk/ci.yml?branch=main&label=ci&labelColor=0b1220&color=5cb08a" alt="CI"></a>
|
|
51
52
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/v/whileai?labelColor=0b1220&color=5cb08a" alt="PyPI"></a>
|
|
52
53
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/pyversions/whileai?labelColor=0b1220&color=3f8f6b" alt="Python"></a>
|
|
53
|
-
<a href="https://
|
|
54
|
+
<a href="https://pepy.tech/project/whileai"><img src="https://img.shields.io/pepy/dt/whileai?labelColor=0b1220&color=3f8f6b" alt="Downloads"></a>
|
|
54
55
|
<a href=".github/workflows/ci.yml"><img src="https://img.shields.io/badge/coverage-%E2%89%A5%2090%25%20gated-5cb08a?labelColor=0b1220" alt="Coverage gate"></a>
|
|
55
56
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-3f8f6b?labelColor=0b1220" alt="License"></a>
|
|
56
57
|
</p>
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
`whileai` makes training and eval data for agents that call tools. Give it
|
|
60
|
+
an agent, or just the agent's tools and system prompt. It writes the
|
|
61
|
+
situations the agent might meet, runs the agent through them against a fake
|
|
62
|
+
world that fails on purpose, and hands back every conversation as a row.
|
|
63
|
+
You grade the rows with your own judge or a verifier. The package then does
|
|
64
|
+
the bookkeeping that is easy to skip and expensive to get wrong: pass rates
|
|
65
|
+
with intervals, difficulty bands for RL, a check that your judge agrees with
|
|
66
|
+
people, decontamination against your eval set, and a scan for rewards the
|
|
67
|
+
policy can game. Every method says where it comes from
|
|
68
|
+
([References](#references)).
|
|
63
69
|
|
|
64
70
|
```bash
|
|
65
71
|
uv add whileai
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
Or `pip install whileai`. Python 3.10 to 3.13, one dependency, typed.
|
|
69
|
-
|
|
75
|
+
This package used to be called `zeroproof`; that name still installs it.
|
|
70
76
|
|
|
71
77
|
## Two ways in
|
|
72
78
|
|
|
73
|
-
**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
**You only want evals.** Plenty of teams cannot train and still need to
|
|
80
|
+
know whether the last prompt edit helped. Run `whileai init-evals` in your
|
|
81
|
+
project. It finds your agent, writes a judge and a runner around it, and
|
|
82
|
+
gives you a pass rate with a 95% interval, a table of where the agent
|
|
83
|
+
fails, and a test that goes red in CI when it gets worse. `coverage_gap`
|
|
84
|
+
tells you which situations your tests never reach. `compare_runs` reruns
|
|
85
|
+
the same tasks after a prompt or tool change and says whether the change
|
|
86
|
+
helped. Start at [docs/evals.md](docs/evals.md).
|
|
79
87
|
|
|
80
|
-
**
|
|
88
|
+
**You want to train.** Grade the same rows, keep the ones that carry
|
|
89
|
+
signal, export. That is the rest of this page.
|
|
81
90
|
|
|
82
91
|
## Sixty seconds, offline
|
|
83
92
|
|
|
84
|
-
No key, no network.
|
|
85
|
-
|
|
93
|
+
No key, no network. `seeded_agent` is a stand-in agent. It answers
|
|
94
|
+
honestly most of the time and, on a labeled fraction of rollouts, does one
|
|
95
|
+
thing wrong on purpose: hedges, flatters, or claims success after a tool
|
|
96
|
+
failed. Each row records what it did in `seeded`, so you can check that
|
|
97
|
+
your judge catches exactly those rows before you trust it on real ones.
|
|
86
98
|
|
|
87
99
|
```python
|
|
88
100
|
import whileai.simulations as wai
|
|
@@ -120,10 +132,16 @@ print(scored.pass_at)
|
|
|
120
132
|
pass@1 0.67 [0.55..0.78] | pass^4 (pass_pow_k) 0.19 [0.00..0.38] | pass@4 1.00 [1.00..1.00] | headroom 0.33 (16 groups, k=4)
|
|
121
133
|
```
|
|
122
134
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
pass@1 is the pass rate over tasks with a bootstrap interval. pass^4 is
|
|
136
|
+
how often all four rollouts of a task pass. Headroom is pass@4 minus
|
|
137
|
+
pass@1, the gap an RL update could close.
|
|
138
|
+
|
|
139
|
+
To use your own agent, pass any callable that takes the user message and
|
|
140
|
+
returns `{"steps": [...], "final_text": "..."}`. To use a model, pass a
|
|
141
|
+
spec string: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
142
|
+
or `ollama:<model>`. With no `agent=` at all, the run uses the Qwen we
|
|
143
|
+
host, on your key from `whileai login`, and Phi-4 grades. The judge is
|
|
144
|
+
never the model it is judging.
|
|
127
145
|
|
|
128
146
|
## The loop
|
|
129
147
|
|
|
@@ -139,43 +157,60 @@ model spec: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
|
139
157
|
|
|
140
158
|
## The science
|
|
141
159
|
|
|
142
|
-
**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
**SFT.** `optimize(mode="sft")` is rejection sampling [14], [16]: keep the
|
|
161
|
+
best-scoring completion for each prompt, with a random selector alongside
|
|
162
|
+
so you can tell whether picking the best did anything. Exported rows carry
|
|
163
|
+
a `loss_mask` per message, so the trainer learns from the agent's turns and
|
|
164
|
+
not from tool output. `unroll=True` splits a long conversation into one
|
|
165
|
+
sample per agent turn, each with the context that turn actually saw.
|
|
166
|
+
`format="trl"` is the shape `SFTTrainer` loads [1, ch. 4].
|
|
167
|
+
|
|
168
|
+
**RL with verifiable rewards.** When a program can check the answer, the
|
|
169
|
+
reward should be that program [5]: `MathEqual`, `CodeExec` against hidden
|
|
170
|
+
tests, `JSONSchema`, and combinations of them. In `mode="rl"` every prompt
|
|
171
|
+
gets two rollouts first. Only prompts where those two disagree are filled
|
|
172
|
+
to k, because a group that all passes or all fails has zero advantage under
|
|
173
|
+
GRPO [19]. That is DAPO's dynamic sampling [12], applied while the rollouts
|
|
174
|
+
are generated instead of after. `optimize(mode="rl")` then keeps the
|
|
175
|
+
prompts the policy solves 20 to 80% of the time [13] and lets you choose
|
|
176
|
+
what happens to rollouts that hit the length cap [12]. `export_environment`
|
|
177
|
+
writes the tasks, the fake world and the reward as a `verifiers` package
|
|
178
|
+
you can hand to a trainer. Rows keep their sampling logprobs so the trainer
|
|
179
|
+
can form the importance ratio [21], and `mean_kl` measures drift from the
|
|
180
|
+
reference model [22].
|
|
181
|
+
|
|
182
|
+
**Character training.** Write down how the model should talk as a
|
|
183
|
+
constitution [23], [24]. `load_spec` hashes it into `spec.version`, so an
|
|
184
|
+
edit to one principle is a new version. The judge is checked against the
|
|
185
|
+
labels the spec itself carries before it grades anything. Preference pairs
|
|
186
|
+
are matched on length [7], so the model learns the trait and not "longer
|
|
187
|
+
is better". Put `spec.behaviors()` in `must_not_regress` and
|
|
188
|
+
`delta_report` fails any run that improved one trait by giving up
|
|
160
189
|
another. [docs/character-training.md](docs/character-training.md).
|
|
161
190
|
|
|
162
|
-
**Evaluation.**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
[
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
**Evaluation.** Intervals are bootstrapped over tasks, not rollouts,
|
|
192
|
+
because rollouts of the same task are not independent [8], [10], [11].
|
|
193
|
+
Run an eval three times with `runs=3` and `delta_report` refuses to call a
|
|
194
|
+
change real when it sits inside twice the run-to-run standard deviation.
|
|
195
|
+
`holdout_size` says how many prompts you need to see a given gain at 80%
|
|
196
|
+
power [11]; most evals are too small. `decontaminate` checks training rows
|
|
197
|
+
against the eval set with the 80% n-gram overlap rule [16], and with
|
|
198
|
+
embeddings when you pass an embedder. [docs/evals.md](docs/evals.md).
|
|
199
|
+
|
|
200
|
+
**Over-optimization.** The reward is a proxy for what you want, and RL
|
|
201
|
+
finds the gap between the two [17]. `hack_scan` looks for the feature that
|
|
202
|
+
predicts reward within a task, against a shuffled baseline, so a judge that
|
|
203
|
+
pays for a phrase or a delimiter shows up before you train on it.
|
|
204
|
+
`judge_probes` tries the tricks a policy finds first, flattery included
|
|
205
|
+
[18]. `delta_report(proxy=, target=)` fails when the training reward went
|
|
206
|
+
up and the metric you care about did not. `HackMonitor` runs the same scan
|
|
207
|
+
inside a TRL training loop and can stop it.
|
|
208
|
+
[docs/reward-hacking.md](docs/reward-hacking.md).
|
|
175
209
|
|
|
176
210
|
## Recipes
|
|
177
211
|
|
|
178
|
-
|
|
212
|
+
Each recipe is one script and a README that says what you learn, what you
|
|
213
|
+
need, and how long it takes. All of them run in CI.
|
|
179
214
|
|
|
180
215
|
| Step | Recipes |
|
|
181
216
|
|---|---|
|
|
@@ -188,15 +223,20 @@ One post-training run as five steps; every recipe runs in CI.
|
|
|
188
223
|
|
|
189
224
|
## Platform
|
|
190
225
|
|
|
226
|
+
Push a graded run to your While account, train on it, serve the result.
|
|
227
|
+
`push` refuses RL data with no mixed groups, since a trainer would learn
|
|
228
|
+
nothing from it.
|
|
229
|
+
|
|
191
230
|
```python
|
|
192
|
-
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
231
|
+
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
193
232
|
run = wai.train(v1["datasetId"], method="grpo", steps=200) # sft | grpo | dpo | rm
|
|
194
233
|
run.wait()
|
|
195
234
|
model = wai.serve("refunds-v2", run) # OpenAI-compatible endpoint
|
|
196
235
|
```
|
|
197
236
|
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
If you train with your own code, `wai.TrainerCallback` reports into the
|
|
238
|
+
same run page. Traces from production come back through `traces=`, which
|
|
239
|
+
points the next simulation at the situations that failed.
|
|
200
240
|
|
|
201
241
|
## Documentation
|
|
202
242
|
|
|
@@ -212,8 +252,8 @@ uv run pytest
|
|
|
212
252
|
uv run ruff check . && uv run mypy
|
|
213
253
|
```
|
|
214
254
|
|
|
215
|
-
CI gates coverage at 90
|
|
216
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
255
|
+
CI runs the suite on Python 3.10 to 3.13, gates coverage at 90%, and runs
|
|
256
|
+
every recipe's `smoke.sh`. [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
217
257
|
|
|
218
258
|
## Cite
|
|
219
259
|
|
|
@@ -13,39 +13,50 @@
|
|
|
13
13
|
<a href="https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/whilehq/whileai-sdk/ci.yml?branch=main&label=ci&labelColor=0b1220&color=5cb08a" alt="CI"></a>
|
|
14
14
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/v/whileai?labelColor=0b1220&color=5cb08a" alt="PyPI"></a>
|
|
15
15
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/pyversions/whileai?labelColor=0b1220&color=3f8f6b" alt="Python"></a>
|
|
16
|
-
<a href="https://
|
|
16
|
+
<a href="https://pepy.tech/project/whileai"><img src="https://img.shields.io/pepy/dt/whileai?labelColor=0b1220&color=3f8f6b" alt="Downloads"></a>
|
|
17
17
|
<a href=".github/workflows/ci.yml"><img src="https://img.shields.io/badge/coverage-%E2%89%A5%2090%25%20gated-5cb08a?labelColor=0b1220" alt="Coverage gate"></a>
|
|
18
18
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-3f8f6b?labelColor=0b1220" alt="License"></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
`whileai` makes training and eval data for agents that call tools. Give it
|
|
22
|
+
an agent, or just the agent's tools and system prompt. It writes the
|
|
23
|
+
situations the agent might meet, runs the agent through them against a fake
|
|
24
|
+
world that fails on purpose, and hands back every conversation as a row.
|
|
25
|
+
You grade the rows with your own judge or a verifier. The package then does
|
|
26
|
+
the bookkeeping that is easy to skip and expensive to get wrong: pass rates
|
|
27
|
+
with intervals, difficulty bands for RL, a check that your judge agrees with
|
|
28
|
+
people, decontamination against your eval set, and a scan for rewards the
|
|
29
|
+
policy can game. Every method says where it comes from
|
|
30
|
+
([References](#references)).
|
|
26
31
|
|
|
27
32
|
```bash
|
|
28
33
|
uv add whileai
|
|
29
34
|
```
|
|
30
35
|
|
|
31
36
|
Or `pip install whileai`. Python 3.10 to 3.13, one dependency, typed.
|
|
32
|
-
|
|
37
|
+
This package used to be called `zeroproof`; that name still installs it.
|
|
33
38
|
|
|
34
39
|
## Two ways in
|
|
35
40
|
|
|
36
|
-
**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
**You only want evals.** Plenty of teams cannot train and still need to
|
|
42
|
+
know whether the last prompt edit helped. Run `whileai init-evals` in your
|
|
43
|
+
project. It finds your agent, writes a judge and a runner around it, and
|
|
44
|
+
gives you a pass rate with a 95% interval, a table of where the agent
|
|
45
|
+
fails, and a test that goes red in CI when it gets worse. `coverage_gap`
|
|
46
|
+
tells you which situations your tests never reach. `compare_runs` reruns
|
|
47
|
+
the same tasks after a prompt or tool change and says whether the change
|
|
48
|
+
helped. Start at [docs/evals.md](docs/evals.md).
|
|
42
49
|
|
|
43
|
-
**
|
|
50
|
+
**You want to train.** Grade the same rows, keep the ones that carry
|
|
51
|
+
signal, export. That is the rest of this page.
|
|
44
52
|
|
|
45
53
|
## Sixty seconds, offline
|
|
46
54
|
|
|
47
|
-
No key, no network.
|
|
48
|
-
|
|
55
|
+
No key, no network. `seeded_agent` is a stand-in agent. It answers
|
|
56
|
+
honestly most of the time and, on a labeled fraction of rollouts, does one
|
|
57
|
+
thing wrong on purpose: hedges, flatters, or claims success after a tool
|
|
58
|
+
failed. Each row records what it did in `seeded`, so you can check that
|
|
59
|
+
your judge catches exactly those rows before you trust it on real ones.
|
|
49
60
|
|
|
50
61
|
```python
|
|
51
62
|
import whileai.simulations as wai
|
|
@@ -83,10 +94,16 @@ print(scored.pass_at)
|
|
|
83
94
|
pass@1 0.67 [0.55..0.78] | pass^4 (pass_pow_k) 0.19 [0.00..0.38] | pass@4 1.00 [1.00..1.00] | headroom 0.33 (16 groups, k=4)
|
|
84
95
|
```
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
pass@1 is the pass rate over tasks with a bootstrap interval. pass^4 is
|
|
98
|
+
how often all four rollouts of a task pass. Headroom is pass@4 minus
|
|
99
|
+
pass@1, the gap an RL update could close.
|
|
100
|
+
|
|
101
|
+
To use your own agent, pass any callable that takes the user message and
|
|
102
|
+
returns `{"steps": [...], "final_text": "..."}`. To use a model, pass a
|
|
103
|
+
spec string: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
104
|
+
or `ollama:<model>`. With no `agent=` at all, the run uses the Qwen we
|
|
105
|
+
host, on your key from `whileai login`, and Phi-4 grades. The judge is
|
|
106
|
+
never the model it is judging.
|
|
90
107
|
|
|
91
108
|
## The loop
|
|
92
109
|
|
|
@@ -102,43 +119,60 @@ model spec: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
|
102
119
|
|
|
103
120
|
## The science
|
|
104
121
|
|
|
105
|
-
**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
**SFT.** `optimize(mode="sft")` is rejection sampling [14], [16]: keep the
|
|
123
|
+
best-scoring completion for each prompt, with a random selector alongside
|
|
124
|
+
so you can tell whether picking the best did anything. Exported rows carry
|
|
125
|
+
a `loss_mask` per message, so the trainer learns from the agent's turns and
|
|
126
|
+
not from tool output. `unroll=True` splits a long conversation into one
|
|
127
|
+
sample per agent turn, each with the context that turn actually saw.
|
|
128
|
+
`format="trl"` is the shape `SFTTrainer` loads [1, ch. 4].
|
|
129
|
+
|
|
130
|
+
**RL with verifiable rewards.** When a program can check the answer, the
|
|
131
|
+
reward should be that program [5]: `MathEqual`, `CodeExec` against hidden
|
|
132
|
+
tests, `JSONSchema`, and combinations of them. In `mode="rl"` every prompt
|
|
133
|
+
gets two rollouts first. Only prompts where those two disagree are filled
|
|
134
|
+
to k, because a group that all passes or all fails has zero advantage under
|
|
135
|
+
GRPO [19]. That is DAPO's dynamic sampling [12], applied while the rollouts
|
|
136
|
+
are generated instead of after. `optimize(mode="rl")` then keeps the
|
|
137
|
+
prompts the policy solves 20 to 80% of the time [13] and lets you choose
|
|
138
|
+
what happens to rollouts that hit the length cap [12]. `export_environment`
|
|
139
|
+
writes the tasks, the fake world and the reward as a `verifiers` package
|
|
140
|
+
you can hand to a trainer. Rows keep their sampling logprobs so the trainer
|
|
141
|
+
can form the importance ratio [21], and `mean_kl` measures drift from the
|
|
142
|
+
reference model [22].
|
|
143
|
+
|
|
144
|
+
**Character training.** Write down how the model should talk as a
|
|
145
|
+
constitution [23], [24]. `load_spec` hashes it into `spec.version`, so an
|
|
146
|
+
edit to one principle is a new version. The judge is checked against the
|
|
147
|
+
labels the spec itself carries before it grades anything. Preference pairs
|
|
148
|
+
are matched on length [7], so the model learns the trait and not "longer
|
|
149
|
+
is better". Put `spec.behaviors()` in `must_not_regress` and
|
|
150
|
+
`delta_report` fails any run that improved one trait by giving up
|
|
123
151
|
another. [docs/character-training.md](docs/character-training.md).
|
|
124
152
|
|
|
125
|
-
**Evaluation.**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
[
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
**Evaluation.** Intervals are bootstrapped over tasks, not rollouts,
|
|
154
|
+
because rollouts of the same task are not independent [8], [10], [11].
|
|
155
|
+
Run an eval three times with `runs=3` and `delta_report` refuses to call a
|
|
156
|
+
change real when it sits inside twice the run-to-run standard deviation.
|
|
157
|
+
`holdout_size` says how many prompts you need to see a given gain at 80%
|
|
158
|
+
power [11]; most evals are too small. `decontaminate` checks training rows
|
|
159
|
+
against the eval set with the 80% n-gram overlap rule [16], and with
|
|
160
|
+
embeddings when you pass an embedder. [docs/evals.md](docs/evals.md).
|
|
161
|
+
|
|
162
|
+
**Over-optimization.** The reward is a proxy for what you want, and RL
|
|
163
|
+
finds the gap between the two [17]. `hack_scan` looks for the feature that
|
|
164
|
+
predicts reward within a task, against a shuffled baseline, so a judge that
|
|
165
|
+
pays for a phrase or a delimiter shows up before you train on it.
|
|
166
|
+
`judge_probes` tries the tricks a policy finds first, flattery included
|
|
167
|
+
[18]. `delta_report(proxy=, target=)` fails when the training reward went
|
|
168
|
+
up and the metric you care about did not. `HackMonitor` runs the same scan
|
|
169
|
+
inside a TRL training loop and can stop it.
|
|
170
|
+
[docs/reward-hacking.md](docs/reward-hacking.md).
|
|
138
171
|
|
|
139
172
|
## Recipes
|
|
140
173
|
|
|
141
|
-
|
|
174
|
+
Each recipe is one script and a README that says what you learn, what you
|
|
175
|
+
need, and how long it takes. All of them run in CI.
|
|
142
176
|
|
|
143
177
|
| Step | Recipes |
|
|
144
178
|
|---|---|
|
|
@@ -151,15 +185,20 @@ One post-training run as five steps; every recipe runs in CI.
|
|
|
151
185
|
|
|
152
186
|
## Platform
|
|
153
187
|
|
|
188
|
+
Push a graded run to your While account, train on it, serve the result.
|
|
189
|
+
`push` refuses RL data with no mixed groups, since a trainer would learn
|
|
190
|
+
nothing from it.
|
|
191
|
+
|
|
154
192
|
```python
|
|
155
|
-
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
193
|
+
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
156
194
|
run = wai.train(v1["datasetId"], method="grpo", steps=200) # sft | grpo | dpo | rm
|
|
157
195
|
run.wait()
|
|
158
196
|
model = wai.serve("refunds-v2", run) # OpenAI-compatible endpoint
|
|
159
197
|
```
|
|
160
198
|
|
|
161
|
-
|
|
162
|
-
|
|
199
|
+
If you train with your own code, `wai.TrainerCallback` reports into the
|
|
200
|
+
same run page. Traces from production come back through `traces=`, which
|
|
201
|
+
points the next simulation at the situations that failed.
|
|
163
202
|
|
|
164
203
|
## Documentation
|
|
165
204
|
|
|
@@ -175,8 +214,8 @@ uv run pytest
|
|
|
175
214
|
uv run ruff check . && uv run mypy
|
|
176
215
|
```
|
|
177
216
|
|
|
178
|
-
CI gates coverage at 90
|
|
179
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
217
|
+
CI runs the suite on Python 3.10 to 3.13, gates coverage at 90%, and runs
|
|
218
|
+
every recipe's `smoke.sh`. [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
180
219
|
|
|
181
220
|
## Cite
|
|
182
221
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "whileai"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.69"
|
|
8
8
|
description = "Post-training data and evaluation for tool-using LLM agents: simulate, grade, measure, select, train, export, with the checks from rlhfbook.com."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -45,8 +45,8 @@ wai = "whileai.cli:main"
|
|
|
45
45
|
whileai-simulations = "whileai.simulations.score.quality:main"
|
|
46
46
|
|
|
47
47
|
[project.optional-dependencies]
|
|
48
|
-
dev = ["pytest>=8.0", "pytest-cov>=5.0", "
|
|
49
|
-
"types-requests"]
|
|
48
|
+
dev = ["pytest>=8.0", "pytest-cov>=5.0", "pytest-xdist>=3.6", "ruff>=0.12",
|
|
49
|
+
"mypy>=1.16", "types-requests"]
|
|
50
50
|
rl = ["verifiers>=0.3; python_version >= '3.11' and python_version < '3.14'"]
|
|
51
51
|
|
|
52
52
|
[tool.setuptools]
|
|
@@ -68,6 +68,10 @@ namespaces = false
|
|
|
68
68
|
[tool.pytest.ini_options]
|
|
69
69
|
testpaths = ["tests"]
|
|
70
70
|
pythonpath = ["."]
|
|
71
|
+
# One worker per core. The suite is 1,600 small tests with no shared state;
|
|
72
|
+
# serial it takes about three minutes, on four cores about forty seconds.
|
|
73
|
+
# Pass -n0 for a serial run (needed for --pdb and for reading -s output in order).
|
|
74
|
+
addopts = "-n auto"
|
|
71
75
|
|
|
72
76
|
[tool.ruff]
|
|
73
77
|
line-length = 100
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whileai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.69
|
|
4
4
|
Summary: Post-training data and evaluation for tool-using LLM agents: simulate, grade, measure, select, train, export, with the checks from rlhfbook.com.
|
|
5
5
|
Author: Jacob Weiss
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,6 +28,7 @@ Requires-Dist: pydantic>=2.0
|
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
30
|
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-xdist>=3.6; extra == "dev"
|
|
31
32
|
Requires-Dist: ruff>=0.12; extra == "dev"
|
|
32
33
|
Requires-Dist: mypy>=1.16; extra == "dev"
|
|
33
34
|
Requires-Dist: types-requests; extra == "dev"
|
|
@@ -50,39 +51,50 @@ Dynamic: license-file
|
|
|
50
51
|
<a href="https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/whilehq/whileai-sdk/ci.yml?branch=main&label=ci&labelColor=0b1220&color=5cb08a" alt="CI"></a>
|
|
51
52
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/v/whileai?labelColor=0b1220&color=5cb08a" alt="PyPI"></a>
|
|
52
53
|
<a href="https://pypi.org/project/whileai/"><img src="https://img.shields.io/pypi/pyversions/whileai?labelColor=0b1220&color=3f8f6b" alt="Python"></a>
|
|
53
|
-
<a href="https://
|
|
54
|
+
<a href="https://pepy.tech/project/whileai"><img src="https://img.shields.io/pepy/dt/whileai?labelColor=0b1220&color=3f8f6b" alt="Downloads"></a>
|
|
54
55
|
<a href=".github/workflows/ci.yml"><img src="https://img.shields.io/badge/coverage-%E2%89%A5%2090%25%20gated-5cb08a?labelColor=0b1220" alt="Coverage gate"></a>
|
|
55
56
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-3f8f6b?labelColor=0b1220" alt="License"></a>
|
|
56
57
|
</p>
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
`whileai` makes training and eval data for agents that call tools. Give it
|
|
60
|
+
an agent, or just the agent's tools and system prompt. It writes the
|
|
61
|
+
situations the agent might meet, runs the agent through them against a fake
|
|
62
|
+
world that fails on purpose, and hands back every conversation as a row.
|
|
63
|
+
You grade the rows with your own judge or a verifier. The package then does
|
|
64
|
+
the bookkeeping that is easy to skip and expensive to get wrong: pass rates
|
|
65
|
+
with intervals, difficulty bands for RL, a check that your judge agrees with
|
|
66
|
+
people, decontamination against your eval set, and a scan for rewards the
|
|
67
|
+
policy can game. Every method says where it comes from
|
|
68
|
+
([References](#references)).
|
|
63
69
|
|
|
64
70
|
```bash
|
|
65
71
|
uv add whileai
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
Or `pip install whileai`. Python 3.10 to 3.13, one dependency, typed.
|
|
69
|
-
|
|
75
|
+
This package used to be called `zeroproof`; that name still installs it.
|
|
70
76
|
|
|
71
77
|
## Two ways in
|
|
72
78
|
|
|
73
|
-
**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
**You only want evals.** Plenty of teams cannot train and still need to
|
|
80
|
+
know whether the last prompt edit helped. Run `whileai init-evals` in your
|
|
81
|
+
project. It finds your agent, writes a judge and a runner around it, and
|
|
82
|
+
gives you a pass rate with a 95% interval, a table of where the agent
|
|
83
|
+
fails, and a test that goes red in CI when it gets worse. `coverage_gap`
|
|
84
|
+
tells you which situations your tests never reach. `compare_runs` reruns
|
|
85
|
+
the same tasks after a prompt or tool change and says whether the change
|
|
86
|
+
helped. Start at [docs/evals.md](docs/evals.md).
|
|
79
87
|
|
|
80
|
-
**
|
|
88
|
+
**You want to train.** Grade the same rows, keep the ones that carry
|
|
89
|
+
signal, export. That is the rest of this page.
|
|
81
90
|
|
|
82
91
|
## Sixty seconds, offline
|
|
83
92
|
|
|
84
|
-
No key, no network.
|
|
85
|
-
|
|
93
|
+
No key, no network. `seeded_agent` is a stand-in agent. It answers
|
|
94
|
+
honestly most of the time and, on a labeled fraction of rollouts, does one
|
|
95
|
+
thing wrong on purpose: hedges, flatters, or claims success after a tool
|
|
96
|
+
failed. Each row records what it did in `seeded`, so you can check that
|
|
97
|
+
your judge catches exactly those rows before you trust it on real ones.
|
|
86
98
|
|
|
87
99
|
```python
|
|
88
100
|
import whileai.simulations as wai
|
|
@@ -120,10 +132,16 @@ print(scored.pass_at)
|
|
|
120
132
|
pass@1 0.67 [0.55..0.78] | pass^4 (pass_pow_k) 0.19 [0.00..0.38] | pass@4 1.00 [1.00..1.00] | headroom 0.33 (16 groups, k=4)
|
|
121
133
|
```
|
|
122
134
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
pass@1 is the pass rate over tasks with a bootstrap interval. pass^4 is
|
|
136
|
+
how often all four rollouts of a task pass. Headroom is pass@4 minus
|
|
137
|
+
pass@1, the gap an RL update could close.
|
|
138
|
+
|
|
139
|
+
To use your own agent, pass any callable that takes the user message and
|
|
140
|
+
returns `{"steps": [...], "final_text": "..."}`. To use a model, pass a
|
|
141
|
+
spec string: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
142
|
+
or `ollama:<model>`. With no `agent=` at all, the run uses the Qwen we
|
|
143
|
+
host, on your key from `whileai login`, and Phi-4 grades. The judge is
|
|
144
|
+
never the model it is judging.
|
|
127
145
|
|
|
128
146
|
## The loop
|
|
129
147
|
|
|
@@ -139,43 +157,60 @@ model spec: `openai:<model>`, `anthropic:<model>`, `vllm:<model>@<url>`,
|
|
|
139
157
|
|
|
140
158
|
## The science
|
|
141
159
|
|
|
142
|
-
**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
**SFT.** `optimize(mode="sft")` is rejection sampling [14], [16]: keep the
|
|
161
|
+
best-scoring completion for each prompt, with a random selector alongside
|
|
162
|
+
so you can tell whether picking the best did anything. Exported rows carry
|
|
163
|
+
a `loss_mask` per message, so the trainer learns from the agent's turns and
|
|
164
|
+
not from tool output. `unroll=True` splits a long conversation into one
|
|
165
|
+
sample per agent turn, each with the context that turn actually saw.
|
|
166
|
+
`format="trl"` is the shape `SFTTrainer` loads [1, ch. 4].
|
|
167
|
+
|
|
168
|
+
**RL with verifiable rewards.** When a program can check the answer, the
|
|
169
|
+
reward should be that program [5]: `MathEqual`, `CodeExec` against hidden
|
|
170
|
+
tests, `JSONSchema`, and combinations of them. In `mode="rl"` every prompt
|
|
171
|
+
gets two rollouts first. Only prompts where those two disagree are filled
|
|
172
|
+
to k, because a group that all passes or all fails has zero advantage under
|
|
173
|
+
GRPO [19]. That is DAPO's dynamic sampling [12], applied while the rollouts
|
|
174
|
+
are generated instead of after. `optimize(mode="rl")` then keeps the
|
|
175
|
+
prompts the policy solves 20 to 80% of the time [13] and lets you choose
|
|
176
|
+
what happens to rollouts that hit the length cap [12]. `export_environment`
|
|
177
|
+
writes the tasks, the fake world and the reward as a `verifiers` package
|
|
178
|
+
you can hand to a trainer. Rows keep their sampling logprobs so the trainer
|
|
179
|
+
can form the importance ratio [21], and `mean_kl` measures drift from the
|
|
180
|
+
reference model [22].
|
|
181
|
+
|
|
182
|
+
**Character training.** Write down how the model should talk as a
|
|
183
|
+
constitution [23], [24]. `load_spec` hashes it into `spec.version`, so an
|
|
184
|
+
edit to one principle is a new version. The judge is checked against the
|
|
185
|
+
labels the spec itself carries before it grades anything. Preference pairs
|
|
186
|
+
are matched on length [7], so the model learns the trait and not "longer
|
|
187
|
+
is better". Put `spec.behaviors()` in `must_not_regress` and
|
|
188
|
+
`delta_report` fails any run that improved one trait by giving up
|
|
160
189
|
another. [docs/character-training.md](docs/character-training.md).
|
|
161
190
|
|
|
162
|
-
**Evaluation.**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
[
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
**Evaluation.** Intervals are bootstrapped over tasks, not rollouts,
|
|
192
|
+
because rollouts of the same task are not independent [8], [10], [11].
|
|
193
|
+
Run an eval three times with `runs=3` and `delta_report` refuses to call a
|
|
194
|
+
change real when it sits inside twice the run-to-run standard deviation.
|
|
195
|
+
`holdout_size` says how many prompts you need to see a given gain at 80%
|
|
196
|
+
power [11]; most evals are too small. `decontaminate` checks training rows
|
|
197
|
+
against the eval set with the 80% n-gram overlap rule [16], and with
|
|
198
|
+
embeddings when you pass an embedder. [docs/evals.md](docs/evals.md).
|
|
199
|
+
|
|
200
|
+
**Over-optimization.** The reward is a proxy for what you want, and RL
|
|
201
|
+
finds the gap between the two [17]. `hack_scan` looks for the feature that
|
|
202
|
+
predicts reward within a task, against a shuffled baseline, so a judge that
|
|
203
|
+
pays for a phrase or a delimiter shows up before you train on it.
|
|
204
|
+
`judge_probes` tries the tricks a policy finds first, flattery included
|
|
205
|
+
[18]. `delta_report(proxy=, target=)` fails when the training reward went
|
|
206
|
+
up and the metric you care about did not. `HackMonitor` runs the same scan
|
|
207
|
+
inside a TRL training loop and can stop it.
|
|
208
|
+
[docs/reward-hacking.md](docs/reward-hacking.md).
|
|
175
209
|
|
|
176
210
|
## Recipes
|
|
177
211
|
|
|
178
|
-
|
|
212
|
+
Each recipe is one script and a README that says what you learn, what you
|
|
213
|
+
need, and how long it takes. All of them run in CI.
|
|
179
214
|
|
|
180
215
|
| Step | Recipes |
|
|
181
216
|
|---|---|
|
|
@@ -188,15 +223,20 @@ One post-training run as five steps; every recipe runs in CI.
|
|
|
188
223
|
|
|
189
224
|
## Platform
|
|
190
225
|
|
|
226
|
+
Push a graded run to your While account, train on it, serve the result.
|
|
227
|
+
`push` refuses RL data with no mixed groups, since a trainer would learn
|
|
228
|
+
nothing from it.
|
|
229
|
+
|
|
191
230
|
```python
|
|
192
|
-
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
231
|
+
v1 = data.push("refunds-v1", holdout=0.2, gate=True)
|
|
193
232
|
run = wai.train(v1["datasetId"], method="grpo", steps=200) # sft | grpo | dpo | rm
|
|
194
233
|
run.wait()
|
|
195
234
|
model = wai.serve("refunds-v2", run) # OpenAI-compatible endpoint
|
|
196
235
|
```
|
|
197
236
|
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
If you train with your own code, `wai.TrainerCallback` reports into the
|
|
238
|
+
same run page. Traces from production come back through `traces=`, which
|
|
239
|
+
points the next simulation at the situations that failed.
|
|
200
240
|
|
|
201
241
|
## Documentation
|
|
202
242
|
|
|
@@ -212,8 +252,8 @@ uv run pytest
|
|
|
212
252
|
uv run ruff check . && uv run mypy
|
|
213
253
|
```
|
|
214
254
|
|
|
215
|
-
CI gates coverage at 90
|
|
216
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
255
|
+
CI runs the suite on Python 3.10 to 3.13, gates coverage at 90%, and runs
|
|
256
|
+
every recipe's `smoke.sh`. [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
217
257
|
|
|
218
258
|
## Cite
|
|
219
259
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|