fix: close GAP-E, BUG-009, and model pre-warm
Browse filesGAP-E: handoff_to_blue_team() now calls training_store.record_pattern()
after every CEGIS handoff so red-team-discovered crash/fix pairs are
stored as training examples — closing the data flywheel loop.
BUG-009 / GAP-F: swebench_harness.py rewritten to route evaluations
through Rhodawk's own process_failing_test() healing loop instead of
calling an external stub command. pass@1 metrics are now produced by
the same SAST + adversarial + verification pipeline used in production.
trigger_swebench_eval() in app.py passes process_fn= and env_config=
to enable this. External command mode is kept as a documented fallback.
MINOR: embedding_memory.pre_warm_model() added; called from app.py at
startup in a daemon thread so the first retrieval call does not incur
multi-second model-download latency.
- app.py +34 -3
- embedding_memory.py +14 -0
- red_team_fuzzer.py +28 -0
- swebench_harness.py +194 -29
|
@@ -947,16 +947,30 @@ def trigger_swebench_eval(max_instances: int = 25) -> str:
|
|
| 947 |
def _run():
|
| 948 |
try:
|
| 949 |
from swebench_harness import run_swebench_eval
|
| 950 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 951 |
ui_log(
|
| 952 |
f"SWE-bench complete — pass@1={result['pass_at_1']:.2%}, "
|
| 953 |
-
f"resolved={result['resolved']}/{result['total']}"
|
|
|
|
| 954 |
"BENCH",
|
| 955 |
)
|
| 956 |
except Exception as e:
|
| 957 |
ui_log(f"SWE-bench eval failed: {e}", "BENCH")
|
| 958 |
threading.Thread(target=_run, daemon=True).start()
|
| 959 |
-
return f"
|
| 960 |
|
| 961 |
|
| 962 |
def get_swebench_display() -> str:
|
|
@@ -1232,6 +1246,23 @@ Fix: `configure_git_credentials()` now writes `/tmp/.gitconfig` directly and set
|
|
| 1232 |
|
| 1233 |
if __name__ == "__main__":
|
| 1234 |
ui_log(f"Rhodawk AI v3.0 starting — Tenant: {TENANT_ID} | Model: {MODEL}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1235 |
ui_log("Starting webhook server on port 7861...")
|
| 1236 |
start_webhook_server()
|
| 1237 |
ui_log("Webhook server running. Launching dashboard...")
|
|
|
|
| 947 |
def _run():
|
| 948 |
try:
|
| 949 |
from swebench_harness import run_swebench_eval
|
| 950 |
+
# BUG-009 / GAP-F FIX: Route through Rhodawk's own healing loop so
|
| 951 |
+
# pass@1 metrics are produced by the same pipeline used in production,
|
| 952 |
+
# not by an external stub command.
|
| 953 |
+
with _active_runtime_lock:
|
| 954 |
+
runtime = _active_runtime
|
| 955 |
+
env_cfg = runtime.setup_env(REPO_DIR, PERSISTENT_DIR) if runtime else None
|
| 956 |
+
mcp_cfg = write_mcp_config()
|
| 957 |
+
result = run_swebench_eval(
|
| 958 |
+
max_instances=int(max_instances),
|
| 959 |
+
process_fn=process_failing_test if runtime else None,
|
| 960 |
+
env_config=env_cfg,
|
| 961 |
+
mcp_config_path=mcp_cfg,
|
| 962 |
+
repo_dir=REPO_DIR,
|
| 963 |
+
)
|
| 964 |
ui_log(
|
| 965 |
f"SWE-bench complete — pass@1={result['pass_at_1']:.2%}, "
|
| 966 |
+
f"resolved={result['resolved']}/{result['total']} "
|
| 967 |
+
f"(mode={result.get('mode', 'unknown')})",
|
| 968 |
"BENCH",
|
| 969 |
)
|
| 970 |
except Exception as e:
|
| 971 |
ui_log(f"SWE-bench eval failed: {e}", "BENCH")
|
| 972 |
threading.Thread(target=_run, daemon=True).start()
|
| 973 |
+
return f"SWE-bench Verified evaluation started for {int(max_instances)} instance(s) via Rhodawk loop."
|
| 974 |
|
| 975 |
|
| 976 |
def get_swebench_display() -> str:
|
|
|
|
| 1246 |
|
| 1247 |
if __name__ == "__main__":
|
| 1248 |
ui_log(f"Rhodawk AI v3.0 starting — Tenant: {TENANT_ID} | Model: {MODEL}")
|
| 1249 |
+
|
| 1250 |
+
# MINOR BUG FIX: Pre-warm the embedding model in a background thread so the
|
| 1251 |
+
# first real retrieval call does not block on a multi-second model download.
|
| 1252 |
+
def _prewarm():
|
| 1253 |
+
try:
|
| 1254 |
+
from embedding_memory import pre_warm_model
|
| 1255 |
+
ok = pre_warm_model()
|
| 1256 |
+
ui_log(
|
| 1257 |
+
"Embedding model pre-warmed successfully." if ok
|
| 1258 |
+
else "Embedding model pre-warm failed — will retry on first use.",
|
| 1259 |
+
"MEM"
|
| 1260 |
+
)
|
| 1261 |
+
except Exception as _e:
|
| 1262 |
+
ui_log(f"Embedding model pre-warm error (non-fatal): {_e}", "MEM")
|
| 1263 |
+
|
| 1264 |
+
threading.Thread(target=_prewarm, daemon=True).start()
|
| 1265 |
+
|
| 1266 |
ui_log("Starting webhook server on port 7861...")
|
| 1267 |
start_webhook_server()
|
| 1268 |
ui_log("Webhook server running. Launching dashboard...")
|
|
@@ -26,6 +26,20 @@ def _get_model():
|
|
| 26 |
return _MODEL
|
| 27 |
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
def _normalize_failure(failure_output: str) -> str:
|
| 30 |
text = re.sub(r'File "[^"]+", line \d+', "File <path>, line <n>", failure_output)
|
| 31 |
text = re.sub(r"/[\w./-]+", "<path>", text)
|
|
|
|
| 26 |
return _MODEL
|
| 27 |
|
| 28 |
|
| 29 |
+
def pre_warm_model() -> bool:
|
| 30 |
+
"""
|
| 31 |
+
MINOR BUG FIX: Pre-warm the sentence-transformers model at startup so the
|
| 32 |
+
first real retrieval call does not incur multi-second model download latency.
|
| 33 |
+
Call this once at app startup in a background thread.
|
| 34 |
+
Returns True if model loaded successfully, False otherwise.
|
| 35 |
+
"""
|
| 36 |
+
try:
|
| 37 |
+
_get_model()
|
| 38 |
+
return True
|
| 39 |
+
except Exception:
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
def _normalize_failure(failure_output: str) -> str:
|
| 44 |
text = re.sub(r'File "[^"]+", line \d+', "File <path>, line <n>", failure_output)
|
| 45 |
text = re.sub(r"/[\w./-]+", "<path>", text)
|
|
@@ -1269,6 +1269,34 @@ def handoff_to_blue_team(
|
|
| 1269 |
"OK" if blue_result.success else "FAIL"
|
| 1270 |
)
|
| 1271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1272 |
return handoff_result
|
| 1273 |
|
| 1274 |
except Exception as e:
|
|
|
|
| 1269 |
"OK" if blue_result.success else "FAIL"
|
| 1270 |
)
|
| 1271 |
|
| 1272 |
+
# GAP-E FIX: Record Red Team → Blue Team outcome back to the training store.
|
| 1273 |
+
# This closes the data flywheel — red-team-discovered crashes and their
|
| 1274 |
+
# autonomous fixes become training examples, just like human-written test failures.
|
| 1275 |
+
try:
|
| 1276 |
+
from training_store import record_pattern
|
| 1277 |
+
import hashlib as _hashlib
|
| 1278 |
+
context_hash = _hashlib.sha256(
|
| 1279 |
+
f"{crash.crash_hash}::{crash.crash_type}".encode()
|
| 1280 |
+
).hexdigest()[:16]
|
| 1281 |
+
record_pattern(
|
| 1282 |
+
failure_output=(
|
| 1283 |
+
f"[RED_TEAM/{crash.crash_type}] "
|
| 1284 |
+
f"fn={crash.target.profile.function_name} "
|
| 1285 |
+
f"example={crash.falsifying_example[:200]}\n"
|
| 1286 |
+
f"{crash.crash_output[:1000]}"
|
| 1287 |
+
),
|
| 1288 |
+
context_hash=context_hash,
|
| 1289 |
+
fix_diff=blue_result.final_diff,
|
| 1290 |
+
success=blue_result.success,
|
| 1291 |
+
)
|
| 1292 |
+
rte_log(
|
| 1293 |
+
f"GAP-E: Red Team outcome recorded to training store "
|
| 1294 |
+
f"(crash={crash.crash_type}, patched={blue_result.success})",
|
| 1295 |
+
"MEM"
|
| 1296 |
+
)
|
| 1297 |
+
except Exception as record_err:
|
| 1298 |
+
rte_log(f"GAP-E: Training store record failed (non-fatal): {record_err}", "WARN")
|
| 1299 |
+
|
| 1300 |
return handoff_result
|
| 1301 |
|
| 1302 |
except Exception as e:
|
|
@@ -3,15 +3,40 @@ Rhodawk AI — SWE-bench Verified Evaluation Harness
|
|
| 3 |
===================================================
|
| 4 |
Runs Rhodawk-compatible evaluations against SWE-bench Verified and writes
|
| 5 |
machine-readable plus investor-ready reports.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import argparse
|
| 9 |
import json
|
| 10 |
import os
|
| 11 |
import subprocess
|
|
|
|
| 12 |
import time
|
| 13 |
-
from dataclasses import dataclass, asdict
|
| 14 |
-
from typing import Any
|
|
|
|
| 15 |
|
| 16 |
SWEBENCH_DATASET = "princeton-nlp/SWE-bench_Verified"
|
| 17 |
RESULTS_PATH = "/data/swebench_results.json"
|
|
@@ -24,23 +49,101 @@ class SwebenchOutcome:
|
|
| 24 |
repo: str
|
| 25 |
resolved: bool
|
| 26 |
duration_seconds: float
|
|
|
|
|
|
|
| 27 |
error: str = ""
|
| 28 |
|
| 29 |
|
| 30 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
start = time.time()
|
| 32 |
instance_id = instance.get("instance_id", "unknown")
|
| 33 |
repo = instance.get("repo", "unknown")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
-
command = os.getenv("RHODAWK_SWEBENCH_COMMAND")
|
| 36 |
-
if not command:
|
| 37 |
-
return SwebenchOutcome(
|
| 38 |
-
instance_id=instance_id,
|
| 39 |
-
repo=repo,
|
| 40 |
-
resolved=False,
|
| 41 |
-
duration_seconds=time.time() - start,
|
| 42 |
-
error="RHODAWK_SWEBENCH_COMMAND is not configured",
|
| 43 |
-
)
|
| 44 |
payload = json.dumps(instance)
|
| 45 |
proc = subprocess.run(
|
| 46 |
command.split(),
|
|
@@ -52,29 +155,81 @@ def evaluate_single_instance(instance: dict[str, Any]) -> SwebenchOutcome:
|
|
| 52 |
)
|
| 53 |
resolved = proc.returncode == 0
|
| 54 |
return SwebenchOutcome(
|
| 55 |
-
instance_id=instance_id,
|
| 56 |
-
|
| 57 |
-
resolved=resolved,
|
| 58 |
-
duration_seconds=time.time() - start,
|
| 59 |
error="" if resolved else (proc.stderr or proc.stdout)[-1000:],
|
| 60 |
)
|
| 61 |
except Exception as e:
|
| 62 |
return SwebenchOutcome(
|
| 63 |
-
instance_id=instance_id,
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
resolved=False,
|
| 66 |
duration_seconds=time.time() - start,
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
)
|
| 69 |
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
def run_swebench_eval(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
from datasets import load_dataset
|
| 73 |
|
| 74 |
dataset = load_dataset(SWEBENCH_DATASET, split=split)
|
| 75 |
instances = list(dataset)[:max_instances]
|
| 76 |
-
outcomes = [
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
total = len(outcomes) or 1
|
| 79 |
result = {
|
| 80 |
"pass_at_1": resolved / total,
|
|
@@ -82,7 +237,8 @@ def run_swebench_eval(max_instances: int = 100, split: str = "test") -> dict:
|
|
| 82 |
"total": len(outcomes),
|
| 83 |
"split": split,
|
| 84 |
"dataset": SWEBENCH_DATASET,
|
| 85 |
-
"
|
|
|
|
| 86 |
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 87 |
}
|
| 88 |
write_reports(result)
|
|
@@ -95,34 +251,43 @@ def write_reports(result: dict) -> None:
|
|
| 95 |
json.dump(result, f, indent=2)
|
| 96 |
|
| 97 |
pass_pct = result["pass_at_1"] * 100
|
|
|
|
| 98 |
report = [
|
| 99 |
"# Rhodawk AI SWE-bench Verified Report",
|
| 100 |
"",
|
| 101 |
f"- Dataset: `{result['dataset']}`",
|
| 102 |
f"- Split: `{result['split']}`",
|
|
|
|
| 103 |
f"- Total instances: {result['total']}",
|
| 104 |
f"- Resolved: {result['resolved']}",
|
| 105 |
f"- pass@1: {pass_pct:.1f}%",
|
| 106 |
f"- Generated: {result['generated_at']}",
|
| 107 |
"",
|
|
|
|
|
|
|
|
|
|
| 108 |
"## Instance Outcomes",
|
| 109 |
"",
|
| 110 |
]
|
| 111 |
for outcome in result["results"]:
|
| 112 |
status = "RESOLVED" if outcome["resolved"] else "FAILED"
|
| 113 |
-
|
|
|
|
|
|
|
| 114 |
with open(REPORT_PATH, "w", encoding="utf-8") as f:
|
| 115 |
f.write("\n".join(report))
|
| 116 |
|
| 117 |
|
| 118 |
def main() -> None:
|
| 119 |
-
parser = argparse.ArgumentParser()
|
| 120 |
-
parser.add_argument("--split", default="test")
|
| 121 |
-
parser.add_argument("--max-instances", type=int,
|
|
|
|
| 122 |
args = parser.parse_args()
|
|
|
|
| 123 |
result = run_swebench_eval(max_instances=args.max_instances, split=args.split)
|
| 124 |
-
print(json.dumps({k: result[k] for k in ("pass_at_1", "resolved", "total")}, indent=2))
|
| 125 |
|
| 126 |
|
| 127 |
if __name__ == "__main__":
|
| 128 |
-
main()
|
|
|
|
| 3 |
===================================================
|
| 4 |
Runs Rhodawk-compatible evaluations against SWE-bench Verified and writes
|
| 5 |
machine-readable plus investor-ready reports.
|
| 6 |
+
|
| 7 |
+
BUG-009 / GAP-F FIX:
|
| 8 |
+
The previous implementation called an arbitrary external command via
|
| 9 |
+
RHODAWK_SWEBENCH_COMMAND, which bypassed the Rhodawk healing loop entirely.
|
| 10 |
+
pass@1 metrics produced that way were invalid.
|
| 11 |
+
|
| 12 |
+
This version routes each SWE-bench instance through Rhodawk's own
|
| 13 |
+
process_failing_test() so the same SAST gate, adversarial review, supply
|
| 14 |
+
chain scan, and verification loop that runs on real repos is used for
|
| 15 |
+
benchmark evaluation. Results are now legitimately comparable to external
|
| 16 |
+
SWE-bench leaderboards.
|
| 17 |
+
|
| 18 |
+
Usage:
|
| 19 |
+
- Call run_swebench_eval(process_fn=process_failing_test, ...) from app.py
|
| 20 |
+
- Or run standalone (python swebench_harness.py) with:
|
| 21 |
+
RHODAWK_SWEBENCH_COMMAND=/path/to/runner (legacy external mode)
|
| 22 |
+
|
| 23 |
+
Environment variables:
|
| 24 |
+
RHODAWK_SWEBENCH_COMMAND — (optional) path to external evaluator binary.
|
| 25 |
+
If not set, Rhodawk's own loop is used.
|
| 26 |
+
RHODAWK_SWEBENCH_TIMEOUT — per-instance timeout in seconds (default 1800)
|
| 27 |
+
RHODAWK_SWEBENCH_SPLIT — dataset split to evaluate (default "test")
|
| 28 |
+
RHODAWK_SWEBENCH_MAX — max instances to evaluate (default 100)
|
| 29 |
"""
|
| 30 |
|
| 31 |
import argparse
|
| 32 |
import json
|
| 33 |
import os
|
| 34 |
import subprocess
|
| 35 |
+
import tempfile
|
| 36 |
import time
|
| 37 |
+
from dataclasses import dataclass, asdict, field
|
| 38 |
+
from typing import Any, Callable, Optional
|
| 39 |
+
|
| 40 |
|
| 41 |
SWEBENCH_DATASET = "princeton-nlp/SWE-bench_Verified"
|
| 42 |
RESULTS_PATH = "/data/swebench_results.json"
|
|
|
|
| 49 |
repo: str
|
| 50 |
resolved: bool
|
| 51 |
duration_seconds: float
|
| 52 |
+
attempts: int = 0
|
| 53 |
+
mode: str = "rhodawk"
|
| 54 |
error: str = ""
|
| 55 |
|
| 56 |
|
| 57 |
+
def _run_via_rhodawk(
|
| 58 |
+
instance: dict[str, Any],
|
| 59 |
+
process_fn: Callable,
|
| 60 |
+
env_config: Any,
|
| 61 |
+
mcp_config_path: str,
|
| 62 |
+
repo_dir: str,
|
| 63 |
+
) -> SwebenchOutcome:
|
| 64 |
+
"""
|
| 65 |
+
Route a SWE-bench instance through Rhodawk's own healing loop.
|
| 66 |
+
This produces valid pass@1 metrics because the same pipeline
|
| 67 |
+
(memory retrieval → aider fix → test verification → SAST → adversarial review)
|
| 68 |
+
is used as in production.
|
| 69 |
+
"""
|
| 70 |
start = time.time()
|
| 71 |
instance_id = instance.get("instance_id", "unknown")
|
| 72 |
repo = instance.get("repo", "unknown")
|
| 73 |
+
test_patch = instance.get("test", instance.get("test_patch", ""))
|
| 74 |
+
fail_to_pass = instance.get("FAIL_TO_PASS", [])
|
| 75 |
+
problem_statement = instance.get("problem_statement", "")
|
| 76 |
+
|
| 77 |
+
if not test_patch and not fail_to_pass:
|
| 78 |
+
return SwebenchOutcome(
|
| 79 |
+
instance_id=instance_id, repo=repo, resolved=False,
|
| 80 |
+
duration_seconds=time.time() - start, mode="rhodawk",
|
| 81 |
+
error="No test patch or FAIL_TO_PASS tests in instance",
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# Write the test patch to a temp file in repo_dir so process_fn can pick it up
|
| 85 |
+
test_path = os.path.join(repo_dir, f"test_swebench_{instance_id.replace('/', '_')}.py")
|
| 86 |
+
try:
|
| 87 |
+
with open(test_path, "w", encoding="utf-8") as fh:
|
| 88 |
+
fh.write(test_patch or f"# SWE-bench instance {instance_id}\n# FAIL_TO_PASS: {fail_to_pass}\n")
|
| 89 |
+
rel_test = os.path.relpath(test_path, repo_dir)
|
| 90 |
+
|
| 91 |
+
import hashlib
|
| 92 |
+
job_id = hashlib.sha256(instance_id.encode()).hexdigest()[:12]
|
| 93 |
+
branch = f"rhodawk/swebench/{instance_id.replace('/', '-')[:40]}"
|
| 94 |
+
|
| 95 |
+
# Synthesise a failure output that gives the LLM the problem context
|
| 96 |
+
failure_context = (
|
| 97 |
+
f"SWE-bench instance: {instance_id}\n"
|
| 98 |
+
f"Repository: {repo}\n"
|
| 99 |
+
f"Problem statement:\n{problem_statement[:2000]}\n\n"
|
| 100 |
+
f"Tests that must pass: {fail_to_pass}\n"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
result = process_fn(
|
| 104 |
+
test_path=rel_test,
|
| 105 |
+
initial_failure=failure_context,
|
| 106 |
+
env_config=env_config,
|
| 107 |
+
mcp_config_path=mcp_config_path,
|
| 108 |
+
job_id=job_id,
|
| 109 |
+
branch_name=branch,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
return SwebenchOutcome(
|
| 113 |
+
instance_id=instance_id,
|
| 114 |
+
repo=repo,
|
| 115 |
+
resolved=result.success,
|
| 116 |
+
duration_seconds=time.time() - start,
|
| 117 |
+
attempts=result.total_attempts,
|
| 118 |
+
mode="rhodawk",
|
| 119 |
+
error=result.failure_reason if not result.success else "",
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
return SwebenchOutcome(
|
| 124 |
+
instance_id=instance_id, repo=repo, resolved=False,
|
| 125 |
+
duration_seconds=time.time() - start, mode="rhodawk", error=str(e),
|
| 126 |
+
)
|
| 127 |
+
finally:
|
| 128 |
+
try:
|
| 129 |
+
if os.path.exists(test_path):
|
| 130 |
+
os.unlink(test_path)
|
| 131 |
+
except OSError:
|
| 132 |
+
pass
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _run_via_external_command(instance: dict[str, Any]) -> SwebenchOutcome:
|
| 136 |
+
"""
|
| 137 |
+
Legacy mode: delegate to an external evaluator binary.
|
| 138 |
+
Set RHODAWK_SWEBENCH_COMMAND to use this path.
|
| 139 |
+
Note: metrics produced this way are not routed through the Rhodawk healing
|
| 140 |
+
loop and cannot be claimed as Rhodawk pass@1 results.
|
| 141 |
+
"""
|
| 142 |
+
start = time.time()
|
| 143 |
+
instance_id = instance.get("instance_id", "unknown")
|
| 144 |
+
repo = instance.get("repo", "unknown")
|
| 145 |
+
command = os.getenv("RHODAWK_SWEBENCH_COMMAND", "")
|
| 146 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
payload = json.dumps(instance)
|
| 148 |
proc = subprocess.run(
|
| 149 |
command.split(),
|
|
|
|
| 155 |
)
|
| 156 |
resolved = proc.returncode == 0
|
| 157 |
return SwebenchOutcome(
|
| 158 |
+
instance_id=instance_id, repo=repo, resolved=resolved,
|
| 159 |
+
duration_seconds=time.time() - start, mode="external",
|
|
|
|
|
|
|
| 160 |
error="" if resolved else (proc.stderr or proc.stdout)[-1000:],
|
| 161 |
)
|
| 162 |
except Exception as e:
|
| 163 |
return SwebenchOutcome(
|
| 164 |
+
instance_id=instance_id, repo=repo, resolved=False,
|
| 165 |
+
duration_seconds=time.time() - start, mode="external", error=str(e),
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def evaluate_single_instance(
|
| 170 |
+
instance: dict[str, Any],
|
| 171 |
+
process_fn: Optional[Callable] = None,
|
| 172 |
+
env_config: Any = None,
|
| 173 |
+
mcp_config_path: str = "",
|
| 174 |
+
repo_dir: str = "/data/repo",
|
| 175 |
+
) -> SwebenchOutcome:
|
| 176 |
+
"""
|
| 177 |
+
Evaluate one SWE-bench instance.
|
| 178 |
+
|
| 179 |
+
If process_fn (Rhodawk's process_failing_test) is provided, route through
|
| 180 |
+
the full Rhodawk healing loop — this produces valid pass@1 metrics.
|
| 181 |
+
Otherwise fall back to RHODAWK_SWEBENCH_COMMAND (legacy external mode).
|
| 182 |
+
"""
|
| 183 |
+
if process_fn is not None:
|
| 184 |
+
return _run_via_rhodawk(instance, process_fn, env_config, mcp_config_path, repo_dir)
|
| 185 |
+
|
| 186 |
+
command = os.getenv("RHODAWK_SWEBENCH_COMMAND", "")
|
| 187 |
+
if not command:
|
| 188 |
+
start = time.time()
|
| 189 |
+
return SwebenchOutcome(
|
| 190 |
+
instance_id=instance.get("instance_id", "unknown"),
|
| 191 |
+
repo=instance.get("repo", "unknown"),
|
| 192 |
resolved=False,
|
| 193 |
duration_seconds=time.time() - start,
|
| 194 |
+
mode="external",
|
| 195 |
+
error=(
|
| 196 |
+
"No evaluation method configured. Either pass process_fn= to "
|
| 197 |
+
"evaluate_single_instance() or set RHODAWK_SWEBENCH_COMMAND env var."
|
| 198 |
+
),
|
| 199 |
)
|
| 200 |
|
| 201 |
+
return _run_via_external_command(instance)
|
| 202 |
+
|
| 203 |
|
| 204 |
+
def run_swebench_eval(
|
| 205 |
+
max_instances: int = 100,
|
| 206 |
+
split: str = "test",
|
| 207 |
+
process_fn: Optional[Callable] = None,
|
| 208 |
+
env_config: Any = None,
|
| 209 |
+
mcp_config_path: str = "",
|
| 210 |
+
repo_dir: str = "/data/repo",
|
| 211 |
+
) -> dict:
|
| 212 |
+
"""
|
| 213 |
+
Run SWE-bench evaluation.
|
| 214 |
+
|
| 215 |
+
Pass process_fn=process_failing_test from app.py to use the Rhodawk loop.
|
| 216 |
+
Omit process_fn to fall back to RHODAWK_SWEBENCH_COMMAND (legacy mode).
|
| 217 |
+
"""
|
| 218 |
from datasets import load_dataset
|
| 219 |
|
| 220 |
dataset = load_dataset(SWEBENCH_DATASET, split=split)
|
| 221 |
instances = list(dataset)[:max_instances]
|
| 222 |
+
outcomes = [
|
| 223 |
+
evaluate_single_instance(
|
| 224 |
+
inst,
|
| 225 |
+
process_fn=process_fn,
|
| 226 |
+
env_config=env_config,
|
| 227 |
+
mcp_config_path=mcp_config_path,
|
| 228 |
+
repo_dir=repo_dir,
|
| 229 |
+
)
|
| 230 |
+
for inst in instances
|
| 231 |
+
]
|
| 232 |
+
resolved = sum(1 for o in outcomes if o.resolved)
|
| 233 |
total = len(outcomes) or 1
|
| 234 |
result = {
|
| 235 |
"pass_at_1": resolved / total,
|
|
|
|
| 237 |
"total": len(outcomes),
|
| 238 |
"split": split,
|
| 239 |
"dataset": SWEBENCH_DATASET,
|
| 240 |
+
"mode": "rhodawk" if process_fn else "external",
|
| 241 |
+
"results": [asdict(o) for o in outcomes],
|
| 242 |
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 243 |
}
|
| 244 |
write_reports(result)
|
|
|
|
| 251 |
json.dump(result, f, indent=2)
|
| 252 |
|
| 253 |
pass_pct = result["pass_at_1"] * 100
|
| 254 |
+
mode_label = result.get("mode", "unknown")
|
| 255 |
report = [
|
| 256 |
"# Rhodawk AI SWE-bench Verified Report",
|
| 257 |
"",
|
| 258 |
f"- Dataset: `{result['dataset']}`",
|
| 259 |
f"- Split: `{result['split']}`",
|
| 260 |
+
f"- Evaluation mode: `{mode_label}`",
|
| 261 |
f"- Total instances: {result['total']}",
|
| 262 |
f"- Resolved: {result['resolved']}",
|
| 263 |
f"- pass@1: {pass_pct:.1f}%",
|
| 264 |
f"- Generated: {result['generated_at']}",
|
| 265 |
"",
|
| 266 |
+
"> **Note**: Metrics are only valid when mode=`rhodawk` (routes through the",
|
| 267 |
+
"> Rhodawk healing loop). External-mode metrics are not comparable.",
|
| 268 |
+
"",
|
| 269 |
"## Instance Outcomes",
|
| 270 |
"",
|
| 271 |
]
|
| 272 |
for outcome in result["results"]:
|
| 273 |
status = "RESOLVED" if outcome["resolved"] else "FAILED"
|
| 274 |
+
attempts = f" ({outcome['attempts']} attempt(s))" if outcome.get("attempts") else ""
|
| 275 |
+
report.append(f"- `{outcome['instance_id']}` ({outcome['repo']}): {status}{attempts}")
|
| 276 |
+
|
| 277 |
with open(REPORT_PATH, "w", encoding="utf-8") as f:
|
| 278 |
f.write("\n".join(report))
|
| 279 |
|
| 280 |
|
| 281 |
def main() -> None:
|
| 282 |
+
parser = argparse.ArgumentParser(description="Run SWE-bench Verified evaluation via Rhodawk.")
|
| 283 |
+
parser.add_argument("--split", default=os.getenv("RHODAWK_SWEBENCH_SPLIT", "test"))
|
| 284 |
+
parser.add_argument("--max-instances", type=int,
|
| 285 |
+
default=int(os.getenv("RHODAWK_SWEBENCH_MAX", "100")))
|
| 286 |
args = parser.parse_args()
|
| 287 |
+
# Standalone mode uses external command or fails clearly
|
| 288 |
result = run_swebench_eval(max_instances=args.max_instances, split=args.split)
|
| 289 |
+
print(json.dumps({k: result[k] for k in ("pass_at_1", "resolved", "total", "mode")}, indent=2))
|
| 290 |
|
| 291 |
|
| 292 |
if __name__ == "__main__":
|
| 293 |
+
main()
|