featurebench 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {featurebench-0.2.0 → featurebench-0.2.2}/PKG-INFO +8 -4
- {featurebench-0.2.0 → featurebench-0.2.2}/README.md +6 -2
- {featurebench-0.2.0 → featurebench-0.2.2}/config_example.toml +12 -2
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/config.md +15 -2
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/harness_cli_arg.md +5 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/infer_cli_arg.md +19 -4
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/cli.py +3 -1
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/docker/image_manager.py +176 -14
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/container.py +4 -2
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/review_codes.py +5 -1
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/run_evaluation.py +460 -29
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/mini_swe_agent.py +16 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/openhands.py +358 -36
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/config.py +90 -11
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/container.py +11 -6
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/models.py +18 -1
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/run_infer.py +394 -21
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/pipeline.py +4 -1
- {featurebench-0.2.0 → featurebench-0.2.2}/pyproject.toml +1 -1
- featurebench-0.2.2/tests/test_dataset_version.py +212 -0
- featurebench-0.2.2/tests/test_eval_data_container_cleanup.py +339 -0
- featurebench-0.2.2/tests/test_infer_container_cleanup.py +116 -0
- featurebench-0.2.2/tests/test_openhands_session_cache.py +97 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/uv.lock +1 -1
- {featurebench-0.2.0 → featurebench-0.2.2}/.gitignore +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/.python-version +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/LICENSE +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/pics/logo.png +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/pics/workflow.png +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/docs/pipeline.md +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/classification/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/classification/code_classifier.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/classification/file_analyzer.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/classification/llm_top_classifier.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/classification/p2p_chooser.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/case_converter.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/eval_code.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/llm_prompt_generator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/llm_testfile_adjuster.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/conversion/render_task.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/docker/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/docker/dynamic_tracer.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/docker/test_runner.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/docker/test_scanner.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/constants.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/grading.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/report.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/runtime.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/test_parsers.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/harness/utils.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/base.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/claude_code.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/codex.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/agents/gemini_cli.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/gpu_scheduler.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/output.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/render_infer_log.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/infer/runtime.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/llm/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/llm/llm_caller.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/llm/llm_exceptions.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/mask/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/mask/mask_generator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/mask/mask_validator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/mask/signature_extractor.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/post_verify/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/post_verify/f2p_validator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/post_verify/level2_validator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/post_verify/p2p_validator.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/constants/fast_images.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/constants/full_images.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/constants/lite_images.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/constants/python_example.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python310.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python310_cu118_torch212.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python310_cu121_torch251.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python310_cu121_torch28.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python311.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python311_cu121_torch28.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python312.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python312_cu121_torch251.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python312_cu121_torch26.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python312_cu121_torch28.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python312_torch251.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/dockerfiles/python39.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/dynamic_script/autograd_backward_patch.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/dynamic_script/dynamic_script.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/dynamic_script/import_hook_patch.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/dynamic_script/joblib_patch.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/dynamic_script/sitecustomize.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/scripts/scanner_script.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/appendix.md.j2 +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/black_links.txt.j2 +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/config_template.yaml +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/level_1.j2 +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/level_2.j2 +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/resources/templates/llm_prompt_templates.yaml +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/scripts/cal_eval_outputs.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/scripts/post_filter_cases.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/scripts/pull_images.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/command_utils.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/config.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/docker_images.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/logger.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/parser/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/parser/pytest_parser.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/repo_manager.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/storage.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/utils/utils.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/__init__.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/post_additions.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/prompts/add_prompt_lv1.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/prompts/add_prompt_lv2.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/prompts/prompt_template_lv1.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/prompts/prompt_template_lv2.txt +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/run_verification.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/featurebench/verified/runner.py +0 -0
- {featurebench-0.2.0 → featurebench-0.2.2}/tests/test_pull_images.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: featurebench
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: FeatureBench Pipeline - A test-driven data generation pipeline for building and evaluating feature-level coding benchmarks
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -41,6 +41,8 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
41
41
|
|
|
42
42
|
## 📰 News
|
|
43
43
|
|
|
44
|
+
📦 **2026.08.24**: We released **FeatureBench Dataset V1.1**, primarily correcting inconsistencies and omissions in task problem statements.
|
|
45
|
+
|
|
44
46
|
📊 **2026.05.18**: We added **lite split** evaluation results for frontier models including **GPT-5.5, Claude Opus 4.7, DeepSeek-V4, GLM-5.1, Kimi-2.6, Mimo-V2.5-Pro**, and more to the leaderboard.
|
|
45
47
|
|
|
46
48
|
🚀 **2026.03.27**: We released the **fast split** containing 100 instances (a subset of full split). These instances require no GPU and are optimized for rapid evaluation. On an Intel Xeon Platinum 8457C with 944GB RAM, the average evaluation time per instance using gold patches is **57.2 seconds**.
|
|
@@ -75,7 +77,8 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
75
77
|
| 16 | GLM 4.7 | Mini-SWE-Agent | 41.2 | 6.7 |
|
|
76
78
|
| 17 | Qwen3-Coder-480B-A35B-Instruct | OpenHands | 38.3 | 6.7 |
|
|
77
79
|
| 18 | DeepSeek V3.2 | OpenHands | 35.9 | 6.7 |
|
|
78
|
-
| 19 | Qwen3-
|
|
80
|
+
| 19 | Qwen3.5-27B | OpenHands | 34.8 | 10.0 |
|
|
81
|
+
| 20 | Qwen3-Coder-30B-A3B-Instruct | OpenHands | 23 | 3.3 |
|
|
79
82
|
|
|
80
83
|
</details>
|
|
81
84
|
<!-- LEADERBOARD_END -->
|
|
@@ -120,6 +123,7 @@ fb pull --mode /path/to/images.txt # one image name per line
|
|
|
120
123
|
```bash
|
|
121
124
|
fb infer \
|
|
122
125
|
--config-path config.toml \
|
|
126
|
+
--data-version v1.1 \
|
|
123
127
|
--agent mini_swe_agent \
|
|
124
128
|
--model openai/qwen3-coder-480b-a35b-instruct \
|
|
125
129
|
--split fast
|
|
@@ -155,4 +159,4 @@ If you found FeatureBench useful, please cite us as:
|
|
|
155
159
|
|
|
156
160
|
## 📧 Contact
|
|
157
161
|
|
|
158
|
-
If you have any questions, feel free to contact [
|
|
162
|
+
If you have any questions, feel free to contact [zhouqixing@stu.cqu.edu.cn](mailto:zhouqixing@stu.cqu.edu.cn) or [zjcheng2022@gmail.com](mailto:zjcheng2022@gmail.com).
|
|
@@ -17,6 +17,8 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
17
17
|
|
|
18
18
|
## 📰 News
|
|
19
19
|
|
|
20
|
+
📦 **2026.08.24**: We released **FeatureBench Dataset V1.1**, primarily correcting inconsistencies and omissions in task problem statements.
|
|
21
|
+
|
|
20
22
|
📊 **2026.05.18**: We added **lite split** evaluation results for frontier models including **GPT-5.5, Claude Opus 4.7, DeepSeek-V4, GLM-5.1, Kimi-2.6, Mimo-V2.5-Pro**, and more to the leaderboard.
|
|
21
23
|
|
|
22
24
|
🚀 **2026.03.27**: We released the **fast split** containing 100 instances (a subset of full split). These instances require no GPU and are optimized for rapid evaluation. On an Intel Xeon Platinum 8457C with 944GB RAM, the average evaluation time per instance using gold patches is **57.2 seconds**.
|
|
@@ -51,7 +53,8 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
51
53
|
| 16 | GLM 4.7 | Mini-SWE-Agent | 41.2 | 6.7 |
|
|
52
54
|
| 17 | Qwen3-Coder-480B-A35B-Instruct | OpenHands | 38.3 | 6.7 |
|
|
53
55
|
| 18 | DeepSeek V3.2 | OpenHands | 35.9 | 6.7 |
|
|
54
|
-
| 19 | Qwen3-
|
|
56
|
+
| 19 | Qwen3.5-27B | OpenHands | 34.8 | 10.0 |
|
|
57
|
+
| 20 | Qwen3-Coder-30B-A3B-Instruct | OpenHands | 23 | 3.3 |
|
|
55
58
|
|
|
56
59
|
</details>
|
|
57
60
|
<!-- LEADERBOARD_END -->
|
|
@@ -96,6 +99,7 @@ fb pull --mode /path/to/images.txt # one image name per line
|
|
|
96
99
|
```bash
|
|
97
100
|
fb infer \
|
|
98
101
|
--config-path config.toml \
|
|
102
|
+
--data-version v1.1 \
|
|
99
103
|
--agent mini_swe_agent \
|
|
100
104
|
--model openai/qwen3-coder-480b-a35b-instruct \
|
|
101
105
|
--split fast
|
|
@@ -131,4 +135,4 @@ If you found FeatureBench useful, please cite us as:
|
|
|
131
135
|
|
|
132
136
|
## 📧 Contact
|
|
133
137
|
|
|
134
|
-
If you have any questions, feel free to contact [
|
|
138
|
+
If you have any questions, feel free to contact [zhouqixing@stu.cqu.edu.cn](mailto:zhouqixing@stu.cqu.edu.cn) or [zjcheng2022@gmail.com](mailto:zjcheng2022@gmail.com).
|
|
@@ -6,6 +6,14 @@ HF_ENDPOINT=""
|
|
|
6
6
|
HF_TOKEN=""
|
|
7
7
|
GITHUB_TOKEN=""
|
|
8
8
|
|
|
9
|
+
# ==============================================================================
|
|
10
|
+
# Hugging Face Dataset
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
[dataset]
|
|
13
|
+
# Dataset tag, branch, or commit SHA used by both `fb infer` and `fb eval`.
|
|
14
|
+
# The CLI flag `--data-version` overrides this value.
|
|
15
|
+
revision="v1.1"
|
|
16
|
+
|
|
9
17
|
# ==============================================================================
|
|
10
18
|
# DataPipeline Configuration
|
|
11
19
|
# ==============================================================================
|
|
@@ -53,13 +61,15 @@ LLM_API_KEY = ""
|
|
|
53
61
|
# Optional: LLM API Base URL
|
|
54
62
|
LLM_BASE_URL = ""
|
|
55
63
|
# Optional: Lock OpenHands version (leave empty to use latest)
|
|
56
|
-
OPENHANDS_VERSION = "
|
|
64
|
+
OPENHANDS_VERSION = ""
|
|
57
65
|
# Optional: Reasoning effort for OpenAI o-series models
|
|
58
66
|
LLM_REASONING_EFFORT = ""
|
|
59
67
|
# Optional: Force OpenHands native tool calling ("true" or "false"; empty uses OpenHands default)
|
|
60
68
|
LLM_NATIVE_TOOL_CALLING = ""
|
|
61
69
|
# Optional: Send prior assistant reasoning_content back to OpenHands requests
|
|
62
70
|
LLM_SEND_REASONING_CONTENT = ""
|
|
71
|
+
# Optional: JSON object passed to LiteLLM extra_body, e.g. {"enable_thinking": true}
|
|
72
|
+
LLM_LITELLM_EXTRA_BODY = ""
|
|
63
73
|
# Azure API Version (required for Azure models only)
|
|
64
74
|
LLM_API_VERSION = ""
|
|
65
75
|
# Optional: OpenHands agent max iterations (step limit). Upstream default is 500.
|
|
@@ -107,4 +117,4 @@ MSWEA_BASE_URL = ""
|
|
|
107
117
|
# Optional: Cost tracking mode (default behavior in FB adapter is ignore_errors)
|
|
108
118
|
MSWEA_COST_TRACKING = ""
|
|
109
119
|
# Optional: Lock mini_swe_agent version (leave empty to use latest)
|
|
110
|
-
MINI_SWE_AGENT_VERSION = ""
|
|
120
|
+
MINI_SWE_AGENT_VERSION = ""
|
|
@@ -31,13 +31,25 @@ HF_TOKEN = ""
|
|
|
31
31
|
GITHUB_TOKEN = ""
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
### [dataset] (dataset version used by harness / infer)
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[dataset]
|
|
38
|
+
revision = "v1.1"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `revision`: Hugging Face dataset tag, branch, or commit SHA used by both
|
|
42
|
+
`fb infer` and `fb eval`. The default is `v1.1`.
|
|
43
|
+
- `--data-version` overrides this value for a single command.
|
|
44
|
+
|
|
34
45
|
---
|
|
35
46
|
|
|
36
47
|
## A. Harness
|
|
37
48
|
|
|
38
49
|
The core idea of harness is: read the `output.jsonl` produced by infer, apply each patch to the corresponding repository image, run tests, and generate evaluation results.
|
|
39
50
|
|
|
40
|
-
When harness loads the HuggingFace dataset during evaluation, it uses `HF_ENDPOINT`
|
|
51
|
+
When harness loads the HuggingFace dataset during evaluation, it uses `HF_ENDPOINT`
|
|
52
|
+
from `[env_vars]` and the pinned revision from `[dataset]`.
|
|
41
53
|
|
|
42
54
|
---
|
|
43
55
|
|
|
@@ -77,6 +89,7 @@ INFER_LOG_RENDER_MODE = "compact" # Optional: compact|full for infer.log renderi
|
|
|
77
89
|
LLM_REASONING_EFFORT = "" # Optional: Reasoning effort for OpenAI o-series models
|
|
78
90
|
LLM_NATIVE_TOOL_CALLING = "" # Optional: true/false to force OpenHands native tool calling; empty uses OpenHands default
|
|
79
91
|
LLM_SEND_REASONING_CONTENT = "" # Optional: true to send prior assistant reasoning_content in history
|
|
92
|
+
LLM_LITELLM_EXTRA_BODY = "" # Optional: JSON object passed to LiteLLM extra_body, e.g. {"enable_thinking": true}
|
|
80
93
|
OPENHANDS_MAX_ITERATIONS = "" # Optional: OpenHands agent max iterations (step limit). Upstream default is 500.
|
|
81
94
|
|
|
82
95
|
```
|
|
@@ -207,4 +220,4 @@ Example (local vLLM):
|
|
|
207
220
|
[llm.local]
|
|
208
221
|
backend = "vllm"
|
|
209
222
|
base_url = "http://localhost:8080/v1"
|
|
210
|
-
```
|
|
223
|
+
```
|
|
@@ -51,6 +51,11 @@ fb eval \
|
|
|
51
51
|
HuggingFace dataset repo name (e.g., `LiberCoders/FeatureBench`).
|
|
52
52
|
Default: `LiberCoders/FeatureBench`.
|
|
53
53
|
|
|
54
|
+
- `--data-version`
|
|
55
|
+
Hugging Face dataset tag, branch, or commit SHA.
|
|
56
|
+
Normally inferred from the inference run's `run_metadata.json`.
|
|
57
|
+
If unavailable, falls back to `[dataset].revision` or `v1.1`.
|
|
58
|
+
|
|
54
59
|
- `--split`
|
|
55
60
|
Dataset split name (e.g., `lite`, `full`).
|
|
56
61
|
Default: `full`.
|
|
@@ -6,6 +6,7 @@ This document describes all CLI arguments supported by `fb infer`.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
fb infer \
|
|
9
|
+
--data-version v1.1 \
|
|
9
10
|
--agent gemini_cli \
|
|
10
11
|
--model gemini-3-pro-preview
|
|
11
12
|
```
|
|
@@ -53,6 +54,12 @@ flags can override metadata (see the argument list below).
|
|
|
53
54
|
HuggingFace dataset repo name (e.g., `LiberCoders/FeatureBench`).
|
|
54
55
|
Default: `LiberCoders/FeatureBench` in non-resume mode.
|
|
55
56
|
|
|
57
|
+
- `--data-version`
|
|
58
|
+
Hugging Face dataset tag, branch, or commit SHA.
|
|
59
|
+
Overrides `[dataset].revision` from `config.toml`; default: `v1.1`.
|
|
60
|
+
Saved into `run_metadata.json`; resume mode keeps the recorded dataset version.
|
|
61
|
+
This is separate from `--version`, which controls the agent software version.
|
|
62
|
+
|
|
56
63
|
- `--split`
|
|
57
64
|
Dataset split name (e.g., `lite`, `full`).
|
|
58
65
|
Default: `full` in non-resume mode. In resume mode, uses metadata.
|
|
@@ -135,9 +142,17 @@ flags can override metadata (see the argument list below).
|
|
|
135
142
|
Force native tool calling off (`LLM_NATIVE_TOOL_CALLING=false`).
|
|
136
143
|
Resume mode: ignored (uses metadata).
|
|
137
144
|
|
|
138
|
-
- `--send-reasoning-content`
|
|
139
|
-
Send prior assistant
|
|
140
|
-
Useful for thinking models whose chat template supports reasoning history.
|
|
145
|
+
- `--send-reasoning-content`
|
|
146
|
+
Send prior assistant reasoning back to the model in subsequent OpenHands requests using both `reasoning_content` and `reasoning` fields.
|
|
147
|
+
Useful for thinking models whose chat template supports reasoning history.
|
|
148
|
+
Resume mode: ignored (uses metadata).
|
|
149
|
+
|
|
150
|
+
- `--litellm-extra-body`
|
|
151
|
+
Pass a JSON object to OpenHands `LLM.litellm_extra_body`, for example `--litellm-extra-body '{"enable_thinking": true}'`.
|
|
152
|
+
Resume mode: ignored (uses metadata).
|
|
153
|
+
|
|
154
|
+
- `--session-cache`
|
|
155
|
+
Enable backend KV-cache session reuse for OpenHands by sending a unique `X-Session-Id` header for each task attempt and releasing it after the run with `DELETE /session_release?session_id=...`.
|
|
141
156
|
Resume mode: ignored (uses metadata).
|
|
142
157
|
|
|
143
158
|
- `--max-iters`
|
|
@@ -158,4 +173,4 @@ runs/{timestamp}/
|
|
|
158
173
|
├── infer.log # Agent execution log
|
|
159
174
|
├── run.log # Runtime log
|
|
160
175
|
└── patch.diff # Generated patch
|
|
161
|
-
```
|
|
176
|
+
```
|
|
@@ -31,7 +31,7 @@ def _print_help(stream=None) -> None:
|
|
|
31
31
|
" data Run data pipeline (supports featurebench-pipeline args)\n"
|
|
32
32
|
"\n"
|
|
33
33
|
"Examples:\n"
|
|
34
|
-
" fb infer --agent mini_swe_agent --model openai/gpt-4o --split fast\n"
|
|
34
|
+
" fb infer --data-version v1.1 --agent mini_swe_agent --model openai/gpt-4o --split fast\n"
|
|
35
35
|
" fb eval --predictions-path runs/xxx/output.jsonl --split fast\n"
|
|
36
36
|
" fb pull --mode fast\n"
|
|
37
37
|
" fb data --config-path constants/python_new.py --output-dir runs/data\n"
|
|
@@ -56,6 +56,8 @@ def _run_with_patched_argv(
|
|
|
56
56
|
if result is None:
|
|
57
57
|
return 0
|
|
58
58
|
return int(result)
|
|
59
|
+
except KeyboardInterrupt:
|
|
60
|
+
return 130
|
|
59
61
|
except SystemExit as exc:
|
|
60
62
|
code = exc.code
|
|
61
63
|
if code is None:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import atexit
|
|
2
3
|
import subprocess
|
|
3
4
|
import tempfile
|
|
4
5
|
import os
|
|
@@ -6,6 +7,8 @@ import hashlib
|
|
|
6
7
|
import platform
|
|
7
8
|
import importlib.util
|
|
8
9
|
import shutil
|
|
10
|
+
import signal
|
|
11
|
+
import threading
|
|
9
12
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
13
|
from typing import Dict, Optional, List, Tuple
|
|
11
14
|
from pathlib import Path
|
|
@@ -24,6 +27,11 @@ from featurebench.utils.logger import (
|
|
|
24
27
|
from featurebench.utils.utils import select_candidate_pool_and_allocate_gpu, release_gpu
|
|
25
28
|
|
|
26
29
|
|
|
30
|
+
FEATUREBENCH_RUN_LABEL = "featurebench.run"
|
|
31
|
+
FEATUREBENCH_KIND_LABEL = "featurebench.kind"
|
|
32
|
+
FEATUREBENCH_TASK_LABEL = "featurebench.task"
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
class ImageManager:
|
|
28
36
|
"""Docker image manager."""
|
|
29
37
|
|
|
@@ -52,6 +60,159 @@ class ImageManager:
|
|
|
52
60
|
|
|
53
61
|
# Track GPUs per container: {container_id: [gpu_id, ...]}
|
|
54
62
|
self._container_gpu_map: Dict[str, List[int]] = {}
|
|
63
|
+
|
|
64
|
+
# Track containers created by fb data so Ctrl+C/process-exit cleanup can
|
|
65
|
+
# remove them even if worker threads do not reach their own finally block.
|
|
66
|
+
self._cleanup_run_id = f"data-{int(time.time() * 1000)}-{os.getpid()}"
|
|
67
|
+
self._active_container_ids_lock = threading.RLock()
|
|
68
|
+
self._active_container_ids: set[str] = set()
|
|
69
|
+
self._cleanup_lock = threading.RLock()
|
|
70
|
+
self._cleanup_in_progress = False
|
|
71
|
+
self._cleanup_interrupt_notice_printed = False
|
|
72
|
+
self._previous_sigint = None
|
|
73
|
+
self._signal_cleanup_installed = False
|
|
74
|
+
if threading.current_thread() is threading.main_thread():
|
|
75
|
+
self._previous_sigint = signal.getsignal(signal.SIGINT)
|
|
76
|
+
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
77
|
+
self._signal_cleanup_installed = True
|
|
78
|
+
self._atexit_cleanup = self._cleanup_active_containers_at_exit
|
|
79
|
+
atexit.register(self._atexit_cleanup)
|
|
80
|
+
|
|
81
|
+
def _container_labels(self, specs_name: str, purpose: str = "data") -> Dict[str, str]:
|
|
82
|
+
return {
|
|
83
|
+
FEATUREBENCH_RUN_LABEL: self._cleanup_run_id,
|
|
84
|
+
FEATUREBENCH_KIND_LABEL: "data",
|
|
85
|
+
FEATUREBENCH_TASK_LABEL: str(specs_name),
|
|
86
|
+
"featurebench.purpose": purpose,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def _add_container_label_args(self, docker_command: List[str], specs_name: str) -> None:
|
|
90
|
+
for key, value in self._container_labels(specs_name).items():
|
|
91
|
+
docker_command.extend(["--label", f"{key}={value}"])
|
|
92
|
+
|
|
93
|
+
def _register_container_id(self, container_id: str) -> None:
|
|
94
|
+
if not container_id:
|
|
95
|
+
return
|
|
96
|
+
with self._active_container_ids_lock:
|
|
97
|
+
self._active_container_ids.add(container_id)
|
|
98
|
+
|
|
99
|
+
def _unregister_container_id(self, container_id: str) -> None:
|
|
100
|
+
if not container_id:
|
|
101
|
+
return
|
|
102
|
+
with self._active_container_ids_lock:
|
|
103
|
+
self._active_container_ids.discard(container_id)
|
|
104
|
+
|
|
105
|
+
def _ignore_interrupt_during_cleanup(self, signum, frame) -> None:
|
|
106
|
+
if not self._cleanup_interrupt_notice_printed:
|
|
107
|
+
self._cleanup_interrupt_notice_printed = True
|
|
108
|
+
try:
|
|
109
|
+
self.logger.warning("Cleanup already in progress; ignoring additional Ctrl+C.")
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
def _handle_interrupt(self, signum, frame) -> None:
|
|
114
|
+
try:
|
|
115
|
+
self.logger.warning("Interrupted; cleaning FeatureBench data containers...")
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
self.cleanup_active_containers("keyboard interrupt")
|
|
119
|
+
|
|
120
|
+
previous = self._previous_sigint
|
|
121
|
+
if callable(previous):
|
|
122
|
+
previous(signum, frame)
|
|
123
|
+
elif previous == signal.SIG_DFL:
|
|
124
|
+
raise KeyboardInterrupt
|
|
125
|
+
|
|
126
|
+
def _release_container_gpu(self, container_id: str) -> None:
|
|
127
|
+
gpu_ids = self._container_gpu_map.pop(container_id, None)
|
|
128
|
+
if gpu_ids:
|
|
129
|
+
release_gpu(gpu_ids, self.logger)
|
|
130
|
+
self.logger.debug(f"Container {container_id[:12]} released GPUs {gpu_ids}")
|
|
131
|
+
|
|
132
|
+
def _remove_container_id_best_effort(self, container_id: str) -> bool:
|
|
133
|
+
if not container_id:
|
|
134
|
+
return False
|
|
135
|
+
try:
|
|
136
|
+
self._release_container_gpu(container_id)
|
|
137
|
+
subprocess.run(["docker", "kill", container_id], capture_output=True)
|
|
138
|
+
subprocess.run(["docker", "rm", "-f", container_id], capture_output=True)
|
|
139
|
+
return True
|
|
140
|
+
finally:
|
|
141
|
+
self._unregister_container_id(container_id)
|
|
142
|
+
|
|
143
|
+
def _cleanup_labeled_containers(self) -> int:
|
|
144
|
+
try:
|
|
145
|
+
result = subprocess.run(
|
|
146
|
+
[
|
|
147
|
+
"docker",
|
|
148
|
+
"ps",
|
|
149
|
+
"-aq",
|
|
150
|
+
"--filter",
|
|
151
|
+
f"label={FEATUREBENCH_RUN_LABEL}={self._cleanup_run_id}",
|
|
152
|
+
"--filter",
|
|
153
|
+
f"label={FEATUREBENCH_KIND_LABEL}=data",
|
|
154
|
+
],
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True,
|
|
157
|
+
check=False,
|
|
158
|
+
)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
self.logger.warning(f"Failed to scan FeatureBench data containers by label: {exc}")
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
if result.returncode != 0:
|
|
164
|
+
stderr = (result.stderr or "").strip()
|
|
165
|
+
if stderr:
|
|
166
|
+
self.logger.warning(f"Failed to scan FeatureBench data containers by label: {stderr}")
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
removed = 0
|
|
170
|
+
for container_id in result.stdout.splitlines():
|
|
171
|
+
if self._remove_container_id_best_effort(container_id.strip()):
|
|
172
|
+
removed += 1
|
|
173
|
+
return removed
|
|
174
|
+
|
|
175
|
+
def cleanup_active_containers(self, reason: str) -> int:
|
|
176
|
+
with self._cleanup_lock:
|
|
177
|
+
if self._cleanup_in_progress:
|
|
178
|
+
return 0
|
|
179
|
+
self._cleanup_in_progress = True
|
|
180
|
+
|
|
181
|
+
previous_sigint = None
|
|
182
|
+
signal_replaced = False
|
|
183
|
+
try:
|
|
184
|
+
if threading.current_thread() is threading.main_thread():
|
|
185
|
+
previous_sigint = signal.getsignal(signal.SIGINT)
|
|
186
|
+
signal.signal(signal.SIGINT, self._ignore_interrupt_during_cleanup)
|
|
187
|
+
signal_replaced = True
|
|
188
|
+
|
|
189
|
+
with self._active_container_ids_lock:
|
|
190
|
+
container_ids = list(self._active_container_ids)
|
|
191
|
+
|
|
192
|
+
removed = 0
|
|
193
|
+
if container_ids:
|
|
194
|
+
self.logger.warning(
|
|
195
|
+
f"Cleaning {len(container_ids)} active FeatureBench data container(s) after {reason}..."
|
|
196
|
+
)
|
|
197
|
+
for container_id in container_ids:
|
|
198
|
+
if self._remove_container_id_best_effort(container_id):
|
|
199
|
+
removed += 1
|
|
200
|
+
|
|
201
|
+
removed += self._cleanup_labeled_containers()
|
|
202
|
+
if removed:
|
|
203
|
+
self.logger.warning(f"Removed {removed} FeatureBench data container(s).")
|
|
204
|
+
return removed
|
|
205
|
+
finally:
|
|
206
|
+
if signal_replaced:
|
|
207
|
+
try:
|
|
208
|
+
signal.signal(signal.SIGINT, previous_sigint)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
with self._cleanup_lock:
|
|
212
|
+
self._cleanup_in_progress = False
|
|
213
|
+
|
|
214
|
+
def _cleanup_active_containers_at_exit(self) -> None:
|
|
215
|
+
self.cleanup_active_containers("process exit")
|
|
55
216
|
|
|
56
217
|
def prepare_images(self, repo_manager: RepoManager) -> None:
|
|
57
218
|
"""
|
|
@@ -664,6 +825,7 @@ RUN echo "source /opt/miniconda3/etc/profile.d/conda.sh && conda activate testbe
|
|
|
664
825
|
|
|
665
826
|
# Add runtime config to docker_command and compute env_setup + GPU IDs
|
|
666
827
|
env_setup_cmd, selected_gpu_ids = self._add_docker_runtime_config(docker_command, specs)
|
|
828
|
+
self._add_container_label_args(docker_command, specs_name)
|
|
667
829
|
|
|
668
830
|
# Add image
|
|
669
831
|
docker_command.append(instance_image)
|
|
@@ -695,6 +857,7 @@ RUN echo "source /opt/miniconda3/etc/profile.d/conda.sh && conda activate testbe
|
|
|
695
857
|
)
|
|
696
858
|
|
|
697
859
|
container_id = result.stdout.strip()
|
|
860
|
+
self._register_container_id(container_id)
|
|
698
861
|
|
|
699
862
|
# Record GPUs used by container (if any)
|
|
700
863
|
if selected_gpu_ids:
|
|
@@ -1097,17 +1260,16 @@ RUN echo "source /opt/miniconda3/etc/profile.d/conda.sh && conda activate testbe
|
|
|
1097
1260
|
container_id: Container ID
|
|
1098
1261
|
force: Force kill without graceful stop (use on timeout)
|
|
1099
1262
|
"""
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
subprocess.run(['docker', 'rm', container_id], capture_output=True)
|
|
1263
|
+
try:
|
|
1264
|
+
# Release GPUs used by container before stop
|
|
1265
|
+
self._release_container_gpu(container_id)
|
|
1266
|
+
|
|
1267
|
+
if force:
|
|
1268
|
+
# Force kill container (no wait)
|
|
1269
|
+
subprocess.run(['docker', 'kill', container_id], capture_output=True)
|
|
1270
|
+
else:
|
|
1271
|
+
# Graceful stop
|
|
1272
|
+
subprocess.run(['docker', 'stop', container_id], capture_output=True)
|
|
1273
|
+
subprocess.run(['docker', 'rm', container_id], capture_output=True)
|
|
1274
|
+
finally:
|
|
1275
|
+
self._unregister_container_id(container_id)
|
|
@@ -190,6 +190,7 @@ class EvalContainerManager:
|
|
|
190
190
|
gpu_ids: str | None = None,
|
|
191
191
|
proxy_port: int | None = None,
|
|
192
192
|
docker_runtime_config: dict | None = None,
|
|
193
|
+
labels: dict[str, str] | None = None,
|
|
193
194
|
) -> Container:
|
|
194
195
|
"""
|
|
195
196
|
Create and start a Docker container for evaluation.
|
|
@@ -201,6 +202,7 @@ class EvalContainerManager:
|
|
|
201
202
|
gpu_ids: Comma-separated GPU IDs to use
|
|
202
203
|
proxy_port: Proxy port for network access (uses bridge network with Docker host gateway)
|
|
203
204
|
docker_runtime_config: Runtime config from repo_settings (need_gpu, shm_size, env_vars, env_exports, number_once)
|
|
205
|
+
labels: Docker labels to attach to the container
|
|
204
206
|
|
|
205
207
|
Returns:
|
|
206
208
|
Docker container object
|
|
@@ -297,6 +299,7 @@ class EvalContainerManager:
|
|
|
297
299
|
environment=environment,
|
|
298
300
|
network_mode=network_mode,
|
|
299
301
|
shm_size=shm_size,
|
|
302
|
+
labels=labels,
|
|
300
303
|
)
|
|
301
304
|
|
|
302
305
|
# Check if GPU is available
|
|
@@ -365,5 +368,4 @@ class EvalContainerManager:
|
|
|
365
368
|
container.remove()
|
|
366
369
|
self.logger.info("Container removed successfully")
|
|
367
370
|
except Exception as e:
|
|
368
|
-
self.logger.warning(f"Failed to cleanup container: {e}")
|
|
369
|
-
|
|
371
|
+
self.logger.warning(f"Failed to cleanup container: {e}")
|
|
@@ -135,6 +135,7 @@ def save_review_codes_level1(
|
|
|
135
135
|
log_dir: Path,
|
|
136
136
|
docker_image: str,
|
|
137
137
|
logger: logging.Logger,
|
|
138
|
+
labels: dict[str, str] | None = None,
|
|
138
139
|
) -> None:
|
|
139
140
|
"""
|
|
140
141
|
Save review codes for Level 1 instance.
|
|
@@ -176,6 +177,7 @@ def save_review_codes_level1(
|
|
|
176
177
|
name=container_name,
|
|
177
178
|
detach=True,
|
|
178
179
|
remove=False,
|
|
180
|
+
labels=labels,
|
|
179
181
|
)
|
|
180
182
|
|
|
181
183
|
# Step 1: Restore project
|
|
@@ -290,6 +292,7 @@ def save_review_codes_level2(
|
|
|
290
292
|
log_dir: Path,
|
|
291
293
|
docker_image: str,
|
|
292
294
|
logger: logging.Logger,
|
|
295
|
+
labels: dict[str, str] | None = None,
|
|
293
296
|
) -> None:
|
|
294
297
|
"""
|
|
295
298
|
Save review codes for Level 2 instance.
|
|
@@ -332,6 +335,7 @@ def save_review_codes_level2(
|
|
|
332
335
|
name=container_name,
|
|
333
336
|
detach=True,
|
|
334
337
|
remove=False,
|
|
338
|
+
labels=labels,
|
|
335
339
|
)
|
|
336
340
|
|
|
337
341
|
# Step 1: Clean /testbed/ and create README.md
|
|
@@ -402,4 +406,4 @@ def save_review_codes_level2(
|
|
|
402
406
|
container.stop(timeout=10)
|
|
403
407
|
container.remove()
|
|
404
408
|
except Exception as e:
|
|
405
|
-
logger.warning(f"Failed to cleanup review container: {e}")
|
|
409
|
+
logger.warning(f"Failed to cleanup review container: {e}")
|