Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/nullius/runs/classify.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
# one can place it here.
_AGENT_EXCEPTIONS = {
"agenttimeouterror", # the agent spent its budget — that is a result
"unitspendingcaperror", # the unit spent its own spending cap (the standard's relay): the same, in money
"agentsafetyrefusalerror", # the agent (or its provider) chose to refuse
"contextwindowexceedederror", # the agent filled its own context
"contextlengthexceedederror", # same, from Harbor's LLM layer
Expand Down Expand Up @@ -131,7 +132,7 @@
NO_PATCH: "The agent finished without producing a patch at all.",
AGENT_ERROR: (
"The agent itself failed — it timed out, filled its context, truncated its "
"own output, refused, or its process exited non-zero. Counted against the "
"own output, refused, spent its own spending cap, or its process exited non-zero. Counted against the "
"agent, because each of those is a result rather than an interruption."
),
RATE_LIMITED: (
Expand Down
16 changes: 11 additions & 5 deletions app/nullius/runs/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# The shape this module produces. A workspace caches parsed Jobs keyed on it,
# so bump it when a Trial or Job field is added, renamed or means something
# new — otherwise a cache written by the old parse is read back as current.
SCHEMA = "14"
SCHEMA = "15"

# What a rate-limit record in `agent/gateway.log` looks like. The agent's SDK
# retries a 429 below the harness (an agent's `max_retries` reaching its
Expand Down Expand Up @@ -284,7 +284,8 @@ class Job:
# recipe from when the run kept none. Empty when config.json is absent.
launch: dict = field(default_factory=dict)
# A run with a spending cap (a Nullius standard job's ``result.json``, ``budget``): the cap and what was
# spent in USD, whether the cap stopped the run, the units it cut off and those never begun. {} without one.
# spent in USD, whether the cap stopped the run, the units it cut off and those never begun; a cap per unit
# (``unit_max_usd``) and the units that reached it (``over_unit``). {} without one.
budget: dict = field(default_factory=dict)
# A Harbor job run with several agents or models (`-m a -m b`, or a config
# listing several): each one's {name, model, effort}, which split_agents
Expand Down Expand Up @@ -837,11 +838,16 @@ def _budget(b) -> dict:
"""A job's recorded spending cap, read leniently: {} unless it names a cap; an amount that is no number is
None (not reported), never zero."""
num = lambda v: v if isinstance(v, (int, float)) and not isinstance(v, bool) else None
if not isinstance(b, dict) or not num(b.get("max_usd")):
if not isinstance(b, dict) or not (num(b.get("max_usd")) or num(b.get("unit_max_usd"))):
return {}
names = lambda v: [str(x) for x in v] if isinstance(v, list) else []
return {"max_usd": b["max_usd"], "spent_usd": num(b.get("spent_usd")), "unsettled_usd": num(b.get("unsettled_usd")),
"stopped": b.get("stopped") is True, "cut_off": names(b.get("cut_off")), "not_run": names(b.get("not_run"))}
return {"max_usd": num(b.get("max_usd")), "spent_usd": num(b.get("spent_usd")), "unsettled_usd": num(b.get("unsettled_usd")),
# only the run's cap stops a run: without one there is no stop to say
"stopped": b.get("stopped") is True and num(b.get("max_usd")) is not None,
"cut_off": names(b.get("cut_off")), "not_run": names(b.get("not_run")),
"unit_max_usd": num(b.get("unit_max_usd")), "over_unit": names(b.get("over_unit")),
# a merged job: its shards, and how many stopped at their share
"shards": num(b.get("shards")) or 0, "shards_stopped": num(b.get("shards_stopped")) or 0}


def parse_job(job_dir: str, reuse=None) -> Job:
Expand Down
7 changes: 5 additions & 2 deletions app/nullius/standard/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
PYTHONPATH=app python3 -m nullius.standard.cli new benchmark|agent|capability KEY [--family F] [--port P]
PYTHONPATH=app python3 -m nullius.standard.cli new version KEY VERSION
PYTHONPATH=app python3 -m nullius.standard.cli conform AGENT|CAPABILITY --model M [--provider P] [--port PORT ...] [--repeat K] [--option NAME=VALUE ...] [--unsandboxed | --backend docker]
PYTHONPATH=app python3 -m nullius.standard.cli run BENCHMARK[@VERSION] AGENT|CAPABILITY --model M [--provider P] [--limit N] [-j N] [--option NAME=VALUE ...] [--bench-option NAME=VALUE ...] [--price MODEL=INPUT,OUTPUT[,CACHED] ...] [--max-usd USD] [--unsandboxed | --backend docker]
PYTHONPATH=app python3 -m nullius.standard.cli run BENCHMARK[@VERSION] AGENT|CAPABILITY --model M [--provider P] [--limit N] [-j N] [--option NAME=VALUE ...] [--bench-option NAME=VALUE ...] [--price MODEL=INPUT,OUTPUT[,CACHED] ...] [--max-usd USD] [--max-usd-per-unit USD] [--unsandboxed | --backend docker]
PYTHONPATH=app python3 -m nullius.standard.cli run BENCHMARK[@VERSION] nop|oracle [--dataset D] [--limit N]
PYTHONPATH=app python3 -m nullius.standard.cli prove BENCHMARK[@VERSION] [--dataset D] [--limit N]
PYTHONPATH=app python3 -m nullius.standard.cli run ... --shard I/N [--launch ID]
Expand Down Expand Up @@ -469,7 +469,8 @@ def cmd_run(a):
concurrency=a.concurrency, jobs_root=a.jobs, name=a.name, unsandboxed=a.unsandboxed, judge_model=a.judge_model, backend=a.backend,
simulator_model=a.simulator_model, options=options(a.option), shard=_shard(a.shard), launch=a.launch,
licences=held, bench_options=options(a.bench_option), repeat=a.repeat, remove_images=a.remove_images,
prices=prices(a.price), max_usd=a.max_usd, **({"env": run_env(a.provider)} if a.from_run else {}))
prices=prices(a.price), max_usd=a.max_usd, max_usd_per_unit=a.max_usd_per_unit,
**({"env": run_env(a.provider)} if a.from_run else {}))
except run.Refused as e:
print("refused:", e)
return 2
Expand Down Expand Up @@ -626,6 +627,8 @@ def main(argv=None):
help="this run's price for a model in USD per million tokens, over your price list (repeatable; nullius prices)")
p.add_argument("--max-usd", type=float, metavar="USD",
help="the run's spending cap: no call goes out once the spend plus the calls in flight would pass it")
p.add_argument("--max-usd-per-unit", type=float, metavar="USD",
help="each unit's own spending cap: a unit that reaches it is cut off there, and the run goes on")
v = sub.add_parser("prove", help="prove a benchmark: its controls, its units, no leak; record what passed")
v.add_argument("benchmark")
v.add_argument("--dataset", help="one dataset (default: each)")
Expand Down
Loading
Loading