whileai 0.64__tar.gz → 0.66__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.
Files changed (98) hide show
  1. whileai-0.66/PKG-INFO +276 -0
  2. whileai-0.66/README.md +239 -0
  3. {whileai-0.64 → whileai-0.66}/pyproject.toml +3 -3
  4. {whileai-0.64 → whileai-0.66}/whileai/ingest.py +13 -6
  5. whileai-0.66/whileai/platform.py +1013 -0
  6. {whileai-0.64 → whileai-0.66}/whileai/simulations/__init__.py +2 -0
  7. {whileai-0.64 → whileai-0.66}/whileai/simulations/data.py +11 -11
  8. {whileai-0.64 → whileai-0.66}/whileai/simulations/ingest/platform.py +58 -1
  9. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/judging.py +16 -6
  10. whileai-0.66/whileai.egg-info/PKG-INFO +276 -0
  11. {whileai-0.64 → whileai-0.66}/whileai.egg-info/SOURCES.txt +1 -0
  12. {whileai-0.64 → whileai-0.66}/whileai.egg-info/requires.txt +1 -0
  13. whileai-0.64/PKG-INFO +0 -1315
  14. whileai-0.64/README.md +0 -1279
  15. whileai-0.64/whileai.egg-info/PKG-INFO +0 -1315
  16. {whileai-0.64 → whileai-0.66}/LICENSE +0 -0
  17. {whileai-0.64 → whileai-0.66}/MANIFEST.in +0 -0
  18. {whileai-0.64 → whileai-0.66}/setup.cfg +0 -0
  19. {whileai-0.64 → whileai-0.66}/whileai/__init__.py +0 -0
  20. {whileai-0.64 → whileai-0.66}/whileai/_env.py +0 -0
  21. {whileai-0.64 → whileai-0.66}/whileai/auth.py +0 -0
  22. {whileai-0.64 → whileai-0.66}/whileai/cli.py +0 -0
  23. {whileai-0.64 → whileai-0.66}/whileai/init_evals.py +0 -0
  24. {whileai-0.64 → whileai-0.66}/whileai/py.typed +0 -0
  25. {whileai-0.64 → whileai-0.66}/whileai/simulations/__main__.py +0 -0
  26. {whileai-0.64 → whileai-0.66}/whileai/simulations/environment.py +0 -0
  27. {whileai-0.64 → whileai-0.66}/whileai/simulations/export.py +0 -0
  28. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/__init__.py +0 -0
  29. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/actionspace.py +0 -0
  30. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/adapters.py +0 -0
  31. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/agents.py +0 -0
  32. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/anthropic_backend.py +0 -0
  33. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/coverage.py +0 -0
  34. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/diversity.py +0 -0
  35. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/embeddings.py +0 -0
  36. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/explore.py +0 -0
  37. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/generator.py +0 -0
  38. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/offline_agent.py +0 -0
  39. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/scenarios.py +0 -0
  40. {whileai-0.64 → whileai-0.66}/whileai/simulations/generate/usage_meter.py +0 -0
  41. {whileai-0.64 → whileai-0.66}/whileai/simulations/ingest/__init__.py +0 -0
  42. {whileai-0.64 → whileai-0.66}/whileai/simulations/ingest/otel.py +0 -0
  43. {whileai-0.64 → whileai-0.66}/whileai/simulations/ingest/traces.py +0 -0
  44. {whileai-0.64 → whileai-0.66}/whileai/simulations/monitor.py +0 -0
  45. {whileai-0.64 → whileai-0.66}/whileai/simulations/py.typed +0 -0
  46. {whileai-0.64 → whileai-0.66}/whileai/simulations/run/__init__.py +0 -0
  47. {whileai-0.64 → whileai-0.66}/whileai/simulations/run/config.py +0 -0
  48. {whileai-0.64 → whileai-0.66}/whileai/simulations/run/engine.py +0 -0
  49. {whileai-0.64 → whileai-0.66}/whileai/simulations/run/rows.py +0 -0
  50. {whileai-0.64 → whileai-0.66}/whileai/simulations/run/spec.py +0 -0
  51. {whileai-0.64 → whileai-0.66}/whileai/simulations/schema.py +0 -0
  52. {whileai-0.64 → whileai-0.66}/whileai/simulations/schemas/row-v1.json +0 -0
  53. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/__init__.py +0 -0
  54. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/agreement.py +0 -0
  55. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/audit.py +0 -0
  56. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/checklist.py +0 -0
  57. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/curriculum.py +0 -0
  58. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/delta.py +0 -0
  59. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/grade_llm.py +0 -0
  60. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/grading.py +0 -0
  61. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/grounding.py +0 -0
  62. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/hack_scan.py +0 -0
  63. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/hygiene.py +0 -0
  64. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/judge_trust.py +0 -0
  65. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/labels.py +0 -0
  66. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/llm_judge.py +0 -0
  67. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/logprobs.py +0 -0
  68. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/markers.py +0 -0
  69. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/optimize.py +0 -0
  70. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/pairwise.py +0 -0
  71. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/passat.py +0 -0
  72. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/preflight.py +0 -0
  73. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/privileged.py +0 -0
  74. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/publish_gate.py +0 -0
  75. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/quality.py +0 -0
  76. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/reference.py +0 -0
  77. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/rubric.py +0 -0
  78. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/spec.py +0 -0
  79. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/stage.py +0 -0
  80. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/stats.py +0 -0
  81. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/style.py +0 -0
  82. {whileai-0.64 → whileai-0.66}/whileai/simulations/score/trace.py +0 -0
  83. {whileai-0.64 → whileai-0.66}/whileai/simulations/simulation.py +0 -0
  84. {whileai-0.64 → whileai-0.66}/whileai/simulations/text.py +0 -0
  85. {whileai-0.64 → whileai-0.66}/whileai/simulations/training.py +0 -0
  86. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/__init__.py +0 -0
  87. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/base.py +0 -0
  88. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/code.py +0 -0
  89. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/math.py +0 -0
  90. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/structured.py +0 -0
  91. {whileai-0.64 → whileai-0.66}/whileai/simulations/verify/text.py +0 -0
  92. {whileai-0.64 → whileai-0.66}/whileai/simulations/world/__init__.py +0 -0
  93. {whileai-0.64 → whileai-0.66}/whileai/simulations/world/sandbox.py +0 -0
  94. {whileai-0.64 → whileai-0.66}/whileai/templates/__init__.py +0 -0
  95. {whileai-0.64 → whileai-0.66}/whileai/templates/evals.py +0 -0
  96. {whileai-0.64 → whileai-0.66}/whileai.egg-info/dependency_links.txt +0 -0
  97. {whileai-0.64 → whileai-0.66}/whileai.egg-info/entry_points.txt +0 -0
  98. {whileai-0.64 → whileai-0.66}/whileai.egg-info/top_level.txt +0 -0
whileai-0.66/PKG-INFO ADDED
@@ -0,0 +1,276 @@
1
+ Metadata-Version: 2.4
2
+ Name: whileai
3
+ Version: 0.66
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
+ Author: While
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/whilehq/whileai-sdk
8
+ Project-URL: Repository, https://github.com/whilehq/whileai-sdk
9
+ Project-URL: Issues, https://github.com/whilehq/whileai-sdk/issues
10
+ Project-URL: Datasets, https://huggingface.co/datasets/zero-proof-ai/agent-simulations
11
+ Keywords: zp,zeroproof,while,evals,agent-evals,rl,reinforcement-learning,grpo,synthetic-data,agents,tool-use,evaluation,simulation,llm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.25.0
27
+ Requires-Dist: pydantic>=2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.12; extra == "dev"
32
+ Requires-Dist: mypy>=1.16; extra == "dev"
33
+ Requires-Dist: types-requests; extra == "dev"
34
+ Provides-Extra: rl
35
+ Requires-Dist: verifiers>=0.3; (python_version >= "3.11" and python_version < "3.14") and extra == "rl"
36
+ Dynamic: license-file
37
+
38
+ # whileai
39
+
40
+ [![CI](https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml)
41
+ [![PyPI](https://img.shields.io/pypi/v/whileai)](https://pypi.org/project/whileai/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/whileai)](https://pypi.org/project/whileai/)
43
+ [![Downloads](https://img.shields.io/pypi/dm/whileai)](https://pypistats.org/packages/whileai)
44
+ [![Coverage gate](https://img.shields.io/badge/coverage-%E2%89%A5%2090%25%20gated-brightgreen)](.github/workflows/ci.yml)
45
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
46
+
47
+ Post-training data and evaluation for tool-using language-model agents.
48
+
49
+ `whileai` simulates the situations an agent can meet, rolls the agent
50
+ through them against a world that fails on schedule, grades every rollout
51
+ under one judge contract, and turns graded rows into SFT, preference and RL
52
+ data with the checks the literature says to run: pass@k with intervals,
53
+ difficulty bands, judge validation, decontamination, reward-hacking scans.
54
+ Every method names the chapter of [rlhfbook.com](https://rlhfbook.com)
55
+ (Lambert, *RLHF and LLM Post-Training*) or the paper it implements, and
56
+ every recipe reports a paired delta with a 95% interval on a held-out set,
57
+ never a mean alone.
58
+
59
+ ```bash
60
+ pip install whileai # or: uv add whileai
61
+ ```
62
+
63
+ One runtime dependency (`requests`), Python 3.10 to 3.13, typed. Formerly
64
+ `zeroproof`; that name still installs this package.
65
+
66
+ ## Sixty seconds, offline
67
+
68
+ No key, no network. The seeded agent answers honestly and, on a labeled
69
+ fraction of rollouts, does one wrong thing on purpose, so a judge that
70
+ catches exactly those rows is a judge that works.
71
+
72
+ ```python
73
+ import whileai.simulations as wai
74
+
75
+ TOOLS = [
76
+ {
77
+ "type": "function",
78
+ "function": {
79
+ "name": "get_order",
80
+ "description": "Look up an order by id.",
81
+ "parameters": {
82
+ "type": "object",
83
+ "properties": {"order_id": {"type": "string"}},
84
+ "required": ["order_id"],
85
+ },
86
+ },
87
+ }
88
+ ]
89
+
90
+ data = wai.simulate(
91
+ wai.seeded_agent(TOOLS),
92
+ tools=TOOLS,
93
+ system_prompt="Help customers with orders.",
94
+ simulator=False, # template writer: no model
95
+ mode="rl", # k rollouts per prompt
96
+ repeats=4,
97
+ repeat_policy="fixed",
98
+ budget=64,
99
+ )
100
+ scored = data.grade(judge=lambda row: {"reward": int(not row["seeded"])})
101
+ print(scored.pass_at) # pass@1 [95% CI over tasks] | pass^k | pass@k | headroom
102
+ ```
103
+
104
+ ```
105
+ 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)
106
+ ```
107
+
108
+ Swap in your agent as `agent(message) -> {"steps": [...], "final_text": ...}`,
109
+ a model spec such as `"openai:gpt-4.1-mini"`, or a model you serve. The
110
+ judge is any callable `row -> {"reward": 0..1}`, a verifier such as
111
+ `CodeExec`, or the hosted judge.
112
+
113
+ ## The loop
114
+
115
+ | Step | Call | What it computes | Source |
116
+ |---|---|---|---|
117
+ | Simulate | `simulate(agent, tools=, system_prompt=, mode="rl", repeats=k)` | a covering grid over tools, world state and user stance; k rollouts per prompt; tool faults on a schedule | ch. 12, 13 |
118
+ | Grade | `data.grade(judge=)`, `verify.MathEqual`, `verify.CodeExec` | reward per rollout under one contract; verifiable rewards where the answer is checkable | ch. 5, 7 |
119
+ | Validate the judge | `judge_trust`, `judge_probes` | agreement and Cohen's kappa against human gold; length bias; exploit probes (filler, rubric echo, unbacked success claim) | ch. 5, 14 |
120
+ | Measure | `pass_at`, `delta_report`, `eval_variance`, `holdout_size` | pass@1, pass^k, pass@k with bootstrap intervals over tasks; paired before/after with a permutation p-value; re-run noise band; power | ch. 16, app. C |
121
+ | Select | `optimize(mode="rl"\|"sft")`, `build_preference_pairs`, `curriculum` | 20 to 80% difficulty band, unanimous-group drop, within-task dedupe, rejection sampling, length-matched pairs, easy-to-hard schedule | ch. 6, 7, 9, 11 |
122
+ | Guard | `decontaminate`, `hack_scan`, `trace_markers`, `HackMonitor` | 8-gram and semantic overlap with the eval set; within-task reward-feature correlation against a shuffle floor; trajectory lies (claimed tests, phantom edits) | ch. 14, 16 |
123
+ | Train and export | `export_dataset`, `export_environment`, `train`, `serve` | loss masks and unrolled turns; a `verifiers` environment for GRPO; hosted LoRA SFT, GRPO, DPO and reward-model runs | ch. 4, 6, 8 |
124
+
125
+ Chapters: 04 instruction tuning, 05 reward models, 06 policy gradients, 07
126
+ reasoning, 08 direct alignment, 09 rejection sampling, 11 preference data,
127
+ 12 synthetic data, 13 tools, 14 over-optimization, 15 regularization, 16
128
+ evaluation, 17 character, appendix C practice.
129
+
130
+ ## What the science looks like here
131
+
132
+ **Supervised fine-tuning.** `optimize(mode="sft")` is rejection sampling:
133
+ the highest-reward completion per prompt above `min_reward`, with random
134
+ selectors as the chance control (ch. 9). Exported rows carry a per-message
135
+ `loss_mask` (loss on agent turns only, never on tool output), `unroll=True`
136
+ turns an N-turn conversation into N samples each trained on the context it
137
+ had, and `format="trl"` is the shape `SFTTrainer` loads (ch. 4).
138
+
139
+ **RL with verifiable rewards.** A reward is a program where it can be:
140
+ `MathEqual`, `CodeExec` against hidden tests, `JSONSchema`, composed with
141
+ `All` and `Weighted` (ch. 7, 13). `mode="rl"` allocates rollouts
142
+ successively: two per prompt as a probe, filled to k only where the group
143
+ splits, because a unanimous group carries zero advantage (DAPO dynamic
144
+ sampling, ch. 6). `optimize(mode="rl")` keeps the 20 to 80% pass-rate band
145
+ with the interval on each task's rate, handles overlong rollouts by policy,
146
+ and `export_environment` writes the task set, world and reward as a
147
+ `verifiers` package for Prime Intellect or TRL. Rows carry sampled
148
+ logprobs for the importance ratio, `staleness_report` flags off-policy
149
+ rows, and `mean_kl` reads the drift from a reference (ch. 6, 15).
150
+
151
+ **Character training.** A constitution is a versioned object: `load_spec`
152
+ hashes its principles into `spec.version`, `stamp_spec` tags the rows a run
153
+ targeted, and the judge is checked against the spec's own labels before it
154
+ grades. Pairs are length-matched so the update learns the trait and not
155
+ the word count, and `delta_report(must_not_regress=spec.behaviors())` fails
156
+ the run that traded one trait for another (ch. 17).
157
+ [docs/character-training.md](docs/character-training.md).
158
+
159
+ **Evaluation.** Every pass@1 is a bootstrap over tasks, not rollouts;
160
+ `simulate(tasks=base, runs=3)` replays the same eval three times and
161
+ `delta_report` refuses a verdict inside twice the re-run standard
162
+ deviation. `holdout_size(effect, before=, after=)` reads the per-task paired
163
+ spread off a previous eval and says how many prompts prove a gain at 80%
164
+ power. `decontaminate` applies the Llama 2 8-gram rule plus task identity
165
+ and an optional embedding pass (ch. 16, app. C).
166
+ [docs/evals.md](docs/evals.md).
167
+
168
+ **Over-optimization.** `hack_scan` centers reward and every candidate
169
+ feature within task, ranks by correlation, and floors it against a
170
+ within-task shuffle, so it finds the delimiter or phrase the judge pays for.
171
+ `judge_probes` tries the exploits a policy finds first. `delta_report(proxy=,
172
+ target=)` fails when the training reward rose and the target did not.
173
+ `HackMonitor` runs the same scan inside a TRL loop and can stop the run
174
+ (ch. 14). [docs/reward-hacking.md](docs/reward-hacking.md).
175
+
176
+ ## Recipes
177
+
178
+ One post-training run as five steps, each a runnable script with what you
179
+ learn, what it needs, and how long it takes. Offline recipes take seconds;
180
+ CI runs every one on every pull request.
181
+
182
+ | Step | Recipes |
183
+ |---|---|
184
+ | [01-simulate](recipes/01-simulate) | bring your own agent, verifiers, a coding agent traced to the platform |
185
+ | [02-measure](recipes/02-measure) | eval your agent, pass@k, is your eval any good, reward hacking, safety evals |
186
+ | [03-select](recipes/03-select) | the row schema, GRPO data with a gradient gate, character training |
187
+ | [04-train](recipes/04-train) | hosted loop, identity SFT, GRPO and DPO on Modal, text-to-SQL hill climb |
188
+ | [05-export](recipes/05-export) | Hugging Face datasets and adapters |
189
+ | [papers](recipes/papers) | one recent paper per recipe, one change to a step, the number it moved with its interval |
190
+
191
+ Index: [recipes/README.md](recipes/README.md). `whileai init-evals` scaffolds
192
+ the eval recipe around the agent it finds in your project.
193
+
194
+ ## Models
195
+
196
+ Any OpenAI-compatible chat endpoint that returns tool calls can play the
197
+ agent, write the situations, play the user, or judge. A spec names the
198
+ backend and the model and works wherever one is accepted.
199
+
200
+ ```python
201
+ data = wai.simulate(
202
+ agent="anthropic:claude-haiku-4-5", # or openai:<model>, vllm:<model>@<url>, ollama:<model>
203
+ simulator="anthropic:claude-sonnet-5", # the situation writer
204
+ tools=TOOLS,
205
+ system_prompt=POLICY,
206
+ output="rollout.jsonl",
207
+ )
208
+ ```
209
+
210
+ With no `agent=`, the run uses While-hosted Qwen on your account key
211
+ (`whileai login`), with Phi-4 as the judge so the judge is never the policy.
212
+
213
+ ## Platform
214
+
215
+ Datasets, training runs and served adapters on [zeroproofai.com](https://zeroproofai.com/platform),
216
+ from the same objects.
217
+
218
+ ```python
219
+ v1 = data.push("refunds-v1", holdout=0.2, gate=True) # gated: refuses gradient-free RL data
220
+ run = wai.train(v1["datasetId"], method="grpo", steps=200) # sft | grpo | dpo | rm
221
+ run.wait()
222
+ model = wai.serve("refunds-v2", run) # OpenAI-compatible endpoint
223
+ ```
224
+
225
+ Your own trainer reports into the same run page through `wai.TrainerCallback`
226
+ or `wai.training_run(...)`. Traces in production come back as `traces=`,
227
+ which aims the next run's grid at the situations that failed.
228
+
229
+ ## Documentation
230
+
231
+ | Page | What it covers |
232
+ |---|---|
233
+ | [docs/reference.md](docs/reference.md) | every call, knob, report and gate, in the order a run happens |
234
+ | [docs/engine.md](docs/engine.md) | how a row is made: the draw, the grid, the search arms, the rollout, the split |
235
+ | [docs/simulations.md](docs/simulations.md) | why the engine is shaped this way |
236
+ | [docs/evals.md](docs/evals.md) | pass rates with intervals and a CI gate for an agent you already have |
237
+ | [docs/reward-hacking.md](docs/reward-hacking.md) | before, during and after training |
238
+ | [docs/safety-evals.md](docs/safety-evals.md) | injection, exfiltration, unauthorized writes, over-refusal controls |
239
+ | [docs/character-training.md](docs/character-training.md) | constitution to graded rows to length-matched pairs |
240
+ | [CHANGELOG.md](CHANGELOG.md) | one entry per release, with the pull request |
241
+
242
+ The row schema is `Task`, `Rollout`, `Judgment`, `Marker`
243
+ (`whileai/simulations/schemas/row-v1.json`); every training target is a
244
+ projection of those four.
245
+
246
+ ## Development
247
+
248
+ ```bash
249
+ uv sync --extra dev
250
+ uv run pytest # about two minutes, no network
251
+ uv run ruff check . && uv run ruff format --check .
252
+ uv run mypy
253
+ ```
254
+
255
+ CI runs the suite on Python 3.10 through 3.13, lint and types, line
256
+ coverage gated at 90%, every recipe's `smoke.sh`, a plain-pip install of
257
+ the built wheel into a clean venv, and the version gate. Contributions:
258
+ [CONTRIBUTING.md](CONTRIBUTING.md).
259
+
260
+ ## Cite
261
+
262
+ ```bibtex
263
+ @software{whileai,
264
+ title = {whileai: post-training data and evaluation for tool-using agents},
265
+ author = {{While}},
266
+ year = {2026},
267
+ url = {https://github.com/whilehq/whileai-sdk}
268
+ }
269
+ ```
270
+
271
+ The methods follow Lambert, N. (2025). *Reinforcement Learning from Human
272
+ Feedback*. arXiv:2504.12501. [rlhfbook.com](https://rlhfbook.com).
273
+
274
+ ## License
275
+
276
+ Apache-2.0
whileai-0.66/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # whileai
2
+
3
+ [![CI](https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/whilehq/whileai-sdk/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/whileai)](https://pypi.org/project/whileai/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/whileai)](https://pypi.org/project/whileai/)
6
+ [![Downloads](https://img.shields.io/pypi/dm/whileai)](https://pypistats.org/packages/whileai)
7
+ [![Coverage gate](https://img.shields.io/badge/coverage-%E2%89%A5%2090%25%20gated-brightgreen)](.github/workflows/ci.yml)
8
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
9
+
10
+ Post-training data and evaluation for tool-using language-model agents.
11
+
12
+ `whileai` simulates the situations an agent can meet, rolls the agent
13
+ through them against a world that fails on schedule, grades every rollout
14
+ under one judge contract, and turns graded rows into SFT, preference and RL
15
+ data with the checks the literature says to run: pass@k with intervals,
16
+ difficulty bands, judge validation, decontamination, reward-hacking scans.
17
+ Every method names the chapter of [rlhfbook.com](https://rlhfbook.com)
18
+ (Lambert, *RLHF and LLM Post-Training*) or the paper it implements, and
19
+ every recipe reports a paired delta with a 95% interval on a held-out set,
20
+ never a mean alone.
21
+
22
+ ```bash
23
+ pip install whileai # or: uv add whileai
24
+ ```
25
+
26
+ One runtime dependency (`requests`), Python 3.10 to 3.13, typed. Formerly
27
+ `zeroproof`; that name still installs this package.
28
+
29
+ ## Sixty seconds, offline
30
+
31
+ No key, no network. The seeded agent answers honestly and, on a labeled
32
+ fraction of rollouts, does one wrong thing on purpose, so a judge that
33
+ catches exactly those rows is a judge that works.
34
+
35
+ ```python
36
+ import whileai.simulations as wai
37
+
38
+ TOOLS = [
39
+ {
40
+ "type": "function",
41
+ "function": {
42
+ "name": "get_order",
43
+ "description": "Look up an order by id.",
44
+ "parameters": {
45
+ "type": "object",
46
+ "properties": {"order_id": {"type": "string"}},
47
+ "required": ["order_id"],
48
+ },
49
+ },
50
+ }
51
+ ]
52
+
53
+ data = wai.simulate(
54
+ wai.seeded_agent(TOOLS),
55
+ tools=TOOLS,
56
+ system_prompt="Help customers with orders.",
57
+ simulator=False, # template writer: no model
58
+ mode="rl", # k rollouts per prompt
59
+ repeats=4,
60
+ repeat_policy="fixed",
61
+ budget=64,
62
+ )
63
+ scored = data.grade(judge=lambda row: {"reward": int(not row["seeded"])})
64
+ print(scored.pass_at) # pass@1 [95% CI over tasks] | pass^k | pass@k | headroom
65
+ ```
66
+
67
+ ```
68
+ 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)
69
+ ```
70
+
71
+ Swap in your agent as `agent(message) -> {"steps": [...], "final_text": ...}`,
72
+ a model spec such as `"openai:gpt-4.1-mini"`, or a model you serve. The
73
+ judge is any callable `row -> {"reward": 0..1}`, a verifier such as
74
+ `CodeExec`, or the hosted judge.
75
+
76
+ ## The loop
77
+
78
+ | Step | Call | What it computes | Source |
79
+ |---|---|---|---|
80
+ | Simulate | `simulate(agent, tools=, system_prompt=, mode="rl", repeats=k)` | a covering grid over tools, world state and user stance; k rollouts per prompt; tool faults on a schedule | ch. 12, 13 |
81
+ | Grade | `data.grade(judge=)`, `verify.MathEqual`, `verify.CodeExec` | reward per rollout under one contract; verifiable rewards where the answer is checkable | ch. 5, 7 |
82
+ | Validate the judge | `judge_trust`, `judge_probes` | agreement and Cohen's kappa against human gold; length bias; exploit probes (filler, rubric echo, unbacked success claim) | ch. 5, 14 |
83
+ | Measure | `pass_at`, `delta_report`, `eval_variance`, `holdout_size` | pass@1, pass^k, pass@k with bootstrap intervals over tasks; paired before/after with a permutation p-value; re-run noise band; power | ch. 16, app. C |
84
+ | Select | `optimize(mode="rl"\|"sft")`, `build_preference_pairs`, `curriculum` | 20 to 80% difficulty band, unanimous-group drop, within-task dedupe, rejection sampling, length-matched pairs, easy-to-hard schedule | ch. 6, 7, 9, 11 |
85
+ | Guard | `decontaminate`, `hack_scan`, `trace_markers`, `HackMonitor` | 8-gram and semantic overlap with the eval set; within-task reward-feature correlation against a shuffle floor; trajectory lies (claimed tests, phantom edits) | ch. 14, 16 |
86
+ | Train and export | `export_dataset`, `export_environment`, `train`, `serve` | loss masks and unrolled turns; a `verifiers` environment for GRPO; hosted LoRA SFT, GRPO, DPO and reward-model runs | ch. 4, 6, 8 |
87
+
88
+ Chapters: 04 instruction tuning, 05 reward models, 06 policy gradients, 07
89
+ reasoning, 08 direct alignment, 09 rejection sampling, 11 preference data,
90
+ 12 synthetic data, 13 tools, 14 over-optimization, 15 regularization, 16
91
+ evaluation, 17 character, appendix C practice.
92
+
93
+ ## What the science looks like here
94
+
95
+ **Supervised fine-tuning.** `optimize(mode="sft")` is rejection sampling:
96
+ the highest-reward completion per prompt above `min_reward`, with random
97
+ selectors as the chance control (ch. 9). Exported rows carry a per-message
98
+ `loss_mask` (loss on agent turns only, never on tool output), `unroll=True`
99
+ turns an N-turn conversation into N samples each trained on the context it
100
+ had, and `format="trl"` is the shape `SFTTrainer` loads (ch. 4).
101
+
102
+ **RL with verifiable rewards.** A reward is a program where it can be:
103
+ `MathEqual`, `CodeExec` against hidden tests, `JSONSchema`, composed with
104
+ `All` and `Weighted` (ch. 7, 13). `mode="rl"` allocates rollouts
105
+ successively: two per prompt as a probe, filled to k only where the group
106
+ splits, because a unanimous group carries zero advantage (DAPO dynamic
107
+ sampling, ch. 6). `optimize(mode="rl")` keeps the 20 to 80% pass-rate band
108
+ with the interval on each task's rate, handles overlong rollouts by policy,
109
+ and `export_environment` writes the task set, world and reward as a
110
+ `verifiers` package for Prime Intellect or TRL. Rows carry sampled
111
+ logprobs for the importance ratio, `staleness_report` flags off-policy
112
+ rows, and `mean_kl` reads the drift from a reference (ch. 6, 15).
113
+
114
+ **Character training.** A constitution is a versioned object: `load_spec`
115
+ hashes its principles into `spec.version`, `stamp_spec` tags the rows a run
116
+ targeted, and the judge is checked against the spec's own labels before it
117
+ grades. Pairs are length-matched so the update learns the trait and not
118
+ the word count, and `delta_report(must_not_regress=spec.behaviors())` fails
119
+ the run that traded one trait for another (ch. 17).
120
+ [docs/character-training.md](docs/character-training.md).
121
+
122
+ **Evaluation.** Every pass@1 is a bootstrap over tasks, not rollouts;
123
+ `simulate(tasks=base, runs=3)` replays the same eval three times and
124
+ `delta_report` refuses a verdict inside twice the re-run standard
125
+ deviation. `holdout_size(effect, before=, after=)` reads the per-task paired
126
+ spread off a previous eval and says how many prompts prove a gain at 80%
127
+ power. `decontaminate` applies the Llama 2 8-gram rule plus task identity
128
+ and an optional embedding pass (ch. 16, app. C).
129
+ [docs/evals.md](docs/evals.md).
130
+
131
+ **Over-optimization.** `hack_scan` centers reward and every candidate
132
+ feature within task, ranks by correlation, and floors it against a
133
+ within-task shuffle, so it finds the delimiter or phrase the judge pays for.
134
+ `judge_probes` tries the exploits a policy finds first. `delta_report(proxy=,
135
+ target=)` fails when the training reward rose and the target did not.
136
+ `HackMonitor` runs the same scan inside a TRL loop and can stop the run
137
+ (ch. 14). [docs/reward-hacking.md](docs/reward-hacking.md).
138
+
139
+ ## Recipes
140
+
141
+ One post-training run as five steps, each a runnable script with what you
142
+ learn, what it needs, and how long it takes. Offline recipes take seconds;
143
+ CI runs every one on every pull request.
144
+
145
+ | Step | Recipes |
146
+ |---|---|
147
+ | [01-simulate](recipes/01-simulate) | bring your own agent, verifiers, a coding agent traced to the platform |
148
+ | [02-measure](recipes/02-measure) | eval your agent, pass@k, is your eval any good, reward hacking, safety evals |
149
+ | [03-select](recipes/03-select) | the row schema, GRPO data with a gradient gate, character training |
150
+ | [04-train](recipes/04-train) | hosted loop, identity SFT, GRPO and DPO on Modal, text-to-SQL hill climb |
151
+ | [05-export](recipes/05-export) | Hugging Face datasets and adapters |
152
+ | [papers](recipes/papers) | one recent paper per recipe, one change to a step, the number it moved with its interval |
153
+
154
+ Index: [recipes/README.md](recipes/README.md). `whileai init-evals` scaffolds
155
+ the eval recipe around the agent it finds in your project.
156
+
157
+ ## Models
158
+
159
+ Any OpenAI-compatible chat endpoint that returns tool calls can play the
160
+ agent, write the situations, play the user, or judge. A spec names the
161
+ backend and the model and works wherever one is accepted.
162
+
163
+ ```python
164
+ data = wai.simulate(
165
+ agent="anthropic:claude-haiku-4-5", # or openai:<model>, vllm:<model>@<url>, ollama:<model>
166
+ simulator="anthropic:claude-sonnet-5", # the situation writer
167
+ tools=TOOLS,
168
+ system_prompt=POLICY,
169
+ output="rollout.jsonl",
170
+ )
171
+ ```
172
+
173
+ With no `agent=`, the run uses While-hosted Qwen on your account key
174
+ (`whileai login`), with Phi-4 as the judge so the judge is never the policy.
175
+
176
+ ## Platform
177
+
178
+ Datasets, training runs and served adapters on [zeroproofai.com](https://zeroproofai.com/platform),
179
+ from the same objects.
180
+
181
+ ```python
182
+ v1 = data.push("refunds-v1", holdout=0.2, gate=True) # gated: refuses gradient-free RL data
183
+ run = wai.train(v1["datasetId"], method="grpo", steps=200) # sft | grpo | dpo | rm
184
+ run.wait()
185
+ model = wai.serve("refunds-v2", run) # OpenAI-compatible endpoint
186
+ ```
187
+
188
+ Your own trainer reports into the same run page through `wai.TrainerCallback`
189
+ or `wai.training_run(...)`. Traces in production come back as `traces=`,
190
+ which aims the next run's grid at the situations that failed.
191
+
192
+ ## Documentation
193
+
194
+ | Page | What it covers |
195
+ |---|---|
196
+ | [docs/reference.md](docs/reference.md) | every call, knob, report and gate, in the order a run happens |
197
+ | [docs/engine.md](docs/engine.md) | how a row is made: the draw, the grid, the search arms, the rollout, the split |
198
+ | [docs/simulations.md](docs/simulations.md) | why the engine is shaped this way |
199
+ | [docs/evals.md](docs/evals.md) | pass rates with intervals and a CI gate for an agent you already have |
200
+ | [docs/reward-hacking.md](docs/reward-hacking.md) | before, during and after training |
201
+ | [docs/safety-evals.md](docs/safety-evals.md) | injection, exfiltration, unauthorized writes, over-refusal controls |
202
+ | [docs/character-training.md](docs/character-training.md) | constitution to graded rows to length-matched pairs |
203
+ | [CHANGELOG.md](CHANGELOG.md) | one entry per release, with the pull request |
204
+
205
+ The row schema is `Task`, `Rollout`, `Judgment`, `Marker`
206
+ (`whileai/simulations/schemas/row-v1.json`); every training target is a
207
+ projection of those four.
208
+
209
+ ## Development
210
+
211
+ ```bash
212
+ uv sync --extra dev
213
+ uv run pytest # about two minutes, no network
214
+ uv run ruff check . && uv run ruff format --check .
215
+ uv run mypy
216
+ ```
217
+
218
+ CI runs the suite on Python 3.10 through 3.13, lint and types, line
219
+ coverage gated at 90%, every recipe's `smoke.sh`, a plain-pip install of
220
+ the built wheel into a clean venv, and the version gate. Contributions:
221
+ [CONTRIBUTING.md](CONTRIBUTING.md).
222
+
223
+ ## Cite
224
+
225
+ ```bibtex
226
+ @software{whileai,
227
+ title = {whileai: post-training data and evaluation for tool-using agents},
228
+ author = {{While}},
229
+ year = {2026},
230
+ url = {https://github.com/whilehq/whileai-sdk}
231
+ }
232
+ ```
233
+
234
+ The methods follow Lambert, N. (2025). *Reinforcement Learning from Human
235
+ Feedback*. arXiv:2504.12501. [rlhfbook.com](https://rlhfbook.com).
236
+
237
+ ## License
238
+
239
+ Apache-2.0
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "whileai"
7
- version = "0.64"
8
- description = "While Python SDK: trace ingestion and verification client, plus agent simulations grounded in your tools and system prompt."
7
+ version = "0.66"
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"}
11
11
  requires-python = ">=3.10"
12
- dependencies = ["requests>=2.25.0"]
12
+ dependencies = ["requests>=2.25.0", "pydantic>=2.0"]
13
13
  authors = [{name = "While"}]
14
14
  keywords = [
15
15
  "zp", "zeroproof", "while", "evals", "agent-evals",
@@ -39,6 +39,13 @@ from whileai._env import getenv
39
39
  _DEFAULT_TRACE_URL = "https://api.zeroproofai.com"
40
40
  _GZIP_MAGIC = b"\x1f\x8b"
41
41
 
42
+ # The resource attribute that names the dataset. The gate reads
43
+ # `zeroproof.dataset` and nothing else, so sending only the whileai spelling
44
+ # lands every batch in a dataset called `traces` whatever you asked for, with
45
+ # a 202 that says so too late to notice. Both are written: the second costs
46
+ # one attribute and means the rename needs no release.
47
+ _DATASET_KEYS = ("zeroproof.dataset", "whileai.dataset")
48
+
42
49
 
43
50
  class WhileIngestError(Exception):
44
51
  """Raised when the gate rejects a trace batch."""
@@ -67,7 +74,7 @@ def otel_env(api_key: str, dataset: str = "traces", base_url: str | None = None)
67
74
  "OTEL_EXPORTER_OTLP_HEADERS": "x-api-key=" + api_key,
68
75
  "OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
69
76
  # Resource attribute the gate reads to name the dataset.
70
- "OTEL_RESOURCE_ATTRIBUTES": "whileai.dataset=" + dataset,
77
+ "OTEL_RESOURCE_ATTRIBUTES": ",".join(k + "=" + dataset for k in _DATASET_KEYS),
71
78
  }
72
79
 
73
80
 
@@ -133,9 +140,9 @@ def ingest_traces(
133
140
  Push a local OTLP batch file end to end and return ``{datasetId, dataset,
134
141
  rows}``.
135
142
 
136
- ``dataset`` overrides the dataset name by setting the
137
- ``whileai.dataset`` resource attribute on every resourceSpan, which
138
- requires reading the batch; leave it unset to send the bytes untouched.
143
+ ``dataset`` overrides the dataset name by setting the dataset resource
144
+ attribute on every resourceSpan, which requires reading the batch; leave
145
+ it unset to send the bytes untouched.
139
146
  """
140
147
  with open(file, "rb") as fh:
141
148
  body = fh.read()
@@ -148,9 +155,9 @@ def ingest_traces(
148
155
  for resource_span in batch.get("resourceSpans", []):
149
156
  resource = resource_span.setdefault("resource", {})
150
157
  attributes = [
151
- a for a in resource.get("attributes", []) if a.get("key") != "whileai.dataset"
158
+ a for a in resource.get("attributes", []) if a.get("key") not in _DATASET_KEYS
152
159
  ]
153
- attributes.append({"key": "whileai.dataset", "value": {"stringValue": dataset}})
160
+ attributes += [{"key": k, "value": {"stringValue": dataset}} for k in _DATASET_KEYS]
154
161
  resource["attributes"] = attributes
155
162
  body = json.dumps(batch).encode("utf-8")
156
163