featurebench 0.2.2__tar.gz → 0.2.3__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.2 → featurebench-0.2.3}/PKG-INFO +3 -3
- {featurebench-0.2.2 → featurebench-0.2.3}/README.md +2 -2
- {featurebench-0.2.2 → featurebench-0.2.3}/config_example.toml +2 -1
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/config.md +3 -2
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/infer_cli_arg.md +7 -0
- featurebench-0.2.3/featurebench/environment_fixes/__init__.py +13 -0
- featurebench-0.2.3/featurebench/environment_fixes/base.py +24 -0
- featurebench-0.2.3/featurebench/environment_fixes/mlflow.py +41 -0
- featurebench-0.2.3/featurebench/environment_fixes/registry.py +43 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/runtime.py +26 -12
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/base.py +14 -2
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/mini_swe_agent.py +104 -22
- featurebench-0.2.3/featurebench/infer/environments/__init__.py +1 -0
- featurebench-0.2.3/featurebench/infer/environments/mini_swe.py +185 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/models.py +8 -1
- featurebench-0.2.3/featurebench/infer/network.py +473 -0
- featurebench-0.2.3/featurebench/infer/proxy_bridge.py +57 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/run_infer.py +141 -6
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/runtime.py +138 -5
- {featurebench-0.2.2 → featurebench-0.2.3}/pyproject.toml +1 -1
- {featurebench-0.2.2 → featurebench-0.2.3}/tests/test_dataset_version.py +3 -0
- featurebench-0.2.3/tests/test_environment_fixes.py +52 -0
- featurebench-0.2.3/tests/test_infer_network.py +150 -0
- featurebench-0.2.3/tests/test_infer_runtime.py +163 -0
- featurebench-0.2.3/tests/test_mini_swe_agent.py +213 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/uv.lock +43 -43
- {featurebench-0.2.2 → featurebench-0.2.3}/.gitignore +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/.python-version +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/LICENSE +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/harness_cli_arg.md +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/pics/logo.png +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/pics/workflow.png +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/docs/pipeline.md +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/classification/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/classification/code_classifier.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/classification/file_analyzer.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/classification/llm_top_classifier.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/classification/p2p_chooser.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/cli.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/case_converter.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/eval_code.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/llm_prompt_generator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/llm_testfile_adjuster.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/conversion/render_task.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/docker/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/docker/dynamic_tracer.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/docker/image_manager.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/docker/test_runner.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/docker/test_scanner.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/constants.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/container.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/grading.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/report.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/review_codes.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/run_evaluation.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/test_parsers.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/harness/utils.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/claude_code.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/codex.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/gemini_cli.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/agents/openhands.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/config.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/container.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/gpu_scheduler.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/output.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/infer/render_infer_log.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/llm/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/llm/llm_caller.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/llm/llm_exceptions.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/mask/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/mask/mask_generator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/mask/mask_validator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/mask/signature_extractor.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/pipeline.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/post_verify/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/post_verify/f2p_validator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/post_verify/level2_validator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/post_verify/p2p_validator.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/constants/fast_images.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/constants/full_images.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/constants/lite_images.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/constants/python_example.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python310.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python310_cu118_torch212.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python310_cu121_torch251.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python310_cu121_torch28.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python311.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python311_cu121_torch28.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python312.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python312_cu121_torch251.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python312_cu121_torch26.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python312_cu121_torch28.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python312_torch251.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/dockerfiles/python39.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/dynamic_script/autograd_backward_patch.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/dynamic_script/dynamic_script.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/dynamic_script/import_hook_patch.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/dynamic_script/joblib_patch.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/dynamic_script/sitecustomize.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/scripts/scanner_script.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/appendix.md.j2 +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/black_links.txt.j2 +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/config_template.yaml +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/level_1.j2 +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/level_2.j2 +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/resources/templates/llm_prompt_templates.yaml +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/scripts/cal_eval_outputs.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/scripts/post_filter_cases.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/scripts/pull_images.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/command_utils.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/config.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/docker_images.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/logger.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/parser/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/parser/pytest_parser.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/repo_manager.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/storage.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/utils/utils.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/__init__.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/post_additions.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/prompts/add_prompt_lv1.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/prompts/add_prompt_lv2.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/prompts/prompt_template_lv1.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/prompts/prompt_template_lv2.txt +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/run_verification.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/featurebench/verified/runner.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/tests/test_eval_data_container_cleanup.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/tests/test_infer_container_cleanup.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/tests/test_openhands_session_cache.py +0 -0
- {featurebench-0.2.2 → featurebench-0.2.3}/tests/test_pull_images.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: featurebench
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -52,7 +52,7 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
52
52
|
<!-- LEADERBOARD_START -->
|
|
53
53
|
## 🏆 Leaderboard
|
|
54
54
|
|
|
55
|
-
[Full interactive leaderboard](https://libercoders.github.io/FeatureBench/leaderboard/) with tabs, filters, and sorting.
|
|
55
|
+
[Full interactive leaderboard](https://libercoders.github.io/FeatureBench/leaderboard/) with tabs, filters, and sorting (Dataset V1.0).
|
|
56
56
|
|
|
57
57
|
<details open>
|
|
58
58
|
<summary><b>Lite split results, ranked by %PASSED</b></summary>
|
|
@@ -159,4 +159,4 @@ If you found FeatureBench useful, please cite us as:
|
|
|
159
159
|
|
|
160
160
|
## 📧 Contact
|
|
161
161
|
|
|
162
|
-
If you have any questions, feel free to contact [
|
|
162
|
+
If you have any questions, feel free to contact [qixing.zhou@smail.nju.edu.cn](mailto:qixing.zhou@smail.nju.edu.cn) or [zjcheng2022@gmail.com](mailto:zjcheng2022@gmail.com).
|
|
@@ -28,7 +28,7 @@ It provides a unified CLI to run inference, evaluation, and dataset generation.
|
|
|
28
28
|
<!-- LEADERBOARD_START -->
|
|
29
29
|
## 🏆 Leaderboard
|
|
30
30
|
|
|
31
|
-
[Full interactive leaderboard](https://libercoders.github.io/FeatureBench/leaderboard/) with tabs, filters, and sorting.
|
|
31
|
+
[Full interactive leaderboard](https://libercoders.github.io/FeatureBench/leaderboard/) with tabs, filters, and sorting (Dataset V1.0).
|
|
32
32
|
|
|
33
33
|
<details open>
|
|
34
34
|
<summary><b>Lite split results, ranked by %PASSED</b></summary>
|
|
@@ -135,4 +135,4 @@ If you found FeatureBench useful, please cite us as:
|
|
|
135
135
|
|
|
136
136
|
## 📧 Contact
|
|
137
137
|
|
|
138
|
-
If you have any questions, feel free to contact [
|
|
138
|
+
If you have any questions, feel free to contact [qixing.zhou@smail.nju.edu.cn](mailto:qixing.zhou@smail.nju.edu.cn) or [zjcheng2022@gmail.com](mailto:zjcheng2022@gmail.com).
|
|
@@ -52,7 +52,8 @@ base_url = "http://localhost:8080/v1" # vLLM server URL
|
|
|
52
52
|
# Optional: Host cache directory for agent downloads; will be mounted to /download inside containers
|
|
53
53
|
# Set to empty to disable cache mount (each container will re-download/install)
|
|
54
54
|
# Use an absolute path here
|
|
55
|
-
|
|
55
|
+
# Optional shared install cache. Leave unset for leakage-resistant evaluations.
|
|
56
|
+
# download_cache_dir="/path/to/FeatureBench/download_cache"
|
|
56
57
|
|
|
57
58
|
# OpenHands Agent Configuration
|
|
58
59
|
[infer_config.openhands]
|
|
@@ -68,10 +68,11 @@ How infer reads `config.toml`:
|
|
|
68
68
|
|
|
69
69
|
```toml
|
|
70
70
|
[infer]
|
|
71
|
-
|
|
71
|
+
# Optional shared install cache. Leave unset for leakage-resistant evaluations.
|
|
72
|
+
# download_cache_dir = "/abs/path/to/FeatureBench/download_cache"
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
- `download_cache_dir`: host-side cache directory
|
|
75
|
+
- `download_cache_dir`: optional host-side cache directory mounted into the container at `/download`. It is disabled by default because the agent can read shared cache contents during inference.
|
|
75
76
|
- Purpose: speed up agent installs/downloads (e.g. npm cache).
|
|
76
77
|
- Empty means no mount (each container downloads on its own; usually slower).
|
|
77
78
|
|
|
@@ -160,6 +160,13 @@ flags can override metadata (see the argument list below).
|
|
|
160
160
|
Default: no override (OpenHands default applies).
|
|
161
161
|
Resume mode: ignored (uses metadata).
|
|
162
162
|
|
|
163
|
+
### mini-swe-agent Only
|
|
164
|
+
|
|
165
|
+
- `--cost-limit`
|
|
166
|
+
Per-task cost limit in USD, passed to mini-swe-agent.
|
|
167
|
+
Set to `0` to disable the limit. If omitted, mini-swe-agent's upstream default applies.
|
|
168
|
+
Resume mode: ignored (uses metadata).
|
|
169
|
+
|
|
163
170
|
## Section 4: Output Directory Structure
|
|
164
171
|
|
|
165
172
|
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Repository-specific fixes for leaked or duplicated task-environment artifacts.
|
|
2
|
+
|
|
3
|
+
Add a fix by implementing :class:`EnvironmentFix`, decorating it with
|
|
4
|
+
``register_environment_fix``, and importing its module below. Each plugin is
|
|
5
|
+
matched by repository and is shared by inference and Oracle evaluation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from featurebench.environment_fixes.registry import apply_environment_fixes
|
|
9
|
+
|
|
10
|
+
# Import built-in plugins so they register themselves.
|
|
11
|
+
from featurebench.environment_fixes import mlflow as _mlflow # noqa: F401, E402
|
|
12
|
+
|
|
13
|
+
__all__ = ["apply_environment_fixes"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Interfaces for repository-specific task-environment fixes."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
CommandRunner = Callable[[str], tuple[int, str | bytes]]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EnvironmentFix(ABC):
|
|
12
|
+
"""A targeted cleanup applied before the task repository becomes the Git baseline."""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
repositories: frozenset[str]
|
|
16
|
+
|
|
17
|
+
def applies_to(self, repository: str) -> bool:
|
|
18
|
+
"""Return whether this fix applies to a canonical ``owner/repo`` name."""
|
|
19
|
+
normalized = repository.strip().casefold()
|
|
20
|
+
return normalized in {repo.casefold() for repo in self.repositories}
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def apply(self, run_command: CommandRunner, logger: logging.Logger) -> bool:
|
|
24
|
+
"""Apply and verify the fix, returning ``False`` on failure."""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""MLflow-specific task-environment cleanup."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from featurebench.environment_fixes.base import CommandRunner, EnvironmentFix
|
|
6
|
+
from featurebench.environment_fixes.registry import register_environment_fix
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@register_environment_fix
|
|
10
|
+
class MlflowParallelSourcesFix(EnvironmentFix):
|
|
11
|
+
"""Remove unmasked MLflow package copies shipped in the monorepo's ``libs`` tree."""
|
|
12
|
+
|
|
13
|
+
name = "mlflow_parallel_sources"
|
|
14
|
+
repositories = frozenset({"mlflow/mlflow"})
|
|
15
|
+
paths = (
|
|
16
|
+
"/testbed/libs/skinny/mlflow",
|
|
17
|
+
"/testbed/libs/tracing/mlflow",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def apply(self, run_command: CommandRunner, logger: logging.Logger) -> bool:
|
|
21
|
+
paths = " ".join(self.paths)
|
|
22
|
+
command = f"""
|
|
23
|
+
paths=({paths})
|
|
24
|
+
for path in "${{paths[@]}}"; do
|
|
25
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
26
|
+
rm -rf -- "$path" || exit 1
|
|
27
|
+
echo "Removed MLflow parallel source: $path"
|
|
28
|
+
fi
|
|
29
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
30
|
+
echo "MLflow parallel source still exists: $path" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
"""
|
|
35
|
+
exit_code, output = run_command(command)
|
|
36
|
+
if exit_code != 0:
|
|
37
|
+
if isinstance(output, bytes):
|
|
38
|
+
output = output.decode("utf-8", errors="replace")
|
|
39
|
+
logger.error("Failed to remove MLflow parallel sources: %s", output)
|
|
40
|
+
return False
|
|
41
|
+
return True
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Registry and execution helpers for task-environment fixes."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
from featurebench.environment_fixes.base import CommandRunner, EnvironmentFix
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_FIXES: list[EnvironmentFix] = []
|
|
10
|
+
_EnvironmentFixType = TypeVar("_EnvironmentFixType", bound=type[EnvironmentFix])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register_environment_fix(fix_type: _EnvironmentFixType) -> _EnvironmentFixType:
|
|
14
|
+
"""Register an environment-fix class at import time."""
|
|
15
|
+
fix = fix_type()
|
|
16
|
+
if any(existing.name == fix.name for existing in _FIXES):
|
|
17
|
+
raise ValueError(f"Duplicate environment fix name: {fix.name}")
|
|
18
|
+
_FIXES.append(fix)
|
|
19
|
+
return fix_type
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def apply_environment_fixes(
|
|
23
|
+
repository: str,
|
|
24
|
+
run_command: CommandRunner,
|
|
25
|
+
logger: logging.Logger,
|
|
26
|
+
) -> bool:
|
|
27
|
+
"""Apply every registered fix matching ``repository`` in registration order."""
|
|
28
|
+
matching_fixes = [fix for fix in _FIXES if fix.applies_to(repository)]
|
|
29
|
+
if not matching_fixes:
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
for fix in matching_fixes:
|
|
33
|
+
logger.info("Applying environment fix %s for %s", fix.name, repository)
|
|
34
|
+
try:
|
|
35
|
+
if not fix.apply(run_command, logger):
|
|
36
|
+
logger.error("Environment fix %s failed for %s", fix.name, repository)
|
|
37
|
+
return False
|
|
38
|
+
except Exception:
|
|
39
|
+
logger.exception("Environment fix %s raised for %s", fix.name, repository)
|
|
40
|
+
return False
|
|
41
|
+
logger.info("Environment fix %s completed for %s", fix.name, repository)
|
|
42
|
+
|
|
43
|
+
return True
|
|
@@ -13,6 +13,7 @@ from typing import Any
|
|
|
13
13
|
import pandas as pd
|
|
14
14
|
from docker.models.containers import Container
|
|
15
15
|
|
|
16
|
+
from featurebench.environment_fixes import apply_environment_fixes
|
|
16
17
|
from featurebench.harness.constants import (
|
|
17
18
|
APPLY_PATCH_FAIL,
|
|
18
19
|
APPLY_PATCH_PASS,
|
|
@@ -55,9 +56,11 @@ def run_instance_level1(
|
|
|
55
56
|
1. Activate conda environment
|
|
56
57
|
2. Restore project from /root/my_repo/
|
|
57
58
|
3. Apply patch (for masking) and directly delete F2P test files
|
|
58
|
-
4.
|
|
59
|
-
5.
|
|
60
|
-
6.
|
|
59
|
+
4. Apply repository-specific environment fixes
|
|
60
|
+
5. Reinitialize git
|
|
61
|
+
6. Apply agent's patch
|
|
62
|
+
7. Delete any generated F2P files and restore the F2P test
|
|
63
|
+
8. Run tests
|
|
61
64
|
|
|
62
65
|
Args:
|
|
63
66
|
instance: Instance data (from HuggingFace)
|
|
@@ -177,8 +180,20 @@ def run_instance_level1(
|
|
|
177
180
|
else:
|
|
178
181
|
logger.warning("No FAIL_TO_PASS tests found in instance, skipping test file deletion")
|
|
179
182
|
|
|
180
|
-
# Step 3:
|
|
181
|
-
|
|
183
|
+
# Step 3: Apply the same repository-specific cleanup used during
|
|
184
|
+
# inference. Running it before Git initialization keeps the cleanup out
|
|
185
|
+
# of the candidate patch while making Oracle validation representative.
|
|
186
|
+
logger.info("Step 3: Applying repository-specific environment fixes")
|
|
187
|
+
if not apply_environment_fixes(
|
|
188
|
+
str(repo_name),
|
|
189
|
+
lambda command: exec_run_with_timeout(container, command, timeout=120),
|
|
190
|
+
logger,
|
|
191
|
+
):
|
|
192
|
+
results["error"] = "Repository-specific environment fix failed"
|
|
193
|
+
return results
|
|
194
|
+
|
|
195
|
+
# Step 4: Reinitialize git repository
|
|
196
|
+
logger.info("Step 4: Reinitializing git repository")
|
|
182
197
|
git_cmds = [
|
|
183
198
|
"cd /testbed && rm -rf .git",
|
|
184
199
|
"cd /testbed && git init",
|
|
@@ -194,8 +209,8 @@ def run_instance_level1(
|
|
|
194
209
|
logger.warning(f"Git command failed: {cmd}")
|
|
195
210
|
logger.warning(f"Output: {output.decode(UTF8, errors='replace')}")
|
|
196
211
|
|
|
197
|
-
# Step
|
|
198
|
-
logger.info("Step
|
|
212
|
+
# Step 5: Apply agent's patch
|
|
213
|
+
logger.info("Step 5: Applying agent patch")
|
|
199
214
|
patch_content = pred[KEY_PREDICTION]
|
|
200
215
|
|
|
201
216
|
if not patch_content or patch_content.strip() == "":
|
|
@@ -231,8 +246,8 @@ def run_instance_level1(
|
|
|
231
246
|
logger.info(f"{APPLY_PATCH_PASS}")
|
|
232
247
|
results["patch_applied"] = True
|
|
233
248
|
|
|
234
|
-
# Step
|
|
235
|
-
logger.info("Step
|
|
249
|
+
# Step 6: Prepare for testing - delete generated F2P files and restore F2P test
|
|
250
|
+
logger.info("Step 6: Preparing for testing - cleaning up and restoring F2P test")
|
|
236
251
|
|
|
237
252
|
fail_to_pass = instance.get('FAIL_TO_PASS', [])
|
|
238
253
|
if not fail_to_pass:
|
|
@@ -268,8 +283,8 @@ def run_instance_level1(
|
|
|
268
283
|
else:
|
|
269
284
|
logger.warning("No test_patch available to restore F2P test file")
|
|
270
285
|
|
|
271
|
-
# Step
|
|
272
|
-
logger.info("Step
|
|
286
|
+
# Step 7: Run tests
|
|
287
|
+
logger.info("Step 7: Running tests")
|
|
273
288
|
|
|
274
289
|
# Get test configuration from repo_settings
|
|
275
290
|
repo_settings = parse_repo_settings(instance)
|
|
@@ -589,4 +604,3 @@ def run_instance_level2(
|
|
|
589
604
|
logger.error(traceback.format_exc())
|
|
590
605
|
results["error"] = str(e)
|
|
591
606
|
return results
|
|
592
|
-
|
|
@@ -41,6 +41,10 @@ class BaseAgent(ABC):
|
|
|
41
41
|
self.env_vars = env_vars or {}
|
|
42
42
|
self.logger = logger or logging.getLogger(__name__)
|
|
43
43
|
self._kwargs = kwargs
|
|
44
|
+
# Agent adapters may populate this with their native terminal status.
|
|
45
|
+
# It deliberately remains optional because not every supported agent
|
|
46
|
+
# exposes a structured status artifact.
|
|
47
|
+
self.agent_exit_status: Optional[str] = None
|
|
44
48
|
|
|
45
49
|
@property
|
|
46
50
|
@abstractmethod
|
|
@@ -354,6 +358,7 @@ class BaseAgent(ABC):
|
|
|
354
358
|
Returns:
|
|
355
359
|
True if agent completed successfully
|
|
356
360
|
"""
|
|
361
|
+
self.agent_exit_status = None
|
|
357
362
|
self.logger.info(f"Running {self.name} agent...")
|
|
358
363
|
|
|
359
364
|
with open(log_file, "a", encoding="utf-8") as f:
|
|
@@ -385,16 +390,23 @@ class BaseAgent(ABC):
|
|
|
385
390
|
log_file=log_file,
|
|
386
391
|
timeout=timeout
|
|
387
392
|
)
|
|
388
|
-
|
|
393
|
+
|
|
389
394
|
success_run = exit_code == 0
|
|
395
|
+
timed_out = not success_run and self._infer_log_has_timeout_marker(log_file)
|
|
390
396
|
success_post_run = self.post_run_hook(container, log_file)
|
|
391
397
|
|
|
398
|
+
# A host-side timeout may kill the agent before it can persist a
|
|
399
|
+
# native terminal status. Preserve the actual reason for stopping
|
|
400
|
+
# instead of reporting an unknown/generic failure.
|
|
401
|
+
if timed_out:
|
|
402
|
+
self.agent_exit_status = "TimeExceeded"
|
|
403
|
+
|
|
392
404
|
if success_run and success_post_run:
|
|
393
405
|
self.logger.info(f"{self.name} agent completed successfully")
|
|
394
406
|
return True
|
|
395
407
|
|
|
396
408
|
if not success_run:
|
|
397
|
-
if self._force_timeout_enabled() and
|
|
409
|
+
if self._force_timeout_enabled() and timed_out:
|
|
398
410
|
post_run_state = "passed" if success_post_run else "failed"
|
|
399
411
|
self.logger.warning(
|
|
400
412
|
f"{self.name} run hit timeout marker (post-run hook {post_run_state}); "
|
|
@@ -4,6 +4,7 @@ mini-swe-agent implementation.
|
|
|
4
4
|
This agent runs the external `mini` CLI in the container.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import json
|
|
7
8
|
import shlex
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Dict, List, Optional
|
|
@@ -76,7 +77,7 @@ set -e
|
|
|
76
77
|
echo "Installing mini-swe-agent..."
|
|
77
78
|
|
|
78
79
|
apt-get update
|
|
79
|
-
apt-get install -y python3 python3-pip python3-venv
|
|
80
|
+
apt-get install -y passwd python3 python3-pip python3-venv util-linux
|
|
80
81
|
|
|
81
82
|
CACHE_ROOT="${{AGENT_DOWNLOAD_CACHE:-/download}}"
|
|
82
83
|
mkdir -p "$CACHE_ROOT" "$CACHE_ROOT/pip"
|
|
@@ -98,29 +99,72 @@ echo "mini-swe-agent installation complete"
|
|
|
98
99
|
self.cm.exec_command(container, "mkdir -p /agent-logs", log_file=log_file)
|
|
99
100
|
return True
|
|
100
101
|
|
|
101
|
-
def
|
|
102
|
-
"""
|
|
103
|
-
|
|
102
|
+
def pre_run_setup(self, container, instance, log_file: Path) -> bool:
|
|
103
|
+
"""Isolate agent-issued shell commands from controller dependencies."""
|
|
104
|
+
controller_dir = "/opt/featurebench-controller"
|
|
105
|
+
environment_source = (
|
|
106
|
+
Path(__file__).parent.parent / "environments" / "mini_swe.py"
|
|
107
|
+
)
|
|
108
|
+
environment_target = f"{controller_dir}/featurebench_mini_swe_environment.py"
|
|
109
|
+
|
|
104
110
|
try:
|
|
105
|
-
self.cm.
|
|
111
|
+
exit_code, output = self.cm.exec_command(
|
|
106
112
|
container,
|
|
107
|
-
"
|
|
108
|
-
|
|
113
|
+
"set -e\n"
|
|
114
|
+
"getent group fbagent >/dev/null || groupadd --system fbagent\n"
|
|
115
|
+
"id -u fbagent >/dev/null 2>&1 || "
|
|
116
|
+
"useradd --system --gid fbagent --home-dir /home/fbagent "
|
|
117
|
+
"--create-home --shell /bin/bash fbagent\n"
|
|
118
|
+
f"install -d -o root -g root -m 700 {controller_dir} "
|
|
119
|
+
"/agent-logs\n",
|
|
120
|
+
log_file=log_file,
|
|
109
121
|
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
if exit_code != 0:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f"Failed to create restricted mini-swe-agent user: {output}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.cm.copy_to_container(
|
|
114
128
|
container,
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
environment_source,
|
|
130
|
+
environment_target,
|
|
117
131
|
)
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
exit_code, output = self.cm.exec_command(
|
|
133
|
+
container,
|
|
134
|
+
"set -e\n"
|
|
135
|
+
f"chown root:root {environment_target}\n"
|
|
136
|
+
f"chmod 600 {environment_target}\n"
|
|
137
|
+
"chown -R fbagent:fbagent /testbed\n"
|
|
138
|
+
"chmod 700 /opt/mini-swe-agent-venv\n"
|
|
139
|
+
f"chmod 700 {controller_dir} /installed-agent /agent-logs\n"
|
|
140
|
+
"setpriv --reuid=fbagent --regid=fbagent --init-groups "
|
|
141
|
+
"--no-new-privs /bin/bash -c "
|
|
142
|
+
"'test -r /testbed && test -w /testbed && "
|
|
143
|
+
"test ! -r /opt/mini-swe-agent-venv/pyvenv.cfg && "
|
|
144
|
+
"test ! -r /installed-agent/setup-env.sh'",
|
|
145
|
+
log_file=log_file,
|
|
146
|
+
)
|
|
147
|
+
if exit_code != 0:
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
f"Failed to enforce mini-swe-agent filesystem isolation: {output}"
|
|
150
|
+
)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
self.logger.error(f"Failed to prepare mini-swe-agent isolation: {e}")
|
|
153
|
+
# Isolation is a security boundary. Do not let the runner continue
|
|
154
|
+
# with root-level tool execution if setup fails.
|
|
155
|
+
raise
|
|
156
|
+
|
|
157
|
+
self.logger.info(
|
|
158
|
+
"mini-swe-agent tool commands will run as restricted user fbagent"
|
|
159
|
+
)
|
|
120
160
|
return True
|
|
121
161
|
|
|
122
|
-
def
|
|
123
|
-
"""
|
|
162
|
+
def post_run_hook(self, container, log_file) -> bool:
|
|
163
|
+
"""Persist artifacts and accept only an explicit agent submission."""
|
|
164
|
+
return self._collect_run_artifacts(container, log_file)
|
|
165
|
+
|
|
166
|
+
def _collect_run_artifacts(self, container, log_file: Path) -> bool:
|
|
167
|
+
"""Copy run artifacts and validate mini-swe-agent's terminal status."""
|
|
124
168
|
log_dir = Path(log_file).parent
|
|
125
169
|
try:
|
|
126
170
|
self.cm.copy_from_container(
|
|
@@ -128,29 +172,67 @@ echo "mini-swe-agent installation complete"
|
|
|
128
172
|
"/agent-logs/mini_swe_agent_output.log",
|
|
129
173
|
log_dir / "mini_swe_agent_output.log",
|
|
130
174
|
)
|
|
131
|
-
except Exception:
|
|
132
|
-
|
|
175
|
+
except Exception as e:
|
|
176
|
+
self.logger.warning(f"Failed to collect mini-swe-agent output: {e}")
|
|
177
|
+
|
|
178
|
+
trajectory_path = log_dir / "traj.json"
|
|
133
179
|
try:
|
|
134
180
|
self.cm.copy_from_container(
|
|
135
181
|
container,
|
|
136
182
|
"/root/.config/mini-swe-agent/last_mini_run.traj.json",
|
|
137
|
-
|
|
183
|
+
trajectory_path,
|
|
138
184
|
)
|
|
139
|
-
except Exception:
|
|
140
|
-
|
|
185
|
+
except Exception as e:
|
|
186
|
+
self.logger.error(f"Failed to collect mini-swe-agent trajectory: {e}")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
trajectory = json.loads(trajectory_path.read_text(encoding="utf-8"))
|
|
191
|
+
exit_status = trajectory.get("info", {}).get("exit_status")
|
|
192
|
+
except (OSError, UnicodeError, json.JSONDecodeError, AttributeError) as e:
|
|
193
|
+
self.logger.error(f"Failed to read mini-swe-agent exit status: {e}")
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
if not isinstance(exit_status, str) or not exit_status.strip():
|
|
197
|
+
self.logger.error("mini-swe-agent trajectory has no exit_status")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
self.agent_exit_status = exit_status.strip()
|
|
201
|
+
if self.agent_exit_status != "Submitted":
|
|
202
|
+
self.logger.warning(
|
|
203
|
+
f"mini-swe-agent exited without submission: {self.agent_exit_status}"
|
|
204
|
+
)
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
self.logger.info("mini-swe-agent exit status: Submitted")
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
def failure_hook(self, container, log_file: Path) -> None:
|
|
211
|
+
"""Collect mini-swe-agent output on failures (best-effort)."""
|
|
212
|
+
trajectory_path = Path(log_file).parent / "traj.json"
|
|
213
|
+
if not trajectory_path.exists():
|
|
214
|
+
self._collect_run_artifacts(container, log_file)
|
|
141
215
|
|
|
142
216
|
def get_run_command(self, instruction: str) -> str:
|
|
143
217
|
"""Get the command to run mini-swe-agent."""
|
|
144
218
|
escaped_instruction = shlex.quote(instruction)
|
|
145
219
|
model_name = self._get_model_name()
|
|
220
|
+
cost_limit = self._kwargs.get("cost_limit")
|
|
221
|
+
cost_limit_arg = ""
|
|
222
|
+
if cost_limit is not None:
|
|
223
|
+
cost_limit_arg = f"--cost-limit {shlex.quote(str(cost_limit))} "
|
|
146
224
|
return (
|
|
147
225
|
"set -o pipefail; "
|
|
148
226
|
f'"$MINI_SWE_AGENT_PYTHON" -I - -m {shlex.quote(model_name)} -t {escaped_instruction} -y --exit-immediately '
|
|
227
|
+
f"{cost_limit_arg}"
|
|
228
|
+
"--environment-class "
|
|
229
|
+
"featurebench_mini_swe_environment.RestrictedLocalEnvironment "
|
|
149
230
|
"<<'PY' | tee /agent-logs/mini_swe_agent_output.log\n"
|
|
150
231
|
"import runpy\n"
|
|
151
232
|
"import sys\n"
|
|
152
233
|
"\n"
|
|
153
234
|
"sys.path = [p for p in sys.path if p and not p.startswith('/testbed')]\n"
|
|
235
|
+
"sys.path.insert(0, '/opt/featurebench-controller')\n"
|
|
154
236
|
"sys.argv = ['minisweagent.run.mini', *sys.argv[1:]]\n"
|
|
155
237
|
"runpy.run_module('minisweagent.run.mini', run_name='__main__')\n"
|
|
156
238
|
"PY"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Runtime environments injected into external inference agents."""
|