ARBITER / arbiter /cli.py
Adwitiya Shukla
Rebuild from the repo
a6af15b
Raw
History Blame Contribute Delete
11.8 kB
from __future__ import annotations
import argparse
import sys
from typing import List, Optional
from . import __version__
from .config import RunConfig, key_for, load_dotenv
from .report import write_report
from .trial import metrics, run_suite
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="arbiter",
description="Reproduce web bug reports with an LLM actor, then have an independent "
"judge confirm or reject the claim from the evidence.")
p.add_argument("--version", action="version", version="arbiter " + __version__)
sub = p.add_subparsers(dest="command", required=True)
run = sub.add_parser("run", help="run the benchmark")
run.add_argument("--only", help="comma separated bug ids, default is all of them")
run.add_argument("--trials", type=int, default=3, help="repeats per bug, for the flakiness score")
run.add_argument("--provider", default="gemini", choices=["gemini", "openai", "anthropic", "mock"])
run.add_argument("--actor-model", default="gemini-3.5-flash-lite")
run.add_argument("--rpm", type=float, default=12.0,
help="client side pacing, requests per minute across all calls. "
"Free tiers throttle hard, so this matters more than retries.")
run.add_argument("--judge-provider", default="", help="defaults to the actor's provider")
run.add_argument("--judge-model", default="gemini-3.5-flash",
help="point this at a different model to strengthen judge independence")
run.add_argument("--record", action="store_true",
help="save every model exchange to traces/ so the run can be replayed offline")
run.add_argument("--headed", action="store_true", help="show the browser window")
run.add_argument("--video", action="store_true", help="record webm video of each trial")
run.add_argument("--out", default="docs", help="where the report is written")
run.add_argument("--bugs-dir", default="benchmark/bugs")
run.add_argument("--apps-dir", default="benchmark/apps")
run.add_argument("--evidence-dir", default="evidence")
run.add_argument("--trace-dir", default="traces")
rej = sub.add_parser("rejudge",
help="re-run only the judge over evidence already on disk")
rej.add_argument("--only", help="comma separated bug ids")
rej.add_argument("--all", action="store_true",
help="re-judge every trial, not just the unresolved ones")
rej.add_argument("--provider", default="gemini", choices=["gemini", "openai", "anthropic", "mock"])
rej.add_argument("--judge-provider", default="")
rej.add_argument("--judge-model", default="gemini-3.5-flash-lite")
rej.add_argument("--rpm", type=float, default=8.0)
rej.add_argument("--record", action="store_true",
help="record the review traffic so an offline replay covers these trials too")
rej.add_argument("--out", default="docs")
rej.add_argument("--bugs-dir", default="benchmark/bugs")
rej.add_argument("--evidence-dir", default="evidence")
rej.add_argument("--trace-dir", default="traces")
aud = sub.add_parser("judge-audit",
help="adversarial check: judge each run's evidence against a "
"report it does not match")
aud.add_argument("--only", help="comma separated bug ids")
aud.add_argument("--provider", default="gemini", choices=["gemini", "openai", "anthropic", "mock"])
aud.add_argument("--judge-provider", default="")
aud.add_argument("--judge-model", default="gemini-3.5-flash-lite")
aud.add_argument("--rpm", type=float, default=8.0)
aud.add_argument("--out", default="docs")
aud.add_argument("--bugs-dir", default="benchmark/bugs")
aud.add_argument("--evidence-dir", default="evidence")
smk = sub.add_parser("smoke", help="drive the benchmark with scripted actions, no API key needed")
smk.add_argument("--apps-dir", default="benchmark/apps")
smk.add_argument("--headed", action="store_true")
chk = sub.add_parser("check", help="verify the environment before a real run")
chk.add_argument("--provider", default="gemini", choices=["gemini", "openai", "anthropic", "mock"])
chk.add_argument("--actor-model", default="gemini-3.5-flash-lite")
chk.add_argument("--judge-model", default="gemini-3.5-flash")
return p
def cmd_check(args: argparse.Namespace) -> int:
ok = True
print("arbiter {0}".format(__version__))
try:
import playwright
print(" [ok] playwright is installed")
except ImportError:
print(" [fail] playwright is missing. Run: pip install -r requirements.txt")
return 1
try:
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
b = pw.chromium.launch(headless=True)
page = b.new_page()
page.set_content("<h1 data-testid='x'>hello</h1>")
title = page.text_content("[data-testid=x]")
b.close()
print(" [ok] chromium launches and renders ({0!r})".format(title))
except Exception as exc:
print(" [fail] chromium could not start: {0}".format(str(exc)[:200]))
print(" Run: python -m playwright install chromium")
ok = False
for mod in ("cv2", "numpy", "PIL", "yaml", "requests"):
try:
__import__(mod)
print(" [ok] {0}".format(mod))
except ImportError:
print(" [fail] {0} is missing".format(mod))
ok = False
key = key_for(args.provider)
if args.provider == "mock":
print(" [ok] mock provider needs no key")
elif not key:
print(" [fail] no API key found for provider {0}. Copy .env.example to .env "
"and fill it in.".format(args.provider))
ok = False
else:
print(" [ok] API key found ({0}...{1})".format(key[:6], key[-4:]))
from .llm.base import build_provider
for role, model in (("actor", args.actor_model), ("judge", args.judge_model)):
try:
reply = build_provider(args.provider, model, key).complete(
"You are a test harness.", "Reply with the single word: ready")
print(" [ok] {0} model {1} answered {2!r} ({3} prompt tokens)".format(
role, model, reply.text.strip()[:40], reply.usage.prompt_tokens))
except Exception as exc:
print(" [fail] {0} model {1} failed: {2}".format(role, model, str(exc)[:260]))
ok = False
if args.actor_model == args.judge_model:
print(" [warn] actor and judge share a model. The judge still cannot see the "
"actor's reasoning, but different models make the review more independent.")
print("\n{0}".format("environment looks good" if ok else "fix the failures above first"))
return 0 if ok else 1
def cmd_run(args: argparse.Namespace) -> int:
cfg = RunConfig(
provider=args.provider, actor_model=args.actor_model,
judge_provider=args.judge_provider, judge_model=args.judge_model,
trials=args.trials, rpm=args.rpm,
headless=not args.headed, record=args.record, video=args.video,
bugs_dir=args.bugs_dir, apps_dir=args.apps_dir, out_dir=args.out,
evidence_dir=args.evidence_dir, trace_dir=args.trace_dir, only=args.only)
if cfg.provider != "mock" and not key_for(cfg.provider):
print("No API key for provider {0}. Copy .env.example to .env and add your key, "
"or run with --provider mock to replay recorded traces.".format(cfg.provider))
return 1
from .llm.base import set_rate_limit
set_rate_limit(cfg.rpm)
suite = run_suite(cfg)
paths = write_report(suite, cfg.out_dir)
m = metrics(suite)
print("\n" + "=" * 66)
print("seeded bugs reproduced {0}/{1} ({2:.0%})".format(
m["reproduced"], m["seeded_bugs"], m["reproduction_rate"]))
print("false positives {0}/{1} controls".format(m["false_positives"], m["controls"]))
print("overall accuracy {0:.0%}".format(m["accuracy"]))
print("actor claimed {0} trials".format(m["actor_claimed"]))
print("judge confirmed {0} trials".format(m["judge_confirmed"]))
print("judge rejected {0} trials ({1:.0%} of claims were overclaims)".format(
m["judge_rejected"], m["overclaim_rate"]))
print("never resolved {0} trials (judge unavailable or unsure)".format(
m["unresolved"]))
print("cost ${0:.4f} over {1} calls".format(
m["cost"]["usd"], m["cost"]["calls"]))
print("=" * 66)
print("report {0}".format(paths["html"]))
print("data {0}".format(paths["json"]))
return 0
def cmd_rejudge(args: argparse.Namespace) -> int:
from .llm.base import set_rate_limit
from .rejudge import rejudge
cfg = RunConfig(provider=args.provider, judge_provider=args.judge_provider,
judge_model=args.judge_model, rpm=args.rpm, bugs_dir=args.bugs_dir,
evidence_dir=args.evidence_dir, trace_dir=args.trace_dir,
out_dir=args.out, only=args.only, record=args.record)
if cfg.effective_judge_provider != "mock" and not key_for(cfg.effective_judge_provider):
print("No API key for provider {0}.".format(cfg.effective_judge_provider))
return 1
set_rate_limit(cfg.rpm)
suite = rejudge(cfg, only_unresolved=not args.all)
if not suite.results:
print("no evidence found under {0}. Run the benchmark first.".format(cfg.evidence_dir))
return 1
paths = write_report(suite, cfg.out_dir)
m = metrics(suite)
print("\n" + "=" * 66)
print("seeded bugs reproduced {0}/{1} ({2:.0%})".format(
m["reproduced"], m["seeded_bugs"], m["reproduction_rate"]))
print("false positives {0}/{1} controls".format(m["false_positives"], m["controls"]))
print("overall accuracy {0:.0%}".format(m["accuracy"]))
print("actor claimed {0} trials".format(m["actor_claimed"]))
print("judge confirmed {0} trials".format(m["judge_confirmed"]))
print("judge rejected {0} trials ({1:.0%} of claims were overclaims)".format(
m["judge_rejected"], m["overclaim_rate"]))
print("never resolved {0} trials".format(m["unresolved"]))
print("=" * 66)
print("report {0}".format(paths["html"]))
return 0
def main(argv: Optional[List[str]] = None) -> int:
load_dotenv()
args = build_parser().parse_args(argv)
if args.command == "run":
return cmd_run(args)
if args.command == "check":
return cmd_check(args)
if args.command == "judge-audit":
from .audit import run_audit
from .llm.base import set_rate_limit
cfg = RunConfig(provider=args.provider, judge_provider=args.judge_provider,
judge_model=args.judge_model, rpm=args.rpm, bugs_dir=args.bugs_dir,
evidence_dir=args.evidence_dir, out_dir=args.out, only=args.only)
if cfg.effective_judge_provider != "mock" and not key_for(cfg.effective_judge_provider):
print("No API key for provider {0}.".format(cfg.effective_judge_provider))
return 1
set_rate_limit(cfg.rpm)
run_audit(cfg)
return 0
if args.command == "rejudge":
return cmd_rejudge(args)
if args.command == "smoke":
from .smoke import run_smoke
return run_smoke(args.apps_dir, headless=not args.headed)
return 1
if __name__ == "__main__":
sys.exit(main())