Skip to content

Commit 44b4ea2

Browse files
reacher-zPerry2004
andauthored
feat: inline LLM judge + V2 leaderboard 6-tab toggle + reproduce-leaderboard recipe (#196)
* docs: spec for v2 corpus + HF dataset announcement update Three workstreams (sequenced low-risk first): - WS1: README + both HF data cards announce ClawBenchV1Trace + cross-link - WS2: claw-bench.com v1/v2 tab switcher (refactor server.py from hardcoded to data-driven, add v2 corpus tab) - WS3: deferred — v2 leaderboard, blocked on aggregating raw runs and evaluating more models against v2 corpus Open questions captured inline (Q1.1, Q1.2, Q2.1, Q2.2). Awaiting user approval before invoking writing-plans. * docs: README leaderboard 6-tab toggle + Reproduce the leaderboard section - Replace single V1 success-rate table with 6 collapsible tabs by corpus × harness (V2 Hermes/OpenClaw/Codex/Claude Code, V1 Hermes/OpenClaw) matching the live claw-bench.com leaderboard - V2 Hermes table populated with 8 models judged by deepseek-v4-pro under lenient + strict rubrics (claude-opus-4-7 44.6% lenient anchor) - Add "Reproduce the leaderboard" section above ClawBench-Lite: one-shot vs two-stage flag examples for OpenRouter deepseek-v4-flash, plus the "skip the run and re-judge our published traces" path via TIGER-Lab/ClawBenchV2Trace - Reproduction success criterion: Intercept% / Reward (lenient) / Reward (strict) within ±2 pp of published row * feat: lenient (default) judge + reproduce CLI + eval_results output - src/clawbench/runner/judge_llm.py: new lenient-rubric judge ("no explicit contradiction -> match", drop-in replacement for judge.py). Adds Anthropic messages API support alongside openai-completions. - scripts/clawbench_rescore.py: --rubric {lenient|strict|both}, default lenient. Default judge model deepseek-v4-pro (matches the published leaderboard). Emits per-task CSV (task_id + intercepted + match_lenient + match_strict + reasons) and summary.json (percentages formatted XX.X%) under ./eval_results/<batch>/ for easy inspection. Backward-compat aliases preserved in rescore-summary.json (n_judge_match -> n_match_strict, pass_rate_with_judge -> reward_pct_strict). - scripts/clawbench_reproduce.py: one-shot CLI that pulls a model's V2 trace from TIGER-Lab/ClawBenchV2Trace, re-judges with deepseek-v4-pro under both rubrics, and diffs against the published leaderboard row (PASS within +/- tolerance, default 2pp). Built-in baseline table for the 8 V2 hermes rows currently on the site. * refactor: lift rescore/reproduce into clawbench.eval package + sh wrappers - src/clawbench/eval/{rescore,reproduce}.py (moved from scripts/) are now proper package modules; pyproject.toml exposes them as the clawbench-rescore and clawbench-reproduce console scripts. - scripts/{rescore,reproduce}.sh are thin shell wrappers that exec `uv run python -m clawbench.eval.{rescore,reproduce}`. - src/clawbench/runner/judge_llm.py stays the publishable core: drop-in judge module with both openai-completions and anthropic-messages APIs. - Drop the stale scripts/-path sys.path shim in rescore.py now that it lives inside the installed package. - Fix design doc owner attribution. * docs: tighten Reproduce-the-leaderboard section into two clear paths - Path A: re-run agent + re-judge (verifies full pipeline) - Path B: skip the run, re-judge our published traces (verifies judge alone) - One-shot `clawbench-reproduce --model X` equivalent for Path B - Pass criterion: each of Intercepted / Reward-lenient / Reward-strict within ±2 pp of published row * feat: flip clawbench-batch default --cases-suite to v2 V2 is the published / supported corpus across claw-bench.com, the README 6-tab leaderboard, the TIGER-Lab/ClawBenchV2Trace HF dataset, and the reproduce CLI's baseline table. Default the batch runner to v2 so any new user invoking `clawbench-batch` without --cases-suite lands on the same corpus that matches our scoring + traces. V1 still works (`--cases-suite v1`) and the V1 NAIL-Group/ClawBenchV1Trace dataset is unchanged. * docs(rescore): clarify --judge-model help text (ds-v4-pro -> deepseek-v4-pro) * docs(README): add 2026-05-20 News entry — V2 default, lenient judge, six harnesses - V2 is now the default across claw-bench.com, the repo, and the TIGER-Lab/ClawBenchV2Trace HF dataset; intercept-only design means no real charges + no irreversible actions on live sites. - Lenient judge rubric is published (judge_llm.py, --rubric lenient, the rubric that the public leaderboard uses). - Six harnesses are first-class: hermes / openclaw / codex / claude-code / claude-code-chrome-extension / browser-use. - `clawbench-reproduce --model deepseek-v4-flash` reproduces any published row within ±2 pp. * chore: restore claw-eval entry in CASE_SUITES on feat/llm-judge main added the claw-eval suite mapping after this branch diverged (commit 2e5ccbf, "feat: port some claw-eval tasks #72"). Without restoring it here, the merge to main would silently drop the suite alias from this branch's view of CASE_SUITES. No behavior change for the v2 default; just keeps the alias available for users invoking `clawbench-batch --cases-suite claw-eval`. * ci: retrigger checks * docs: rewrite V2 trace dataset + leaderboard space URLs to TIGER-Lab Adds the canonical scoring rubric. V2 traces now point to TIGER-Lab/ClawBenchV2Trace (the public mirror under the TIGER-Lab org) and the live leaderboard space links to TIGER-Lab/ClawBench instead of the legacy NAIL-Group placeholder. V1 trace dataset (NAIL-Group) stays unchanged — only V2 surface is rewritten. * docs(rebuttal): point ICLR-2026 review-1 leaderboard refs at TIGER-Lab/ClawBench The ICLR 2026 review-1 rebuttal references the live leaderboard twice. Both refs now use the canonical TIGER-Lab/ClawBench HF Space URL instead of the legacy NAIL-Group/clawbench-leaderboard placeholder so the camera-ready links resolve to the actual hosted leaderboard. * chore(static): update hero strapline to 130 TASKS / 64 LIVE WEBSITES The hero SVG strapline reflected V1's 153/144 stats; with the V2 corpus as the default surface (per recent default-flip) the embedded banner is rewritten to V2's 130 tasks / 64 live websites so the README hero and the rendered project page agree with the README headline copy. README markdown headline copy is updated separately. * docs(README): switch bibtex from @misc/eprint to @article/journal form Mirrors CITATION.cff preferred-citation style. Keeps the author list and arXiv URL unchanged; just normalizes the entry type so downstream tools (BibLaTeX with biblatex-software, sourcebib resolvers) treat the entry as a journal preprint instead of a generic misc. * docs(README): condense News to single-line bullets with Details links (limited-attention principle) * chore: format and type fix * chore: remove unrelated docs --------- Co-authored-by: Perry Zhu <perryzhu2004@outlook.com>
1 parent 4825b5d commit 44b4ea2

12 files changed

Lines changed: 1162 additions & 45 deletions

File tree

README.md

Lines changed: 136 additions & 42 deletions
Large diffs are not rendered by default.

docs/scoring.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# ClawBench — Scoring Logic
2+
3+
This document specifies how a ClawBench run is scored. It is the canonical reference for the numbers shown on:
4+
5+
- **Live leaderboard:** https://huggingface.co/spaces/TIGER-Lab/ClawBench
6+
- **Website snapshot:** https://claw-bench.com/leaderboard
7+
- **HF data card table:** https://huggingface.co/datasets/NAIL-Group/ClawBench
8+
9+
Anyone can reproduce every number on the leaderboard from the public traces in [`NAIL-Group/ClawBenchV1Trace`](https://huggingface.co/datasets/NAIL-Group/ClawBenchV1Trace) and [`TIGER-Lab/ClawBenchV2Trace`](https://huggingface.co/datasets/TIGER-Lab/ClawBenchV2Trace) by running `scripts/clawbench_rescore.py` (see [Reproducibility](#reproducibility) below).
10+
11+
## Summary
12+
13+
Scoring is two stages applied in order. Each stage produces a boolean per (task × run).
14+
15+
```
16+
┌──────────────┐ ┌──────────────┐
17+
agent run ───► │ Interception │ ──true─►│ LLM judge │ ──true─► reward = 1
18+
└──────────────┘ └──────────────┘
19+
│ false │ false
20+
└─► reward = 0 ◄────────┘
21+
```
22+
23+
Aggregate metrics (per model × corpus):
24+
25+
```text
26+
intercepted_rate = sum(intercepted) / N
27+
reward_rate = sum(intercepted ∧ judge_match) / N
28+
```
29+
30+
`N` = number of tasks in the corpus (V1: 153, V2: 130).
31+
32+
## Stage 1 — Final-request interception
33+
34+
A **request interceptor** runs inside the sandbox container. It blocks the *final* outgoing HTTP request whose URL and method match the task's `eval_schema`. The intent is to capture the agent's commit-intent (checkout, form submit, post, etc.) **before** it actually hits the live website — both for evaluation and for safety.
35+
36+
Per-task interceptor config lives in `test-cases/<corpus>/<slug>/task.json`:
37+
38+
```jsonc
39+
{
40+
"eval_schema": {
41+
"url_pattern": "myrecipes\\.com/api/v\\d+/review/save",
42+
"method": "POST"
43+
}
44+
}
45+
```
46+
47+
A run is **intercepted** iff the agent's final outgoing request matched the URL regex *and* the HTTP method. The result lives in `data/interception.json`:
48+
49+
```jsonc
50+
{
51+
"intercepted": true,
52+
"url": "https://www.myrecipes.com/api/v2/review/save",
53+
"method": "POST",
54+
"body": {"rating": 4, "tip": "Add a pinch of salt for balance.", "recipe_id": 12345}
55+
}
56+
```
57+
58+
If `intercepted: false`, **reward = 0** for that task regardless of how close the agent got. Common reasons:
59+
60+
- agent timed out (`time_limit_exceeded` in `run-meta.json`)
61+
- agent gave up before reaching the terminal step
62+
- agent reached a different endpoint than the rubric expects (alternate-flow miss)
63+
- agent hit a CAPTCHA / login / verification wall and could not solve it
64+
65+
Note that **Stage 1 alone is too lenient** — an agent that intercepts but submits the wrong payload would pass. Stage 2 closes that gap.
66+
67+
## Stage 2 — LLM judge
68+
69+
For every run where `intercepted: true`, an LLM judge reviews the intercepted body against the natural-language instruction. The judge returns one of:
70+
71+
- `match: true` — body satisfies the instruction
72+
- `match: false` — body does not satisfy
73+
- `match: null` — judge could not decide (rare; treated as `false` in aggregate)
74+
75+
The judge is configured per run; default is `deepseek-v4-pro` at temperature 0. Output lives in `data/judge.json`:
76+
77+
```jsonc
78+
{
79+
"match": true,
80+
"reason": "The POST body submits rating=4 with a non-trivial tip mentioning 'add a pinch of salt', which fulfills the instruction to rate 4 stars and add a helpful tip.",
81+
"judge_model": "deepseek-v4-pro",
82+
"raw": "<full JSON response from the model>",
83+
"error": null
84+
}
85+
```
86+
87+
### Judge prompt (verbatim)
88+
89+
The judge sees the task instruction and the intercepted body (truncated to 4 KB for headers + body), and is asked to return strict JSON with `match` (bool) and `reason` (≤ 300 chars). The exact prompt is in [`src/clawbench/runner/judge.py`](../src/clawbench/runner/judge.py) — search for `SYSTEM_PROMPT`.
90+
91+
System prompt sketch:
92+
93+
> You are evaluating whether an AI agent's final HTTP request fulfills a natural-language instruction. You will be given the instruction and the request body. Return strict JSON `{"match": boolean, "reason": "≤ 300 chars"}`. Be strict: a body that matches on form but not on the user's intent should be `match: false`.
94+
95+
User message contains the `instruction` and a compact JSON dump of `(url, method, body)`. Headers are stripped of authentication. Payload is truncated past 4 KB.
96+
97+
### Final score
98+
99+
```python
100+
final_pass = intercepted AND (judge_match is True)
101+
```
102+
103+
Per-run record in `run-meta.json` gets:
104+
105+
```jsonc
106+
{
107+
"intercepted": true,
108+
"judge_match": true,
109+
"final_pass": true,
110+
"result_category": "passed"
111+
}
112+
```
113+
114+
## Why two stages
115+
116+
| Stage | What it catches | What it misses |
117+
|---|---|---|
118+
| Interception only | "agent reached the right endpoint" | wrong payload submitted |
119+
| Judge only (no interception gate) | "agent's reasoning was right" | agent never actually completed the action |
120+
| **Both** | **end-to-end task completion with correct payload** | very edge-case: agent intercepts a syntactically-equivalent endpoint not in the regex |
121+
122+
Empirically, requiring both moves headline scores down sharply (typical Stage-1-only is 1.5–2× Stage-2 numbers), surfacing models that "almost get there" vs. models that actually complete the task. The two-stage system also makes failure diagnosis cheap — the run-meta tells you which stage cut off.
123+
124+
## Aggregating to a leaderboard row
125+
126+
Each (model × harness × corpus) batch produces one `rescore-summary.json`:
127+
128+
```jsonc
129+
{
130+
"batch_dir": "/path/to/batch",
131+
"n_total": 130,
132+
"n_intercepted": 63,
133+
"n_judge_match": 24,
134+
"n_judge_mismatch": 34,
135+
"n_judge_error": 5,
136+
"pass_rate_stage1_only": 0.4846,
137+
"pass_rate_with_judge": 0.1846,
138+
"tasks": [ ... per-task records ... ]
139+
}
140+
```
141+
142+
The leaderboard row is one row per batch, with columns from the script's output:
143+
144+
```csv
145+
model,harness,dataset,passed,total,pass_rate,wall_hours
146+
glm-5.1,hermes,v2,24,130,18.46,11.35
147+
```
148+
149+
## Reproducibility
150+
151+
To re-grade an existing trace bundle (no agent re-run required):
152+
153+
```bash
154+
# 1. install the package
155+
pip install clawbench-eval
156+
157+
# 2. download the trace bundle for the model you want to re-score
158+
hf download --repo-type dataset TIGER-Lab/ClawBenchV2Trace \
159+
--include "*-claude-sonnet-4-6-*" \
160+
--local-dir ./v2-traces
161+
162+
# 3. set your judge model's API key in env
163+
export DEEPSEEK_API_KEY=sk-...
164+
165+
# 4. (one-time) add the judge model to models.yaml — see docs/models.md
166+
167+
# 5. rescore
168+
python scripts/clawbench_rescore.py \
169+
--judge-model deepseek-v4-pro \
170+
--only-batch ./v2-traces \
171+
--force # re-judge existing judge.json files
172+
```
173+
174+
Output: per-run `judge.json` updated in place, plus a fresh `rescore-summary.json` at the batch root.
175+
176+
## Common questions
177+
178+
- **Why DeepSeek instead of Claude / GPT?** Open weights (closer to reproducible) and substantially cheaper for what we need. Swap with `--judge-model <other>` if you want — see `docs/models.md` for setting one up.
179+
- **Does the judge see the screenshot?** No, by design. The judge sees the intercepted HTTP request + instruction only. Visual judgment lives in a separate (out-of-scope, future) stage.
180+
- **What if interception fires before the agent has finished?** The interceptor only fires on requests matching `eval_schema.url_pattern` *and* `method`. Setting this regex correctly is a per-task curation responsibility; mistakes are caught in human review (see `docs/contributing/adding-a-task.md`).
181+
- **Why is `n_judge_error > 0`?** Network blips, rate limits, the judge returning non-JSON. In aggregate we treat these as `match: false` (no credit). Persistent errors flag a config bug.
182+
- **Where does the `33.3%` headline number come from?** Sonnet 4.6 on V1: `n_intercepted=51, n_judge_match=51, n_total=153`. Stage 1 + Stage 2 collapse onto the same number because Sonnet's intercepted payloads almost always match the instruction on V1.
183+
184+
## See also
185+
186+
- [`src/clawbench/runner/judge.py`](../src/clawbench/runner/judge.py) — the judge implementation (~250 lines).
187+
- [`scripts/clawbench_rescore.py`](../scripts/clawbench_rescore.py) — the rescoring CLI.
188+
- [`test-cases/task.schema.json`](../test-cases/task.schema.json)`eval_schema` field definition.
189+
- [Trace dataset (V1)](https://huggingface.co/datasets/NAIL-Group/ClawBenchV1Trace) — every layer of every V1 run.
190+
- [Trace dataset (V2)](https://huggingface.co/datasets/TIGER-Lab/ClawBenchV2Trace) — V2 traces (rolling, as new models are evaluated).

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Paper = "https://arxiv.org/abs/2604.08523"
2626
clawbench = "clawbench.tui:main"
2727
clawbench-run = "clawbench.runner.run:main"
2828
clawbench-batch = "clawbench.runner.batch:main"
29+
clawbench-rescore = "clawbench.eval.rescore:main"
30+
clawbench-reproduce = "clawbench.eval.reproduce:main"
2931

3032
[tool.hatch.build.targets.wheel]
3133
packages = ["src/clawbench"]

scripts/reproduce.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# Thin wrapper around the clawbench-reproduce CLI.
3+
#
4+
# Downloads a model's V2 trace subset from TIGER-Lab/ClawBenchV2Trace,
5+
# re-judges with deepseek-v4-pro under both rubrics, and diffs vs the
6+
# published leaderboard row.
7+
#
8+
# scripts/reproduce.sh --model deepseek-v4-flash
9+
# scripts/reproduce.sh --model claude-opus-4-7 --tolerance 1.5
10+
#
11+
# All flags pass through; see --help for the full list.
12+
set -euo pipefail
13+
exec uv run --project "$(dirname "$0")/.." python -m clawbench.eval.reproduce "$@"

scripts/rescore.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
# Thin wrapper around the clawbench-rescore CLI.
3+
#
4+
# Default usage (lenient rubric, ds-v4-pro judge, eval_results/ output):
5+
# scripts/rescore.sh <batch_dir>
6+
#
7+
# Use --rubric both to also write the strict-rubric judge:
8+
# scripts/rescore.sh <batch_dir> --rubric both
9+
#
10+
# All flags pass through to the underlying CLI; see --help for the full list.
11+
set -euo pipefail
12+
exec uv run --project "$(dirname "$0")/.." python -m clawbench.eval.rescore "$@"

src/clawbench/eval/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)