narcolepticchicken commited on
Commit
07f29dc
·
verified ·
1 Parent(s): 366b543

Upload quick_validate.py

Browse files
Files changed (1) hide show
  1. quick_validate.py +309 -0
quick_validate.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Quick single-instance cascade validation.
3
+
4
+ Runs the cascade agent + verification on ONE instance.
5
+ This is the minimal proof that the cascade works end-to-end.
6
+
7
+ Usage:
8
+ python quick_validate.py
9
+ python quick_validate.py --instance django__django-11815
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+ import time
19
+ import traceback
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+ from typing import Optional, Tuple
23
+
24
+ from huggingface_hub import InferenceClient
25
+
26
+
27
+ # ============================================================
28
+ # Pick the easiest instance first
29
+ # ============================================================
30
+ DEFAULT_INSTANCE = "django__django-14315" # django bug with clean fix
31
+
32
+ # For T1/T2 models (free HF inference)
33
+ T1_MODEL = "meta-llama/Llama-3.1-8B-Instruct"
34
+ T2_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
35
+
36
+
37
+ def run(cmd, cwd=None, timeout=120):
38
+ result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout, shell=True)
39
+ return result.returncode, result.stdout, result.stderr
40
+
41
+
42
+ def call_model(client, messages, max_tokens=4096):
43
+ """Call HF inference, return (text, input_tokens, output_tokens)."""
44
+ try:
45
+ completion = client.chat.completions.create(
46
+ model=client.model,
47
+ messages=messages,
48
+ max_tokens=max_tokens,
49
+ temperature=0.2,
50
+ )
51
+ text = completion.choices[0].message.content
52
+ itok = completion.usage.prompt_tokens if hasattr(completion, 'usage') and completion.usage else 0
53
+ otok = completion.usage.completion_tokens if hasattr(completion, 'usage') and completion.usage else 0
54
+ return text, itok, otok
55
+ except Exception as e:
56
+ return f"[ERROR: {e}]", 0, 0
57
+
58
+
59
+ def extract_patch(text):
60
+ """Extract a diff/patch from model output."""
61
+ for tag in ['patch', 'diff']:
62
+ m = re.search(rf'<{tag}>(.*?)</{tag}>', text, re.DOTALL)
63
+ if m:
64
+ return m.group(1).strip()
65
+ for block in ['diff', 'patch']:
66
+ m = re.search(rf'```{block}\s*\n(.*?)```', text, re.DOTALL)
67
+ if m:
68
+ return m.group(1).strip()
69
+ diff_match = re.search(r'(diff --git a/.*?(?:\n(?:@@|\+\+\+|diff --git|```|</).*)*)', text, re.DOTALL)
70
+ if diff_match:
71
+ return diff_match.group(1).strip()
72
+ return None
73
+
74
+
75
+ def run_cascade(instance, repo_dir):
76
+ """Run T1 then T2. Returns {patch, tier, turns, tokens}."""
77
+
78
+ problem = instance.get("problem_statement", "")
79
+
80
+ system = f"""You are fixing a bug in {instance['repo']}. Repository is at {repo_dir}.
81
+
82
+ Output format:
83
+ - Bash commands: <bash>command</bash>
84
+ - Final patch: <patch>your diff here</patch>
85
+ - Done: <submit>Done</submit>
86
+
87
+ First explore the codebase to understand the issue, then make a minimal fix and verify it."""
88
+
89
+ messages = [
90
+ {"role": "system", "content": system},
91
+ {"role": "user", "content": f"PROBLEM:\n{problem}\n\nStart by exploring the repository."}
92
+ ]
93
+
94
+ tiers = [
95
+ ("T1", T1_MODEL, 30),
96
+ ("T2", T2_MODEL, 30),
97
+ ]
98
+
99
+ for tier_name, model_id, max_turns in tiers:
100
+ print(f"\n[{tier_name}] Running {model_id}...")
101
+ client = InferenceClient(model_id)
102
+ tier_turns = 0
103
+ tier_itok = 0
104
+ tier_otok = 0
105
+
106
+ for turn in range(max_turns):
107
+ text, itok, otok = call_model(client, messages, max_tokens=4096)
108
+ tier_turns += 1
109
+ tier_itok += itok
110
+ tier_otok += otok
111
+ messages.append({"role": "assistant", "content": text})
112
+
113
+ # Extract patch
114
+ patch = extract_patch(text)
115
+ if patch:
116
+ print(f" ✅ Patch found ({len(patch)} chars) at turn {turn+1}")
117
+ return {"patch": patch, "tier": tier_name, "turns": tier_turns, "input_tokens": tier_itok, "output_tokens": tier_otok}
118
+
119
+ # Execute bash commands
120
+ cmds = re.findall(r'<bash>(.*?)</bash>', text, re.DOTALL)
121
+ for cmd in cmds:
122
+ cmd = cmd.strip()
123
+ print(f" $ {cmd[:80]}...")
124
+ rc, stdout, stderr = run(cmd, cwd=str(repo_dir), timeout=30)
125
+ output = (stdout + stderr)[:1500]
126
+ if rc != 0:
127
+ output += f" [EXIT:{rc}]"
128
+ messages.append({"role": "user", "content": f"<output>\n{output}\n</output>"})
129
+
130
+ if "<submit>" in text:
131
+ break
132
+
133
+ return {"patch": None, "tier": None, "turns": 0, "input_tokens": 0, "output_tokens": 0}
134
+
135
+
136
+ def verify_patch(instance, model_patch, repo_dir, env_name=None):
137
+ """Apply patch + test_patch, run FAIL_TO_PASS tests."""
138
+ base_commit = instance.get("base_commit", "")
139
+ test_patch = instance.get("test_patch", "")
140
+ f2p = instance.get("FAIL_TO_PASS", [])
141
+
142
+ if not base_commit or not test_patch or not f2p:
143
+ return {"resolved": False, "error": "missing base_commit/test_patch/FAIL_TO_PASS"}
144
+
145
+ # Reset
146
+ run(f"git checkout -f {base_commit}", cwd=str(repo_dir))
147
+
148
+ # Apply model patch
149
+ patch_file = repo_dir / "_aco.patch"
150
+ patch_file.write_text(model_patch)
151
+ rc, out, err = run(f"git apply --check {patch_file}", cwd=str(repo_dir))
152
+ if rc != 0:
153
+ return {"resolved": False, "error": f"patch --check: {err[:200]}"}
154
+ rc, out, err = run(f"git apply {patch_file}", cwd=str(repo_dir))
155
+ if rc != 0:
156
+ rc, out, err = run(f"git apply --reject {patch_file}", cwd=str(repo_dir))
157
+ if rc != 0:
158
+ return {"resolved": False, "error": f"patch apply: {err[:200]}"}
159
+
160
+ # Apply test patch
161
+ test_file = repo_dir / "_aco_test.patch"
162
+ test_file.write_text(test_patch)
163
+ rc, out, err = run(f"git apply --check {test_file}", cwd=str(repo_dir))
164
+ if rc == 0:
165
+ run(f"git apply {test_file}", cwd=str(repo_dir))
166
+
167
+ # Run FAIL_TO_PASS
168
+ cmd_prefix = f"conda run -n {env_name} " if env_name else ""
169
+ cmd = f"{cmd_prefix}python -m pytest -v --tb=short -x {' '.join(f2p)}"
170
+ print(f" Running: pytest {' '.join(f2p[:2])}...")
171
+ rc, out, err = run(cmd, cwd=str(repo_dir), timeout=300)
172
+
173
+ if rc == 0:
174
+ # Check regressions
175
+ p2p = instance.get("PASS_TO_PASS", [])
176
+ if p2p:
177
+ cmd2 = f"{cmd_prefix}python -m pytest -v --tb=short -x {' '.join(p2p[:15])}"
178
+ rc2, out2, err2 = run(cmd2, cwd=str(repo_dir), timeout=300)
179
+ if rc2 == 0:
180
+ return {"resolved": True, "regressions": False}
181
+ else:
182
+ return {"resolved": False, "error": f"regression: {(out2+err2)[:200]}", "regressions": True}
183
+ return {"resolved": True, "regressions": False}
184
+
185
+ # Count failures
186
+ failures = [l.strip() for l in (out+err).split('\n') if 'FAILED' in l]
187
+ return {"resolved": False, "error": f"{len(failures)} F2P failures", "failures": failures[:5]}
188
+
189
+
190
+ def main():
191
+ from datasets import load_dataset
192
+
193
+ instance_id = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_INSTANCE
194
+ print(f"Validating: {instance_id}")
195
+
196
+ ds = load_dataset("princeton-nlp/SWE-bench_Verified", split="test")
197
+ instance = None
198
+ for row in ds:
199
+ if row["instance_id"] == instance_id:
200
+ instance = dict(row)
201
+ break
202
+
203
+ if not instance:
204
+ print(f"Instance {instance_id} not found!")
205
+ sys.exit(1)
206
+
207
+ print(f" Repo: {instance['repo']}")
208
+ print(f" Base: {instance['base_commit'][:12]}")
209
+ print(f" F2P: {len(instance.get('FAIL_TO_PASS', []))} tests")
210
+
211
+ with tempfile.TemporaryDirectory(prefix=f"aco_quick_") as tmpdir:
212
+ repo_dir = Path(tmpdir) / "repo"
213
+ env_name = f"aco_q_{instance_id.replace('__','_').replace('-','_')[:30]}"
214
+
215
+ # Clone
216
+ repo = instance["repo"]
217
+ url = f"https://github.com/{repo}.git"
218
+ print(f"\n[CLONE] {url}")
219
+ rc, out, err = run(f"git clone --depth 50 {url} {repo_dir}", timeout=180)
220
+ if rc != 0:
221
+ rc, out, err = run(f"git clone {url} {repo_dir}", timeout=600)
222
+ if rc != 0:
223
+ print(f"CLONE FAILED: {err[:300]}")
224
+ sys.exit(1)
225
+
226
+ # Set up conda env
227
+ env_commit = instance.get("environment_setup_commit", "")
228
+ if env_commit:
229
+ run(f"cd {repo_dir} && git fetch origin {env_commit}", timeout=60)
230
+ run(f"cd {repo_dir} && git checkout {env_commit}", timeout=30)
231
+
232
+ env_yml = None
233
+ for c in ["environment.yml", "dev/environment.yml", ".github/environment.yml"]:
234
+ if (repo_dir / c).exists():
235
+ env_yml = c
236
+ break
237
+
238
+ print(f"\n[ENV] Creating conda env '{env_name}'...")
239
+ if env_yml:
240
+ rc, out, err = run(f"cd {repo_dir} && conda env create -f {env_yml} -n {env_name} --quiet", timeout=600)
241
+ else:
242
+ rc, out, err = run(f"conda create -n {env_name} python=3.10 pip -y --quiet", timeout=300)
243
+
244
+ if rc != 0:
245
+ print(f"ENV SETUP FAILED: {err[:300]}")
246
+ sys.exit(1)
247
+
248
+ # Install repo at base_commit
249
+ base_commit = instance["base_commit"]
250
+ run(f"cd {repo_dir} && git fetch origin {base_commit}", timeout=60)
251
+ run(f"cd {repo_dir} && git checkout {base_commit}", timeout=30)
252
+ rc, out, err = run(f"cd {repo_dir} && conda run -n {env_name} pip install -e . --quiet", timeout=300)
253
+ if rc != 0:
254
+ print(f"PIP INSTALL FAILED (continuing): {err[:200]}")
255
+
256
+ print(f"\n[CASCADE] Running agent...")
257
+ t0 = time.time()
258
+ agent_result = run_cascade(instance, repo_dir)
259
+ agent_time = time.time() - t0
260
+
261
+ print(f" Patch: {'FOUND' if agent_result['patch'] else 'NOT FOUND'}")
262
+ print(f" Tier: {agent_result['tier']}")
263
+ print(f" Time: {agent_time:.1f}s")
264
+
265
+ if not agent_result["patch"]:
266
+ print("FAILED: No patch produced")
267
+ sys.exit(1)
268
+
269
+ print(f"\n[VERIFY] Testing patch...")
270
+ verify_result = verify_patch(instance, agent_result["patch"], repo_dir, env_name)
271
+
272
+ print(f"\n{'='*60}")
273
+ print(f"RESULT: {'✅ RESOLVED' if verify_result['resolved'] else '❌ NOT RESOLVED'}")
274
+ print(f"{'='*60}")
275
+ print(f" Instance: {instance_id}")
276
+ print(f" Tier: {agent_result['tier']}")
277
+ print(f" Turns: {agent_result['turns']}")
278
+ print(f" Tokens: {agent_result['input_tokens']} in / {agent_result['output_tokens']} out")
279
+ print(f" Agent time: {agent_time:.1f}s")
280
+ if not verify_result["resolved"]:
281
+ print(f" Error: {verify_result.get('error', 'unknown')}")
282
+
283
+ # Save result
284
+ final = {
285
+ "instance_id": instance_id,
286
+ "repo": instance["repo"],
287
+ "timestamp": datetime.now().isoformat(),
288
+ "resolved": verify_result["resolved"],
289
+ "tier": agent_result["tier"],
290
+ "turns": agent_result["turns"],
291
+ "input_tokens": agent_result["input_tokens"],
292
+ "output_tokens": agent_result["output_tokens"],
293
+ "agent_time_seconds": agent_time,
294
+ "error": verify_result.get("error"),
295
+ }
296
+
297
+ result_path = f"quick_validate_{instance_id}.json"
298
+ with open(result_path, "w") as f:
299
+ json.dump(final, f, indent=2)
300
+ print(f"\n Saved: {result_path}")
301
+
302
+ # Cleanup
303
+ run(f"conda env remove -n {env_name} -y --quiet", timeout=30)
304
+
305
+ return 0 if verify_result["resolved"] else 1
306
+
307
+
308
+ if __name__ == "__main__":
309
+ sys.exit(main())