neurocode-ai 0.1.0__py3-none-any.whl
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.
- neurocode_ai-0.1.0.dist-info/METADATA +193 -0
- neurocode_ai-0.1.0.dist-info/RECORD +30 -0
- neurocode_ai-0.1.0.dist-info/WHEEL +5 -0
- neurocode_ai-0.1.0.dist-info/entry_points.txt +4 -0
- neurocode_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
- neurocode_ai-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/agent/llm_interface.py +62 -0
- src/agent/patcher.py +124 -0
- src/benchmark/create_thumbnail.py +64 -0
- src/benchmark/end_to_end_benchmark.py +638 -0
- src/benchmark/evaluator.py +124 -0
- src/benchmark/generate_4x3_figures.py +259 -0
- src/benchmark/mlsys_microbenchmarks.py +351 -0
- src/benchmark/plot_ablation.py +96 -0
- src/benchmark/plot_architecture.py +79 -0
- src/benchmark/plot_empirical_fig1.py +97 -0
- src/benchmark/run_ablation_study.py +205 -0
- src/benchmark/run_code_benchmarks.py +181 -0
- src/benchmark/run_comprehensive_10_tests.py +424 -0
- src/benchmark/run_dense_vector_rag_benchmark.py +271 -0
- src/benchmark/run_ephemeral_swebench.py +474 -0
- src/benchmark/run_hardware_cliff_test.py +341 -0
- src/benchmark/swebench_evaluator.py +147 -0
- src/cli.py +298 -0
- src/graph/__init__.py +0 -0
- src/graph/builder.py +124 -0
- src/graph/parser.py +175 -0
- src/graph/slicer.py +202 -0
- src/mcp_server.py +370 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NeuroCode End-to-End Bug Resolution Benchmark Harness.
|
|
3
|
+
Directly addresses Academic Review Gap 1:
|
|
4
|
+
Evaluates real repository repair with automated test suites (pytest),
|
|
5
|
+
comparing:
|
|
6
|
+
1. Raw Context Dump (Naive Baseline)
|
|
7
|
+
2. BM25 Lexical RAG (Agentless Baseline)
|
|
8
|
+
3. Unconstrained Graph Traversal (RepoGraph Baseline)
|
|
9
|
+
4. NeuroCode Priority-Ranked Knapsack Slicer (Our Proposed Method)
|
|
10
|
+
|
|
11
|
+
Measures:
|
|
12
|
+
- Retrieval Accuracy (Target Bug Entity captured)
|
|
13
|
+
- Patch Generation & Syntactic Validity
|
|
14
|
+
- Unit Test Pass Rate (% Resolved)
|
|
15
|
+
- Token Footprint & Peak GPU VRAM
|
|
16
|
+
- Preprocessing, Inference, and Total Latencies
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Ensure project root is in sys.path
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
25
|
+
|
|
26
|
+
import time
|
|
27
|
+
import tempfile
|
|
28
|
+
import subprocess
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
31
|
+
|
|
32
|
+
import torch
|
|
33
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
34
|
+
|
|
35
|
+
from src.graph.builder import RepoGraphBuilder
|
|
36
|
+
from src.graph.slicer import CodeGraphSlicer
|
|
37
|
+
from src.agent.patcher import PatchApplier, SYSTEM_PROMPT
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class BugTask:
|
|
42
|
+
task_id: str
|
|
43
|
+
title: str
|
|
44
|
+
problem_statement: str
|
|
45
|
+
files: Dict[str, str]
|
|
46
|
+
test_path: str
|
|
47
|
+
test_code: str
|
|
48
|
+
target_bug_file: str
|
|
49
|
+
target_bug_symbol: str
|
|
50
|
+
category: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class MethodEvaluationResult:
|
|
55
|
+
method: str
|
|
56
|
+
task_id: str
|
|
57
|
+
input_tokens: int
|
|
58
|
+
output_tokens: int
|
|
59
|
+
prep_time_ms: float
|
|
60
|
+
infer_time_ms: float
|
|
61
|
+
total_time_ms: float
|
|
62
|
+
peak_vram_mb: float
|
|
63
|
+
captured_target: bool
|
|
64
|
+
valid_patch_format: bool
|
|
65
|
+
patch_applied: bool
|
|
66
|
+
test_passed: bool
|
|
67
|
+
model_output: str
|
|
68
|
+
error_message: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_curated_benchmark_tasks() -> List[BugTask]:
|
|
72
|
+
"""
|
|
73
|
+
Curated suite of realistic multi-module software engineering bugs.
|
|
74
|
+
Each bug has a multi-file dependency structure and an isolated unit test suite.
|
|
75
|
+
"""
|
|
76
|
+
tasks = []
|
|
77
|
+
|
|
78
|
+
# -------------------------------------------------------------------------
|
|
79
|
+
# Task 1: Cross-File Indirect Call Bug
|
|
80
|
+
# Issue keywords describe API gateway crash, but root cause is in crypto signer.
|
|
81
|
+
# -------------------------------------------------------------------------
|
|
82
|
+
tasks.append(BugTask(
|
|
83
|
+
task_id="NC-TASK-001",
|
|
84
|
+
title="Indirect Callee NoneType Crash in HMAC Verification",
|
|
85
|
+
category="Indirect Call Chain",
|
|
86
|
+
problem_statement=(
|
|
87
|
+
"Error: handle_incoming_request in api/gateway.py crashes with AttributeError "
|
|
88
|
+
"when client signature payload contains None body. Client request authentication "
|
|
89
|
+
"must return False instead of raising an unhandled exception."
|
|
90
|
+
),
|
|
91
|
+
files={
|
|
92
|
+
"api/gateway.py": (
|
|
93
|
+
"from crypto.signer import verify_signature\n\n"
|
|
94
|
+
"def handle_incoming_request(request_payload: dict) -> dict:\n"
|
|
95
|
+
" '''Entrypoint that processes incoming client requests.'''\n"
|
|
96
|
+
" sig = request_payload.get('signature')\n"
|
|
97
|
+
" data = request_payload.get('body')\n"
|
|
98
|
+
" if not verify_signature(data, sig):\n"
|
|
99
|
+
" raise ValueError('Invalid client signature verification failed')\n"
|
|
100
|
+
" return {'status': 'success'}\n"
|
|
101
|
+
),
|
|
102
|
+
"crypto/signer.py": (
|
|
103
|
+
"import hmac\n"
|
|
104
|
+
"import hashlib\n\n"
|
|
105
|
+
"def verify_signature(data: str, signature: str) -> bool:\n"
|
|
106
|
+
" '''Calculates HMAC SHA256 digest.'''\n"
|
|
107
|
+
" if not signature:\n"
|
|
108
|
+
" return False\n"
|
|
109
|
+
" expected = hmac.new(b'secret_key', data.encode('utf-8'), hashlib.sha256).hexdigest()\n"
|
|
110
|
+
" return hmac.compare_digest(expected, signature)\n"
|
|
111
|
+
),
|
|
112
|
+
"api/logger.py": (
|
|
113
|
+
"def client_request_logger(client_name: str, signature_status: str):\n"
|
|
114
|
+
" '''Logs client request signature verification status.'''\n"
|
|
115
|
+
" print(f'Client {client_name} signature: {signature_status}')\n"
|
|
116
|
+
),
|
|
117
|
+
"common/utils.py": (
|
|
118
|
+
"def format_response(code: int, message: str) -> dict:\n"
|
|
119
|
+
" return {'code': code, 'message': message}\n"
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
test_path="tests/test_gateway.py",
|
|
123
|
+
test_code=(
|
|
124
|
+
"import pytest\n"
|
|
125
|
+
"from api.gateway import handle_incoming_request\n"
|
|
126
|
+
"from crypto.signer import verify_signature\n\n"
|
|
127
|
+
"def test_signature_handles_none():\n"
|
|
128
|
+
" # Fails if verify_signature crashes on data=None\n"
|
|
129
|
+
" res = verify_signature(None, 'some_sig')\n"
|
|
130
|
+
" assert res is False\n\n"
|
|
131
|
+
"def test_gateway_handles_none_body():\n"
|
|
132
|
+
" with pytest.raises(ValueError, match='Invalid client signature'):\n"
|
|
133
|
+
" handle_incoming_request({'body': None, 'signature': 'abc'})\n"
|
|
134
|
+
),
|
|
135
|
+
target_bug_file="crypto/signer.py",
|
|
136
|
+
target_bug_symbol="def verify_signature",
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
# -------------------------------------------------------------------------
|
|
140
|
+
# Task 2: Class Inheritance Hierarchy Override Bug
|
|
141
|
+
# Subclass handler crashes because parent BaseSerializer lacks ISO-8601 parsing.
|
|
142
|
+
# -------------------------------------------------------------------------
|
|
143
|
+
tasks.append(BugTask(
|
|
144
|
+
task_id="NC-TASK-002",
|
|
145
|
+
title="Class Inheritance Fallback in Datetime Serialization",
|
|
146
|
+
category="Inheritance Hierarchy",
|
|
147
|
+
problem_statement=(
|
|
148
|
+
"TypeError: CustomJsonSerializer in handlers/json_handler.py fails when serializing "
|
|
149
|
+
"objects containing datetime instances. Base serializer fallback must support "
|
|
150
|
+
"ISO-8601 formatting for datetime objects."
|
|
151
|
+
),
|
|
152
|
+
files={
|
|
153
|
+
"core/base_handler.py": (
|
|
154
|
+
"from datetime import datetime\n\n"
|
|
155
|
+
"class BaseSerializer:\n"
|
|
156
|
+
" '''Base serializer with primitive dispatch.'''\n"
|
|
157
|
+
" def serialize_value(self, val):\n"
|
|
158
|
+
" if isinstance(val, (int, float, str, bool)):\n"
|
|
159
|
+
" return val\n"
|
|
160
|
+
" raise TypeError(f'Type {type(val)} not serializable')\n"
|
|
161
|
+
),
|
|
162
|
+
"handlers/json_handler.py": (
|
|
163
|
+
"import json\n"
|
|
164
|
+
"from core.base_handler import BaseSerializer\n\n"
|
|
165
|
+
"class CustomJsonSerializer(BaseSerializer):\n"
|
|
166
|
+
" '''JSON serializer extending base serialization.'''\n"
|
|
167
|
+
" def serialize(self, data: dict) -> str:\n"
|
|
168
|
+
" out = {}\n"
|
|
169
|
+
" for k, v in data.items():\n"
|
|
170
|
+
" out[k] = self.serialize_value(v)\n"
|
|
171
|
+
" return json.dumps(out)\n"
|
|
172
|
+
),
|
|
173
|
+
"handlers/xml_handler.py": (
|
|
174
|
+
"class XmlSerializer:\n"
|
|
175
|
+
" def serialize(self, data):\n"
|
|
176
|
+
" return '<xml></xml>'\n"
|
|
177
|
+
),
|
|
178
|
+
"common/helpers.py": (
|
|
179
|
+
"def dump_json(obj):\n"
|
|
180
|
+
" return str(obj)\n"
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
test_path="tests/test_serializer.py",
|
|
184
|
+
test_code=(
|
|
185
|
+
"from datetime import datetime\n"
|
|
186
|
+
"from handlers.json_handler import CustomJsonSerializer\n\n"
|
|
187
|
+
"def test_custom_serializer_datetime():\n"
|
|
188
|
+
" s = CustomJsonSerializer()\n"
|
|
189
|
+
" now = datetime(2026, 10, 18, 12, 0, 0)\n"
|
|
190
|
+
" res = s.serialize({'timestamp': now, 'status': 'ok'})\n"
|
|
191
|
+
" assert '2026-10-18T12:00:00' in res or '2026-10-18' in res\n"
|
|
192
|
+
),
|
|
193
|
+
target_bug_file="core/base_handler.py",
|
|
194
|
+
target_bug_symbol="class BaseSerializer",
|
|
195
|
+
))
|
|
196
|
+
|
|
197
|
+
# -------------------------------------------------------------------------
|
|
198
|
+
# Task 3: Cross-Module Config Validation Bug
|
|
199
|
+
# Issue mentions auth token error, but bug is in configuration loader.
|
|
200
|
+
# -------------------------------------------------------------------------
|
|
201
|
+
tasks.append(BugTask(
|
|
202
|
+
task_id="NC-TASK-003",
|
|
203
|
+
title="Cross-Module JWT Configuration Validation",
|
|
204
|
+
category="Module Import & Call",
|
|
205
|
+
problem_statement=(
|
|
206
|
+
"Configuration error: validate_jwt_settings in config/jwt_config.py fails to reject "
|
|
207
|
+
"RS256 algorithm when public_key is empty or None. It currently only checks secret_key."
|
|
208
|
+
),
|
|
209
|
+
files={
|
|
210
|
+
"auth/token_manager.py": (
|
|
211
|
+
"from config.jwt_config import validate_jwt_settings\n\n"
|
|
212
|
+
"def create_auth_token(claims: dict, config: dict) -> str:\n"
|
|
213
|
+
" '''Generates verified auth token using configured algorithm.'''\n"
|
|
214
|
+
" if not validate_jwt_settings(config):\n"
|
|
215
|
+
" raise ValueError('Invalid JWT configuration')\n"
|
|
216
|
+
" return f'token.{claims.get(\"sub\")}'\n"
|
|
217
|
+
),
|
|
218
|
+
"config/jwt_config.py": (
|
|
219
|
+
"def validate_jwt_settings(config: dict) -> bool:\n"
|
|
220
|
+
" '''Validates token configuration parameters.'''\n"
|
|
221
|
+
" algo = config.get('algorithm', 'HS256')\n"
|
|
222
|
+
" secret = config.get('secret_key')\n"
|
|
223
|
+
" if not secret:\n"
|
|
224
|
+
" return False\n"
|
|
225
|
+
" return True\n"
|
|
226
|
+
),
|
|
227
|
+
"auth/user_service.py": (
|
|
228
|
+
"def get_user_claims(user_id: str) -> dict:\n"
|
|
229
|
+
" return {'sub': user_id, 'role': 'admin'}\n"
|
|
230
|
+
),
|
|
231
|
+
"config/db_config.py": (
|
|
232
|
+
"def get_database_url():\n"
|
|
233
|
+
" return 'sqlite:///:memory:'\n"
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
test_path="tests/test_jwt_config.py",
|
|
237
|
+
test_code=(
|
|
238
|
+
"import pytest\n"
|
|
239
|
+
"from config.jwt_config import validate_jwt_settings\n"
|
|
240
|
+
"from auth.token_manager import create_auth_token\n\n"
|
|
241
|
+
"def test_rs256_requires_public_key():\n"
|
|
242
|
+
" bad_cfg = {'algorithm': 'RS256', 'secret_key': 'secret', 'public_key': None}\n"
|
|
243
|
+
" assert validate_jwt_settings(bad_cfg) is False\n\n"
|
|
244
|
+
"def test_token_creation_fails_on_bad_rs256():\n"
|
|
245
|
+
" bad_cfg = {'algorithm': 'RS256', 'secret_key': 'secret', 'public_key': ''}\n"
|
|
246
|
+
" with pytest.raises(ValueError, match='Invalid JWT configuration'):\n"
|
|
247
|
+
" create_auth_token({'sub': 'user1'}, bad_cfg)\n"
|
|
248
|
+
),
|
|
249
|
+
target_bug_file="config/jwt_config.py",
|
|
250
|
+
target_bug_symbol="def validate_jwt_settings",
|
|
251
|
+
))
|
|
252
|
+
|
|
253
|
+
# -------------------------------------------------------------------------
|
|
254
|
+
# Task 4: Multi-hop Payment Charge Amount Boundary Check
|
|
255
|
+
# Entrypoint in checkout route calls payment client where negative amount is permitted.
|
|
256
|
+
# -------------------------------------------------------------------------
|
|
257
|
+
tasks.append(BugTask(
|
|
258
|
+
task_id="NC-TASK-004",
|
|
259
|
+
title="Nested Multi-hop Payment Negative Charge Guard",
|
|
260
|
+
category="Nested Call Chain",
|
|
261
|
+
problem_statement=(
|
|
262
|
+
"Bug: checkout route allows negative charge amounts to reach payment gateway. "
|
|
263
|
+
"create_charge in payments/gateway_client.py must raise ValueError if amount <= 0."
|
|
264
|
+
),
|
|
265
|
+
files={
|
|
266
|
+
"web/checkout_route.py": (
|
|
267
|
+
"from services.billing import process_order_payment\n\n"
|
|
268
|
+
"def checkout_endpoint(order_id: str, amount: float) -> dict:\n"
|
|
269
|
+
" '''Web handler for processing checkout orders.'''\n"
|
|
270
|
+
" charge_id = process_order_payment(order_id, amount)\n"
|
|
271
|
+
" return {'order_id': order_id, 'charge_id': charge_id, 'status': 'paid'}\n"
|
|
272
|
+
),
|
|
273
|
+
"services/billing.py": (
|
|
274
|
+
"from payments.gateway_client import create_charge\n\n"
|
|
275
|
+
"def process_order_payment(order_id: str, amount: float) -> str:\n"
|
|
276
|
+
" '''Orchestrates billing transaction for an order.'''\n"
|
|
277
|
+
" return create_charge(f'order_{order_id}', amount)\n"
|
|
278
|
+
),
|
|
279
|
+
"payments/gateway_client.py": (
|
|
280
|
+
"def create_charge(customer_ref: str, amount: float) -> str:\n"
|
|
281
|
+
" '''Submits charge to payment backend.'''\n"
|
|
282
|
+
" return f'ch_{customer_ref}_{int(amount)}'\n"
|
|
283
|
+
),
|
|
284
|
+
"web/distractor_payment.py": (
|
|
285
|
+
"def log_payment_amount(order_id, amount):\n"
|
|
286
|
+
" '''Logs payment checkout amount.'''\n"
|
|
287
|
+
" print(f'Order {order_id} checkout amount: {amount}')\n"
|
|
288
|
+
),
|
|
289
|
+
},
|
|
290
|
+
test_path="tests/test_payment.py",
|
|
291
|
+
test_code=(
|
|
292
|
+
"import pytest\n"
|
|
293
|
+
"from payments.gateway_client import create_charge\n"
|
|
294
|
+
"from web.checkout_route import checkout_endpoint\n\n"
|
|
295
|
+
"def test_negative_charge_rejected():\n"
|
|
296
|
+
" with pytest.raises(ValueError, match='amount'):\n"
|
|
297
|
+
" create_charge('cust_1', -50.0)\n\n"
|
|
298
|
+
"def test_zero_charge_rejected():\n"
|
|
299
|
+
" with pytest.raises(ValueError, match='amount'):\n"
|
|
300
|
+
" create_charge('cust_1', 0.0)\n\n"
|
|
301
|
+
"def test_valid_charge_succeeds():\n"
|
|
302
|
+
" res = create_charge('cust_1', 100.0)\n"
|
|
303
|
+
" assert res.startswith('ch_')\n"
|
|
304
|
+
),
|
|
305
|
+
target_bug_file="payments/gateway_client.py",
|
|
306
|
+
target_bug_symbol="def create_charge",
|
|
307
|
+
))
|
|
308
|
+
|
|
309
|
+
# -------------------------------------------------------------------------
|
|
310
|
+
# Task 5: Database Query Boundary Slicing
|
|
311
|
+
# sanitize_limit allows negative offset/limit causing index crashes.
|
|
312
|
+
# -------------------------------------------------------------------------
|
|
313
|
+
tasks.append(BugTask(
|
|
314
|
+
task_id="NC-TASK-005",
|
|
315
|
+
title="Query Builder Sanitization Boundary Check",
|
|
316
|
+
category="Database Query Slicing",
|
|
317
|
+
problem_statement=(
|
|
318
|
+
"Issue: sanitize_limit in db/query_builder.py allows negative integers, "
|
|
319
|
+
"causing SQL syntax errors. It must enforce limit >= 1 and clamp values greater than 100 to 100."
|
|
320
|
+
),
|
|
321
|
+
files={
|
|
322
|
+
"db/repository.py": (
|
|
323
|
+
"from db.query_builder import sanitize_limit\n\n"
|
|
324
|
+
"class UserRepository:\n"
|
|
325
|
+
" def fetch_users(self, limit: int = 10) -> str:\n"
|
|
326
|
+
" valid_limit = sanitize_limit(limit)\n"
|
|
327
|
+
" return f'SELECT * FROM users LIMIT {valid_limit}'\n"
|
|
328
|
+
),
|
|
329
|
+
"db/query_builder.py": (
|
|
330
|
+
"def sanitize_limit(limit: int) -> int:\n"
|
|
331
|
+
" '''Sanitizes user pagination limit.'''\n"
|
|
332
|
+
" return limit\n"
|
|
333
|
+
),
|
|
334
|
+
"db/connection.py": (
|
|
335
|
+
"def get_pool():\n"
|
|
336
|
+
" return {}\n"
|
|
337
|
+
),
|
|
338
|
+
"api/user_controller.py": (
|
|
339
|
+
"def get_user_page(page: int, limit: int):\n"
|
|
340
|
+
" return {'page': page, 'limit': limit}\n"
|
|
341
|
+
),
|
|
342
|
+
},
|
|
343
|
+
test_path="tests/test_query.py",
|
|
344
|
+
test_code=(
|
|
345
|
+
"import pytest\n"
|
|
346
|
+
"from db.query_builder import sanitize_limit\n"
|
|
347
|
+
"from db.repository import UserRepository\n\n"
|
|
348
|
+
"def test_sanitize_limit_clamping():\n"
|
|
349
|
+
" assert sanitize_limit(150) == 100\n"
|
|
350
|
+
" assert sanitize_limit(50) == 50\n\n"
|
|
351
|
+
"def test_sanitize_limit_negative():\n"
|
|
352
|
+
" with pytest.raises(ValueError, match='limit'):\n"
|
|
353
|
+
" sanitize_limit(-5)\n"
|
|
354
|
+
),
|
|
355
|
+
target_bug_file="db/query_builder.py",
|
|
356
|
+
target_bug_symbol="def sanitize_limit",
|
|
357
|
+
))
|
|
358
|
+
|
|
359
|
+
return tasks
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class EndToEndBenchmarkRunner:
|
|
363
|
+
"""
|
|
364
|
+
Executes real multi-condition evaluation on physical hardware.
|
|
365
|
+
"""
|
|
366
|
+
def __init__(self, model_id: str = "Qwen/Qwen2.5-Coder-0.5B-Instruct", use_gpu: bool = True):
|
|
367
|
+
self.model_id = model_id
|
|
368
|
+
self.use_gpu = use_gpu and torch.cuda.is_available()
|
|
369
|
+
self.device = "cuda" if self.use_gpu else "cpu"
|
|
370
|
+
self.tokenizer = None
|
|
371
|
+
self.model = None
|
|
372
|
+
|
|
373
|
+
def load_model(self):
|
|
374
|
+
if self.model is None:
|
|
375
|
+
print(f"[BENCHMARK] Loading {self.model_id} on {self.device}...")
|
|
376
|
+
t0 = time.perf_counter()
|
|
377
|
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_id)
|
|
378
|
+
self.model = AutoModelForCausalLM.from_pretrained(
|
|
379
|
+
self.model_id,
|
|
380
|
+
torch_dtype=torch.float16 if self.use_gpu else torch.float32,
|
|
381
|
+
device_map=self.device
|
|
382
|
+
)
|
|
383
|
+
print(f"[BENCHMARK] Model loaded in {time.perf_counter() - t0:.2f}s")
|
|
384
|
+
|
|
385
|
+
def _setup_workspace(self, task: BugTask, target_dir: Path):
|
|
386
|
+
for rel_path, content in task.files.items():
|
|
387
|
+
file_path = target_dir / rel_path
|
|
388
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
389
|
+
file_path.write_text(content, encoding="utf-8")
|
|
390
|
+
|
|
391
|
+
test_file = target_dir / task.test_path
|
|
392
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
393
|
+
test_file.write_text(task.test_code, encoding="utf-8")
|
|
394
|
+
|
|
395
|
+
def _run_pytest(self, workspace_dir: Path, test_rel_path: str) -> bool:
|
|
396
|
+
env = os.environ.copy()
|
|
397
|
+
env["PYTHONPATH"] = str(workspace_dir)
|
|
398
|
+
try:
|
|
399
|
+
res = subprocess.run(
|
|
400
|
+
[sys.executable, "-m", "pytest", "-p", "no:launch_testing", test_rel_path],
|
|
401
|
+
cwd=str(workspace_dir),
|
|
402
|
+
capture_output=True,
|
|
403
|
+
text=True,
|
|
404
|
+
env=env,
|
|
405
|
+
timeout=15
|
|
406
|
+
)
|
|
407
|
+
return res.returncode == 0
|
|
408
|
+
except Exception:
|
|
409
|
+
return False
|
|
410
|
+
|
|
411
|
+
def build_prompt_raw(self, workspace_dir: Path, task: BugTask) -> Tuple[str, float]:
|
|
412
|
+
t0 = time.perf_counter()
|
|
413
|
+
full_text = []
|
|
414
|
+
for rel_path in sorted(task.files.keys()):
|
|
415
|
+
content = (workspace_dir / rel_path).read_text(encoding="utf-8")
|
|
416
|
+
full_text.append(f"FILE: {rel_path}\n```python\n{content}\n```")
|
|
417
|
+
context_str = "\n\n".join(full_text)
|
|
418
|
+
prep_ms = (time.perf_counter() - t0) * 1000
|
|
419
|
+
|
|
420
|
+
prompt = (
|
|
421
|
+
f"You are an expert autonomous software engineer.\n"
|
|
422
|
+
f"Repository Files:\n{context_str}\n\n"
|
|
423
|
+
f"Issue to fix: {task.problem_statement}\n"
|
|
424
|
+
f"Provide the fix using exact SEARCH/REPLACE format:\n"
|
|
425
|
+
f"FILE: <file_path>\n<<<<<<< SEARCH\n=======\n>>>>>>>\n"
|
|
426
|
+
)
|
|
427
|
+
return prompt, prep_ms
|
|
428
|
+
|
|
429
|
+
def build_prompt_bm25(self, workspace_dir: Path, task: BugTask) -> Tuple[str, float]:
|
|
430
|
+
t0 = time.perf_counter()
|
|
431
|
+
q_tokens = set(task.problem_statement.lower().replace(".", " ").replace("/", " ").split())
|
|
432
|
+
scores = {}
|
|
433
|
+
for rel_path in task.files.keys():
|
|
434
|
+
content = (workspace_dir / rel_path).read_text(encoding="utf-8")
|
|
435
|
+
words = content.lower().split()
|
|
436
|
+
score = sum(words.count(q) for q in q_tokens)
|
|
437
|
+
scores[rel_path] = score
|
|
438
|
+
|
|
439
|
+
top_files = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:2]
|
|
440
|
+
context_parts = []
|
|
441
|
+
for rel_path, _ in top_files:
|
|
442
|
+
content = (workspace_dir / rel_path).read_text(encoding="utf-8")
|
|
443
|
+
context_parts.append(f"FILE: {rel_path}\n```python\n{content}\n```")
|
|
444
|
+
context_str = "\n\n".join(context_parts)
|
|
445
|
+
prep_ms = (time.perf_counter() - t0) * 1000
|
|
446
|
+
|
|
447
|
+
prompt = (
|
|
448
|
+
f"You are an expert autonomous software engineer.\n"
|
|
449
|
+
f"Retrieved Context:\n{context_str}\n\n"
|
|
450
|
+
f"Issue to fix: {task.problem_statement}\n"
|
|
451
|
+
f"Provide the fix using exact SEARCH/REPLACE format:\n"
|
|
452
|
+
f"FILE: <file_path>\n<<<<<<< SEARCH\n=======\n>>>>>>>\n"
|
|
453
|
+
)
|
|
454
|
+
return prompt, prep_ms
|
|
455
|
+
|
|
456
|
+
def build_prompt_graph_unconstrained(self, workspace_dir: Path, task: BugTask) -> Tuple[str, float]:
|
|
457
|
+
t0 = time.perf_counter()
|
|
458
|
+
builder = RepoGraphBuilder(str(workspace_dir))
|
|
459
|
+
graph = builder.build()
|
|
460
|
+
slicer = CodeGraphSlicer(graph)
|
|
461
|
+
seeds = slicer.find_seed_nodes(task.problem_statement)
|
|
462
|
+
# Unconstrained 1-hop traversal without knapsack packing
|
|
463
|
+
subgraph = slicer.extract_subgraph(seeds, k_hops=1)
|
|
464
|
+
|
|
465
|
+
# Serialize entire subgraph without token budgeting
|
|
466
|
+
lines = []
|
|
467
|
+
for node in subgraph.nodes:
|
|
468
|
+
data = subgraph.nodes[node]
|
|
469
|
+
lines.append(f"Node: {node} ({data.get('type')})\n{data.get('source', '')}\n")
|
|
470
|
+
context_str = "\n".join(lines)
|
|
471
|
+
prep_ms = (time.perf_counter() - t0) * 1000
|
|
472
|
+
|
|
473
|
+
prompt = (
|
|
474
|
+
f"You are an expert autonomous software engineer.\n"
|
|
475
|
+
f"Graph Context:\n{context_str}\n\n"
|
|
476
|
+
f"Issue to fix: {task.problem_statement}\n"
|
|
477
|
+
f"Provide the fix using exact SEARCH/REPLACE format:\n"
|
|
478
|
+
f"FILE: <file_path>\n<<<<<<< SEARCH\n=======\n>>>>>>>\n"
|
|
479
|
+
)
|
|
480
|
+
return prompt, prep_ms
|
|
481
|
+
|
|
482
|
+
def build_prompt_neurocode(self, workspace_dir: Path, task: BugTask) -> Tuple[str, float]:
|
|
483
|
+
t0 = time.perf_counter()
|
|
484
|
+
builder = RepoGraphBuilder(str(workspace_dir))
|
|
485
|
+
graph = builder.build()
|
|
486
|
+
slicer = CodeGraphSlicer(graph)
|
|
487
|
+
seeds = slicer.find_seed_nodes(task.problem_statement)
|
|
488
|
+
subgraph = slicer.extract_subgraph(seeds, k_hops=1)
|
|
489
|
+
slice_text = slicer.format_slice_for_llm(subgraph, seeds, query=task.problem_statement, max_tokens=1500)
|
|
490
|
+
prep_ms = (time.perf_counter() - t0) * 1000
|
|
491
|
+
|
|
492
|
+
prompt = (
|
|
493
|
+
f"You are an expert autonomous software engineer.\n"
|
|
494
|
+
f"{slice_text}\n\n"
|
|
495
|
+
f"Issue to fix: {task.problem_statement}\n"
|
|
496
|
+
f"Provide the fix using exact SEARCH/REPLACE format:\n"
|
|
497
|
+
f"FILE: <file_path>\n<<<<<<< SEARCH\n=======\n>>>>>>>\n"
|
|
498
|
+
)
|
|
499
|
+
return prompt, prep_ms
|
|
500
|
+
|
|
501
|
+
def evaluate_task_method(self, task: BugTask, method: str) -> MethodEvaluationResult:
|
|
502
|
+
with tempfile.TemporaryDirectory() as td:
|
|
503
|
+
workspace = Path(td)
|
|
504
|
+
self._setup_workspace(task, workspace)
|
|
505
|
+
|
|
506
|
+
# 1. Verify that test originally FAILS
|
|
507
|
+
initial_test_passed = self._run_pytest(workspace, task.test_path)
|
|
508
|
+
assert not initial_test_passed, f"Task {task.task_id} test passed before bug patch! Invalid test."
|
|
509
|
+
|
|
510
|
+
builder = RepoGraphBuilder(str(workspace))
|
|
511
|
+
graph = builder.build()
|
|
512
|
+
|
|
513
|
+
# 2. Build Prompt according to Method
|
|
514
|
+
if method == "Raw Context Dump":
|
|
515
|
+
prompt, prep_ms = self.build_prompt_raw(workspace, task)
|
|
516
|
+
elif method == "BM25 Lexical RAG":
|
|
517
|
+
prompt, prep_ms = self.build_prompt_bm25(workspace, task)
|
|
518
|
+
elif method == "Graph Unconstrained":
|
|
519
|
+
prompt, prep_ms = self.build_prompt_graph_unconstrained(workspace, task)
|
|
520
|
+
elif method == "NeuroCode (Our Method)":
|
|
521
|
+
prompt, prep_ms = self.build_prompt_neurocode(workspace, task)
|
|
522
|
+
else:
|
|
523
|
+
raise ValueError(f"Unknown method {method}")
|
|
524
|
+
|
|
525
|
+
captured_target = task.target_bug_symbol in prompt and task.target_bug_file in prompt
|
|
526
|
+
|
|
527
|
+
messages = [
|
|
528
|
+
{"role": "system", "content": SYSTEM_PROMPT},
|
|
529
|
+
{"role": "user", "content": prompt}
|
|
530
|
+
]
|
|
531
|
+
chat_prompt = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
532
|
+
inputs = self.tokenizer(chat_prompt, return_tensors="pt").to(self.device)
|
|
533
|
+
input_tokens = inputs.input_ids.shape[1]
|
|
534
|
+
|
|
535
|
+
# 3. Model Inference on Device
|
|
536
|
+
if self.use_gpu:
|
|
537
|
+
torch.cuda.reset_peak_memory_stats()
|
|
538
|
+
torch.cuda.empty_cache()
|
|
539
|
+
|
|
540
|
+
t0_infer = time.perf_counter()
|
|
541
|
+
|
|
542
|
+
with torch.no_grad():
|
|
543
|
+
outputs = self.model.generate(
|
|
544
|
+
**inputs,
|
|
545
|
+
max_new_tokens=220,
|
|
546
|
+
temperature=0.2,
|
|
547
|
+
do_sample=True,
|
|
548
|
+
pad_token_id=self.tokenizer.eos_token_id
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
infer_ms = (time.perf_counter() - t0_infer) * 1000
|
|
552
|
+
total_ms = prep_ms + infer_ms
|
|
553
|
+
output_tokens = outputs.shape[1] - inputs.input_ids.shape[1]
|
|
554
|
+
response_text = self.tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
|
|
555
|
+
|
|
556
|
+
peak_vram_mb = 0.0
|
|
557
|
+
if self.use_gpu:
|
|
558
|
+
peak_vram_mb = torch.cuda.max_memory_allocated() / (1024 ** 2)
|
|
559
|
+
|
|
560
|
+
# 4. Patch Application & Verification (with AST fallback)
|
|
561
|
+
patch_res = PatchApplier.extract_patch(response_text, graph=graph, repo_dir=str(workspace))
|
|
562
|
+
valid_patch = patch_res.success
|
|
563
|
+
patch_applied = False
|
|
564
|
+
test_passed = False
|
|
565
|
+
err_msg = None
|
|
566
|
+
|
|
567
|
+
if valid_patch:
|
|
568
|
+
target_file_path = workspace / patch_res.file_path
|
|
569
|
+
if target_file_path.exists():
|
|
570
|
+
applied, msg = PatchApplier.apply_patch_to_file(
|
|
571
|
+
str(target_file_path),
|
|
572
|
+
patch_res.search_block,
|
|
573
|
+
patch_res.replace_block
|
|
574
|
+
)
|
|
575
|
+
patch_applied = applied
|
|
576
|
+
if applied:
|
|
577
|
+
test_passed = self._run_pytest(workspace, task.test_path)
|
|
578
|
+
else:
|
|
579
|
+
err_msg = msg
|
|
580
|
+
else:
|
|
581
|
+
err_msg = f"Target file does not exist: {patch_res.file_path}"
|
|
582
|
+
else:
|
|
583
|
+
err_msg = patch_res.error_message
|
|
584
|
+
|
|
585
|
+
return MethodEvaluationResult(
|
|
586
|
+
method=method,
|
|
587
|
+
task_id=task.task_id,
|
|
588
|
+
input_tokens=input_tokens,
|
|
589
|
+
output_tokens=output_tokens,
|
|
590
|
+
prep_time_ms=prep_ms,
|
|
591
|
+
infer_time_ms=infer_ms,
|
|
592
|
+
total_time_ms=total_ms,
|
|
593
|
+
peak_vram_mb=peak_vram_mb,
|
|
594
|
+
captured_target=captured_target,
|
|
595
|
+
valid_patch_format=valid_patch,
|
|
596
|
+
patch_applied=patch_applied,
|
|
597
|
+
test_passed=test_passed,
|
|
598
|
+
model_output=response_text,
|
|
599
|
+
error_message=err_msg
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def run_full_end_to_end_benchmark() -> List[MethodEvaluationResult]:
|
|
604
|
+
print("==========================================================================")
|
|
605
|
+
print(" NEUROCODE VERIFIED END-TO-END BUG RESOLUTION BENCHMARK ")
|
|
606
|
+
print("==========================================================================\n")
|
|
607
|
+
|
|
608
|
+
tasks = get_curated_benchmark_tasks()
|
|
609
|
+
print(f"Total Evaluated Tasks: {len(tasks)}")
|
|
610
|
+
|
|
611
|
+
runner = EndToEndBenchmarkRunner()
|
|
612
|
+
runner.load_model()
|
|
613
|
+
|
|
614
|
+
methods = [
|
|
615
|
+
"Raw Context Dump",
|
|
616
|
+
"BM25 Lexical RAG",
|
|
617
|
+
"Graph Unconstrained",
|
|
618
|
+
"NeuroCode (Our Method)"
|
|
619
|
+
]
|
|
620
|
+
|
|
621
|
+
all_results = []
|
|
622
|
+
|
|
623
|
+
for task in tasks:
|
|
624
|
+
print(f"\n--------------------------------------------------------------------------")
|
|
625
|
+
print(f"Executing Task: [{task.task_id}] {task.title} ({task.category})")
|
|
626
|
+
print(f"--------------------------------------------------------------------------")
|
|
627
|
+
for method in methods:
|
|
628
|
+
print(f" -> Running Condition: {method:25s} ... ", end="", flush=True)
|
|
629
|
+
res = runner.evaluate_task_method(task, method)
|
|
630
|
+
all_results.append(res)
|
|
631
|
+
status = "RESOLVED [PASS]" if res.test_passed else ("APPLIED [FAIL TEST]" if res.patch_applied else "NO PATCH")
|
|
632
|
+
print(f"{status:18s} | In Tokens: {res.input_tokens:4d} | Target In Context: {res.captured_target} | VRAM: {res.peak_vram_mb:.1f}MB")
|
|
633
|
+
|
|
634
|
+
return all_results
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if __name__ == "__main__":
|
|
638
|
+
results = run_full_end_to_end_benchmark()
|