whileai 0.59__tar.gz → 0.61__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.59/whileai.egg-info → whileai-0.61}/PKG-INFO +35 -4
- {whileai-0.59 → whileai-0.61}/README.md +34 -3
- {whileai-0.59 → whileai-0.61}/pyproject.toml +5 -1
- {whileai-0.59 → whileai-0.61}/whileai/auth.py +95 -1
- {whileai-0.59 → whileai-0.61}/whileai/cli.py +21 -1
- whileai-0.61/whileai/init_evals.py +526 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/__init__.py +10 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/data.py +14 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/actionspace.py +14 -5
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/adapters.py +25 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/agents.py +51 -4
- whileai-0.61/whileai/simulations/generate/anthropic_backend.py +405 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/generator.py +6 -3
- {whileai-0.59 → whileai-0.61}/whileai/simulations/run/config.py +5 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/run/engine.py +209 -13
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/agreement.py +64 -3
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/grade_llm.py +1 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/hygiene.py +18 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/judge_trust.py +45 -9
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/judging.py +16 -1
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/llm_judge.py +7 -2
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/passat.py +31 -2
- whileai-0.61/whileai/simulations/score/preflight.py +927 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/stats.py +26 -7
- {whileai-0.59 → whileai-0.61}/whileai/simulations/simulation.py +13 -1
- whileai-0.61/whileai/templates/__init__.py +1 -0
- whileai-0.61/whileai/templates/evals.py +455 -0
- {whileai-0.59 → whileai-0.61/whileai.egg-info}/PKG-INFO +35 -4
- {whileai-0.59 → whileai-0.61}/whileai.egg-info/SOURCES.txt +5 -1
- {whileai-0.59 → whileai-0.61}/whileai.egg-info/entry_points.txt +2 -0
- whileai-0.59/whileai/simulations/score/preflight.py +0 -365
- {whileai-0.59 → whileai-0.61}/LICENSE +0 -0
- {whileai-0.59 → whileai-0.61}/MANIFEST.in +0 -0
- {whileai-0.59 → whileai-0.61}/setup.cfg +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/_env.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/ingest.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/py.typed +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/__main__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/environment.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/export.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/coverage.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/diversity.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/embeddings.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/explore.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/offline_agent.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/scenarios.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/generate/usage_meter.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/ingest/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/ingest/otel.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/ingest/platform.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/ingest/traces.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/monitor.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/py.typed +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/run/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/run/rows.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/run/spec.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/schema.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/schemas/row-v1.json +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/audit.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/checklist.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/curriculum.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/delta.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/grading.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/grounding.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/hack_scan.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/labels.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/logprobs.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/markers.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/optimize.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/pairwise.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/privileged.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/publish_gate.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/quality.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/reference.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/rubric.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/spec.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/stage.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/style.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/score/trace.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/training.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/base.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/code.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/math.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/structured.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/verify/text.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/world/__init__.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai/simulations/world/sandbox.py +0 -0
- {whileai-0.59 → whileai-0.61}/whileai.egg-info/dependency_links.txt +0 -0
- {whileai-0.59 → whileai-0.61}/whileai.egg-info/requires.txt +0 -0
- {whileai-0.59 → whileai-0.61}/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.61
|
|
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
|
|
@@ -41,7 +41,9 @@ The While Python SDK. One package, two importable modules:
|
|
|
41
41
|
- `whileai`: the platform client. OTLP trace ingest and trace-dataset listing against the token gate.
|
|
42
42
|
- `whileai.simulations`: post-training data for an agent. Give it the agent's traces, or its tools and system prompt; it simulates the situations, the people, and the world, plays the agent through multi-turn tool-calling conversations, and returns rows for your grader.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Have an agent and want a pass rate with an interval? Start at [docs/evals.md](docs/evals.md) (offline, seconds, `coverage_gap` names what your tests miss).
|
|
45
|
+
|
|
46
|
+
**Renamed.** This SDK was `zeroproof` (ZeroProof is now While). `pip install zeroproof` still works: it installs `whileai`, and `import zeroproof` (or the older `zeroproof_simulations`) resolves to the same modules with a deprecation warning. `ZEROPROOF_*` environment variables and a saved `~/.zeroproof/credentials.json` are still read. Change the import when you can; new releases land under `whileai`. zp, ZeroProof and While all name this one product: the package is `whileai`, the import is `whileai.simulations`, keys start with `zp_`. `zp`, `wai` and `whileai` run the same CLI, so `zp login` and `whileai login` do the same thing (the help text says `whileai`). A machine with the old package still picks up `~/.zeroproof/credentials.json`; set `WHILEAI_HOME` to a fresh directory to isolate a new account from it.
|
|
45
47
|
|
|
46
48
|
Releases of `whileai` before 0.3 were an unrelated encrypted agent-to-agent messaging client. That code was removed in 0.04; pin `whileai<0.3` if you still depend on it.
|
|
47
49
|
|
|
@@ -51,7 +53,7 @@ Two ways in, one engine. Give it the agent's tools and system prompt and it samp
|
|
|
51
53
|
|
|
52
54
|

|
|
53
55
|
|
|
54
|
-
A situation is drawn across the world axes (from the agent's tools) and the human axes (from a separate writer). It fills a cell in the coverage grid, nudges the five search arms, and the agent plays it against a world that breaks on schedule. The row that comes out splits into `Task`, `Rollout`, `Judgment`, and `Marker`, and every training target is a projection of some of those four. The engine on one page, with references: [docs/engine.md](docs/engine.md), also at [
|
|
56
|
+
A situation is drawn across the world axes (from the agent's tools) and the human axes (from a separate writer). It fills a cell in the coverage grid, nudges the five search arms, and the agent plays it against a world that breaks on schedule. The row that comes out splits into `Task`, `Rollout`, `Judgment`, and `Marker`, and every training target is a projection of some of those four. The engine on one page, with references: [docs/engine.md](docs/engine.md), also at [zeroproofai.com/docs/engine](https://zeroproofai.com/docs/engine).
|
|
55
57
|
|
|
56
58
|
## Overview
|
|
57
59
|
|
|
@@ -195,7 +197,9 @@ that reads the trajectory, run the asks `k` times each, and read pass@1
|
|
|
195
197
|
with its interval. Offline first, then the hosted writer. The how-to is
|
|
196
198
|
[docs/evals.md](docs/evals.md); the runnable version is
|
|
197
199
|
[`recipes/02-measure/eval-your-agent`](recipes/02-measure/eval-your-agent),
|
|
198
|
-
which ends at a CI gate, not a push.
|
|
200
|
+
which ends at a CI gate, not a push. `whileai init-evals` writes those
|
|
201
|
+
four files for you, wired to the tools, system prompt and callable it
|
|
202
|
+
finds in the project, and prints what it picked.
|
|
199
203
|
|
|
200
204
|
```python
|
|
201
205
|
data = wai.simulate(
|
|
@@ -241,6 +245,33 @@ data = wai.simulate(
|
|
|
241
245
|
)
|
|
242
246
|
```
|
|
243
247
|
|
|
248
|
+
A model spec names the backend and the model. Four are built in:
|
|
249
|
+
|
|
250
|
+
- `ollama:<model>`: a local Ollama server, no key.
|
|
251
|
+
- `vllm:<model>@<url>`: any vLLM or OpenAI-compatible endpoint you serve.
|
|
252
|
+
- `openai:<model>`: `OPENAI_API_KEY`, and `OPENAI_BASE_URL` for a
|
|
253
|
+
compatible endpoint that is not OpenAI's.
|
|
254
|
+
- `anthropic:<model>`: the Claude Messages API on `ANTHROPIC_API_KEY`
|
|
255
|
+
(`WHILEAI_ANTHROPIC_API_KEY` overrides it).
|
|
256
|
+
|
|
257
|
+
A spec works everywhere one is accepted: `agent=`, `simulator=` for the
|
|
258
|
+
situation writer, `user_model=` for the simulated person, and `spec=` for the
|
|
259
|
+
judge.
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
export ANTHROPIC_API_KEY=...
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
data = wai.simulate(
|
|
267
|
+
agent="anthropic:claude-haiku-4-5",
|
|
268
|
+
tools=my_tools,
|
|
269
|
+
system_prompt=my_system_prompt,
|
|
270
|
+
simulator="anthropic:claude-sonnet-5", # the writer, on the same key
|
|
271
|
+
output="rollout.jsonl",
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
244
275
|
## Five calls
|
|
245
276
|
|
|
246
277
|
Agent to gated dataset. Everything else in this README is one layer down.
|
|
@@ -5,7 +5,9 @@ The While Python SDK. One package, two importable modules:
|
|
|
5
5
|
- `whileai`: the platform client. OTLP trace ingest and trace-dataset listing against the token gate.
|
|
6
6
|
- `whileai.simulations`: post-training data for an agent. Give it the agent's traces, or its tools and system prompt; it simulates the situations, the people, and the world, plays the agent through multi-turn tool-calling conversations, and returns rows for your grader.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Have an agent and want a pass rate with an interval? Start at [docs/evals.md](docs/evals.md) (offline, seconds, `coverage_gap` names what your tests miss).
|
|
9
|
+
|
|
10
|
+
**Renamed.** This SDK was `zeroproof` (ZeroProof is now While). `pip install zeroproof` still works: it installs `whileai`, and `import zeroproof` (or the older `zeroproof_simulations`) resolves to the same modules with a deprecation warning. `ZEROPROOF_*` environment variables and a saved `~/.zeroproof/credentials.json` are still read. Change the import when you can; new releases land under `whileai`. zp, ZeroProof and While all name this one product: the package is `whileai`, the import is `whileai.simulations`, keys start with `zp_`. `zp`, `wai` and `whileai` run the same CLI, so `zp login` and `whileai login` do the same thing (the help text says `whileai`). A machine with the old package still picks up `~/.zeroproof/credentials.json`; set `WHILEAI_HOME` to a fresh directory to isolate a new account from it.
|
|
9
11
|
|
|
10
12
|
Releases of `whileai` before 0.3 were an unrelated encrypted agent-to-agent messaging client. That code was removed in 0.04; pin `whileai<0.3` if you still depend on it.
|
|
11
13
|
|
|
@@ -15,7 +17,7 @@ Two ways in, one engine. Give it the agent's tools and system prompt and it samp
|
|
|
15
17
|
|
|
16
18
|

|
|
17
19
|
|
|
18
|
-
A situation is drawn across the world axes (from the agent's tools) and the human axes (from a separate writer). It fills a cell in the coverage grid, nudges the five search arms, and the agent plays it against a world that breaks on schedule. The row that comes out splits into `Task`, `Rollout`, `Judgment`, and `Marker`, and every training target is a projection of some of those four. The engine on one page, with references: [docs/engine.md](docs/engine.md), also at [
|
|
20
|
+
A situation is drawn across the world axes (from the agent's tools) and the human axes (from a separate writer). It fills a cell in the coverage grid, nudges the five search arms, and the agent plays it against a world that breaks on schedule. The row that comes out splits into `Task`, `Rollout`, `Judgment`, and `Marker`, and every training target is a projection of some of those four. The engine on one page, with references: [docs/engine.md](docs/engine.md), also at [zeroproofai.com/docs/engine](https://zeroproofai.com/docs/engine).
|
|
19
21
|
|
|
20
22
|
## Overview
|
|
21
23
|
|
|
@@ -159,7 +161,9 @@ that reads the trajectory, run the asks `k` times each, and read pass@1
|
|
|
159
161
|
with its interval. Offline first, then the hosted writer. The how-to is
|
|
160
162
|
[docs/evals.md](docs/evals.md); the runnable version is
|
|
161
163
|
[`recipes/02-measure/eval-your-agent`](recipes/02-measure/eval-your-agent),
|
|
162
|
-
which ends at a CI gate, not a push.
|
|
164
|
+
which ends at a CI gate, not a push. `whileai init-evals` writes those
|
|
165
|
+
four files for you, wired to the tools, system prompt and callable it
|
|
166
|
+
finds in the project, and prints what it picked.
|
|
163
167
|
|
|
164
168
|
```python
|
|
165
169
|
data = wai.simulate(
|
|
@@ -205,6 +209,33 @@ data = wai.simulate(
|
|
|
205
209
|
)
|
|
206
210
|
```
|
|
207
211
|
|
|
212
|
+
A model spec names the backend and the model. Four are built in:
|
|
213
|
+
|
|
214
|
+
- `ollama:<model>`: a local Ollama server, no key.
|
|
215
|
+
- `vllm:<model>@<url>`: any vLLM or OpenAI-compatible endpoint you serve.
|
|
216
|
+
- `openai:<model>`: `OPENAI_API_KEY`, and `OPENAI_BASE_URL` for a
|
|
217
|
+
compatible endpoint that is not OpenAI's.
|
|
218
|
+
- `anthropic:<model>`: the Claude Messages API on `ANTHROPIC_API_KEY`
|
|
219
|
+
(`WHILEAI_ANTHROPIC_API_KEY` overrides it).
|
|
220
|
+
|
|
221
|
+
A spec works everywhere one is accepted: `agent=`, `simulator=` for the
|
|
222
|
+
situation writer, `user_model=` for the simulated person, and `spec=` for the
|
|
223
|
+
judge.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
export ANTHROPIC_API_KEY=...
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
data = wai.simulate(
|
|
231
|
+
agent="anthropic:claude-haiku-4-5",
|
|
232
|
+
tools=my_tools,
|
|
233
|
+
system_prompt=my_system_prompt,
|
|
234
|
+
simulator="anthropic:claude-sonnet-5", # the writer, on the same key
|
|
235
|
+
output="rollout.jsonl",
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
208
239
|
## Five calls
|
|
209
240
|
|
|
210
241
|
Agent to gated dataset. Everything else in this README is one layer down.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "whileai"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.61"
|
|
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"}
|
|
@@ -38,6 +38,10 @@ Datasets = "https://huggingface.co/datasets/zero-proof-ai/agent-simulations"
|
|
|
38
38
|
|
|
39
39
|
[project.scripts]
|
|
40
40
|
whileai = "whileai.cli:main"
|
|
41
|
+
# same CLI under the two names people type: the product is "zp"
|
|
42
|
+
# (keys start with zp_) and "wai" is the short import alias.
|
|
43
|
+
zp = "whileai.cli:main"
|
|
44
|
+
wai = "whileai.cli:main"
|
|
41
45
|
whileai-simulations = "whileai.simulations.score.quality:main"
|
|
42
46
|
|
|
43
47
|
[project.optional-dependencies]
|
|
@@ -34,12 +34,59 @@ from pathlib import Path
|
|
|
34
34
|
from whileai._env import env_name, getenv
|
|
35
35
|
|
|
36
36
|
DEFAULT_API_URL = "https://api.zeroproofai.com"
|
|
37
|
+
SIGN_IN_URL = "https://www.zeroproofai.com/sign-in"
|
|
38
|
+
#: the trial allowance the gate hands out, used when the reply does not say
|
|
39
|
+
DEFAULT_TRIAL_INPUT_TOKENS = 25_000
|
|
40
|
+
#: Input tokens one hosted situation spends, measured on a 4-tool spec: a
|
|
41
|
+
#: 12-situation run of one cost 28,490, so round it to 2,000 a situation.
|
|
42
|
+
#: The trial allowance is small enough that the count is the first thing
|
|
43
|
+
#: anyone needs to know about it.
|
|
44
|
+
INPUT_TOKENS_PER_SITUATION = 2_000
|
|
37
45
|
|
|
38
46
|
|
|
39
47
|
class LoginError(RuntimeError):
|
|
40
48
|
pass
|
|
41
49
|
|
|
42
50
|
|
|
51
|
+
def trial_situations(daily_input_tokens: float | None = None) -> int:
|
|
52
|
+
"""About how many hosted situations a trial day buys, in round numbers."""
|
|
53
|
+
tokens = float(daily_input_tokens or DEFAULT_TRIAL_INPUT_TOKENS)
|
|
54
|
+
return max(1, int(tokens / INPUT_TOKENS_PER_SITUATION))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def trial_note(daily_input_tokens: float | None = None) -> str:
|
|
58
|
+
"""The two facts a trial key needs before its first hosted run: how
|
|
59
|
+
far the daily allowance goes, and the offline writer that has no
|
|
60
|
+
allowance at all."""
|
|
61
|
+
return (
|
|
62
|
+
f"That is about {trial_situations(daily_input_tokens)} hosted situations a day "
|
|
63
|
+
f"(a 4-tool spec spends around {INPUT_TOKENS_PER_SITUATION:,} input tokens per "
|
|
64
|
+
"situation); simulate(..., simulator=False) writes situations offline with no quota "
|
|
65
|
+
f"and no network; signing in once at {SIGN_IN_URL} lifts the limit."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def trial_prerun_note() -> str | None:
|
|
70
|
+
"""The one line a trial key needs before a hosted run spends it, or ``None``.
|
|
71
|
+
|
|
72
|
+
Read from the tier the credentials file recorded at sign-up or at the
|
|
73
|
+
last ``whileai status`` / ``whileai login``, so a run can say this
|
|
74
|
+
without a network call. A key from the environment has no recorded
|
|
75
|
+
tier, so this says nothing rather than guess at one.
|
|
76
|
+
"""
|
|
77
|
+
if getenv("API_KEY"):
|
|
78
|
+
return None
|
|
79
|
+
saved = _read(credentials_path()) or {}
|
|
80
|
+
if not saved.get("api_key") or str(saved.get("tier") or "") != "trial":
|
|
81
|
+
return None
|
|
82
|
+
tokens = int(float(saved.get("daily_input_tokens") or DEFAULT_TRIAL_INPUT_TOKENS))
|
|
83
|
+
return (
|
|
84
|
+
f"trial key: the hosted writer covers about {trial_situations(tokens)} situations a day "
|
|
85
|
+
f"({tokens // 1000}k input tokens); simulator=False writes them offline with no quota; "
|
|
86
|
+
f"sign in once at {SIGN_IN_URL} to lift it"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
43
90
|
def _api_url() -> str:
|
|
44
91
|
return getenv("API_URL", DEFAULT_API_URL).rstrip("/")
|
|
45
92
|
|
|
@@ -86,6 +133,37 @@ def _read(path: Path) -> dict | None:
|
|
|
86
133
|
return data if isinstance(data, dict) else None
|
|
87
134
|
|
|
88
135
|
|
|
136
|
+
def _tier_fields(payload: dict) -> dict:
|
|
137
|
+
"""The tier facts worth keeping next to the key, from ``/signup`` or ``/me``."""
|
|
138
|
+
tier = str((payload or {}).get("tier") or "").strip()
|
|
139
|
+
if not tier:
|
|
140
|
+
return {}
|
|
141
|
+
fields: dict = {"tier": tier}
|
|
142
|
+
trial = (payload or {}).get("trial") or {}
|
|
143
|
+
if isinstance(trial, dict):
|
|
144
|
+
if trial.get("daily_input_tokens") is not None:
|
|
145
|
+
fields["daily_input_tokens"] = trial["daily_input_tokens"]
|
|
146
|
+
if trial.get("expires_at"):
|
|
147
|
+
fields["expires_at"] = str(trial["expires_at"])
|
|
148
|
+
return fields
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def remember_account(payload: dict) -> None:
|
|
152
|
+
"""Record a ``/signup`` or ``/me`` reply's tier in the credentials file.
|
|
153
|
+
|
|
154
|
+
Only for the key that file holds: a key from the environment may belong
|
|
155
|
+
to another account, and writing its tier here would mislabel this one.
|
|
156
|
+
"""
|
|
157
|
+
fields = _tier_fields(payload)
|
|
158
|
+
saved = _read(credentials_path())
|
|
159
|
+
if not fields or not saved or not saved.get("api_key"):
|
|
160
|
+
return
|
|
161
|
+
if all(saved.get(key) == value for key, value in fields.items()):
|
|
162
|
+
return
|
|
163
|
+
saved.update(fields)
|
|
164
|
+
_write_private(credentials_path(), saved)
|
|
165
|
+
|
|
166
|
+
|
|
89
167
|
def stored_api_key() -> str | None:
|
|
90
168
|
"""The key saved by ``whileai login``, or ``None``."""
|
|
91
169
|
data = _read(credentials_path())
|
|
@@ -238,6 +316,10 @@ def login(
|
|
|
238
316
|
)
|
|
239
317
|
with contextlib.suppress(OSError):
|
|
240
318
|
_pending_path().unlink()
|
|
319
|
+
# the tier the key carries, saved next to it, so a run can
|
|
320
|
+
# name a trial limit before it spends one
|
|
321
|
+
with contextlib.suppress(LoginError, OSError, ValueError):
|
|
322
|
+
remember_account(account(data["api_key"]))
|
|
241
323
|
say(f"Logged in. Key saved to {credentials_path()}")
|
|
242
324
|
return data["api_key"]
|
|
243
325
|
error = data.get("error", "")
|
|
@@ -280,6 +362,9 @@ def signup(email: str, *, name: str | None = None, out: Callable[[str], None] |
|
|
|
280
362
|
"user_id": data.get("user_id"),
|
|
281
363
|
"email": data.get("email", email),
|
|
282
364
|
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
365
|
+
# so simulate() can warn about the trial before a hosted
|
|
366
|
+
# run spends it, without a call to /me
|
|
367
|
+
**_tier_fields(data),
|
|
283
368
|
},
|
|
284
369
|
)
|
|
285
370
|
say(f"Account created for {data.get('email', email)}. Key saved to {credentials_path()}")
|
|
@@ -291,6 +376,7 @@ def signup(email: str, *, name: str | None = None, out: Callable[[str], None] |
|
|
|
291
376
|
f"output tokens a day, {int(trial.get('storage_bytes', 104857600)) // 1048576} MB, "
|
|
292
377
|
f"{trial.get('datasets', 10)} datasets, expires {str(trial.get('expires_at', ''))[:10]}."
|
|
293
378
|
)
|
|
379
|
+
say(trial_note(trial.get("daily_input_tokens")))
|
|
294
380
|
say(
|
|
295
381
|
trial.get("lift")
|
|
296
382
|
or "Sign in once at https://www.zeroproofai.com/sign-in with an email code to lift trial limits."
|
|
@@ -321,7 +407,12 @@ def logout() -> bool:
|
|
|
321
407
|
|
|
322
408
|
|
|
323
409
|
def status() -> dict:
|
|
324
|
-
"""What the SDK would use right now, with the key masked.
|
|
410
|
+
"""What the SDK would use right now, with the key masked.
|
|
411
|
+
|
|
412
|
+
On a trial key ``trial_note`` says how many hosted situations the
|
|
413
|
+
daily allowance covers and names the offline writer that has none.
|
|
414
|
+
``tier`` comes from ``GET /me``, so reading it costs one call.
|
|
415
|
+
"""
|
|
325
416
|
env = getenv("API_KEY")
|
|
326
417
|
env_var = env_name("API_KEY")
|
|
327
418
|
saved = _read(credentials_path()) or {}
|
|
@@ -350,8 +441,11 @@ def status() -> dict:
|
|
|
350
441
|
out["tier"] = f"unknown ({err})"
|
|
351
442
|
else:
|
|
352
443
|
out["tier"] = me.get("tier")
|
|
444
|
+
if not env:
|
|
445
|
+
remember_account(me)
|
|
353
446
|
if me.get("tier") == "trial":
|
|
354
447
|
trial = me.get("trial") or {}
|
|
355
448
|
out["trial_expires_at"] = trial.get("expires_at")
|
|
356
449
|
out["lift"] = trial.get("lift")
|
|
450
|
+
out["trial_note"] = trial_note(trial.get("daily_input_tokens"))
|
|
357
451
|
return out
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""``whileai`` command line: login, signup, logout, status."""
|
|
1
|
+
"""``whileai`` command line: login, signup, logout, status, init-evals."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
9
|
from . import auth
|
|
10
|
+
from .init_evals import add_arguments as init_evals_args
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def main(argv: list[str] | None = None) -> int:
|
|
@@ -33,6 +34,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
33
34
|
sub.add_parser("logout", help="delete the saved key")
|
|
34
35
|
sub.add_parser("status", help="show which key the SDK will use")
|
|
35
36
|
|
|
37
|
+
p_init = sub.add_parser(
|
|
38
|
+
"init-evals",
|
|
39
|
+
help="write an eval harness (agent, judge, run, test) wired to this project",
|
|
40
|
+
)
|
|
41
|
+
init_evals_args(p_init)
|
|
42
|
+
|
|
36
43
|
p_purge = sub.add_parser(
|
|
37
44
|
"purge", help="delete an agent's traces, datasets and record, or empty datasets"
|
|
38
45
|
)
|
|
@@ -72,6 +79,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
72
79
|
return 1
|
|
73
80
|
return 0
|
|
74
81
|
|
|
82
|
+
if args.command == "init-evals":
|
|
83
|
+
from .init_evals import init_evals
|
|
84
|
+
|
|
85
|
+
return init_evals(
|
|
86
|
+
agent=args.agent,
|
|
87
|
+
tools=args.tools,
|
|
88
|
+
system_prompt=args.system_prompt,
|
|
89
|
+
out=args.dir,
|
|
90
|
+
force=args.force,
|
|
91
|
+
)
|
|
92
|
+
|
|
75
93
|
if args.command == "logout":
|
|
76
94
|
print("Logged out." if auth.logout() else "No saved key.")
|
|
77
95
|
return 0
|
|
@@ -79,6 +97,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
79
97
|
if args.command == "status":
|
|
80
98
|
shown = auth.status()
|
|
81
99
|
print(json.dumps(shown, indent=2))
|
|
100
|
+
if shown.get("trial_note"):
|
|
101
|
+
print(shown["trial_note"])
|
|
82
102
|
if not shown.get("configured"):
|
|
83
103
|
print(
|
|
84
104
|
"no API key configured: run `whileai login` or set WHILEAI_API_KEY",
|