agentbox-rl 0.2.0__py3-none-any.whl
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.
- agentbox/__init__.py +52 -0
- agentbox/agent/__init__.py +6 -0
- agentbox/agent/agent.py +59 -0
- agentbox/agent/loop.py +201 -0
- agentbox/agent/prompts.py +44 -0
- agentbox/benchmark/__init__.py +29 -0
- agentbox/benchmark/compare.py +51 -0
- agentbox/benchmark/hash.py +59 -0
- agentbox/benchmark/loader.py +152 -0
- agentbox/benchmark/report.py +175 -0
- agentbox/benchmark/runner.py +131 -0
- agentbox/benchmark/schema.py +198 -0
- agentbox/benchmark/setup_check.py +57 -0
- agentbox/cli/__init__.py +1 -0
- agentbox/cli/bench.py +571 -0
- agentbox/cli/generate.py +314 -0
- agentbox/cli/main.py +276 -0
- agentbox/cli/traj.py +82 -0
- agentbox/config.py +84 -0
- agentbox/config_load.py +107 -0
- agentbox/errors.py +79 -0
- agentbox/jobs/__init__.py +15 -0
- agentbox/jobs/artifacts.py +65 -0
- agentbox/jobs/cache.py +39 -0
- agentbox/jobs/types.py +67 -0
- agentbox/metrics/__init__.py +5 -0
- agentbox/metrics/aggregate.py +63 -0
- agentbox/model/__init__.py +17 -0
- agentbox/model/base.py +32 -0
- agentbox/model/errors.py +5 -0
- agentbox/model/mock.py +43 -0
- agentbox/model/openai_compat.py +153 -0
- agentbox/model/probe.py +186 -0
- agentbox/py.typed +0 -0
- agentbox/runner/__init__.py +6 -0
- agentbox/runner/parallel.py +194 -0
- agentbox/runner/rollout.py +251 -0
- agentbox/sandbox/__init__.py +15 -0
- agentbox/sandbox/docker_backend.py +158 -0
- agentbox/sandbox/images.py +29 -0
- agentbox/sandbox/manager.py +265 -0
- agentbox/sandbox/paths.py +65 -0
- agentbox/sandbox/prune.py +42 -0
- agentbox/sandbox/snapshot.py +43 -0
- agentbox/sandbox/types.py +67 -0
- agentbox/tasks/__init__.py +18 -0
- agentbox/tasks/bulk.py +67 -0
- agentbox/tasks/filter.py +100 -0
- agentbox/tasks/generate/__init__.py +42 -0
- agentbox/tasks/generate/batch.py +308 -0
- agentbox/tasks/generate/constraints.py +41 -0
- agentbox/tasks/generate/dedup.py +109 -0
- agentbox/tasks/generate/generator.py +665 -0
- agentbox/tasks/generate/llm_validate.py +325 -0
- agentbox/tasks/generate/lm.py +79 -0
- agentbox/tasks/generate/prompts.py +7 -0
- agentbox/tasks/generate/review.py +111 -0
- agentbox/tasks/generate/signatures.py +178 -0
- agentbox/tasks/generate/static_qc.py +236 -0
- agentbox/tasks/generate/strip_impl.py +71 -0
- agentbox/tasks/generate/validate.py +212 -0
- agentbox/tasks/loader.py +15 -0
- agentbox/tasks/rewards.py +47 -0
- agentbox/tasks/schema.py +79 -0
- agentbox/tasks/seeder.py +78 -0
- agentbox/tasks/verifier.py +65 -0
- agentbox/tools/__init__.py +16 -0
- agentbox/tools/base.py +54 -0
- agentbox/tools/builtins.py +31 -0
- agentbox/tools/decorator.py +89 -0
- agentbox/tools/executor.py +87 -0
- agentbox/tools/file_tools.py +162 -0
- agentbox/tools/registry.py +99 -0
- agentbox/tools/shell.py +63 -0
- agentbox/tools/tests_tool.py +35 -0
- agentbox/trajectory/__init__.py +12 -0
- agentbox/trajectory/formats/__init__.py +6 -0
- agentbox/trajectory/formats/art.py +68 -0
- agentbox/trajectory/formats/jsonl.py +17 -0
- agentbox/trajectory/recorder.py +76 -0
- agentbox/trajectory/render.py +334 -0
- agentbox/trajectory/schema.py +124 -0
- agentbox/types.py +42 -0
- agentbox/version.py +3 -0
- agentbox_rl-0.2.0.dist-info/METADATA +427 -0
- agentbox_rl-0.2.0.dist-info/RECORD +89 -0
- agentbox_rl-0.2.0.dist-info/WHEEL +4 -0
- agentbox_rl-0.2.0.dist-info/entry_points.txt +2 -0
- agentbox_rl-0.2.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Build and render benchmark reports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from agentbox.benchmark.schema import (
|
|
10
|
+
BenchmarkReport,
|
|
11
|
+
ModelReport,
|
|
12
|
+
TaskResult,
|
|
13
|
+
)
|
|
14
|
+
from agentbox.metrics.aggregate import AggregateMetrics, aggregate_trajectories
|
|
15
|
+
from agentbox.trajectory.schema import Trajectory
|
|
16
|
+
from agentbox.types import FinalStatus
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def redact_model_config(cfg: dict[str, Any]) -> dict[str, Any]:
|
|
20
|
+
out = dict(cfg)
|
|
21
|
+
if "api_key" in out:
|
|
22
|
+
out["api_key"] = "***" if out["api_key"] else None
|
|
23
|
+
return out
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def build_task_results(
|
|
27
|
+
model_id: str,
|
|
28
|
+
trajs: Sequence[Trajectory],
|
|
29
|
+
*,
|
|
30
|
+
trajectory_paths: dict[str, list[str]] | None = None,
|
|
31
|
+
) -> list[TaskResult]:
|
|
32
|
+
by_task: dict[str, list[Trajectory]] = defaultdict(list)
|
|
33
|
+
for t in trajs:
|
|
34
|
+
by_task[t.task_id].append(t)
|
|
35
|
+
|
|
36
|
+
results: list[TaskResult] = []
|
|
37
|
+
for task_id, group in sorted(by_task.items()):
|
|
38
|
+
n = len(group)
|
|
39
|
+
successes = sum(1 for x in group if x.final_status == FinalStatus.SUCCESS)
|
|
40
|
+
statuses: dict[str, int] = defaultdict(int)
|
|
41
|
+
seed_errors = 0
|
|
42
|
+
setup_fails = 0
|
|
43
|
+
for x in group:
|
|
44
|
+
statuses[x.final_status.value] += 1
|
|
45
|
+
err = (x.error or "").lower()
|
|
46
|
+
if x.final_status == FinalStatus.ERROR:
|
|
47
|
+
if "setup check" in err:
|
|
48
|
+
setup_fails += 1
|
|
49
|
+
elif "seed" in err:
|
|
50
|
+
seed_errors += 1
|
|
51
|
+
pass_at_k = 1.0 if successes > 0 else 0.0
|
|
52
|
+
results.append(
|
|
53
|
+
TaskResult(
|
|
54
|
+
task_id=task_id,
|
|
55
|
+
model_id=model_id,
|
|
56
|
+
trajectory_paths=(trajectory_paths or {}).get(task_id, []),
|
|
57
|
+
n=n,
|
|
58
|
+
successes=successes,
|
|
59
|
+
pass_at_1=successes / n if n else 0.0,
|
|
60
|
+
pass_at_k=pass_at_k,
|
|
61
|
+
mean_reward=sum(x.reward for x in group) / n if n else 0.0,
|
|
62
|
+
mean_steps=sum(x.metrics.steps for x in group) / n if n else 0.0,
|
|
63
|
+
mean_duration_s=sum(x.metrics.duration_s for x in group) / n if n else 0.0,
|
|
64
|
+
seed_errors=seed_errors,
|
|
65
|
+
setup_check_failures=setup_fails,
|
|
66
|
+
statuses=dict(statuses),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
return results
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def build_model_report(
|
|
73
|
+
model_id: str,
|
|
74
|
+
model_config_public: dict[str, Any],
|
|
75
|
+
trajs: Sequence[Trajectory],
|
|
76
|
+
*,
|
|
77
|
+
wall_clock_s: float,
|
|
78
|
+
trajectory_paths: dict[str, list[str]] | None = None,
|
|
79
|
+
) -> ModelReport:
|
|
80
|
+
by_task = build_task_results(
|
|
81
|
+
model_id, trajs, trajectory_paths=trajectory_paths
|
|
82
|
+
)
|
|
83
|
+
agg = aggregate_trajectories(trajs)
|
|
84
|
+
pass_at_k_mean = (
|
|
85
|
+
sum(t.pass_at_k or 0.0 for t in by_task) / len(by_task) if by_task else None
|
|
86
|
+
)
|
|
87
|
+
return ModelReport(
|
|
88
|
+
model_id=model_id,
|
|
89
|
+
model_config_public=redact_model_config(model_config_public),
|
|
90
|
+
aggregate=agg,
|
|
91
|
+
by_task=by_task,
|
|
92
|
+
wall_clock_s=wall_clock_s,
|
|
93
|
+
pass_at_k_mean=pass_at_k_mean,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def build_leaderboard(models: Sequence[ModelReport]) -> list[dict[str, Any]]:
|
|
98
|
+
rows = []
|
|
99
|
+
for m in models:
|
|
100
|
+
rows.append(
|
|
101
|
+
{
|
|
102
|
+
"model_id": m.model_id,
|
|
103
|
+
"success_rate": m.aggregate.success_rate,
|
|
104
|
+
"mean_reward": m.aggregate.mean_reward,
|
|
105
|
+
"mean_steps": m.aggregate.mean_steps,
|
|
106
|
+
"mean_duration_s": m.aggregate.mean_duration_s,
|
|
107
|
+
"pass_at_k_mean": m.pass_at_k_mean,
|
|
108
|
+
"n": m.aggregate.n,
|
|
109
|
+
"wall_clock_s": m.wall_clock_s,
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
rows.sort(key=lambda r: (-r["success_rate"], -r["mean_reward"], r["mean_steps"]))
|
|
113
|
+
return rows
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def render_markdown(report: BenchmarkReport) -> str:
|
|
117
|
+
lines: list[str] = [
|
|
118
|
+
f"# Benchmark Report: {report.suite_id}",
|
|
119
|
+
"",
|
|
120
|
+
f"- **Suite version:** {report.suite_version}",
|
|
121
|
+
f"- **Content hash:** `{report.suite_content_hash or 'none'}`",
|
|
122
|
+
f"- **AgentBox:** {report.agentbox_version}",
|
|
123
|
+
f"- **Report id:** {report.report_id}",
|
|
124
|
+
f"- **Started:** {report.started_at.isoformat()}",
|
|
125
|
+
f"- **Finished:** {report.finished_at.isoformat()}",
|
|
126
|
+
"",
|
|
127
|
+
"## Leaderboard",
|
|
128
|
+
"",
|
|
129
|
+
"| Rank | Model | Success rate | Mean reward | Mean steps | Mean duration (s) | Pass@k | n |",
|
|
130
|
+
"| --- | --- | --- | --- | --- | --- | --- | --- |",
|
|
131
|
+
]
|
|
132
|
+
for i, row in enumerate(report.leaderboard, start=1):
|
|
133
|
+
pak = row.get("pass_at_k_mean")
|
|
134
|
+
pak_s = f"{pak:.3f}" if pak is not None else "—"
|
|
135
|
+
lines.append(
|
|
136
|
+
f"| {i} | {row['model_id']} | {row['success_rate']:.3f} | "
|
|
137
|
+
f"{row['mean_reward']:.3f} | {row['mean_steps']:.2f} | "
|
|
138
|
+
f"{row['mean_duration_s']:.2f} | {pak_s} | {row['n']} |"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for m in report.models:
|
|
142
|
+
lines.extend(
|
|
143
|
+
[
|
|
144
|
+
"",
|
|
145
|
+
f"## Model: {m.model_id}",
|
|
146
|
+
"",
|
|
147
|
+
f"- Config: `{m.model_config_public}`",
|
|
148
|
+
f"- Success rate: **{m.aggregate.success_rate:.3f}**",
|
|
149
|
+
f"- Mean reward: {m.aggregate.mean_reward:.3f}",
|
|
150
|
+
f"- Mean steps: {m.aggregate.mean_steps:.2f}",
|
|
151
|
+
f"- Statuses: {m.aggregate.by_status}",
|
|
152
|
+
f"- Wall clock: {m.wall_clock_s:.1f}s",
|
|
153
|
+
"",
|
|
154
|
+
"| Task | n | Successes | Pass@1 | Pass@k | Mean reward | Mean steps |",
|
|
155
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
for t in m.by_task:
|
|
159
|
+
pak = f"{t.pass_at_k:.3f}" if t.pass_at_k is not None else "—"
|
|
160
|
+
lines.append(
|
|
161
|
+
f"| {t.task_id} | {t.n} | {t.successes} | {t.pass_at_1:.3f} | "
|
|
162
|
+
f"{pak} | {t.mean_reward:.3f} | {t.mean_steps:.2f} |"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
lines.extend(
|
|
166
|
+
[
|
|
167
|
+
"",
|
|
168
|
+
"## Notes",
|
|
169
|
+
"",
|
|
170
|
+
f"- Pass@k definition: {report.pass_at_k_definition}",
|
|
171
|
+
"- API keys are redacted from this report.",
|
|
172
|
+
"",
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Execute frozen benchmark suites against model endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from agentbox.benchmark.loader import snapshot_suite
|
|
14
|
+
from agentbox.benchmark.report import build_leaderboard, build_model_report
|
|
15
|
+
from agentbox.benchmark.schema import (
|
|
16
|
+
BenchmarkReport,
|
|
17
|
+
BenchmarkRunConfig,
|
|
18
|
+
ModelUnderTest,
|
|
19
|
+
)
|
|
20
|
+
from agentbox.config import AgentConfig, ModelConfig, RolloutConfig
|
|
21
|
+
from agentbox.runner.parallel import ParallelRunner
|
|
22
|
+
from agentbox.trajectory.schema import Trajectory
|
|
23
|
+
from agentbox.version import __version__
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("agentbox.benchmark")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BenchmarkRunner:
|
|
29
|
+
"""Run a BenchmarkSuite against one or more OpenAI-compatible models."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, config: BenchmarkRunConfig) -> None:
|
|
32
|
+
self.config = config
|
|
33
|
+
|
|
34
|
+
async def run(self) -> BenchmarkReport:
|
|
35
|
+
cfg = self.config
|
|
36
|
+
suite = cfg.suite
|
|
37
|
+
suite.verify_integrity(strict=cfg.strict_integrity)
|
|
38
|
+
|
|
39
|
+
out = Path(cfg.output_dir)
|
|
40
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
snapshot_suite(suite, out / "suite_snapshot")
|
|
42
|
+
|
|
43
|
+
started = datetime.now(timezone.utc)
|
|
44
|
+
model_reports = []
|
|
45
|
+
for mut in cfg.models:
|
|
46
|
+
logger.info("benchmark model_id=%s start", mut.model_id)
|
|
47
|
+
mr = await self._run_model(mut)
|
|
48
|
+
model_reports.append(mr)
|
|
49
|
+
|
|
50
|
+
finished = datetime.now(timezone.utc)
|
|
51
|
+
report = BenchmarkReport(
|
|
52
|
+
report_id=str(uuid.uuid4()),
|
|
53
|
+
suite_id=suite.manifest.suite_id,
|
|
54
|
+
suite_version=suite.manifest.version,
|
|
55
|
+
suite_content_hash=suite.manifest.content_hash,
|
|
56
|
+
agentbox_version=__version__,
|
|
57
|
+
started_at=started,
|
|
58
|
+
finished_at=finished,
|
|
59
|
+
run_tags=dict(cfg.tags),
|
|
60
|
+
models=model_reports,
|
|
61
|
+
leaderboard=build_leaderboard(model_reports),
|
|
62
|
+
)
|
|
63
|
+
report.save_json(out / "report.json")
|
|
64
|
+
report.save_markdown(out / "REPORT.md")
|
|
65
|
+
return report
|
|
66
|
+
|
|
67
|
+
async def _run_model(self, mut: ModelUnderTest) -> Any:
|
|
68
|
+
cfg = self.config
|
|
69
|
+
suite = cfg.suite
|
|
70
|
+
mdir = Path(cfg.output_dir) / "models" / mut.model_id
|
|
71
|
+
traj_dir = mdir / "trajectories"
|
|
72
|
+
traj_dir.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
# Fairness: suite agent tools/max_steps only; model cannot change tools
|
|
75
|
+
agent_cfg = suite.manifest.agent.model_copy(
|
|
76
|
+
deep=True,
|
|
77
|
+
update={"custom_tools": [], "drop_tools_prob": 0.0},
|
|
78
|
+
)
|
|
79
|
+
concurrency = cfg.concurrency or suite.manifest.concurrency
|
|
80
|
+
n_per = cfg.n_per_task or suite.manifest.n_per_task
|
|
81
|
+
seed = suite.manifest.seed
|
|
82
|
+
|
|
83
|
+
rollout_config = RolloutConfig(
|
|
84
|
+
sandbox=suite.manifest.sandbox,
|
|
85
|
+
model=mut.model,
|
|
86
|
+
agent=agent_cfg,
|
|
87
|
+
seed=seed,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
path_map: dict[str, list[str]] = defaultdict(list)
|
|
91
|
+
trajs_acc: list[Trajectory] = []
|
|
92
|
+
|
|
93
|
+
def on_trajectory(traj: Trajectory) -> None:
|
|
94
|
+
trajs_acc.append(traj)
|
|
95
|
+
if cfg.save_trajectories:
|
|
96
|
+
fname = f"{traj.task_id}_{traj.run_id[:8]}.json"
|
|
97
|
+
path = traj_dir / fname
|
|
98
|
+
traj.save(path)
|
|
99
|
+
path_map[traj.task_id].append(str(path))
|
|
100
|
+
|
|
101
|
+
# Thread setup_checks into each Rollout via ParallelRunner extension
|
|
102
|
+
runner = ParallelRunner(
|
|
103
|
+
concurrency=concurrency,
|
|
104
|
+
config=rollout_config,
|
|
105
|
+
model=mut.model,
|
|
106
|
+
agent=agent_cfg,
|
|
107
|
+
sandbox=suite.manifest.sandbox,
|
|
108
|
+
)
|
|
109
|
+
setup_checks = list(suite.manifest.scoring.setup_checks)
|
|
110
|
+
|
|
111
|
+
t0 = time.monotonic()
|
|
112
|
+
trajs = await runner.run_tasks(
|
|
113
|
+
suite.tasks,
|
|
114
|
+
n_per_task=n_per,
|
|
115
|
+
progress=cfg.progress,
|
|
116
|
+
on_trajectory=on_trajectory,
|
|
117
|
+
setup_checks=setup_checks,
|
|
118
|
+
)
|
|
119
|
+
wall = time.monotonic() - t0
|
|
120
|
+
|
|
121
|
+
public = mut.model.model_dump(mode="json")
|
|
122
|
+
report = build_model_report(
|
|
123
|
+
mut.model_id,
|
|
124
|
+
public,
|
|
125
|
+
trajs,
|
|
126
|
+
wall_clock_s=wall,
|
|
127
|
+
trajectory_paths=dict(path_map),
|
|
128
|
+
)
|
|
129
|
+
report_path = mdir / "model_report.json"
|
|
130
|
+
report_path.write_text(report.model_dump_json(indent=2), encoding="utf-8")
|
|
131
|
+
return report
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Benchmark suite, run, and report schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
from agentbox.config import AgentConfig, ModelConfig, SandboxConfig
|
|
12
|
+
from agentbox.metrics.aggregate import AggregateMetrics
|
|
13
|
+
from agentbox.tasks.schema import Task
|
|
14
|
+
from agentbox.trajectory.schema import Trajectory
|
|
15
|
+
from agentbox.types import ToolMode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SetupCheckSpec(BaseModel):
|
|
19
|
+
"""Post-seed environment health check (not task success)."""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
command: str
|
|
23
|
+
success_exit_code: int = 0
|
|
24
|
+
timeout_s: float = 60.0
|
|
25
|
+
required: bool = True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ScoringConfig(BaseModel):
|
|
29
|
+
"""How a suite scores rollouts."""
|
|
30
|
+
|
|
31
|
+
primary: Literal["verifier"] = "verifier"
|
|
32
|
+
require_seed_ok: bool = True
|
|
33
|
+
setup_checks: list[SetupCheckSpec] = Field(default_factory=list)
|
|
34
|
+
track_steps: bool = True
|
|
35
|
+
track_duration: bool = True
|
|
36
|
+
enable_ruler: bool = False
|
|
37
|
+
ruler_group_size: int = 4
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_bench_agent() -> AgentConfig:
|
|
41
|
+
return AgentConfig(
|
|
42
|
+
tools=ToolMode.STRUCTURED,
|
|
43
|
+
max_steps=40,
|
|
44
|
+
drop_tools_prob=0.0,
|
|
45
|
+
custom_tools=[],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BenchmarkSuiteManifest(BaseModel):
|
|
50
|
+
"""suite.json manifest for a frozen evaluation pack."""
|
|
51
|
+
|
|
52
|
+
schema_version: Literal["1"] = "1"
|
|
53
|
+
suite_id: str
|
|
54
|
+
name: str
|
|
55
|
+
description: str = ""
|
|
56
|
+
version: str = "1.0.0"
|
|
57
|
+
tasks_path: str = "tasks"
|
|
58
|
+
sandbox: SandboxConfig = Field(default_factory=SandboxConfig)
|
|
59
|
+
agent: AgentConfig = Field(default_factory=_default_bench_agent)
|
|
60
|
+
n_per_task: int = 1
|
|
61
|
+
concurrency: int = 8
|
|
62
|
+
seed: int | None = 42
|
|
63
|
+
scoring: ScoringConfig = Field(default_factory=ScoringConfig)
|
|
64
|
+
content_hash: str | None = None
|
|
65
|
+
created_at: datetime | None = None
|
|
66
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
67
|
+
|
|
68
|
+
model_config = ConfigDict(extra="ignore")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class BenchmarkSuite(BaseModel):
|
|
72
|
+
"""In-memory suite: manifest + loaded tasks."""
|
|
73
|
+
|
|
74
|
+
root: Path | None = None
|
|
75
|
+
manifest: BenchmarkSuiteManifest
|
|
76
|
+
tasks: list[Task] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
79
|
+
|
|
80
|
+
def task_ids(self) -> list[str]:
|
|
81
|
+
return [t.task_id for t in self.tasks]
|
|
82
|
+
|
|
83
|
+
def freeze(self) -> BenchmarkSuite:
|
|
84
|
+
from agentbox.benchmark.hash import compute_suite_content_hash
|
|
85
|
+
|
|
86
|
+
m = self.manifest.model_copy(deep=True)
|
|
87
|
+
m.created_at = m.created_at or datetime.now(timezone.utc)
|
|
88
|
+
m.content_hash = compute_suite_content_hash(self.tasks, m)
|
|
89
|
+
return BenchmarkSuite(root=self.root, manifest=m, tasks=list(self.tasks))
|
|
90
|
+
|
|
91
|
+
def verify_integrity(self, *, strict: bool = False) -> bool:
|
|
92
|
+
from agentbox.benchmark.hash import compute_suite_content_hash
|
|
93
|
+
from agentbox.errors import SuiteIntegrityError
|
|
94
|
+
|
|
95
|
+
if not self.manifest.content_hash:
|
|
96
|
+
if strict:
|
|
97
|
+
raise SuiteIntegrityError("Suite has no content_hash; run freeze()")
|
|
98
|
+
return True
|
|
99
|
+
current = compute_suite_content_hash(self.tasks, self.manifest)
|
|
100
|
+
ok = current == self.manifest.content_hash
|
|
101
|
+
if not ok and strict:
|
|
102
|
+
raise SuiteIntegrityError(
|
|
103
|
+
f"Suite integrity check failed: expected {self.manifest.content_hash}, got {current}"
|
|
104
|
+
)
|
|
105
|
+
return ok
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ModelUnderTest(BaseModel):
|
|
109
|
+
"""One model endpoint under evaluation."""
|
|
110
|
+
|
|
111
|
+
model_id: str
|
|
112
|
+
model: ModelConfig
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class BenchmarkRunConfig(BaseModel):
|
|
116
|
+
"""Configuration for executing a suite against one or more models."""
|
|
117
|
+
|
|
118
|
+
suite: BenchmarkSuite
|
|
119
|
+
models: list[ModelUnderTest]
|
|
120
|
+
concurrency: int | None = None
|
|
121
|
+
n_per_task: int | None = None
|
|
122
|
+
save_trajectories: bool = True
|
|
123
|
+
output_dir: Path
|
|
124
|
+
progress: bool = True
|
|
125
|
+
fail_fast: bool = False
|
|
126
|
+
strict_integrity: bool = False
|
|
127
|
+
tags: dict[str, str] = Field(default_factory=dict)
|
|
128
|
+
|
|
129
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TaskResult(BaseModel):
|
|
133
|
+
task_id: str
|
|
134
|
+
model_id: str
|
|
135
|
+
trajectory_paths: list[str] = Field(default_factory=list)
|
|
136
|
+
n: int = 0
|
|
137
|
+
successes: int = 0
|
|
138
|
+
pass_at_1: float = 0.0
|
|
139
|
+
pass_at_k: float | None = None
|
|
140
|
+
mean_reward: float = 0.0
|
|
141
|
+
mean_steps: float = 0.0
|
|
142
|
+
mean_duration_s: float = 0.0
|
|
143
|
+
seed_errors: int = 0
|
|
144
|
+
setup_check_failures: int = 0
|
|
145
|
+
statuses: dict[str, int] = Field(default_factory=dict)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ModelReport(BaseModel):
|
|
149
|
+
model_id: str
|
|
150
|
+
model_config_public: dict[str, Any]
|
|
151
|
+
aggregate: AggregateMetrics
|
|
152
|
+
by_task: list[TaskResult] = Field(default_factory=list)
|
|
153
|
+
wall_clock_s: float = 0.0
|
|
154
|
+
pass_at_k_mean: float | None = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class BenchmarkReport(BaseModel):
|
|
158
|
+
schema_version: Literal["1"] = "1"
|
|
159
|
+
report_id: str
|
|
160
|
+
suite_id: str
|
|
161
|
+
suite_version: str
|
|
162
|
+
suite_content_hash: str | None = None
|
|
163
|
+
agentbox_version: str
|
|
164
|
+
started_at: datetime
|
|
165
|
+
finished_at: datetime
|
|
166
|
+
run_tags: dict[str, str] = Field(default_factory=dict)
|
|
167
|
+
models: list[ModelReport] = Field(default_factory=list)
|
|
168
|
+
leaderboard: list[dict[str, Any]] = Field(default_factory=list)
|
|
169
|
+
pass_at_k_definition: str = (
|
|
170
|
+
"pass_at_k = fraction of tasks with >=1 success among n_per_task rollouts; "
|
|
171
|
+
"pass_at_1 = mean per-task success rate of first/all rollouts"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def save_json(self, path: str | Path) -> None:
|
|
175
|
+
path = Path(path)
|
|
176
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
path.write_text(self.model_dump_json(indent=2), encoding="utf-8")
|
|
178
|
+
|
|
179
|
+
def save_markdown(self, path: str | Path) -> None:
|
|
180
|
+
from agentbox.benchmark.report import render_markdown
|
|
181
|
+
|
|
182
|
+
path = Path(path)
|
|
183
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
path.write_text(render_markdown(self), encoding="utf-8")
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def load(cls, path: str | Path) -> BenchmarkReport:
|
|
188
|
+
import json
|
|
189
|
+
|
|
190
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
191
|
+
return cls.model_validate(data)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ComparisonResult(BaseModel):
|
|
195
|
+
suite_id: str
|
|
196
|
+
hash_match: bool
|
|
197
|
+
warnings: list[str] = Field(default_factory=list)
|
|
198
|
+
deltas: list[dict[str, Any]] = Field(default_factory=list)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Post-seed environment health checks (not task success scoring)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
from agentbox.benchmark.schema import SetupCheckSpec
|
|
10
|
+
from agentbox.sandbox.types import ExecResult
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from agentbox.sandbox.manager import SandboxManager
|
|
14
|
+
from agentbox.sandbox.types import Sandbox
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SetupCheckResult(BaseModel):
|
|
18
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
19
|
+
|
|
20
|
+
ok: bool
|
|
21
|
+
results: list[ExecResult] = Field(default_factory=list)
|
|
22
|
+
failed_check: str | None = None
|
|
23
|
+
error: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SetupChecker:
|
|
27
|
+
"""Run suite-level commands after seed, before the agent starts."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, manager: SandboxManager) -> None:
|
|
30
|
+
self.manager = manager
|
|
31
|
+
|
|
32
|
+
async def run(
|
|
33
|
+
self,
|
|
34
|
+
sandbox: Sandbox,
|
|
35
|
+
checks: list[SetupCheckSpec],
|
|
36
|
+
) -> SetupCheckResult:
|
|
37
|
+
if not checks:
|
|
38
|
+
return SetupCheckResult(ok=True)
|
|
39
|
+
|
|
40
|
+
results: list[ExecResult] = []
|
|
41
|
+
for check in checks:
|
|
42
|
+
result = await self.manager.exec(
|
|
43
|
+
sandbox,
|
|
44
|
+
check.command,
|
|
45
|
+
timeout_s=check.timeout_s,
|
|
46
|
+
)
|
|
47
|
+
results.append(result)
|
|
48
|
+
failed = result.timed_out or result.exit_code != check.success_exit_code
|
|
49
|
+
if failed and check.required:
|
|
50
|
+
err = result.stderr or result.stdout or "setup check failed"
|
|
51
|
+
return SetupCheckResult(
|
|
52
|
+
ok=False,
|
|
53
|
+
results=results,
|
|
54
|
+
failed_check=check.name,
|
|
55
|
+
error=f"setup check {check.name!r} failed: {err}",
|
|
56
|
+
)
|
|
57
|
+
return SetupCheckResult(ok=True, results=results)
|
agentbox/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command-line interface."""
|