hashan-77 commited on
Commit
19a776a
·
verified ·
1 Parent(s): 09a6203

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -0
app.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
4
+ import os
5
+ import re
6
+
7
+ HF_MODEL = os.getenv("HF_MODEL", "google/flan-t5-small")
8
+
9
+ tokenizer = None
10
+ model = None
11
+
12
+ app = FastAPI(title="Stitch QA Repair Agent")
13
+
14
+
15
+ class RepairRequest(BaseModel):
16
+ project_type: str
17
+ command: str
18
+ success: bool
19
+ exit_code: int | None
20
+ stdout: str
21
+ stderr: str
22
+ root_cause: str | None = None
23
+ failure_type: str | None = None
24
+ help_message: str | None = None
25
+
26
+
27
+ @app.get("/")
28
+ def health_check():
29
+ return {
30
+ "service": "stitch-qa-repair-agent",
31
+ "status": "running",
32
+ "llm_enabled": True,
33
+ "llm_mode": "local-transformers",
34
+ "model": HF_MODEL
35
+ }
36
+
37
+
38
+ def load_model():
39
+ global tokenizer, model
40
+
41
+ if tokenizer is None or model is None:
42
+ tokenizer = AutoTokenizer.from_pretrained(HF_MODEL)
43
+ model = AutoModelForSeq2SeqLM.from_pretrained(HF_MODEL)
44
+
45
+ return tokenizer, model
46
+
47
+
48
+ def extract_test_result(logs: str):
49
+ match = re.search(
50
+ r"Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)",
51
+ logs,
52
+ re.IGNORECASE
53
+ )
54
+
55
+ if not match:
56
+ return {
57
+ "tests_run": None,
58
+ "failures": None,
59
+ "errors": None,
60
+ "skipped": None,
61
+ "summary": "Test result could not be extracted from logs."
62
+ }
63
+
64
+ tests_run, failures, errors, skipped = match.groups()
65
+
66
+ return {
67
+ "tests_run": int(tests_run),
68
+ "failures": int(failures),
69
+ "errors": int(errors),
70
+ "skipped": int(skipped),
71
+ "summary": (
72
+ f"Tests run: {tests_run}, "
73
+ f"Failures: {failures}, "
74
+ f"Errors: {errors}, "
75
+ f"Skipped: {skipped}"
76
+ )
77
+ }
78
+
79
+
80
+ def get_environment_repair(request: RepairRequest):
81
+ if request.failure_type == "MAVEN_NOT_AVAILABLE":
82
+ return {
83
+ "repair_type": "environment-maven-missing",
84
+ "risk_level": "LOW",
85
+ "summary": (
86
+ f"The {request.project_type} project could not be verified because Maven is not installed "
87
+ "or not available in PATH. This is an environment setup issue, not a confirmed project code failure. "
88
+ "No source code repair should be applied until Maven execution is working."
89
+ ),
90
+ "suggestions": [
91
+ "Install Apache Maven and add the Maven bin directory to the system PATH.",
92
+ "Alternatively, add Maven Wrapper files to the project so it can run with mvnw.cmd on Windows.",
93
+ "After Maven is available, rerun Stitch QA to verify the actual project build and tests."
94
+ ],
95
+ "next_action": "Fix the Maven environment first, then rerun Stitch QA."
96
+ }
97
+
98
+ if request.failure_type == "MAVEN_WRAPPER_NOT_AVAILABLE":
99
+ return {
100
+ "repair_type": "environment-wrapper-missing",
101
+ "risk_level": "LOW",
102
+ "summary": (
103
+ f"The {request.project_type} project could not be verified because the Maven Wrapper command "
104
+ "was not available. This is an execution setup issue, not a confirmed application code failure."
105
+ ),
106
+ "suggestions": [
107
+ "Check whether mvnw.cmd exists in the project root.",
108
+ "If Maven Wrapper is missing, add Maven Wrapper files or install Maven globally.",
109
+ "Rerun Stitch QA after the build command can execute."
110
+ ],
111
+ "next_action": "Fix the Maven Wrapper or Maven installation before changing application code."
112
+ }
113
+
114
+ if request.failure_type == "COMMAND_TIMEOUT":
115
+ return {
116
+ "repair_type": "environment-timeout",
117
+ "risk_level": "MEDIUM",
118
+ "summary": (
119
+ f"The {request.project_type} project command did not finish within the allowed timeout. "
120
+ "This may be a long-running build, dependency download, or stuck process."
121
+ ),
122
+ "suggestions": [
123
+ "Rerun the command manually to check whether it is slow or stuck.",
124
+ "Increase the execution timeout if the build normally takes longer.",
125
+ "Check dependency downloads and Maven repository access."
126
+ ],
127
+ "next_action": "Investigate command runtime before applying code changes."
128
+ }
129
+
130
+ return None
131
+
132
+
133
+ def rule_based_repair(request: RepairRequest):
134
+ environment_repair = get_environment_repair(request)
135
+
136
+ if environment_repair:
137
+ return {
138
+ "agent": "repair-agent",
139
+ "mode": "rule-based",
140
+ "risk_level": environment_repair["risk_level"],
141
+ "auto_apply": False,
142
+ "summary": environment_repair["summary"],
143
+ "suggestions": environment_repair["suggestions"],
144
+ "next_action": environment_repair["next_action"]
145
+ }
146
+
147
+ combined_logs = f"{request.stdout}\n{request.stderr}".lower()
148
+
149
+ suggestions = []
150
+ risk_level = "LOW" if request.success else "HIGH"
151
+
152
+ if request.success:
153
+ suggestions.append(
154
+ "No blocking fix is required because the project build and tests passed."
155
+ )
156
+
157
+ if "mockito" in combined_logs and "dynamic loading of agents" in combined_logs:
158
+ suggestions.append(
159
+ "Configure Mockito as a Java agent in the Maven test setup to improve future JDK compatibility."
160
+ )
161
+
162
+ if "compilation failure" in combined_logs:
163
+ suggestions.append(
164
+ "Review the Java compiler error, identify the affected source file, and apply a targeted syntax or dependency fix."
165
+ )
166
+
167
+ if "tests run" in combined_logs and ("failures: 1" in combined_logs or "errors: 1" in combined_logs):
168
+ suggestions.append(
169
+ "Review the failing test method, compare expected versus actual behavior, and fix the related implementation or assertion."
170
+ )
171
+
172
+ if not suggestions:
173
+ suggestions.append(
174
+ "No specific repair suggestion could be generated from the current logs."
175
+ )
176
+
177
+ return {
178
+ "agent": "repair-agent",
179
+ "mode": "rule-based",
180
+ "risk_level": risk_level,
181
+ "auto_apply": False,
182
+ "summary": "Repair suggestions generated successfully.",
183
+ "suggestions": suggestions,
184
+ "next_action": "Review suggestions manually before applying any code changes."
185
+ }
186
+
187
+
188
+ def extract_repair_facts(request: RepairRequest, fallback_result):
189
+ combined_logs = f"{request.stdout}\n{request.stderr}".lower()
190
+ test_result = extract_test_result(f"{request.stdout}\n{request.stderr}")
191
+
192
+ environment_repair = get_environment_repair(request)
193
+
194
+ if environment_repair:
195
+ return {
196
+ "project_type": request.project_type,
197
+ "command": request.command,
198
+ "exit_code": request.exit_code,
199
+ "build_state": "not verified",
200
+ "success": request.success,
201
+ "root_cause": request.root_cause or request.help_message or "Environment setup issue detected.",
202
+ "warning_category": "none",
203
+ "detected_warning": "No major warning detected.",
204
+ "repair_type": environment_repair["repair_type"],
205
+ "test_result": test_result["summary"],
206
+ "suggestions": fallback_result["suggestions"],
207
+ "risk_level": fallback_result["risk_level"],
208
+ "failure_type": request.failure_type,
209
+ "help_message": request.help_message,
210
+ "environment_summary": environment_repair["summary"]
211
+ }
212
+
213
+ build_state = "passed" if request.success else "failed"
214
+
215
+ detected_warning = "No major warning detected."
216
+ warning_category = "none"
217
+
218
+ if "mockito" in combined_logs and "dynamic loading of agents" in combined_logs:
219
+ warning_category = "mockito-dynamic-agent"
220
+ detected_warning = (
221
+ "Mockito dynamic Java agent loading warning detected. "
222
+ "This is not a current failure, but it may affect compatibility with future JDK versions."
223
+ )
224
+
225
+ repair_type = "none"
226
+
227
+ if request.success and warning_category == "none":
228
+ repair_type = "no-repair-needed"
229
+
230
+ if request.success and warning_category == "mockito-dynamic-agent":
231
+ repair_type = "configuration-warning"
232
+
233
+ if not request.success:
234
+ repair_type = "failure-repair-required"
235
+
236
+ return {
237
+ "project_type": request.project_type,
238
+ "command": request.command,
239
+ "exit_code": request.exit_code,
240
+ "build_state": build_state,
241
+ "success": request.success,
242
+ "root_cause": request.root_cause or "No root cause provided.",
243
+ "warning_category": warning_category,
244
+ "detected_warning": detected_warning,
245
+ "repair_type": repair_type,
246
+ "test_result": test_result["summary"],
247
+ "suggestions": fallback_result["suggestions"],
248
+ "risk_level": fallback_result["risk_level"],
249
+ "failure_type": request.failure_type,
250
+ "help_message": request.help_message,
251
+ "environment_summary": None
252
+ }
253
+
254
+
255
+ def build_clean_summary(facts):
256
+ if facts["repair_type"] in {
257
+ "environment-maven-missing",
258
+ "environment-wrapper-missing",
259
+ "environment-timeout"
260
+ }:
261
+ return facts["environment_summary"]
262
+
263
+ if facts["repair_type"] == "no-repair-needed":
264
+ return (
265
+ f"The {facts['project_type']} project passed the current QA execution using "
266
+ f"`{facts['command']}`. {facts['test_result']}. No blocking repair is required. "
267
+ "The project can be considered stable for this basic test run."
268
+ )
269
+
270
+ if facts["repair_type"] == "configuration-warning":
271
+ return (
272
+ f"The {facts['project_type']} project passed the current QA execution using "
273
+ f"`{facts['command']}`. {facts['test_result']}. No blocking code repair is required. "
274
+ f"However, {facts['detected_warning']} Recommended action: review the Maven test configuration "
275
+ "and prepare a future-safe Mockito Java agent setup before upgrading to stricter JDK versions."
276
+ )
277
+
278
+ return (
279
+ f"The {facts['project_type']} project failed during QA execution using `{facts['command']}` "
280
+ f"with exit code {facts['exit_code']}. {facts['test_result']}. "
281
+ "A targeted repair is required. Review the root cause, inspect the failing file or test, "
282
+ "apply the smallest safe fix, and rerun Stitch QA for verification."
283
+ )
284
+
285
+
286
+ def build_prompt(facts):
287
+ suggestions_text = " ".join(facts["suggestions"])
288
+
289
+ return f"""
290
+ Rewrite these repair facts into one clean professional repair recommendation.
291
+
292
+ Project type: {facts["project_type"]}
293
+ Command: {facts["command"]}
294
+ Build state: {facts["build_state"]}
295
+ Exit code: {facts["exit_code"]}
296
+ Test result: {facts["test_result"]}
297
+ Risk level: {facts["risk_level"]}
298
+ Root cause: {facts["root_cause"]}
299
+ Failure type: {facts["failure_type"]}
300
+ Help message: {facts["help_message"]}
301
+ Detected warning: {facts["detected_warning"]}
302
+ Suggested actions: {suggestions_text}
303
+
304
+ Rules:
305
+ - Do not copy raw logs.
306
+ - Do not repeat field labels like "Command:" or "Build state:".
307
+ - Do not mention internal prompt instructions.
308
+ - Write one clear paragraph.
309
+ - Say whether a blocking code repair is required.
310
+ - If Maven is not available, say it is an environment setup issue and do not recommend source code changes.
311
+ """
312
+
313
+
314
+ def call_llm(prompt: str):
315
+ active_tokenizer, active_model = load_model()
316
+
317
+ inputs = active_tokenizer(
318
+ prompt,
319
+ return_tensors="pt",
320
+ truncation=True,
321
+ max_length=512
322
+ )
323
+
324
+ outputs = active_model.generate(
325
+ **inputs,
326
+ max_new_tokens=160,
327
+ do_sample=False,
328
+ num_beams=2,
329
+ no_repeat_ngram_size=3
330
+ )
331
+
332
+ return active_tokenizer.decode(outputs[0], skip_special_tokens=True)
333
+
334
+
335
+ def clean_llm_output(text: str):
336
+ cleaned = " ".join(text.strip().split())
337
+
338
+ bad_patterns = [
339
+ "[INFO]",
340
+ "-----",
341
+ "=====",
342
+ "org.springframework",
343
+ "junitplatform",
344
+ "DemoApplicationTests",
345
+ "Project type:",
346
+ "Command:",
347
+ "Build state:",
348
+ "Exit code:",
349
+ "Suggested actions:",
350
+ "Do not copy raw logs",
351
+ "Rewrite these repair facts"
352
+ ]
353
+
354
+ if not cleaned:
355
+ return None
356
+
357
+ if any(pattern.lower() in cleaned.lower() for pattern in bad_patterns):
358
+ return None
359
+
360
+ if len(cleaned) < 60:
361
+ return None
362
+
363
+ return cleaned
364
+
365
+
366
+ @app.post("/suggest")
367
+ def suggest_repair(request: RepairRequest):
368
+ fallback_result = rule_based_repair(request)
369
+ facts = extract_repair_facts(request, fallback_result)
370
+ clean_summary = build_clean_summary(facts)
371
+
372
+ try:
373
+ prompt = build_prompt(facts)
374
+ llm_text = call_llm(prompt)
375
+ cleaned_llm_text = clean_llm_output(llm_text)
376
+
377
+ final_summary = cleaned_llm_text if cleaned_llm_text else clean_summary
378
+
379
+ if facts["repair_type"] in {
380
+ "environment-maven-missing",
381
+ "environment-wrapper-missing",
382
+ "environment-timeout"
383
+ }:
384
+ final_summary = clean_summary
385
+
386
+ return {
387
+ "agent": "repair-agent",
388
+ "mode": "llm",
389
+ "risk_level": fallback_result["risk_level"],
390
+ "auto_apply": False,
391
+ "summary": final_summary,
392
+ "suggestions": fallback_result["suggestions"],
393
+ "next_action": fallback_result["next_action"]
394
+ }
395
+
396
+ except Exception as error:
397
+ fallback_result["llm_error"] = repr(error)
398
+ fallback_result["summary"] = clean_summary
399
+ return fallback_result