mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-17 13:04:44 +00:00
Summary: - Add the `zed-eval` Python CLI for Modal/Harbor/Pier benchmark orchestration, including content-addressed remote builds, run/suite management, reporting, rejudge, baseline, and cleanup workflows. - Extend `eval-cli` for remote evals with provider/model overrides and step/tool-call metrics in `result.json`. - Add install/source-run helper scripts so `zed-eval` can be installed or run from the checkout without manually setting `PYTHONPATH`. - Harden the remote harness wrappers around exit-code preservation, archive extraction, custom secret wiring, and Harbor/Pier option parity, with regression coverage. Testing: - Using the CLI for two weeks - `PYTHONPATH=crates/eval_cli python3 -m compileall -q crates/eval_cli/zed_eval` - `uv run --project crates/eval_cli/zed_eval python -m unittest discover -s crates/eval_cli/zed_eval/tests` - `bash -n crates/eval_cli/script/install-zed-eval crates/eval_cli/script/zed-eval` - `cargo check -p eval_cli` - `cargo fmt --package eval_cli -- --check` - `cargo test -p eval_cli --no-run` - `./script/clippy -p eval_cli` Release Notes: - N/A
670 lines
23 KiB
Python
670 lines
23 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from . import benchmarks, config, harness_command, run_index, source
|
|
from .builds import prepare_build_request, resolve_source, validate_build_id
|
|
from .common import (
|
|
dedupe_preserving_order,
|
|
default_namespace,
|
|
deployed_function,
|
|
modal_call_id,
|
|
parse_parts,
|
|
print_json,
|
|
utc_now,
|
|
utc_timestamp,
|
|
)
|
|
from .volume import build_ready_on_volume
|
|
|
|
# Local scratch dir used only for dry-run/plan previews of harness commands.
|
|
PREVIEW_JOBS_DIR = "/tmp/agent-evals/harbor-jobs"
|
|
|
|
|
|
def read_task_file(path: str | None) -> list[str]:
|
|
if not path:
|
|
return []
|
|
return [
|
|
task_name
|
|
for line in Path(path).read_text().splitlines()
|
|
if (task_name := line.strip()) and not task_name.startswith("#")
|
|
]
|
|
|
|
|
|
def baseten_provider_json(
|
|
*,
|
|
model_id: str,
|
|
api_url: str,
|
|
display_name: str | None,
|
|
max_tokens: int,
|
|
max_output_tokens: int,
|
|
) -> str:
|
|
provider = {
|
|
config.BASETEN_PROVIDER_ID: {
|
|
"api_url": api_url,
|
|
"available_models": [
|
|
{
|
|
"name": model_id,
|
|
"display_name": display_name or model_id,
|
|
"max_tokens": max_tokens,
|
|
"max_output_tokens": max_output_tokens,
|
|
"capabilities": {
|
|
"tools": True,
|
|
"images": False,
|
|
"parallel_tool_calls": False,
|
|
"prompt_cache_key": False,
|
|
},
|
|
}
|
|
],
|
|
}
|
|
}
|
|
return json.dumps(provider, separators=(",", ":"))
|
|
|
|
|
|
def resolve_model_preset(model: str) -> str:
|
|
resolved = config.resolve_model_preset(model)
|
|
if resolved != model:
|
|
return resolved
|
|
if model.startswith("baseten:"):
|
|
return f"{config.BASETEN_PROVIDER_ID}/{model.split(':', 1)[1]}"
|
|
return model
|
|
|
|
|
|
def resolve_model_options(
|
|
args: argparse.Namespace,
|
|
) -> tuple[str, str | None, list[str]]:
|
|
raw_model = getattr(args, "model", None) or config.DEFAULT_MODEL
|
|
model = resolve_model_preset(raw_model)
|
|
openai_compatible_provider_json = getattr(
|
|
args, "openai_compatible_provider_json", None
|
|
)
|
|
extra_api_secrets = list(getattr(args, "extra_api_secret", None) or [])
|
|
|
|
if getattr(args, "model_provider", None) == "baseten":
|
|
baseten_model = getattr(args, "baseten_model", None) or model
|
|
if baseten_model.startswith(f"{config.BASETEN_PROVIDER_ID}/"):
|
|
baseten_model = baseten_model.split("/", 1)[1]
|
|
if baseten_model.startswith("baseten:"):
|
|
baseten_model = baseten_model.split(":", 1)[1]
|
|
model = f"{config.BASETEN_PROVIDER_ID}/{baseten_model}"
|
|
|
|
if model.startswith(f"{config.BASETEN_PROVIDER_ID}/"):
|
|
baseten_model = model.split("/", 1)[1]
|
|
if not openai_compatible_provider_json:
|
|
openai_compatible_provider_json = baseten_provider_json(
|
|
model_id=baseten_model,
|
|
api_url=getattr(args, "baseten_api_url", config.BASETEN_API_URL),
|
|
display_name=getattr(args, "baseten_model_display_name", None),
|
|
max_tokens=getattr(
|
|
args, "baseten_model_max_tokens", config.BASETEN_DEFAULT_MAX_TOKENS
|
|
),
|
|
max_output_tokens=getattr(
|
|
args,
|
|
"baseten_model_max_output_tokens",
|
|
config.BASETEN_DEFAULT_MAX_OUTPUT_TOKENS,
|
|
),
|
|
)
|
|
|
|
return model, openai_compatible_provider_json, extra_api_secrets
|
|
|
|
|
|
def derive_run_id(
|
|
base_run_id: str | None, suite_id: str | None, suffix: str, index: int
|
|
) -> str:
|
|
"""Pick a run id for one leg of a (possibly multi-target) launch.
|
|
|
|
An explicit --run-id is used verbatim for the first leg and suffixed for the
|
|
rest; otherwise legs hang off the suite id, falling back to a timestamped id
|
|
for a lone run.
|
|
"""
|
|
if base_run_id:
|
|
return base_run_id if index == 0 else f"{base_run_id}-{suffix}"
|
|
if suite_id:
|
|
return f"{suite_id}-{suffix}-{uuid.uuid4().hex[:6]}"
|
|
return f"{utc_timestamp()}-{uuid.uuid4().hex[:6]}"
|
|
|
|
|
|
def mint_suite_id(args: argparse.Namespace, run_count: int) -> str | None:
|
|
"""A suite id groups multiple runs from one invocation; None for a lone run."""
|
|
if run_count <= 1:
|
|
return None
|
|
explicit = getattr(args, "suite_id", None)
|
|
if explicit:
|
|
return explicit
|
|
prefix = getattr(args, "run_id", None) or "run"
|
|
return f"{source.sanitize_namespace(prefix)}-{utc_timestamp()}"
|
|
|
|
|
|
def build_rejudge_request(args: argparse.Namespace) -> dict[str, Any]:
|
|
"""Build the request for re-grading an existing run with a different judge.
|
|
|
|
The positional `run_id` is the *parent* run; the derived run gets a new id
|
|
under the same experiment so `report`/`list` group them together. Only the
|
|
judge differs — no build, no agent model, no task selection.
|
|
"""
|
|
judge_preset = args.judge
|
|
config.get_judge(judge_preset) # validate before spawning anything remote
|
|
if getattr(args, "experiment_name", None):
|
|
experiment_name = source.sanitize_namespace(args.experiment_name)
|
|
namespace = default_namespace(args)
|
|
else:
|
|
entry = run_index.lookup(args.run_id)
|
|
if not entry:
|
|
raise ValueError(
|
|
f"could not locate run '{args.run_id}' in the local run index "
|
|
f"({run_index.index_path()}). Pass --experiment-name (and "
|
|
"--namespace if it isn't yours)."
|
|
)
|
|
experiment_name = source.sanitize_namespace(entry["experiment_name"])
|
|
namespace = source.sanitize_namespace(
|
|
getattr(args, "namespace", None) or entry["namespace"]
|
|
)
|
|
parent_namespace = (
|
|
source.sanitize_namespace(args.parent_namespace)
|
|
if getattr(args, "parent_namespace", None)
|
|
else namespace
|
|
)
|
|
parent_run_id = args.run_id
|
|
judge_slug = source.sanitize_namespace(judge_preset)
|
|
new_run_id = (
|
|
args.new_run_id
|
|
or f"{parent_run_id}-rejudge-{judge_slug}-{uuid.uuid4().hex[:6]}"
|
|
)
|
|
return {
|
|
"namespace": namespace,
|
|
"experiment_name": experiment_name,
|
|
"run_id": new_run_id,
|
|
"parent": {
|
|
"namespace": parent_namespace,
|
|
"experiment_name": experiment_name,
|
|
"run_id": parent_run_id,
|
|
},
|
|
"judge_preset": judge_preset,
|
|
"judge_model": getattr(args, "judge_model", None),
|
|
"volume_name": args.volume,
|
|
"api_secret_name": args.api_secret,
|
|
"created_at": utc_now(),
|
|
}
|
|
|
|
|
|
def command_rejudge(args: argparse.Namespace) -> int:
|
|
rejudge_request = build_rejudge_request(args)
|
|
if getattr(args, "dry_run", False) or getattr(args, "plan", False):
|
|
print_json(rejudge_request)
|
|
return 0
|
|
|
|
controller = deployed_function(args, "rejudge_controller")
|
|
call = controller.spawn(rejudge_request)
|
|
run_index.record_run(
|
|
{**rejudge_request, "volume_name": args.volume, "kind": "rejudge"}
|
|
)
|
|
parent = rejudge_request["parent"]
|
|
print(f"Namespace: {rejudge_request['namespace']}")
|
|
print(f"Experiment: {rejudge_request['experiment_name']}")
|
|
print(
|
|
f"Source run: {parent['namespace']}/{parent['experiment_name']}/"
|
|
f"{parent['run_id']}"
|
|
)
|
|
print(f"New run id: {rejudge_request['run_id']}")
|
|
print(f"Judge: {rejudge_request['judge_preset']}")
|
|
print(f"Spawned rejudge controller: {modal_call_id(call)}")
|
|
print("\nNext steps (run id alone is enough):")
|
|
print(f" zed-eval status {rejudge_request['run_id']}")
|
|
print(f" zed-eval report {rejudge_request['run_id']} --fetch")
|
|
return 0
|
|
|
|
|
|
def common_run_request_fields(
|
|
args: argparse.Namespace,
|
|
*,
|
|
namespace: str,
|
|
run_id: str,
|
|
experiment_name: str,
|
|
judge_preset: str | None,
|
|
build_id: str | None,
|
|
suite_id: str | None,
|
|
) -> dict[str, Any]:
|
|
"""Fields shared by every benchmark run request."""
|
|
agent_model, openai_compatible_provider_json, extra_api_secrets = (
|
|
resolve_model_options(args)
|
|
)
|
|
task_names = dedupe_preserving_order(
|
|
read_task_file(getattr(args, "tasks", None))
|
|
+ (getattr(args, "include_task_name", None) or [])
|
|
)
|
|
return {
|
|
"created_at": utc_now(),
|
|
"namespace": namespace,
|
|
"run_id": run_id,
|
|
"experiment_name": experiment_name,
|
|
"volume_name": args.volume,
|
|
"api_secret_name": args.api_secret,
|
|
"modal_token_secret_name": args.modal_token_secret,
|
|
"orchestration": config.orchestration_info(),
|
|
"agent_model": agent_model,
|
|
"judge_preset": judge_preset,
|
|
"judge_model": getattr(args, "judge_model", None),
|
|
"build_id": build_id,
|
|
"task_names": task_names,
|
|
"n_tasks": getattr(args, "n_tasks", None),
|
|
"n_concurrent": getattr(args, "n_concurrent", config.DEFAULT_N_CONCURRENT),
|
|
"override_cpus": getattr(args, "override_cpus", config.DEFAULT_OVERRIDE_CPUS),
|
|
"override_memory_mb": getattr(
|
|
args, "override_memory_mb", config.DEFAULT_OVERRIDE_MEMORY_MB
|
|
),
|
|
"sandbox_timeout_secs": getattr(
|
|
args, "sandbox_timeout_secs", config.DEFAULT_SANDBOX_TIMEOUT_SECS
|
|
),
|
|
"sandbox_idle_timeout_secs": getattr(
|
|
args, "sandbox_idle_timeout_secs", config.DEFAULT_SANDBOX_IDLE_TIMEOUT_SECS
|
|
),
|
|
"build_wait_timeout_secs": getattr(args, "build_wait_timeout_secs", 7200),
|
|
"extra_harbor_args": list(getattr(args, "extra_harbor_arg", None) or []),
|
|
"openai_compatible_provider_json": openai_compatible_provider_json,
|
|
"anthropic_available_models_json": getattr(
|
|
args, "anthropic_available_models_json", None
|
|
),
|
|
"extra_api_secrets": dedupe_preserving_order(extra_api_secrets),
|
|
"suite_id": suite_id,
|
|
}
|
|
|
|
|
|
def benchmark_plan_entry(
|
|
benchmark_id: str, run_request: dict[str, Any], build_request: dict[str, Any] | None
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"benchmark": benchmark_id,
|
|
"run_id": run_request["run_id"],
|
|
"harness": run_request["benchmark"]["harness"],
|
|
"model": run_request["agent_model"],
|
|
"judge": run_request.get("judge_preset"),
|
|
"build_id": run_request.get("build_id"),
|
|
"will_build": build_request is not None,
|
|
"n_tasks": run_request.get("n_tasks"),
|
|
}
|
|
|
|
|
|
def print_plan(
|
|
summaries: list[dict[str, Any]],
|
|
details: list[tuple[str, dict[str, Any], dict[str, Any] | None]],
|
|
*,
|
|
verbose: bool,
|
|
) -> None:
|
|
print("Plan:")
|
|
print_json(summaries)
|
|
if verbose:
|
|
for header, run_request, build_request in details:
|
|
print(f"\n=== {header} ===")
|
|
print_dry_run(run_request, build_request)
|
|
|
|
|
|
def print_dry_run(
|
|
run_request: dict[str, Any], build_request: dict[str, Any] | None
|
|
) -> None:
|
|
print("Run request:")
|
|
print_json(run_request)
|
|
if build_request:
|
|
print("\nBuild request:")
|
|
build_preview = dict(build_request)
|
|
patch = build_preview.pop("patch", "")
|
|
build_preview["patch_line_count"] = len(patch.splitlines())
|
|
build_preview["source"] = source.public_source_info(
|
|
build_request.get("source") or {}
|
|
)
|
|
print_json(build_preview)
|
|
command = config.redacted_command(
|
|
harness_command.build_harness_command(run_request, PREVIEW_JOBS_DIR)
|
|
)
|
|
print("\nHarness command:")
|
|
print(command)
|
|
|
|
|
|
def execute_prepared_runs(
|
|
args: argparse.Namespace,
|
|
prepared: list[tuple[str, dict[str, Any], dict[str, Any] | None]],
|
|
plan_entry,
|
|
) -> int:
|
|
if args.plan:
|
|
print_plan(
|
|
[
|
|
plan_entry(label, run_request, build_request)
|
|
for label, run_request, build_request in prepared
|
|
],
|
|
prepared,
|
|
verbose=args.verbose,
|
|
)
|
|
return 0
|
|
if args.dry_run:
|
|
for label, run_request, build_request in prepared:
|
|
print(f"\n=== {label} ===")
|
|
print_dry_run(run_request, build_request)
|
|
return 0
|
|
|
|
spawned_builds: set[str] = set()
|
|
for label, run_request, build_request in prepared:
|
|
print(f"\n=== Launching {label} ===")
|
|
launch_prepared_run(args, run_request, build_request, spawned_builds)
|
|
return 0
|
|
|
|
|
|
def launch_prepared_run(
|
|
args: argparse.Namespace,
|
|
run_request: dict[str, Any],
|
|
build_request: dict[str, Any] | None,
|
|
spawned_builds: set[str] | None = None,
|
|
) -> None:
|
|
build_function = None
|
|
if build_request:
|
|
run_request["source"] = source.public_source_info(build_request["source"])
|
|
print_untracked_warning(build_request)
|
|
build_function = deployed_function(args, "build_eval_cli")
|
|
record_function = deployed_function(args, "create_run_record")
|
|
controller_function = deployed_function(args, "run_controller")
|
|
|
|
record_state = record_function.remote(run_request)
|
|
run_index.record_run(run_request)
|
|
|
|
print(f"Namespace: {run_request['namespace']}")
|
|
print(f"Experiment: {run_request['experiment_name']}")
|
|
print(f"Run id: {run_request['run_id']}")
|
|
print(f"Volume: {run_request['volume_name']}")
|
|
print(f"Run state: {record_state['status']}")
|
|
print(f"Model: {run_request['agent_model']}")
|
|
if run_request.get("judge_preset"):
|
|
print(f"Judge: {run_request['judge_preset']}")
|
|
if run_request.get("suite_id"):
|
|
print(f"Suite: {run_request['suite_id']}")
|
|
if run_request.get("build_id"):
|
|
print(f"Build id: {run_request['build_id']}")
|
|
if run_request.get("task_names"):
|
|
print(f"Tasks: {len(run_request['task_names'])} explicit task(s)")
|
|
elif run_request.get("n_tasks"):
|
|
print(f"Tasks: Harbor --n-tasks {run_request['n_tasks']}")
|
|
else:
|
|
print("Tasks: full dataset selection")
|
|
|
|
build_id = run_request.get("build_id")
|
|
if build_function is not None and build_id not in (spawned_builds or set()):
|
|
build_call = build_function.spawn(build_request)
|
|
print(f"Spawned build: {modal_call_id(build_call)}")
|
|
if spawned_builds is not None and build_id:
|
|
spawned_builds.add(build_id)
|
|
controller_call = controller_function.spawn(run_request)
|
|
print(f"Spawned controller: {modal_call_id(controller_call)}")
|
|
|
|
run_id = run_request["run_id"]
|
|
print(
|
|
"\nNext steps (run id alone is enough; namespace/experiment are resolved "
|
|
"from this machine's local run index):"
|
|
)
|
|
print(f" zed-eval status {run_id}")
|
|
print(f" zed-eval logs {run_id}")
|
|
print(f" zed-eval report {run_id} --fetch")
|
|
|
|
|
|
def resolve_benchmark_judge(
|
|
args: argparse.Namespace, benchmark: benchmarks.Benchmark
|
|
) -> str | None:
|
|
if not benchmark.needs_judge:
|
|
return None
|
|
judge = getattr(args, "judge", None) or config.DEFAULT_JUDGE_PRESET
|
|
if judge == "auto":
|
|
return benchmark.default_judge or "leaderboard"
|
|
config.get_judge(judge)
|
|
return judge
|
|
|
|
|
|
def benchmark_metadata_for_run(
|
|
args: argparse.Namespace, benchmark: benchmarks.Benchmark
|
|
) -> dict[str, object]:
|
|
metadata = benchmarks.benchmark_metadata(benchmark)
|
|
dataset = metadata.get("dataset")
|
|
if (
|
|
isinstance(dataset, dict)
|
|
and dataset.get("repo_url") == benchmarks.SWE_ATLAS_REPO_URL
|
|
):
|
|
dataset["repo_url"] = (
|
|
getattr(args, "swe_atlas_repo_url", None) or benchmarks.SWE_ATLAS_REPO_URL
|
|
)
|
|
dataset["repo_ref"] = (
|
|
getattr(args, "swe_atlas_repo_ref", None) or benchmarks.SWE_ATLAS_REPO_REF
|
|
)
|
|
return metadata
|
|
|
|
|
|
def print_untracked_warning(build_request: dict[str, Any]) -> None:
|
|
build_source = build_request.get("source") or {}
|
|
untracked_files = build_source.get("untracked_files") or []
|
|
if untracked_files:
|
|
print(
|
|
f"Warning: proceeding with {len(untracked_files)} untracked file(s) "
|
|
"not included in the build patch.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
|
|
def prepare_shared_build(
|
|
args: argparse.Namespace,
|
|
) -> tuple[str | None, dict[str, Any] | None]:
|
|
"""Resolve the build once for a whole (possibly multi-benchmark) run.
|
|
|
|
Returns `(build_id, build_request)`. `build_request` is None when the target
|
|
build already exists; otherwise the caller should spawn the build.
|
|
"""
|
|
explicit_build_id = getattr(args, "build", None)
|
|
validate_build_id(explicit_build_id)
|
|
if (
|
|
explicit_build_id
|
|
and not getattr(args, "plan", False)
|
|
and not getattr(args, "dry_run", False)
|
|
and build_ready_on_volume(args, explicit_build_id)
|
|
):
|
|
print(f"Reusing existing build {explicit_build_id} (already on volume)")
|
|
return explicit_build_id, None
|
|
|
|
base_sha, clean_source, source_label, pre_resolved = resolve_source(args)
|
|
|
|
build_request = prepare_build_request(
|
|
base_sha=base_sha,
|
|
patch_path=getattr(args, "patch_path", None),
|
|
build_id=explicit_build_id,
|
|
allow_untracked=getattr(args, "allow_untracked", False),
|
|
require_clean=getattr(args, "require_clean", False),
|
|
repo_url=getattr(args, "repo_url", None),
|
|
clean_source=clean_source,
|
|
source_label=source_label,
|
|
pre_resolved_base_sha=pre_resolved,
|
|
)
|
|
build_id = build_request["build_id"]
|
|
|
|
if not getattr(args, "plan", False) and not getattr(args, "dry_run", False):
|
|
if build_ready_on_volume(args, build_id):
|
|
print(f"Reusing existing build {build_id} (already on volume)")
|
|
return build_id, None
|
|
|
|
return build_id, build_request
|
|
|
|
|
|
def command_build(args: argparse.Namespace) -> int:
|
|
build_id, build_request = prepare_shared_build(args)
|
|
if build_request is None:
|
|
return 0
|
|
|
|
print(f"Build id: {build_id}")
|
|
print(f"Base sha: {build_request['base_sha']}")
|
|
print(f"Patch sha256: {build_request['patch_sha256'] or '(none)'}")
|
|
print_untracked_warning(build_request)
|
|
|
|
build_function = deployed_function(args, "build_eval_cli")
|
|
if args.detach:
|
|
call = build_function.spawn(build_request)
|
|
print(f"Spawned build: {modal_call_id(call)}")
|
|
else:
|
|
result = build_function.remote(build_request)
|
|
print_json(result)
|
|
return 0
|
|
|
|
|
|
def build_benchmark_run_request(
|
|
args: argparse.Namespace,
|
|
*,
|
|
benchmark_id: str,
|
|
build_id: str | None,
|
|
suite_id: str | None,
|
|
index: int,
|
|
run_id_suffix: str | None = None,
|
|
) -> dict[str, Any]:
|
|
benchmark = benchmarks.get_benchmark(benchmark_id)
|
|
run_id = derive_run_id(
|
|
getattr(args, "run_id", None), suite_id, run_id_suffix or benchmark_id, index
|
|
)
|
|
# Staff mode is off by default for remote runs: it enables the sandboxed
|
|
# terminal, which hangs inside Modal sandboxes.
|
|
extra_env = {"EVAL_CLI_STAFF": "true" if getattr(args, "staff", False) else "false"}
|
|
|
|
return {
|
|
**common_run_request_fields(
|
|
args,
|
|
namespace=default_namespace(args),
|
|
run_id=run_id,
|
|
# The benchmark id doubles as the experiment name for run storage
|
|
# paths (runs/<namespace>/<experiment_name>/<run_id>), keeping
|
|
# monitoring and fetch uniform across benchmarks.
|
|
experiment_name=source.sanitize_namespace(benchmark_id),
|
|
judge_preset=resolve_benchmark_judge(args, benchmark),
|
|
build_id=build_id,
|
|
suite_id=suite_id,
|
|
),
|
|
"benchmark": benchmark_metadata_for_run(args, benchmark),
|
|
"eval_cli_timeout": getattr(args, "eval_cli_timeout", None),
|
|
"extra_env": extra_env,
|
|
}
|
|
|
|
|
|
def prepare_runs_for_benchmarks(
|
|
args: argparse.Namespace,
|
|
benchmark_ids: list[str],
|
|
*,
|
|
suite_id: str | None,
|
|
label_for_benchmark,
|
|
mark_swe_atlas_parts: bool = False,
|
|
) -> list[tuple[str, dict[str, Any], dict[str, Any] | None]]:
|
|
if not benchmark_ids:
|
|
raise ValueError("choose at least one benchmark to run")
|
|
|
|
build_id, build_request = prepare_shared_build(args)
|
|
prepared: list[tuple[str, dict[str, Any], dict[str, Any] | None]] = []
|
|
for index, benchmark_id in enumerate(benchmark_ids):
|
|
label = label_for_benchmark(benchmark_id)
|
|
run_request = build_benchmark_run_request(
|
|
args,
|
|
benchmark_id=benchmark_id,
|
|
build_id=build_id,
|
|
suite_id=suite_id,
|
|
index=index,
|
|
run_id_suffix=label,
|
|
)
|
|
if (
|
|
mark_swe_atlas_parts
|
|
and benchmark_id in benchmarks.SWE_ATLAS_PART_BENCHMARKS.values()
|
|
):
|
|
run_request["suite_part"] = label
|
|
# Only the first run carries the build_request; launch_prepared_run
|
|
# dedups the actual spawn via spawned_builds anyway.
|
|
prepared.append((label, run_request, build_request if index == 0 else None))
|
|
return prepared
|
|
|
|
|
|
def prepare_benchmark_runs(
|
|
args: argparse.Namespace,
|
|
) -> list[tuple[str, dict[str, Any], dict[str, Any] | None]]:
|
|
benchmark_ids = benchmarks.resolve_benchmarks(
|
|
list(getattr(args, "benchmark", None) or [])
|
|
)
|
|
return prepare_runs_for_benchmarks(
|
|
args,
|
|
benchmark_ids,
|
|
suite_id=mint_suite_id(args, len(benchmark_ids)),
|
|
label_for_benchmark=lambda benchmark_id: benchmark_id,
|
|
)
|
|
|
|
|
|
def command_run(args: argparse.Namespace) -> int:
|
|
return execute_prepared_runs(
|
|
args,
|
|
prepare_benchmark_runs(args),
|
|
benchmark_plan_entry,
|
|
)
|
|
|
|
|
|
def resolve_suite_parts(args: argparse.Namespace) -> list[str]:
|
|
parts = parse_parts([args.parts] if getattr(args, "parts", None) else [])
|
|
if parts:
|
|
return parts
|
|
raise ValueError(
|
|
"choose at least one SWE-Atlas part with --parts (e.g. --parts rf,qna or --parts all)"
|
|
)
|
|
|
|
|
|
def suite_plan_entry(
|
|
part: str, run_request: dict[str, Any], build_request: dict[str, Any] | None
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"part": part,
|
|
**benchmark_plan_entry(
|
|
run_request["benchmark"]["id"], run_request, build_request
|
|
),
|
|
}
|
|
|
|
|
|
def suite_entry_label(benchmark_id: str) -> str:
|
|
for part, part_benchmark_id in benchmarks.SWE_ATLAS_PART_BENCHMARKS.items():
|
|
if part_benchmark_id == benchmark_id:
|
|
return part
|
|
return benchmark_id
|
|
|
|
|
|
def prepare_benchmark_suite(
|
|
args: argparse.Namespace, selectors: list[str]
|
|
) -> list[tuple[str, dict[str, Any], dict[str, Any] | None]]:
|
|
benchmark_ids = benchmarks.resolve_benchmarks(selectors)
|
|
if not benchmark_ids:
|
|
raise ValueError("choose at least one benchmark to run")
|
|
if getattr(args, "zed_version", None):
|
|
args.clean_source = True
|
|
args.require_clean = True
|
|
timestamp = utc_timestamp()
|
|
prefix_seed = args.run_id_prefix or args.experiment_prefix or "swe-atlas"
|
|
if getattr(args, "zed_version", None):
|
|
prefix_seed = f"{prefix_seed}-{source.sanitize_namespace(args.zed_version)}"
|
|
suite_id = args.suite_id or f"{source.sanitize_namespace(prefix_seed)}-{timestamp}"
|
|
|
|
return prepare_runs_for_benchmarks(
|
|
args,
|
|
benchmark_ids,
|
|
suite_id=suite_id,
|
|
label_for_benchmark=suite_entry_label,
|
|
mark_swe_atlas_parts=True,
|
|
)
|
|
|
|
|
|
def prepare_suite(
|
|
args: argparse.Namespace,
|
|
) -> list[tuple[str, dict[str, Any], dict[str, Any] | None]]:
|
|
return prepare_benchmark_suite(args, resolve_suite_parts(args))
|
|
|
|
|
|
def command_swe_atlas(args: argparse.Namespace) -> int:
|
|
from .interactive import configure_interactive_suite
|
|
|
|
configure_interactive_suite(args)
|
|
prepared = (
|
|
prepare_benchmark_suite(args, args.benchmark)
|
|
if getattr(args, "benchmark", None)
|
|
else prepare_suite(args)
|
|
)
|
|
return execute_prepared_runs(args, prepared, suite_plan_entry)
|