whileai 0.58__tar.gz → 0.60__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.58/whileai.egg-info → whileai-0.60}/PKG-INFO +29 -2
- {whileai-0.58 → whileai-0.60}/README.md +28 -1
- {whileai-0.58 → whileai-0.60}/pyproject.toml +1 -1
- {whileai-0.58 → whileai-0.60}/whileai/auth.py +34 -1
- {whileai-0.58 → whileai-0.60}/whileai/cli.py +2 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/__init__.py +10 -1
- {whileai-0.58 → whileai-0.60}/whileai/simulations/data.py +4 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/agents.py +51 -4
- whileai-0.60/whileai/simulations/generate/anthropic_backend.py +405 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/agreement.py +64 -3
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/grade_llm.py +1 -1
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/judge_trust.py +71 -14
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/judging.py +6 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/llm_judge.py +7 -2
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/passat.py +31 -2
- whileai-0.60/whileai/simulations/score/preflight.py +927 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/stats.py +26 -7
- {whileai-0.58 → whileai-0.60/whileai.egg-info}/PKG-INFO +29 -2
- {whileai-0.58 → whileai-0.60}/whileai.egg-info/SOURCES.txt +1 -0
- whileai-0.58/whileai/simulations/score/preflight.py +0 -365
- {whileai-0.58 → whileai-0.60}/LICENSE +0 -0
- {whileai-0.58 → whileai-0.60}/MANIFEST.in +0 -0
- {whileai-0.58 → whileai-0.60}/setup.cfg +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/_env.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/ingest.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/py.typed +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/__main__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/environment.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/export.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/actionspace.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/adapters.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/coverage.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/diversity.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/embeddings.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/explore.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/generator.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/offline_agent.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/scenarios.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/generate/usage_meter.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/ingest/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/ingest/otel.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/ingest/platform.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/ingest/traces.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/monitor.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/py.typed +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/run/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/run/config.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/run/engine.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/run/rows.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/run/spec.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/schema.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/schemas/row-v1.json +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/audit.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/checklist.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/curriculum.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/delta.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/grading.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/grounding.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/hack_scan.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/hygiene.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/labels.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/logprobs.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/markers.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/optimize.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/pairwise.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/privileged.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/publish_gate.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/quality.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/reference.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/rubric.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/spec.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/stage.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/style.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/score/trace.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/simulation.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/training.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/base.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/code.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/math.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/structured.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/verify/text.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/world/__init__.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai/simulations/world/sandbox.py +0 -0
- {whileai-0.58 → whileai-0.60}/whileai.egg-info/dependency_links.txt +0 -0
- {whileai-0.58 → whileai-0.60}/whileai.egg-info/entry_points.txt +0 -0
- {whileai-0.58 → whileai-0.60}/whileai.egg-info/requires.txt +0 -0
- {whileai-0.58 → whileai-0.60}/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.60
|
|
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
|
|
@@ -51,7 +51,7 @@ Two ways in, one engine. Give it the agent's tools and system prompt and it samp
|
|
|
51
51
|
|
|
52
52
|

|
|
53
53
|
|
|
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 [
|
|
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 [zeroproofai.com/docs/engine](https://zeroproofai.com/docs/engine).
|
|
55
55
|
|
|
56
56
|
## Overview
|
|
57
57
|
|
|
@@ -241,6 +241,33 @@ data = wai.simulate(
|
|
|
241
241
|
)
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
A model spec names the backend and the model. Four are built in:
|
|
245
|
+
|
|
246
|
+
- `ollama:<model>`: a local Ollama server, no key.
|
|
247
|
+
- `vllm:<model>@<url>`: any vLLM or OpenAI-compatible endpoint you serve.
|
|
248
|
+
- `openai:<model>`: `OPENAI_API_KEY`, and `OPENAI_BASE_URL` for a
|
|
249
|
+
compatible endpoint that is not OpenAI's.
|
|
250
|
+
- `anthropic:<model>`: the Claude Messages API on `ANTHROPIC_API_KEY`
|
|
251
|
+
(`WHILEAI_ANTHROPIC_API_KEY` overrides it).
|
|
252
|
+
|
|
253
|
+
A spec works everywhere one is accepted: `agent=`, `simulator=` for the
|
|
254
|
+
situation writer, `user_model=` for the simulated person, and `spec=` for the
|
|
255
|
+
judge.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
export ANTHROPIC_API_KEY=...
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
data = wai.simulate(
|
|
263
|
+
agent="anthropic:claude-haiku-4-5",
|
|
264
|
+
tools=my_tools,
|
|
265
|
+
system_prompt=my_system_prompt,
|
|
266
|
+
simulator="anthropic:claude-sonnet-5", # the writer, on the same key
|
|
267
|
+
output="rollout.jsonl",
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
244
271
|
## Five calls
|
|
245
272
|
|
|
246
273
|
Agent to gated dataset. Everything else in this README is one layer down.
|
|
@@ -15,7 +15,7 @@ Two ways in, one engine. Give it the agent's tools and system prompt and it samp
|
|
|
15
15
|
|
|
16
16
|

|
|
17
17
|
|
|
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 [
|
|
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 [zeroproofai.com/docs/engine](https://zeroproofai.com/docs/engine).
|
|
19
19
|
|
|
20
20
|
## Overview
|
|
21
21
|
|
|
@@ -205,6 +205,33 @@ data = wai.simulate(
|
|
|
205
205
|
)
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
A model spec names the backend and the model. Four are built in:
|
|
209
|
+
|
|
210
|
+
- `ollama:<model>`: a local Ollama server, no key.
|
|
211
|
+
- `vllm:<model>@<url>`: any vLLM or OpenAI-compatible endpoint you serve.
|
|
212
|
+
- `openai:<model>`: `OPENAI_API_KEY`, and `OPENAI_BASE_URL` for a
|
|
213
|
+
compatible endpoint that is not OpenAI's.
|
|
214
|
+
- `anthropic:<model>`: the Claude Messages API on `ANTHROPIC_API_KEY`
|
|
215
|
+
(`WHILEAI_ANTHROPIC_API_KEY` overrides it).
|
|
216
|
+
|
|
217
|
+
A spec works everywhere one is accepted: `agent=`, `simulator=` for the
|
|
218
|
+
situation writer, `user_model=` for the simulated person, and `spec=` for the
|
|
219
|
+
judge.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
export ANTHROPIC_API_KEY=...
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
data = wai.simulate(
|
|
227
|
+
agent="anthropic:claude-haiku-4-5",
|
|
228
|
+
tools=my_tools,
|
|
229
|
+
system_prompt=my_system_prompt,
|
|
230
|
+
simulator="anthropic:claude-sonnet-5", # the writer, on the same key
|
|
231
|
+
output="rollout.jsonl",
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
208
235
|
## Five calls
|
|
209
236
|
|
|
210
237
|
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.60"
|
|
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"}
|
|
@@ -34,12 +34,38 @@ 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
|
+
|
|
43
69
|
def _api_url() -> str:
|
|
44
70
|
return getenv("API_URL", DEFAULT_API_URL).rstrip("/")
|
|
45
71
|
|
|
@@ -291,6 +317,7 @@ def signup(email: str, *, name: str | None = None, out: Callable[[str], None] |
|
|
|
291
317
|
f"output tokens a day, {int(trial.get('storage_bytes', 104857600)) // 1048576} MB, "
|
|
292
318
|
f"{trial.get('datasets', 10)} datasets, expires {str(trial.get('expires_at', ''))[:10]}."
|
|
293
319
|
)
|
|
320
|
+
say(trial_note(trial.get("daily_input_tokens")))
|
|
294
321
|
say(
|
|
295
322
|
trial.get("lift")
|
|
296
323
|
or "Sign in once at https://www.zeroproofai.com/sign-in with an email code to lift trial limits."
|
|
@@ -321,7 +348,12 @@ def logout() -> bool:
|
|
|
321
348
|
|
|
322
349
|
|
|
323
350
|
def status() -> dict:
|
|
324
|
-
"""What the SDK would use right now, with the key masked.
|
|
351
|
+
"""What the SDK would use right now, with the key masked.
|
|
352
|
+
|
|
353
|
+
On a trial key ``trial_note`` says how many hosted situations the
|
|
354
|
+
daily allowance covers and names the offline writer that has none.
|
|
355
|
+
``tier`` comes from ``GET /me``, so reading it costs one call.
|
|
356
|
+
"""
|
|
325
357
|
env = getenv("API_KEY")
|
|
326
358
|
env_var = env_name("API_KEY")
|
|
327
359
|
saved = _read(credentials_path()) or {}
|
|
@@ -354,4 +386,5 @@ def status() -> dict:
|
|
|
354
386
|
trial = me.get("trial") or {}
|
|
355
387
|
out["trial_expires_at"] = trial.get("expires_at")
|
|
356
388
|
out["lift"] = trial.get("lift")
|
|
389
|
+
out["trial_note"] = trial_note(trial.get("daily_input_tokens"))
|
|
357
390
|
return out
|
|
@@ -79,6 +79,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
79
79
|
if args.command == "status":
|
|
80
80
|
shown = auth.status()
|
|
81
81
|
print(json.dumps(shown, indent=2))
|
|
82
|
+
if shown.get("trial_note"):
|
|
83
|
+
print(shown["trial_note"])
|
|
82
84
|
if not shown.get("configured"):
|
|
83
85
|
print(
|
|
84
86
|
"no API key configured: run `whileai login` or set WHILEAI_API_KEY",
|
|
@@ -147,7 +147,14 @@ from .score.optimize import (
|
|
|
147
147
|
)
|
|
148
148
|
from .score.pairwise import judge_pairs, pairwise_judge
|
|
149
149
|
from .score.passat import PassAt, pass_at
|
|
150
|
-
from .score.preflight import
|
|
150
|
+
from .score.preflight import (
|
|
151
|
+
FAILURE_CLASSES,
|
|
152
|
+
classify_failure,
|
|
153
|
+
coverage_gap,
|
|
154
|
+
dataset_report,
|
|
155
|
+
format_coverage_gap,
|
|
156
|
+
preflight,
|
|
157
|
+
)
|
|
151
158
|
from .score.privileged import format_leak_report, leak_report
|
|
152
159
|
from .score.publish_gate import PublishGateError, calibrate, publish_gate
|
|
153
160
|
from .score.quality import rank_rows, score_row
|
|
@@ -252,6 +259,7 @@ __all__ = [
|
|
|
252
259
|
"conduct_grade",
|
|
253
260
|
"connect",
|
|
254
261
|
"conversation",
|
|
262
|
+
"coverage_gap",
|
|
255
263
|
"curriculum",
|
|
256
264
|
"cut",
|
|
257
265
|
"cuts",
|
|
@@ -277,6 +285,7 @@ __all__ = [
|
|
|
277
285
|
"filter_rl_rows",
|
|
278
286
|
"flaw_rows",
|
|
279
287
|
"format_audit",
|
|
288
|
+
"format_coverage_gap",
|
|
280
289
|
"format_curriculum",
|
|
281
290
|
"format_delta_report",
|
|
282
291
|
"format_hack_monitor",
|
|
@@ -670,6 +670,10 @@ class SimulationData:
|
|
|
670
670
|
``ScoredData.rows`` is a list attribute and this used to be a
|
|
671
671
|
method only, so ``for r in data.rows`` raised ``TypeError``
|
|
672
672
|
without hinting at the missing parentheses.
|
|
673
|
+
|
|
674
|
+
The other direction still bites: ``ScoredData.rows``, what
|
|
675
|
+
``evaluate`` and ``grade`` hand back, is a plain list, so
|
|
676
|
+
``scored.rows()`` is a ``TypeError``. Method here, list there.
|
|
673
677
|
"""
|
|
674
678
|
return RowList(export_row(t) for t in self.trajectories)
|
|
675
679
|
|
|
@@ -15,8 +15,14 @@ from typing import Any
|
|
|
15
15
|
from urllib.parse import urlparse
|
|
16
16
|
|
|
17
17
|
from whileai._env import getenv
|
|
18
|
+
from whileai.auth import SIGN_IN_URL
|
|
18
19
|
|
|
19
20
|
from ..world.sandbox import MockEnvironment
|
|
21
|
+
from .anthropic_backend import ANTHROPIC_BASE_URL, is_anthropic_url
|
|
22
|
+
from .anthropic_backend import DEFAULT_MODEL as ANTHROPIC_DEFAULT_MODEL
|
|
23
|
+
from .anthropic_backend import complete as anthropic_complete
|
|
24
|
+
from .anthropic_backend import missing_key as missing_anthropic_key
|
|
25
|
+
from .anthropic_backend import resolve_key as anthropic_key
|
|
20
26
|
from .diversity import running_turn_mean, sample_turn_budget
|
|
21
27
|
from .usage_meter import report_usage
|
|
22
28
|
|
|
@@ -60,7 +66,7 @@ current_rollout = _CurrentRollout()
|
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
def parse_backend_spec(spec: str) -> tuple[str, str]:
|
|
63
|
-
"""Return (base_url, model) for ollama:/vllm:/openai: specs."""
|
|
69
|
+
"""Return (base_url, model) for ollama:/vllm:/openai:/anthropic: specs."""
|
|
64
70
|
kind, _, rest = str(spec).partition(":")
|
|
65
71
|
if kind == "ollama":
|
|
66
72
|
return "http://localhost:11434/v1", rest or "llama3.1:8b"
|
|
@@ -74,9 +80,14 @@ def parse_backend_spec(spec: str) -> tuple[str, str]:
|
|
|
74
80
|
os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1",
|
|
75
81
|
rest or "gpt-4o-mini",
|
|
76
82
|
)
|
|
83
|
+
if kind == "anthropic":
|
|
84
|
+
# The Messages API, on ANTHROPIC_API_KEY. The URL is fixed, so the
|
|
85
|
+
# spec is just the model name and every caller (writer, user model,
|
|
86
|
+
# agent, judge) records that name the way the other backends do.
|
|
87
|
+
return ANTHROPIC_BASE_URL, rest or ANTHROPIC_DEFAULT_MODEL
|
|
77
88
|
raise ValueError(
|
|
78
89
|
f"unsupported backend spec {spec!r}; use ollama:<model>, "
|
|
79
|
-
"vllm:<model>@<url>, or
|
|
90
|
+
"vllm:<model>@<url>, openai:<model>, or anthropic:<model>"
|
|
80
91
|
)
|
|
81
92
|
|
|
82
93
|
|
|
@@ -227,6 +238,8 @@ def resolve_completion_key(base_url: str | None = None, api_key: str | None = No
|
|
|
227
238
|
"""
|
|
228
239
|
if api_key:
|
|
229
240
|
return str(api_key).strip()
|
|
241
|
+
if is_anthropic_url(base_url):
|
|
242
|
+
return anthropic_key()
|
|
230
243
|
vllm = str(os.environ.get("VLLM_API_KEY") or "").strip()
|
|
231
244
|
if not base_url:
|
|
232
245
|
# no URL means the default agent, whichever route that resolves to
|
|
@@ -264,6 +277,8 @@ def missing_hosted_key(base_url: str | None = None, api_key: str | None = None)
|
|
|
264
277
|
endpoints run without one. Without this a bring-your-own run with no
|
|
265
278
|
key spent its whole time budget on 401s and returned nothing.
|
|
266
279
|
"""
|
|
280
|
+
if is_anthropic_url(base_url):
|
|
281
|
+
return missing_anthropic_key(api_key)
|
|
267
282
|
key = resolve_completion_key(base_url, api_key)
|
|
268
283
|
if key:
|
|
269
284
|
return None
|
|
@@ -285,12 +300,20 @@ MISSING_HOSTED_KEY = (
|
|
|
285
300
|
"VLLM_API_KEY for the shared pool."
|
|
286
301
|
)
|
|
287
302
|
QUOTA_MARK = "quota exceeded"
|
|
303
|
+
#: What to do about a spent daily allowance. A trial day is about 12
|
|
304
|
+
#: hosted situations (``auth.trial_note``), which one real run spends, so
|
|
305
|
+
#: the error names the writer that has no allowance and the sign-in that
|
|
306
|
+
#: lifts the limit instead of leaving the run dead with a number.
|
|
307
|
+
QUOTA_FIX = (
|
|
308
|
+
"simulate(..., simulator=False) writes the situations offline with no quota and no "
|
|
309
|
+
f"network; signing in once at {SIGN_IN_URL} lifts a trial key's daily limit."
|
|
310
|
+
)
|
|
288
311
|
|
|
289
312
|
|
|
290
313
|
def _quota_error(status: int, body: str) -> str | None:
|
|
291
314
|
"""The proxy's 429 for a spent daily allowance, or None. Not transient:
|
|
292
315
|
every later call today answers the same, so the run stops instead of
|
|
293
|
-
retrying into the clock."""
|
|
316
|
+
retrying into the clock. Carries ``QUOTA_FIX``, the two ways on."""
|
|
294
317
|
if int(status) != 429:
|
|
295
318
|
return None
|
|
296
319
|
text = str(body or "")
|
|
@@ -300,7 +323,8 @@ def _quota_error(status: int, body: str) -> str | None:
|
|
|
300
323
|
msg = json.loads(text).get("error", {}).get("message") or text
|
|
301
324
|
except (ValueError, AttributeError):
|
|
302
325
|
msg = text
|
|
303
|
-
|
|
326
|
+
head = f"Hosted model daily {msg[msg.lower().find('quota') :]}".rstrip(". ")
|
|
327
|
+
return f"{head}. {QUOTA_FIX}"
|
|
304
328
|
|
|
305
329
|
|
|
306
330
|
def _client_metered(base_url: str | None) -> bool:
|
|
@@ -541,7 +565,30 @@ def complete(
|
|
|
541
565
|
reply then carries ``_logprobs`` (sum, n, and with ``"tokens"`` the
|
|
542
566
|
per-token list). ``_finish_reason`` is always set from the first choice.
|
|
543
567
|
A server that rejects ``logprobs`` gets the request again without it.
|
|
568
|
+
|
|
569
|
+
An ``anthropic:`` spec goes to the Messages API instead, translated to
|
|
570
|
+
and from this same shape by ``anthropic_backend``. That API returns no
|
|
571
|
+
log-probabilities, so ``logprobs`` yields no ``_logprobs`` there.
|
|
544
572
|
"""
|
|
573
|
+
if is_anthropic_url(base_url):
|
|
574
|
+
# The Messages API, translated at the boundary. It runs before the
|
|
575
|
+
# context squeeze below because that budget is sized to hosted Qwen's
|
|
576
|
+
# 4k window, not to a 200k one; report_usage stays out of it because a
|
|
577
|
+
# bring-your-own model is the customer's own bill.
|
|
578
|
+
reply = anthropic_complete(
|
|
579
|
+
base_url,
|
|
580
|
+
model,
|
|
581
|
+
messages,
|
|
582
|
+
tools=tools,
|
|
583
|
+
api_key=api_key,
|
|
584
|
+
temperature=temperature,
|
|
585
|
+
max_tokens=max_tokens,
|
|
586
|
+
timeout=timeout,
|
|
587
|
+
n=n,
|
|
588
|
+
extra=extra,
|
|
589
|
+
)
|
|
590
|
+
_trim_length_cut({"finish_reason": reply.get("_finish_reason"), "message": reply})
|
|
591
|
+
return reply
|
|
545
592
|
key = resolve_completion_key(base_url, api_key)
|
|
546
593
|
auth_err = missing_hosted_key(base_url, key)
|
|
547
594
|
if auth_err:
|