ワンクリックで
benchrouter
Guide for understanding the BenchRouter LLM evaluation framework (v0.4) and adding new benchmarks.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Guide for understanding the BenchRouter LLM evaluation framework (v0.4) and adding new benchmarks.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | BenchRouter |
| description | Guide for understanding the BenchRouter LLM evaluation framework (v0.4) and adding new benchmarks. |
BenchRouter is a lightweight LLM evaluation platform. Models are pre-deployed externally (vLLM, OpenAI-compatible API). BenchRouter only orchestrates evaluation tasks inside Docker containers.
Benchmark (benchmark.yaml)
├── environments/ ← Dockerfiles for each environment
│ ├── env-a/Dockerfile
│ └── env-b/Dockerfile
├── Task A/ (task.yaml + code) ← independent Docker container
├── Task B/ (task.yaml + code) ← runs in parallel
└── Task C/ (task.yaml + code) ← results auto-aggregated
Core abstractions:
benchmark.yaml at the roottask.yaml + executable codeExecution flow:
benchrouter run <benchmark> --model <name>environments/{env_name}/Dockerfile if needed -> launches container -> SDK runner reads task.yaml -> executes command -> reads result.json -> standardizes outputoverall scores into a benchmark-level meanKey source files:
benchrouter/server/scheduler.py — Run/Job orchestration, Docker/Compose execution, result aggregationbenchrouter/server/models.py — Pydantic models (EvalJobInfo, EvalRunInfo, SubmitEvalRequest)benchrouter/server/registry.py — File-based persistence for benchmarks, environments, jobs, runsbenchrouter/sdk/runner.py — In-container entrypoint: reads task.yaml, runs command, standardizes result.jsonbenchrouter/client/cli.py — Typer CLI (benchrouter run, benchrouter benchmark register, etc.)Every task must satisfy one simple contract:
$BENCHROUTER_OUTPUT_DIR containing an overall field (float, 0.0-1.0)| Variable | Description | Example |
|---|---|---|
BENCHROUTER_BENCHMARK_DIR | Path to task files inside container (read-only mount) | /app/benchmark |
BENCHROUTER_OUTPUT_DIR | Path to write results (read-write mount) | /app/results |
BENCHROUTER_MODEL_ENDPOINT | OpenAI-compatible API base URL | https://api.example.com/v1 |
BENCHROUTER_MODEL_NAME | Model identifier to pass in API calls | gpt-4o |
OPENAI_API_KEY | API key for the model endpoint | sk-... |
PYTHONPATH | Set to /app so benchrouter.sdk is importable | /app |
Additional variables from extra_env in task.yaml are merged in (e.g., database credentials for sidecar tasks).
The SDK runner (python -m benchrouter.sdk.runner) handles standardization. Your task command must produce a JSON file in $BENCHROUTER_OUTPUT_DIR. By default the runner looks for result.json with an overall key. You can customize both via task.yaml:
result_file: "scores.json" # default: "result.json"
result_mapping:
overall: "pass@1" # default: "overall"
The runner wraps your raw output into a standardized envelope:
{
"benchmark_name": "...",
"model_name": "...",
"overall": 0.85,
"raw_metrics": { /* your original JSON */ }
}
The overall value is what gets aggregated across tasks. The scheduler computes the benchmark-level score as the mean of all task overall values.
Located at the root of the benchmark directory.
name: "my-benchmark" # required — unique benchmark identifier
description: "What this benchmark does" # recommended
version: "1.0.0" # recommended
tasks: # required — at least one task
- name: task-a # required — must match a subdirectory name
environment: task-a # required — Docker environment name
- name: task-b
environment: task-b
The name in each task entry must correspond to a subdirectory containing a task.yaml. The environment determines which Docker image the task runs in; the scheduler auto-builds it from environments/{env_name}/Dockerfile within the benchmark directory.
Located in each task subdirectory.
name: "my-benchmark-task-a" # required — task display name
command: "python eval.py" # required — shell command to execute
description: "..." # optional
timeout: 3600 # optional — seconds (default: 7200)
result_file: "result.json" # optional — filename the task writes (default: "result.json")
result_mapping: # optional — field name remapping
overall: "pass@1" # maps "pass@1" in your output to "overall"
# Sidecar services (optional — uses Docker Compose when present)
sidecars:
- name: postgres # service name (used in networking)
image: "postgres:16" # Docker image
environment: # env vars for sidecar container
POSTGRES_PASSWORD: "password"
ports: ["5432:5432"] # port mappings
healthcheck: # wait for service readiness
test: "pg_isready -U postgres"
interval: "2s"
timeout: "5s"
retries: 30
post_start: # commands to run after sidecar is healthy
- "psql -U postgres -c 'CREATE DATABASE testdb;'"
extra_env: # optional — additional env vars for eval container
POSTGRES_HOST: "postgres"
POSTGRES_PORT: "5432"
Best when: you are building a new eval or the logic is straightforward.
Directory structure:
benchmarks/my-bench/
├── benchmark.yaml
├── environments/
│ └── my-bench/Dockerfile
└── default/
├── task.yaml
└── eval.py
benchmark.yaml:
name: "my-bench"
description: "My new evaluation"
version: "1.0.0"
tasks:
- name: default
environment: my-bench
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
RUN pip install openai pyyaml
default/task.yaml:
name: "my-bench-default"
command: "python eval.py"
default/eval.py (minimal example):
import os, json
from openai import OpenAI
client = OpenAI(
base_url=os.environ["BENCHROUTER_MODEL_ENDPOINT"],
api_key=os.environ.get("OPENAI_API_KEY", "EMPTY"),
)
model = os.environ.get("BENCHROUTER_MODEL_NAME", "default")
output_dir = os.environ["BENCHROUTER_OUTPUT_DIR"]
# ... run evaluation logic, call client.chat.completions.create() ...
result = {"overall": 0.85, "details": [...]}
os.makedirs(output_dir, exist_ok=True)
with open(os.path.join(output_dir, "result.json"), "w") as f:
json.dump(result, f, indent=2)
Best when: integrating an existing benchmark codebase (e.g., MCPMark, HumanEval) that already has its own Docker image and evaluation logic.
Key idea: Write a thin bridge script that translates BenchRouter env vars into whatever the existing tool expects, runs it, then collects its output into BenchRouter's result.json format.
Directory structure:
benchmarks/my-bench/
├── benchmark.yaml
└── task-name/
├── task.yaml
└── run_bridge.py # the bridge script
The Dockerfile for this scenario typically uses the existing benchmark's image as the base. Register it as an environment or place it in environments/{env_name}/Dockerfile. For example, MCPMark tasks use evalsysorg/mcpmark:latest as their base image, and the bridge script (run_mcpmark.py) translates env vars, invokes MCPMark's pipeline module, collects meta.json outputs, and writes result.json.
Bridge script pattern:
import os, json, subprocess, sys
# 1. Read BenchRouter env vars
model_endpoint = os.environ["BENCHROUTER_MODEL_ENDPOINT"]
model_name = os.environ.get("BENCHROUTER_MODEL_NAME", "default")
output_dir = os.environ.get("BENCHROUTER_OUTPUT_DIR", "/app/results")
# 2. Translate to the existing tool's env vars / CLI args
os.environ["EXISTING_TOOL_API_URL"] = model_endpoint
# 3. Run the existing tool
subprocess.run([sys.executable, "-m", "existing_tool", "--model", model_name], check=True)
# 4. Collect results and write result.json
raw_results = json.load(open("/tmp/existing_output/scores.json"))
overall = raw_results["accuracy"]
result = {"overall": overall, "raw": raw_results}
os.makedirs(output_dir, exist_ok=True)
with open(os.path.join(output_dir, "result.json"), "w") as f:
json.dump(result, f, indent=2)
Alternative (no bridge script): If the existing tool already writes a JSON file, you can point task.yaml directly at it using result_file and result_mapping:
name: "wrapped-bench-default"
command: "python existing_benchmark.py"
result_file: "scores.json"
result_mapping:
overall: "pass@1"
Best when: the evaluation needs a database, web application, or other service alongside the eval container.
When task.yaml contains a sidecars list, the scheduler uses Docker Compose instead of a single container. It generates a docker-compose.yml with:
post_start commands)eval service that depends_on all sidecars and runs the evaluationExample — PostgreSQL sidecar:
name: "my-db-eval"
command: "python eval.py"
timeout: 7200
sidecars:
- name: postgres
image: "pgvector/pgvector:0.8.0-pg17-bookworm"
environment:
POSTGRES_PASSWORD: "password"
ports: ["5432:5432"]
healthcheck:
test: "pg_isready -U postgres"
interval: "2s"
timeout: "5s"
retries: 30
extra_env:
POSTGRES_HOST: "postgres"
POSTGRES_PORT: "5432"
POSTGRES_PASSWORD: "password"
Example — Heavy sidecar with post_start (WebArena):
sidecars:
- name: shopping_admin
image: "shopping_admin_final_0719"
ports: ["7780:80"]
healthcheck:
test: "curl -sf http://localhost:80/admin || exit 1"
interval: "10s"
timeout: "15s"
retries: 60
post_start:
- "mysql -u magentouser -pMyPassword magentodb -e \"UPDATE ...\""
- "/var/www/magento2/bin/magento cache:flush"
extra_env:
WEBARENA_SHOPPING_ADMIN_URL: "http://shopping_admin:80"
Sidecar networking: containers communicate via Docker Compose service names (e.g., postgres, shopping_admin). The eval container references sidecars by their name field in extra_env.
# 1. Start the server
benchrouter-server --data-dir /tmp/benchrouter-data --port 9000
# 2. Register environment (builds Docker image on server)
benchrouter env register --name my-bench --dockerfile benchmarks/my-bench/environments/my-bench/Dockerfile
benchrouter env status --name my-bench # wait for "Status: ready"
# 3. Register benchmark (uploads directory to server)
benchrouter benchmark register --name my-bench --path benchmarks/my-bench/
# 4. Run all tasks
export BENCHROUTER_MODEL_ENDPOINT=https://your-api/v1
export OPENAI_API_KEY=sk-...
benchrouter run my-bench --model your-model
# 5. Run specific tasks only
benchrouter run my-bench --model your-model --tasks task-a,task-b
# 6. View results
benchrouter eval status --run-id <run_id>
benchrouter eval result --run-id <run_id>
# 7. Compare models
benchrouter compare my-bench
benchrouter compare my-bench --detail
Note: environment auto-build is supported. If you place a Dockerfile at environments/{env_name}/Dockerfile within the benchmark directory, the scheduler will auto-register and build the environment when a job runs. Explicit benchrouter env register is optional but recommended for faster first runs.
Most benchmarks need zero changes to BenchRouter itself. You only touch framework code if:
New result aggregation logic — The default aggregation computes the mean of all task overall scores (see _aggregate_run_results in scheduler.py). If your benchmark needs weighted averaging, min, or custom logic, modify that method.
Custom container resources — Default limits are 16 GB memory, 4 CPU cores, 2-hour timeout. Per-task timeout is configurable in task.yaml. Memory/CPU limits require changes to scheduler.py constants (DEFAULT_MEM_LIMIT, DEFAULT_CPU_QUOTA).
Non-Docker execution — The entire system assumes Docker. If you need bare-metal execution, significant scheduler changes are needed.
New environment variables — If you need the scheduler to inject variables beyond the standard set, add them in _build_environment_vars in scheduler.py. However, extra_env in task.yaml covers most cases without code changes.
New CLI commands — Add to benchrouter/client/cli.py (Typer app). The server API is in benchrouter/server/api.py (FastAPI).
MCPMark is the flagship multi-task benchmark demonstrating all three patterns.
Structure:
benchmarks/mcpmark/
├── benchmark.yaml # declares 4 tasks
├── environments/
│ ├── mcpmark-fs/Dockerfile
│ ├── mcpmark-pg/Dockerfile
│ ├── mcpmark-pw/Dockerfile
│ └── mcpmark-pw-webarena/Dockerfile
├── mcpmark-fs/ # simple: no sidecars
│ ├── task.yaml
│ └── run_mcpmark.py # bridge script
├── mcpmark-pg/ # sidecar: PostgreSQL
│ ├── task.yaml # sidecars + extra_env
│ └── run_mcpmark.py
├── mcpmark-pw/ # simple: Playwright (no sidecar)
│ ├── task.yaml
│ └── run_mcpmark.py
└── mcpmark-pw-webarena/ # heavy sidecar: WebArena Magento with post_start
├── task.yaml
└── run_mcpmark.py
Each task reuses the same run_mcpmark.py bridge script. The script:
BENCHROUTER_MODEL_ENDPOINT, BENCHROUTER_MODEL_NAME, OPENAI_API_KEYOPENAI_API_BASE for LiteLLM compatibilityopenai/ for LiteLLM routingsubprocessmeta.json files from MCPMark outputresult.json with overall fieldEach task uses a different Docker image (pre-built from MCPMark with the right MCP server baked in). The scheduler auto-builds or finds the environment by the environment field in benchmark.yaml.
benchmarks/ (or examples/ for demos)benchmark.yaml with name, tasks list; each task entry has name and environmentnametask.yaml with name and commandDockerfile in environments/{env_name}/ within the benchmark directoryresult.json with overallresult_file/result_mappingsidecars and extra_env to task.yamlresult.json contains a numeric overall fieldbenchrouter eval result --run-id <id>