toolbench 0.0.1__tar.gz → 0.2.0__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.
- {toolbench-0.0.1 → toolbench-0.2.0}/.gitignore +13 -0
- toolbench-0.2.0/PKG-INFO +150 -0
- toolbench-0.2.0/README.md +108 -0
- toolbench-0.2.0/examples/calculator/requirements.txt +1 -0
- toolbench-0.2.0/examples/calculator/toolkit.yaml +37 -0
- toolbench-0.2.0/examples/calculator/tools/__init__.py +4 -0
- toolbench-0.2.0/examples/calculator/tools/basic.py +29 -0
- toolbench-0.2.0/examples/calculator/tools/scientific.py +18 -0
- toolbench-0.2.0/examples/geometry/benchmark.yaml +47 -0
- toolbench-0.2.0/examples/geometry/ground_truth/answer.json +1 -0
- toolbench-0.2.0/examples/geometry/harnesses/orchestral/anthropic.yaml +13 -0
- toolbench-0.2.0/examples/geometry/harnesses/orchestral/groq.yaml +13 -0
- toolbench-0.2.0/examples/geometry/harnesses/orchestral/litellm.yaml +19 -0
- toolbench-0.2.0/examples/geometry/harnesses/orchestral/openai.yaml +13 -0
- toolbench-0.2.0/examples/geometry/loadouts/additive_only.yaml +6 -0
- toolbench-0.2.0/examples/geometry/loadouts/all_metrics.yaml +8 -0
- toolbench-0.2.0/examples/geometry/loadouts/calc_toolbase.yaml +8 -0
- toolbench-0.2.0/examples/geometry/loadouts/core_only.yaml +4 -0
- toolbench-0.2.0/examples/geometry/loadouts/full_local.yaml +6 -0
- toolbench-0.2.0/examples/geometry/loadouts/full_mixed.yaml +9 -0
- toolbench-0.2.0/examples/geometry/loadouts/full_toolbase.yaml +11 -0
- toolbench-0.2.0/examples/geometry/loadouts/guided.yaml +8 -0
- toolbench-0.2.0/examples/geometry/loadouts/primitives_only.yaml +5 -0
- toolbench-0.2.0/examples/geometry/skills/distance_recipe.md +9 -0
- toolbench-0.2.0/examples/geometry/tools/chebyshev.py +14 -0
- toolbench-0.2.0/examples/geometry/tools/dunderkit.py +43 -0
- toolbench-0.2.0/examples/geometry/tools/euclid.py +15 -0
- toolbench-0.2.0/examples/geometry/tools/extras.py +14 -0
- toolbench-0.2.0/examples/geometry/tools/taxicab.py +14 -0
- toolbench-0.2.0/examples/geometry/variants/derived/prompts/system.md +3 -0
- toolbench-0.2.0/examples/geometry/variants/derived/prompts/user.md +17 -0
- toolbench-0.2.0/examples/geometry/variants/derived/variant.yaml +17 -0
- toolbench-0.2.0/examples/geometry/variants/direct/prompts/system.md +3 -0
- toolbench-0.2.0/examples/geometry/variants/direct/prompts/user.md +12 -0
- toolbench-0.2.0/examples/geometry/variants/direct/sandbox/template/points.json +1 -0
- toolbench-0.2.0/examples/geometry/variants/direct/variant.yaml +16 -0
- toolbench-0.2.0/examples/geometry/variants/polar/prompts/system.md +3 -0
- toolbench-0.2.0/examples/geometry/variants/polar/prompts/user.md +21 -0
- toolbench-0.2.0/examples/geometry/variants/polar/variant.yaml +24 -0
- toolbench-0.2.0/harness_templates/README.md +91 -0
- toolbench-0.2.0/harness_templates/claude-code/README.md +54 -0
- toolbench-0.2.0/harness_templates/claude-code/default.yaml +20 -0
- toolbench-0.2.0/harness_templates/codex/README.md +43 -0
- toolbench-0.2.0/harness_templates/codex/default.yaml +18 -0
- toolbench-0.2.0/harness_templates/orchestral/README.md +49 -0
- toolbench-0.2.0/harness_templates/orchestral/anthropic.yaml +22 -0
- toolbench-0.2.0/harness_templates/orchestral/google.yaml +22 -0
- toolbench-0.2.0/harness_templates/orchestral/groq.yaml +28 -0
- toolbench-0.2.0/harness_templates/orchestral/litellm.yaml +32 -0
- toolbench-0.2.0/harness_templates/orchestral/openai.yaml +22 -0
- toolbench-0.2.0/mkdocs.yml +97 -0
- toolbench-0.2.0/pyproject.toml +72 -0
- toolbench-0.2.0/tests/__init__.py +0 -0
- toolbench-0.2.0/tests/helpers.py +12 -0
- toolbench-0.2.0/tests/test_artifact_cleanup.py +82 -0
- toolbench-0.2.0/tests/test_artifact_policy.py +86 -0
- toolbench-0.2.0/tests/test_benchmark_extends.py +250 -0
- toolbench-0.2.0/tests/test_checks.py +158 -0
- toolbench-0.2.0/tests/test_cli_runtime_stderr_drain.py +89 -0
- toolbench-0.2.0/tests/test_codex_runtime.py +136 -0
- toolbench-0.2.0/tests/test_content_checks.py +309 -0
- toolbench-0.2.0/tests/test_cost_extraction.py +92 -0
- toolbench-0.2.0/tests/test_crash_classifier.py +219 -0
- toolbench-0.2.0/tests/test_geometry.py +71 -0
- toolbench-0.2.0/tests/test_integrity.py +80 -0
- toolbench-0.2.0/tests/test_json_repair.py +113 -0
- toolbench-0.2.0/tests/test_judge_score.py +157 -0
- toolbench-0.2.0/tests/test_judge_select.py +137 -0
- toolbench-0.2.0/tests/test_mcp_source.py +134 -0
- toolbench-0.2.0/tests/test_metrics.py +262 -0
- toolbench-0.2.0/tests/test_numeric_checks.py +151 -0
- toolbench-0.2.0/tests/test_paired_deltas.py +82 -0
- toolbench-0.2.0/tests/test_resume.py +129 -0
- toolbench-0.2.0/tests/test_rubric_loader.py +32 -0
- toolbench-0.2.0/tests/test_runner_llm_judge.py +126 -0
- toolbench-0.2.0/tests/test_runner_rate_limit.py +106 -0
- toolbench-0.2.0/tests/test_runner_request_params.py +125 -0
- toolbench-0.2.0/tests/test_runner_retry.py +221 -0
- toolbench-0.2.0/tests/test_runner_transient.py +108 -0
- toolbench-0.2.0/tests/test_runtime_registry.py +95 -0
- toolbench-0.2.0/tests/test_runtime_version_label.py +11 -0
- toolbench-0.2.0/tests/test_schema.py +82 -0
- toolbench-0.2.0/tests/test_skills.py +136 -0
- toolbench-0.2.0/tests/test_summary_text_cost.py +36 -0
- toolbench-0.2.0/tests/test_tool_error_visibility.py +80 -0
- toolbench-0.2.0/tests/test_tool_resolver.py +83 -0
- toolbench-0.2.0/tests/test_toolbase_sandbox_scoping.py +105 -0
- toolbench-0.2.0/tests/test_toolbase_select_provenance.py +117 -0
- toolbench-0.2.0/tests/test_trial_loop.py +140 -0
- toolbench-0.2.0/tests/test_variant.py +39 -0
- toolbench-0.2.0/toolbench/__init__.py +15 -0
- toolbench-0.2.0/toolbench/cli.py +2010 -0
- toolbench-0.2.0/toolbench/core/__init__.py +0 -0
- toolbench-0.2.0/toolbench/core/artifact_policy.py +109 -0
- toolbench-0.2.0/toolbench/core/benchmark.py +190 -0
- toolbench-0.2.0/toolbench/core/budget.py +52 -0
- toolbench-0.2.0/toolbench/core/checks.py +211 -0
- toolbench-0.2.0/toolbench/core/content_checks.py +395 -0
- toolbench-0.2.0/toolbench/core/crash_classifier.py +280 -0
- toolbench-0.2.0/toolbench/core/failure_modes.py +94 -0
- toolbench-0.2.0/toolbench/core/harness.py +152 -0
- toolbench-0.2.0/toolbench/core/integrity.py +94 -0
- toolbench-0.2.0/toolbench/core/json_repair.py +201 -0
- toolbench-0.2.0/toolbench/core/judge.py +426 -0
- toolbench-0.2.0/toolbench/core/judge_select.py +252 -0
- toolbench-0.2.0/toolbench/core/litellm_pricing.py +126 -0
- toolbench-0.2.0/toolbench/core/llm_factory.py +289 -0
- toolbench-0.2.0/toolbench/core/loadout.py +140 -0
- toolbench-0.2.0/toolbench/core/metrics.py +345 -0
- toolbench-0.2.0/toolbench/core/numeric_checks.py +195 -0
- toolbench-0.2.0/toolbench/core/runner.py +1177 -0
- toolbench-0.2.0/toolbench/core/runtime.py +1011 -0
- toolbench-0.2.0/toolbench/core/skills.py +110 -0
- toolbench-0.2.0/toolbench/core/store.py +52 -0
- toolbench-0.2.0/toolbench/core/task.py +148 -0
- toolbench-0.2.0/toolbench/core/tool_policy.py +44 -0
- toolbench-0.2.0/toolbench/core/tool_resolver.py +622 -0
- toolbench-0.2.0/toolbench/core/trajectory.py +316 -0
- toolbench-0.2.0/toolbench/core/variant.py +147 -0
- toolbench-0.2.0/toolbench/reporting/__init__.py +0 -0
- toolbench-0.2.0/toolbench/reporting/_output.py +53 -0
- toolbench-0.2.0/toolbench/reporting/_shared.py +135 -0
- toolbench-0.2.0/toolbench/reporting/audit_log.py +250 -0
- toolbench-0.2.0/toolbench/reporting/k_sweep.py +299 -0
- toolbench-0.2.0/toolbench/reporting/parallel_coords.py +319 -0
- toolbench-0.2.0/toolbench/reporting/per_stage_k.py +352 -0
- toolbench-0.2.0/toolbench/reporting/plot_overview.py +363 -0
- toolbench-0.2.0/toolbench/reporting/summary_text.py +375 -0
- toolbench-0.2.0/toolbench/reporting/transcript.py +406 -0
- toolbench-0.2.0/userdocs/authoring/custom-tools.md +94 -0
- toolbench-0.2.0/userdocs/authoring/define-a-benchmark.md +122 -0
- toolbench-0.2.0/userdocs/authoring/overview.md +98 -0
- toolbench-0.2.0/userdocs/authoring/rubrics-and-checks.md +240 -0
- toolbench-0.2.0/userdocs/explanation.md +106 -0
- toolbench-0.2.0/userdocs/guides/harnesses.md +147 -0
- toolbench-0.2.0/userdocs/guides/install-and-quickstart.md +70 -0
- toolbench-0.2.0/userdocs/guides/loadouts.md +150 -0
- toolbench-0.2.0/userdocs/guides/reading-results.md +189 -0
- toolbench-0.2.0/userdocs/guides/running-a-benchmark.md +151 -0
- toolbench-0.2.0/userdocs/guides/toolbase.md +118 -0
- toolbench-0.2.0/userdocs/guides/variants.md +67 -0
- toolbench-0.2.0/userdocs/index.md +88 -0
- toolbench-0.2.0/userdocs/reference/commands.md +88 -0
- toolbench-0.2.0/userdocs/reference/files-and-layout.md +122 -0
- toolbench-0.2.0/userdocs/reference/metrics.md +164 -0
- toolbench-0.2.0/userdocs/reference/schemas.md +247 -0
- toolbench-0.2.0/userdocs/reference/vocabulary.md +145 -0
- toolbench-0.2.0/userdocs/stylesheets/extra.css +78 -0
- toolbench-0.2.0/userdocs/troubleshooting.md +118 -0
- toolbench-0.0.1/PKG-INFO +0 -46
- toolbench-0.0.1/README.md +0 -22
- toolbench-0.0.1/pyproject.toml +0 -35
- toolbench-0.0.1/src/toolbench/__init__.py +0 -5
- {toolbench-0.0.1 → toolbench-0.2.0}/LICENSE +0 -0
|
@@ -41,9 +41,22 @@ ENV/
|
|
|
41
41
|
htmlcov/
|
|
42
42
|
.hypothesis/
|
|
43
43
|
|
|
44
|
+
# Claude Code local state
|
|
45
|
+
.claude/
|
|
46
|
+
|
|
44
47
|
# IDE
|
|
45
48
|
.vscode/
|
|
46
49
|
.idea/
|
|
47
50
|
*.swp
|
|
48
51
|
*.swo
|
|
49
52
|
.DS_Store
|
|
53
|
+
|
|
54
|
+
# local handoff / scratch — not for commit
|
|
55
|
+
PORT_STATUS.md
|
|
56
|
+
*.orig
|
|
57
|
+
|
|
58
|
+
# built docs
|
|
59
|
+
site/
|
|
60
|
+
|
|
61
|
+
# run output
|
|
62
|
+
runs/
|
toolbench-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toolbench
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A platform and CLI for building benchmarks for agentic tools and harnesses.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tonymenzo/toolbench
|
|
6
|
+
Project-URL: Repository, https://github.com/tonymenzo/toolbench
|
|
7
|
+
Project-URL: Issues, https://github.com/tonymenzo/toolbench/issues
|
|
8
|
+
Author: Tony Menzo
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,benchmark,evaluation,llm,orchestral,tools
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: click>=8.0
|
|
23
|
+
Requires-Dist: matplotlib>=3.10.8
|
|
24
|
+
Requires-Dist: numpy>=2.3.5
|
|
25
|
+
Requires-Dist: orchestral-ai>=1.6.3
|
|
26
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
27
|
+
Requires-Dist: requests>=2.32.0
|
|
28
|
+
Requires-Dist: rich>=14.2.0
|
|
29
|
+
Requires-Dist: scipy>=1.16.3
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
33
|
+
Provides-Extra: docs
|
|
34
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
35
|
+
Provides-Extra: dotenv
|
|
36
|
+
Requires-Dist: python-dotenv>=1.2.1; extra == 'dotenv'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
39
|
+
Provides-Extra: toolbase
|
|
40
|
+
Requires-Dist: toolbase>=0.3.0; extra == 'toolbase'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# toolbench
|
|
44
|
+
|
|
45
|
+
**toolbench is a platform and CLI for building benchmarks for agentic tools and harnesses.**
|
|
46
|
+
Compose a benchmark (a task + a grading rubric) with a *harness* (the agent runtime), a
|
|
47
|
+
*loadout* (the tools the agent is given), and a *variant* (the prompt + sandbox), run N
|
|
48
|
+
trials per cell against a model, and get reach / pass@k / pass^k metrics with plots.
|
|
49
|
+
|
|
50
|
+
It is the benchmarking sibling of [toolbase](https://github.com/alexr314/toolbase) — the
|
|
51
|
+
package manager and runtime for agent tools. toolbase serves the tools; toolbench measures
|
|
52
|
+
how well an agent uses them. Together they close the loop for agentic tool and harness
|
|
53
|
+
development.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install toolbench # core framework
|
|
59
|
+
pip install 'toolbench[toolbase]' # + resolve tools from toolbase profiles
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Requires Python ≥ 3.12. The CLI is available as both `toolbench` and the short alias `tbe`.
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
The example `geometry` benchmark (Euclidean distance + midpoint between two 2-D points) is a
|
|
67
|
+
self-contained, dependency-free example that exercises the whole framework.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Validate the wiring with no LLM calls or cost:
|
|
71
|
+
toolbench run --benchmark examples/geometry --model stub \
|
|
72
|
+
--loadouts full_local --n 1 --max-cost-usd 0 --dry-run
|
|
73
|
+
|
|
74
|
+
# A real run (a few cheap trials):
|
|
75
|
+
toolbench run --benchmark examples/geometry --model claude-haiku-4-5 \
|
|
76
|
+
--loadouts core_only,full_local --n 3 --max-cost-usd 0.50
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Each run writes a directory under `runs/<run_id>/` with a `manifest.json`, per-trial
|
|
80
|
+
transcripts and artifacts, an aggregated `summary.json` / `summary.txt`, and headline plots
|
|
81
|
+
(k-sweep, parallel-coordinates, per-stage breakdown).
|
|
82
|
+
|
|
83
|
+
## Concepts
|
|
84
|
+
|
|
85
|
+
A benchmark lives in `examples/<name>/` and is composed from four declarative
|
|
86
|
+
axes — vary any of them on the command line to run an ablation:
|
|
87
|
+
|
|
88
|
+
| Concept | What it is | Where it lives |
|
|
89
|
+
|---------------|----------------------------------------------------------------------|-------------------------|
|
|
90
|
+
| **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
|
|
91
|
+
| **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
|
|
92
|
+
| **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
|
|
93
|
+
| **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
|
|
94
|
+
| **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
|
|
95
|
+
|
|
96
|
+
A **loadout source** is one of:
|
|
97
|
+
|
|
98
|
+
- `python:` — import a module exposing `TOOLS` / `make_tools()` (the no-dependency escape hatch),
|
|
99
|
+
- `toolbase:` — resolve tools from a [toolbase](https://github.com/alexr314/toolbase) profile,
|
|
100
|
+
in-process (requires `toolbench[toolbase]`); served toolkit versions are recorded in the
|
|
101
|
+
run manifest as reproducibility provenance, or
|
|
102
|
+
- `mcp:` — serve any MCP server's tools, stdio or HTTP (requires `toolbench[mcp]`):
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
tools:
|
|
106
|
+
sources:
|
|
107
|
+
- toolbase: { profile: my-profile }
|
|
108
|
+
select: [calculator__add] # optional: ablate within the profile
|
|
109
|
+
- mcp: { url: "https://host/mcp", headers: { Authorization: "Bearer ${TOK}" } }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Metrics
|
|
113
|
+
|
|
114
|
+
For each (model × condition) cell over `k` trials:
|
|
115
|
+
|
|
116
|
+
- $\overline{\text{reach}}_k$ — mean rubric-weighted reach: how far through the task the agent gets, on average.
|
|
117
|
+
- $\text{pass@}k$ — probability that at least one of $k$ trials passes (best-of-$k$).
|
|
118
|
+
- $\text{pass}^{k}$ — probability that all $k$ trials pass (worst-of-$k$).
|
|
119
|
+
|
|
120
|
+
with bootstrap 95% confidence intervals and a metric-correlation matrix. A trial *passes*
|
|
121
|
+
when it clears the rubric's pass criterion — every stage by default, or reach ≥ a
|
|
122
|
+
`pass_threshold` once the rubric uses partial-credit stages. See
|
|
123
|
+
[Metrics](https://toolbench-ai.com/docs/reference/metrics/) for the exact estimators.
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
| Command | What it does |
|
|
128
|
+
|--------------------|----------------------------------------------------------------------|
|
|
129
|
+
| `toolbench run` | Run a benchmark across the harness × loadout × variant × model grid. |
|
|
130
|
+
| `toolbench resume` | Resume an interrupted run; run only the seeds not yet completed. |
|
|
131
|
+
| `toolbench regrade`| Re-judge a finished run's preserved artifacts after a rubric change. |
|
|
132
|
+
|
|
133
|
+
Run `toolbench --help` (or `tbe --help`) for the full reference.
|
|
134
|
+
|
|
135
|
+
## Also
|
|
136
|
+
|
|
137
|
+
- **Runtimes** — besides the API-driven `orchestral` runtime, toolbench drives the
|
|
138
|
+
`claude_code` and `codex` CLIs directly (subscription auth, no API key), with per-turn
|
|
139
|
+
token accounting and a filesystem sandbox. Copy-paste starting points live in
|
|
140
|
+
[`harness_templates/`](harness_templates/).
|
|
141
|
+
- **Judges** — grade with the deterministic rule judge (default) or add an LLM second
|
|
142
|
+
opinion (`--judge rule+llm`); the rule grade always stays authoritative. Any judge can be
|
|
143
|
+
applied after the fact with `toolbench regrade --judge …`.
|
|
144
|
+
- **Safeguards** — trials that read the ground-truth answer key are quarantined
|
|
145
|
+
(`INTEGRITY_LEAK`, scored 0) so a leak can't inflate the headline; every trial also gets a
|
|
146
|
+
readable `audit.txt` of its full trajectory.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# toolbench
|
|
2
|
+
|
|
3
|
+
**toolbench is a platform and CLI for building benchmarks for agentic tools and harnesses.**
|
|
4
|
+
Compose a benchmark (a task + a grading rubric) with a *harness* (the agent runtime), a
|
|
5
|
+
*loadout* (the tools the agent is given), and a *variant* (the prompt + sandbox), run N
|
|
6
|
+
trials per cell against a model, and get reach / pass@k / pass^k metrics with plots.
|
|
7
|
+
|
|
8
|
+
It is the benchmarking sibling of [toolbase](https://github.com/alexr314/toolbase) — the
|
|
9
|
+
package manager and runtime for agent tools. toolbase serves the tools; toolbench measures
|
|
10
|
+
how well an agent uses them. Together they close the loop for agentic tool and harness
|
|
11
|
+
development.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install toolbench # core framework
|
|
17
|
+
pip install 'toolbench[toolbase]' # + resolve tools from toolbase profiles
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Python ≥ 3.12. The CLI is available as both `toolbench` and the short alias `tbe`.
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
The example `geometry` benchmark (Euclidean distance + midpoint between two 2-D points) is a
|
|
25
|
+
self-contained, dependency-free example that exercises the whole framework.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Validate the wiring with no LLM calls or cost:
|
|
29
|
+
toolbench run --benchmark examples/geometry --model stub \
|
|
30
|
+
--loadouts full_local --n 1 --max-cost-usd 0 --dry-run
|
|
31
|
+
|
|
32
|
+
# A real run (a few cheap trials):
|
|
33
|
+
toolbench run --benchmark examples/geometry --model claude-haiku-4-5 \
|
|
34
|
+
--loadouts core_only,full_local --n 3 --max-cost-usd 0.50
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each run writes a directory under `runs/<run_id>/` with a `manifest.json`, per-trial
|
|
38
|
+
transcripts and artifacts, an aggregated `summary.json` / `summary.txt`, and headline plots
|
|
39
|
+
(k-sweep, parallel-coordinates, per-stage breakdown).
|
|
40
|
+
|
|
41
|
+
## Concepts
|
|
42
|
+
|
|
43
|
+
A benchmark lives in `examples/<name>/` and is composed from four declarative
|
|
44
|
+
axes — vary any of them on the command line to run an ablation:
|
|
45
|
+
|
|
46
|
+
| Concept | What it is | Where it lives |
|
|
47
|
+
|---------------|----------------------------------------------------------------------|-------------------------|
|
|
48
|
+
| **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
|
|
49
|
+
| **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
|
|
50
|
+
| **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
|
|
51
|
+
| **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
|
|
52
|
+
| **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
|
|
53
|
+
|
|
54
|
+
A **loadout source** is one of:
|
|
55
|
+
|
|
56
|
+
- `python:` — import a module exposing `TOOLS` / `make_tools()` (the no-dependency escape hatch),
|
|
57
|
+
- `toolbase:` — resolve tools from a [toolbase](https://github.com/alexr314/toolbase) profile,
|
|
58
|
+
in-process (requires `toolbench[toolbase]`); served toolkit versions are recorded in the
|
|
59
|
+
run manifest as reproducibility provenance, or
|
|
60
|
+
- `mcp:` — serve any MCP server's tools, stdio or HTTP (requires `toolbench[mcp]`):
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
tools:
|
|
64
|
+
sources:
|
|
65
|
+
- toolbase: { profile: my-profile }
|
|
66
|
+
select: [calculator__add] # optional: ablate within the profile
|
|
67
|
+
- mcp: { url: "https://host/mcp", headers: { Authorization: "Bearer ${TOK}" } }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Metrics
|
|
71
|
+
|
|
72
|
+
For each (model × condition) cell over `k` trials:
|
|
73
|
+
|
|
74
|
+
- $\overline{\text{reach}}_k$ — mean rubric-weighted reach: how far through the task the agent gets, on average.
|
|
75
|
+
- $\text{pass@}k$ — probability that at least one of $k$ trials passes (best-of-$k$).
|
|
76
|
+
- $\text{pass}^{k}$ — probability that all $k$ trials pass (worst-of-$k$).
|
|
77
|
+
|
|
78
|
+
with bootstrap 95% confidence intervals and a metric-correlation matrix. A trial *passes*
|
|
79
|
+
when it clears the rubric's pass criterion — every stage by default, or reach ≥ a
|
|
80
|
+
`pass_threshold` once the rubric uses partial-credit stages. See
|
|
81
|
+
[Metrics](https://toolbench-ai.com/docs/reference/metrics/) for the exact estimators.
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
| Command | What it does |
|
|
86
|
+
|--------------------|----------------------------------------------------------------------|
|
|
87
|
+
| `toolbench run` | Run a benchmark across the harness × loadout × variant × model grid. |
|
|
88
|
+
| `toolbench resume` | Resume an interrupted run; run only the seeds not yet completed. |
|
|
89
|
+
| `toolbench regrade`| Re-judge a finished run's preserved artifacts after a rubric change. |
|
|
90
|
+
|
|
91
|
+
Run `toolbench --help` (or `tbe --help`) for the full reference.
|
|
92
|
+
|
|
93
|
+
## Also
|
|
94
|
+
|
|
95
|
+
- **Runtimes** — besides the API-driven `orchestral` runtime, toolbench drives the
|
|
96
|
+
`claude_code` and `codex` CLIs directly (subscription auth, no API key), with per-turn
|
|
97
|
+
token accounting and a filesystem sandbox. Copy-paste starting points live in
|
|
98
|
+
[`harness_templates/`](harness_templates/).
|
|
99
|
+
- **Judges** — grade with the deterministic rule judge (default) or add an LLM second
|
|
100
|
+
opinion (`--judge rule+llm`); the rule grade always stays authoritative. Any judge can be
|
|
101
|
+
applied after the fact with `toolbench regrade --judge …`.
|
|
102
|
+
- **Safeguards** — trials that read the ground-truth answer key are quarantined
|
|
103
|
+
(`INTEGRITY_LEAK`, scored 0) so a leak can't inflate the headline; every trial also gets a
|
|
104
|
+
readable `audit.txt` of its full trajectory.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
orchestral-ai>=1.3.0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: calculator
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
description: A small calculator toolkit — basic arithmetic plus a scientific bundle.
|
|
4
|
+
author: toolbench examples
|
|
5
|
+
license: MIT
|
|
6
|
+
category: utils
|
|
7
|
+
python_version: "3.12"
|
|
8
|
+
|
|
9
|
+
bundles:
|
|
10
|
+
basic: {}
|
|
11
|
+
scientific: {}
|
|
12
|
+
|
|
13
|
+
tools:
|
|
14
|
+
- name: add
|
|
15
|
+
function: tools.basic.add
|
|
16
|
+
description: Add two numbers.
|
|
17
|
+
bundle: basic
|
|
18
|
+
- name: subtract
|
|
19
|
+
function: tools.basic.subtract
|
|
20
|
+
description: Subtract b from a.
|
|
21
|
+
bundle: basic
|
|
22
|
+
- name: multiply
|
|
23
|
+
function: tools.basic.multiply
|
|
24
|
+
description: Multiply two numbers.
|
|
25
|
+
bundle: basic
|
|
26
|
+
- name: divide
|
|
27
|
+
function: tools.basic.divide
|
|
28
|
+
description: Divide a by b.
|
|
29
|
+
bundle: basic
|
|
30
|
+
- name: power
|
|
31
|
+
function: tools.scientific.power
|
|
32
|
+
description: Raise base to the given exponent (use exp=0.5 for square root).
|
|
33
|
+
bundle: scientific
|
|
34
|
+
- name: sqrt
|
|
35
|
+
function: tools.scientific.sqrt
|
|
36
|
+
description: Square root of a number.
|
|
37
|
+
bundle: scientific
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Basic arithmetic tools for the calculator example toolkit."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from orchestral import define_tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@define_tool
|
|
9
|
+
def add(a: float, b: float) -> str:
|
|
10
|
+
"""Add two numbers."""
|
|
11
|
+
return json.dumps({"result": float(a) + float(b)})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@define_tool
|
|
15
|
+
def subtract(a: float, b: float) -> str:
|
|
16
|
+
"""Subtract b from a."""
|
|
17
|
+
return json.dumps({"result": float(a) - float(b)})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@define_tool
|
|
21
|
+
def multiply(a: float, b: float) -> str:
|
|
22
|
+
"""Multiply two numbers."""
|
|
23
|
+
return json.dumps({"result": float(a) * float(b)})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@define_tool
|
|
27
|
+
def divide(a: float, b: float) -> str:
|
|
28
|
+
"""Divide a by b."""
|
|
29
|
+
return json.dumps({"result": float(a) / float(b)})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Scientific tools for the calculator example toolkit."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
from orchestral import define_tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@define_tool
|
|
10
|
+
def power(base: float, exp: float) -> str:
|
|
11
|
+
"""Raise base to the given exponent (use exp=0.5 for a square root)."""
|
|
12
|
+
return json.dumps({"result": float(base) ** float(exp)})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@define_tool
|
|
16
|
+
def sqrt(x: float) -> str:
|
|
17
|
+
"""Square root of a number."""
|
|
18
|
+
return json.dumps({"result": math.sqrt(float(x))})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: geometry
|
|
2
|
+
version: 0.1.0
|
|
3
|
+
description: Euclidean distance and midpoint between two 2-D points.
|
|
4
|
+
|
|
5
|
+
default_harness: orchestral/anthropic
|
|
6
|
+
default_loadout: full_local
|
|
7
|
+
# Three variants (direct/derived/polar) — see variants/. All three
|
|
8
|
+
# share the same ground truth (distance 5.0, midpoint [1.5, 2.0]) so
|
|
9
|
+
# the family-level rubric grades them identically and cross-variant
|
|
10
|
+
# reach deltas are meaningful (and not contaminated by reference-value
|
|
11
|
+
# differences). Names describe what each variant supplies, not a
|
|
12
|
+
# presumed difficulty ordering.
|
|
13
|
+
default_variant: direct
|
|
14
|
+
|
|
15
|
+
ground_truth:
|
|
16
|
+
dir: ./ground_truth
|
|
17
|
+
|
|
18
|
+
rubric:
|
|
19
|
+
type: stagewise # ordered stages; trial score = prefix product
|
|
20
|
+
stages:
|
|
21
|
+
- id: answer_written
|
|
22
|
+
description: output/answer.json exists with the required keys
|
|
23
|
+
weight: 0.2
|
|
24
|
+
checks:
|
|
25
|
+
- json_with_keys:
|
|
26
|
+
file: output/answer.json
|
|
27
|
+
required_keys: [distance, midpoint]
|
|
28
|
+
- id: midpoint_correct
|
|
29
|
+
description: midpoint is within 1% of ground truth
|
|
30
|
+
weight: 0.3
|
|
31
|
+
checks:
|
|
32
|
+
- close_to:
|
|
33
|
+
file: output/answer.json
|
|
34
|
+
field: midpoint
|
|
35
|
+
reference: ./ground_truth/answer.json
|
|
36
|
+
tolerance_frac: 0.01
|
|
37
|
+
expected_tool_calls: [add, divide] # optional diagnostic, not graded
|
|
38
|
+
- id: distance_correct
|
|
39
|
+
description: Euclidean distance is within 1% of ground truth
|
|
40
|
+
weight: 0.5
|
|
41
|
+
checks:
|
|
42
|
+
- close_to:
|
|
43
|
+
file: output/answer.json
|
|
44
|
+
field: distance
|
|
45
|
+
reference: ./ground_truth/answer.json
|
|
46
|
+
tolerance_frac: 0.01
|
|
47
|
+
expected_tool_calls: [euclidean_distance]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"distance": 5.0, "midpoint": [1.5, 2.0]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: orchestral/anthropic
|
|
2
|
+
runtime:
|
|
3
|
+
name: orchestral
|
|
4
|
+
version: ">=1.3"
|
|
5
|
+
provider:
|
|
6
|
+
name: anthropic
|
|
7
|
+
max_tokens: 8192
|
|
8
|
+
core:
|
|
9
|
+
tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
|
|
10
|
+
loop:
|
|
11
|
+
max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
|
|
12
|
+
max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
|
|
13
|
+
continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: orchestral/groq
|
|
2
|
+
runtime:
|
|
3
|
+
name: orchestral
|
|
4
|
+
version: ">=1.3"
|
|
5
|
+
provider:
|
|
6
|
+
name: groq
|
|
7
|
+
max_tokens: 8192
|
|
8
|
+
core:
|
|
9
|
+
tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
|
|
10
|
+
loop:
|
|
11
|
+
max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
|
|
12
|
+
max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
|
|
13
|
+
continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: orchestral/litellm
|
|
2
|
+
runtime:
|
|
3
|
+
name: orchestral
|
|
4
|
+
version: ">=1.3"
|
|
5
|
+
provider:
|
|
6
|
+
name: litellm # routes via the LiteLLM proxy (LITELLM_HOST / LITELLM_API_KEY)
|
|
7
|
+
max_tokens: 8192
|
|
8
|
+
# A LiteLLM proxy may have *response-level* caching enabled, which would
|
|
9
|
+
# hand every trial of a cell the same completion (k trials stop being
|
|
10
|
+
# independent samples). cache_bust appends a per-trial nonce to the
|
|
11
|
+
# prompt to defeat it. Off by default for direct providers: their
|
|
12
|
+
# prompt/KV caches don't affect sampling independence.
|
|
13
|
+
cache_bust: true
|
|
14
|
+
core:
|
|
15
|
+
tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
|
|
16
|
+
loop:
|
|
17
|
+
max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
|
|
18
|
+
max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
|
|
19
|
+
continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: orchestral/openai
|
|
2
|
+
runtime:
|
|
3
|
+
name: orchestral
|
|
4
|
+
version: ">=1.3"
|
|
5
|
+
provider:
|
|
6
|
+
name: openai
|
|
7
|
+
max_tokens: 8192
|
|
8
|
+
core:
|
|
9
|
+
tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
|
|
10
|
+
loop:
|
|
11
|
+
max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
|
|
12
|
+
max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
|
|
13
|
+
continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
name: all_metrics
|
|
2
|
+
tools:
|
|
3
|
+
sources:
|
|
4
|
+
- python: tools/dunderkit.py # for the midpoint
|
|
5
|
+
- python: tools/euclid.py # correct metric for this task -> 5.0
|
|
6
|
+
- python: tools/taxicab.py # L1 -> 7.0 (wrong)
|
|
7
|
+
- python: tools/chebyshev.py # L-inf -> 4.0 (wrong)
|
|
8
|
+
skills: []
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
name: calc_toolbase
|
|
2
|
+
# Resolve tools from a toolbase profile (the `calculator` example toolkit).
|
|
3
|
+
# Requires: toolbench[toolbase] installed, and the calculator toolkit installed
|
|
4
|
+
# + activated into the named profile (see docs/guides/toolbase.md).
|
|
5
|
+
tools:
|
|
6
|
+
sources:
|
|
7
|
+
- toolbase: { profile: default }
|
|
8
|
+
skills: []
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Distance + midpoint
|
|
2
|
+
|
|
3
|
+
Given two points `p1 = [x1, y1]` and `p2 = [x2, y2]`:
|
|
4
|
+
|
|
5
|
+
1. `dx = subtract(x2, x1)`, `dy = subtract(y2, y1)`
|
|
6
|
+
2. `distance = power( add(power(dx, 2), power(dy, 2)), 0.5 )`
|
|
7
|
+
3. `midpoint = [ divide(add(x1, x2), 2), divide(add(y1, y2), 2) ]`
|
|
8
|
+
|
|
9
|
+
Write the result to `output/answer.json` as `{"distance": ..., "midpoint": [...]}`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""The Chebyshev (L-infinity) distance metric as its own one-tool toolset."""
|
|
2
|
+
|
|
3
|
+
from orchestral.tools import define_tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@define_tool
|
|
7
|
+
def chebyshev_distance(p1: list[float], p2: list[float]) -> dict:
|
|
8
|
+
"""L-infinity distance: max |a_i - b_i|."""
|
|
9
|
+
return {"result": max(abs(float.__sub__(float(a), float(b)))
|
|
10
|
+
for a, b in zip(p1, p2))}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TOOLS = [chebyshev_distance]
|
|
14
|
+
BUNDLES = {"metric": [chebyshev_distance]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Arithmetic primitives, each tool's body a single Python dunder call."""
|
|
2
|
+
|
|
3
|
+
from orchestral.tools import define_tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@define_tool
|
|
7
|
+
def add(a: float, b: float) -> dict:
|
|
8
|
+
"""a + b, via float.__add__."""
|
|
9
|
+
return {"result": float.__add__(float(a), float(b))}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@define_tool
|
|
13
|
+
def subtract(a: float, b: float) -> dict:
|
|
14
|
+
"""a - b, via float.__sub__."""
|
|
15
|
+
return {"result": float.__sub__(float(a), float(b))}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@define_tool
|
|
19
|
+
def multiply(a: float, b: float) -> dict:
|
|
20
|
+
"""a * b, via float.__mul__."""
|
|
21
|
+
return {"result": float.__mul__(float(a), float(b))}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@define_tool
|
|
25
|
+
def divide(a: float, b: float) -> dict:
|
|
26
|
+
"""a / b, via float.__truediv__."""
|
|
27
|
+
return {"result": float.__truediv__(float(a), float(b))}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@define_tool
|
|
31
|
+
def power(base: float, exp: float) -> dict:
|
|
32
|
+
"""base ** exp, via float.__pow__. (sqrt = power(x, 0.5))"""
|
|
33
|
+
return {"result": float.__pow__(float(base), float(exp))}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
TOOLS = [add, subtract, multiply, divide, power] # discovery entrypoint (all tools)
|
|
37
|
+
|
|
38
|
+
# BUNDLES — author-declared groupings within this toolset (a toolbase "bundle").
|
|
39
|
+
BUNDLES = {
|
|
40
|
+
"additive": [add, subtract],
|
|
41
|
+
"multiplicative": [multiply, divide],
|
|
42
|
+
"powers": [power],
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""The Euclidean (L2) distance metric as its own one-tool toolset."""
|
|
2
|
+
|
|
3
|
+
from orchestral.tools import define_tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@define_tool
|
|
7
|
+
def euclidean_distance(p1: list[float], p2: list[float]) -> dict:
|
|
8
|
+
"""L2 distance: sqrt(sum (a_i - b_i)^2)."""
|
|
9
|
+
s = sum(float.__pow__(float.__sub__(float(a), float(b)), 2.0)
|
|
10
|
+
for a, b in zip(p1, p2))
|
|
11
|
+
return {"result": float.__pow__(s, 0.5)}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TOOLS = [euclidean_distance]
|
|
15
|
+
BUNDLES = {"metric": [euclidean_distance]}
|