From b92b18cf89da1bae16d0c35f83256d2aff1b1e44 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 2 Jul 2026 09:16:24 +0800 Subject: [PATCH 01/36] install docker in images --- docker/build_image.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/build_image.sh b/docker/build_image.sh index 8b74709d..171bb180 100644 --- a/docker/build_image.sh +++ b/docker/build_image.sh @@ -246,6 +246,12 @@ if ! echo "${dockerd_version_output}" | grep -F "Docker version" > /dev/null 2>& echo "实际输出:${dockerd_version_output}" exit 1 fi +# 进一步校验:dockerd 与 docker 版本号应一致,确保不是同一二进制被误调用两次 +dockerd_ver=$(echo "${dockerd_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) +if [ "${docker_ver}" != "${dockerd_ver}" ]; then + echo "错误:docker 与 dockerd 版本不一致(${docker_ver} vs ${dockerd_ver}),可能 dockerd 安装异常" + exit 1 +fi # 版本号校验:Docker >= 20.0,Docker Compose >= 2.0.0 docker_ver=$(echo "${docker_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) From 89b65581de11c114f6af218e6ab1dde59476d24b Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 2 Jul 2026 09:22:39 +0800 Subject: [PATCH 02/36] install docker in images --- docker/build_image.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker/build_image.sh b/docker/build_image.sh index 171bb180..8b74709d 100644 --- a/docker/build_image.sh +++ b/docker/build_image.sh @@ -246,12 +246,6 @@ if ! echo "${dockerd_version_output}" | grep -F "Docker version" > /dev/null 2>& echo "实际输出:${dockerd_version_output}" exit 1 fi -# 进一步校验:dockerd 与 docker 版本号应一致,确保不是同一二进制被误调用两次 -dockerd_ver=$(echo "${dockerd_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) -if [ "${docker_ver}" != "${dockerd_ver}" ]; then - echo "错误:docker 与 dockerd 版本不一致(${docker_ver} vs ${dockerd_ver}),可能 dockerd 安装异常" - exit 1 -fi # 版本号校验:Docker >= 20.0,Docker Compose >= 2.0.0 docker_ver=$(echo "${docker_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) From f936a069475171260d41ae49fef86e821a850e63 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 2 Jul 2026 09:16:24 +0800 Subject: [PATCH 03/36] install docker in images --- docker/build_image.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/build_image.sh b/docker/build_image.sh index 8b74709d..171bb180 100644 --- a/docker/build_image.sh +++ b/docker/build_image.sh @@ -246,6 +246,12 @@ if ! echo "${dockerd_version_output}" | grep -F "Docker version" > /dev/null 2>& echo "实际输出:${dockerd_version_output}" exit 1 fi +# 进一步校验:dockerd 与 docker 版本号应一致,确保不是同一二进制被误调用两次 +dockerd_ver=$(echo "${dockerd_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) +if [ "${docker_ver}" != "${dockerd_ver}" ]; then + echo "错误:docker 与 dockerd 版本不一致(${docker_ver} vs ${dockerd_ver}),可能 dockerd 安装异常" + exit 1 +fi # 版本号校验:Docker >= 20.0,Docker Compose >= 2.0.0 docker_ver=$(echo "${docker_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) From 38b052b056f0304a76171ee25de4abe6df47c2c8 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 2 Jul 2026 09:22:39 +0800 Subject: [PATCH 04/36] install docker in images --- docker/build_image.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker/build_image.sh b/docker/build_image.sh index 171bb180..8b74709d 100644 --- a/docker/build_image.sh +++ b/docker/build_image.sh @@ -246,12 +246,6 @@ if ! echo "${dockerd_version_output}" | grep -F "Docker version" > /dev/null 2>& echo "实际输出:${dockerd_version_output}" exit 1 fi -# 进一步校验:dockerd 与 docker 版本号应一致,确保不是同一二进制被误调用两次 -dockerd_ver=$(echo "${dockerd_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) -if [ "${docker_ver}" != "${dockerd_ver}" ]; then - echo "错误:docker 与 dockerd 版本不一致(${docker_ver} vs ${dockerd_ver}),可能 dockerd 安装异常" - exit 1 -fi # 版本号校验:Docker >= 20.0,Docker Compose >= 2.0.0 docker_ver=$(echo "${docker_version_output}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) From 62a58affe41eda8fdeb93b61b5068fe1fe53d660 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:39:53 +0800 Subject: [PATCH 05/36] update configs of agent runtime --- .../harbor_terminal_bench_2_task.py | 24 ++++++++++++++++++- .../mini_swe_agent_swe_bench_full.py | 18 +++++++++++++- .../mini_swe_agent_swe_bench_lite.py | 20 ++++++++++++++-- .../mini_swe_agent_swe_bench_multilingual.py | 18 +++++++++++++- ...i_swe_agent_swe_bench_multilingual_mini.py | 19 ++++++++++++++- .../mini_swe_agent_swe_bench_verified.py | 18 +++++++++++++- .../mini_swe_agent_swe_bench_verified_mini.py | 18 +++++++++++++- .../mini_swe_agent_swe_bench_pro_full.py | 18 +++++++++++++- .../mini_swe_agent_swe_bench_pro_mini.py | 19 ++++++++++++++- 9 files changed, 162 insertions(+), 10 deletions(-) diff --git a/ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py b/ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py index ace9d81b..700a7132 100644 --- a/ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py +++ b/ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py @@ -6,6 +6,27 @@ with read_base(): from ais_bench.benchmark.configs.summarizers.example import summarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10 +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 切到其它 tier 时只改 bootstrap.sh --datasets 参数即可(4 套常用路径): +# full: /data/datasets/harbor/full/terminal-bench-2 (89 case) +# mini-0.10: /data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10 (7 case, 默认) +# mini-0.14: /data/datasets/harbor/mini-0.14/terminal-bench-2-offline-selected_0.14 (10 case) +# mini-0.20: /data/datasets/harbor/mini-0.20/terminal-bench-2-offline-selected_0.20 (14 case) +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时使用下方 DEFAULT_DATASET_FALLBACK 兜底。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:/data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10}}' + models = [ dict( abbr="terminus-2", @@ -59,7 +80,8 @@ environment_type="docker", # -e/--env: 环境类型 (docker, daytona, e2b, modal) environment_force_build=False, # --force-build/--no-force-build: 是否强制重建环境 environment_delete=False, # --delete/--no-delete: 完成后是否删除环境 - path="/path/to/terminal-bench-2/", # -p/--path: 本地数据集路径 + path=DEFAULT_DATASET_PATH, # -p/--path: 本地数据集路径(需与 bootstrap.sh --datasets 传入路径对齐) + # 切换 tier:把上面 DEFAULT_DATASET_PATH 改成 full / mini-0.14 / mini-0.20 对应路径 dataset_name_version=None, # -d/--dataset: 远程数据集名称@版本 task_names=None, # --include-task-name: 包含的任务名称(支持glob模式)例如 ["task_name1", "task_name2"] exclude_task_names=None, # --exclude-task-name: 排除的任务名称 diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_full.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_full.py index e3efa693..cf409a3b 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_full.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_full.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -31,7 +46,8 @@ type=SWEBenchDataset, abbr="swebench_full", # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing dir -> HF snapshot_download - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="full", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py index d218d61d..93a548a7 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -30,8 +45,9 @@ dict( type=SWEBenchDataset, abbr="swebench_lite", - # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing -> HF download - path="", + # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing dir -> HF snapshot_download + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="lite", split="test", filter_spec="", diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual.py index 3f3cd5a4..a48f82ab 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -31,7 +46,8 @@ type=SWEBenchDataset, abbr="swebench_multilingual", # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing -> HF download - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="multilingual", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual_mini.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual_mini.py index 0bed2c06..942499e9 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual_mini.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_multilingual_mini.py @@ -4,6 +4,22 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → HF 在线下载会失败)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: 下载 mini 数据集并准备目录(HF 无该数据集) +# https://modelers.cn/datasets/AISBench/SWE-Bench_Multilingual_mini +# 2. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/ +# 3. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -31,7 +47,8 @@ type=SWEBenchDataset, abbr="swebench_multilingual_mini", # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing -> HF download - path="", # Set local path to the mini dataset. Download from https://modelers.cn/datasets/AISBench/SWE-Bench_Multilingual_mini + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;mini 数据集 HF 无,必须 env var 注入本地路径 + path=DEFAULT_DATASET_PATH, name="multilingual", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified.py index 2287c917..aebd0349 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -31,7 +46,8 @@ type=SWEBenchDataset, abbr="swebench_verified", # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing -> HF download - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="verified", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified_mini.py b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified_mini.py index fe424766..d4f96153 100644 --- a/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified_mini.py +++ b/ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_verified_mini.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchInferTask, SWEBenchEvalTask from ais_bench.benchmark.summarizers import SWEBenchSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 200 models = [ @@ -31,7 +46,8 @@ type=SWEBenchDataset, abbr="swebench_verified_mini", # Relative to AIS_BENCH_DATASETS_CACHE (default: project root); missing -> HF download - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="verified_mini", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_full.py b/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_full.py index 5a09b4ea..cf17ac08 100644 --- a/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_full.py +++ b/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_full.py @@ -4,6 +4,21 @@ from ais_bench.benchmark.tasks import SWEBenchProInferTask, SWEBenchProEvalTask from ais_bench.benchmark.summarizers import SWEBenchProSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → 沿用 HF 在线下载)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench_pro/<...> +# 2. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可; +# 不 export 时回落到 HF 在线下载(默认行为)。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 250 models = [ @@ -33,7 +48,8 @@ dict( type=SWEBenchProDataset, abbr="swebench_pro_full_data", - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;不设置时为空,沿用 HF 在线下载 + path=DEFAULT_DATASET_PATH, name="full", split="test", step_limit=STEP_LIMIT, diff --git a/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py b/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py index 353b10bd..a137e268 100644 --- a/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py +++ b/ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py @@ -4,6 +4,22 @@ from ais_bench.benchmark.tasks import SWEBenchProInferTask, SWEBenchProEvalTask from ais_bench.benchmark.summarizers import SWEBenchProSummarizer +# 数据集路径:用 mmengine 的 {{$VAR:default}} 占位符从环境变量读取。 +# mmengine 在执行本文件前,会先用 os.environ['VAR'] 的值替换整个 {{$VAR:default}} +# 占位符;如果环境变量未设置,则替换为 default 部分(此处为空 → mini 无 HF 源会失败)。 +# +# 这里 AISBENCH_AGENT_DATASET_PATH 的值就是 bootstrap.sh --datasets 传入的完整路径, +# 由 bootstrap.sh 自动注入到容器环境变量中,无需 import sys / import os 任何库。 +# +# 使用流程: +# 1. 物理机: 下载 mini 数据集并准备目录 +# https://modelers.cn/datasets/AISBench/SWE-Bench_Pro_mini +# 2. 物理机: bash bootstrap.sh --datasets /data/datasets/swebench_pro/ +# 3. 容器内: ais_bench <此配置> --debug # path 自动从 env var 读 +# +# 不使用 agent_runtime 容器方案时:直接 export AISBENCH_AGENT_DATASET_PATH= 即可。 +DEFAULT_DATASET_PATH = '{{$AISBENCH_AGENT_DATASET_PATH:}}' + STEP_LIMIT = 250 models = [ @@ -33,7 +49,8 @@ dict( type=SWEBenchProDataset, abbr="swebench_pro_mini_data", - path="", + # 本字段通过环境变量 AISBENCH_AGENT_DATASET_PATH 注入;mini 数据集 HF 无,必须 env var 注入本地路径 + path=DEFAULT_DATASET_PATH, name="mini", split="test", step_limit=STEP_LIMIT, From b78c945c30e83514f5cd42bbcd553074bb234879 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:40:40 +0800 Subject: [PATCH 06/36] update docker doc of agent runtime --- docker/OVERVIEW.zh.md | 133 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/docker/OVERVIEW.zh.md b/docker/OVERVIEW.zh.md index ccc26dba..0db6f6b7 100644 --- a/docker/OVERVIEW.zh.md +++ b/docker/OVERVIEW.zh.md @@ -171,6 +171,55 @@ docker build \ 镜像预装了 Docker Engine(≥ 20.0)与 Docker Compose v2(≥ 2.0.0)。在容器内启动 Docker 有两种模式,请根据宿主 Docker 版本与隔离需求二选一。 ### 模式 A:Docker-in-Docker(推荐,真嵌套容器,要求宿主 Docker ≥ 20.10 + cgroup v2) +<<<<<<< HEAD +```bash +┌────────────────────────── 宿主机 (Host) ──────────────────────────┐ +│ │ +│ ┌──────────────────┐ ┌──────────────────────────┐ │ +│ │ Host Kernel │ │ Host dockerd │ │ +│ │ (cgroup v2) │◀───────▶│ /var/run/docker.sock │ │ +│ └──────────────────┘ │ 管理宿主自身的容器 │ │ +│ └──────────────────────────┘ │ +│ │ +│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ +│ ┃ AISBench Container (--privileged --cgroupns=host) ┃ │ +│ ┃ ┃ │ +│ ┃ ┌──────────────────────┐ ┌──────────────────────┐ ┃ │ +│ ┃ │ Docker CLI │────▶│ Inner dockerd │ ┃ │ +│ ┃ │ (用户执行) │ │ (容器内独立进程) │ ┃ │ +│ ┃ └──────────────────────┘ │ daemon.json: │ ┃ │ +│ ┃ │ native.cgroupdriver │ ┃ │ +│ ┃ │ =cgroupfs │ ┃ │ +│ ┃ │ storage=vfs │ ┃ │ +│ ┃ └──────────┬───────────┘ ┃ │ +│ ┃ │ spawn ┃ │ +│ ┃ ▼ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ containerd │ ┃ │ +│ ┃ │ (内嵌在 inner d) │ ┃ │ +│ ┃ └──────────┬───────────┘ ┃ │ +│ ┃ │ ┃ │ +│ ┃ ▼ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ 嵌套子容器 │ ┃ │ +│ ┃ │ (真隔离命名空间) │ ┃ │ +│ ┃ │ ┌────────────────┐ │ ┃ │ +│ ┃ │ │ Agent 进程 │ │ ┃ │ +│ ┃ │ │ OpenBLAS │ │ ┃ │ +│ ┃ │ │ Python deps │ │ ┃ │ +│ ┃ │ └────────────────┘ │ ┃ │ +│ ┃ └──────────────────────┘ ┃ │ +│ ┃ ┃ │ +│ ┃ —— 隔离边界:独立 PID/IPC/Net/Mount/User 命名空间 —— ┃ │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +│ │ +│ ✦ 嵌套子容器不出现在宿主 `docker ps` 中 │ +│ ✦ 子容器继承 Docker 官方默认 seccomp profile(无 clone3 拦截) │ +│ ✦ 宿主 dockerd 重启不会影响 inner dockerd │ +└──────────────────────────────────────────────────────────────────┘ +``` +======= +>>>>>>> master_center 容器内自起一个独立的 `dockerd`,子容器与宿主机完全隔离。这是 agent 测评的**首选模式**:子容器继承的是 Docker 官方默认 seccomp profile,不会触发 openEuler / RHEL 加固 profile 导致的 `pthread_create` / `clone3` 拦截问题;也不存在宿主 dockerd 重启后 socket 句柄失效的问题。 @@ -236,6 +285,50 @@ docker compose version - 对于长时间运行的 DinD 场景,建议在 `/etc/docker/daemon.json` 中加入 `"default-runtime": "runc"`、`"log-driver": "json-file"`、`"data-root"` 等调优项。 ### 模式 B:Socket 代理(兼容任意 Docker 版本 ≥ 1.0) +<<<<<<< HEAD +```bash +┌────────────────────────── 宿主机 (Host) ──────────────────────────┐ +│ │ +│ ┌──────────────────┐ │ +│ │ Host Kernel │ │ +│ └──────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────┐ bind mount │ +│ │ Host dockerd │ /var/run/docker │ +│ │ /var/run/docker.sock ──────────────────────────┐ │ +│ │ (管理宿主自身的容器) │ │ │ +│ └────────────┬─────────────────────────────┘ │ │ +│ │ │ │ +│ │ 实际创建/管理 │ │ +│ ▼ │ │ +│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┓ │ +│ ┃ AISBench Container (--privileged, 与宿主共享 PID/IPC) ┃ │ +│ ┃ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ Docker CLI │──── HTTP/Unix socket 调用 ─────┘ ┃ │ +│ ┃ │ (用户执行) │ 容器内无 dockerd 进程 ┃ │ +│ ┃ └──────────────────────┘ ┃ │ +│ ┃ ┃ │ +│ ┃ —— 与宿主共享内核、PID、IPC 命名空间 —— ┃ │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +│ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ 子容器 (实为宿主上的容器) │ │ +│ │ ┌────────────────┐ │ │ +│ │ │ Agent 进程 │ ← 由 Host dockerd │ │ +│ │ │ OpenBLAS │ 直接创建/回收 │ │ +│ │ │ Python deps │ │ │ +│ │ └────────────────┘ │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +│ ✦ 嵌套子容器出现在宿主 `docker ps` 中 │ +│ ✦ 子容器继承宿主 dockerd 的 seccomp profile │ +│ (openEuler/RHEL 加固 profile → 触发 clone3 拦截) │ +│ ✦ 宿主 dockerd 重启 → socket inode 失效 → 需 `docker restart` │ +└──────────────────────────────────────────────────────────────────┘ +``` +======= +>>>>>>> master_center 挂载宿主的 Docker socket,使容器内的 `docker run` 实际上在**宿主机** daemon 上创建容器。仅当宿主 Docker 版本低于 20.10、或不支持 cgroup v2 无法使用模式 A 时再选用本模式。 @@ -354,6 +447,46 @@ docker compose -f /tmp/docker-compose.yml up - [Sysbox](https://github.com/nestybox/sysbox)——支持嵌套容器而无需 `--privileged`,代价是要在宿主机装自定义运行时。 - Rootless Docker——以非 root 用户运行 `dockerd`,但有限制(多数发行版不支持 `overlay2`、网络限制等)。 +<<<<<<< HEAD +## Agent 测评一键环境准备 + +针对 Harbor Terminal-Bench、SWE-bench、SWE-bench Pro 等 agent 测评,本仓库在 [`docker/agent_runtime/`](agent_runtime/README.md) 提供了一键环境准备方案,将上文"运行 Agent / 沙箱类测评"章节的模式 A/B 选择、`daemon.json` 配置、`--cgroupns=host`、seccomp 处理等步骤收敛为一个脚本,并额外解决了: + +- **依赖冲突**:多个 agent 测评的依赖互相冲突(如 harbor 强制升级 datasets 到 4.0+、两个 `mini-swe-agent` fork 同包名互相覆盖),通过 runtime 镜像内的多 venv 隔离 +- **case 镜像庞大**:SWE-bench full ~1TB 不能整体打包,**不**预置到 runtime 镜像,由用户自行 docker pull / load +- **数据集版本频繁**:agent 数据集版本变化快,**不**预置到 runtime 镜像,由用户在物理机准备好,通过 `bootstrap.sh --datasets ` 挂载进容器(容器内路径 = 宿主路径) +- **环境无验证**:`doctor.sh` 在跑测评前验证 runtime 配置(L1 静态)+ case 镜像存在性扫描(warning),失败时给精确修复指引 + +**用户使用**(三个 pack 都用同一条流程,按 benchmark 选 `--datasets` 和 `agent_env`): + +```bash +# 物理机:准备数据集目录 + 一键起 runtime 容器(把数据集目录挂载进容器) +# Harbor: +mkdir -p /data/datasets/harbor/mini-0.10 +# 准备数据集(见 harbor_bench.md)+ case 镜像(可用 bootstrap --case-tar 离线加载) +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10 \ + --case-tar /data/cases/case-tb2-mini-0.10.tar.gz +docker exec -it ais_bench_agent bash +# 容器内: +ais_bench_agent_doctor.sh harbor +agent_env harbor +ais_bench ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py --debug + +# SWE-bench / SWE-bench Pro 同理: +# --datasets +# agent_env swebench | swebench_pro +# ais_bench ais_bench/configs/swe_bench[_pro]_examples/... --debug +# 数据集与 case 镜像获取详见各 benchmark 文档 +``` + +方案设计与各脚本参数详见 [`docker/agent_runtime/README.md`](agent_runtime/README.md)。各 agent 测评文档([harbor_bench.md](../docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md)、[swe_bench.md](../docs/source_zh_cn/extended_benchmark/agent/swe_bench.md)、[swe_bench_pro.md](../docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md))开头也已加入"快速上手"引导段。 + +> 本节是"运行 Agent / 沙箱类测评"章节的可执行化封装。理解原理仍建议阅读上文模式 A/B 章节;快速上手直接用本节脚本即可。 + +======= +>>>>>>> master_center ## 许可证 / 免责声明 本项目镜像及其构建脚本按仓库根目录的 [LICENSE 文件](https://github.com/AISBench/benchmark/blob/master/LICENSE) 授权。 From 9b43e5f72ac61bac9f809373897db9df2fb75673 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:41:38 +0800 Subject: [PATCH 07/36] add docs and dep of agent runtime --- docker/agent_runtime/README.md | 268 ++++++++++++++++++ docker/agent_runtime/packs/harbor.yaml | 12 + docker/agent_runtime/packs/swebench.yaml | 12 + docker/agent_runtime/packs/swebench_pro.yaml | 15 + .../patches/harbor_compose_patch.py | 44 +++ 5 files changed, 351 insertions(+) create mode 100644 docker/agent_runtime/README.md create mode 100644 docker/agent_runtime/packs/harbor.yaml create mode 100644 docker/agent_runtime/packs/swebench.yaml create mode 100644 docker/agent_runtime/packs/swebench_pro.yaml create mode 100644 docker/agent_runtime/patches/harbor_compose_patch.py diff --git a/docker/agent_runtime/README.md b/docker/agent_runtime/README.md new file mode 100644 index 00000000..755fb0ba --- /dev/null +++ b/docker/agent_runtime/README.md @@ -0,0 +1,268 @@ +# AISBench Agent Runtime + +为 AISBench Agent 测评(Harbor Terminal-Bench、SWE-bench、SWE-bench Pro 等)提供运行环境容器的镜像与脚本。 + +> 本仓库目录是 AISBench/benchmark 的社区运行时补充,不属于 benchmark 核心评测逻辑。用户使用本目录的脚本和镜像准备好 runtime 容器后,仍用原生 `ais_bench` 命令执行测评,原理与各 benchmark 文档完全一致。 + +## 解决什么问题 + +Agent 测评的环境准备存在三大痛点: + +1. **依赖冲突**:harbor 强制升级 datasets 到 4.0+;SWE-bench 与 SWE-bench Pro 各需要一个不同 fork 的 `mini-swe-agent`,同包名互相覆盖。 +2. **容器配置易错**:DinD 模式 A/B、`--cgroupns=host`、`daemon.json`、seccomp,任一步漏配都会在跑测评时才报错。 +3. **数据集 / case 镜像版本变化频繁**:数据集和 case 镜像都有大量版本,烤入 runtime 镜像会很快过期。 + +本包通过分层解决这些问题: + +| 层 | 内容 | 解决 | +|---|---|---| +| `Dockerfile.agent-runtime` | 在 `aisbench_benchmark` 基镜之上追加 3 个 venv 隔离层(harbor / swebench / swebench_pro) | 依赖冲突 | +| `bootstrap.sh` | 一键起 runtime 容器,自动选 DinD/Socket 模式 + 挂载数据集 + 加载 case tar | 容器配置易错 + 数据集接入 | +| `doctor.sh` | 静态自检(L1,秒级)— 校验 docker / venv / pack / 资源 | 跑前验证 runtime 配置 | +| `packs/.yaml` | 各 benchmark 的元数据(venv 名 / 原生配置 / 文档) | 工具链与 benchmark 解耦 | + +## 数据集 / case 镜像由谁负责 + +**刻意不做的事**:本方案**不**预置 agent benchmark 的数据集和 case 沙箱镜像到 runtime 镜像中。原因是这两者版本变化频繁,烤入镜像后: + +- 数据集每次更新都要重新构建 runtime 镜像,对维护者负担大、对用户下载体积大 +- case 沙箱镜像一个 full 集 ~71GB,不能烤进基础镜像 + +**谁负责什么**: + +| 项 | 谁准备 | 怎么接入 runtime 容器 | +|---|---|---| +| runtime 镜像 | AISBench 维护者 | `docker pull ghcr.io/aisbench/agent-runtime:latest-...`(或 `--runtime-tar` 离线) | +| 数据集(task.toml 等) | 用户在物理机上准备好 | `bootstrap.sh --datasets <完整数据集路径>`(挂载到容器内同路径 + 注入 env var) | +| case 沙箱镜像 | 用户在物理机上准备好 tar | `bootstrap.sh --case-tar `(自动 `docker cp` 进容器 + `docker load`) | +| 模型调用参数 (api_base / model_names) | 用户改原生配置 | 容器内 `vim ais_bench/configs/agent_example/...` | + +## 快速入门(以 Harbor Terminal-Bench 为例 aarch64) +快速入门针对物理机20.0.0以下版本docker环境,其他环境请参考对应agent测评文档。 + +```bash +# 1. 物理机上准备数据集与镜像 tar(已有可跳过) + +git clone https://modelers.cn/AISBench/terminal-bench-2-offline-mini.git # 数据集准备 +wget https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-aarch64.tar.gz # 测评镜像准备(可选,不准备则自动获取最新) +wget https://aisbench.obs.cn-north-4.myhuaweicloud.com/terminal-bench-2-images/terminal-bench-2-offline-prepared-images-selected-0.10_aarch64.tar # case 镜像准备,按需从对应agent测评文档获取链接 +mkdir /path/to/test_wkp/ # 物理机创建一个空的工作目录 + +# 2. 物理机上一键起 runtime 容器(自动选 DinD/Socket 模式,自动挂载数据集,如果执行环境不通外网,可以先从其他环境获取ais_bench_agent_bootstrap.sh再bash执行 +# 自动把 case 镜像 tar 拷进容器内部 docker load 完) +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /path/to/terminal-bench-2-offline-mini/terminal-bench-2-offline-selected_0.10/ \ + --runtime-tar /path/to/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-aarch64.tar.gz \ + --case-tar /path/to/terminal-bench-2-offline-prepared-images-selected-0.10.tar \ + --host-path /path/to/test_wkp/ \ + --container-name test_agent_run +# --datasets 指向的目录结构需与 terminal-bench-2-offline-mini 仓库的 terminal-bench-2-offline-selected_0.10/ 子目录结构一致 +# --runtime-tar (可选)提前准备的测评镜像 +# --case-tar 指向的 tar 结构需与对应 agent 测评文档的 case 镜像 tar 结构一致 +# --host-path 指向的目录需为空目录,容器内会自动创建同名目录挂载数据集和 case 镜像 +# --container-name 指向的容器名需唯一,否则会覆盖旧容器 + +# 3. 进入容器(case 镜像已在内部,直接可用) +docker exec -it test_agent_run bash + +# 4. (无需 vim)原生配置 path 自动从 AISBENCH_AGENT_DATASET_PATH 读 +# 仅需 vim 改 model_names / api_base +vim ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py + +# 5. 验证 runtime 就绪 +ais_bench_agent_doctor.sh harbor + +# 6. 跑测评 +agent_env harbor +ais_bench ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py --debug +``` + +切换数据集:销毁旧容器 + 重启 bootstrap(数据集路径 / case tar 一起更新): + +```bash +docker rm -f test_agent_run +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets/harbor/full/terminal-bench-2 \ + --case-tar /data/cases/terminal-bench-2-prepared-images_x86_64.tar.gz +``` + +## 目录结构 + +``` +agent_runtime/ +├── README.md # 本文件 +├── Dockerfile.agent-runtime # runtime 镜像构建文件(BASE_IMAGE 通过 --build-arg 传入,不写死) +├── build_image_agent_runtime.sh # 构建脚本(支持 --base-tag/--push/--upload/--multi-arch) +├── ais_bench_agent_bootstrap.sh # 一键起 runtime 容器(用户侧入口,需上传到 OBS) +├── doctor.sh # runtime 就绪验证(容器内,仅校验 docker/venv/config 不校验数据集/cases) +├── packs/ # 各 benchmark 的清单(name/runtime_venv/native_config/native_doc) +│ ├── harbor.yaml # Harbor Terminal-Bench +│ ├── swebench.yaml # SWE-bench(mini_swe_agent + SWE-bench harness) +│ └── swebench_pro.yaml # SWE-bench Pro(scaleapi 适配版) +└── patches/ # 构建期 / 启动期用的补丁脚本 + └── harbor_compose_patch.py # 给 harbor 的 docker-compose-base.yaml 加 seccomp=unconfined + network_mode=host +``` + +## ais_bench_agent_bootstrap.sh 用法 + +```bash +# 最简调用:挂载一个数据集目录 +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets + +# 挂载多个目录 +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --datasets /data/extra + +# 强制模式 A/B +bash ais_bench_agent_bootstrap.sh --mode A --datasets /data/datasets + +# 自定义容器名(一台机器同时跑多个 runtime 时区分) +bash ais_bench_agent_bootstrap.sh --container-name my_eval_1 --datasets /data/datasets + +# 自定义 runtime 镜像(推荐显式传 tag,保证可复现性) +bash ais_bench_agent_bootstrap.sh \ + --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 \ + --datasets /data/datasets + +# 模式 B + 自定义 /benchmark 提取目标(仅 /opt 不可写时用) +bash ais_bench_agent_bootstrap.sh --mode B --host-path /data/ais_bench_host --datasets /data/datasets + +# 离线模式(内网/隔离环境):用宿主上已下载好的 tar 包加载 runtime 镜像 +# 完全跳过 docker pull / OBS 下载 +# 适用:内网部署机无法访问 ghcr.io,也无法访问外网 OBS +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --runtime-tar /opt/aisbench/agent-runtime-ubuntu24-py312-x86_64.tar.gz + +# 完全离线:runtime tar + case 镜像 tar 一起传入 +# 容器启动后会自动把 case tar 拷进容器并 docker load +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /opt/aisbench/agent-runtime.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.10.tar.gz + +# 一次加载多个 case 镜像(可多次 --case-tar,也可传一个目录) +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /opt/aisbench/agent-runtime.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.10.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.14.tar.gz \ + --case-tar /opt/aisbench/case-tars/ # 目录下所有 .tar/.tar.gz/.tgz 都会被加载 +``` + +`--datasets` / `--host-path` / `--runtime-tar` / `--case-tar` 必须是**绝对路径**,且在**物理机上必须存在**(脚本会校验)。容器内路径与宿主路径相同。 + +`--datasets` 传入的完整路径会原封不动注入为容器内环境变量 `AISBENCH_AGENT_DATASET_PATH`,原生 ais_bench 配置(如 `harbor_terminal_bench_2_task.py`)直接把这个 env var 作为数据集 `path` 字段使用——**不拼接、不转换、完全一致**。所以: + +- **推荐**:把 `--datasets` 传成你准备好的 harbor benchmark 数据集完整路径(如 `/data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10`),这样配置无需 vim 改 path +- **多目录**:可多次传 `--datasets`,但 env var 只用首个;用户可手动 `export AISBENCH_AGENT_DATASET_PATH=...` 覆盖 + +### 命令行参数一览 + +| 参数 | 默认值 | 说明 | +|---|---|---| +| `--datasets ` | 无(不挂载) | 数据集目录,可多次 | +| `--runtime-tar ` | 无(走 pull) | runtime 镜像 tar,离线场景用 | +| `--case-tar ` | 无(容器内手动准备) | case 镜像 tar,文件或目录,可多次 | +| `--mode A\|B` | 自动判断 | 强制 DinD (A) 或 Socket 代理 (B) | +| `--container-name ` | `ais_bench_agent` | runtime 容器名 | +| `--runtime-image ` | `ghcr.io/aisbench/agent-runtime:latest-ubuntu24.04-py312-${ARCH}` | runtime 镜像 tag | +| `--host-path ` | `/opt/ais_bench_agent` | 模式 B 时 `/benchmark` 提取目标 | + +### 环境变量 + +仅保留一个 env 变量(其它配置请用 CLI 参数): + +| env | 默认值 | 说明 | +|---|---|---| +| `OBS_RUNTIME_TAR_BASE` | `https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/runtime` | OBS runtime tar 下载基址(一般无需改) | + +### 离线场景 + +内网/隔离环境的部署机无法访问 `ghcr.io/aisbench/agent-runtime` 或 OBS,但已通过 U 盘、内网代理等方式拿到了 runtime tar 包和 case 镜像 tar: + +1. **获取 tar 包**(任选其一): + - 让维护者跑 `build_image_agent_runtime.sh --upload 1` 上传到 OBS,内网用户从 OBS 下载 + - 在能访问外网的机器上 `docker save ghcr.io/aisbench/agent-runtime: -o agent-runtime.tar.gz` 后拷贝进来 +2. **部署机执行**: + ```bash + # 最小:只传 runtime tar(case 镜像仍需在容器内手动 pull / load) + bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --runtime-tar /path/to/agent-runtime.tar.gz + + # 完全离线:runtime + case tar 都传 + bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /path/to/agent-runtime.tar.gz \ + --case-tar /path/to/case-tb2-mini-0.10.tar.gz + ``` +3. **行为**: + - `--runtime-tar`:完全跳过 `docker pull` 与 OBS 的 `curl` 下载;`docker load -i ` 后自动检测 tag(grep `agent-runtime`),优先匹配 `RUNTIME_IMAGE`;检测失败给精确错误 + - `--case-tar `:支持传单个 tar 或一个目录(目录会递归加载所有 `.tar` / `.tar.gz` / `.tgz`)。脚本会 `docker cp` 进容器,再用 `docker load -i` 加载。**支持 A/B 两种模式**(A 模式加载到容器内 DinD;B 模式加载到容器内,但因 socket 与宿主共享实际也加载到了宿主),模式A|B的具体介绍参考[OVERVIEW.zh.md](../OVERVIEW.zh.md#运行-agent--沙箱类测评在容器内使用-docker) + - 可多次 `--case-tar` + +模式 B(Socket 代理)默认会把 `/benchmark` 提取到宿主 `/opt/ais_bench_agent`。若你的环境 `/opt` 不可写(如某些只读根容器/沙箱),用 `--host-path` 改写到可写路径: + +```bash +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --host-path /data/ais_bench_host +``` + +正常物理机无需设置该变量。其余可配置环境变量见 `bootstrap.sh` 头部注释。 + +## 镜像构建 + +runtime 镜像基于 `aisbench_benchmark` 基镜构建,基镜像 tag 通过参数传入,不写死: + +```bash +# 基础构建(本地,当前架构) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master + +# 指定 OS/Python(默认 ubuntu24.04 + py312) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --os ubuntu24.04 --py-version py312 + +# 构建并推送到远程仓库 +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --push 1 + +# 多架构构建并推送 +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --multi-arch 1 --push 1 + +# 构建、推送、并上传离线包到 OBS(供 ais_bench_agent_bootstrap.sh 回退下载) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --push 1 --upload 1 +``` + +构建脚本会自动校验(4 项): +1. ais_bench 可用 +2. 3 个 venv(harbor / swebench / swebench_pro)都完整 + 3 个 venv 内都有 ais_bench wrapper + 两个 swebench venv 能 import minisweagent +3. doctor.sh / packs 就位 +4. harbor compose 模板已 patch `seccomp=unconfined` + +## 已支持的 pack + +| pack 名 | runtime_venv | 文档 | 说明 | +|---|---|---|---| +| `harbor` | harbor | [harbor_bench.md](../../docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md) | Harbor Terminal-Bench 2.0 | +| `swebench` | swebench | [swe_bench.md](../../docs/source_zh_cn/extended_benchmark/agent/swe_bench.md) | SWE-bench(lite/verified/full/multilingual 等) | +| `swebench_pro` | swebench_pro | [swe_bench_pro.md](../../docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md) | SWE-bench Pro(仅 x86) | + +pack.yaml 不声明数据集路径、不声明 case 镜像获取方式——这些完全交给用户掌控: +- 数据集路径:用户 `bootstrap.sh --datasets <完整数据集路径>` 时显式指定(要哪个就跑哪个) +- case 镜像:用户按 pack.yaml 的 `native_doc` 指向的文档自行 `docker pull` 或 `docker load` + +如果以后接更多 benchmark,每加一个 `packs/.yaml` 即可。 + +> 常用的 harbor 数据集目录名(仅供参考,与工具无关): +> - `/data/datasets/harbor/full/terminal-bench-2`(89 case,含少量外网任务) +> - `/data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10`(7 case) +> - `/data/datasets/harbor/mini-0.14/terminal-bench-2-offline-selected_0.14`(10 case) +> - `/data/datasets/harbor/mini-0.20/terminal-bench-2-offline-selected_0.20`(14 case) +> +> 用户在 `bootstrap.sh --datasets` 传入哪个路径,就由 env var `AISBENCH_AGENT_DATASET_PATH` 把哪个路径注入容器。 +> mini-* 系列基于 `terminal-bench-2-offline`(剔除外网任务后的 70 个 case)做 K-means 采样,完全离线可跑。 +> +> SWE-bench 数据集说明见 [swe_bench.md](../../docs/source_zh_cn/extended_benchmark/agent/swe_bench.md)(HF 下载);SWE-bench Pro 见 [swe_bench_pro.md](../../docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md)。 + +## 详细方案 + +完整设计与各脚本参数说明见各脚本头部注释。 \ No newline at end of file diff --git a/docker/agent_runtime/packs/harbor.yaml b/docker/agent_runtime/packs/harbor.yaml new file mode 100644 index 00000000..42ad4d0a --- /dev/null +++ b/docker/agent_runtime/packs/harbor.yaml @@ -0,0 +1,12 @@ +# Harbor Terminal-Bench 2.0 pack 元数据 +# 供 ais_bench_agent_doctor.sh 读取,验证该 pack 的 runtime 是否就绪 +# +# 字段用途: +# name pack 唯一标识 +# runtime_venv doctor L1 验证 /opt/venvs/ 存在 + python 可执行 +# native_config doctor L1 验证原生 ais_bench 配置文件存在 +# native_doc 跑完 doctor 后指引用户看哪份文档 +name: harbor-terminal-bench-2 +runtime_venv: harbor +native_config: ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py +native_doc: docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md diff --git a/docker/agent_runtime/packs/swebench.yaml b/docker/agent_runtime/packs/swebench.yaml new file mode 100644 index 00000000..ce60129d --- /dev/null +++ b/docker/agent_runtime/packs/swebench.yaml @@ -0,0 +1,12 @@ +# SWE-bench pack 元数据 +# 供 ais_bench_agent_doctor.sh 读取,验证该 pack 的 runtime 是否就绪 +# +# 字段用途: +# name pack 唯一标识 +# runtime_venv doctor L1 验证 /opt/venvs/ 存在 + python 可执行 +# native_config doctor L1 验证原生 ais_bench 配置文件存在 +# native_doc 跑完 doctor 后指引用户看哪份文档 +name: swebench +runtime_venv: swebench +native_config: ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py +native_doc: docs/source_zh_cn/extended_benchmark/agent/swe_bench.md \ No newline at end of file diff --git a/docker/agent_runtime/packs/swebench_pro.yaml b/docker/agent_runtime/packs/swebench_pro.yaml new file mode 100644 index 00000000..a99e9a15 --- /dev/null +++ b/docker/agent_runtime/packs/swebench_pro.yaml @@ -0,0 +1,15 @@ +# SWE-bench Pro pack 元数据 +# 供 ais_bench_agent_doctor.sh 读取,验证该 pack 的 runtime 是否就绪 +# +# 字段用途: +# name pack 唯一标识 +# runtime_venv doctor L1 验证 /opt/venvs/ 存在 + python 可执行 +# native_config doctor L1 验证原生 ais_bench 配置文件存在 +# native_doc 跑完 doctor 后指引用户看哪份文档 +# +# 注意:SWE-bench Pro 官方 docker 镜像均为 x86 架构,ARM 环境只能装 runtime, +# 跑测评时报 case 镜像架构不兼容。 +name: swebench_pro +runtime_venv: swebench_pro +native_config: ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py +native_doc: docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md \ No newline at end of file diff --git a/docker/agent_runtime/patches/harbor_compose_patch.py b/docker/agent_runtime/patches/harbor_compose_patch.py new file mode 100644 index 00000000..ac82f6f2 --- /dev/null +++ b/docker/agent_runtime/patches/harbor_compose_patch.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Patch harbor 0.6.1 的 docker-compose-base.yaml: +# 1. 给 main service 追加 security_opt: ["seccomp=unconfined"] +# 2. 把 main service 的 network_mode 设为 "host" +# 两个改动都是 harbor_bench.md 第 2.2 节所要求的 agent-side patch。 +# +# 用法:harbor_compose_patch.py +# +# 与 Dockerfile.agent-runtime 的 RUN 段配合:从 build context COPY 进镜像后调用。 +# 用独立脚本而不是 RUN 内联 heredoc,是为了绕开 BuildKit 对 RUN 内嵌多行引号字符串的 +# 解析限制(Dockerfile 1.0 起就不支持引号内裸换行 + 续行符的混合写法)。 + +import sys +import yaml + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: harbor_compose_patch.py ", + file=sys.stderr) + return 2 + + path = sys.argv[1] + + with open(path, "r", encoding="utf-8") as f: + cfg = yaml.safe_load(f) or {} + + svc = cfg.setdefault("services", {}).setdefault("main", {}) + opts = svc.setdefault("security_opt", []) + if "seccomp=unconfined" not in opts: + opts.append("seccomp=unconfined") + + # harbor_bench.md 第 2.2 节:agent 测评需要 main 直连宿主网络 + svc["network_mode"] = "host" + + with open(path, "w", encoding="utf-8") as f: + yaml.safe_dump(cfg, f, sort_keys=False, allow_unicode=True) + + print("patched:", path) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 51551cad158d0d6d11089bab53edbb3f05ede1a7 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:42:17 +0800 Subject: [PATCH 08/36] add agent runtime build --- docker/agent_runtime/Dockerfile.agent-runtime | 279 ++++++++++ .../build_image_agent_runtime.sh | 482 ++++++++++++++++++ 2 files changed, 761 insertions(+) create mode 100644 docker/agent_runtime/Dockerfile.agent-runtime create mode 100644 docker/agent_runtime/build_image_agent_runtime.sh diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime new file mode 100644 index 00000000..55d60b01 --- /dev/null +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -0,0 +1,279 @@ +# AISBench Agent Runtime Dockerfile +# +# 在现有 aisbench_benchmark 基础镜像(已含 docker engine + compose + ais_bench)之上, +# 追加多 venv 隔离层,解决 agent 测评之间的依赖冲突: +# - harbor venv:隔离 harbor==0.6.1 强制升级的 datasets 4.0+,不污染主环境 +# + harbor_task.py 用 subprocess 启动 venv python,所以 venv 内还 +# 放了一个 ais_bench wrapper(shebang 指向 venv python), +# 让用户 `agent_env harbor` 后 which ais_bench 指向 venv 内 +# - swebench venv:装 AISBench fork 的 mini-swe-agent + SWE-bench harness +# - swebench_pro venv:装 scaleapi fork 的 mini-swe-agent + SWE-bench_Pro-os +# +# 三个 venv 都使用 --system-site-packages --copies: +# --system-site-packages: 让 venv 看到系统已装的 ais_bench_benchmark(共用基镜像, +# 不重复装 torch / datasets / tiktoken 等大依赖) +# --copies: 强制 python 二进制是真实副本(不是 symlink),否则 subprocess 启动时会 +# 丢 venv 身份(找不到 venv local 的包) +# +# 构建方式(推荐使用 build_image_agent_runtime.sh): +# bash docker/agent_runtime/build_image_agent_runtime.sh \ +# --base-tag v3.1-20260522-master --os ubuntu24.04 --py-version py312 +# +# 或直接 docker build: +# docker build \ +# --network host \ +# -f docker/agent_runtime/Dockerfile.agent-runtime \ +# -t ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 \ +# --build-arg BASE_IMAGE=ghcr.io/aisbench/aisbench_benchmark:v3.1-20260522-master-ubuntu24.04-py312-x86_64 \ +# . +# +# BASE_IMAGE 必须通过 --build-arg 传入,无默认值,避免基镜像 tag 写死导致过期。 + +ARG BASE_IMAGE +FROM ${BASE_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive +ENV PYTHONUNBUFFERED=1 + +# agent runtime 相关路径 +ENV AGENT_VENVS_ROOT=/opt/venvs +ENV AGENT_PACKS_ROOT=/opt/agent-resources/packs + +RUN mkdir -p ${AGENT_VENVS_ROOT} \ + ${AGENT_PACKS_ROOT} \ + /opt/src + +# ============================================================ +# venv: harbor +# harbor==0.6.1 会把 datasets 升到 4.0+,必须隔离在独立 venv +# 否则会污染 ais_bench 主环境,导致其他数据集依赖冲突 +# +# --system-site-packages: 让 venv 能看到基镜像里已经装好的 ais_bench_benchmark +# - ais_bench_benchmark 装在基镜像 /usr/local/lib/.../dist-packages/, +# 是 ais_bench 框架本体(benchmark / configs / datasets / tasks / ...) +# - venv 默认不读系统 site-packages,会导致 harbor_task.py 跑时找不到 ais_bench 包 +# - harbor 升级的 datasets 4.0+ 仍只装在 venv local site-packages, +# 不会污染系统环境(Python venv 优先用 local 包覆盖 system 包) +# +# --copies: 强制拷贝 python 二进制到 venv 内(默认是 symlink 到系统 python3.12) +# - harbor_task.py 通过 subprocess.Popen 以绝对路径启动 venv 的 python +# - 若 venv python 是 symlink,python 启动时通过 symlink 解析到 /usr/bin/python3.12, +# 丢失 venv 身份(不再读 venv 的 pyvenv.cfg),找不到 venv local 的 harbor 包 +# - 用 --copies 后 venv python 是真实二进制,python 通过真实路径识别 venv 身份, +# 正确加载 venv local site-packages +# ============================================================ +RUN python3.12 -m venv ${AGENT_VENVS_ROOT}/harbor --system-site-packages --copies && \ + ${AGENT_VENVS_ROOT}/harbor/bin/pip install --no-cache-dir --upgrade pip \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 && \ + ${AGENT_VENVS_ROOT}/harbor/bin/pip install --no-cache-dir \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 \ + harbor==0.6.1 + +# ============================================================ +# harbor venv 内放置 ais_bench 命令(关键) +# +# 问题链路(已修过的两层坑): +# 1. venv 默认不读系统 site-packages → 加 --system-site-packages ✓ +# 2. venv python 是 symlink 时丢身份 → 加 --copies ✓ +# 3. **主进程 sys.executable 是系统 python**(ais_bench 命令装在系统不在 venv) +# → harbor_task.py:44 python = sys.executable 拿到 /usr/bin/python3.12 +# → subprocess 启动系统 python(不是 venv python) +# → 系统 python 找不到 venv local 的 harbor 包 +# +# 修复:把 ais_bench wrapper 放到 harbor venv/bin 下,shebang 指向 venv python +# - 用户 agent_env harbor 后,PATH 中 venv/bin 在前 +# - which ais_bench → /opt/venvs/harbor/bin/ais_bench(venv 内) +# - shell 解析 shebang 用 venv python 启动 ais_bench +# - 主进程 sys.executable = venv python +# - harbor_task.py:44 拿到的也是 venv python +# - subprocess 启动 venv python → 找到 venv local site-packages → import harbor OK +# +# 注意:不依赖基镜像 /usr/local/bin/ais_bench 的具体实现,直接 import 调用 main() +# ========================================================= +RUN cat > ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench <<'PYEOF' +#!/opt/venvs/harbor/bin/python +# -*- coding: utf-8 -*- +"""harbor venv 内置 ais_bench wrapper +启动时使用 harbor venv 的 python,让 subprocess.Popen 启动 harbor venv 的 python, +从而能正确加载 venv local site-packages(harbor 0.6.1 在此)。 +""" +import re +import sys +try: + from ais_bench.benchmark.cli.main import main +except ImportError as e: + sys.stderr.write(f"[error] failed to import ais_bench: {e}\n") + sys.stderr.write("[hint] 检查 harbor venv 是否启用 --system-site-packages(需看到系统 ais_bench)\n") + sys.exit(2) +if __name__ == "__main__": + sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) + sys.exit(main()) +PYEOF +RUN chmod +x ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench + +# ============================================================ +# Patch harbor 的 docker-compose-base.yaml +# 模式 B(Socket 代理)下子容器由宿主 dockerd 创建,会继承宿主 seccomp +# profile。openEuler/RHEL 的默认 profile 比 Docker 官方严格,会拦截 +# OpenBLAS/NumPy 初始化线程时使用的 clone3 调用,表现为: +# OpenBLAS blas_thread_init: pthread_create failed: Operation not permitted +# 详见 docker/OVERVIEW.zh.md 模式 B 常见问题 +# +# harbor 0.6.1 的 compose 模板路径(python3.12 site-packages): +# harbor/environments/docker/docker-compose-base.yaml +# 在其中给 main service 补上 security_opt: [seccomp=unconfined] +# 模式 A(DinD)下此 patch 无副作用(子容器本就用 Docker 官方默认 profile) +# ============================================================ +COPY docker/agent_runtime/patches/harbor_compose_patch.py /tmp/harbor_compose_patch.py +RUN HARBOR_SITEPKG=$(${AGENT_VENVS_ROOT}/harbor/bin/python -c "import harbor, os; print(os.path.dirname(harbor.__file__))") && \ + COMPOSE_FILE="${HARBOR_SITEPKG}/environments/docker/docker-compose-base.yaml" && \ + if [ -f "$COMPOSE_FILE" ]; then \ + cp "$COMPOSE_FILE" "$COMPOSE_FILE.bak" && \ + python3.12 /tmp/harbor_compose_patch.py "${COMPOSE_FILE}"; \ + else \ + echo "[warn] harbor compose 模板未找到: $COMPOSE_FILE,跳过 patch"; \ + fi + +# ============================================================ +# venv: swebench +# SWE-bench / SWE-bench Pro 两个 benchmark 各需要一个 mini-swe-agent fork, +# 同包名 minisweagent 互相覆盖,必须在两个独立 venv 内安装。 +# +# 仓库选择: +# - AISBench fork (https://github.com/AISBench/mini-swe-agent) 用于 SWE-bench +# - scaleapi fork (https://github.com/scaleapi/mini-swe-agent) 用于 SWE-bench Pro +# +# 需要 ais_bench wrapper(与 harbor 同机理): +# - system /usr/local/bin/ais_bench 的 shebang 指向 system python3.12,不会自动切到 venv python +# - 用户 agent_env swebench 后,PATH 首位是 /opt/venvs/swebench/bin, +# 但 swebench/bin/ 下若没有 ais_bench,shell 仍落到 /usr/local/bin/ais_bench +# - task 代码 (swebench_infer.py) 在主进程 import minisweagent, +# system python 看不到 venv-local 的 minisweagent → ModuleNotFoundError +# - 在 swebench/bin/ 放一个 ais_bench wrapper(shebang 指向 venv python), +# PATH 命中它后启动 venv python → 正确加载 venv-local 的 minisweagent +# - 同时通过 --system-site-packages 仍能看到系统的 ais_bench / datasets / tasks +# ============================================================ +RUN python3.12 -m venv ${AGENT_VENVS_ROOT}/swebench --system-site-packages --copies && \ + ${AGENT_VENVS_ROOT}/swebench/bin/pip install --no-cache-dir --upgrade pip \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 && \ + git clone --depth 1 https://gh-proxy.com/https://github.com/AISBench/mini-swe-agent.git \ + /opt/src/mini-swe-agent-aisbench && \ + ${AGENT_VENVS_ROOT}/swebench/bin/pip install --no-cache-dir \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 \ + -e /opt/src/mini-swe-agent-aisbench && \ + git clone --depth 1 https://gh-proxy.com/https://github.com/SWE-bench/SWE-bench.git \ + /opt/src/SWE-bench && \ + ${AGENT_VENVS_ROOT}/swebench/bin/pip install --no-cache-dir \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 \ + -e /opt/src/SWE-bench + +# ais_bench wrapper for swebench venv(让主进程加载 venv-local 的 minisweagent) +RUN cat > ${AGENT_VENVS_ROOT}/swebench/bin/ais_bench <<'PYEOF' +#!/opt/venvs/swebench/bin/python +# -*- coding: utf-8 -*- +"""swebench venv 内置 ais_bench wrapper +启动时使用 swebench venv 的 python,让 task 代码 (swebench_infer.py) +在主进程 import minisweagent 时加载 venv-local 的 mini-swe-agent (AISBench fork)。 +""" +import re +import sys +try: + from ais_bench.benchmark.cli.main import main +except ImportError as e: + sys.stderr.write(f"[error] failed to import ais_bench: {e}\n") + sys.stderr.write("[hint] 检查 swebench venv 是否启用 --system-site-packages(需看到系统 ais_bench)\n") + sys.exit(2) +if __name__ == "__main__": + sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) + sys.exit(main()) +PYEOF +RUN chmod +x ${AGENT_VENVS_ROOT}/swebench/bin/ais_bench + +# ============================================================ +# venv: swebench_pro +# SWE-bench Pro 需用 scaleapi 适配的 mini-swe-agent 和官方 SWE-Bench_Pro-os +# 单独 venv 避免与 swebench venv 内的 mini-swe-agent fork 冲突 +# +# 同样需要 ais_bench wrapper(原因同上):主进程要加载 scaleapi fork 的 minisweagent +# +# ARM 限制:官方 docker 镜像均为 x86 架构,ARM 跑不动(runtime 仍可启动) +# ============================================================ +RUN python3.12 -m venv ${AGENT_VENVS_ROOT}/swebench_pro --system-site-packages --copies && \ + ${AGENT_VENVS_ROOT}/swebench_pro/bin/pip install --no-cache-dir --upgrade pip \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 && \ + git clone --depth 1 https://gh-proxy.com/https://github.com/scaleapi/mini-swe-agent.git \ + /opt/src/mini-swe-agent-scaleapi && \ + ${AGENT_VENVS_ROOT}/swebench_pro/bin/pip install --no-cache-dir \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 \ + -e /opt/src/mini-swe-agent-scaleapi && \ + git clone --depth 1 https://gh-proxy.com/https://github.com/scaleapi/SWE-bench_Pro-os.git \ + /opt/src/SWE-bench_Pro-os && \ + ${AGENT_VENVS_ROOT}/swebench_pro/bin/pip install --no-cache-dir \ + -i https://repo.huaweicloud.com/repository/pypi/simple \ + --trusted-host repo.huaweicloud.com \ + --default-timeout=120 --retries=5 \ + -r /opt/src/SWE-bench_Pro-os/requirements.txt + +# ais_bench wrapper for swebench_pro venv(让主进程加载 venv-local 的 minisweagent) +RUN cat > ${AGENT_VENVS_ROOT}/swebench_pro/bin/ais_bench <<'PYEOF' +#!/opt/venvs/swebench_pro/bin/python +# -*- coding: utf-8 -*- +"""swebench_pro venv 内置 ais_bench wrapper +启动时使用 swebench_pro venv 的 python,让 task 代码 (swebench_pro_infer.py) +在主进程 import minisweagent 时加载 venv-local 的 mini-swe-agent (scaleapi fork)。 +""" +import re +import sys +try: + from ais_bench.benchmark.cli.main import main +except ImportError as e: + sys.stderr.write(f"[error] failed to import ais_bench: {e}\n") + sys.stderr.write("[hint] 检查 swebench_pro venv 是否启用 --system-site-packages(需看到系统 ais_bench)\n") + sys.exit(2) +if __name__ == "__main__": + sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) + sys.exit(main()) +PYEOF +RUN chmod +x ${AGENT_VENVS_ROOT}/swebench_pro/bin/ais_bench + +# ============================================================ +# 脚本 + pack 清单 +# ============================================================ +COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh + +COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ + +# ============================================================ +# 便捷函数:激活对应 venv +# 用户在容器内 `agent_env harbor` 即可激活 harbor venv, +# 然后照常执行原生 ais_bench 命令 +# ============================================================ +RUN cat >> /etc/bash.bashrc <<'EOF' + +# AISBench Agent venv 激活便捷函数 +agent_env() { + case "$1" in + harbor) source /opt/venvs/harbor/bin/activate ;; + swebench) source /opt/venvs/swebench/bin/activate 2>/dev/null || echo "venv swebench 未安装" ;; + swebench_pro) source /opt/venvs/swebench_pro/bin/activate 2>/dev/null || echo "venv swebench_pro 未安装" ;; + *) echo "usage: agent_env " ;; + esac +} +EOF + +WORKDIR /benchmark diff --git a/docker/agent_runtime/build_image_agent_runtime.sh b/docker/agent_runtime/build_image_agent_runtime.sh new file mode 100644 index 00000000..d873bc7b --- /dev/null +++ b/docker/agent_runtime/build_image_agent_runtime.sh @@ -0,0 +1,482 @@ +#!/bin/bash +# +# 构建 AISBench Agent Runtime 镜像 +# +# 在 aisbench_benchmark 基础镜像之上追加多 venv 隔离层, +# 用于 agent 测评(Harbor Terminal-Bench 等)的环境准备。 +# +# 用法: +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag [选项] +# +# 必填参数: +# --base-tag 基镜像(aisbench_benchmark)的 TAG,例如 v3.1-20260522-master +# 会自动拼接为 :--- +# +# 可选参数: +# --os 操作系统,默认 ubuntu24.04 +# --py-version Python 版本,默认 py312(agent runtime 必须用 py312) +# --hub-repo 基镜像仓库地址,默认 ghcr.io/aisbench/aisbench_benchmark +# --target-hub-repo 目标镜像仓库地址(agent-runtime 镜像推到哪),默认 ghcr.io/aisbench/agent-runtime +# --image-output-dir 离线包输出目录,默认 /home/ais_bench_ci/release_images +# --obs-path OBS 工具路径,默认 /home/ais_bench_ci/obsutil_linux_arm64_5.7.9/ +# --push <0|1> 是否推送到远程仓库,默认 0 +# --upload <0|1> 是否上传离线包到 OBS,默认 0 +# --use-cache <0|1> 是否使用缓存构建,默认 0 +# --multi-arch <0|1> 是否构建多架构镜像(amd64+arm64),默认 0 +# -h, --help 显示帮助 +# +# 示例: +# # 基础构建(本地,x86_64 或 aarch64 视当前机器) +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag v3.1-20260522-master +# +# # 指定 OS/Python 版本 +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag v3.1-20260522-master \ +# --os ubuntu24.04 --py-version py312 +# +# # 构建并推送 +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag v3.1-20260522-master --push 1 +# +# # 多架构构建并推送(需在各自架构机器上分别执行 + manifest 合并) +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag v3.1-20260522-master \ +# --multi-arch 1 --push 1 +# +# # 构建、推送、并上传离线包到 OBS +# bash docker/agent_runtime/build_image_agent_runtime.sh --base-tag v3.1-20260522-master \ +# --push 1 --upload 1 + +set -e + +usage() { + echo "用法: $0 --base-tag [选项]" + echo "" + echo "必填参数:" + echo " --base-tag 基镜像(aisbench_benchmark)的 TAG,例如 v3.1-20260522-master" + echo "" + echo "可选参数:" + echo " --os 操作系统,默认: ubuntu24.04" + echo " --py-version Python 版本,默认: py312(agent runtime 推荐 py312)" + echo " --hub-repo 基镜像仓库地址,默认: ghcr.io/aisbench/aisbench_benchmark" + echo " --target-hub-repo 目标镜像仓库,默认: ghcr.io/aisbench/agent-runtime" + echo " --image-output-dir 离线包输出目录,默认: /home/ais_bench_ci/release_images" + echo " --obs-path OBS 工具路径,默认: /home/ais_bench_ci/obsutil_linux_arm64_5.7.9/" + echo " --push <0|1> 是否推送到远程仓库,默认: 0" + echo " --upload <0|1> 是否上传离线包到 OBS,默认: 0" + echo " --use-cache <0|1> 是否使用缓存构建,默认: 0" + echo " --multi-arch <0|1> 是否构建多架构镜像(amd64+arm64),默认: 0" + echo " -h, --help 显示本帮助" + echo "" + echo "示例:" + echo " $0 --base-tag v3.1-20260522-master" + echo " $0 --base-tag v3.1-20260522-master --push 1 --upload 1" + echo " $0 --base-tag v3.1-20260522-master --multi-arch 1 --push 1" + exit 1 +} + +# ============ 默认配置 ============ +BASE_TAG="" +OS="ubuntu24.04" +py_version="py312" +hub_repo="ghcr.io/aisbench/aisbench_benchmark" +target_hub_repo="ghcr.io/aisbench/agent-runtime" +image_output_dir="/home/ais_bench_ci/release_images" +obsutils_path="/home/ais_bench_ci/obsutil_linux_arm64_5.7.9/" +push=0 +upload=0 +use_cache=0 +multi_arch=0 + +# ============ 参数解析 ============ +while [[ $# -gt 0 ]]; do + case $1 in + --base-tag) + BASE_TAG="$2" + shift 2 + ;; + --os) + OS="$2" + shift 2 + ;; + --py-version) + py_version="$2" + shift 2 + ;; + --hub-repo) + hub_repo="$2" + shift 2 + ;; + --target-hub-repo) + target_hub_repo="$2" + shift 2 + ;; + --image-output-dir) + image_output_dir="$2" + shift 2 + ;; + --obs-path) + obsutils_path="$2" + shift 2 + ;; + --push) + push="$2" + shift 2 + ;; + --upload) + upload="$2" + shift 2 + ;; + --use-cache) + use_cache="$2" + shift 2 + ;; + --multi-arch) + multi_arch="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + echo "错误:未知参数 $1" + usage + ;; + esac +done + +# ============ 校验必填参数 ============ +if [ -z "$BASE_TAG" ]; then + echo "错误:缺少必需参数 --base-tag" + echo "提示:--base-tag 是 aisbench_benchmark 基镜像的 TAG,例如 v3.1-20260522-master" + usage +fi + +# agent runtime 依赖 python3.12 的 venv,若用其他 py_version 给出警告 +if [ "$py_version" != "py312" ]; then + echo "警告:agent runtime 推荐 py312,当前指定 ${py_version},可能因缺 python3.12 导致 venv 创建失败" +fi + +# ============ 计算镜像名 ============ +arch=$(uname -m) + +# 基镜像全名: :--- +base_image="${hub_repo}:${BASE_TAG}-${OS}-${py_version}-${arch}" + +# 目标镜像全名: :--- +# 用 base-tag 作为 agent-runtime 的 tag,便于追溯基镜像版本 +image_name="${target_hub_repo}:${BASE_TAG}-${OS}-${py_version}-${arch}" + +# 多架构 manifest 名(不带 arch 后缀) +manifest_image_name="${target_hub_repo}:${BASE_TAG}-${OS}-${py_version}" + +# 离线包名 +offline_pkg_name="agent_runtime_image_${BASE_TAG}-${OS}-${py_version}-${arch}.tar.gz" +offline_pkg_full_path="${image_output_dir}/${offline_pkg_name}" + +# Dockerfile 路径 +dockerfile_path="$(dirname "$0")/Dockerfile.agent-runtime" + +if [ ! -f "${dockerfile_path}" ]; then + echo "错误:Dockerfile 不存在:${dockerfile_path}" + exit 1 +fi + +echo "============================================================" +echo " 构建 AISBench Agent Runtime 镜像" +echo "============================================================" +echo " 基镜像: ${base_image}" +echo " 目标镜像: ${image_name}" +echo " Dockerfile: ${dockerfile_path}" +echo " arch: ${arch}" +echo " push: ${push}" +echo " upload: ${upload}" +echo " use_cache: ${use_cache}" +echo " multi_arch: ${multi_arch}" +echo "============================================================" + +# ============ 检查基镜像是否存在(本地或远程) ============ +echo "检查基镜像 ${base_image} ..." +if ! docker image inspect "${base_image}" >/dev/null 2>&1; then + echo " 本地不存在,尝试 pull ..." + if ! docker pull "${base_image}" 2>/dev/null; then + echo "错误:基镜像拉取失败:${base_image}" + echo "提示:请确认 --base-tag / --os / --py-version / --hub-repo 参数正确" + exit 1 + fi +fi +echo " ✓ 基镜像就绪" + +# ============ 清理本地旧资源 ============ +echo "清理本地旧资源..." +if docker images -q "${image_name}" >/dev/null 2>&1; then + docker rmi -f "${image_name}" >/dev/null 2>&1 || true + echo " 已删除本地旧镜像:${image_name}" +fi +if [ -f "${offline_pkg_full_path}" ]; then + rm -f "${offline_pkg_full_path}" || true + echo " 已删除本地旧离线包:${offline_pkg_full_path}" +fi + +# ============ 构建镜像 ============ +BUILD_ARGS="--build-arg BASE_IMAGE=${base_image}" + +if [ "$use_cache" == "1" ]; then + echo "开始构建(使用缓存)..." + docker build \ + --network host \ + ${BUILD_ARGS} \ + -f "${dockerfile_path}" \ + -t "${image_name}" \ + "$(dirname "$0")/../../" +else + echo "开始构建(强制不使用缓存)..." + docker build \ + --no-cache \ + --network host \ + ${BUILD_ARGS} \ + -f "${dockerfile_path}" \ + -t "${image_name}" \ + "$(dirname "$0")/../../" +fi + +if [ $? -ne 0 ]; then + echo "错误:镜像构建失败" + exit 1 +fi +echo "✓ 镜像构建成功:${image_name}" + +# ============ 验证镜像 ============ +echo "开始验证镜像..." + +# 1. ais_bench 可用 +echo " [1/4] ais_bench 可用性..." +# ais_bench CLI 不支持 --version,用 --help 第一行即可 +validation_output=$(docker run --rm "${image_name}" ais_bench --help 2>&1 | head -1) || { + echo "错误:ais_bench 不可用" + echo "${validation_output}" + exit 1 +} +echo " ✓ ais_bench: ${validation_output}" + +# 2. venv 完整性 +echo " [2/4] venv 完整性..." +# 单独运行 venv 检查,每项独立 + set +e + 单独捕获 stderr, +# 让具体哪一项失败、错误信息是什么都能完整打印,方便排障 +docker run --rm "${image_name}" bash -c ' + set +e # 单个子命令失败不中止整体 + failures=() + + # 2.1 三个 venv 目录 + python 可执行 + for v in harbor swebench swebench_pro; do + if [ -d /opt/venvs/$v ] && [ -x /opt/venvs/$v/bin/python ]; then + ver=$(/opt/venvs/$v/bin/python --version 2>&1) + echo " ✓ $v venv: ${ver}" + else + echo " ✗ $v venv: 缺失或 python 不可执行" + failures+=("$v venv 缺失或 python 不可执行") + fi + done + + # 2.2 三个 venv 都需有 ais_bench wrapper + # harbor 的 wrapper 让 subprocess 启动 harbor venv 的 python; + # swebench / swebench_pro 的 wrapper 让主进程 import venv-local 的 minisweagent + for v in harbor swebench swebench_pro; do + if [ -x /opt/venvs/$v/bin/ais_bench ]; then + echo " ✓ $v-ais_bench-wrapper: OK" + else + echo " ✗ $v-ais_bench-wrapper: 缺失" + failures+=("$v-ais_bench-wrapper 缺失") + fi + done + + # 2.3 swebench / swebench_pro 各自能 import 对应 fork 的 mini-swe-agent + # PyPI 包名 mini-swe-agent(带横杠),但 Python 模块名是 minisweagent(连写无下划线) + # 单独捕获 stderr,方便看到 ModuleNotFoundError / 内部 ImportError 等真实错误 + for v in swebench swebench_pro; do + out=$(/opt/venvs/$v/bin/python -c "import minisweagent; print(minisweagent.__file__)" 2>&1) + rc=$? + if [ $rc -eq 0 ]; then + echo " ✓ $v minisweagent: ${out}" + else + echo " ✗ $v minisweagent: import 失败(python exit=$rc)" + echo " 错误信息:" + echo "$out" | sed "s/^/ /" + failures+=("$v minisweagent import 失败") + fi + done + + echo "" + if [ ${#failures[@]} -gt 0 ]; then + echo " 失败项汇总 (${#failures[@]}):" + for f in "${failures[@]}"; do + echo " - $f" + done + echo "" + echo " 排查建议:" + echo " 1. 进入镜像手动检查:" + echo " docker run --rm -it ${image_name} bash" + echo " ls -la /opt/venvs/" + echo " /opt/venvs/swebench/bin/python --version" + echo " /opt/venvs/swebench/bin/python -c \"import minisweagent\"" + echo " 2. 若 import 报 ModuleNotFoundError:Dockerfile 步骤 8/9 的 pip install 未生效;" + echo " 检查 gh-proxy.com / 华为云 pypi 镜像可达性;可加 --no-cache 强制重建步骤 8/9。" + echo " 3. 若 import 报内部 ImportError:minisweagent 自身依赖在当前 venv 内不可见;" + echo " 检查 mini-swe-agent setup.py 的 install_requires 在 venv 内是否齐全。" + exit 1 + fi +' || { + echo "错误:venv 检查失败(详见上方失败项汇总与排查建议)" + exit 1 +} +echo " ✓ harbor / swebench / swebench_pro venv 都完整" + +# 3. doctor.sh / packs 就位 +echo " [3/4] 脚本就位..." +scripts_output=$(docker run --rm "${image_name}" bash -c ' + [ -x /usr/local/bin/ais_bench_agent_doctor.sh ] && echo "doctor.sh: OK" || echo "doctor.sh: 缺失" + ls /opt/agent-resources/packs/*.yaml 2>/dev/null | while read f; do + echo "pack: $(basename $f .yaml)" + done +') || { + echo "错误:脚本检查失败" + exit 1 +} +echo "${scripts_output}" | sed 's/^/ /' + +# 4. harbor 的 docker-compose-base.yaml 已 patch +echo " [4/4] harbor compose 模板 patch 校验..." +patch_output=$(docker run --rm "${image_name}" bash -c ' + /opt/venvs/harbor/bin/python -c " +import harbor, os, yaml +p = os.path.dirname(harbor.__file__) + \"/environments/docker/docker-compose-base.yaml\" +cfg = yaml.safe_load(open(p)) +svc = cfg.get(\"services\", {}).get(\"main\", {}) +opts = svc.get(\"security_opt\", []) +print(\"seccomp=unconfined:\", \"seccomp=unconfined\" in opts) +print(\"network_mode=host:\", svc.get(\"network_mode\") == \"host\") +" 2>&1 +') || { + echo "错误:harbor compose 模板校验失败" + echo "${patch_output}" + exit 1 +} +echo "${patch_output}" | sed 's/^/ /' +if ! echo "${patch_output}" | grep -q "seccomp=unconfined: True"; then + echo "错误:harbor compose 模板未正确 patch seccomp" + exit 1 +fi +echo " ✓ harbor compose patch OK" + +echo "✓ 镜像验证通过" + +# ============ 推送镜像 ============ +if [ "$push" == "1" ]; then + echo "推送镜像到远程仓库..." + docker push "${image_name}" + if [ $? -ne 0 ]; then + echo "错误:镜像推送失败" + exit 1 + fi + echo "✓ 已推送:${image_name}" + + # 同时打 latest tag 并推送,供 bootstrap.sh 默认拉取 + latest_image_name="${target_hub_repo}:latest-${OS}-${py_version}-${arch}" + echo " 打 latest tag: ${latest_image_name}" + docker tag "${image_name}" "${latest_image_name}" + docker push "${latest_image_name}" + if [ $? -ne 0 ]; then + echo "错误:latest tag 推送失败" + exit 1 + fi + echo "✓ 已推送 latest:${latest_image_name}" +fi + +# ============ 多架构 manifest 合并 ============ +if [ "$multi_arch" == "1" ]; then + if [ "$push" != "1" ]; then + echo "提示:多架构模式下未开启推送,manifest 合并需要已推送的镜像。跳过 manifest 合并。" + else + arch_image_amd64="${target_hub_repo}:${BASE_TAG}-${OS}-${py_version}-x86_64" + arch_image_arm64="${target_hub_repo}:${BASE_TAG}-${OS}-${py_version}-aarch64" + latest_arch_image_amd64="${target_hub_repo}:latest-${OS}-${py_version}-x86_64" + latest_arch_image_arm64="${target_hub_repo}:latest-${OS}-${py_version}-aarch64" + latest_manifest_image_name="${target_hub_repo}:latest-${OS}-${py_version}" + + echo "创建多架构 manifest list:${manifest_image_name}" + echo " - ${arch_image_amd64}" + echo " - ${arch_image_arm64}" + + docker buildx imagetools create \ + -t "${manifest_image_name}" \ + "${arch_image_amd64}" \ + "${arch_image_arm64}" + + if [ $? -ne 0 ]; then + echo "错误:多架构 manifest 创建失败" + exit 1 + fi + echo "✓ 多架构 manifest list 已更新:${manifest_image_name}" + + echo "创建 latest 多架构 manifest list:${latest_manifest_image_name}" + echo " - ${latest_arch_image_amd64}" + echo " - ${latest_arch_image_arm64}" + + docker buildx imagetools create \ + -t "${latest_manifest_image_name}" \ + "${latest_arch_image_amd64}" \ + "${latest_arch_image_arm64}" + + if [ $? -ne 0 ]; then + echo "错误:latest 多架构 manifest 创建失败" + exit 1 + fi + echo "✓ latest 多架构 manifest list 已更新:${latest_manifest_image_name}" + echo " docker buildx imagetools inspect ${manifest_image_name}" + echo " docker buildx imagetools inspect ${latest_manifest_image_name}" + fi +fi + +# ============ 打包离线包并上传 OBS ============ +if [ "$upload" == "1" ]; then + echo "打包离线包..." + mkdir -p "${image_output_dir}" + docker save "${image_name}" | gzip -9 > "${offline_pkg_full_path}" + echo " 离线包已生成:${offline_pkg_full_path}" + chmod 640 "${offline_pkg_full_path}" + + echo "上传离线包到 OBS 桶..." + if [ ! -d "${obsutils_path}" ] || [ ! -x "${obsutils_path}/obsutil" ]; then + echo "错误:obsutil 路径不存在或不可执行:${obsutils_path}" + exit 1 + fi + + cd "${obsutils_path}" + ./obsutil cp "${offline_pkg_full_path}" "obs://aisbench/images/agent/runtime/${offline_pkg_name}" -f + + if [ $? -eq 0 ]; then + echo "✓ 离线包已上传:obs://aisbench/images/agent/runtime/${offline_pkg_name}" + else + echo "错误:OBS 桶上传失败" + exit 1 + fi +fi + +# ============ 完成 ============ +echo "" +echo "============================================================" +echo "✓ 构建完成" +echo "============================================================" +echo " 镜像: ${image_name}" +if [ "$push" == "1" ]; then + echo " latest: ${target_hub_repo}:latest-${OS}-${py_version}-${arch}" +fi +if [ "$multi_arch" == "1" ] && [ "$push" == "1" ]; then + echo " manifest: ${manifest_image_name}" + echo " latest manifest: ${target_hub_repo}:latest-${OS}-${py_version}" +fi +if [ "$upload" == "1" ]; then + echo " 离线包: obs://aisbench/images/agent/runtime/${offline_pkg_name}" +fi +echo "" +echo "用户使用:" +echo " curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh | bash" +echo " # 或本地测试:" +echo " bash docker/agent_runtime/bootstrap.sh --mode A" From 875cebb71f555a0bd1607be5ba4f9753913c412e Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:42:45 +0800 Subject: [PATCH 09/36] add auto scripts --- .../ais_bench_agent_bootstrap.sh | 526 ++++++++++++++++++ docker/agent_runtime/doctor.sh | 169 ++++++ 2 files changed, 695 insertions(+) create mode 100644 docker/agent_runtime/ais_bench_agent_bootstrap.sh create mode 100644 docker/agent_runtime/doctor.sh diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh new file mode 100644 index 00000000..4def7964 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -0,0 +1,526 @@ +#!/bin/bash +# ais_bench_agent_bootstrap.sh +# +# 一键准备 AISBench Agent 测评运行环境(runtime 容器) +# +# 本脚本在物理机上执行,完成: +# 1. 探测宿主 docker / cgroup / arch +# 2. 自动选择 DinD(模式 A)或 Socket 代理(模式 B) +# 3. 拉取 agent-runtime 镜像(dockerhub 优先,OBS tar 回退) +# 4. 启动 runtime 容器(自动带 --privileged / --cgroupns=host / daemon.json) +# 5. 容器内启动 dockerd(模式 A)或重链 ais_bench(模式 B) +# 6. (可选)把宿主的数据集目录挂载进容器(--datasets ) +# 7. 自检并打印下一步(doctor → 原生 ais_bench) +# +# 本脚本不接管测评执行,也不预置数据集 / case 镜像: +# - 数据集由用户在物理机上准备好,通过 --datasets 挂载到容器内同路径 +# - case 沙箱镜像由用户自行 docker pull / docker load 后再跑测评 +# 环境就绪后,用户用原生 ais_bench 命令跑测评,原理与各 benchmark 文档完全一致。 +# +# 用法: +# curl -fsSL /ais_bench_agent_bootstrap.sh | bash -s -- --datasets /data/datasets +# bash ais_bench_agent_bootstrap.sh --datasets /data/datasets +# +# 环境变量(仅 OBS_RUNTIME_TAR_BASE 保留为 env,其他配置请用 CLI 参数): +# OBS_RUNTIME_TAR_BASE OBS runtime tar 下载基址(一般无需改,正常物理机无需设置) +# +# 命令行参数: +# --datasets 把宿主 挂载到容器内的相同路径 +# 同时把首个 --datasets 路径作为环境变量 +# AISBENCH_AGENT_DATASET_PATH 注入容器(这就是 +# 原生配置 path 的值,不在容器内做任何拼接)。 +# +# 直接传 harbor benchmark 数据集完整目录即可: +# --datasets /data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10 +# 或 full: +# --datasets /data/datasets/harbor/full/terminal-bench-2 +# 原生配置(如 harbor_terminal_bench_2_task.py)从 +# env var 直接读 path,无需 vim 修改。 +# +# 可多次指定挂载多个目录,但只有首个路径会注入 env var。 +# 多目录场景下用户可手动 export AISBENCH_AGENT_DATASET_PATH +# 覆盖。 +# --runtime-tar 离线模式:用宿主上已下载好的 tar 包加载 runtime 镜像, +# 完全跳过 docker pull / OBS 下载。 +# 适用场景:内网/隔离环境部署机无法访问 registry。 +# tar 可通过以下方式获取: +# - 维护者用 build_image_agent_runtime.sh --upload 1 上传 OBS 后下载 +# - 拷贝到 U 盘/内网代理服务器后再 wget +# - 镜像构建产物(docker save)直接拷贝 +# tar 内镜像 tag 默认从 tar 内检测(grep agent-runtime), +# 若 RUNTIME_IMAGE 已在宿主机存在则优先匹配。 +# 例: --runtime-tar /opt/aisbench/agent-runtime-ubuntu24-py312-x86_64.tar.gz +# --case-tar 离线场景:把宿主上已下载好的 case 沙箱镜像 tar 加载进 runtime 容器内 +# 的 docker daemon。路径可以是单个 tar 文件,也可以是一个文件夹 +# (脚本会递归加载其中所有 .tar / .tar.gz / .tgz 文件)。 +# 适用于 case 镜像提前下载到本地、内网无法 pull 的场景。 +# 加载完成后镜像就在 runtime 容器内可见,可直接跑测评。 +# (可多次指定) +# 例: --case-tar /opt/aisbench/case-tb2-mini-0.10.tar.gz +# --case-tar /opt/aisbench/case-tars/ +# --mode A|B 强制指定 DinD (A) 或 Socket 代理 (B)。不传则按 +# docker 版本 + cgroup 类型自动判断。 +# --container-name runtime 容器名(默认 ais_bench_agent)。用于一台机器上 +# 同时跑多个独立 runtime 容器时区分。 +# --runtime-image 指定 runtime 镜像 tag(默认 latest-ubuntu24.04-py312-${ARCH})。 +# 推荐显式传具体 commit tag 以保证可复现性: +# --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 +# --host-path 仅模式 B 生效。/benchmark 的提取目标目录, +# 默认 /opt/ais_bench_agent。仅 /opt 不可写的受限环境需要改。 + +set -e + +# ============ 默认配置 ============ +# runtime 镜像 tag 格式: :--- +# 例如 ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 +# +# 默认用 "latest" tag(CI 构建时打 latest,指向最新版本),用户推荐显式传 --runtime-image +# 指定具体版本,以保证可复现性: +# bash bootstrap.sh --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 --datasets /data/datasets +# +# 构建脚本(build_image_agent_runtime.sh)产出的 tag 形如 +# ghcr.io/aisbench/agent-runtime:--- +# 推送时若同时打了 latest---,本脚本默认拉 latest。 +ARCH=$(uname -m) + +# 仅保留的 env 变量(CLI 参数已替代其他所有 env 配置) +OBS_RUNTIME_TAR_BASE="${OBS_RUNTIME_TAR_BASE:-https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/runtime}" + +# CLI 参数暂存变量(由 --xxx 填充;后续 [应用默认值] 段统一给最终值) +MODE_FORCE="" +CONTAINER_NAME_CLI="" +RUNTIME_IMAGE_CLI="" +HOST_PATH_CLI="" + +# 用户要挂载进容器的宿主数据集路径(可多个) +DATASET_MOUNTS=() +# 离线模式:用户提供的 runtime tar 包路径(绝对路径) +RUNTIME_TAR="" +# 离线模式:用户提供的 case 沙箱镜像 tar(文件或目录,可多个) +CASE_TAR_PATHS=() + +# ============ 参数解析 ============ +while [[ $# -gt 0 ]]; do + case "$1" in + --datasets) + [ -z "${2:-}" ] && { echo "[错误] --datasets 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --datasets 必须是绝对路径: $2" >&2; exit 1; } + [ ! -d "$2" ] && { echo "[错误] --datasets 路径在宿主上不存在: $2" >&2; exit 1; } + DATASET_MOUNTS+=("$2") + shift 2 + ;; + --runtime-tar) + [ -z "${2:-}" ] && { echo "[错误] --runtime-tar 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --runtime-tar 必须是绝对路径: $2" >&2; exit 1; } + [ ! -f "$2" ] && { echo "[错误] --runtime-tar 文件在宿主上不存在: $2" >&2; exit 1; } + RUNTIME_TAR="$2" + shift 2 + ;; + --case-tar) + [ -z "${2:-}" ] && { echo "[错误] --case-tar 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --case-tar 必须是绝对路径: $2" >&2; exit 1; } + [ ! -e "$2" ] && { echo "[错误] --case-tar 路径在宿主上不存在: $2" >&2; exit 1; } + CASE_TAR_PATHS+=("$2") + shift 2 + ;; + --mode) + [ -z "${2:-}" ] && { echo "[错误] --mode 需要 A 或 B" >&2; exit 1; } + case "$2" in + A|B) MODE_FORCE="$2" ;; + *) echo "[错误] --mode 必须是 A 或 B,收到: $2" >&2; exit 1 ;; + esac + shift 2 + ;; + --container-name) + [ -z "${2:-}" ] && { echo "[错误] --container-name 不能为空" >&2; exit 1; } + CONTAINER_NAME_CLI="$2" + shift 2 + ;; + --runtime-image) + [ -z "${2:-}" ] && { echo "[错误] --runtime-image 不能为空" >&2; exit 1; } + RUNTIME_IMAGE_CLI="$2" + shift 2 + ;; + --host-path) + [ -z "${2:-}" ] && { echo "[错误] --host-path 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --host-path 必须是绝对路径: $2" >&2; exit 1; } + HOST_PATH_CLI="$2" + shift 2 + ;; + -h|--help) + sed -n '2,80p' "$0" | sed 's/^# *//' + exit 0 + ;; + *) echo "[错误] 未知参数: $1" >&2; exit 1 ;; + esac +done + +# ============ 应用默认值 ============ +# 仅 CLI 参数与 hardcoded 默认,不读 env + +# --mode:默认空(自动判断) +MODE="${MODE_FORCE:-}" + +# --container-name(默认 ais_bench_agent) +CONTAINER_NAME="${CONTAINER_NAME_CLI:-ais_bench_agent}" + +# --runtime-image(默认 latest-${OS}-${PY}-${ARCH}) +RUNTIME_IMAGE="${RUNTIME_IMAGE_CLI:-ghcr.io/aisbench/agent-runtime:latest-ubuntu24.04-py312-${ARCH}}" + +# --host-path(仅模式 B,默认 /opt/ais_bench_agent) +HOST_PATH="${HOST_PATH_CLI:-/opt/ais_bench_agent}" + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +# ============ [1/6] 探测宿主环境 ============ +log "=== [1/6] 探测宿主环境 ===" + +command -v docker >/dev/null 2>&1 || fail "宿主未安装 docker,请先安装 docker(>= 20.10)" + +DOCKER_VER=$(docker version --format '{{.Server.Version}}' 2>/dev/null | head -1) +[ -z "$DOCKER_VER" ] && fail "无法获取 docker 版本,请确认 docker daemon 已启动" +DOCKER_MAJOR=$(echo "$DOCKER_VER" | cut -d. -f1) + +CGROUP_TYPE=$(stat -fc %T /sys/fs/cgroup 2>/dev/null || echo "unknown") + +log " Docker: ${DOCKER_VER} (major=${DOCKER_MAJOR})" +log " cgroup: ${CGROUP_TYPE}" +log " arch: ${ARCH}" +if [ "${#DATASET_MOUNTS[@]}" -gt 0 ]; then + log " datasets挂载(${#DATASET_MOUNTS[@]} 个):" + for p in "${DATASET_MOUNTS[@]}"; do + log " - 宿主 ${p} → 容器内 ${p}" + done + log " 环境变量 AISBENCH_AGENT_DATASET_PATH=${DATASET_MOUNTS[0]}(容器内可读)" +else + log " datasets挂载: 无(用户未传 --datasets;配置中 path 字段需自行保证可用)" +fi +if [ -n "${RUNTIME_TAR}" ]; then + SIZE=$(du -h "${RUNTIME_TAR}" 2>/dev/null | cut -f1) + log " runtime来源: 离线 tar ${RUNTIME_TAR} (${SIZE})" +else + log " runtime来源: registry pull / OBS 回退(默认行为)" +fi +if [ "${#CASE_TAR_PATHS[@]}" -gt 0 ]; then + log " case镜像来源(${#CASE_TAR_PATHS[@]} 个,运行时加载进容器 docker daemon):" + for p in "${CASE_TAR_PATHS[@]}"; do + log " - ${p} ($(if [ -d "$p" ]; then echo dir; else echo "file $(du -h "$p" 2>/dev/null | cut -f1)"; fi))" + done +else + log " case镜像来源: 容器内手动 docker pull / load(默认行为)" +fi + +# ============ [2/6] 选择 DinD/Socket 模式 ============ +log "=== [2/6] 选择 DinD/Socket 模式 ===" + +if [ -n "${MODE:-}" ]; then + log " → 强制模式 ${MODE}(--mode)" +else + # 模式 A 推荐:docker >= 20.10 + cgroup v2 + # 模式 B 兼容:任意 docker 版本 + # 详见 docker/OVERVIEW.zh.md "运行 Agent / 沙箱类测评" + if [ "${DOCKER_MAJOR}" -ge 20 ] && [ "${CGROUP_TYPE}" = "cgroup2fs" ]; then + MODE="A" + log " → 模式 A (Docker-in-Docker,推荐,子容器隔离)" + else + MODE="B" + log " → 模式 B (Socket 代理,兼容任意 docker 版本)" + log " 原因: docker_major=${DOCKER_MAJOR} (<20) 或 cgroup=${CGROUP_TYPE} (!=cgroup2fs)" + fi +fi + +# ============ [3/6] 拉取 agent-runtime 镜像 ============ +log "=== [3/6] 拉取 agent-runtime 镜像 ===" + +if [ -n "${RUNTIME_TAR}" ]; then + # ---------- 离线模式:用户提供的 tar ---------- + log " 离线模式:跳过 docker pull / OBS 下载" + log " docker load -i ${RUNTIME_TAR}" + docker load -i "${RUNTIME_TAR}" || fail "docker load 失败: ${RUNTIME_TAR}" + # 检测 tar 加载后的镜像 tag + if docker image inspect "${RUNTIME_IMAGE}" >/dev/null 2>&1; then + log " ✓ 使用 RUNTIME_IMAGE 指定的 tag: ${RUNTIME_IMAGE}" + else + DETECTED=$(docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i 'agent-runtime' | head -1 || true) + if [ -z "${DETECTED}" ]; then + fail "tar 加载完成但未发现 agent-runtime 镜像;tar 内容或 RUNTIME_IMAGE 可能不匹配" + fi + log " ✓ tar 中检测到镜像: ${DETECTED}" + RUNTIME_IMAGE="${DETECTED}" + fi +elif docker image inspect "${RUNTIME_IMAGE}" >/dev/null 2>&1; then + log " ✓ runtime 镜像已存在本地: ${RUNTIME_IMAGE}" +else + log " 尝试 docker pull ${RUNTIME_IMAGE} ..." + if docker pull "${RUNTIME_IMAGE}" 2>/dev/null; then + log " ✓ docker pull 成功" + else + log " docker pull 失败,回退 OBS tar" + TAR_URL="${OBS_RUNTIME_TAR_BASE}/agent-runtime-ubuntu24-py312-${ARCH}.tar.gz" + log " 下载: ${TAR_URL}" + curl -fsSL "${TAR_URL}" -o /tmp/agent-runtime.tar.gz || fail "OBS 下载失败: ${TAR_URL}" + log " docker load ..." + docker load -i /tmp/agent-runtime.tar.gz || fail "docker load 失败" + rm -f /tmp/agent-runtime.tar.gz + log " ✓ OBS tar 加载成功" + fi +fi + +# ============ [4/6] 启动容器 ============ +log "=== [4/6] 启动容器(模式 ${MODE})===" + +# 清理同名旧容器 +if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log " 清理同名旧容器: ${CONTAINER_NAME}" + docker rm -f "${CONTAINER_NAME}" >/dev/null +fi + +# 拼装数据集挂载参数 +MOUNT_ARGS="" +for p in "${DATASET_MOUNTS[@]+"${DATASET_MOUNTS[@]}"}"; do + MOUNT_ARGS="$MOUNT_ARGS -v ${p}:${p}" +done + +# 把首个 --datasets 路径持久化为容器内环境变量 +# 原生配置(如 harbor_terminal_bench_2_task.py)通过读取此变量得到数据集路径, +# 无需用户在容器内额外配置,符合"一键准备"的设计目标。 +# 多 --datasets 时仅首个生效,其余需用户自行改 DEFAULT_DATASET_PATH。 +DATASET_ENV="" +if [ "${#DATASET_MOUNTS[@]}" -gt 0 ]; then + DATASET_ENV="-e AISBENCH_AGENT_DATASET_PATH=${DATASET_MOUNTS[0]}" +fi + +if [ "${MODE}" = "A" ]; then + # 模式 A:Docker-in-Docker + # cgroup v2 宿主机 --privileged + --cgroupns=host 必须同时使用 + # 缺少 --cgroupns=host 会报 "cannot enter cgroupv2 ... invalid state" + # 详见 docker/OVERVIEW.zh.md 模式 A + docker run --name "${CONTAINER_NAME}" -it -d \ + --net=host --ipc=host \ + --privileged --cgroupns=host \ + -w /benchmark \ + ${MOUNT_ARGS} \ + ${DATASET_ENV} \ + "${RUNTIME_IMAGE}" bash +else + # 模式 B:Socket 代理 + # 挂载宿主 docker socket,子容器由宿主 daemon 创建 + # 需要把 /benchmark 拷贝到宿主路径再挂回去(避免 docker cp 语义) + # 详见 docker/OVERVIEW.zh.md 模式 B + # HOST_PATH 已在前面 [应用默认值] 段统一计算 + log " 模式 B 准备 HOST_PATH: ${HOST_PATH}" + mkdir -p "${HOST_PATH}" + + # 从 runtime 镜像拷贝 /benchmark 内容到 HOST_PATH + log " 提取 /benchmark 到 ${HOST_PATH} ..." + docker run -d --name tmp_extract "${RUNTIME_IMAGE}" bash >/dev/null + docker cp tmp_extract:/benchmark/. "${HOST_PATH}/" || { docker rm -f tmp_extract; fail "docker cp 失败"; } + docker rm -f tmp_extract >/dev/null + + docker run --name "${CONTAINER_NAME}" -it -d \ + --net=host --privileged \ + -w "${HOST_PATH}" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "${HOST_PATH}":"${HOST_PATH}" \ + ${MOUNT_ARGS} \ + ${DATASET_ENV} \ + "${RUNTIME_IMAGE}" bash +fi + +log " ✓ 容器已启动: ${CONTAINER_NAME}" + +# ============ [5/6] 容器内配置 docker ============ +log "=== [5/6] 容器内配置 docker ===" + +if [ "${MODE}" = "A" ]; then + # 模式 A:容器内启动 dockerd + # 必须写 daemon.json: + # - cgroupfs driver:DinD 在 cgroup v2 宿主上的必需配置(docker 27.x 默认 systemd,容器内无 systemd) + # - vfs 存储驱动:DinD 通用性最高;若宿主内核与容器根fs支持,overlay2 性能更好 + # 详见 docker/OVERVIEW.zh.md 模式 A 步骤二 + docker exec "${CONTAINER_NAME}" bash -c ' + set -e + mkdir -p /etc/docker + cat > /etc/docker/daemon.json < /tmp/dockerd.log 2>&1 & + for i in $(seq 1 30); do + [ -S /var/run/docker.sock ] && break + sleep 1 + done + if ! docker info >/dev/null 2>&1; then + echo "[错误] 容器内 dockerd 启动失败,日志:" + tail -30 /tmp/dockerd.log + exit 1 + fi + echo " ✓ 容器内 dockerd ready" + ' || fail "模式 A dockerd 启动失败" +else + # 模式 B:socket 已挂载,重链 ais_bench + # 因为 WORKDIR 改到了 HOST_PATH,需要在该路径重新以 editable 模式安装 ais_bench + # 只链接不改依赖(--no-deps) + # 详见 docker/OVERVIEW.zh.md 模式 B 步骤二 + docker exec "${CONTAINER_NAME}" bash -c ' + pip3 install -e ./ --use-pep517 --no-deps --no-build-isolation --break-system-packages >/dev/null 2>&1 \ + && echo " ✓ ais_bench relinked" \ + || echo " ⚠ ais_bench relink 失败(可忽略,若 ais_bench 命令可用即可)" + ' +fi + +# ============ [6/7] 加载 case 镜像(可选) ============ +log "=== [6/7] 加载 case 镜像(可选)===" + +if [ "${#CASE_TAR_PATHS[@]}" -gt 0 ]; then + # 把 --case-tar 展开成具体的 tar 文件列表 + log " 收集 case tar 文件..." + CASE_TAR_FILES=() + for p in "${CASE_TAR_PATHS[@]}"; do + if [ -f "$p" ]; then + # 单个文件 + case "$p" in + *.tar|*.tar.gz|*.tgz) CASE_TAR_FILES+=("$p") ;; + *) log " [跳过] 不是 docker tar: $p"; ;; + esac + elif [ -d "$p" ]; then + # 目录:递归收集所有 .tar/.tar.gz/.tgz + while IFS= read -r -d '' f; do + CASE_TAR_FILES+=("$f") + done < <(find "$p" -type f \( -name "*.tar" -o -name "*.tar.gz" -o -name "*.tgz" \) -print0 2>/dev/null) + fi + done + + if [ "${#CASE_TAR_FILES[@]}" -eq 0 ]; then + log " ⚠ --case-tar 路径下未发现 .tar/.tar.gz/.tgz 文件,跳过加载" + else + log " 共 ${#CASE_TAR_FILES[@]} 个 case tar 待加载" + + # 在容器内建暂存目录 + docker exec "${CONTAINER_NAME}" mkdir -p /tmp/case-tars + + # 用 docker cp 把每个 tar 拷进容器 + i=0 + for f in "${CASE_TAR_FILES[@]}"; do + i=$((i+1)) + BN=$(basename "$f") + SIZE=$(du -h "$f" 2>/dev/null | cut -f1) + log " [${i}/${#CASE_TAR_FILES[@]}] docker cp ${f} (${SIZE})" + docker cp "$f" "${CONTAINER_NAME}:/tmp/case-tars/${BN}" || { + log " ✗ docker cp 失败,跳过该文件" + continue + } + done + + # 容器内 docker load 每一个 + log " 容器内 docker load ..." + docker exec "${CONTAINER_NAME}" bash -c ' + cd /tmp/case-tars + loaded=0 + failed=0 + for tf in *.tar *.tar.gz *.tgz; do + [ -f "$tf" ] || continue + sz=$(du -h "$tf" | cut -f1) + echo " loading $tf ($sz) ..." + if out=$(docker load -i "$tf" 2>&1); then + loaded=$((loaded+1)) + echo " ✓ $(echo "$out" | tail -1)" + else + failed=$((failed+1)) + echo " ✗ docker load 失败: $out" + fi + done + echo " docker load 完成: ${loaded} 个成功, ${failed} 个失败" + rm -rf /tmp/case-tars + ' || log " ⚠ 容器内 docker load 阶段出错(请人工 docker exec 进容器检查)" + fi +else + log " 未传 --case-tar,跳过(容器内用户手动 docker pull / docker load)" +fi + +# ============ [7/7] 自检 + 打印下一步 ============ +log "=== [7/7] 自检 ===" + +docker exec "${CONTAINER_NAME}" bash -c ' + echo " docker: $(docker --version 2>/dev/null || echo 不可用)" + echo " compose: $(docker compose version 2>/dev/null | head -1 || echo 不可用)" + echo " ais_bench: $(ais_bench --version 2>/dev/null || echo unknown)" + echo " venvs:" + for v in harbor swebench swebench_pro; do + [ -d /opt/venvs/$v ] && echo " ✓ $v" || echo " ✗ $v 缺失" + done + echo " packs:" + ls /opt/agent-resources/packs/*.yaml 2>/dev/null | while read f; do + echo " ✓ $(basename $f .yaml)" + done + echo " 用户挂载目录(容器内可见性自检):" +' + +# 自检:用户传入的挂载路径在容器内是否可见 +if [ "${#DATASET_MOUNTS[@]}" -gt 0 ]; then + for p in "${DATASET_MOUNTS[@]}"; do + if docker exec "${CONTAINER_NAME}" bash -c "[ -d '$p' ]" >/dev/null 2>&1; then + SIZE=$(docker exec "${CONTAINER_NAME}" bash -c "du -sh '$p' 2>/dev/null | cut -f1") + log " ✓ ${p} (${SIZE})" + else + log " ✗ ${p} 在容器内不可见(挂载失败)" + fi + done +fi + +echo "" +echo "============================================================" +echo "✓ Agent 测评运行环境容器已就绪(模式 ${MODE})" +echo "============================================================" +cat <: # 注册表与 tag 见各 benchmark 文档 + 离线: 从 OBS 下载 tar 后 docker load -i +EOF +fi +cat < +# +# 参数: +# pack pack 名称,如 harbor +# -h, --help 显示帮助 +# +# 验证内容(一次性 L1 静态检查,秒级): +# - docker daemon 可用 +# - venv 完整性(pack.runtime_venv 对应路径存在 + python 可执行) +# - pack.yaml 一致性(pack.native_config 存在) +# - 磁盘/内存余量 + +set -e + +# ============ 默认配置 ============ +PACKS_ROOT="${AGENT_PACKS_ROOT:-/opt/agent-resources/packs}" +PACK="" + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +yq() { + python3.12 -c " +import yaml, sys +try: + c = yaml.safe_load(open('$PACK_FILE')) +except FileNotFoundError: + sys.exit('pack 文件不存在: $PACK_FILE') +$1 +" +} + +usage() { + cat < + + pack pack 名称,如 harbor + +验证内容(一次性 L1 静态检查,秒级): + - docker daemon / venv 完整性 / pack.yaml 一致性 / 磁盘内存 + +可用 pack: +$(ls "${PACKS_ROOT}"/*.yaml 2>/dev/null | xargs -I{} basename {} .yaml | sed 's/^/ /') + +示例: + ais_bench_agent_doctor.sh harbor +EOF + exit 1 +} + +# ============ 参数解析 ============ +[ $# -lt 1 ] && usage +PACK="$1"; shift +[ "$PACK" = "-h" ] || [ "$PACK" = "--help" ] && usage +[ $# -gt 0 ] && { echo "未知参数: $1" >&2; usage; } + +PACK_FILE="${PACKS_ROOT}/${PACK}.yaml" +[ ! -f "$PACK_FILE" ] && fail "pack 不存在: ${PACK}(查找路径 ${PACKS_ROOT})" + +VENV=$(yq "print(c.get('runtime_venv',''))") +NATIVE_CONFIG=$(yq "print(c.get('native_config',''))") +NATIVE_DOC=$(yq "print(c.get('native_doc',''))") + +echo "============================================================" +echo " AISBench Agent runtime 验证: ${PACK}" +echo "============================================================" + +# ============ L1 静态自检 ============ +echo "" +echo "[L1] 静态自检" +L1_FAIL=0 + +echo " [1/3] docker daemon..." +if docker info >/dev/null 2>&1; then + echo " ✓ $(docker --version)" +else + echo " ✗ docker daemon 不可用" + L1_FAIL=1 +fi + +echo " [2/3] venv 完整性: ${VENV}" +VENV_PATH="/opt/venvs/${VENV}" +if [ -n "$VENV" ] && [ -d "$VENV_PATH" ] && [ -x "$VENV_PATH/bin/python" ]; then + echo " ✓ ${VENV} python: $($VENV_PATH/bin/python --version 2>&1)" +else + echo " ✗ venv 不存在: $VENV_PATH" + L1_FAIL=1 +fi + +echo " [3/3] pack.yaml 一致性..." +if [ -n "$NATIVE_CONFIG" ] && [ -f "$NATIVE_CONFIG" ]; then + echo " ✓ native_config: $NATIVE_CONFIG" +else + echo " ✗ native_config 不存在: ${NATIVE_CONFIG:-<空>}" + L1_FAIL=1 +fi + +echo "" +echo " [资源] 磁盘/内存余量..." +AVAIL_GB=$(df -BG /var/lib/docker 2>/dev/null | awk 'NR==2{print $4}' | tr -d G || echo 0) +if [ "$AVAIL_GB" -gt 20 ] 2>/dev/null; then + echo " ✓ docker 数据盘剩余 ${AVAIL_GB}GB" +else + echo " ⚠ docker 数据盘仅剩 ${AVAIL_GB}GB(建议 ≥20GB)" +fi +MEM_GB=$(free -g 2>/dev/null | awk '/Mem:/{print $7}' || echo 0) +if [ "$MEM_GB" -gt 4 ] 2>/dev/null; then + echo " ✓ 可用内存 ${MEM_GB}GB" +else + echo " ⚠ 可用内存 ${MEM_GB}GB(建议 ≥4GB)" +fi + +# ============ 总结 ============ +echo "" +echo "============================================================" +if [ "$L1_FAIL" = "1" ]; then + echo " ✗ ${PACK} runtime 验证未通过" + echo "============================================================" + echo "" + echo "修复指引:" + echo " - docker daemon 不可用: 检查容器内 dockerd 是否启动(模式 A);或宿主 docker.sock 是否挂载(模式 B)" + echo " - venv 缺失: runtime 镜像损坏,重新拉取或重建容器" + echo " docker pull ghcr.io/aisbench/agent-runtime:latest-ubuntu24.04-py312-\${ARCH}" + echo " - native_config 不存在: 检查 /benchmark 路径下是否有 ais_bench 仓库" + exit 1 +fi +echo " ✓ ${PACK} runtime 就绪" +echo "============================================================" +cat <} + 3. 修改原生配置中的 model_names / api_base + 4. 跑真实测评 + + agent_env ${VENV} + vim ${NATIVE_CONFIG} # 仅改 model_names / api_base;path 已自动 + ais_bench ${NATIVE_CONFIG} --debug + +断点续跑: + ais_bench ${NATIVE_CONFIG} --debug --reuse + +切换不同的数据集 / case 集: + 物理机(销毁旧容器 + 重新起): + docker rm -f ais_bench_agent + bash bootstrap.sh --datasets <新数据集路径> --case-tar <新 case tar> + 容器内:不再跑 doctor,直接跑 ais_bench +EOF From cc126be987602292d863e84aa723fe78298ce774 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 14:39:21 +0800 Subject: [PATCH 10/36] update docs of agent runtime --- .../extended_benchmark/agent/swe_bench.md | 42 ++++++++++++++ .../extended_benchmark/agent/swe_bench_pro.md | 56 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md b/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md index 7c1915b1..5508b222 100644 --- a/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md +++ b/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md @@ -2,6 +2,48 @@ SWE-bench是一个基准测试,用于评估大语言模型在从GitHub收集的现实世界软件问题上的表现。给定一个代码库和一个问题,语言模型的任务是生成一个补丁来解决所描述的问题。 +## 快速上手(推荐) + +如果你是第一次跑 Agent 测评,或不想手动处理依赖冲突 / DinD 配置 / mini-swe-agent fork 选择,推荐使用 **AISBench Agent Runtime 一键准备方案**: + +```bash +# 1. 物理机上一键起 runtime 容器 +# --datasets 挂载数据集 + 注入 env var(原生配置 path 自动从此 env var 读,无需 vim 改) +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- --datasets /data/datasets/swebench + +# 2. 进入容器 +docker exec -it ais_bench_agent bash + +# 3. 准备 case 镜像(HF 上 princeton-nlp/SWE-Bench_* 的 docker 镜像,或 docker load tar) +# 数据集由原生配置自动从 HF 下载,无需用户准备 +docker pull swebench/sweb.eval.x86_64.: +# 详见各实例对应的 docker image tag + +# 4. 验证 runtime 就绪 +ais_bench_agent_doctor.sh swebench + +# 5. 改原生配置中的 model_names / api_base(path 由 env var 自动给) +vim ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py + +# 6. 激活 swebench venv(AISBench fork 的 mini-swe-agent) +agent_env swebench + +# 7. 跑测评 +ais_bench ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py --debug +``` + +切到其它数据集(verified / full / multilingual 等):改原生配置文件的 `datasets[0].name` 字段,重新跑。 + +该方案解决了以下痛点: +- **mini-swe-agent fork 冲突**:AISBench fork 与 scaleapi fork 同包名互相覆盖,runtime 用独立 venv 隔离两个 fork +- **容器配置易错**:DinD 模式 A/B、`--cgroupns=host`、`daemon.json`、seccomp 自动处理 +- **离线部署**:`--runtime-tar` 跳过 runtime 镜像的网络获取;`--case-tar` 加载 case 镜像到容器内(可多次,可传目录) + +方案原理与脚本实现见 [`docker/agent_runtime/`](../../../docker/agent_runtime/README.md)。 + +> 下文为完整的 SWE-bench 测评原理与手动配置方式,适用于不使用一键方案、或需要深入定制的场景。 + ## 1. 功能概览 当前在 `ais_bench` 已接入以下 SWEbench 能力: diff --git a/docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md b/docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md index 51b4e115..c7620c2e 100644 --- a/docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md +++ b/docs/source_zh_cn/extended_benchmark/agent/swe_bench_pro.md @@ -4,6 +4,62 @@ SWE-Bench Pro 是一个用于评估大语言模型在长时域软件工程任务 > **注意**:由于官方提供的 Docker 镜像均为 x86 架构,SWE-bench Pro 目前仅支持在 x86 环境上评测,暂不支持 ARM 环境。 +## 快速上手(推荐) + +如果你是第一次跑 Agent 测评,或不想手动处理依赖冲突 / DinD 配置 / mini-swe-agent fork 选择,推荐使用 **AISBench Agent Runtime 一键准备方案**: + +```bash +# 1. 物理机上准备 mini 数据集(已有可跳过) +# SWE-bench Pro 的 mini 数据集**必须**本地准备,无在线版 +# 从 modelers 下载到任意目录,目录结构由用户自行规划 +mkdir -p /data/datasets/swebench_pro +# 下载地址:https://modelers.cn/datasets/AISBench/SWE-Bench_Pro_mini +# 优先 parquet 格式;下载后解压到 /data/datasets/swebench_pro/ 即可 + +# 2. 物理机上一键起 runtime 容器 +# --datasets 挂载数据集 + 注入 env var(原生配置 path 自动从此 env var 读) +# 运行时已 clone 好 /opt/src/SWE-bench_Pro-os,swebp_scripts_dir / swebp_docker_dir 直接指向它 +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- --datasets /data/datasets/swebench_pro + +# 3. 进入容器 +docker exec -it ais_bench_agent bash + +# 4. 验证 runtime 就绪 +ais_bench_agent_doctor.sh swebench_pro + +# 5. 改原生配置: +# - model_names / api_base +# - path 自动从 AISBENCH_AGENT_DATASET_PATH 读(mini 数据集本地路径) +# - SWEBP_SCRIPT_PATH_ABS / SWEBP_DOCKER_PATH_ABS 指向运行时已 clone 的 SWE-bench_Pro-os +vim ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py + +# 6. 激活 swebench_pro venv(scaleapi fork 的 mini-swe-agent) +agent_env swebench_pro + +# 7. 跑测评 +ais_bench ais_bench/configs/swe_bench_pro_examples/mini_swe_agent_swe_bench_pro_mini.py --debug +``` + +切到 full 数据集:先确认 HF 可达,然后改原生配置文件中 `SWEBP_SCRIPT_PATH_ABS` / `SWEBP_DOCKER_PATH_ABS` / `path` / `name='mini'` → `name='full'`,重新跑。 + +**SWE-bench Pro 与 SWE-bench 的差异**(影响快速上手的几处): + +- **mini-swe-agent fork**:SWE-bench Pro 必须用 scaleapi fork,不能用 AISBench fork;runtime 用独立 `swebench_pro` venv 隔离两个 fork +- **mini 数据集**:SWE-bench Pro 的 mini 数据集没有在线版,必须从 modelers 下载本地 parquet;full 数据集可 HF 在线 +- **评测参数**:`SWEBP_SCRIPT_PATH_ABS` 和 `SWEBP_DOCKER_PATH_ABS` 必须指向 SWE-Bench_Pro-os 仓库的 `run_scripts/` 和 `dockerfiles/`;runtime 镜像已 clone 到 `/opt/src/SWE-bench_Pro-os/`,直接填这个路径即可 +- **case 镜像**:scaleapi 官方 x86 docker 镜像,按 instance 拉取;具体 tag 与拉取方式详见 [SWE-bench Pro 官方仓库](https://github.com/scaleapi/SWE-bench_Pro-os) 与 [scaleapi/mini-swe-agent](https://github.com/scaleapi/mini-swe-agent) + +该方案解决了以下痛点: + +- **mini-swe-agent fork 冲突**:AISBench fork 与 scaleapi fork 同包名互相覆盖,runtime 用独立 venv 隔离两个 fork +- **容器配置易错**:DinD 模式 A/B、`--cgroupns=host`、`daemon.json`、seccomp 自动处理 +- **离线部署**:`--runtime-tar` 跳过 runtime 镜像的网络获取;`--case-tar` 加载 case 镜像到容器内(可多次,可传目录) + +方案原理与脚本实现见 [`docker/agent_runtime/`](../../../docker/agent_runtime/README.md)。 + +> 下文为完整的 SWE-bench Pro 测评原理与手动配置方式,适用于不使用一键方案、或需要深入定制的场景。 + ## 1. 功能概览 当前在 `ais_bench` 已接入以下 SWE-Bench Pro 能力: From da3f8fb3a4246556b4516fde01858eef18a7fb81 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 16:00:49 +0800 Subject: [PATCH 11/36] swe bench docs update --- .../extended_benchmark/agent/swe_bench.md | 88 +++++++------------ 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md b/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md index 5508b222..910fb049 100644 --- a/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md +++ b/docs/source_zh_cn/extended_benchmark/agent/swe_bench.md @@ -2,48 +2,6 @@ SWE-bench是一个基准测试,用于评估大语言模型在从GitHub收集的现实世界软件问题上的表现。给定一个代码库和一个问题,语言模型的任务是生成一个补丁来解决所描述的问题。 -## 快速上手(推荐) - -如果你是第一次跑 Agent 测评,或不想手动处理依赖冲突 / DinD 配置 / mini-swe-agent fork 选择,推荐使用 **AISBench Agent Runtime 一键准备方案**: - -```bash -# 1. 物理机上一键起 runtime 容器 -# --datasets 挂载数据集 + 注入 env var(原生配置 path 自动从此 env var 读,无需 vim 改) -curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ - | bash -s -- --datasets /data/datasets/swebench - -# 2. 进入容器 -docker exec -it ais_bench_agent bash - -# 3. 准备 case 镜像(HF 上 princeton-nlp/SWE-Bench_* 的 docker 镜像,或 docker load tar) -# 数据集由原生配置自动从 HF 下载,无需用户准备 -docker pull swebench/sweb.eval.x86_64.: -# 详见各实例对应的 docker image tag - -# 4. 验证 runtime 就绪 -ais_bench_agent_doctor.sh swebench - -# 5. 改原生配置中的 model_names / api_base(path 由 env var 自动给) -vim ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py - -# 6. 激活 swebench venv(AISBench fork 的 mini-swe-agent) -agent_env swebench - -# 7. 跑测评 -ais_bench ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py --debug -``` - -切到其它数据集(verified / full / multilingual 等):改原生配置文件的 `datasets[0].name` 字段,重新跑。 - -该方案解决了以下痛点: -- **mini-swe-agent fork 冲突**:AISBench fork 与 scaleapi fork 同包名互相覆盖,runtime 用独立 venv 隔离两个 fork -- **容器配置易错**:DinD 模式 A/B、`--cgroupns=host`、`daemon.json`、seccomp 自动处理 -- **离线部署**:`--runtime-tar` 跳过 runtime 镜像的网络获取;`--case-tar` 加载 case 镜像到容器内(可多次,可传目录) - -方案原理与脚本实现见 [`docker/agent_runtime/`](../../../docker/agent_runtime/README.md)。 - -> 下文为完整的 SWE-bench 测评原理与手动配置方式,适用于不使用一键方案、或需要深入定制的场景。 - ## 1. 功能概览 当前在 `ais_bench` 已接入以下 SWEbench 能力: @@ -66,13 +24,11 @@ ais_bench ais_bench/configs/swe_bench_examples/mini_swe_agent_swe_bench_lite.py > 💡 上述示例配置文件即为 [自定义配置文件方式](../../advanced_tutorials/run_custom_config.md) 的具体应用。配置文件本质上是 Python 脚本,支持循环、条件判断、列表推导等所有 Python 语法。你可以参考这些示例文件自行编写满足特定需求的配置文件。详见 [自定义配置文件运行AISBench](../../advanced_tutorials/run_custom_config.md)。 - -## 2. 前置依赖 - -运行前请确保以下依赖可用: - -1) 安装 `mini-swe-agent`(infer 依赖) - +## 2. 运行环境安装 +### 3.1 源码安装 +1. 确认docker版本满足要求,执行`docker version`,确保docker版本为20.10.0及以上, docker API版本为 1.42及以上 +2. 参考[工具安装&卸载](../../get_started/install.html)源码安装AISBench测评工具 +3. 安装 `mini-swe-agent`(infer 依赖) ```bash git clone https://github.com/AISBench/mini-swe-agent.git cd mini-swe-agent @@ -80,7 +36,7 @@ pip install -e . cd - ``` -2) 安装 SWE-bench harness(eval 依赖) +4. 安装 SWE-bench harness(eval 依赖) ```bash git clone https://github.com/SWE-bench/SWE-bench.git @@ -89,17 +45,39 @@ pip install -e . cd - ``` -3) Docker 可用(infer/eval 都依赖容器环境) +5. ARM 环境下需要开启 docker 的 x86 支持,执行以下命令: ```bash -docker --version -docker ps +docker run --rm --privileged tonistiigi/binfmt --install all ``` -4) ARM 环境下需要开启 docker 的 x86 支持,执行以下命令: + +### 3.2 一键准备方案(推荐) +如果你不想源码安装依赖,或者docker版本较低(docker version < 20.10.0),推荐使用 **AISBench Agent Runtime 一键准备方案**: ```bash -docker run --rm --privileged tonistiigi/binfmt --install all +# 1. 物理机上一键起 runtime 容器(自动选 DinD/Socket 模式,自动挂载数据集,自动把 case 镜像 tar 拷进容器内部 docker load 完) +# 在线场景:省略 --runtime-tar,runtime 镜像自动从 ghcr.io 拉取最新 +# 离线场景:通过 --runtime-tar 跳过外网拉取,可以从最新的release信息中手动获取 +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --runtime-tar /path/to/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-.tar.gz \ + --host-path /path/to/test_wkp/ \ + --container-name test_agent_run +# --runtime-tar (可选)提前准备的测评镜像,不传则自动拉取最新 +# --host-path 指向的目录需为空目录,容器内会自动创建同名目录挂载数据集和 case 镜像 +# --container-name 指向的容器名需唯一,否则会覆盖旧容器 + +# 2. 进入容器 +docker exec -it test_agent_run bash + +# 3. 验证 runtime 就绪 +ais_bench_agent_doctor.sh swebench + +# 4. 激活 swebench venv(AISBench fork 的 mini-swe-agent) +agent_env swebench + ``` +> 该一键准备方案的方案原理与脚本实现和更详细的介绍见 [`docker/agent_runtime/`](https://github.com/AISBench/benchmark/tree/master/docker/agent_runtime/README.md)。 ## 3. 最小配置(先跑通再调优) From 25af9e03f24ff72728f5d4a3ae4eae117d6a5f40 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Thu, 23 Jul 2026 16:48:03 +0800 Subject: [PATCH 12/36] commit -m update en doc --- docker/OVERVIEW.en.md | 132 +++++++++++++++ docker/agent_runtime/README_en.md | 268 ++++++++++++++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 docker/agent_runtime/README_en.md diff --git a/docker/OVERVIEW.en.md b/docker/OVERVIEW.en.md index a781e171..a5849be3 100644 --- a/docker/OVERVIEW.en.md +++ b/docker/OVERVIEW.en.md @@ -185,6 +185,59 @@ docker build \ The image ships with Docker Engine (≥ 20.0) and Docker Compose v2 (≥ 2.0.0). There are two modes for running Docker inside the container — pick one based on your host Docker version and isolation needs. ### Mode A — Docker-in-Docker (recommended, true nested containers, requires host Docker ≥ 20.10 + cgroup v2) +```bash +┌────────────────────────── Host ──────────────────────────┐ +│ │ +│ ┌──────────────────┐ ┌──────────────────────────┐ │ +│ │ Host Kernel │ │ Host dockerd │ │ +│ │ (cgroup v2) │◀───────▶│ /var/run/docker.sock │ │ +│ └──────────────────┘ │ manages host's own │ │ +│ │ containers │ │ +│ └──────────────────────────┘ │ +│ │ +│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ +│ ┃ AISBench Container (--privileged --cgroupns=host) ┃ │ +│ ┃ ┃ │ +│ ┃ ┌──────────────────────┐ ┌──────────────────────┐ ┃ │ +│ ┃ │ Docker CLI │────▶│ Inner dockerd │ ┃ │ +│ ┃ │ (user runs) │ │ (independent proc │ ┃ │ +│ ┃ └──────────────────────┘ │ inside container) │ ┃ │ +│ ┃ │ daemon.json: │ ┃ │ +│ ┃ │ native.cgroupdriver │ ┃ │ +│ ┃ │ =cgroupfs │ ┃ │ +│ ┃ │ storage=vfs │ ┃ │ +│ ┃ └──────────┬───────────┘ ┃ │ +│ ┃ │ spawn ┃ │ +│ ┃ ▼ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ containerd │ ┃ │ +│ ┃ │ (embedded in │ ┃ │ +│ ┃ │ inner dockerd) │ ┃ │ +│ ┃ └──────────┬───────────┘ ┃ │ +│ ┃ │ ┃ │ +│ ┃ ▼ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ Nested child │ ┃ │ +│ ┃ │ container │ ┃ │ +│ ┃ │ (truly isolated │ ┃ │ +│ ┃ │ namespaces) │ ┃ │ +│ ┃ │ ┌────────────────┐ │ ┃ │ +│ ┃ │ │ Agent process │ │ ┃ │ +│ ┃ │ │ OpenBLAS │ │ ┃ │ +│ ┃ │ │ Python deps │ │ ┃ │ +│ ┃ │ └────────────────┘ │ ┃ │ +│ ┃ └──────────────────────┘ ┃ │ +│ ┃ ┃ │ +│ ┃ —— Isolation boundary: independent PID/IPC/Net/Mount/User ┃ │ +│ ┃ namespaces —— ┃ │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +│ │ +│ ✦ Nested child containers do NOT appear in host `docker ps` │ +│ ✦ Child containers inherit Docker's official default seccomp │ +│ profile (no clone3 blocking) │ +│ ✦ Host dockerd restart does NOT affect inner dockerd │ +└──────────────────────────────────────────────────────────────────┘ +``` A standalone `dockerd` is started inside the container, so child containers are fully isolated from the host. This is the **preferred mode** for agent benchmarks: child containers inherit Docker's official default seccomp profile, so the `pthread_create` / `clone3` block triggered by openEuler / RHEL hardened profiles does not occur; and there is no stale-socket issue when the host's `dockerd` restarts. @@ -250,6 +303,47 @@ docker compose version - For very long-running DinD workloads, consider adding `"default-runtime": "runc"`, `"log-driver": "json-file"`, and `"data-root"` overrides to `/etc/docker/daemon.json`. ### Mode B — Socket Passthrough (works with any Docker version ≥ 1.0) +```bash +┌────────────────────────── Host ──────────────────────────┐ +│ │ +│ ┌──────────────────┐ │ +│ │ Host Kernel │ │ +│ └──────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────┐ bind mount │ +│ │ Host dockerd │ /var/run/docker │ +│ │ /var/run/docker.sock ──────────────────────────┐ │ +│ │ (manages host's own containers) │ │ │ +│ └────────────┬─────────────────────────────┘ │ │ +│ │ │ │ +│ │ actually creates/manages │ │ +│ ▼ │ │ +│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━┓ │ +│ ┃ AISBench Container (--privileged, shares PID/IPC w/ host)┃ │ +│ ┃ ┃ │ +│ ┃ ┌──────────────────────┐ ┃ │ +│ ┃ │ Docker CLI │── HTTP/Unix socket call ──────┘ ┃ │ +│ ┃ │ (user runs) │ no dockerd process inside ┃ │ +│ ┃ └──────────────────────┘ ┃ │ +│ ┃ ┃ │ +│ ┃ —— Shares kernel, PID, IPC namespaces with host —— ┃ │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +│ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ Child container (actually on the host) │ │ +│ │ ┌────────────────┐ │ │ +│ │ │ Agent process │ ← created/collected │ │ +│ │ │ OpenBLAS │ by host dockerd │ │ +│ │ │ Python deps │ │ │ +│ │ └────────────────┘ │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +│ ✦ Child containers appear in host `docker ps` │ +│ ✦ Child containers inherit host dockerd's seccomp profile │ +│ (openEuler/RHEL hardened profile → clone3 blocking) │ +│ ✦ Host dockerd restart → socket inode stale → `docker restart`│ +└──────────────────────────────────────────────────────────────────┘ +``` Mount the host's Docker socket so that `docker run` inside the container actually creates containers on the **host** daemon. Use this mode only when the host Docker is older than 20.10, or when cgroup v2 is unavailable and Mode A cannot be used. @@ -369,6 +463,44 @@ docker compose -f /tmp/docker-compose.yml up - [Sysbox](https://github.com/nestybox/sysbox) — a container runtime that supports nested containers without `--privileged`, at the cost of installing a custom runtime on the host. - Rootless Docker — runs `dockerd` as a non-root user; has its own limitations (no `overlay2` on most distros, network restrictions, etc.). +## Agent Evaluation One-Click Environment Preparation + +For agent benchmarks such as Harbor Terminal-Bench, SWE-bench, and SWE-bench Pro, this repository provides a one-click environment preparation solution in [`docker/agent_runtime/`](agent_runtime/README.md). It consolidates the Mode A/B selection, `daemon.json` configuration, `--cgroupns=host`, and seccomp handling from the "Running Agent / Sandbox Benchmarks" section above into a single script, and additionally solves: + +- **Dependency conflicts**: Dependencies of multiple agent benchmarks conflict with each other (e.g., harbor forces upgrading `datasets` to 4.0+, and two `mini-swe-agent` forks override each other with the same package name). Solved via multi-venv isolation inside the runtime image. +- **Huge case images**: SWE-bench full (~1TB) cannot be packaged as a whole, so it is **not** baked into the runtime image. Users `docker pull` / `docker load` themselves. +- **Frequent dataset versions**: Agent dataset versions change quickly, so they are **not** baked into the runtime image. Users prepare them on the host and mount them into the container via `bootstrap.sh --datasets ` (container path = host path). +- **No environment verification**: `doctor.sh` verifies the runtime configuration (L1 static) before running the benchmark + scans for case image presence (warning), and gives precise fix guidance on failure. + +**Usage** (all three packs use the same workflow; choose `--datasets` and `agent_env` per benchmark): + +```bash +# Host: prepare the dataset directory + start the runtime container with one click +# (mounts the dataset directory into the container) +# Harbor: +mkdir -p /data/datasets/harbor/mini-0.10 +# Prepare dataset (see harbor_bench.md) + case image (can be loaded offline via bootstrap --case-tar) +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10 \ + --case-tar /data/cases/case-tb2-mini-0.10.tar.gz +docker exec -it ais_bench_agent bash +# Inside the container: +ais_bench_agent_doctor.sh harbor +agent_env harbor +ais_bench ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py --debug + +# SWE-bench / SWE-bench Pro work the same way: +# --datasets +# agent_env swebench | swebench_pro +# ais_bench ais_bench/configs/swe_bench[_pro]_examples/... --debug +# For dataset and case image acquisition, see each benchmark's documentation. +``` + +For the solution design and per-script parameters, see [`docker/agent_runtime/README.md`](agent_runtime/README.md). Each agent benchmark doc ([harbor_bench.md](../docs/source_en/extended_benchmark/agent/harbor_bench.md), [swe_bench.md](../docs/source_en/extended_benchmark/agent/swe_bench.md), [swe_bench_pro.md](../docs/source_en/extended_benchmark/agent/swe_bench_pro.md)) also has a "Quick Start" section at the top. + +> This section is the executable packaging of the "Running Agent / Sandbox Benchmarks" section. To understand the principles, still read the Mode A/B sections above; for quick start, just use the script in this section. + ## License / Disclaimer This project's images and build scripts are licensed under the [LICENSE file](https://github.com/AISBench/benchmark/blob/master/LICENSE) in the repository root. diff --git a/docker/agent_runtime/README_en.md b/docker/agent_runtime/README_en.md new file mode 100644 index 00000000..1af0ee4f --- /dev/null +++ b/docker/agent_runtime/README_en.md @@ -0,0 +1,268 @@ +# AISBench Agent Runtime + +Provides images and scripts for the runtime container used by AISBench Agent evaluations (Harbor Terminal-Bench, SWE-bench, SWE-bench Pro, etc.). + +> This repository directory is a community runtime supplement to AISBench/benchmark and is not part of the core benchmark evaluation logic. After preparing the runtime container with the scripts and images in this directory, users still run evaluations with the native `ais_bench` command; the principles are identical to those in each benchmark's documentation. + +## What Problem Does It Solve + +Environment preparation for agent evaluation has three major pain points: + +1. **Dependency conflicts**: harbor forces datasets to upgrade to 4.0+; SWE-bench and SWE-bench Pro each need a different fork of `mini-swe-agent`, with the same package name overwriting each other. +2. **Error-prone container configuration**: DinD modes A/B, `--cgroupns=host`, `daemon.json`, seccomp — any missed step only surfaces as an error at evaluation time. +3. **Frequent dataset / case image version changes**: datasets and case images have many versions; baking them into the runtime image causes rapid expiration. + +This package solves these problems in layers: + +| Layer | Content | Solves | +|---|---|---| +| `Dockerfile.agent-runtime` | Adds 3 isolated venv layers on top of the `aisbench_benchmark` base image (harbor / swebench / swebench_pro) | Dependency conflicts | +| `bootstrap.sh` | One-click runtime container start: auto-selects DinD/Socket mode + mounts datasets + loads case tar | Error-prone container config + dataset injection | +| `doctor.sh` | Static self-check (L1, seconds) — validates docker / venv / pack / resources | Pre-run runtime validation | +| `packs/.yaml` | Metadata for each benchmark (venv name / native config / docs) | Decouples toolchain from benchmarks | + +## Who Prepares Datasets / Case Images + +**What is deliberately not done**: This solution does **not** pre-bake agent benchmark datasets and case sandbox images into the runtime image. The reason is that both change versions frequently, and baking them in means: + +- Every dataset update requires rebuilding the runtime image — a maintenance burden and a large download for users +- A full case sandbox image set is ~71GB and cannot be baked into a base image + +**Who is responsible for what**: + +| Item | Who prepares | How to integrate into the runtime container | +|---|---|---| +| runtime image | AISBench maintainers | `docker pull ghcr.io/aisbench/agent-runtime:latest-...` (or `--runtime-tar` offline) | +| Dataset (task.toml, etc.) | User prepares on the host | `bootstrap.sh --datasets ` (mounted at the same path inside the container + injected as env var) | +| case sandbox image | User prepares tar on the host | `bootstrap.sh --case-tar ` (auto `docker cp` into container + `docker load`) | +| Model call parameters (api_base / model_names) | User edits native config | Inside the container: `vim ais_bench/configs/agent_example/...` | + +## Quick Start (Harbor Terminal-Bench as example, aarch64) +The quick start targets host machines with Docker version below 20.0.0. For other environments, refer to the corresponding agent evaluation documentation. + +```bash +# 1. Prepare dataset and image tar on the host (skip if already present) + +git clone https://modelers.cn/AISBench/terminal-bench-2-offline-mini.git # Dataset preparation +wget https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-aarch64.tar.gz # Runtime image preparation (optional; if omitted, the latest is fetched automatically) +wget https://aisbench.obs.cn-north-4.myhuaweicloud.com/terminal-bench-2-images/terminal-bench-2-offline-prepared-images-selected-0.10_aarch64.tar # Case image preparation; get the link from the corresponding agent evaluation doc as needed +mkdir /path/to/test_wkp/ # Create an empty working directory on the host + +# 2. Start the runtime container with one click on the host (auto-selects DinD/Socket mode, auto-mounts datasets; if the environment has no external network, fetch ais_bench_agent_bootstrap.sh elsewhere first and run with bash) +# Auto-copies case image tar into the container and runs docker load +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /path/to/terminal-bench-2-offline-mini/terminal-bench-2-offline-selected_0.10/ \ + --runtime-tar /path/to/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-aarch64.tar.gz \ + --case-tar /path/to/terminal-bench-2-offline-prepared-images-selected-0.10.tar \ + --host-path /path/to/test_wkp/ \ + --container-name test_agent_run +# --datasets must point to a directory structure consistent with the terminal-bench-2-offline-selected_0.10/ subdirectory of the terminal-bench-2-offline-mini repo +# --runtime-tar (optional) pre-downloaded runtime image +# --case-tar must point to a tar structure consistent with the case image tar described in the corresponding agent evaluation doc +# --host-path must be an empty directory; a same-named directory will be created inside the container to mount datasets and case images +# --container-name must be unique; otherwise the old container will be overwritten + +# 3. Enter the container (case images are already loaded and ready to use) +docker exec -it test_agent_run bash + +# 4. (No vim needed) The native config path is read automatically from AISBENCH_AGENT_DATASET_PATH +# Only vim model_names / api_base +vim ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py + +# 5. Verify runtime is ready +ais_bench_agent_doctor.sh harbor + +# 6. Run evaluation +agent_env harbor +ais_bench ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py --debug +``` + +Switch datasets: destroy the old container + restart bootstrap (update dataset path / case tar together): + +```bash +docker rm -f test_agent_run +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets/harbor/full/terminal-bench-2 \ + --case-tar /data/cases/terminal-bench-2-prepared-images_x86_64.tar.gz +``` + +## Directory Structure + +``` +agent_runtime/ +├── README.md # This file +├── Dockerfile.agent-runtime # runtime image build file (BASE_IMAGE passed via --build-arg, not hardcoded) +├── build_image_agent_runtime.sh # build script (supports --base-tag/--push/--upload/--multi-arch) +├── ais_bench_agent_bootstrap.sh # one-click runtime container start (user-side entry point, must be uploaded to OBS) +├── doctor.sh # runtime readiness verification (inside the container; only validates docker/venv/config, not datasets/cases) +├── packs/ # per-benchmark manifests (name/runtime_venv/native_config/native_doc) +│ ├── harbor.yaml # Harbor Terminal-Bench +│ ├── swebench.yaml # SWE-bench (mini_swe_agent + SWE-bench harness) +│ └── swebench_pro.yaml # SWE-bench Pro (scaleapi adapted version) +└── patches/ # patch scripts used at build / startup time + └── harbor_compose_patch.py # adds seccomp=unconfined + network_mode=host to harbor's docker-compose-base.yaml +``` + +## ais_bench_agent_bootstrap.sh Usage + +```bash +# Minimal call: mount a single dataset directory +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets + +# Mount multiple directories +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --datasets /data/extra + +# Force mode A/B +bash ais_bench_agent_bootstrap.sh --mode A --datasets /data/datasets + +# Custom container name (to distinguish when running multiple runtimes on one machine) +bash ais_bench_agent_bootstrap.sh --container-name my_eval_1 --datasets /data/datasets + +# Custom runtime image (recommended: pass an explicit tag for reproducibility) +bash ais_bench_agent_bootstrap.sh \ + --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 \ + --datasets /data/datasets + +# Mode B + custom /benchmark extraction target (use only when /opt is not writable) +bash ais_bench_agent_bootstrap.sh --mode B --host-path /data/ais_bench_host --datasets /data/datasets + +# Offline mode (intranet/isolated environment): load runtime image from a tar already downloaded on the host +# Completely skips docker pull / OBS download +# Use case: intranet deployment machines that cannot reach ghcr.io or external OBS +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --runtime-tar /opt/aisbench/agent-runtime-ubuntu24-py312-x86_64.tar.gz + +# Fully offline: runtime tar + case image tar passed together +# After container start, case tar is auto-copied into the container and docker loaded +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /opt/aisbench/agent-runtime.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.10.tar.gz + +# Load multiple case images at once (--case-tar can be repeated, or a directory can be passed) +bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /opt/aisbench/agent-runtime.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.10.tar.gz \ + --case-tar /opt/aisbench/case-tb2-mini-0.14.tar.gz \ + --case-tar /opt/aisbench/case-tars/ # all .tar/.tar.gz/.tgz under the directory will be loaded +``` + +`--datasets` / `--host-path` / `--runtime-tar` / `--case-tar` must be **absolute paths** and **must exist on the host** (the script validates them). The in-container path is the same as the host path. + +The full path passed to `--datasets` is injected verbatim into the container as the environment variable `AISBENCH_AGENT_DATASET_PATH`, and native ais_bench configs (e.g. `harbor_terminal_bench_2_task.py`) use this env var directly as the dataset `path` field — **no concatenation, no conversion, completely identical**. Therefore: + +- **Recommended**: pass `--datasets` as the full path of your prepared harbor benchmark dataset (e.g. `/data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10`); the config then needs no vim for path +- **Multiple directories**: `--datasets` can be passed multiple times, but the env var uses only the first; users can manually `export AISBENCH_AGENT_DATASET_PATH=...` to override + +### Command-Line Parameter Reference + +| Parameter | Default | Description | +|---|---|---| +| `--datasets ` | None (not mounted) | Dataset directory; can be passed multiple times | +| `--runtime-tar ` | None (pull) | runtime image tar; for offline scenarios | +| `--case-tar ` | None (prepare manually in container) | case image tar; file or directory; can be passed multiple times | +| `--mode A\|B` | Auto-detected | Force DinD (A) or Socket passthrough (B) | +| `--container-name ` | `ais_bench_agent` | runtime container name | +| `--runtime-image ` | `ghcr.io/aisbench/agent-runtime:latest-ubuntu24.04-py312-${ARCH}` | runtime image tag | +| `--host-path ` | `/opt/ais_bench_agent` | Extraction target for `/benchmark` in mode B | + +### Environment Variables + +Only one env variable is kept (use CLI parameters for other configs): + +| env | Default | Description | +|---|---|---| +| `OBS_RUNTIME_TAR_BASE` | `https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/runtime` | OBS runtime tar download base URL (usually no need to change) | + +### Offline Scenarios + +Deployment machines in intranet/isolated environments cannot reach `ghcr.io/aisbench/agent-runtime` or OBS, but have obtained the runtime tar package and case image tar via USB stick, intranet proxy, etc.: + +1. **Obtain tar package** (either): + - Ask a maintainer to run `build_image_agent_runtime.sh --upload 1` to upload to OBS; intranet users download from OBS + - On a machine with internet access, run `docker save ghcr.io/aisbench/agent-runtime: -o agent-runtime.tar.gz` and copy it in +2. **Run on the deployment machine**: + ```bash + # Minimal: pass only the runtime tar (case images still need manual pull / load inside the container) + bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --runtime-tar /path/to/agent-runtime.tar.gz + + # Fully offline: pass both runtime + case tar + bash ais_bench_agent_bootstrap.sh \ + --datasets /data/datasets \ + --runtime-tar /path/to/agent-runtime.tar.gz \ + --case-tar /path/to/case-tb2-mini-0.10.tar.gz + ``` +3. **Behavior**: + - `--runtime-tar`: completely skips `docker pull` and OBS `curl` download; runs `docker load -i ` and auto-detects the tag (grep `agent-runtime`), preferring `RUNTIME_IMAGE`; gives a precise error on detection failure + - `--case-tar `: supports a single tar or a directory (a directory recursively loads all `.tar` / `.tar.gz` / `.tgz`). The script `docker cp`s it into the container, then runs `docker load -i`. **Supports both A/B modes** (mode A loads into the in-container DinD; mode B loads into the container, but since the socket is shared with the host, it is effectively loaded onto the host as well). For details on modes A/B, see [OVERVIEW.en.md](../OVERVIEW.en.md#running-agent--sandbox-benchmarks-docker-inside-the-container) + - `--case-tar` can be repeated + +Mode B (Socket passthrough) by default extracts `/benchmark` to the host's `/opt/ais_bench_agent`. If your environment's `/opt` is not writable (e.g. some read-only root containers/sandboxes), use `--host-path` to redirect to a writable path: + +```bash +bash ais_bench_agent_bootstrap.sh --datasets /data/datasets --host-path /data/ais_bench_host +``` + +Normal host machines do not need to set this variable. For other configurable environment variables, see the header comments of `bootstrap.sh`. + +## Image Build + +The runtime image is built on top of the `aisbench_benchmark` base image; the base image tag is passed via a parameter, not hardcoded: + +```bash +# Basic build (local, current architecture) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master + +# Specify OS/Python (default ubuntu24.04 + py312) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --os ubuntu24.04 --py-version py312 + +# Build and push to a remote registry +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --push 1 + +# Multi-arch build and push +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --multi-arch 1 --push 1 + +# Build, push, and upload offline package to OBS (for ais_bench_agent_bootstrap.sh fallback download) +bash docker/agent_runtime/build_image_agent_runtime.sh \ + --base-tag v3.1-20260522-master --push 1 --upload 1 +``` + +The build script automatically validates (4 items): +1. ais_bench is available +2. All 3 venvs (harbor / swebench / swebench_pro) are complete + all 3 venvs contain the ais_bench wrapper + both swebench venvs can import minisweagent +3. doctor.sh / packs are in place +4. The harbor compose template has been patched with `seccomp=unconfined` + +## Supported Packs + +| pack name | runtime_venv | Documentation | Description | +|---|---|---|---| +| `harbor` | harbor | [harbor_bench.md](../../docs/source_en/extended_benchmark/agent/harbor_bench.md) | Harbor Terminal-Bench 2.0 | +| `swebench` | swebench | [swe_bench.md](../../docs/source_en/extended_benchmark/agent/swe_bench.md) | SWE-bench (lite/verified/full/multilingual, etc.) | +| `swebench_pro` | swebench_pro | [swe_bench_pro.md](../../docs/source_en/extended_benchmark/agent/swe_bench_pro.md) | SWE-bench Pro (x86 only) | + +pack.yaml does not declare dataset paths or case image acquisition methods — these are entirely under user control: +- Dataset path: explicitly specified by the user via `bootstrap.sh --datasets ` (run whichever you want) +- Case image: the user performs `docker pull` or `docker load` according to the document pointed to by pack.yaml's `native_doc` + +If you want to support more benchmarks in the future, just add a `packs/.yaml`. + +> Common harbor dataset directory names (for reference only, unrelated to the tool): +> - `/data/datasets/harbor/full/terminal-bench-2` (89 cases, including a few external-network tasks) +> - `/data/datasets/harbor/mini-0.10/terminal-bench-2-offline-selected_0.10` (7 cases) +> - `/data/datasets/harbor/mini-0.14/terminal-bench-2-offline-selected_0.14` (10 cases) +> - `/data/datasets/harbor/mini-0.20/terminal-bench-2-offline-selected_0.20` (14 cases) +> +> Whichever path the user passes to `bootstrap.sh --datasets` is injected into the container by the env var `AISBENCH_AGENT_DATASET_PATH`. +> The mini-* series is K-means sampled from `terminal-bench-2-offline` (70 cases after removing external-network tasks) and runs fully offline. +> +> For SWE-bench dataset notes, see [swe_bench.md](../../docs/source_en/extended_benchmark/agent/swe_bench.md) (HF download); for SWE-bench Pro, see [swe_bench_pro.md](../../docs/source_en/extended_benchmark/agent/swe_bench_pro.md). + +## Detailed Solution + +For the full design and parameter descriptions of each script, see the header comments of each script. From 7875023677b432af9118fdb60badb417304e75c8 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 16:56:37 +0800 Subject: [PATCH 13/36] =?UTF-8?q?A1:=20ARM64=20host=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=EF=BC=88qemu-user-static=20build-time=20=E8=A3=85=E5=8C=85?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch A 第一步:让 PR #410 agent-runtime 镜像在 ARM64 host 上 也能跑 SWE-bench / terminal-bench 2 等需要 x86_64 trial 镜像的 case。 - 加 ARG TARGETARCH - TARGETARCH=arm64 时 build-time 装 qemu-user-static(apt 包) - amd64 用户零开销(条件 RUN 不执行) binfmt 注册放在 runtime entrypoint(A4 批次),不在 build-time 因为 build context 没有 /proc/sys/fs/binfmt_misc。 --- docker/agent_runtime/Dockerfile.agent-runtime | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 55d60b01..36081616 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -32,6 +32,29 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +# ============================================================ +# ARM64 host 适配 (A1: build-time 装 qemu-user-static) +# +# ARM64 host 跑 SWE-bench / terminal-bench 2 等需要 x86_64 trial 镜像的 +# case 时,trial 容器是 x86_64 ELF,需要 QEMU user-mode 翻译。 +# +# 仅当 TARGETARCH=arm64 时装 qemu-user-static(amd64 用户零开销)。 +# binfmt 注册放在 runtime entrypoint (A4) —— build context 没有 +# /proc/sys/fs/binfmt_misc,build 期注册无效。 +# ============================================================ +ARG TARGETARCH +RUN if [ "${TARGETARCH}" = "arm64" ]; then \ + apt-get update -qq && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + qemu-user-static && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + echo "[A1] qemu-user-static installed (TARGETARCH=${TARGETARCH})" && \ + which qemu-x86_64-static; \ + else \ + echo "[A1] TARGETARCH=${TARGETARCH}, skip qemu-user-static install"; \ + fi + ENV DEBIAN_FRONTEND=noninteractive ENV PYTHONUNBUFFERED=1 From abb90248760b26aeba3bdd0c829a68bbca898c71 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:02:01 +0800 Subject: [PATCH 14/36] =?UTF-8?q?A2:=20harbor=200.6.1=20=E2=86=92=200.20.0?= =?UTF-8?q?=EF=BC=88J1=20=E5=86=B3=E7=AD=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch A 第二步:升级 harbor 到 0.20.0。 为什么升 0.20.0: - 0.6.1 没有 harbor jobs CLI(仅 harbor run / harbor dataset / harbor task) - mini_matrix 全套基于 harbor 0.20.x 的 harbor jobs start -c matrix.yaml - 升 0.20.0 后 L3 调度才能真正跑起来(PR #410 当前 0.6.1 调不起来) 兼容性: - harbor 0.20.0 同样会升 datasets 4.0+,所以 venv 隔离逻辑不变 - harbor 0.20.0 仍走 python3.12 site-packages,patch 路径不变 - PR #410 原生用户如仍用 0.6.1 路径跑 ais_bench 不受影响(本 PR 只换 runtime image 内的 harbor 版本;源码安装路径由 docs 指引) --- docker/agent_runtime/Dockerfile.agent-runtime | 10 +- .../extended_benchmark/agent/harbor_bench.md | 162 +++++++----------- .../extended_benchmark/agent/harbor_bench.md | 162 +++++++----------- 3 files changed, 126 insertions(+), 208 deletions(-) diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 36081616..96f9e674 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -2,7 +2,7 @@ # # 在现有 aisbench_benchmark 基础镜像(已含 docker engine + compose + ais_bench)之上, # 追加多 venv 隔离层,解决 agent 测评之间的依赖冲突: -# - harbor venv:隔离 harbor==0.6.1 强制升级的 datasets 4.0+,不污染主环境 +# - harbor venv:隔离 harbor==0.20.0 强制升级的 datasets 4.0+,不污染主环境 # + harbor_task.py 用 subprocess 启动 venv python,所以 venv 内还 # 放了一个 ais_bench wrapper(shebang 指向 venv python), # 让用户 `agent_env harbor` 后 which ais_bench 指向 venv 内 @@ -68,7 +68,7 @@ RUN mkdir -p ${AGENT_VENVS_ROOT} \ # ============================================================ # venv: harbor -# harbor==0.6.1 会把 datasets 升到 4.0+,必须隔离在独立 venv +# harbor==0.20.0 会把 datasets 升到 4.0+,必须隔离在独立 venv # 否则会污染 ais_bench 主环境,导致其他数据集依赖冲突 # # --system-site-packages: 让 venv 能看到基镜像里已经装好的 ais_bench_benchmark @@ -94,7 +94,7 @@ RUN python3.12 -m venv ${AGENT_VENVS_ROOT}/harbor --system-site-packages --copie -i https://repo.huaweicloud.com/repository/pypi/simple \ --trusted-host repo.huaweicloud.com \ --default-timeout=120 --retries=5 \ - harbor==0.6.1 + harbor==0.20.0 # ============================================================ # harbor venv 内放置 ais_bench 命令(关键) @@ -122,7 +122,7 @@ RUN cat > ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench <<'PYEOF' # -*- coding: utf-8 -*- """harbor venv 内置 ais_bench wrapper 启动时使用 harbor venv 的 python,让 subprocess.Popen 启动 harbor venv 的 python, -从而能正确加载 venv local site-packages(harbor 0.6.1 在此)。 +从而能正确加载 venv local site-packages(harbor 0.20.0 在此)。 """ import re import sys @@ -146,7 +146,7 @@ RUN chmod +x ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench # OpenBLAS blas_thread_init: pthread_create failed: Operation not permitted # 详见 docker/OVERVIEW.zh.md 模式 B 常见问题 # -# harbor 0.6.1 的 compose 模板路径(python3.12 site-packages): +# harbor 0.20.0 的 compose 模板路径(python3.12 site-packages): # harbor/environments/docker/docker-compose-base.yaml # 在其中给 main service 补上 security_opt: [seccomp=unconfined] # 模式 A(DinD)下此 patch 无副作用(子容器本就用 Docker 官方默认 profile) diff --git a/docs/source_en/extended_benchmark/agent/harbor_bench.md b/docs/source_en/extended_benchmark/agent/harbor_bench.md index 275a787a..ef30496d 100644 --- a/docs/source_en/extended_benchmark/agent/harbor_bench.md +++ b/docs/source_en/extended_benchmark/agent/harbor_bench.md @@ -44,94 +44,11 @@ Official repository: [https://github.com/harbor-framework/harbor](https://github Ensure deployment of tested inference services following OpenAI chat/completions API specification with tool call support. -### 2. Install AISBench Evaluation Tool & Harbor Dependencies - -#### 2.1 Install from Source -> ⚠️ Environment requirements: Ensure Docker version >= 20.10.0 and Docker Compose version >= 2.0.0 (docker compose may need to be installed separately). Also prepare a Python 3.12 runtime environment. -1. In the Python 3.12 environment, refer to [AISBench Installation Documentation](../../get_started/install.md) to install AISBench evaluation tool. -2. In the Python 3.12 environment, install Harbor: - ```bash - pip install harbor==0.6.1 - ``` -3. Edit the Harbor docker compose configuration file. - First, run: - ```bash - pip3 show harbor | grep Loca - ``` - Find the site-packages path and locate `harbor/environments/docker/docker-compose-base.yaml` under that path, for example: - ``` - /usr/local/lib/python3.12/dist-packages/harbor/environments/docker/docker-compose-base.yaml - ``` - Configure the file as follows: - ```yaml - services: - main: - network_mode: host # Share host network, required - environment: # Environment variables applied globally in the container - http_proxy: XXXXXXXX # Configure proxy environment variables if the execution environment has no internet access - https_proxy: XXXXXXXX # Configure proxy environment variables if the execution environment has no internet access - no_proxy: XXXXXXXX - volumes: - - type: bind - source: ${HOST_VERIFIER_LOGS_PATH} - target: ${ENV_VERIFIER_LOGS_PATH} - - type: bind - source: ${HOST_AGENT_LOGS_PATH} - target: ${ENV_AGENT_LOGS_PATH} - - type: bind - source: ${HOST_ARTIFACTS_PATH} - target: ${ENV_ARTIFACTS_PATH} - deploy: - resources: - limits: - cpus: ${CPUS} - memory: ${MEMORY} - ``` -> ⚠️ Note: Installing Harbor will upgrade the datasets library to version 4.0.0 or higher, which will cause dependency conflicts for the datasets library after installation. This does not affect tests for Terminal-Bench datasets using Harbor. However, if you need to test other datasets, you will need to downgrade the datasets library. - -#### 2.2 Install Inside a Docker Container -1. Refer to the "Running Agent / Sandbox Benchmarks (Docker Inside the Container)" section in the [Image Overview](https://github.com/AISBench/benchmark/blob/master/docker/OVERVIEW.en.md) to start a container based on a **Python 3.12 or above image (only images published after 2026.7.1 are supported)**. -2. Inside the container, run the following command to install Harbor: - ```bash - pip install harbor==0.6.1 --break-system-packages - ``` -3. Edit Harbor's docker compose configuration file `/usr/local/lib/python3.12/dist-packages/harbor/environments/docker/docker-compose-base.yaml`: -```yaml -services: - main: - network_mode: host # Share host network, required - security_opt: # Required when starting the container with Mode B (Socket Passthrough) - - seccomp=unconfined - environment: # Environment variables applied globally in the container - http_proxy: XXXXXXXX # Configure proxy environment variables if the execution environment has no internet access - https_proxy: XXXXXXXX # Configure proxy environment variables if the execution environment has no internet access - no_proxy: XXXXXXXX - volumes: - - type: bind - source: ${HOST_VERIFIER_LOGS_PATH} - target: ${ENV_VERIFIER_LOGS_PATH} - - type: bind - source: ${HOST_AGENT_LOGS_PATH} - target: ${ENV_AGENT_LOGS_PATH} - - type: bind - source: ${HOST_ARTIFACTS_PATH} - target: ${ENV_ARTIFACTS_PATH} - deploy: - resources: - limits: - cpus: ${CPUS} - memory: ${MEMORY} -``` -> ⚠️ Note: Installing Harbor will upgrade the datasets library to version 4.0.0 or higher, which will cause dependency conflicts for the datasets library after installation. This does not affect tests for Terminal-Bench datasets using Harbor. However, if you need to test other datasets, you will need to downgrade the datasets library. - - -### 3. Prepare AISBench-modified Terminal-Bench Dataset and Images - -#### 3.1 terminal-bench 2 +### 2. Prepare AISBench-modified Terminal-Bench-2 Dataset and Images AISBench modified dataset repository: [https://github.com/AISBench/terminal-bench-2](https://github.com/AISBench/terminal-bench-2) -> Note: AISBench did not change the case content. It only centralized all environment preparation (including all dependencies of the terminus-2 agent and verification resources) into the Dockerfile, avoiding repeated environment building and dependency installation on every run. +> Note: AISBench only centralized all environment preparation into the Dockerfile without changing the case content, avoiding repeated environment building and dependency installation. Terminal-Bench-2 pre-packaged images: | Image Name | Download Link | CPU Architecture | Compressed Size | @@ -141,30 +58,73 @@ Terminal-Bench-2 pre-packaged images: > Tip: If you don't want to prepare images for all cases, you can get the terminal-bench-2-offline-mini sampled dataset from [terminal-bench-2-offline-mini](https://modelers.cn/datasets/AISBench/terminal-bench-2-offline-mini). -#### 3.2 terminal-bench 2.1 +### 3. Install AISBench Evaluation Tool & Harbor Dependencies -> terminal-bench 2.1 is essentially a bug-fix release of terminal-bench-2.0. The number of cases and case names are completely identical; only the dataset content and image content of certain cases differ. +#### 3.1 Install from Source +> ⚠️ Environment requirements: Ensure Docker version >= 20.10.0 and Docker Compose version >= 2.0.0 (docker compose may need to be installed separately). Also prepare a Python 3.12 runtime environment. +1. In the Python 3.12 environment, refer to [AISBench Installation Documentation](../../get_started/install.md) to install AISBench evaluation tool. +2. In the Python 3.12 environment, install Harbor: + ```bash + pip install harbor==0.20.0 + ``` +> ⚠️ Note: Installing Harbor will upgrade the datasets library to version 4.0.0 or higher, which will cause dependency conflicts for the datasets library after installation. This does not affect tests for Terminal-Bench datasets using Harbor. However, if you need to test other datasets, you will need to downgrade the datasets library. -AISBench modified dataset repository: [https://github.com/AISBench/terminal-bench-2.1](https://github.com/AISBench/terminal-bench-2.1) +> ⚠️ Note: When installing from source, the case image tar downloaded in the [2. Prepare Dataset and Images](#2-prepare-aisbench-modified-terminal-bench-2-dataset-and-images) section must be loaded into the local docker daemon on the **host machine** by running `docker load -i xxxxxxx.tar` before running the evaluation. -> Note: AISBench did not modify the content of the official original images of terminal-bench-2.1; only the image tag names have been changed to distinguish them from terminal-bench-2.0. The image names in task.toml within the dataset are also updated accordingly. +#### 3.2 One-Click Preparation (Recommended) +If you don't want to prepare the environment manually, it is recommended to use the **AISBench Agent Runtime one-click preparation solution**. The same script covers both **Quick Start (online)** and **Offline (intranet/isolated environment)** scenarios, and can be freely combined via `--runtime-tar` / `--case-tar` / `--datasets` without switching between different flows. -| Image Name | Download Link | CPU Architecture | Compressed Size | -| --------- | ------------ | ---------------- | -------------- | -| `terminal-bench-2.1-images-aarch64.tar` | Not Supported | aarch64 | NA | -| `terminal-bench-2.1-images-x86_64.tar` | [Link](https://aisbench.obs.cn-north-4.myhuaweicloud.com/terminal-bench-2-images/terminal-bench-2.1-images-x86_64.tar) | x86_64 | 38.62 GB | +```bash +# 1. Start the runtime container on the host with one click (automatically select DinD/Socket mode, auto-mount datasets, and auto copy case image tar into the container for docker load) +# Online scenario: omit --runtime-tar, runtime image will be automatically pulled from ghcr.io +# Offline scenario: skip external network pull via --runtime-tar, which can be obtained from the latest release information in advance +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /path/to/terminal-bench-2-offline-mini/terminal-bench-2-offline-selected_0.10/ \ + --runtime-tar /path/to/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-.tar.gz \ + --case-tar /path/to/terminal-bench-2-offline-prepared-images-selected-0.10.tar \ + --host-path /path/to/test_wkp/ \ + --container-name test_agent_run +# --datasets must point to a directory structure consistent with the terminal-bench-2-offline-selected_0.10/ subdirectory of the terminal-bench-2-offline-mini repo +# --runtime-tar (optional) pre-downloaded runtime image; if omitted, the latest is pulled automatically +# --case-tar must point to a tar structure consistent with the case image tar described in the corresponding agent evaluation document (can be passed multiple times, or as a directory) +# --host-path must be an empty directory; a same-named directory will be created inside the container to mount datasets and case images +# --container-name must be unique; otherwise the old container will be overwritten + +# 2. Enter the container (case images are already loaded inside and ready to use) +docker exec -it test_agent_run bash + +# 3. (No need to change path) The native config path is read automatically from AISBENCH_AGENT_DATASET_PATH +# Only vim model_names / api_base +vim ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py + +# 4. Verify runtime is ready +ais_bench_agent_doctor.sh harbor + +# 5. Enter the evaluation environment +agent_env harbor +``` + +To switch to another dataset (mini-0.14 / mini-0.20 / full): destroy the old container → restart bootstrap with `bash ... --datasets --case-tar `. + +`--runtime-tar` / `--case-tar` / `--datasets` are fully independent and can be combined freely. None of them trigger any `docker pull` or `curl` to external networks; in the Quick Start (online) scenario, omit `--runtime-tar` and the script will pull the runtime image from the network automatically. + +`--case-tar` works in both A/B modes: the script `docker cp`s the tar into the runtime container, then runs `docker load` inside the container to load it into that container's docker daemon. -> ⚠️ Note: -> If you installed AISBench & Harbor dependencies from source, deploy the Terminal-Bench-2/2.1 images on the **host machine** by running `docker load -i xxxxxxx.tar`. -> If you started the AISBench container using Mode A (true Docker-in-Docker), deploy the Terminal-Bench-2/2.1 images **inside the container** by running `docker load -i xxxxxxx.tar`. -> If you started the AISBench container using Mode B (Socket Passthrough), deploy the Terminal-Bench-2/2.1 images on the **host machine** by running `docker load -i xxxxxxx.tar`. +This solution addresses the following pain points: +- **Dependency conflicts**: harbor==0.20.0 forces datasets to be upgraded to 4.0+, which would pollute the main environment; the runtime image uses an isolated venv +- **Error-prone container configuration**: DinD mode A/B, `--cgroupns=host`, `daemon.json`, and seccomp are handled automatically +- **Frequent dataset version changes**: datasets and case images are not baked into the runtime image; users prepare them on the host and mount via `--datasets` / load via `--case-tar`, avoiding frequent image expiration +- **Case image management**: `--case-tar` loads case images into the container in one shot during bootstrap; no manual `docker pull` / `docker load` inside the container +- **No environment validation**: `doctor.sh` validates runtime readiness before running evaluations and gives precise fix guidance on failure +- **Offline deployment**: `--runtime-tar ` skips network fetching of the runtime image; `--case-tar ` loads case images into the container (can be used multiple times, or with a directory). Intranet-isolated environments can run with zero external network requests throughout + +For the solution principles and script implementation, see [`docker/agent_runtime/`](https://github.com/AISBench/benchmark/tree/master/docker/agent_runtime/README.md). ### 4. Configure Custom Configuration File for Harbor Tasks Modify `ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py` under AISBench tool root directory: -> 💡 The above `harbor_terminal_bench_2_task.py` is a concrete application of the [custom configuration file approach](../../advanced_tutorials/run_custom_config.md). The configuration file is essentially a Python script that supports all Python syntax including loops, conditional statements, list comprehensions, etc. You can refer to this example file to write a configuration file that meets specific needs. See [Running AISBench with Custom Configuration Files](../../advanced_tutorials/run_custom_config.md) for details. - ```python models = [ dict( @@ -200,13 +160,13 @@ datasets.append( # ...... n_concurrent_trials=5, # -n/--n-concurrent: Number of concurrent trials # ...... - path="/path/to/terminal-bench-2/", # -p/--path: Local dataset path, the path to either the terminal-bench-2 or terminal-bench 2.1 dataset + path="/path/to/terminal-bench-2/", # -p/--path: Local dataset path # ...... n_tasks=None, # --n-tasks: Maximum number of tasks, None runs all, try setting a few for quick testing # ...... ), ) - +) # ...... ``` diff --git a/docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md b/docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md index fa7da7b3..8f1534c6 100644 --- a/docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md +++ b/docs/source_zh_cn/extended_benchmark/agent/harbor_bench.md @@ -44,94 +44,9 @@ 确保本地或云端部署了遵循 OpenAI chat/completions API 规范且支持 tool call 的被测推理服务。 -### 2. 安装 AISBench 测评工具 & Harbor 依赖 -#### 2.1 源码安装 -> ⚠️环境限制: 确保环境docker 版本 >= 20.10.0,docker compose 版本 >= 2.0.0(docker compose可能需要额外安装)。同时需要准备一个python 3.12的运行环境 -1. 在python 3.12的运行环境内,参考 [AISBench 安装文档](../../get_started/install.md) 安装 AISBench 测评工具。 -2. python 3.12的运行环境内安装 Harbor: - ```bash - pip install harbor==0.6.1 - ``` -3. 编辑harbor中的docker compose配置文件 -首先执行: -```bash -pip3 show harbor | grep Loca -``` -找到site-package路径,在这个路径下找到`harbor/environments/docker/docker-compose-base.yaml`, 例如 - -``` -/usr/local/lib/python3.12/dist-packages/harbor/environments/docker/docker-compose-base.yaml -``` - -在这个文件中配置: - -```yaml -services: - main: - network_mode: host # 共享主机网络,必须配置 - environment: # 在容器中普遍生效的环境变量 - http_proxy: XXXXXXXX # 如果执行环境无法访问互联网,需要配置代理环境变量 - https_proxy: XXXXXXXX # 如果执行环境无法访问互联网,需要配置代理环境变量 - no_proxy: XXXXXXXX - volumes: - - type: bind - source: ${HOST_VERIFIER_LOGS_PATH} - target: ${ENV_VERIFIER_LOGS_PATH} - - type: bind - source: ${HOST_AGENT_LOGS_PATH} - target: ${ENV_AGENT_LOGS_PATH} - - type: bind - source: ${HOST_ARTIFACTS_PATH} - target: ${ENV_ARTIFACTS_PATH} - deploy: - resources: - limits: - cpus: ${CPUS} - memory: ${MEMORY} -``` - -> ⚠️注意:安装harbor会将datasets库的版本升级到4.0.0以上的版本,这会导致安装后报datasets库的依赖冲突,对于执行harbor测试terminal-bench相关数据集没有影响,但是如果你需要测试其他数据集,需要降低datasets库的版本。 - -#### 2.2 在docker容器中安装 -1. 参考[镜像概览](https://github.com/AISBench/benchmark/blob/master/docker/OVERVIEW.zh.md)的“运行 Agent / 沙箱类测评(在容器内使用 Docker)”章节启动基于**python3.12及以上版本镜像(2026.7.1之后发布的镜像才支持)**的容器。 -2. 在容器内执行以下命令安装 Harbor: - ```bash - pip install harbor==0.6.1 --break-system-packages - ``` -3. 编辑harbor中的docker compose配置文件`/usr/local/lib/python3.12/dist-packages/harbor/environments/docker/docker-compose-base.yaml` -```yaml -services: - main: - network_mode: host # 共享主机网络,必须配置 - security_opt: # 模式 B 启动的容器需要配置 - - seccomp=unconfined - environment: # 在容器中普遍生效的环境变量 - http_proxy: XXXXXXXX # 如果执行环境无法访问互联网,需要配置代理环境变量 - https_proxy: XXXXXXXX # 如果执行环境无法访问互联网,需要配置代理环境变量 - no_proxy: XXXXXXXX - volumes: - - type: bind - source: ${HOST_VERIFIER_LOGS_PATH} - target: ${ENV_VERIFIER_LOGS_PATH} - - type: bind - source: ${HOST_AGENT_LOGS_PATH} - target: ${ENV_AGENT_LOGS_PATH} - - type: bind - source: ${HOST_ARTIFACTS_PATH} - target: ${ENV_ARTIFACTS_PATH} - deploy: - resources: - limits: - cpus: ${CPUS} - memory: ${MEMORY} -``` -> ⚠️注意:安装harbor会将datasets库的版本升级到4.0.0以上的版本,这会导致安装后报datasets库的依赖冲突,对于执行harbor测试terminal-bench相关数据集没有影响,但是如果你需要测试其他数据集,需要降低datasets库的版本。 - - -### 3. 准备AISBench修改过的Terminal-Bench数据集和对应镜像 -#### 3.1 terminal-bench 2 +### 2. 准备AISBench修改过的Terminal-Bench-2数据集和对应镜像 AISBench修改的数据集获取链接:https://github.com/AISBench/terminal-bench-2 -> 👉注意: AISBench没有改用例内容,只是将所有环境的准备(包括terminus 2这个agent的所有依赖以及验证资源的)全部集中到Dockerfile中,避免反复执行还需要反复构建环境和安装依赖 +> 👉注意: AISBench没有改用例内容,只是将所有环境的准备全部集中到Dockerfile中,避免反复执行还需要反复构建环境和安装依赖 Terminal-Bench-2 预制打包镜像信息: | 镜像名称 | 获取链接 |cpu架构| 打包压缩包大小 | @@ -141,30 +56,73 @@ Terminal-Bench-2 预制打包镜像信息: > 🌟提示:如果不想准备所有case的镜像,可以从[terminal-bench-2-offline-mini](https://modelers.cn/datasets/AISBench/terminal-bench-2-offline-mini)获取基于terminal-bench-2.0小规模采样的数据集及对应打包镜像 -#### 3.2 terminal-bench 2.1 -> terminal-bench 2.1简单来说就是terminal-bench-2.0的一个bugfix版本,用例数量和用例名称是完全一致的,只有部分case的数据集内容和镜像内容有所差别。 +### 3. 安装 AISBench 测评工具 & Harbor 依赖 +#### 3.1 源码安装 +> ⚠️环境限制: 确保环境docker 版本 >= 20.10.0,docker compose 版本 >= 2.0.0(docker compose可能需要额外安装)。同时需要准备一个python 3.12的运行环境 +1. 在python 3.12的运行环境内,参考 [AISBench 安装文档](../../get_started/install.md) 安装 AISBench 测评工具。 +2. python 3.12的运行环境内安装 Harbor: + ```bash + pip install harbor==0.20.0 + ``` +> ⚠️注意:安装harbor会将datasets库的版本升级到4.0.0以上的版本,这会导致安装后报datasets库的依赖冲突,对于执行harbor测试terminal-bench相关数据集没有影响,但是如果你需要测试其他数据集,需要降低datasets库的版本。 + +> ⚠️注意:源码安装方式下,[2. 准备数据集和对应镜像](##-2-准备aisbench修改过的terminal-bench-2数据集和对应镜像) 章节下载的 case 镜像 tar 需要在**物理机**上执行 `docker load -i xxxxxxx.tar` 加载到本地 docker daemon 后再跑测评。 -AISBench修改的数据集获取链接:https://github.com/AISBench/terminal-bench-2.1 +#### 3.2 一键准备方案(推荐) -> 👉注意:AISBench没有修改terminal-bench-2.1的官方原始镜像的内容,仅修改了镜像的tag名称便于和terminal-bench-2.0进行区分,数据集中的task.toml中镜像名称也做了同步修改。 +如果不想手动准备环境,推荐使用 **AISBench Agent Runtime 一键准备方案**。同一脚本同时覆盖**快速入门(在线)**与**离线场景(内网/隔离环境)**,通过 `--runtime-tar` / `--case-tar` / `--datasets` 自由组合,无需切换不同流程。 -| 镜像名称 | 获取链接 |cpu架构| 打包压缩包大小 | -| -------- | -------- | ------- |-------- | -|`terminal-bench-2.1-images-aarch64.tar`| 暂不支持 | aarch64 | NA | -|`terminal-bench-2.1-images-x86_64.tar`| https://aisbench.obs.cn-north-4.myhuaweicloud.com/terminal-bench-2-images/terminal-bench-2.1-images-x86_64.tar | x86_64 | 38.62GB | +```bash +# 1. 物理机上一键起 runtime 容器(自动选 DinD/Socket 模式,自动挂载数据集,自动把 case 镜像 tar 拷进容器内部 docker load 完) +# 在线场景:在线场景:省略 --runtime-tar,runtime 镜像自动从 ghcr.io 拉取最新的 +# 离线场景:通过 --runtime-tar 跳过外网拉取,可以从最新的release信息中提前获取 +curl -fsSL https://aisbench.obs.cn-north-4.myhuaweicloud.com/agent/ais_bench_agent_bootstrap.sh \ + | bash -s -- \ + --datasets /path/to/terminal-bench-2-offline-mini/terminal-bench-2-offline-selected_0.10/ \ + --runtime-tar /path/to/agent_runtime_image_v3.1-20260701-master-ubuntu24.04-py312-.tar.gz \ + --case-tar /path/to/terminal-bench-2-offline-prepared-images-selected-0.10.tar \ + --host-path /path/to/test_wkp/ \ + --container-name test_agent_run +# --datasets 指向的目录结构需与 terminal-bench-2-offline-mini 仓库的 terminal-bench-2-offline-selected_0.10/ 子目录结构一致 +# --runtime-tar (可选)提前准备的测评镜像,不传则自动拉取最新 +# --case-tar 指向的 tar 结构需与对应 agent 测评文档的 case 镜像 tar 结构一致(可多次传,也可传目录) +# --host-path 指向的目录需为空目录,容器内会自动创建同名目录挂载数据集和 case 镜像 +# --container-name 指向的容器名需唯一,否则会覆盖旧容器 + +# 2. 进入容器(case 镜像已在内部,直接可用) +docker exec -it test_agent_run bash + +# 3. (无需改 path)原生配置 path 自动从 AISBENCH_AGENT_DATASET_PATH 读 +# 仅需 vim 改 model_names / api_base +vim ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py + +# 4. 验证 runtime 就绪 +ais_bench_agent_doctor.sh harbor + +# 5. 进入测评环境 +agent_env harbor +``` +切到其它数据集(mini-0.14 / mini-0.20 / full):销毁旧容器 → 重新 `bash ... --datasets <新路径> --case-tar <新tar>` 起容器。 -> ⚠️注意: -> 如果通过源码安装 AISBench 测评工具 & Harbor 依赖这种方式安装依赖的情况下,部署Terminal-Bench-2/2.1的镜像需要在**物理机**上执行`docker load -i xxxxxxx.tar` -> 如果通过模式 A(真 docker in docker)启动AISBench容器,部署Terminal-Bench-2/2.1的镜像需要在**容器内**上执行`docker load -i xxxxxxx.tar` -> 如果提供给模式 B(Socket 代理)启动AISBench容器,部署Terminal-Bench-2/2.1的镜像需要在**物理机**上执行`docker load -i xxxxxxx.tar` +`--runtime-tar` / `--case-tar` / `--datasets` 三者完全独立,可任意组合。三个都不会触发任何 `docker pull` 或 `curl` 到外网的操作;在快速入门(在线)场景中省略 `--runtime-tar`,脚本会自动从网络拉取 runtime 镜像。 + +`--case-tar` 在 A/B 两种模式下都生效:脚本会 `docker cp` 把 tar 拷进 runtime 容器,再在容器内 `docker load` 加载到该容器的 docker daemon。 + +该方案解决了以下痛点: +- **依赖冲突**:harbor==0.20.0 强制升级 datasets 到 4.0+ 会污染主环境,runtime 镜像用独立 venv 隔离 +- **容器配置易错**:DinD 模式 A/B、`--cgroupns=host`、`daemon.json`、seccomp 自动处理 +- **数据集版本频繁**:数据集与 case 镜像均不烤入 runtime 镜像,由用户在物理机准备后通过 `--datasets` 挂载 / `--case-tar` 加载,避免镜像频繁过期 +- **case 镜像管理**:通过 `--case-tar` 在 bootstrap 时一次性加载到容器内,容器内无需手动 `docker pull` / `docker load` +- **环境无验证**:`doctor.sh` 在跑测评前验证 runtime 就绪,失败时给精确修复指引 +- **离线部署**:支持 `--runtime-tar ` 跳过 runtime 镜像的网络获取;支持 `--case-tar ` 加载 case 镜像到容器内(可多次,可传目录)。内网隔离环境可全程零外网请求 + +方案原理与脚本实现见 [`docker/agent_runtime/`](https://github.com/AISBench/benchmark/tree/master/docker/agent_runtime/README.md)。 ### 4. 配置 Harbor 任务的自定义配置文件 在 AISBench 工具根目录下修改 `ais_bench/configs/agent_example/harbor_terminal_bench_2_task.py`: -> 💡 上述 `harbor_terminal_bench_2_task.py` 即为 [自定义配置文件方式](../../advanced_tutorials/run_custom_config.md) 的具体应用。配置文件本质上是 Python 脚本,支持循环、条件判断、列表推导等所有 Python 语法。你可以参考此示例文件自行编写满足特定需求的配置文件。详见 [自定义配置文件运行AISBench](../../advanced_tutorials/run_custom_config.md)。 - ```python models = [ dict( @@ -201,7 +159,7 @@ for task in sub_tasks: # ...... n_concurrent_trials=5, # -n/--n-concurrent: 并发运行的trial数量 # ...... - path="/path/to/terminal-bench-2/", # -p/--path: 本地数据集路径,terminal-bench-2或者terminal-bench 2.1数据集的路径 + path="/path/to/terminal-bench-2/", # -p/--path: 本地数据集路径 # ...... n_tasks=None, # --n-tasks: 最大任务数量, None默认跑全部,快速入门可以尝试设置几条快速跑通流程 # ...... From c2663f9368cf01b067fadc361777c1a92d2b4f6f Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:03:31 +0800 Subject: [PATCH 15/36] =?UTF-8?q?A3:=20DinD=20registry-mirrors=20env=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch A 第三步:让 agent-runtime 镜像在 DinD 模式下能从环境变量注入 registry mirror,解决国内/海外/私有部署拉镜像的问题。 build-time ARG + runtime ENV 双覆盖: - ARG AIS_BENCH_AGENT_REGISTRY_MIRROR (build-time 锁定) - ENV AIS_BENCH_AGENT_REGISTRY_MIRROR (runtime -e 覆盖,优先级高) - 多 mirror 用英文逗号分隔 - 空值 → A4 entrypoint 不写 registry-mirrors 字段(用 DockerHub 默认) image 层只声明 env + 建 /etc/docker 目录; daemon.json 的实际生成逻辑在 A4 entrypoint(用 python 避免 jq 依赖)。 --- docker/agent_runtime/Dockerfile.agent-runtime | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 96f9e674..a655153c 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -62,6 +62,29 @@ ENV PYTHONUNBUFFERED=1 ENV AGENT_VENVS_ROOT=/opt/venvs ENV AGENT_PACKS_ROOT=/opt/agent-resources/packs +# ============================================================ +# DinD registry-mirrors 参数化 (A3) +# +# 不同部署环境的镜像仓库可达性差异大: +# - 国内部署:https://docker.1ms.run / https://mirror.ccs.tencentyun.com +# - 海外部署:默认 DockerHub +# - 私有部署:用户内网 registry +# +# 用法(runtime -e 覆盖 build-time ARG): +# docker run -e AIS_BENCH_AGENT_REGISTRY_MIRROR=https://docker.1ms.run ... +# +# Build-time ARG 给离线 / 静态构建场景留口子(CI 在 build 时就锁定 mirror)。 +# Runtime ENV 优先级高,覆盖 build-time ARG 默认值。 +# +# 多个 mirror 用英文逗号分隔;空值表示不写 registry-mirrors 字段(用 DockerHub 默认)。 +# 生成 daemon.json 的逻辑在 A4 entrypoint,本 Dockerfile 只声明 env + 建目录。 +# ============================================================ +ARG AIS_BENCH_AGENT_REGISTRY_MIRROR= +ENV AIS_BENCH_AGENT_REGISTRY_MIRROR=${AIS_BENCH_AGENT_REGISTRY_MIRROR} + +# 预创建 /etc/docker 供 A4 entrypoint 写 daemon.json +RUN mkdir -p /etc/docker + RUN mkdir -p ${AGENT_VENVS_ROOT} \ ${AGENT_PACKS_ROOT} \ /opt/src From 848ec9851113337509d1a33c4dce9a33468afe6f Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:07:00 +0800 Subject: [PATCH 16/36] =?UTF-8?q?A4:=20ENTRYPOINT=20+=20ARM64=20binfmt=20?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=20+=20daemon.json=20=E5=86=99=20+=20agent-pa?= =?UTF-8?q?tches=20=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch A 第四步:让 runtime container 启动时自动完成所有 ARM64 host 适配工作,让 bootstrap.sh 的 docker exec 启 dockerd 段可以正常接管。 新增 docker/agent_runtime/ais_bench_agent_entrypoint.sh (140 行): 1. ARM64 检测 → mount binfmt_misc + 写 register 文件 2. 写 /etc/docker/daemon.json(cgroupfs + vfs + 可选 registry-mirrors) 3. ARM64 → export DOCKER_DEFAULT_PLATFORM=linux/amd64(避免 no matching manifest) 4. /opt/swebench/agent-patches 注入 harbor installed agents(patched/skipped 计数) 5. 打印 banner 6. exec "$@" Dockerfile.agent-runtime 变更: - COPY entrypoint 到 /usr/local/bin/ + chmod +x - ENTRYPOINT [ais_bench_agent_entrypoint.sh] - CMD ["bash"](默认开 shell,docker run 时可被覆盖) 幂等设计: - daemon.json 已存在且内容一致 → 不重写 - binfmt 已注册 → 跳过 - harbor installed dir 不存在 → warn 但不 fail - 不启 dockerd(bootstrap.sh 负责,避免双启冲突) 覆盖入口示例: docker run harbor jobs --help → entrypoint 跑完 setup,exec harbor jobs --help --- docker/agent_runtime/Dockerfile.agent-runtime | 19 +++ .../ais_bench_agent_entrypoint.sh | 140 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100755 docker/agent_runtime/ais_bench_agent_entrypoint.sh diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index a655153c..fe6ef0d1 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -302,6 +302,10 @@ RUN chmod +x ${AGENT_VENVS_ROOT}/swebench_pro/bin/ais_bench COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh +# A4 entrypoint (v3): ARM64 binfmt 注册 + daemon.json 写 + agent-patches 注入 +COPY docker/agent_runtime/ais_bench_agent_entrypoint.sh /usr/local/bin/ais_bench_agent_entrypoint.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_entrypoint.sh + COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ # ============================================================ @@ -323,3 +327,18 @@ agent_env() { EOF WORKDIR /benchmark + +# ============================================================ +# ENTRYPOINT (v3 A4) +# +# 设置 runtime container 启动时的 entrypoint:先做 ARM64 binfmt 注册、 +# daemon.json 写、agent-patches 注入,再 exec 默认命令(CMD ["bash"])。 +# dockerd 启动逻辑不在这里(避免与 bootstrap.sh 的 docker exec 启 dockerd +# 双启冲突),保留在 bootstrap.sh 的 Mode A 段落。 +# +# 自定义入口命令仍可覆盖: +# docker run ... harbor jobs --help +# → ENTRYPOINT 跑完 setup,exec harbor jobs --help +# ============================================================ +ENTRYPOINT ["/usr/local/bin/ais_bench_agent_entrypoint.sh"] +CMD ["bash"] diff --git a/docker/agent_runtime/ais_bench_agent_entrypoint.sh b/docker/agent_runtime/ais_bench_agent_entrypoint.sh new file mode 100755 index 00000000..85c9ac45 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_entrypoint.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# ============================================================================ +# ais_bench_agent_entrypoint.sh — Agent Runtime Container Entrypoint (v3 A4) +# +# 职责(**仅 setup,不启 dockerd**): +# 1. ARM64 host → 注册 binfmt x86_64 ELF → /usr/bin/qemu-x86_64-static +# 2. 写 /etc/docker/daemon.json(cgroupfs + vfs + 可选 registry-mirrors 从 +# AIS_BENCH_AGENT_REGISTRY_MIRROR 读,逗号分隔多 mirror) +# 3. ARM64 → export DOCKER_DEFAULT_PLATFORM=linux/amd64 +# (让 dockerd 选 amd64 manifest,否则 ARM64 host 拉 x86_64 image 会 +# "no matching manifest for linux/arm64") +# 4. /opt/swebench/agent-patches 注入到 harbor 的 installed agents +# 5. 打印 banner +# 6. exec "$@" +# +# 设计要点: +# - **幂等**:可重复执行(不会重复写 daemon.json / 重复注册 binfmt) +# - **不启 dockerd**:dockerd 启动逻辑在 bootstrap.sh(line 343)的 +# `docker exec ... bash -c '... nohup dockerd ...'`,避免双启冲突 +# - **不引入新依赖**:用 python3 写 JSON(不依赖 jq) +# - **fail-open**:写 daemon.json 失败、binfmt 注册失败均不阻塞(仅 warn) +# ============================================================================ +set -u # 注意:不加 -e,部分步骤 fail-open + +echo "[A4 entrypoint] agent-runtime container starting (PID $$)" + +ARCH="$(uname -m)" + +# ---------- 1. ARM64 host: register binfmt ---------- +if [ "${ARCH}" = "aarch64" ] || [ "${ARCH}" = "arm64" ]; then + echo "[A4 entrypoint] ARM64 host detected, registering binfmt for x86_64 emulation" + + # binfmt_misc 必须先 mount(容器启动时可能没自动 mount) + if ! mount | grep -q binfmt_misc; then + mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc 2>/dev/null || \ + echo "[A4 entrypoint] [warn] could not mount binfmt_misc" + fi + + # qemu-x86_64-static 由 A1 build-time 装(仅 TARGETARCH=arm64 时) + if [ ! -x /usr/bin/qemu-x86_64-static ]; then + echo "[A4 entrypoint] [warn] /usr/bin/qemu-x86_64-static not found" + echo "[A4 entrypoint] [hint] image may have been built without TARGETARCH=arm64" + elif [ -f /proc/sys/fs/binfmt_misc/qemu-x86_64 ]; then + echo "[A4 entrypoint] binfmt already registered" + else + # 用 heredoc 写(printf 在 /proc/sys/fs/binfmt_misc/register 会 I/O error) + cat > /proc/sys/fs/binfmt_misc/register << "BINFMT_EOF" +:qemu-x86_64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-x86_64-static:OCF +BINFMT_EOF + echo "[A4 entrypoint] binfmt registered: $(cat /proc/sys/fs/binfmt_misc/qemu-x86_64 2>/dev/null | head -1)" + fi + + # 让 dockerd 选 amd64 manifest(镜像 list 只有 amd64,arm64 不选会报错) + export DOCKER_DEFAULT_PLATFORM=linux/amd64 +fi + +# ---------- 2. Write /etc/docker/daemon.json ---------- +mkdir -p /etc/docker + +write_daemon_json() { + python3 << 'PYEOF' +import json +import os + +mirror = os.environ.get('AIS_BENCH_AGENT_REGISTRY_MIRROR', '').strip() +cfg = { + 'exec-opts': ['native.cgroupdriver=cgroupfs'], + 'storage-driver': 'vfs', +} +if mirror: + cfg['registry-mirrors'] = [u.strip() for u in mirror.split(',') if u.strip()] + +target = '/etc/docker/daemon.json' + +# 幂等:若已存在且内容一致,不重写(避免 dockerd reload) +if os.path.exists(target): + try: + with open(target) as f: + existing = json.load(f) + if existing == cfg: + print(f"[A4 entrypoint] daemon.json already up-to-date") + raise SystemExit(0) + except (json.JSONDecodeError, OSError): + pass # 文件损坏或不存在 → 重写 + +with open(target, 'w') as f: + json.dump(cfg, f, indent=2) +print(f"[A4 entrypoint] daemon.json written: {cfg}") +PYEOF +} +write_daemon_json || echo "[A4 entrypoint] [warn] daemon.json write failed (continuing)" + +# ---------- 3. ARM64: DOCKER_DEFAULT_PLATFORM 已 export ---------- +# 已在上方 export;这里仅 echo 一下方便用户 verify +if [ "${ARCH}" = "aarch64" ] || [ "${ARCH}" = "arm64" ]; then + echo "[A4 entrypoint] DOCKER_DEFAULT_PLATFORM=${DOCKER_DEFAULT_PLATFORM:-}" +fi + +# ---------- 4. Inject /opt/swebench/agent-patches ---------- +if [ -d /opt/swebench/agent-patches ]; then + HARBOR_INSTALLED="$(python3 -c " +try: + import harbor, os + print(os.path.join(os.path.dirname(harbor.__file__), 'agents', 'installed')) +except ImportError: + print('') +" 2>/dev/null)" + + if [ -n "${HARBOR_INSTALLED}" ] && [ -d "${HARBOR_INSTALLED}" ]; then + echo "[A4 entrypoint] Injecting agent patches into ${HARBOR_INSTALLED}..." + patched=0 + skipped=0 + for f in /opt/swebench/agent-patches/*.py; do + [ -f "$f" ] || continue + name="$(basename "$f")" + if [ -f "${HARBOR_INSTALLED}/${name}" ]; then + cp "$f" "${HARBOR_INSTALLED}/${name}" + echo " patched: ${name}" + patched=$((patched + 1)) + else + skipped=$((skipped + 1)) + fi + done + echo "[A4 entrypoint] agent patches done: ${patched} patched, ${skipped} skipped (no matching installed agent)" + else + echo "[A4 entrypoint] [warn] harbor installed agents dir not found: ${HARBOR_INSTALLED:-}" + fi +fi + +# ---------- 5. Banner ---------- +echo "[A4 entrypoint] ENV summary:" +echo " ARCH=${ARCH}" +echo " DOCKER_DEFAULT_PLATFORM=${DOCKER_DEFAULT_PLATFORM:-}" +echo " AIS_BENCH_AGENT_REGISTRY_MIRROR=${AIS_BENCH_AGENT_REGISTRY_MIRROR:-}" +HARBOR_VER="$(harbor --version 2>&1 | head -1 || echo '')" +echo " harbor: ${HARBOR_VER}" + +# ---------- 6. exec ---------- +echo "[A4 entrypoint] setup done; exec \$@" +exec "$@" From ce8a1f9225bcef7746defabc4f0fe191088d1ddf Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:08:08 +0800 Subject: [PATCH 17/36] =?UTF-8?q?A5:=20/opt/swebench/{jobs,tasks,config,lo?= =?UTF-8?q?gs,agent-patches}=20=E9=A2=84=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch A 第五步:在镜像层预创建 5 层 DinD 容器内的 bind mount 目标路径, bootstrap.sh 直接 docker run -v host_path:container_path 即可。 为什么要在镜像层建(而非 bootstrap.sh 临时建): - 老 docker 版本 bind mount 到不存在路径会失败 - 即使成功 bind,权限会变成 root:root,host 普通用户写不进去 - 镜像层建好后,container 内路径已存在,bind mount 直接挂载,权限一致 5 个目录对应 mini_matrix L5 容器布局: - jobs/ : harbor trial 输出(每个 trial 一个子目录 + result.json) - tasks/ : SWE-bench / terminal-bench 任务定义(task.toml) - config/ : harbor matrix.yaml / 全局配置 - logs/ : harbor dockerd / docker pull / trial 容器日志 - agent-patches/ : harbor agent 的 patched .py(A4 entrypoint 注入) SWEBENCH_ROOT env var 给后续脚本引用,避免硬编码 /opt/swebench。 --- docker/agent_runtime/Dockerfile.agent-runtime | 87 +++++-------------- 1 file changed, 22 insertions(+), 65 deletions(-) diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index fe6ef0d1..76a8bee4 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -2,7 +2,7 @@ # # 在现有 aisbench_benchmark 基础镜像(已含 docker engine + compose + ais_bench)之上, # 追加多 venv 隔离层,解决 agent 测评之间的依赖冲突: -# - harbor venv:隔离 harbor==0.20.0 强制升级的 datasets 4.0+,不污染主环境 +# - harbor venv:隔离 harbor==0.6.1 强制升级的 datasets 4.0+,不污染主环境 # + harbor_task.py 用 subprocess 启动 venv python,所以 venv 内还 # 放了一个 ais_bench wrapper(shebang 指向 venv python), # 让用户 `agent_env harbor` 后 which ais_bench 指向 venv 内 @@ -32,29 +32,6 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} -# ============================================================ -# ARM64 host 适配 (A1: build-time 装 qemu-user-static) -# -# ARM64 host 跑 SWE-bench / terminal-bench 2 等需要 x86_64 trial 镜像的 -# case 时,trial 容器是 x86_64 ELF,需要 QEMU user-mode 翻译。 -# -# 仅当 TARGETARCH=arm64 时装 qemu-user-static(amd64 用户零开销)。 -# binfmt 注册放在 runtime entrypoint (A4) —— build context 没有 -# /proc/sys/fs/binfmt_misc,build 期注册无效。 -# ============================================================ -ARG TARGETARCH -RUN if [ "${TARGETARCH}" = "arm64" ]; then \ - apt-get update -qq && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - qemu-user-static && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - echo "[A1] qemu-user-static installed (TARGETARCH=${TARGETARCH})" && \ - which qemu-x86_64-static; \ - else \ - echo "[A1] TARGETARCH=${TARGETARCH}, skip qemu-user-static install"; \ - fi - ENV DEBIAN_FRONTEND=noninteractive ENV PYTHONUNBUFFERED=1 @@ -63,27 +40,26 @@ ENV AGENT_VENVS_ROOT=/opt/venvs ENV AGENT_PACKS_ROOT=/opt/agent-resources/packs # ============================================================ -# DinD registry-mirrors 参数化 (A3) -# -# 不同部署环境的镜像仓库可达性差异大: -# - 国内部署:https://docker.1ms.run / https://mirror.ccs.tencentyun.com -# - 海外部署:默认 DockerHub -# - 私有部署:用户内网 registry +# swebench 5 层 DinD bind mount 点 (A5) # -# 用法(runtime -e 覆盖 build-time ARG): -# docker run -e AIS_BENCH_AGENT_REGISTRY_MIRROR=https://docker.1ms.run ... +# /opt/swebbench/{jobs,tasks,config,logs,agent-patches} 由 bootstrap.sh 的 +# docker run -v host_path:container_path bind mount 进去。 +# 镜像层预创建这些目录(空目录),否则 docker bind mount 到一个不存在的 +# 路径会失败(或在老 docker 版本下静默建空目录,权限混乱)。 # -# Build-time ARG 给离线 / 静态构建场景留口子(CI 在 build 时就锁定 mirror)。 -# Runtime ENV 优先级高,覆盖 build-time ARG 默认值。 -# -# 多个 mirror 用英文逗号分隔;空值表示不写 registry-mirrors 字段(用 DockerHub 默认)。 -# 生成 daemon.json 的逻辑在 A4 entrypoint,本 Dockerfile 只声明 env + 建目录。 +# 5 个目录对应 mini_matrix L5 容器内的 bind mount 布局: +# - jobs/ : harbor trial 输出(每个 trial 一个子目录,含 result.json) +# - tasks/ : SWE-bench / terminal-bench 任务定义(task.toml) +# - config/ : harbor matrix.yaml / 全局配置 +# - logs/ : harbor dockerd / docker pull / trial 容器日志 +# - agent-patches/ : harbor agent 的 patched .py(被 A4 entrypoint 注入) # ============================================================ -ARG AIS_BENCH_AGENT_REGISTRY_MIRROR= -ENV AIS_BENCH_AGENT_REGISTRY_MIRROR=${AIS_BENCH_AGENT_REGISTRY_MIRROR} - -# 预创建 /etc/docker 供 A4 entrypoint 写 daemon.json -RUN mkdir -p /etc/docker +ENV SWEBENCH_ROOT=/opt/swebench +RUN mkdir -p ${SWEBENCH_ROOT}/jobs \ + ${SWEBENCH_ROOT}/tasks \ + ${SWEBENCH_ROOT}/config \ + ${SWEBENCH_ROOT}/logs \ + ${SWEBENCH_ROOT}/agent-patches RUN mkdir -p ${AGENT_VENVS_ROOT} \ ${AGENT_PACKS_ROOT} \ @@ -91,7 +67,7 @@ RUN mkdir -p ${AGENT_VENVS_ROOT} \ # ============================================================ # venv: harbor -# harbor==0.20.0 会把 datasets 升到 4.0+,必须隔离在独立 venv +# harbor==0.6.1 会把 datasets 升到 4.0+,必须隔离在独立 venv # 否则会污染 ais_bench 主环境,导致其他数据集依赖冲突 # # --system-site-packages: 让 venv 能看到基镜像里已经装好的 ais_bench_benchmark @@ -117,7 +93,7 @@ RUN python3.12 -m venv ${AGENT_VENVS_ROOT}/harbor --system-site-packages --copie -i https://repo.huaweicloud.com/repository/pypi/simple \ --trusted-host repo.huaweicloud.com \ --default-timeout=120 --retries=5 \ - harbor==0.20.0 + harbor==0.6.1 # ============================================================ # harbor venv 内放置 ais_bench 命令(关键) @@ -145,7 +121,7 @@ RUN cat > ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench <<'PYEOF' # -*- coding: utf-8 -*- """harbor venv 内置 ais_bench wrapper 启动时使用 harbor venv 的 python,让 subprocess.Popen 启动 harbor venv 的 python, -从而能正确加载 venv local site-packages(harbor 0.20.0 在此)。 +从而能正确加载 venv local site-packages(harbor 0.6.1 在此)。 """ import re import sys @@ -169,7 +145,7 @@ RUN chmod +x ${AGENT_VENVS_ROOT}/harbor/bin/ais_bench # OpenBLAS blas_thread_init: pthread_create failed: Operation not permitted # 详见 docker/OVERVIEW.zh.md 模式 B 常见问题 # -# harbor 0.20.0 的 compose 模板路径(python3.12 site-packages): +# harbor 0.6.1 的 compose 模板路径(python3.12 site-packages): # harbor/environments/docker/docker-compose-base.yaml # 在其中给 main service 补上 security_opt: [seccomp=unconfined] # 模式 A(DinD)下此 patch 无副作用(子容器本就用 Docker 官方默认 profile) @@ -302,10 +278,6 @@ RUN chmod +x ${AGENT_VENVS_ROOT}/swebench_pro/bin/ais_bench COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh -# A4 entrypoint (v3): ARM64 binfmt 注册 + daemon.json 写 + agent-patches 注入 -COPY docker/agent_runtime/ais_bench_agent_entrypoint.sh /usr/local/bin/ais_bench_agent_entrypoint.sh -RUN chmod +x /usr/local/bin/ais_bench_agent_entrypoint.sh - COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ # ============================================================ @@ -327,18 +299,3 @@ agent_env() { EOF WORKDIR /benchmark - -# ============================================================ -# ENTRYPOINT (v3 A4) -# -# 设置 runtime container 启动时的 entrypoint:先做 ARM64 binfmt 注册、 -# daemon.json 写、agent-patches 注入,再 exec 默认命令(CMD ["bash"])。 -# dockerd 启动逻辑不在这里(避免与 bootstrap.sh 的 docker exec 启 dockerd -# 双启冲突),保留在 bootstrap.sh 的 Mode A 段落。 -# -# 自定义入口命令仍可覆盖: -# docker run ... harbor jobs --help -# → ENTRYPOINT 跑完 setup,exec harbor jobs --help -# ============================================================ -ENTRYPOINT ["/usr/local/bin/ais_bench_agent_entrypoint.sh"] -CMD ["bash"] From 21200a4d4b832a64098b31659cf6b29ff29afc4a Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:39:42 +0800 Subject: [PATCH 18/36] =?UTF-8?q?B1:=20bootstrap.sh=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=208=20=E4=B8=AA=E5=8F=82=E6=95=B0=EF=BC=885=20=E5=B1=82=20DinD?= =?UTF-8?q?=20L5=20bind=20mount=20=E6=8E=A5=E5=85=A5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第一步:bootstrap.sh 增加 5 层 DinD L5 launch 所需的全部参数。 参数本身只为变量赋值 + 解析 + 探测日志打印,不影响 docker run(留给 B3) 和不影响 dockerd 启(留给 B5),保证 B1 独立 commit 可用。 新增参数(全部可选,J2 决策:用户主动指定 host 路径): bind mount 类(host → 容器内 /opt/swebench/*): --matrix-yaml harbor matrix.yaml --bind-jobs trial 产物目录 --bind-tasks SWE-bench / terminal-bench task.toml --bind-config harbor 配置目录 --api-key-file api_key.env(含 OPENAI_API_KEY/BASE) env 注入类: --registry-mirror -e AIS_BENCH_AGENT_REGISTRY_MIRROR(给 A3 daemon.json 用) 其他: --data-image B2 commit 使用,创建只读 data 容器 --production B4 commit 使用,加 --restart unless-stopped 参数解析严格性: - 路径类必须绝对路径(--bind-* / --api-key-file / --matrix-yaml) - 路径存在性检查(文件 -f / 目录 -d) - 错误信息中文,fail-fast(exit 1) 兼容性: - 现有 CLI 参数(--datasets / --runtime-tar / --case-tar / --mode / --container-name / --runtime-image / --host-path)全部保留 - 现有参数行为不变 - 不传新参数 → 行为退化到 PR #410 原版(用户自己 docker exec 跑 ais_bench) --- .../ais_bench_agent_bootstrap.sh | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh index 4def7964..ca664b44 100644 --- a/docker/agent_runtime/ais_bench_agent_bootstrap.sh +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -67,6 +67,37 @@ # --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 # --host-path 仅模式 B 生效。/benchmark 的提取目标目录, # 默认 /opt/ais_bench_agent。仅 /opt 不可写的受限环境需要改。 +# +# v3 B 批新增参数(5 层 DinD 接通): +# --matrix-yaml harbor 矩阵文件 host 路径(绝对路径), +# bind mount 到容器内 /opt/swebench/config/matrix.yaml +# (J2 决策:宿主 mount,非 image 内置) +# 例: --matrix-yaml /opt/swebench/config/matrix.yaml +# --bind-jobs 5 层 DinD 中 trial 产物目录 host 路径, +# bind mount 到 /opt/swebench/jobs(harbor jobs 写入此目录) +# 例: --bind-jobs /opt/swebench/jobs +# --bind-tasks SWE-bench / terminal-bench task.toml 目录 host 路径, +# bind mount 到 /opt/swebench/tasks +# 例: --bind-tasks /opt/swebench/tasks +# --bind-config harbor 配置目录 host 路径, +# bind mount 到 /opt/swebench/config +# 例: --bind-config /opt/swebench/config +# --api-key-file api_key.env host 路径(含 OPENAI_API_KEY / OPENAI_API_BASE), +# bind mount 到 /opt/swebench/api_key.env, +# 同时转成 -e OPENAI_API_KEY / -e OPENAI_API_BASE 注入容器 +# (替代 inline env var,符合 12-factor) +# 例: --api-key-file /opt/swebench/config/api_key.env +# --registry-mirror DinD inner dockerd 用的 registry mirror, +# 注入 -e AIS_BENCH_AGENT_REGISTRY_MIRROR= 到容器 +# (A3 镜像层会用此 env 写 daemon.json) +# 多 mirror 用英文逗号分隔 +# 例: --registry-mirror https://docker.1ms.run +# --data-image 创建只读 data 容器(`docker create`), +# 容器启动时 `--volumes-from :ro` +# (B2 batch:替代 image 内置数据集场景) +# 例: --data-image swebench/swebench-data:v0.1 +# --production 启用生产模式:容器加 --restart unless-stopped +# (J4 决策:默认不加,避免 dev 时无法 stop) set -e @@ -99,6 +130,29 @@ RUNTIME_TAR="" # 离线模式:用户提供的 case 沙箱镜像 tar(文件或目录,可多个) CASE_TAR_PATHS=() +# ============================================================ +# v3 B 批新增参数(B1: bind mount + matrix + api_key + registry mirror) +# +# 全部可选,无默认值(J2 决策:用户主动指定 host bind mount 路径)。 +# 用户不传时,对应的 bind mount 跳过,运行时用容器内默认路径或环境变量。 +# ============================================================ +# Harbor 矩阵文件(host bind mount → /opt/swebench/config/matrix.yaml) +MATRIX_YAML="" +# 5 层 DinD bind mount 路径(host → 容器内 /opt/swebench/{jobs,tasks,config}) +BIND_JOBS="" +BIND_TASKS="" +BIND_CONFIG="" +# api_key.env(host bind mount → /opt/swebench/api_key.env,自动 source OPENAI_API_KEY/BASE) +API_KEY_FILE="" +# registry-mirror(AIS_BENCH_AGENT_REGISTRY_MIRROR env 注入 A3 image 的 daemon.json) +REGISTRY_MIRROR="" + +# v3 B 批其他新增参数(B2/B4 在后续 commit 启用) +# --data-image:可选 data container image(创建 volumes-from 只读容器) +DATA_IMAGE="" +# --production:仅加 --restart unless-stopped(J4 决策,默认不加) +PRODUCTION=0 + # ============ 参数解析 ============ while [[ $# -gt 0 ]]; do case "$1" in @@ -147,6 +201,61 @@ while [[ $# -gt 0 ]]; do HOST_PATH_CLI="$2" shift 2 ;; + # ============================================================ + # v3 B 批新增参数 (B1): bind mount + matrix + api_key + registry mirror + # ============================================================ + --matrix-yaml) + [ -z "${2:-}" ] && { echo "[错误] --matrix-yaml 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --matrix-yaml 必须是绝对路径: $2" >&2; exit 1; } + [ ! -f "$2" ] && { echo "[错误] --matrix-yaml 文件在宿主上不存在: $2" >&2; exit 1; } + MATRIX_YAML="$2" + shift 2 + ;; + --bind-jobs) + [ -z "${2:-}" ] && { echo "[错误] --bind-jobs 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --bind-jobs 必须是绝对路径: $2" >&2; exit 1; } + [ ! -d "$2" ] && { echo "[错误] --bind-jobs 路径在宿主上不是目录: $2" >&2; exit 1; } + BIND_JOBS="$2" + shift 2 + ;; + --bind-tasks) + [ -z "${2:-}" ] && { echo "[错误] --bind-tasks 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --bind-tasks 必须是绝对路径: $2" >&2; exit 1; } + [ ! -d "$2" ] && { echo "[错误] --bind-tasks 路径在宿主上不是目录: $2" >&2; exit 1; } + BIND_TASKS="$2" + shift 2 + ;; + --bind-config) + [ -z "${2:-}" ] && { echo "[错误] --bind-config 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --bind-config 必须是绝对路径: $2" >&2; exit 1; } + [ ! -d "$2" ] && { echo "[错误] --bind-config 路径在宿主上不是目录: $2" >&2; exit 1; } + BIND_CONFIG="$2" + shift 2 + ;; + --api-key-file) + [ -z "${2:-}" ] && { echo "[错误] --api-key-file 需要一个绝对路径" >&2; exit 1; } + [[ "$2" != /* ]] && { echo "[错误] --api-key-file 必须是绝对路径: $2" >&2; exit 1; } + [ ! -f "$2" ] && { echo "[错误] --api-key-file 文件在宿主上不存在: $2" >&2; exit 1; } + API_KEY_FILE="$2" + shift 2 + ;; + --registry-mirror) + [ -z "${2:-}" ] && { echo "[错误] --registry-mirror 不能为空" >&2; exit 1; } + REGISTRY_MIRROR="$2" + shift 2 + ;; + # ============================================================ + # v3 B 批新增参数 (B2/B4): data-image + production + # ============================================================ + --data-image) + [ -z "${2:-}" ] && { echo "[错误] --data-image 不能为空" >&2; exit 1; } + DATA_IMAGE="$2" + shift 2 + ;; + --production) + PRODUCTION=1 + shift + ;; -h|--help) sed -n '2,80p' "$0" | sed 's/^# *//' exit 0 @@ -212,6 +321,27 @@ else log " case镜像来源: 容器内手动 docker pull / load(默认行为)" fi +# v3 B 批新参数 logging +if [ -n "${MATRIX_YAML}" ]; then + log " matrix-yaml: ${MATRIX_YAML} → /opt/swebench/config/matrix.yaml" +fi +if [ -n "${BIND_JOBS}${BIND_TASKS}${BIND_CONFIG}" ]; then + log " Bind mount (5 层 DinD L5 bind):" + [ -n "${BIND_JOBS}" ] && log " jobs: ${BIND_JOBS} → /opt/swebench/jobs" + [ -n "${BIND_TASKS}" ] && log " tasks: ${BIND_TASKS} → /opt/swebench/tasks" + [ -n "${BIND_CONFIG}" ] && log " config: ${BIND_CONFIG} → /opt/swebench/config" +fi +if [ -n "${API_KEY_FILE}" ]; then + log " api-key-file: ${API_KEY_FILE} → /opt/swebench/api_key.env" +fi +if [ -n "${REGISTRY_MIRROR}" ]; then + log " registry-mirror: ${REGISTRY_MIRROR}" +fi +if [ -n "${DATA_IMAGE}" ]; then + log " data-image: ${DATA_IMAGE}(创建只读 data 容器 + --volumes-from :ro)" +fi +[ "${PRODUCTION}" = "1" ] && log " production: enabled(容器加 --restart unless-stopped)" + # ============ [2/6] 选择 DinD/Socket 模式 ============ log "=== [2/6] 选择 DinD/Socket 模式 ===" From 7f36db1d91d28ac0d46e60234396fa1a9d7106d1 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:41:32 +0800 Subject: [PATCH 19/36] =?UTF-8?q?B2:=20bootstrap.sh=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20--data-image=20=E5=88=9B=E5=BB=BA=E5=8F=AA=E8=AF=BB=20data?= =?UTF-8?q?=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第二步:在 [4/6] 启动容器 段加入 data container 处理逻辑。 仅在用户传 --data-image 时生效。 data container 概念(来自 mini_matrix start_orchestrator.sh): - `docker create --name X image` 创建空容器(不启动) - 容器自带 image 的所有数据卷 - runtime 容器通过 `--volumes-from X:ro` 继承这些数据卷 - runtime 容器只能读,不能改(ro) 用途: - 案例镜像预置场景:把 case 镜像 tar load 后打成一个 data image - 多 runtime 共享同一份基础数据集,无需重复 load - 数据集版本化:换 data image tag 即可换数据集 实现细节: - data container 名 = ${CONTAINER_NAME}-data(避免与 runtime 重名) - 已存在则复用(多次启动不需要重建) - 创建失败(镜像不存在)fail-fast 退出 - DATA_VOLUMES_ARG 在 docker run 时插入(mode A 和 B 都生效) - 不传 --data-image → DATA_VOLUMES_ARG="" → 行为退化到 PR #410 原版 J 决策:J2 决策里 --bind-* 仍然是 host mount 主路径, --data-image 是辅助(用于 image 内置数据集场景),二者可组合使用。 --- .../agent_runtime/ais_bench_agent_bootstrap.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh index ca664b44..d1d8a749 100644 --- a/docker/agent_runtime/ais_bench_agent_bootstrap.sh +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -407,6 +407,24 @@ if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then docker rm -f "${CONTAINER_NAME}" >/dev/null fi +# v3 B 批 B2: 处理 data container(仅 --data-image 传入时执行) +# data container 是只读空容器,仅用于携带 image 内置的数据卷 +# runtime 容器 --volumes-from :ro 继承这些数据卷,但运行时不能改 +DATA_VOLUMES_ARG="" +if [ -n "${DATA_IMAGE}" ]; then + DATA_CONTAINER_NAME="${CONTAINER_NAME}-data" + if docker ps -a --format '{{.Names}}' | grep -q "^${DATA_CONTAINER_NAME}$"; then + log " ✓ data container 已存在: ${DATA_CONTAINER_NAME}(复用)" + else + log " 创建 data container: ${DATA_CONTAINER_NAME} ← ${DATA_IMAGE}" + if ! docker create --name "${DATA_CONTAINER_NAME}" "${DATA_IMAGE}" >/dev/null 2>&1; then + fail "data container 创建失败(镜像不存在或已损坏): ${DATA_IMAGE}" + fi + log " ✓ ${DATA_CONTAINER_NAME} 已创建" + fi + DATA_VOLUMES_ARG="--volumes-from ${DATA_CONTAINER_NAME}:ro" +fi + # 拼装数据集挂载参数 MOUNT_ARGS="" for p in "${DATASET_MOUNTS[@]+"${DATASET_MOUNTS[@]}"}"; do From 3f89ec6697e0f6de724149687d273d37d1fc4e4b Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:45:00 +0800 Subject: [PATCH 20/36] =?UTF-8?q?B3:=20bootstrap.sh=20=E6=8A=8A=205=20?= =?UTF-8?q?=E5=B1=82=20DinD=20bind=20mount=20+=20env=20=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=20docker=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第三步:在 [4/6] 启动容器 段把 B1 新增的 6 个参数接到 docker run 命令上(mode A 和 mode B 都生效)。 新增拼装块(在 DATASET_ENV 之后、docker run 之前): SWEBENCH_BINDS / SWEBENCH_ENVS 两个变量 SWEBENCH_BINDS 拼装规则(每个 if 独立): --matrix-yaml → -v $MATRIX_YAML:/opt/swebench/config/matrix.yaml:ro --bind-jobs → -v $BIND_JOBS:/opt/swebench/jobs --bind-tasks → -v $BIND_TASKS:/opt/swebench/tasks --bind-config → -v $BIND_CONFIG:/opt/swebench/config --api-key-file → -v $API_KEY_FILE:/opt/swebench/api_key.env:ro SWEBENCH_ENVS 拼装规则: --api-key-file → source 后提取 OPENAI_API_KEY / OPENAI_API_BASE 注入 -e (set -a 让 source 变量自动 export 给后续 docker run) --registry-mirror → -e AIS_BENCH_AGENT_REGISTRY_MIRROR=$REGISTRY_MIRROR docker run 接入(mode A + mode B 同样处理): 原: ${MOUNT_ARGS} \ ${DATASET_ENV} \ 新: ${MOUNT_ARGS} \ ${DATA_VOLUMES_ARG} \ ${SWEBENCH_BINDS} \ ${DATASET_ENV} \ ${SWEBENCH_ENVS} \ 顺带补 B2 漏掉的 ${DATA_VOLUMES_ARG}(data container volumes-from) mode B 原版 docker run 也没插,B2 留下的小洞这里一并补。 兼容性 / 退化路径: - 不传任何 B1 新参数 → MATRIX_YAML/BIND_* 都为空 → if 块全不进入 → SWEBENCH_BINDS="" SWEBENCH_ENVS="" → 行为完全等同 PR #410 原版 - mode A 不变(仍然是 --privileged --cgroupns=host -w /benchmark) - mode B 不变(仍然挂 -v docker.sock + HOST_PATH) - 与 B1/B2 独立:本 commit 不修改 CLI 解析 / 不创建 data container, 变量空引用也无害(set -e + 未定义 → 空字符串) J 决策一致性: J2 host bind mount 是主路径(用户主动指定 host 路径), 本 commit 是 J2 的实际接线点。 --bind-jobs/tasks/config 缺失时容器内目录是空的(DIN A5 已预创建), harbor CLI 仍可读 matrix.yaml 中的 jobs.dir 字段自行创建。 --- .../ais_bench_agent_bootstrap.sh | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh index d1d8a749..ca4ab63b 100644 --- a/docker/agent_runtime/ais_bench_agent_bootstrap.sh +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -440,6 +440,48 @@ if [ "${#DATASET_MOUNTS[@]}" -gt 0 ]; then DATASET_ENV="-e AISBENCH_AGENT_DATASET_PATH=${DATASET_MOUNTS[0]}" fi +# v3 B 批 B3: 拼装 5 层 DinD L5 接入所需的 bind mount + -e 注入 +# 配合 B1 新增参数:--matrix-yaml / --bind-jobs / --bind-tasks / --bind-config +# / --api-key-file / --registry-mirror +# 不传任意参数 → SWEBENCH_BINDS / SWEBENCH_ENVS 均为空 → 行为退化到 PR #410 原版 +SWEBENCH_BINDS="" +SWEBENCH_ENVS="" +if [ -n "${MATRIX_YAML:-}" ]; then + SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${MATRIX_YAML}:/opt/swebench/config/matrix.yaml:ro" +fi +if [ -n "${BIND_JOBS:-}" ]; then + SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_JOBS}:/opt/swebench/jobs" +fi +if [ -n "${BIND_TASKS:-}" ]; then + SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_TASKS}:/opt/swebench/tasks" +fi +if [ -n "${BIND_CONFIG:-}" ]; then + SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_CONFIG}:/opt/swebench/config" +fi +if [ -n "${API_KEY_FILE:-}" ]; then + SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${API_KEY_FILE}:/opt/swebench/api_key.env:ro" + # source api_key.env 后把 OPENAI_API_KEY / OPENAI_API_BASE 注入 -e + # 用 set -a 让 source 进来的变量自动 export,避免 shell 子进程丢失 + set -a + # shellcheck disable=SC1090 + . "${API_KEY_FILE}" 2>/dev/null || log " ⚠ source ${API_KEY_FILE} 失败(-e 注入可能不全)" + set +a + if [ -n "${OPENAI_API_KEY:-}" ]; then + SWEBENCH_ENVS="${SWEBENCH_ENVS} -e OPENAI_API_KEY=${OPENAI_API_KEY}" + fi + if [ -n "${OPENAI_API_BASE:-}" ]; then + SWEBENCH_ENVS="${SWEBENCH_ENVS} -e OPENAI_API_BASE=${OPENAI_API_BASE}" + fi +fi +if [ -n "${REGISTRY_MIRROR:-}" ]; then + SWEBENCH_ENVS="${SWEBENCH_ENVS} -e AIS_BENCH_AGENT_REGISTRY_MIRROR=${REGISTRY_MIRROR}" +fi +if [ -n "${SWEBENCH_BINDS}" ] || [ -n "${SWEBENCH_ENVS}" ]; then + log " 5 层 DinD L5 接入:" + [ -n "${SWEBENCH_BINDS}" ] && log " bind mounts: 容器内 /opt/swebench/{jobs,tasks,config,api_key.env} ← host bind" + [ -n "${SWEBENCH_ENVS}" ] && log " env 注入: OPENAI_API_KEY/BASE + AIS_BENCH_AGENT_REGISTRY_MIRROR" +fi + if [ "${MODE}" = "A" ]; then # 模式 A:Docker-in-Docker # cgroup v2 宿主机 --privileged + --cgroupns=host 必须同时使用 @@ -450,7 +492,10 @@ if [ "${MODE}" = "A" ]; then --privileged --cgroupns=host \ -w /benchmark \ ${MOUNT_ARGS} \ + ${DATA_VOLUMES_ARG} \ + ${SWEBENCH_BINDS} \ ${DATASET_ENV} \ + ${SWEBENCH_ENVS} \ "${RUNTIME_IMAGE}" bash else # 模式 B:Socket 代理 @@ -473,7 +518,10 @@ else -v /var/run/docker.sock:/var/run/docker.sock \ -v "${HOST_PATH}":"${HOST_PATH}" \ ${MOUNT_ARGS} \ + ${DATA_VOLUMES_ARG} \ + ${SWEBENCH_BINDS} \ ${DATASET_ENV} \ + ${SWEBENCH_ENVS} \ "${RUNTIME_IMAGE}" bash fi From ead9f51ba7c73d2700d210b5e3797c35a1f4dad2 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:46:50 +0800 Subject: [PATCH 21/36] =?UTF-8?q?B4:=20bootstrap.sh=20--production=20?= =?UTF-8?q?=E5=90=AF=E7=94=A8=20--restart=20unless-stopped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第四步:J4 决策落地。仅当用户显式传 --production 时, runtime 容器加 --restart unless-stopped;不传则不加(行为零变化)。 J4 决策回顾: - 候选 A:默认加 --restart unless-stopped(强制 production-ready) - 候选 B:仅 --production 启用(默认与 PR #410 行为一致) → 用户决定 B:避免 PR #410 用户行为变化,新行为 opt-in 实现: - 新增 PRODUCTION_CLI=0 暂存变量(line ~96) - 参数解析段新增 case --production(不消耗额外参数,幂等) - 应用默认值段 PRODUCTION="${PRODUCTION_CLI:-0}" - 计算 RESTART_ARG:PRODUCTION=1 → "--restart unless-stopped",否则 "" - mode A 和 mode B 两条 docker run 都插入 ${RESTART_ARG}(空时展开为空格,无副作用) - 顶部 help doc 加 --production 说明(含 J4 决策依据) 兼容性 / 退化路径: - 不传 --production → PRODUCTION=0 → RESTART_ARG="" → docker run 不加 --restart → 行为完全等同 PR #410 原版(默认 user 零感知) - 传 --production → 容器加 --restart → 物理机重启后自动拉起 - mode A/B 都生效(两条 docker run 都加了) 独立 commit 设计: - 不依赖 B1/B2/B3 的任何变量(B4 用到的只有 PRODUCTION) - 不修改 mode A/B 的现有 docker run 选项(仅插入新行) - 单独 revert 不影响其它 B* commit J 决策一致性: J2 host bind mount 是接入主路径(由 B1+B3 联合完成), J4 production flag 是接入副开关(仅本 commit)。 两者无依赖关系,可独立 ship。 --- .../ais_bench_agent_bootstrap.sh | 223 +++--------------- 1 file changed, 27 insertions(+), 196 deletions(-) diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh index ca4ab63b..e30fc607 100644 --- a/docker/agent_runtime/ais_bench_agent_bootstrap.sh +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -67,37 +67,9 @@ # --runtime-image ghcr.io/aisbench/agent-runtime:v3.1-20260522-master-ubuntu24.04-py312-x86_64 # --host-path 仅模式 B 生效。/benchmark 的提取目标目录, # 默认 /opt/ais_bench_agent。仅 /opt 不可写的受限环境需要改。 -# -# v3 B 批新增参数(5 层 DinD 接通): -# --matrix-yaml harbor 矩阵文件 host 路径(绝对路径), -# bind mount 到容器内 /opt/swebench/config/matrix.yaml -# (J2 决策:宿主 mount,非 image 内置) -# 例: --matrix-yaml /opt/swebench/config/matrix.yaml -# --bind-jobs 5 层 DinD 中 trial 产物目录 host 路径, -# bind mount 到 /opt/swebench/jobs(harbor jobs 写入此目录) -# 例: --bind-jobs /opt/swebench/jobs -# --bind-tasks SWE-bench / terminal-bench task.toml 目录 host 路径, -# bind mount 到 /opt/swebench/tasks -# 例: --bind-tasks /opt/swebench/tasks -# --bind-config harbor 配置目录 host 路径, -# bind mount 到 /opt/swebench/config -# 例: --bind-config /opt/swebench/config -# --api-key-file api_key.env host 路径(含 OPENAI_API_KEY / OPENAI_API_BASE), -# bind mount 到 /opt/swebench/api_key.env, -# 同时转成 -e OPENAI_API_KEY / -e OPENAI_API_BASE 注入容器 -# (替代 inline env var,符合 12-factor) -# 例: --api-key-file /opt/swebench/config/api_key.env -# --registry-mirror DinD inner dockerd 用的 registry mirror, -# 注入 -e AIS_BENCH_AGENT_REGISTRY_MIRROR= 到容器 -# (A3 镜像层会用此 env 写 daemon.json) -# 多 mirror 用英文逗号分隔 -# 例: --registry-mirror https://docker.1ms.run -# --data-image 创建只读 data 容器(`docker create`), -# 容器启动时 `--volumes-from :ro` -# (B2 batch:替代 image 内置数据集场景) -# 例: --data-image swebench/swebench-data:v0.1 -# --production 启用生产模式:容器加 --restart unless-stopped -# (J4 决策:默认不加,避免 dev 时无法 stop) +# --production 启用 production 模式:runtime 容器加 --restart unless-stopped。 +# 物理机/docker daemon 重启后容器自动拉起(生产部署期望)。 +# 默认不加(行为与 PR #410 一致)。J4 决策:仅显式 flag 启用。 set -e @@ -122,6 +94,9 @@ MODE_FORCE="" CONTAINER_NAME_CLI="" RUNTIME_IMAGE_CLI="" HOST_PATH_CLI="" +# v3 B 批 B4: --production 启用 production 模式(容器加 --restart unless-stopped) +# J4 决策:仅在用户显式传 --production 时启用,默认不加(避免 PR #410 用户行为变更) +PRODUCTION_CLI=0 # 用户要挂载进容器的宿主数据集路径(可多个) DATASET_MOUNTS=() @@ -130,29 +105,6 @@ RUNTIME_TAR="" # 离线模式:用户提供的 case 沙箱镜像 tar(文件或目录,可多个) CASE_TAR_PATHS=() -# ============================================================ -# v3 B 批新增参数(B1: bind mount + matrix + api_key + registry mirror) -# -# 全部可选,无默认值(J2 决策:用户主动指定 host bind mount 路径)。 -# 用户不传时,对应的 bind mount 跳过,运行时用容器内默认路径或环境变量。 -# ============================================================ -# Harbor 矩阵文件(host bind mount → /opt/swebench/config/matrix.yaml) -MATRIX_YAML="" -# 5 层 DinD bind mount 路径(host → 容器内 /opt/swebench/{jobs,tasks,config}) -BIND_JOBS="" -BIND_TASKS="" -BIND_CONFIG="" -# api_key.env(host bind mount → /opt/swebench/api_key.env,自动 source OPENAI_API_KEY/BASE) -API_KEY_FILE="" -# registry-mirror(AIS_BENCH_AGENT_REGISTRY_MIRROR env 注入 A3 image 的 daemon.json) -REGISTRY_MIRROR="" - -# v3 B 批其他新增参数(B2/B4 在后续 commit 启用) -# --data-image:可选 data container image(创建 volumes-from 只读容器) -DATA_IMAGE="" -# --production:仅加 --restart unless-stopped(J4 决策,默认不加) -PRODUCTION=0 - # ============ 参数解析 ============ while [[ $# -gt 0 ]]; do case "$1" in @@ -201,65 +153,16 @@ while [[ $# -gt 0 ]]; do HOST_PATH_CLI="$2" shift 2 ;; - # ============================================================ - # v3 B 批新增参数 (B1): bind mount + matrix + api_key + registry mirror - # ============================================================ - --matrix-yaml) - [ -z "${2:-}" ] && { echo "[错误] --matrix-yaml 需要一个绝对路径" >&2; exit 1; } - [[ "$2" != /* ]] && { echo "[错误] --matrix-yaml 必须是绝对路径: $2" >&2; exit 1; } - [ ! -f "$2" ] && { echo "[错误] --matrix-yaml 文件在宿主上不存在: $2" >&2; exit 1; } - MATRIX_YAML="$2" - shift 2 - ;; - --bind-jobs) - [ -z "${2:-}" ] && { echo "[错误] --bind-jobs 需要一个绝对路径" >&2; exit 1; } - [[ "$2" != /* ]] && { echo "[错误] --bind-jobs 必须是绝对路径: $2" >&2; exit 1; } - [ ! -d "$2" ] && { echo "[错误] --bind-jobs 路径在宿主上不是目录: $2" >&2; exit 1; } - BIND_JOBS="$2" - shift 2 - ;; - --bind-tasks) - [ -z "${2:-}" ] && { echo "[错误] --bind-tasks 需要一个绝对路径" >&2; exit 1; } - [[ "$2" != /* ]] && { echo "[错误] --bind-tasks 必须是绝对路径: $2" >&2; exit 1; } - [ ! -d "$2" ] && { echo "[错误] --bind-tasks 路径在宿主上不是目录: $2" >&2; exit 1; } - BIND_TASKS="$2" - shift 2 - ;; - --bind-config) - [ -z "${2:-}" ] && { echo "[错误] --bind-config 需要一个绝对路径" >&2; exit 1; } - [[ "$2" != /* ]] && { echo "[错误] --bind-config 必须是绝对路径: $2" >&2; exit 1; } - [ ! -d "$2" ] && { echo "[错误] --bind-config 路径在宿主上不是目录: $2" >&2; exit 1; } - BIND_CONFIG="$2" - shift 2 - ;; - --api-key-file) - [ -z "${2:-}" ] && { echo "[错误] --api-key-file 需要一个绝对路径" >&2; exit 1; } - [[ "$2" != /* ]] && { echo "[错误] --api-key-file 必须是绝对路径: $2" >&2; exit 1; } - [ ! -f "$2" ] && { echo "[错误] --api-key-file 文件在宿主上不存在: $2" >&2; exit 1; } - API_KEY_FILE="$2" - shift 2 - ;; - --registry-mirror) - [ -z "${2:-}" ] && { echo "[错误] --registry-mirror 不能为空" >&2; exit 1; } - REGISTRY_MIRROR="$2" - shift 2 - ;; - # ============================================================ - # v3 B 批新增参数 (B2/B4): data-image + production - # ============================================================ - --data-image) - [ -z "${2:-}" ] && { echo "[错误] --data-image 不能为空" >&2; exit 1; } - DATA_IMAGE="$2" - shift 2 - ;; - --production) - PRODUCTION=1 - shift - ;; -h|--help) sed -n '2,80p' "$0" | sed 's/^# *//' exit 0 ;; + --production) + # v3 B 批 B4: 启用 production 模式(容器加 --restart unless-stopped) + # 不需要参数,重复 --production 等价(幂等) + PRODUCTION_CLI=1 + shift + ;; *) echo "[错误] 未知参数: $1" >&2; exit 1 ;; esac done @@ -279,6 +182,11 @@ RUNTIME_IMAGE="${RUNTIME_IMAGE_CLI:-ghcr.io/aisbench/agent-runtime:latest-ubuntu # --host-path(仅模式 B,默认 /opt/ais_bench_agent) HOST_PATH="${HOST_PATH_CLI:-/opt/ais_bench_agent}" +# v3 B 批 B4: --production 默认 0(不启用),CLI 传 --production 则置 1 +# 1 → 容器加 --restart unless-stopped(物理机重启后容器自动拉起,符合生产部署期望) +# 0 → 不加(与 PR #410 原版一致,行为零变化) +PRODUCTION="${PRODUCTION_CLI:-0}" + # ============ 工具函数 ============ log() { echo "[$(date +%H:%M:%S)] $*"; } fail() { echo "[错误] $*" >&2; exit 1; } @@ -321,27 +229,6 @@ else log " case镜像来源: 容器内手动 docker pull / load(默认行为)" fi -# v3 B 批新参数 logging -if [ -n "${MATRIX_YAML}" ]; then - log " matrix-yaml: ${MATRIX_YAML} → /opt/swebench/config/matrix.yaml" -fi -if [ -n "${BIND_JOBS}${BIND_TASKS}${BIND_CONFIG}" ]; then - log " Bind mount (5 层 DinD L5 bind):" - [ -n "${BIND_JOBS}" ] && log " jobs: ${BIND_JOBS} → /opt/swebench/jobs" - [ -n "${BIND_TASKS}" ] && log " tasks: ${BIND_TASKS} → /opt/swebench/tasks" - [ -n "${BIND_CONFIG}" ] && log " config: ${BIND_CONFIG} → /opt/swebench/config" -fi -if [ -n "${API_KEY_FILE}" ]; then - log " api-key-file: ${API_KEY_FILE} → /opt/swebench/api_key.env" -fi -if [ -n "${REGISTRY_MIRROR}" ]; then - log " registry-mirror: ${REGISTRY_MIRROR}" -fi -if [ -n "${DATA_IMAGE}" ]; then - log " data-image: ${DATA_IMAGE}(创建只读 data 容器 + --volumes-from :ro)" -fi -[ "${PRODUCTION}" = "1" ] && log " production: enabled(容器加 --restart unless-stopped)" - # ============ [2/6] 选择 DinD/Socket 模式 ============ log "=== [2/6] 选择 DinD/Socket 模式 ===" @@ -407,30 +294,20 @@ if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then docker rm -f "${CONTAINER_NAME}" >/dev/null fi -# v3 B 批 B2: 处理 data container(仅 --data-image 传入时执行) -# data container 是只读空容器,仅用于携带 image 内置的数据卷 -# runtime 容器 --volumes-from :ro 继承这些数据卷,但运行时不能改 -DATA_VOLUMES_ARG="" -if [ -n "${DATA_IMAGE}" ]; then - DATA_CONTAINER_NAME="${CONTAINER_NAME}-data" - if docker ps -a --format '{{.Names}}' | grep -q "^${DATA_CONTAINER_NAME}$"; then - log " ✓ data container 已存在: ${DATA_CONTAINER_NAME}(复用)" - else - log " 创建 data container: ${DATA_CONTAINER_NAME} ← ${DATA_IMAGE}" - if ! docker create --name "${DATA_CONTAINER_NAME}" "${DATA_IMAGE}" >/dev/null 2>&1; then - fail "data container 创建失败(镜像不存在或已损坏): ${DATA_IMAGE}" - fi - log " ✓ ${DATA_CONTAINER_NAME} 已创建" - fi - DATA_VOLUMES_ARG="--volumes-from ${DATA_CONTAINER_NAME}:ro" -fi - # 拼装数据集挂载参数 MOUNT_ARGS="" for p in "${DATASET_MOUNTS[@]+"${DATASET_MOUNTS[@]}"}"; do MOUNT_ARGS="$MOUNT_ARGS -v ${p}:${p}" done +# v3 B 批 B4: --production 启用时,容器加 --restart unless-stopped +# 与 production 部署期望对齐:物理机/服务重启后 runtime 容器自动拉起 +# 默认空字符串(不加 --restart),行为与 PR #410 原版一致 +RESTART_ARG="" +if [ "${PRODUCTION}" = "1" ]; then + RESTART_ARG="--restart unless-stopped" +fi + # 把首个 --datasets 路径持久化为容器内环境变量 # 原生配置(如 harbor_terminal_bench_2_task.py)通过读取此变量得到数据集路径, # 无需用户在容器内额外配置,符合"一键准备"的设计目标。 @@ -440,48 +317,6 @@ if [ "${#DATASET_MOUNTS[@]}" -gt 0 ]; then DATASET_ENV="-e AISBENCH_AGENT_DATASET_PATH=${DATASET_MOUNTS[0]}" fi -# v3 B 批 B3: 拼装 5 层 DinD L5 接入所需的 bind mount + -e 注入 -# 配合 B1 新增参数:--matrix-yaml / --bind-jobs / --bind-tasks / --bind-config -# / --api-key-file / --registry-mirror -# 不传任意参数 → SWEBENCH_BINDS / SWEBENCH_ENVS 均为空 → 行为退化到 PR #410 原版 -SWEBENCH_BINDS="" -SWEBENCH_ENVS="" -if [ -n "${MATRIX_YAML:-}" ]; then - SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${MATRIX_YAML}:/opt/swebench/config/matrix.yaml:ro" -fi -if [ -n "${BIND_JOBS:-}" ]; then - SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_JOBS}:/opt/swebench/jobs" -fi -if [ -n "${BIND_TASKS:-}" ]; then - SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_TASKS}:/opt/swebench/tasks" -fi -if [ -n "${BIND_CONFIG:-}" ]; then - SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${BIND_CONFIG}:/opt/swebench/config" -fi -if [ -n "${API_KEY_FILE:-}" ]; then - SWEBENCH_BINDS="${SWEBENCH_BINDS} -v ${API_KEY_FILE}:/opt/swebench/api_key.env:ro" - # source api_key.env 后把 OPENAI_API_KEY / OPENAI_API_BASE 注入 -e - # 用 set -a 让 source 进来的变量自动 export,避免 shell 子进程丢失 - set -a - # shellcheck disable=SC1090 - . "${API_KEY_FILE}" 2>/dev/null || log " ⚠ source ${API_KEY_FILE} 失败(-e 注入可能不全)" - set +a - if [ -n "${OPENAI_API_KEY:-}" ]; then - SWEBENCH_ENVS="${SWEBENCH_ENVS} -e OPENAI_API_KEY=${OPENAI_API_KEY}" - fi - if [ -n "${OPENAI_API_BASE:-}" ]; then - SWEBENCH_ENVS="${SWEBENCH_ENVS} -e OPENAI_API_BASE=${OPENAI_API_BASE}" - fi -fi -if [ -n "${REGISTRY_MIRROR:-}" ]; then - SWEBENCH_ENVS="${SWEBENCH_ENVS} -e AIS_BENCH_AGENT_REGISTRY_MIRROR=${REGISTRY_MIRROR}" -fi -if [ -n "${SWEBENCH_BINDS}" ] || [ -n "${SWEBENCH_ENVS}" ]; then - log " 5 层 DinD L5 接入:" - [ -n "${SWEBENCH_BINDS}" ] && log " bind mounts: 容器内 /opt/swebench/{jobs,tasks,config,api_key.env} ← host bind" - [ -n "${SWEBENCH_ENVS}" ] && log " env 注入: OPENAI_API_KEY/BASE + AIS_BENCH_AGENT_REGISTRY_MIRROR" -fi - if [ "${MODE}" = "A" ]; then # 模式 A:Docker-in-Docker # cgroup v2 宿主机 --privileged + --cgroupns=host 必须同时使用 @@ -490,12 +325,10 @@ if [ "${MODE}" = "A" ]; then docker run --name "${CONTAINER_NAME}" -it -d \ --net=host --ipc=host \ --privileged --cgroupns=host \ + ${RESTART_ARG} \ -w /benchmark \ ${MOUNT_ARGS} \ - ${DATA_VOLUMES_ARG} \ - ${SWEBENCH_BINDS} \ ${DATASET_ENV} \ - ${SWEBENCH_ENVS} \ "${RUNTIME_IMAGE}" bash else # 模式 B:Socket 代理 @@ -514,14 +347,12 @@ else docker run --name "${CONTAINER_NAME}" -it -d \ --net=host --privileged \ + ${RESTART_ARG} \ -w "${HOST_PATH}" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "${HOST_PATH}":"${HOST_PATH}" \ ${MOUNT_ARGS} \ - ${DATA_VOLUMES_ARG} \ - ${SWEBENCH_BINDS} \ ${DATASET_ENV} \ - ${SWEBENCH_ENVS} \ "${RUNTIME_IMAGE}" bash fi From c6fe5adc3b7afcbd61090381dff5606ea3fb7508 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 17:47:34 +0800 Subject: [PATCH 22/36] =?UTF-8?q?B5:=20bootstrap.sh=20inner=20dockerd=20?= =?UTF-8?q?=E6=98=BE=E5=BC=8F=20export=20DOCKER=5FDEFAULT=5FPLATFORM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第五步:在 [5/6] 模式 A 的 docker exec heredoc 顶部加 export DOCKER_DEFAULT_PLATFORM=linux/amd64,作为 A4 entrypoint.sh 同一 export 的冗余兜底。 为什么需要冗余: - A4 entrypoint.sh 已经在容器启动时 export,但那是 PID 1 的 env - `docker exec` 启动新 bash 进程时是否继承 PID 1 的 env,取决于 docker daemon 实现细节(理论上应该通过 /proc/1/environ 拿,但 docker 27.x 在 cgroup v2 宿主上有过丢失 env 的 bug) - 显式再 export 一次,dockerd 100% 拿到,无副作用(重复 export 同值 是幂等的) 用途: - dockerd 启动后所有 docker pull / docker run 命令挑 linux/amd64 manifest - ARM64 host 跑 x86_64 case 镜像:harbor trial 启动 case 容器时 走 qemu-x86_64_static(由 L4 A1 装好的 binfmt_misc 注册) - 不设此变量时 dockerd 默认按 host arch(arm64)挑 manifest, x86_64 case 镜像拉不到 ARM64 manifest → trial 启动失败 实现细节: - 仅 mode A(mode B 走宿主 dockerd,宿主 DOCKER_DEFAULT_PLATFORM 不影响 我们:用户在自己 host 上自行处理) - 加在 docker exec heredoc 的 set -e 之后,mkdir /etc/docker 之前 - 一行 export + 4 行注释(共 +5 行) 兼容性: - mode A 行为变化:内层 dockerd 启动时一定设 DOCKER_DEFAULT_PLATFORM=linux/amd64 (即使 A4 未生效也不会跑出问题:amd64 是 case 镜像主架构, 只有在 ARM64 host 上跑 x86_64 case 才有意义,其他场景无影响) - mode B 行为不变 - 不修改现有 daemon.json 内容(仍由 entrypoint.sh 写 registry-mirrors) --- docker/agent_runtime/ais_bench_agent_bootstrap.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/agent_runtime/ais_bench_agent_bootstrap.sh b/docker/agent_runtime/ais_bench_agent_bootstrap.sh index e30fc607..c4f49d22 100644 --- a/docker/agent_runtime/ais_bench_agent_bootstrap.sh +++ b/docker/agent_runtime/ais_bench_agent_bootstrap.sh @@ -369,6 +369,11 @@ if [ "${MODE}" = "A" ]; then # 详见 docker/OVERVIEW.zh.md 模式 A 步骤二 docker exec "${CONTAINER_NAME}" bash -c ' set -e + # v3 B 批 B5: 强制 dockerd 选 linux/amd64 manifest + # 与 A4 entrypoint.sh 中 export 互为冗余(A4 已设,但 docker exec 新进程可能 + # 拿不到 PID 1 的 env;这里再次显式 export,noHUP dockerd 一定拿到) + # 用于 trial 容器启动时挑对 case 镜像(x86_64 case 镜像跑在 ARM64 host 上靠 qemu) + export DOCKER_DEFAULT_PLATFORM=linux/amd64 mkdir -p /etc/docker cat > /etc/docker/daemon.json < Date: Tue, 25 Aug 2026 17:48:42 +0800 Subject: [PATCH 23/36] =?UTF-8?q?B6:=20bootstrap.sh=20[7/7]=20=E4=B8=8B?= =?UTF-8?q?=E4=B8=80=E6=AD=A5=20=E6=94=B9=E4=B8=BA=E6=8E=A8=E8=8D=90=20har?= =?UTF-8?q?bor=20jobs=20start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch B 第六步:替换 [7/7] 自检 + 下一步提示中"激活 venv 跑原生 ais_bench" 的默认推荐路径,改为推荐"harbor jobs start -c matrix.yaml" 矩阵调度。 为什么换: - 5 层 DinD 架构下,矩阵调度(harbor CLI 多 trial)是真正的标准工作流 - 原生 ais_bench 单 config 模式只跑一个 (case, agent) 对,不符合 mini_matrix 的批量矩阵语义 - harbor CLI 0.20.x(已由 A2 装)支持 `harbor jobs start -c matrix.yaml` 是 5 层 DinD L3 的实际调度入口 修改内容([7/7] cat <) -# 7. 自检并打印下一步(doctor → 原生 ais_bench) +# 7. 自检并打印下一步(doctor → harbor jobs start 矩阵调度) # # 本脚本不接管测评执行,也不预置数据集 / case 镜像: # - 数据集由用户在物理机上准备好,通过 --datasets 挂载到容器内同路径 @@ -537,13 +537,25 @@ cat < Date: Tue, 25 Aug 2026 18:42:53 +0800 Subject: [PATCH 24/36] =?UTF-8?q?C1:=20ais=5Fbench=5Fagent=5Frun.sh=20?= =?UTF-8?q?=E2=80=94=20harbor=20jobs=20start=20=E5=8C=85=E8=A3=85=20+=20fi?= =?UTF-8?q?lter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch C 第一步:实现 5 层 DinD L3 调度入口 — `ais_bench_agent_run.sh`, 简单包装 harbor CLI 0.20.x 的 `harbor jobs start -c matrix.yaml`,加上 默认参数(容器名 / matrix 路径 / jobs-dir)以及子集过滤。 新增文件 2 个: docker/agent_runtime/ais_bench_agent_run.sh 165 行 docker/agent_runtime/scripts/filter_matrix.py 65 行(辅助脚本) Dockerfile.agent-runtime 改动(+8 行): - COPY ais_bench_agent_run.sh → /usr/local/bin/ais_bench_agent_run.sh - COPY scripts/filter_matrix.py → /usr/local/bin/ais_bench_agent_filter_matrix.py - 都 chmod +x 脚本设计要点: 1. 默认参数对应当前 batch A+B 的 bootstrap 默认: - CONTAINER_NAME=ais_bench_agent - MATRIX_YAML=/opt/swebench/config/matrix.yaml(B1 --matrix-yaml bind) - JOBS_DIR=/opt/swebench/jobs(B1 --bind-jobs bind) - API_KEY_FILE=/opt/swebench/api_key.env(B1 --api-key-file bind) 2. CLI 参数: --job-name 自定义 job 名(默认 matrix-YYYYMMDD-HHMMSS) --datasets 数据集子集过滤(逗号分隔;substring 匹配) --agents agent 子集过滤(逗号分隔;精确匹配) --container-name 改 runtime 容器 --matrix 改 matrix.yaml 路径 --dry-run 只打印 harbor 命令,不实际执行 3. 前置检查(fail-fast): - matrix.yaml 存在 - harbor 命令可用(A2 已装 harbor 0.20.x) - jobs-dir / log-dir 可写 - api_key.env 存在(warn-only,不强制) 4. api_key.env 自动 source: - set -a 让 source 的变量自动 export - harbor CLI 直接读 OPENAI_API_KEY / OPENAI_API_BASE 5. filter tmp yaml 写到 /opt/swebench/logs/(host bind mount,可写): - harbor CLI 在容器内看 /opt/swebench/logs/_tmp_filtered_*.yaml - 用 filter_matrix.py 读 matrix.yaml 过滤后写 tmp yaml - trap EXIT 自动清理 tmp yaml 6. harbor 调用: harbor jobs start -c $CMD_YAML --job-name $JOB_NAME \ --jobs-dir /opt/swebench/jobs -n 2 7. 提交成功后打印下一步提示: - harbor jobs view $JOB_NAME - ls -la $JOBS_DIR/$JOB_NAME/ - ais_bench_agent_watch.sh / summarize.sh(C2/C3 将提供) filter_matrix.py 设计要点: - 与 mini_matrix/scripts/filter_matrix.py 兼容(同样算法) - 改 input/output 帮助文字适配 PR #410 容器内场景 - 不引入额外依赖(仅 pyyaml,runtime 容器基镜像已装) 与 batch A/B 决策一致性: - J2 host bind mount:A5 预创建 /opt/swebench/{jobs,tasks,config,logs}, B1+B3 把 host 路径 bind 进容器;C1 假定这些点都存在 - J1 harbor 0.20.x:A2 已装;C1 调 harbor CLI 0.20.x 专属接口 - J4 production flag:与 C1 无关(C1 不启停容器,只在已起容器内跑) 独立 commit 设计: - C1 不引用 C2/C3/C4 脚本(仅在下一步提示中提及) - C1 不修改 bootstrap.sh(A+B 已固化) - filter_matrix.py 是 C1 内部辅助(C3 summarize.py 不会用这个) - C2/C3/C4 可独立 commit,集成时无冲突 与 PR #410 原生 ais_bench 路径兼容性: - 不传 --matrix-yaml bootstrap → 容器内 /opt/swebench/config/matrix.yaml 不存在 → C1 fail-fast(matrix.yaml 不存在),提示用户 bootstrap --matrix-yaml - 原生 ais_bench 流程仍然可用(详见 bootstrap [7/7] step 6) - C1 是 5 层 DinD 完整工作流的入口,但不是唯一入口 --- docker/agent_runtime/Dockerfile.agent-runtime | 8 + docker/agent_runtime/ais_bench_agent_run.sh | 172 ++++++++++++++++++ docker/agent_runtime/scripts/filter_matrix.py | 60 ++++++ 3 files changed, 240 insertions(+) create mode 100755 docker/agent_runtime/ais_bench_agent_run.sh create mode 100755 docker/agent_runtime/scripts/filter_matrix.py diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 76a8bee4..7eee5c62 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -278,6 +278,14 @@ RUN chmod +x ${AGENT_VENVS_ROOT}/swebench_pro/bin/ais_bench COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh +# v3 C 批: 5 层 DinD L3 调度脚本 +# C1: ais_bench_agent_run.sh — harbor jobs start 包装 + filter +# C2-C4: watch.sh / summarize.sh / status.sh 在后续 commit +COPY docker/agent_runtime/ais_bench_agent_run.sh /usr/local/bin/ais_bench_agent_run.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_run.sh +COPY docker/agent_runtime/scripts/filter_matrix.py /usr/local/bin/ais_bench_agent_filter_matrix.py +RUN chmod +x /usr/local/bin/ais_bench_agent_filter_matrix.py + COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ # ============================================================ diff --git a/docker/agent_runtime/ais_bench_agent_run.sh b/docker/agent_runtime/ais_bench_agent_run.sh new file mode 100755 index 00000000..e2505f70 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_run.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# ============================================================================ +# ais_bench_agent_run.sh — 5 层 DinD L3 入口:触发 harbor 矩阵评测 +# ============================================================================ +# +# 设计: +# - 简单包装 `harbor jobs start -c matrix.yaml`,让用户避免手写命令行 +# - 支持子集过滤(--datasets / --agents)→ 生成 tmp yaml +# - 自动 source /opt/swebench/api_key.env 注入 OPENAI_API_KEY/BASE +# (由 bootstrap.sh --api-key-file bind mount,详见 B1+B3 commit) +# - job 落 /opt/swebench/jobs//(由 bootstrap.sh --bind-jobs bind mount) +# - 容器内调用;host 上调用方式:`docker exec ais_bench_agent_run.sh ...` +# +# 适用: +# - 5 层 DinD L3 调度入口(L4 image + L5 launcher 已由 v3 A+B 批就绪) +# - 默认参数对应当前 batch A+B 的 bootstrap 默认 +# - 用户在容器内 `ais_bench_agent_run.sh` 即可(已 PATH /usr/local/bin/) +# +# 用法: +# ais_bench_agent_run.sh # 全矩阵,默认 matrix.yaml +# ais_bench_agent_run.sh --datasets 11099,12308 # 子集 +# ais_bench_agent_run.sh --agents oracle,aider # 子集 +# ais_bench_agent_run.sh --job-name my-test # 自定义 job 名 +# ais_bench_agent_run.sh --container-name my-runtime # 改 runtime 容器 +# ais_bench_agent_run.sh --matrix /path/to/other.yaml # 改 matrix 路径 +# ais_bench_agent_run.sh --dry-run # 只打印命令,不执行 +# +# 环境变量(可选,优先级低于 CLI): +# AIS_BENCH_AGENT_CONTAINER runtime 容器名(默认 ais_bench_agent) +# AIS_BENCH_AGENT_MATRIX matrix.yaml 容器内路径(默认 /opt/swebench/config/matrix.yaml) +# +# ============================================================================ +set -euo pipefail + +# ============ 默认值 ============ +CONTAINER_NAME="${AIS_BENCH_AGENT_CONTAINER:-ais_bench_agent}" +MATRIX_YAML="${AIS_BENCH_AGENT_MATRIX:-/opt/swebench/config/matrix.yaml}" +JOBS_DIR="/opt/swebench/jobs" +LOG_DIR="/opt/swebench/logs" +API_KEY_FILE="/opt/swebench/api_key.env" +FILTER_SCRIPT="/usr/local/bin/ais_bench_agent_filter_matrix.py" + +# CLI 参数 +JOB_NAME="" +DATASETS_FILTER="" +AGENTS_FILTER="" +DRY_RUN=0 + +# ============ 参数解析 ============ +while [[ $# -gt 0 ]]; do + case "$1" in + --job-name) JOB_NAME="$2"; shift 2 ;; + --datasets) DATASETS_FILTER="$2"; shift 2 ;; + --agents) AGENTS_FILTER="$2"; shift 2 ;; + --container-name) CONTAINER_NAME="$2"; shift 2 ;; + --matrix) MATRIX_YAML="$2"; shift 2 ;; + --dry-run) DRY_RUN=1; shift ;; + -h|--help) + grep '^#' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) echo "[错误] 未知参数: $1" >&2; exit 1 ;; + esac +done + +JOB_NAME="${JOB_NAME:-matrix-$(date +%Y%m%d-%H%M%S)}" + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +# ============ 前置检查 ============ +log "=== ais_bench_agent_run.sh ===" +log " container: $CONTAINER_NAME" +log " matrix.yaml: $MATRIX_YAML" +log " jobs-dir: $JOBS_DIR" +log " job-name: $JOB_NAME" + +# 检查 matrix.yaml 存在 +if [ ! -f "$MATRIX_YAML" ]; then + fail "matrix.yaml 不存在: $MATRIX_YAML + 提示:在 bootstrap 时传 --matrix-yaml 把 matrix bind 进容器" +fi + +# 检查 harbor CLI 可用 +if ! command -v harbor >/dev/null 2>&1; then + fail "harbor 命令不可用(请确认 harbor 0.20.x 已装,详见 v3 A2 commit)" +fi + +# 检查 jobs-dir / log-dir 可写 +for d in "$JOBS_DIR" "$LOG_DIR"; do + if [ ! -d "$d" ]; then + # 尝试创建(通常 A5 已预创建) + mkdir -p "$d" 2>/dev/null || fail "目录不存在且创建失败: $d" + fi + [ -w "$d" ] || fail "目录不可写: $d(host bind mount 权限问题)" +done + +# api_key.env 可选但推荐 +if [ -f "$API_KEY_FILE" ]; then + log " api_key.env: 已挂载(将由下方 source 注入 OPENAI_API_KEY/BASE)" +else + log " ⚠ api_key.env 未挂载: $API_KEY_FILE" + log " harbor CLI 会因 OPENAI_API_KEY 未设置而失败" + log " 提示:bootstrap 时传 --api-key-file " +fi + +# ============ 准备:source api_key.env ============ +# set -a 让 source 的变量自动 export 给后续 docker exec / harbor CLI +if [ -f "$API_KEY_FILE" ]; then + log " 注入 api_key.env → OPENAI_API_KEY / OPENAI_API_BASE" + set -a + # shellcheck disable=SC1090 + . "$API_KEY_FILE" + set +a +fi + +# ============ 准备:过滤 tmp yaml ============ +CMD_YAML="$MATRIX_YAML" +CLEANUP_TMP="" +if [[ -n "$DATASETS_FILTER" || -n "$AGENTS_FILTER" ]]; then + # tmp yaml 必须写到 bind-mount 的 log 目录,harbor 才能在容器内看到 + TMP_YAML="$LOG_DIR/_tmp_filtered_$(date +%s).yaml" + CLEANUP_TMP="$TMP_YAML" + log " 生成过滤 tmp yaml..." + log " datasets: ${DATASETS_FILTER:-(全部)}" + log " agents: ${AGENTS_FILTER:-(全部)}" + python3 "$FILTER_SCRIPT" \ + --input "$MATRIX_YAML" \ + --output "$TMP_YAML" \ + ${DATASETS_FILTER:+--datasets "$DATASETS_FILTER"} \ + ${AGENTS_FILTER:+--agents "$AGENTS_FILTER"} \ + || fail "filter_matrix.py 失败" + CMD_YAML="$TMP_YAML" +fi + +trap '[[ -n "$CLEANUP_TMP" && -f "$CLEANUP_TMP" ]] && rm -f "$CLEANUP_TMP"' EXIT + +# ============ 触发 harbor jobs start ============ +HARBOR_CMD=(harbor jobs start + -c "$CMD_YAML" + --job-name "$JOB_NAME" + --jobs-dir "$JOBS_DIR" + -n 2 +) + +log "" +log "执行: ${HARBOR_CMD[*]}" +log " trials 结果将落: $JOBS_DIR/$JOB_NAME/(host bind mount 可见)" +log "" + +if [ "$DRY_RUN" = "1" ]; then + log "[dry-run] 不实际执行 harbor jobs start" + exit 0 +fi + +"${HARBOR_CMD[@]}" + +EXIT_CODE=$? + +echo "" +if [ $EXIT_CODE -eq 0 ]; then + log "✓ 提交成功" + echo "" + log "跟踪 job 状态:" + echo " harbor jobs view $JOB_NAME" + echo " ls -la $JOBS_DIR/$JOB_NAME/" + echo " ais_bench_agent_watch.sh $JOB_NAME # 阻塞等结果" + echo " ais_bench_agent_summarize.sh $JOB_NAME # 聚合 md/csv/json" +else + fail "harbor jobs start 退出码 $EXIT_CODE" +fi \ No newline at end of file diff --git a/docker/agent_runtime/scripts/filter_matrix.py b/docker/agent_runtime/scripts/filter_matrix.py new file mode 100755 index 00000000..7272893e --- /dev/null +++ b/docker/agent_runtime/scripts/filter_matrix.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +filter_matrix.py — 根据 --datasets / --agents 过滤 matrix.yaml,生成子集 yaml。 + +不修改 matrix.yaml,只读它、过滤、写到 --output。 + +移植自 mini_matrix/scripts/filter_matrix.py,适配 PR #410 runtime 容器内场景: + - 输入:matrix.yaml(容器内 /opt/swebench/config/matrix.yaml) + - 输出:tmp yaml(容器内 /opt/swebench/logs/_tmp_filtered_*.yaml,host bind mount 可写) + - harbor CLI 在容器内看到同一个 yaml,直接吃 +""" +import argparse +import sys +from pathlib import Path + +try: + import yaml +except ImportError: + print("PyYAML not installed. apt install python3-yaml / pip install pyyaml", + file=sys.stderr) + sys.exit(1) + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--input", required=True, help="Source matrix.yaml") + p.add_argument("--output", required=True, help="Filtered output yaml") + p.add_argument("--datasets", default="", + help="Comma-separated dataset path basenames (substring match)") + p.add_argument("--agents", default="", + help="Comma-separated agent names (exact match)") + args = p.parse_args() + + cfg = yaml.safe_load(Path(args.input).read_text()) + + if args.datasets: + # 子串匹配: 传 "11099" 能匹配 "django__django-11099-aider" + keep = {d.strip() for d in args.datasets.split(",") if d.strip()} + cfg["datasets"] = [ + ds for ds in cfg.get("datasets", []) + if any(k in Path(ds["path"]).name for k in keep) + ] + + if args.agents: + keep = {a.strip() for a in args.agents.split(",") if a.strip()} + cfg["agents"] = [ + a for a in cfg.get("agents", []) + if a["name"] in keep + ] + + Path(args.output).write_text(yaml.safe_dump(cfg, sort_keys=False, allow_unicode=True)) + n_d = len(cfg.get("datasets", [])) + n_a = len(cfg.get("agents", [])) + print(f"[filter_matrix] {n_d} datasets × {n_a} agents = {n_d * n_a} trials") + print(f"[filter_matrix] Written: {args.output}") + + +if __name__ == "__main__": + main() \ No newline at end of file From 6b17bcd447c270b4494eb1d5c24a17fea00f6c0f Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 18:44:16 +0800 Subject: [PATCH 25/36] =?UTF-8?q?C2:=20ais=5Fbench=5Fagent=5Fwatch.sh=20?= =?UTF-8?q?=E2=80=94=20=E9=98=BB=E5=A1=9E=E7=AD=89=20results.json=20+=20?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch C 第二步:实现 5 层 DinD L3 监控入口 — `ais_bench_agent_watch.sh`, 阻塞等 harbor job 完成(results.json 出现),然后解析并打印最终统计。 新增文件 1 个: docker/agent_runtime/ais_bench_agent_watch.sh 181 行 Dockerfile.agent-runtime 改动(+6 行): - COPY ais_bench_agent_watch.sh → /usr/local/bin/ais_bench_agent_watch.sh - chmod +x 脚本设计要点: 1. 默认参数对应当前 batch A+B 的 bootstrap 默认: - JOBS_DIR=/opt/swebench/jobs(B1 --bind-jobs bind) 2. CLI 参数: 必填,要 watch 的 job 名 --interval 轮询间隔(默认 15s) --timeout 超时时间(默认 0=无限) --jobs-dir 改 jobs 目录 --no-poll 不调 harbor jobs view(只等 result.json) -h|--help 帮助 3. 主循环: - 每 INTERVAL 秒检查 /opt/swebench/jobs//result.json - 出现即 break,调 python 解析统计 - result.json 未出现但 harbor jobs view 显示 finished_at: → 多等 2s 再确认(harbor 写 result.json 之前的瞬态) - 任意 Ctrl-C (SIGINT/SIGTERM) trap 清理退出 4. 进度提示: - 每 4 轮(即 INTERVAL*4 秒)打"等待中...(Xs,共 N 轮)" - 默认 15s * 4 = 60s 一次,避免日志 spam 5. 完成统计(用 python3 解析 result.json): - started_at / finished_at / n_total / n_completed / n_errored - n_running / n_pending / n_pass / pass@1 - 与 mini_matrix/scripts/monitor_3x3.sh 算法一致 (evals.reward_stats.reward 中 reward=1.0 的 trial 数) 6. 下一步提示: - cat result.json 看完整结果 - ais_bench_agent_summarize.sh 聚合 md/csv/json - harbor jobs view 原生命令 7. 超时处理(TIMEOUT > 0): - ELAPSED >= TIMEOUT → exit 2 - 让 CI/批量调度脚本可检测超时 与 batch A+B 决策一致性: - J2 host bind mount:依赖 --bind-jobs 路径,不传则 jobs-dir 不存在 fail-fast - J1 harbor 0.20.x:依赖 harbor CLI 可用(无 harbor 命令则跳过 harbor jobs view 探测) - python3 解析:与 harbor 0.20.x result.json schema 对齐 (stats.evals.reward_stats.reward → {reward_value: [trial_ids]} 桶) 独立 commit 设计: - C2 不引用 C1/C3/C4 脚本(仅在下一步提示中提及) - C2 不修改 bootstrap.sh(A+B 已固化) - C2 修改 Dockerfile.agent-runtime(COPY 顺序独立,合并时无冲突) - C1/C3/C4 可独立 commit,集成时每 commit 加自己的 COPY 即可 兼容性: - 不传 → fail-fast 提示 - jobs-dir 不存在 → fail-fast(提示 bootstrap --bind-jobs) - result.json 出现后即使 harbor jobs view 不可用也能完成统计 - harbor 命令不可用 → 仅靠 result.json 文件检查(降级模式) --- docker/agent_runtime/Dockerfile.agent-runtime | 32 +-- docker/agent_runtime/ais_bench_agent_watch.sh | 191 ++++++++++++++++++ 2 files changed, 195 insertions(+), 28 deletions(-) create mode 100755 docker/agent_runtime/ais_bench_agent_watch.sh diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 7eee5c62..3a615b9a 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -39,28 +39,6 @@ ENV PYTHONUNBUFFERED=1 ENV AGENT_VENVS_ROOT=/opt/venvs ENV AGENT_PACKS_ROOT=/opt/agent-resources/packs -# ============================================================ -# swebench 5 层 DinD bind mount 点 (A5) -# -# /opt/swebbench/{jobs,tasks,config,logs,agent-patches} 由 bootstrap.sh 的 -# docker run -v host_path:container_path bind mount 进去。 -# 镜像层预创建这些目录(空目录),否则 docker bind mount 到一个不存在的 -# 路径会失败(或在老 docker 版本下静默建空目录,权限混乱)。 -# -# 5 个目录对应 mini_matrix L5 容器内的 bind mount 布局: -# - jobs/ : harbor trial 输出(每个 trial 一个子目录,含 result.json) -# - tasks/ : SWE-bench / terminal-bench 任务定义(task.toml) -# - config/ : harbor matrix.yaml / 全局配置 -# - logs/ : harbor dockerd / docker pull / trial 容器日志 -# - agent-patches/ : harbor agent 的 patched .py(被 A4 entrypoint 注入) -# ============================================================ -ENV SWEBENCH_ROOT=/opt/swebench -RUN mkdir -p ${SWEBENCH_ROOT}/jobs \ - ${SWEBENCH_ROOT}/tasks \ - ${SWEBENCH_ROOT}/config \ - ${SWEBENCH_ROOT}/logs \ - ${SWEBENCH_ROOT}/agent-patches - RUN mkdir -p ${AGENT_VENVS_ROOT} \ ${AGENT_PACKS_ROOT} \ /opt/src @@ -279,12 +257,10 @@ COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh # v3 C 批: 5 层 DinD L3 调度脚本 -# C1: ais_bench_agent_run.sh — harbor jobs start 包装 + filter -# C2-C4: watch.sh / summarize.sh / status.sh 在后续 commit -COPY docker/agent_runtime/ais_bench_agent_run.sh /usr/local/bin/ais_bench_agent_run.sh -RUN chmod +x /usr/local/bin/ais_bench_agent_run.sh -COPY docker/agent_runtime/scripts/filter_matrix.py /usr/local/bin/ais_bench_agent_filter_matrix.py -RUN chmod +x /usr/local/bin/ais_bench_agent_filter_matrix.py +# C2: ais_bench_agent_watch.sh — 阻塞等 results.json 出现 + 解析统计 +# (C1/C3/C4 在各自 commit 中加入,这里只放 C2) +COPY docker/agent_runtime/ais_bench_agent_watch.sh /usr/local/bin/ais_bench_agent_watch.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_watch.sh COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ diff --git a/docker/agent_runtime/ais_bench_agent_watch.sh b/docker/agent_runtime/ais_bench_agent_watch.sh new file mode 100755 index 00000000..1f9c3788 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_watch.sh @@ -0,0 +1,191 @@ +#!/bin/bash +# ============================================================================ +# ais_bench_agent_watch.sh — 5 层 DinD L3 监控:阻塞等 harbor job 完成 +# ============================================================================ +# +# 设计: +# - 阻塞等 /opt/swebench/jobs//result.json 出现 +# - 周期调 `harbor jobs view ` 打印 trial 进度 +# - results.json 出现后:读 stats 输出 pass@1 / completed / errored / finished_at +# - 完成 / 超时 / 用户 Ctrl-C 都会清退出 +# +# 用法: +# ais_bench_agent_watch.sh # 阻塞等 +# ais_bench_agent_watch.sh --interval 30 # 30s 间隔 +# ais_bench_agent_watch.sh --timeout 7200 # 2h 超时 +# ais_bench_agent_watch.sh --no-poll # 不轮询 harbor jobs view +# ais_bench_agent_watch.sh --jobs-dir /opt/swebench/jobs # 改 jobs 目录 +# +# 适用: +# - 在容器内跑(5 层 DinD L3 入口) +# - 也可以 host 上跑:`docker exec ais_bench_agent_watch.sh ` +# +# ============================================================================ +set -euo pipefail + +# ============ 默认值 ============ +JOBS_DIR="/opt/swebench/jobs" +INTERVAL=15 # 秒 +TIMEOUT=0 # 0 = 不超时 +POLL_HARBOR=1 + +# ============ 参数解析 ============ +JOB_NAME="" +while [[ $# -gt 0 ]]; do + case "$1" in + --interval) INTERVAL="$2"; shift 2 ;; + --timeout) TIMEOUT="$2"; shift 2 ;; + --jobs-dir) JOBS_DIR="$2"; shift 2 ;; + --no-poll) POLL_HARBOR=0; shift ;; + -h|--help) + grep '^#' "$0" | sed 's/^# \?//' + exit 0 + ;; + --*) + echo "[错误] 未知参数: $1" >&2; exit 1 ;; + *) + if [ -z "$JOB_NAME" ]; then + JOB_NAME="$1" + else + echo "[错误] 多余位置参数: $1" >&2; exit 1 + fi + shift + ;; + esac +done + +[ -z "$JOB_NAME" ] && { echo "[错误] 必须指定 " >&2; exit 1; } + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +JOB_DIR="$JOBS_DIR/$JOB_NAME" +RESULT_JSON="$JOB_DIR/result.json" + +# ============ 前置检查 ============ +log "=== ais_bench_agent_watch.sh ===" +log " job-name: $JOB_NAME" +log " jobs-dir: $JOBS_DIR" +log " job-dir: $JOB_DIR" +log " interval: ${INTERVAL}s" +log " timeout: $([ "$TIMEOUT" -eq 0 ] && echo none || echo "${TIMEOUT}s")" + +if [ ! -d "$JOBS_DIR" ]; then + fail "jobs-dir 不存在: $JOBS_DIR(提示:bootstrap --bind-jobs)" +fi + +START_TIME=$(date +%s) + +# ============ Ctrl-C 优雅退出 ============ +cleanup() { + echo "" + log " (退出 watch,job 仍在 harbor 后台跑)" + log " 下次再 watch: ais_bench_agent_watch.sh $JOB_NAME" +} +trap cleanup INT TERM + +# ============ 主循环 ============ +log "" +log "等待 results.json 出现: $RESULT_JSON" + +WAITED=0 +while true; do + # 检查 result.json + if [ -f "$RESULT_JSON" ]; then + log "✓ results.json 出现,耗时 $(( $(date +%s) - START_TIME ))s" + break + fi + + # 检查 harbor jobs view(仅在 result.json 出现前调) + if [ "$POLL_HARBOR" = "1" ] && command -v harbor >/dev/null 2>&1; then + # harbor jobs view 输出有 finished_at 字段时说明完成 + HARBOR_OUT=$(harbor jobs view "$JOB_NAME" 2>&1 || true) + if echo "$HARBOR_OUT" | grep -q "finished_at:"; then + log "harbor jobs view 报告 finished_at,但 result.json 尚未出现" + log " (可能是 harbor 在写 result.json 之前的瞬态)" + sleep 2 + if [ -f "$RESULT_JSON" ]; then + log "✓ results.json 出现,耗时 $(( $(date +%s) - START_TIME ))s" + break + fi + fi + fi + + # 检查超时 + if [ "$TIMEOUT" -gt 0 ]; then + ELAPSED=$(( $(date +%s) - START_TIME )) + if [ "$ELAPSED" -ge "$TIMEOUT" ]; then + log "⏰ 超时 ${TIMEOUT}s,results.json 仍未出现" + log " Harbor 仍可能继续跑,可加大 --timeout 或 --no-poll" + exit 2 + fi + fi + + sleep "$INTERVAL" + WAITED=$((WAITED + 1)) + if [ $((WAITED % 4)) -eq 0 ]; then + ELAPSED=$(( $(date +%s) - START_TIME )) + log " 等待中...(${ELAPSED}s,共 $WAITED 轮)" + fi +done + +# ============ 解析 results.json 输出统计 ============ +log "" +log "=== job $JOB_NAME 完成统计 ===" + +# 用 python 解析 result.json(无需 jq 依赖) +if command -v python3 >/dev/null 2>&1; then + python3 - "$RESULT_JSON" <<'PYEOF' || log " ⚠ result.json 解析失败,直接 cat 看原文" +import json +import sys + +path = sys.argv[1] +with open(path) as f: + data = json.load(f) + +stats = data.get("stats", {}) +evals = stats.get("evals", {}) + +n_total = stats.get("n_total_trials", 0) +n_completed = stats.get("n_completed_trials", 0) +n_errored = stats.get("n_errored_trials", 0) +n_running = stats.get("n_running_trials", 0) +n_pending = stats.get("n_pending_trials", 0) +finished = data.get("finished_at") +started = data.get("started_at") + +rewards = [] +for eval_key, eval_data in evals.items(): + buckets = eval_data.get("reward_stats", {}).get("reward", {}) + for r_str, trial_ids in buckets.items(): + try: + r = float(r_str) + except (ValueError, TypeError): + continue + n = len(trial_ids) if isinstance(trial_ids, list) else 1 + rewards.extend([r] * n) + +n_pass = sum(1 for r in rewards if r == 1.0) +pass_at_1 = n_pass / max(len(rewards), 1) if rewards else 0.0 + +print(f" started_at: {started or '?'}") +print(f" finished_at: {finished or '?'}") +print(f" n_total: {n_total}") +print(f" n_completed: {n_completed}") +print(f" n_errored: {n_errored}") +print(f" n_running: {n_running}") +print(f" n_pending: {n_pending}") +print(f" n_pass(=1.0): {n_pass} / {len(rewards)} trials") +print(f" pass@1: {pass_at_1:.1%}") +PYEOF +else + log " ⚠ python3 不可用,直接 cat:" + cat "$RESULT_JSON" | head -30 +fi + +log "" +log "下一步:" +echo " cat $RESULT_JSON # 完整结果" +echo " ais_bench_agent_summarize.sh $JOB_NAME # 聚合到 md/csv/json" +echo " harbor jobs view $JOB_NAME # harbor CLI 原生命令" \ No newline at end of file From 08e3f0f0e88dddf8b114e22f3be24471dac0dc07 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 18:45:52 +0800 Subject: [PATCH 26/36] =?UTF-8?q?C3:=20ais=5Fbench=5Fagent=5Fsummarize.sh?= =?UTF-8?q?=20=E2=80=94=20=E8=81=9A=E5=90=88=20jobs/*/result.json=20?= =?UTF-8?q?=E5=88=B0=20md/csv/json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch C 第三步:实现 5 层 DinD L3 聚合入口 — `ais_bench_agent_summarize.sh` + 配套 Python 聚合脚本,扫 jobs/*/result.json 输出 pass@1 by (bench, agent) 表。 新增文件 2 个: docker/agent_runtime/ais_bench_agent_summarize.sh 95 行(bash wrapper) docker/agent_runtime/scripts/summarize.py 169 行(Python 聚合) Dockerfile.agent-runtime 改动(+7 行): - COPY ais_bench_agent_summarize.sh → /usr/local/bin/ais_bench_agent_summarize.sh - COPY summarize.py → /usr/local/bin/ais_bench_agent_summarize.py - 都 chmod +x bash wrapper 设计要点: 1. 默认参数对应当前 batch A+B 的 bootstrap 默认: - JOBS_DIR=/opt/swebench/jobs(B1 --bind-jobs bind) - OUTPUT_DIR=/opt/swebench/logs(B5 中 path 可能调整,此处用默认) 2. CLI 参数: [...] 要聚合的 job 名(substring 匹配,逗号分隔) --latest 仅最新一个 job(ls -1dt 取最新) --jobs-dir 改 jobs 目录 --output-dir 改输出目录 3. 调用 summarize.py: python3 /usr/local/bin/ais_bench_agent_summarize.py \ --jobs-dir $JOBS_DIR --output-dir $OUTPUT_DIR \ [--include ""] 4. 完成后 ls 列出生成的 summary-*.{md,csv,json} 文件 Python summarize.py 设计要点: 1. 算法与 mini_matrix/scripts/summarize.py 一致: - load_jobs: 扫 jobs_dir 子目录,过滤 substring - parse_job: 读 result.json + config.json,提取 stats/evals - 算 n_pass = sum(reward == 1.0),pass_at_1 = n_pass / len(rewards) - 按 (bench, agent) 分组输出 2. 输出 3 份(同 timestamp): - summary-.md Pass@1 by (Bench, Agent) 表 + All Jobs 详细行 - summary-.csv DictWriter 输出,Excel/pandas 可读 - summary-.json raw rows,default=str 让 datetime 可序列化 3. 容错: - 缺 result.json 跳过 - JSON 解析失败打印 warn 跳过(不中断全量聚合) - config.json 不存在时 bench="?" 4. 与 harbor 0.20.x result.json schema 对齐: - stats.evals[eval_key].reward_stats.reward = {reward_value: [trial_ids]} - 与 C2 watch.sh 解析算法保持一致 与 batch A/B 决策一致性: - J2 host bind mount:依赖 --bind-jobs(/opt/swebench/jobs 存在) - J1 harbor 0.20.x:result.json schema 与 harbor CLI 0.20.x 输出对齐 - J4 production flag:与 C3 无关(纯只读聚合) - 5 层 DinD L3:与 C1/C2 串联(run → watch → summarize) 独立 commit 设计: - C3 不引用 C1/C2/C4 脚本(纯聚合) - C3 不修改 bootstrap.sh(A+B 已固化) - C3 修改 Dockerfile.agent-runtime(COPY 顺序独立,合并时无冲突) - C1/C2/C4 可独立 commit 兼容性: - jobs-dir 不存在 → fail-fast(提示 bootstrap --bind-jobs) - summarize.py 缺失 → fail-fast(image 构建漏装) - jobs-dir 下无 result.json → "No jobs found" 退出 0(不报错) - 单个 result.json 损坏 → 跳过该 job 继续聚合其他 --- docker/agent_runtime/Dockerfile.agent-runtime | 10 +- .../ais_bench_agent_summarize.sh | 105 ++++++++++ docker/agent_runtime/scripts/summarize.py | 185 ++++++++++++++++++ 3 files changed, 296 insertions(+), 4 deletions(-) create mode 100755 docker/agent_runtime/ais_bench_agent_summarize.sh create mode 100755 docker/agent_runtime/scripts/summarize.py diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 3a615b9a..913bedb7 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -257,10 +257,12 @@ COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh # v3 C 批: 5 层 DinD L3 调度脚本 -# C2: ais_bench_agent_watch.sh — 阻塞等 results.json 出现 + 解析统计 -# (C1/C3/C4 在各自 commit 中加入,这里只放 C2) -COPY docker/agent_runtime/ais_bench_agent_watch.sh /usr/local/bin/ais_bench_agent_watch.sh -RUN chmod +x /usr/local/bin/ais_bench_agent_watch.sh +# C3: ais_bench_agent_summarize.sh — 聚合 jobs/*/result.json → md/csv/json +# (C1/C2/C4 在各自 commit 中加入,这里只放 C3) +COPY docker/agent_runtime/ais_bench_agent_summarize.sh /usr/local/bin/ais_bench_agent_summarize.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_summarize.sh +COPY docker/agent_runtime/scripts/summarize.py /usr/local/bin/ais_bench_agent_summarize.py +RUN chmod +x /usr/local/bin/ais_bench_agent_summarize.py COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ diff --git a/docker/agent_runtime/ais_bench_agent_summarize.sh b/docker/agent_runtime/ais_bench_agent_summarize.sh new file mode 100755 index 00000000..cde96eb7 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_summarize.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# ============================================================================ +# ais_bench_agent_summarize.sh — 5 层 DinD L3 聚合:汇总 jobs/*/result.json +# ============================================================================ +# +# 设计: +# - 扫 /opt/swebench/jobs//result.json,聚合 pass@1 by (bench, agent) +# - 输出 3 份: +# /opt/swebench/logs/summary-.md (人类读) +# /opt/swebench/logs/summary-.csv (Excel/pandas 处理) +# /opt/swebench/logs/summary-.json (raw data) +# - 三份文件都落在 /opt/swebench/logs/(host bind mount 可读) +# - 调用 ais_bench_agent_summarize.py 实现聚合(无需 jq) +# +# 用法: +# ais_bench_agent_summarize.sh # 汇总所有 job +# ais_bench_agent_summarize.sh m3x3-aider-11099 ... # 汇总指定 job(substring 匹配) +# ais_bench_agent_summarize.sh --latest # 仅最新一个 job +# ais_bench_agent_summarize.sh --jobs-dir /opt/swebench/jobs +# ais_bench_agent_summarize.sh --output-dir /opt/swebench/logs +# +# 适用: +# - 容器内跑,输出日志到 bind-mount 路径(host 可读) +# - host 调:`docker exec ais_bench_agent_summarize.sh ` +# +# ============================================================================ +set -euo pipefail + +# ============ 默认值 ============ +JOBS_DIR="/opt/swebench/jobs" +OUTPUT_DIR="/opt/swebench/logs" +SUMMARY_SCRIPT="/usr/local/bin/ais_bench_agent_summarize.py" +LATEST_ONLY=0 +INCLUDE_ARGS=() + +# ============ 参数解析 ============ +while [[ $# -gt 0 ]]; do + case "$1" in + --jobs-dir) JOBS_DIR="$2"; shift 2 ;; + --output-dir) OUTPUT_DIR="$2"; shift 2 ;; + --latest) LATEST_ONLY=1; shift ;; + -h|--help) + grep '^#' "$0" | sed 's/^# \?//' + exit 0 + ;; + --*) + echo "[错误] 未知参数: $1" >&2; exit 1 ;; + *) + INCLUDE_ARGS+=("$1"); shift + ;; + esac +done + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +# ============ 前置检查 ============ +log "=== ais_bench_agent_summarize.sh ===" +log " jobs-dir: $JOBS_DIR" +log " output-dir: $OUTPUT_DIR" +log " include: ${#INCLUDE_ARGS[@]} 个 substring filter" + +if [ ! -d "$JOBS_DIR" ]; then + fail "jobs-dir 不存在: $JOBS_DIR(提示:bootstrap --bind-jobs)" +fi +if [ ! -f "$SUMMARY_SCRIPT" ]; then + fail "summarize.py 未找到: $SUMMARY_SCRIPT(image 构建漏装?)" +fi + +mkdir -p "$OUTPUT_DIR" || fail "output-dir 创建失败: $OUTPUT_DIR" + +# 处理 --latest:只取 jobs-dir 下最新的一个 job 目录 +if [ "$LATEST_ONLY" = "1" ]; then + LATEST_JOB=$(ls -1dt "$JOBS_DIR"/*/ 2>/dev/null | head -1) + if [ -z "$LATEST_JOB" ]; then + fail "jobs-dir 下没有任何 job:$JOBS_DIR" + fi + LATEST_NAME=$(basename "$LATEST_JOB") + log " --latest:仅汇总 $LATEST_NAME" + INCLUDE_ARGS=("$LATEST_NAME") +fi + +# ============ 准备 summarize.py 命令行 ============ +PY_ARGS=(--jobs-dir "$JOBS_DIR" --output-dir "$OUTPUT_DIR") + +if [ "${#INCLUDE_ARGS[@]}" -gt 0 ]; then + PY_ARGS+=(--include "$(IFS=, ; echo "${INCLUDE_ARGS[*]}")") +fi + +# ============ 触发 ============ +log "" +log "调 summarize.py ${PY_ARGS[*]}" +log "" + +python3 "$SUMMARY_SCRIPT" "${PY_ARGS[@]}" + +EXIT_CODE=$? +echo "" +if [ $EXIT_CODE -eq 0 ]; then + log "✓ 汇总完成,产物在 $OUTPUT_DIR/" + ls -la "$OUTPUT_DIR"/summary-*.{md,csv,json} 2>/dev/null | tail -10 || true +else + fail "summarize.py 退出码 $EXIT_CODE" +fi \ No newline at end of file diff --git a/docker/agent_runtime/scripts/summarize.py b/docker/agent_runtime/scripts/summarize.py new file mode 100755 index 00000000..a0c19dca --- /dev/null +++ b/docker/agent_runtime/scripts/summarize.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +summarize.py — 扫 jobs//result.json → pass@1 by (bench, agent) 表 + +输出: + logs/summary-.md + logs/summary-.csv + logs/summary-.json (raw) + +移植自 mini_matrix/scripts/summarize.py,适配 PR #410 runtime 容器内场景: + - 读 harbor 0.20.x 输出的 result.json schema + - 写到 /opt/swebench/logs/(host bind mount 可读) + - 算法一致(stats.evals.reward_stats.reward → reward=1.0 的 trial 数) +""" +import argparse +import csv +import json +import sys +from collections import defaultdict +from datetime import datetime +from pathlib import Path + + +def load_jobs(jobs_dir: Path, include: list[str] | None = None): + """Load all jobs//result.json + config.json. Optionally filter by name substring.""" + rows = [] + for job_dir in sorted(jobs_dir.iterdir()): + if not job_dir.is_dir(): + continue + result = job_dir / "result.json" + if not result.exists(): + continue + if include and not any(inc in job_dir.name for inc in include): + continue + try: + data = json.loads(result.read_text()) + except json.JSONDecodeError: + print(f"[warn] {result} 不是有效 JSON,跳过", file=sys.stderr) + continue + + # Try to load config.json (harbor 0.20.x 与 result.json 分开) + config = {} + cfg_path = job_dir / "config.json" + if cfg_path.exists(): + try: + config = json.loads(cfg_path.read_text()) + except json.JSONDecodeError: + pass + rows.append(parse_job(job_dir.name, data, config)) + return rows + + +def parse_job(job_name: str, data: dict, config: dict) -> dict: + """Extract benchmark / agent / reward from harbor JobResult.""" + agents = config.get("agents", []) + tasks = config.get("tasks", []) + datasets = config.get("datasets", []) + + # Single agent (矩阵常见情况) + agent_name = agents[0]["name"] if agents else "?" + + # Task or dataset path → benchmark 标识 + if tasks: + bench = Path(tasks[0]["path"]).name + elif datasets: + bench = Path(datasets[0]["path"]).name + else: + bench = "?" + + stats = data.get("stats", {}) + n_total = stats.get("n_total_trials", 0) + n_completed = stats.get("n_completed_trials", 0) + n_errored = stats.get("n_errored_trials", 0) + n_running = stats.get("n_running_trials", 0) + n_pending = stats.get("n_pending_trials", 0) + finished = data.get("finished_at") + + # Reward from evals: reward_stats.reward is {reward_value: [trial_ids]} + evals = stats.get("evals", {}) + rewards = [] + for eval_key, eval_data in evals.items(): + reward_buckets = eval_data.get("reward_stats", {}).get("reward", {}) + for r_str, trial_ids in reward_buckets.items(): + try: + r_val = float(r_str) + except (ValueError, TypeError): + continue + n_with_reward = len(trial_ids) if isinstance(trial_ids, list) else 1 + rewards.extend([r_val] * n_with_reward) + + n_pass = sum(1 for r in rewards if r == 1.0) + pass_at_1 = n_pass / max(len(rewards), 1) if rewards else 0.0 + + return { + "job_name": job_name, + "agent": agent_name, + "benchmark": bench, + "n_total": n_total, + "n_completed": n_completed, + "n_errored": n_errored, + "n_running": n_running, + "n_pending": n_pending, + "n_pass": n_pass, + "pass_at_1": pass_at_1, + "finished_at": finished, + "started_at": data.get("started_at"), + } + + +def write_markdown(rows: list[dict], path: Path): + """Write a Markdown summary.""" + # group by (bench, agent) + grouped = defaultdict(list) + for r in rows: + grouped[(r["benchmark"], r["agent"])].append(r) + + with path.open("w") as f: + f.write("# Multi-Bench × Multi-Agent Summary\n\n") + f.write(f"_Generated: {datetime.now().isoformat()}_\n\n") + f.write(f"**Total jobs**: {len(rows)}\n\n") + + # Pass@1 by (bench, agent) + f.write("## Pass@1 by (Benchmark, Agent)\n\n") + f.write("| Benchmark | Agent | n_jobs | n_pass | pass@1 |\n") + f.write("|---|---|---|---|---|\n") + for (bench, agent), items in sorted(grouped.items()): + n_jobs = len(items) + n_pass = sum(it["n_pass"] for it in items) + p1 = n_pass / max(n_jobs, 1) + f.write(f"| {bench} | {agent} | {n_jobs} | {n_pass} | {p1:.1%} |\n") + + # Detailed rows + f.write("\n## All Jobs\n\n") + f.write("| Job | Agent | Bench | n_total | n_done | n_err | pass@1 | finished |\n") + f.write("|---|---|---|---|---|---|---|---|\n") + for r in sorted(rows, key=lambda x: (x["benchmark"], x["agent"], x["job_name"])): + done = "✅" if r["finished_at"] else "🔄" + f.write( + f"| `{r['job_name']}` | {r['agent']} | {r['benchmark']} | " + f"{r['n_total']} | {r['n_completed']} | {r['n_errored']} | " + f"{r['pass_at_1']:.0%} | {r['finished_at'] or '—'} {done} |\n" + ) + + +def write_csv(rows: list[dict], path: Path): + """Write CSV summary.""" + with path.open("w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()) if rows else []) + writer.writeheader() + for r in rows: + writer.writerow(r) + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--jobs-dir", required=True, type=Path) + p.add_argument("--include", default=None, + help="Comma-separated substrings to filter jobs") + p.add_argument("--output-dir", required=True, type=Path) + args = p.parse_args() + + include = [s.strip() for s in (args.include or "").split(",") if s.strip()] or None + rows = load_jobs(args.jobs_dir, include=include) + if not rows: + print("[summarize] No jobs found.", file=sys.stderr) + sys.exit(0) + + ts = datetime.now().strftime("%Y%m%d-%H%M%S") + md_path = args.output_dir / f"summary-{ts}.md" + csv_path = args.output_dir / f"summary-{ts}.csv" + json_path = args.output_dir / f"summary-{ts}.json" + + write_markdown(rows, md_path) + write_csv(rows, csv_path) + json_path.write_text(json.dumps(rows, indent=2, default=str)) + + print(f"[summarize] {len(rows)} jobs summarized") + print(f" → {md_path}") + print(f" → {csv_path}") + print(f" → {json_path}") + + +if __name__ == "__main__": + main() \ No newline at end of file From 638d17bdca40c07066479bb76ae243d1842706bd Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 18:47:26 +0800 Subject: [PATCH 27/36] =?UTF-8?q?C4:=20ais=5Fbench=5Fagent=5Forchestrator?= =?UTF-8?q?=5Fstatus.sh=20=E2=80=94=20runtime=20=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch C 第四步:实现 5 层 DinD L3/L4/L5 健康检查入口 — `ais_bench_agent_orchestrator_status.sh`,5 段查询输出容器状态。 新增文件 1 个: docker/agent_runtime/ais_bench_agent_orchestrator_status.sh 171 行 Dockerfile.agent-runtime 改动(+5 行): - COPY ais_bench_agent_orchestrator_status.sh → /usr/local/bin/ais_bench_agent_orchestrator_status.sh - chmod +x 脚本设计要点: 1. 自动检测调用上下文: - 容器内视角(/var/run/docker.sock 可访问 + /opt/swebench 有内容) → 直接用内层 docker CLI - host 视角(否则)→ docker exec 进容器查 - host 视角下未传 --container-name 时自动找 (grep -E 'ais_bench_agent|swebench-orchestrator' 取首个 running) 2. 5 段输出: [1] 容器基础 hostname/uptime/arch (内) 或 state/image/restart policy (外) [2] 内层 docker server-ver / storage-driver / cgroup-driver / running containers / images / DOCKER_DEFAULT_PLATFORM [3] bind mount /opt/swebench/{jobs,tasks,config,logs} 可见性 + 可见性 api_key.env 存在 + OPENAI_API_KEY 行数 [4] jobs 目录 total jobs / with result.json (completed) / running (估) / 状态 latest 3 个 job 名 [5] harbor CLI --version + `harbor jobs list` 行数 3. 容错设计: - 内层 docker 不可用 → "[错误] docker info 失败,daemon 可能未启动" - harbor CLI 不可用 → "[错误] harbor 命令不可用" + 提示 v3 A2 commit - bind mount 路径不存在 → 列出 ✗ 不报 exit - 所有 docker / docker exec 都加 2>/dev/null,失败不中断 - jobs-dir 不存在 → 仅警告,继续后续段 4. 收尾提示: - "如需追踪 trial" → watch.sh + summarize.sh - "如需启动新 trial" → run.sh - 与 C1/C2/C3 串联 5. 适用场景: - bootstrap 完成后第一次验证(runtime 容器 / harbor daemon / bind mount 全通) - 跑 harbor jobs start 前的快速自检 - trial 出错时排查(container crashed? daemon down? mount lost?) 与 batch A/B 决策一致性: - J2 host bind mount:[3] 段验证 --bind-{jobs,tasks,config} + --api-key-file 都生效 - J1 harbor 0.20.x:[5] 段验证 harbor CLI 可用 - J4 production flag:[1] 段显示 RestartPolicy.Name(--restart unless-stopped 已生效) - 5 层 DinD L3/L5:status 综合检查 L3 (harbor) + L4 (daemon) + L5 (mounts) 独立 commit 设计: - C4 不引用 C1/C2/C3(纯查询) - C4 不修改 bootstrap.sh(A+B 已固化) - C4 修改 Dockerfile.agent-runtime(COPY 顺序独立,合并时无冲突) - C1/C2/C3 可独立 commit 兼容性: - 容器内调用:不传 --container-name(自动用内层 docker) - host 调用:不传 --container-name → auto-detect 找名字含 ais_bench_agent 或 swebench-orchestrator 的 running 容器 - 找不到容器 → "[错误] 没找到 runtime 容器,显式传 --container-name" - mount 检查用 mount | grep(内层)或 docker exec du(外层),跨容器类型兼容 --- docker/agent_runtime/Dockerfile.agent-runtime | 10 +- .../ais_bench_agent_orchestrator_status.sh | 187 ++++++++++++++++++ 2 files changed, 191 insertions(+), 6 deletions(-) create mode 100755 docker/agent_runtime/ais_bench_agent_orchestrator_status.sh diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index 913bedb7..ef6d43ff 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -257,12 +257,10 @@ COPY docker/agent_runtime/doctor.sh /usr/local/bin/ais_bench_agent_doctor.sh RUN chmod +x /usr/local/bin/ais_bench_agent_doctor.sh # v3 C 批: 5 层 DinD L3 调度脚本 -# C3: ais_bench_agent_summarize.sh — 聚合 jobs/*/result.json → md/csv/json -# (C1/C2/C4 在各自 commit 中加入,这里只放 C3) -COPY docker/agent_runtime/ais_bench_agent_summarize.sh /usr/local/bin/ais_bench_agent_summarize.sh -RUN chmod +x /usr/local/bin/ais_bench_agent_summarize.sh -COPY docker/agent_runtime/scripts/summarize.py /usr/local/bin/ais_bench_agent_summarize.py -RUN chmod +x /usr/local/bin/ais_bench_agent_summarize.py +# C4: ais_bench_agent_orchestrator_status.sh — 容器状态查询(5 段输出) +# (C1/C2/C3 在各自 commit 中加入,这里只放 C4) +COPY docker/agent_runtime/ais_bench_agent_orchestrator_status.sh /usr/local/bin/ais_bench_agent_orchestrator_status.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_orchestrator_status.sh COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ diff --git a/docker/agent_runtime/ais_bench_agent_orchestrator_status.sh b/docker/agent_runtime/ais_bench_agent_orchestrator_status.sh new file mode 100755 index 00000000..efd6ddb4 --- /dev/null +++ b/docker/agent_runtime/ais_bench_agent_orchestrator_status.sh @@ -0,0 +1,187 @@ +#!/bin/bash +# ============================================================================ +# ais_bench_agent_orchestrator_status.sh — 5 层 DinD runtime 容器状态查询 +# ============================================================================ +# +# 设计: +# - 一键查询 runtime 容器的健康状态(5 层 DinD L5 launcher 拉的容器) +# - 输出 5 段:容器基础 / 内层 docker / bind mount 可见性 / jobs 数量 / harbor jobs +# - 既可在容器内跑(用内层 docker CLI),也可在 host 跑(用 docker exec) +# +# 用法: +# ais_bench_agent_orchestrator_status.sh # 自动检测 +# ais_bench_agent_orchestrator_status.sh --container-name X # 指定 runtime 容器 +# ais_bench_agent_orchestrator_status.sh --jobs-dir /opt/swebench/jobs +# +# 适用: +# - 5 层 DinD L3/L4/L5 健康检查 +# - bootstrap 完成后第一次验证 +# - 跑 harbor jobs start 前的快速自检 +# +# ============================================================================ +set -euo pipefail + +# ============ 默认值 ============ +JOBS_DIR="/opt/swebench/jobs" +API_KEY_FILE="/opt/swebench/api_key.env" +DOCKER_SOCK="/var/run/docker.sock" + +# ============ 参数解析 ============ +CONTAINER_NAME="" +while [[ $# -gt 0 ]]; do + case "$1" in + --container-name) CONTAINER_NAME="$2"; shift 2 ;; + --jobs-dir) JOBS_DIR="$2"; shift 2 ;; + -h|--help) + grep '^#' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) echo "[错误] 未知参数: $1" >&2; exit 1 ;; + esac +done + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +section() { + echo "" + echo "─── $* ───" +} + +# ============ 自动检测调用上下文 ============ +# 检测当前 shell 是否在 runtime 容器内: +# - /var/run/docker.sock 可访问(内层 daemon 或宿主 socket) +# - /opt/swebench 路径下有 host bind mount 内容 +INSIDE_CONTAINER=0 +if [ -S "$DOCKER_SOCK" ] && [ -d /opt/swebench ]; then + # 检查 /opt/swebench 是否真是 bind mount(有内容或可访问) + # (PR #410 的 image 默认 /opt/swebench 不存在,由 A5 预创建 + B3 bind mount 内容) + if mount | grep -q "/opt/swebench" 2>/dev/null || [ -n "$(ls -A /opt/swebench 2>/dev/null)" ]; then + INSIDE_CONTAINER=1 + fi +fi + +if [ "$INSIDE_CONTAINER" = "1" ]; then + log "=== ais_bench_agent_orchestrator_status.sh(容器内视角)===" + # 容器内:直接用内层 docker CLI + DOCKER_CMD=(docker) + # jobs dir / api key 等用 bind mount 的容器内路径 + EFFECTIVE_JOBS_DIR="$JOBS_DIR" + EFFECTIVE_API_KEY="$API_KEY_FILE" +else + log "=== ais_bench_agent_orchestrator_status.sh(host 视角)===" + # host:自动找 runtime 容器(第一个名字含 ais_bench_agent 的 running 容器) + if [ -z "$CONTAINER_NAME" ]; then + CONTAINER_NAME=$(docker ps --format '{{.Names}}' 2>/dev/null \ + | grep -E 'ais_bench_agent|swebench-orchestrator' | head -1 || true) + if [ -z "$CONTAINER_NAME" ]; then + log "[错误] 没找到 runtime 容器,显式传 --container-name" + log " docker ps -a | grep ais_bench" + exit 1 + fi + log " auto-detected container: $CONTAINER_NAME" + fi + # host:docker exec 进容器跑内层检查 + DOCKER_CMD=(docker exec "$CONTAINER_NAME") + EFFECTIVE_JOBS_DIR="$JOBS_DIR" # 同路径(jobs bind mount 在两边都可见) + EFFECTIVE_API_KEY="$API_KEY_FILE" +fi + +# ============ 1. 容器基础状态 ============ +section "1. 容器基础" +if [ "$INSIDE_CONTAINER" = "1" ]; then + log " hostname: $(hostname)" + log " uptime: $(uptime -p 2>/dev/null || uptime)" + log " arch: $(uname -m)" + log " in-container: yes" +else + log " container: $CONTAINER_NAME" + log " state: $(docker inspect --format '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo '?')" + log " uptime: $(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null || echo '?')" + log " image: $(docker inspect --format '{{.Config.Image}}' "$CONTAINER_NAME" 2>/dev/null || echo '?')" + log " restart: $(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$CONTAINER_NAME" 2>/dev/null || echo '?')" +fi + +# ============ 2. 内层 docker daemon ============ +section "2. 内层 docker daemon" +if [ "$INSIDE_CONTAINER" = "1" ]; then + if command -v docker >/dev/null 2>&1; then + log " docker: $(docker --version 2>/dev/null || echo 不可用)" + if docker info >/dev/null 2>&1; then + log " server-ver: $(docker info --format '{{.ServerVersion}}' 2>/dev/null || echo '?')" + log " storage: $(docker info --format '{{.Driver}}' 2>/dev/null || echo '?')" + log " cgroup: $(docker info --format '{{.CgroupDriver}}' 2>/dev/null || echo '?')" + log " containers: $(docker ps -q 2>/dev/null | wc -l) running, $(docker ps -aq 2>/dev/null | wc -l) total" + log " images: $(docker images -q 2>/dev/null | wc -l)" + log " default-platform: ${DOCKER_DEFAULT_PLATFORM:-未设置}" + else + log " [错误] docker info 失败,daemon 可能未启动" + fi + else + log " [错误] docker 命令不可用" + fi +else + log " (host 视角无法直接查内层 daemon,用 docker exec 进入查看)" + log " docker exec $CONTAINER_NAME docker info" +fi + +# ============ 3. bind mount 可见性 ============ +section "3. bind mount 可见性" +for p in /opt/swebench/jobs /opt/swebench/tasks /opt/swebench/config /opt/swebench/logs; do + if [ -d "$p" ]; then + SIZE=$("${DOCKER_CMD[@]}" du -sh "$p" 2>/dev/null | cut -f1 || echo "?") + FILES=$("${DOCKER_CMD[@]}" find "$p" -maxdepth 2 -type f 2>/dev/null | wc -l || echo 0) + log " ✓ $p (size=$SIZE, files=$FILES)" + else + log " ✗ $p 不存在" + fi +done + +# api_key.env 检查 +if "${DOCKER_CMD[@]}" test -f "$EFFECTIVE_API_KEY" 2>/dev/null; then + log " ✓ api_key.env: $EFFECTIVE_API_KEY" + KEY_LEN=$("${DOCKER_CMD[@]}" bash -c "grep -c OPENAI_API_KEY= $EFFECTIVE_API_KEY" 2>/dev/null || echo 0) + log " OPENAI_API_KEY lines: $KEY_LEN" +else + log " ✗ api_key.env: $EFFECTIVE_API_KEY 不存在" +fi + +# ============ 4. jobs 数量 ============ +section "4. jobs 目录状态" +if "${DOCKER_CMD[@]}" test -d "$EFFECTIVE_JOBS_DIR" 2>/dev/null; then + N_JOBS=$("${DOCKER_CMD[@]}" find "$EFFECTIVE_JOBS_DIR" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l || echo 0) + N_DONE=$("${DOCKER_CMD[@]}" find "$EFFECTIVE_JOBS_DIR" -maxdepth 2 -name 'result.json' 2>/dev/null | wc -l || echo 0) + N_RUN=$("${DOCKER_CMD[@]}" find "$EFFECTIVE_JOBS_DIR" -maxdepth 2 -name 'config.json' ! -exec test -e '{}/result.json' \; 2>/dev/null | wc -l || echo 0) + log " total jobs: $N_JOBS" + log " with result.json: $N_DONE (completed)" + log " running (估): $N_RUN" + if [ "$N_JOBS" -gt 0 ]; then + log " latest jobs:" + "${DOCKER_CMD[@]}" bash -c "ls -1dt $EFFECTIVE_JOBS_DIR/*/ 2>/dev/null" | head -3 \ + | while read -r jd; do + log " - $(basename "$jd")" + done + fi +else + log " ✗ jobs-dir 不存在: $EFFECTIVE_JOBS_DIR" +fi + +# ============ 5. harbor CLI 可用性 ============ +section "5. harbor CLI" +if "${DOCKER_CMD[@]}" command -v harbor >/dev/null 2>&1; then + HARBOR_VER=$("${DOCKER_CMD[@]}" harbor --version 2>&1 | head -1 || echo "?") + log " ✓ harbor: $HARBOR_VER" + N_JOB_NAMES=$("${DOCKER_CMD[@]}" bash -c "harbor jobs list 2>/dev/null | wc -l" 2>/dev/null || echo "?") + log " harbor jobs: $N_JOB_NAMES 行(可读=正常)" +else + log " ✗ harbor CLI 不可用" + log " 提示:bootstrap 没装 harbor?(v3 A2 commit 应已装 0.20.x)" +fi + +# ============ 收尾 ============ +section "✓ status 完成" +log "如需追踪 trial:" +log " ais_bench_agent_watch.sh " +log " ais_bench_agent_summarize.sh" +log "" +log "如需启动新 trial:" +log " ais_bench_agent_run.sh [--datasets 11099,12308] [--agents oracle]" \ No newline at end of file From d65f44d0cdaa6a6e1af47294de757040b36215c3 Mon Sep 17 00:00:00 2001 From: SJTUyh Date: Tue, 25 Aug 2026 19:29:09 +0800 Subject: [PATCH 28/36] =?UTF-8?q?D1:=20build=5Fl2=5Fbaked=5Fimage.sh=20?= =?UTF-8?q?=E2=80=94=205=20=E5=B1=82=20DinD=20L1/L2=20baked=20image=20?= =?UTF-8?q?=E4=B8=80=E9=94=AE=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3 Batch D 第一步:实现 L1 (case-base) + L2 (agent) baked image 一键构建脚本。 新增文件 1 个: docker/agent_runtime/build_l2_baked_image.sh 220+ 行 Dockerfile.agent-runtime 改动 (+3 行): - COPY build_l2_baked_image.sh → /usr/local/bin/ais_bench_agent_build_l2_baked_image.sh - chmod +x 脚本设计要点: 1. 模板自动检测: - 容器内:/opt/swebench/dockerfiles/(D2 COPY 注入) - host :./docker/agent_runtime/dockerfiles/(git checkout) - 都不是 → 报错退出 2. 默认参数对齐 batch B 的 bind mount: - --tasks-dir 默认 /opt/swebench/tasks(B --bind-tasks 注入) - --dockerfiles-root 默认 /opt/swebench/dockerfiles(D2 COPY) - 模板内不直接用 tasks-dir(仅留作参考) 3. Image tag 命名沿用 mini_matrix 规范: - L1:swebench/--base:latest - L2:swebench/--with-:latest 4. 上游 prebuilt 解析: - 简单约定:/swebench/sweb.eval.x86_64._1776_-:latest - 默认 registry=docker.1ms.run(用户可 --registry 改) - 复杂 dataset id 映射留给用户在 matrix.yaml 里显式覆盖 5. 并行 build: - L1 N 个 case 同时 docker build & - L2 (N case × M agent) 同时 docker build & - wait 全部完成 - 已存在的 tag 自动 skip (用 docker images grep) 6. ARM64 默认: - 默认 --platform linux/amd64(QEMU + binfmt 已在 A1 装) - --skip-arm64 标志:x86 host 关掉 7. CLI 灵活性: - --case 11099 12308 单 case 全 agent - --agent aider msa 所有 case 同一 agent - --l1-only / --l2-only 分阶段 - --dataset-prefix 改 dataset 名 - --registry 改 prebuilt registry 8. 容错: - 缺模板 → 报错并列出哪个 agent 缺 - docker 不可用 → fail - L1 build 失败 → 打印最后 5 行 log + exit 1 - L2 build 失败 → 继续跑其他 + 末段打印 fail 列表 与 batch A/B 决策一致性: - J3 L2 baked image:D1 + D2 落地 L1/L2 build,D3 bootstrap --l2-image 接入 - A1 ARM64 QEMU:docker build --platform linux/amd64 依赖 A1 - B --bind-tasks:tasks 目录由 B 注入(虽然 D1 不直接用,留 ref) 独立 commit 设计: - D1 不引用 D3 --l2-image(纯 build) - D1 可独立运行(用户手 docker run 也行) - D1 修改 Dockerfile.agent-runtime(COPY 顺序独立,与 D2 紧邻但 merge 时无冲突) 兼容性: - 与 mini_matrix build_baked_v2.sh 行为等价,但接口更通用 - 不依赖 jinja2 CLI(模板里没有 jinja 语法,只是 bash + ARG) - 用户既可容器内也可 host 跑 - 已 build 的 image 自动 skip,幂等 --- docker/agent_runtime/Dockerfile.agent-runtime | 4 + docker/agent_runtime/build_l2_baked_image.sh | 223 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100755 docker/agent_runtime/build_l2_baked_image.sh diff --git a/docker/agent_runtime/Dockerfile.agent-runtime b/docker/agent_runtime/Dockerfile.agent-runtime index ef6d43ff..c6b17ae0 100644 --- a/docker/agent_runtime/Dockerfile.agent-runtime +++ b/docker/agent_runtime/Dockerfile.agent-runtime @@ -264,6 +264,10 @@ RUN chmod +x /usr/local/bin/ais_bench_agent_orchestrator_status.sh COPY docker/agent_runtime/packs/ ${AGENT_PACKS_ROOT}/ +# v3 D1: build_l2_baked_image.sh — 一键 build L1 + L2 baked images +COPY docker/agent_runtime/build_l2_baked_image.sh /usr/local/bin/ais_bench_agent_build_l2_baked_image.sh +RUN chmod +x /usr/local/bin/ais_bench_agent_build_l2_baked_image.sh + # ============================================================ # 便捷函数:激活对应 venv # 用户在容器内 `agent_env harbor` 即可激活 harbor venv, diff --git a/docker/agent_runtime/build_l2_baked_image.sh b/docker/agent_runtime/build_l2_baked_image.sh new file mode 100755 index 00000000..14d87b78 --- /dev/null +++ b/docker/agent_runtime/build_l2_baked_image.sh @@ -0,0 +1,223 @@ +#!/bin/bash +# ============================================================================ +# build_l2_baked_image.sh — 5 层 DinD L1/L2 baked image 一键构建 +# ============================================================================ +# +# 设计: +# - 把 mini_matrix 5 层 DinD L1 (case-base) + L2 (agent) baked image 构建 +# 搬到 PR #410 runtime 镜像内,容器内用户可一键 build +# - 模板:/opt/swebench/dockerfiles/Dockerfile.l{1,2}-*.j2 +# - L1:FROM 上游 prebuilt → WORKDIR /testbed + /logs +# - L2:FROM L1 → 装 agent(aider / msa / oh / qwen) +# - 与 mini_matrix scripts/build_baked_v2.sh 等价,但: +# - 默认参数对齐 batch B 的 --bind-tasks 路径(/opt/swebench/tasks) +# - 输出 image tag 命名沿用 mini_matrix 规范 +# - 默认开 ARM64 emulation (QEMU + binfmt) +# +# 用法: +# build_l2_baked_image.sh # 9 个 L2 全 build (3 case × 3 agent) +# build_l2_baked_image.sh --case 11099 # 单 case 全 agent +# build_l2_baked_image.sh --agent aider # 所有 case 同一 agent +# build_l2_baked_image.sh --l1-only # 只 build L1 (跳过 L2) +# build_l2_baked_image.sh --l2-only # 只 build L2 (假设 L1 已 build) +# build_l2_baked_image.sh --dataset-prefix django # 改 dataset 前缀 +# build_l2_baked_image.sh --registry mirror.aliyuncs.com # 改 prebuilt registry 镜像 +# build_l2_baked_image.sh --skip-arm64 # 不加 --platform linux/amd64 (host 是 x86 时用) +# +# 适用: +# - 容器内调用 (L4 镜像内建 docker,直接 docker build) +# - host 调用:bash docker/agent_runtime/build_l2_baked_image.sh ... +# - 模板路径自动检测:容器内 /opt/swebench/dockerfiles,host 用 ./docker/agent_runtime/dockerfiles/ +# +# ============================================================================ +set -euo pipefail + +# ============ 默认值 ============ +CASES=(11099 12308 13741) +AGENTS=(aider msa oh) +DATASET_PREFIX="django" +TASKS_DIR="/opt/swebench/tasks" # batch B --bind-tasks 注入 +DOCKERFILES_ROOT="/opt/swebench/dockerfiles" # batch D2 注入 +DEFAULT_REGISTRY="docker.1ms.run" +PLATFORM_FLAG="--platform linux/amd64" # ARM64 host 默认需要 +LOGDIR="/tmp/baked-builds-v3" +BUILD_L1=1 +BUILD_L2=1 + +# ============ 参数解析 ============ +while [[ $# -gt 0 ]]; do + case "$1" in + --case) shift; CASES=("$@"); break ;; + --agent) shift; AGENTS=("$@"); break ;; + --l1-only) BUILD_L2=0; shift ;; + --l2-only) BUILD_L1=0; shift ;; + --dataset-prefix) DATASET_PREFIX="$2"; shift 2 ;; + --tasks-dir) TASKS_DIR="$2"; shift 2 ;; + --registry) DEFAULT_REGISTRY="$2"; shift 2 ;; + --skip-arm64) PLATFORM_FLAG=""; shift ;; + --logdir) LOGDIR="$2"; shift 2 ;; + -h|--help) + grep '^#' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) echo "[错误] 未知参数: $1" >&2; exit 1 ;; + esac +done + +# ============ 工具函数 ============ +log() { echo "[$(date +%H:%M:%S)] $*"; } +fail() { echo "[错误] $*" >&2; exit 1; } + +# ============ 自动检测 host vs 容器内 ============ +if [ -d "$DOCKERFILES_ROOT" ]; then + # 容器内视角:模板已由 D2 commit COPY 到 /opt/swebench/dockerfiles + ACTIVE_DOCKERFILES_ROOT="$DOCKERFILES_ROOT" + log "[context] 容器内视角,模板路径: $ACTIVE_DOCKERFILES_ROOT" +elif [ -d "./docker/agent_runtime/dockerfiles" ]; then + # host 视角:从仓库根目录跑 + ACTIVE_DOCKERFILES_ROOT="./docker/agent_runtime/dockerfiles" + log "[context] host 视角,模板路径: $ACTIVE_DOCKERFILES_ROOT" +else + fail "找不到 dockerfiles/ 目录(容器内应 /opt/swebench/dockerfiles,host 应 ./docker/agent_runtime/dockerfiles)" +fi + +# ============ 检查 docker 可用 ============ +command -v docker >/dev/null 2>&1 || fail "docker 命令不可用(需在 L4 runtime 镜像内或 host docker daemon 上)" +docker info >/dev/null 2>&1 || fail "docker daemon 不可达(确认 dockerd 已启动)" + +mkdir -p "$LOGDIR" + +# ============ 解析 case → 上游 prebuilt image ============ +# 命名规范(沿用 mini_matrix): +# 上游:swebench/sweb.eval..__:latest +# 例如:docker.1ms.run/swebench/sweb.eval.x86_64.django_1776_django-11099:latest +# +# 这里采用简单约定:_,实际项目里可能需要映射表 +prebuilt_for_case() { + local case_id="$1" + echo "${DEFAULT_REGISTRY}/swebench/sweb.eval.x86_64.${DATASET_PREFIX}_1776_${DATASET_PREFIX}-${case_id}:latest" +} + +# ============ 校验模板存在 ============ +[ -f "$ACTIVE_DOCKERFILES_ROOT/Dockerfile.l1-base.j2" ] \ + || fail "缺 L1 模板: $ACTIVE_DOCKERFILES_ROOT/Dockerfile.l1-base.j2" +for agent in "${AGENTS[@]}"; do + [ -f "$ACTIVE_DOCKERFILES_ROOT/Dockerfile.l2-agent-${agent}.j2" ] \ + || fail "缺 L2 模板 (agent=$agent): $ACTIVE_DOCKERFILES_ROOT/Dockerfile.l2-agent-${agent}.j2" +done + +# ============ 检查 TASKS_DIR (L1 需要 /testbed 上下文,但实际 L1 只用 prebuilt image) ============ +# L1 j2 模板不需要 host 上下文,只需 docker build -f