whileai 0.64__tar.gz → 0.65__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 (95) hide show
  1. {whileai-0.64/whileai.egg-info → whileai-0.65}/PKG-INFO +40 -2
  2. {whileai-0.64 → whileai-0.65}/README.md +38 -1
  3. {whileai-0.64 → whileai-0.65}/pyproject.toml +2 -2
  4. {whileai-0.64 → whileai-0.65}/whileai/ingest.py +13 -6
  5. whileai-0.65/whileai/platform.py +916 -0
  6. {whileai-0.64 → whileai-0.65}/whileai/simulations/__init__.py +2 -0
  7. {whileai-0.64 → whileai-0.65}/whileai/simulations/data.py +11 -11
  8. {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/platform.py +58 -1
  9. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/judging.py +16 -6
  10. {whileai-0.64 → whileai-0.65/whileai.egg-info}/PKG-INFO +40 -2
  11. {whileai-0.64 → whileai-0.65}/whileai.egg-info/SOURCES.txt +1 -0
  12. {whileai-0.64 → whileai-0.65}/whileai.egg-info/requires.txt +1 -0
  13. {whileai-0.64 → whileai-0.65}/LICENSE +0 -0
  14. {whileai-0.64 → whileai-0.65}/MANIFEST.in +0 -0
  15. {whileai-0.64 → whileai-0.65}/setup.cfg +0 -0
  16. {whileai-0.64 → whileai-0.65}/whileai/__init__.py +0 -0
  17. {whileai-0.64 → whileai-0.65}/whileai/_env.py +0 -0
  18. {whileai-0.64 → whileai-0.65}/whileai/auth.py +0 -0
  19. {whileai-0.64 → whileai-0.65}/whileai/cli.py +0 -0
  20. {whileai-0.64 → whileai-0.65}/whileai/init_evals.py +0 -0
  21. {whileai-0.64 → whileai-0.65}/whileai/py.typed +0 -0
  22. {whileai-0.64 → whileai-0.65}/whileai/simulations/__main__.py +0 -0
  23. {whileai-0.64 → whileai-0.65}/whileai/simulations/environment.py +0 -0
  24. {whileai-0.64 → whileai-0.65}/whileai/simulations/export.py +0 -0
  25. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/__init__.py +0 -0
  26. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/actionspace.py +0 -0
  27. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/adapters.py +0 -0
  28. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/agents.py +0 -0
  29. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/anthropic_backend.py +0 -0
  30. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/coverage.py +0 -0
  31. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/diversity.py +0 -0
  32. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/embeddings.py +0 -0
  33. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/explore.py +0 -0
  34. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/generator.py +0 -0
  35. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/offline_agent.py +0 -0
  36. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/scenarios.py +0 -0
  37. {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/usage_meter.py +0 -0
  38. {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/__init__.py +0 -0
  39. {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/otel.py +0 -0
  40. {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/traces.py +0 -0
  41. {whileai-0.64 → whileai-0.65}/whileai/simulations/monitor.py +0 -0
  42. {whileai-0.64 → whileai-0.65}/whileai/simulations/py.typed +0 -0
  43. {whileai-0.64 → whileai-0.65}/whileai/simulations/run/__init__.py +0 -0
  44. {whileai-0.64 → whileai-0.65}/whileai/simulations/run/config.py +0 -0
  45. {whileai-0.64 → whileai-0.65}/whileai/simulations/run/engine.py +0 -0
  46. {whileai-0.64 → whileai-0.65}/whileai/simulations/run/rows.py +0 -0
  47. {whileai-0.64 → whileai-0.65}/whileai/simulations/run/spec.py +0 -0
  48. {whileai-0.64 → whileai-0.65}/whileai/simulations/schema.py +0 -0
  49. {whileai-0.64 → whileai-0.65}/whileai/simulations/schemas/row-v1.json +0 -0
  50. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/__init__.py +0 -0
  51. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/agreement.py +0 -0
  52. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/audit.py +0 -0
  53. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/checklist.py +0 -0
  54. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/curriculum.py +0 -0
  55. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/delta.py +0 -0
  56. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grade_llm.py +0 -0
  57. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grading.py +0 -0
  58. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grounding.py +0 -0
  59. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/hack_scan.py +0 -0
  60. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/hygiene.py +0 -0
  61. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/judge_trust.py +0 -0
  62. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/labels.py +0 -0
  63. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/llm_judge.py +0 -0
  64. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/logprobs.py +0 -0
  65. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/markers.py +0 -0
  66. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/optimize.py +0 -0
  67. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/pairwise.py +0 -0
  68. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/passat.py +0 -0
  69. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/preflight.py +0 -0
  70. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/privileged.py +0 -0
  71. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/publish_gate.py +0 -0
  72. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/quality.py +0 -0
  73. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/reference.py +0 -0
  74. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/rubric.py +0 -0
  75. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/spec.py +0 -0
  76. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/stage.py +0 -0
  77. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/stats.py +0 -0
  78. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/style.py +0 -0
  79. {whileai-0.64 → whileai-0.65}/whileai/simulations/score/trace.py +0 -0
  80. {whileai-0.64 → whileai-0.65}/whileai/simulations/simulation.py +0 -0
  81. {whileai-0.64 → whileai-0.65}/whileai/simulations/text.py +0 -0
  82. {whileai-0.64 → whileai-0.65}/whileai/simulations/training.py +0 -0
  83. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/__init__.py +0 -0
  84. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/base.py +0 -0
  85. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/code.py +0 -0
  86. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/math.py +0 -0
  87. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/structured.py +0 -0
  88. {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/text.py +0 -0
  89. {whileai-0.64 → whileai-0.65}/whileai/simulations/world/__init__.py +0 -0
  90. {whileai-0.64 → whileai-0.65}/whileai/simulations/world/sandbox.py +0 -0
  91. {whileai-0.64 → whileai-0.65}/whileai/templates/__init__.py +0 -0
  92. {whileai-0.64 → whileai-0.65}/whileai/templates/evals.py +0 -0
  93. {whileai-0.64 → whileai-0.65}/whileai.egg-info/dependency_links.txt +0 -0
  94. {whileai-0.64 → whileai-0.65}/whileai.egg-info/entry_points.txt +0 -0
  95. {whileai-0.64 → whileai-0.65}/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.64
3
+ Version: 0.65
4
4
  Summary: While Python SDK: trace ingestion and verification client, plus agent simulations grounded in your tools and system prompt.
5
5
  Author: While
6
6
  License: Apache-2.0
@@ -24,6 +24,7 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: requests>=2.25.0
27
+ Requires-Dist: pydantic>=2.0
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: pytest>=8.0; extra == "dev"
29
30
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -919,7 +920,8 @@ The platform's "Make training data" button, as one line:
919
920
 
920
921
  ```python
921
922
  wai.send_score("4bf92f3577b34da6", 1.0) # this run passed
922
- wai.cuts(agent="my-agent") # what a cut would hold
923
+ summary = wai.cuts(agent="my-agent") # what a cut would hold
924
+ print(wai.format_cuts(summary, agent="my-agent")) # the traces page's sentence
923
925
  made = wai.cut(agent="my-agent", kind="rl") # make it
924
926
  wai.pull(made["train"]["datasetId"], "train.jsonl")
925
927
  made["holdout"]["datasetId"] # measure on this, never train on it
@@ -1117,6 +1119,42 @@ run.holdout(before=0.42, after=0.58) # did it work? the run page opens with thi
1117
1119
 
1118
1120
  A run's page opens with one word — **Better**, **Worse**, **About the same** — over the held-out pass rate before and after. The platform's trainer measures it; a run on your own hardware says it with `run.holdout(before, after)`, or `wai.attach_holdout(run_id, before=..., after=...)` once the run has finished. Pass rates are 0 to 1, so 58% is `0.58`; `metric="loss"` sends held-out loss instead (SFT), where lower is better. `run.delta(...)` and `wai.attach_delta(...)` already measure both sides, so they fill the two numbers in themselves, and add `summary["holdout"]` (also `run.holdout_summary`): each side's pass rate with `n_tasks`, `k` and a `ci95`, plus the delta report's verdict word (`moved`, `moved_unreplicated`, `within_eval_noise`, `no_change_detected`). A hosted run read back with `run.refresh()` has the same block with the interval fields `None` and a note that the platform only returned two numbers.
1119
1121
 
1122
+ ### Report a run so a person can decide
1123
+
1124
+ The platform draws one screen per tracked agent at [while.ai/platform/runs](https://while.ai/platform/runs): the held-out score by version with the frontier model as the line to beat, the training curve, what moved on the behaviors you did not train, the judge checks, live traffic on the served version, and cost. A coding agent fills it with `whileai.platform`; the person reads it and presses Promote. Your agent framework stays yours: `track` takes the agent object you already have (OpenAI Agents SDK, Pydantic AI, LangGraph, Claude Agent SDK) and reads the model, the instructions and the tools off it, or you describe it by hand.
1125
+
1126
+ ```python
1127
+ from whileai.platform import Behavior, Frontier, Harness, Judge, track
1128
+
1129
+ tracked = track(
1130
+ "refund-bot", # or track(my_agent): name, model, prompt and tools come from the object
1131
+ model="Qwen/Qwen3-4B",
1132
+ harness=Harness(instructions=SYSTEM_PROMPT, tools=["lookup_order", "issue_refund"]),
1133
+ frontier=Frontier(name="Sonnet 5", score=81, cost_per_1k=18.0),
1134
+ )
1135
+ tracked.behavior(
1136
+ Behavior(
1137
+ name="refunds",
1138
+ test_version="v2",
1139
+ n=240,
1140
+ judge=Judge(agreement=0.86, human_n=60, length_bias=0.08),
1141
+ noise_floor=2.4,
1142
+ contamination=0,
1143
+ reward_is_judge=False,
1144
+ )
1145
+ )
1146
+
1147
+ run = tracked.run("v4", method="GRPO", targets=["refunds"], trained_on=["refunds-grpo"])
1148
+ run.log(10, reward=0.41, kl=0.01) # or trainer.add_callback(wai.TrainerCallback(run))
1149
+ run.score("refunds", 83, ci=2.7, n=240) # every behavior, not only the targets
1150
+ run.score("length", 76, ci=2.8, n=120)
1151
+ run.finish(hours=2.1, gpu="1xH100", cost_usd=31)
1152
+
1153
+ str(tracked.verdict()) # "refunds: v4 beats v3 by 5 (interval excludes zero); 1 regression"
1154
+ ```
1155
+
1156
+ Every object is a pydantic model that validates before it leaves the process, and each one's docstring names the rlhfbook.com chapter it comes from. A *harness* is the instructions, tools and model name around the weights; its fingerprint is its version, so a prompt edit shows up as a new version without anyone naming it (Evaluation: a score is only comparable with its setup held constant). A *behavior* has its own frozen held-out test (`test_version`), a `noise_floor` measured by scoring the same model twice, and a judge checked against people (`agreement` over `human_n`) and for `length_bias`. A *run* is scored on every behavior: `targets` are the claim, the rest are the check (Over-Optimization: verbosity, sycophancy and refusals are what moves when the reward is gamed). `ci` is the half-width of the 95% interval; the verdict is whether the candidate's and the served version's intervals overlap. `tracked.live(day, version=, replies=, flagged=)` reports a day of traffic when you serve the model yourself. Logging buffers and never raises into the training loop. Worked example: `recipes/04-train/report-run/`.
1157
+
1120
1158
  ### Is it hacking the reward right now?
1121
1159
 
1122
1160
  ```python
@@ -883,7 +883,8 @@ The platform's "Make training data" button, as one line:
883
883
 
884
884
  ```python
885
885
  wai.send_score("4bf92f3577b34da6", 1.0) # this run passed
886
- wai.cuts(agent="my-agent") # what a cut would hold
886
+ summary = wai.cuts(agent="my-agent") # what a cut would hold
887
+ print(wai.format_cuts(summary, agent="my-agent")) # the traces page's sentence
887
888
  made = wai.cut(agent="my-agent", kind="rl") # make it
888
889
  wai.pull(made["train"]["datasetId"], "train.jsonl")
889
890
  made["holdout"]["datasetId"] # measure on this, never train on it
@@ -1081,6 +1082,42 @@ run.holdout(before=0.42, after=0.58) # did it work? the run page opens with thi
1081
1082
 
1082
1083
  A run's page opens with one word — **Better**, **Worse**, **About the same** — over the held-out pass rate before and after. The platform's trainer measures it; a run on your own hardware says it with `run.holdout(before, after)`, or `wai.attach_holdout(run_id, before=..., after=...)` once the run has finished. Pass rates are 0 to 1, so 58% is `0.58`; `metric="loss"` sends held-out loss instead (SFT), where lower is better. `run.delta(...)` and `wai.attach_delta(...)` already measure both sides, so they fill the two numbers in themselves, and add `summary["holdout"]` (also `run.holdout_summary`): each side's pass rate with `n_tasks`, `k` and a `ci95`, plus the delta report's verdict word (`moved`, `moved_unreplicated`, `within_eval_noise`, `no_change_detected`). A hosted run read back with `run.refresh()` has the same block with the interval fields `None` and a note that the platform only returned two numbers.
1083
1084
 
1085
+ ### Report a run so a person can decide
1086
+
1087
+ The platform draws one screen per tracked agent at [while.ai/platform/runs](https://while.ai/platform/runs): the held-out score by version with the frontier model as the line to beat, the training curve, what moved on the behaviors you did not train, the judge checks, live traffic on the served version, and cost. A coding agent fills it with `whileai.platform`; the person reads it and presses Promote. Your agent framework stays yours: `track` takes the agent object you already have (OpenAI Agents SDK, Pydantic AI, LangGraph, Claude Agent SDK) and reads the model, the instructions and the tools off it, or you describe it by hand.
1088
+
1089
+ ```python
1090
+ from whileai.platform import Behavior, Frontier, Harness, Judge, track
1091
+
1092
+ tracked = track(
1093
+ "refund-bot", # or track(my_agent): name, model, prompt and tools come from the object
1094
+ model="Qwen/Qwen3-4B",
1095
+ harness=Harness(instructions=SYSTEM_PROMPT, tools=["lookup_order", "issue_refund"]),
1096
+ frontier=Frontier(name="Sonnet 5", score=81, cost_per_1k=18.0),
1097
+ )
1098
+ tracked.behavior(
1099
+ Behavior(
1100
+ name="refunds",
1101
+ test_version="v2",
1102
+ n=240,
1103
+ judge=Judge(agreement=0.86, human_n=60, length_bias=0.08),
1104
+ noise_floor=2.4,
1105
+ contamination=0,
1106
+ reward_is_judge=False,
1107
+ )
1108
+ )
1109
+
1110
+ run = tracked.run("v4", method="GRPO", targets=["refunds"], trained_on=["refunds-grpo"])
1111
+ run.log(10, reward=0.41, kl=0.01) # or trainer.add_callback(wai.TrainerCallback(run))
1112
+ run.score("refunds", 83, ci=2.7, n=240) # every behavior, not only the targets
1113
+ run.score("length", 76, ci=2.8, n=120)
1114
+ run.finish(hours=2.1, gpu="1xH100", cost_usd=31)
1115
+
1116
+ str(tracked.verdict()) # "refunds: v4 beats v3 by 5 (interval excludes zero); 1 regression"
1117
+ ```
1118
+
1119
+ Every object is a pydantic model that validates before it leaves the process, and each one's docstring names the rlhfbook.com chapter it comes from. A *harness* is the instructions, tools and model name around the weights; its fingerprint is its version, so a prompt edit shows up as a new version without anyone naming it (Evaluation: a score is only comparable with its setup held constant). A *behavior* has its own frozen held-out test (`test_version`), a `noise_floor` measured by scoring the same model twice, and a judge checked against people (`agreement` over `human_n`) and for `length_bias`. A *run* is scored on every behavior: `targets` are the claim, the rest are the check (Over-Optimization: verbosity, sycophancy and refusals are what moves when the reward is gamed). `ci` is the half-width of the 95% interval; the verdict is whether the candidate's and the served version's intervals overlap. `tracked.live(day, version=, replies=, flagged=)` reports a day of traffic when you serve the model yourself. Logging buffers and never raises into the training loop. Worked example: `recipes/04-train/report-run/`.
1120
+
1084
1121
  ### Is it hacking the reward right now?
1085
1122
 
1086
1123
  ```python
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "whileai"
7
- version = "0.64"
7
+ version = "0.65"
8
8
  description = "While Python SDK: trace ingestion and verification client, plus agent simulations grounded in your tools and system prompt."
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