CodeCommunity commited on
Commit
106dc42
·
verified ·
1 Parent(s): 1159189

Update app/services/reviewer_service.py

Browse files
Files changed (1) hide show
  1. app/services/reviewer_service.py +47 -33
app/services/reviewer_service.py CHANGED
@@ -5,6 +5,8 @@ import hashlib
5
  import sqlite3
6
  import threading
7
  from typing import List
 
 
8
  from app.core.model_loader import llm_engine
9
 
10
  logger = logging.getLogger(__name__)
@@ -12,7 +14,7 @@ DB_PATH = "review_cache.db"
12
 
13
  class AIReviewerService:
14
  def __init__(self):
15
- self.batch_size = 3 # Reduced from 10 to speed up individual prompts
16
  self.db_lock = threading.Lock()
17
  self._init_db()
18
 
@@ -81,29 +83,32 @@ class AIReviewerService:
81
  logger.info(f"⚡ Served all {len(files)} files instantly from SQLite cache.")
82
  return results
83
 
84
- # 2. Sequential batch processing (prevents LLM thread lock contention)
85
  batches = [
86
  uncached_files[i : i + self.batch_size]
87
  for i in range(0, len(uncached_files), self.batch_size)
88
  ]
89
-
90
- new_results = []
91
- for batch in batches:
92
- try:
93
- batch_results = self._process_batch(batch)
94
- new_results.extend(batch_results)
95
- except Exception as e:
96
- logger.error(f"Batch processing error: {e}")
97
- for f_item in batch:
98
- new_results.append({
99
- "fileName": f_item.fileName,
100
- "vulnerabilities": [],
101
- "improvement_suggestions": [],
102
- "metrics": {"complexity": 0, "maintainability": 0},
103
- "error": "analysis_failed",
104
- })
105
 
106
- new_results_map = {item.get("fileName"): item for item in new_results}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  to_cache = []
108
 
109
  # 3. Assemble final response and update cache
@@ -114,7 +119,7 @@ class AIReviewerService:
114
  "fileName": f.fileName,
115
  "vulnerabilities": [],
116
  "improvement_suggestions": [],
117
- "metrics": {"complexity": 0, "maintainability": 0},
118
  "error": "analysis_failed",
119
  },
120
  )
@@ -131,25 +136,27 @@ class AIReviewerService:
131
  file_names = []
132
  for f in batch:
133
  raw_content = f.content or ""
134
- minified_content = self._minify_code(raw_content[:2000], f.fileName) # Reduced preview limit to 2000 chars
135
  combined_code += f"\n--- FILE: {f.fileName} ---\n{minified_content}\n"
136
  file_names.append(f.fileName)
137
 
138
  prompt = f"""Analyze {len(batch)} files. Be ultra-concise.
 
 
139
  {combined_code}
 
140
  Return ONLY valid JSON array:
141
  [
142
  {{
143
- "fileName": "name",
144
  "vulnerabilities": [{{"type": "type", "line": 1, "description": "short", "suggestion": "fix"}}],
145
  "improvement_suggestions": ["tip"],
146
- "metrics": {{"complexity": 3, "maintainability": 8}}
147
  }}
148
  ]"""
149
  batch_results = []
150
  try:
151
- # Capped token length to 1024 max per batch turn
152
- response_text = llm_engine.generate(prompt, max_tokens=1024)
153
  parsed = self._parse_json(response_text)
154
  processed_map = {item.get("fileName"): item for item in parsed if isinstance(item, dict)}
155
 
@@ -158,14 +165,14 @@ Return ONLY valid JSON array:
158
  res = processed_map[fn]
159
  res.setdefault("vulnerabilities", [])
160
  res.setdefault("improvement_suggestions", [])
161
- res.setdefault("metrics", {"complexity": 5, "maintainability": 5})
162
  batch_results.append(res)
163
  else:
164
  batch_results.append({
165
  "fileName": fn,
166
  "vulnerabilities": [],
167
  "improvement_suggestions": [],
168
- "metrics": {"complexity": 1, "maintainability": 10},
169
  })
170
  except Exception as e:
171
  logger.error(f"Batch processing error: {e}")
@@ -174,7 +181,7 @@ Return ONLY valid JSON array:
174
  "fileName": fn,
175
  "vulnerabilities": [],
176
  "improvement_suggestions": [],
177
- "metrics": {"complexity": 0, "maintainability": 0},
178
  "error": str(e),
179
  })
180
  return batch_results
@@ -183,7 +190,7 @@ Return ONLY valid JSON array:
183
  lines = code.split("\n")
184
  cleaned_lines = []
185
  is_python = filename.endswith(".py")
186
- is_js_style = filename.endswith((".js", ".ts", ".jsx", ".tsx"))
187
 
188
  for line in lines:
189
  stripped = line.strip()
@@ -195,15 +202,22 @@ Return ONLY valid JSON array:
195
  continue
196
  cleaned_lines.append(stripped)
197
 
198
- return "\n".join(cleaned_lines[:50]) # Cap at 50 lines max per file
199
 
200
  def _parse_json(self, text: str):
201
  text = text.strip()
202
  if not text:
203
  return []
204
- text = re.sub(r"^[^\[]*\[", "[", text)
205
- text = re.sub(r"\][^\]]*$", "]", text)
206
- text = text.replace("```json", "").replace("```", "").strip()
 
 
 
 
 
 
 
207
  try:
208
  return json.loads(text)
209
  except json.JSONDecodeError:
 
5
  import sqlite3
6
  import threading
7
  from typing import List
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+
10
  from app.core.model_loader import llm_engine
11
 
12
  logger = logging.getLogger(__name__)
 
14
 
15
  class AIReviewerService:
16
  def __init__(self):
17
+ self.batch_size = 3
18
  self.db_lock = threading.Lock()
19
  self._init_db()
20
 
 
83
  logger.info(f"⚡ Served all {len(files)} files instantly from SQLite cache.")
84
  return results
85
 
86
+ # 2. Parallel Sub-Batch Processing
87
  batches = [
88
  uncached_files[i : i + self.batch_size]
89
  for i in range(0, len(uncached_files), self.batch_size)
90
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ new_results = []
93
+ with ThreadPoolExecutor(max_workers=5) as pool:
94
+ future_to_batch = {pool.submit(self._process_batch, batch): batch for batch in batches}
95
+ for future in as_completed(future_to_batch):
96
+ try:
97
+ batch_results = future.result()
98
+ new_results.extend(batch_results)
99
+ except Exception as e:
100
+ batch = future_to_batch[future]
101
+ logger.error(f"Batch processing error: {e}")
102
+ for f_item in batch:
103
+ new_results.append({
104
+ "fileName": f_item.fileName,
105
+ "vulnerabilities": [],
106
+ "improvement_suggestions": [],
107
+ "metrics": {"complexity": 0, "maintainability": 0.0},
108
+ "error": str(e),
109
+ })
110
+
111
+ new_results_map = {item.get("fileName"): item for item in new_results if isinstance(item, dict)}
112
  to_cache = []
113
 
114
  # 3. Assemble final response and update cache
 
119
  "fileName": f.fileName,
120
  "vulnerabilities": [],
121
  "improvement_suggestions": [],
122
+ "metrics": {"complexity": 0, "maintainability": 0.0},
123
  "error": "analysis_failed",
124
  },
125
  )
 
136
  file_names = []
137
  for f in batch:
138
  raw_content = f.content or ""
139
+ minified_content = self._minify_code(raw_content, f.fileName)
140
  combined_code += f"\n--- FILE: {f.fileName} ---\n{minified_content}\n"
141
  file_names.append(f.fileName)
142
 
143
  prompt = f"""Analyze {len(batch)} files. Be ultra-concise.
144
+ CRITICAL: The "fileName" field MUST EXACTLY match the file path given in header (e.g. "{file_names[0]}").
145
+
146
  {combined_code}
147
+
148
  Return ONLY valid JSON array:
149
  [
150
  {{
151
+ "fileName": "{file_names[0]}",
152
  "vulnerabilities": [{{"type": "type", "line": 1, "description": "short", "suggestion": "fix"}}],
153
  "improvement_suggestions": ["tip"],
154
+ "metrics": {{"complexity": 3, "maintainability": 8.0}}
155
  }}
156
  ]"""
157
  batch_results = []
158
  try:
159
+ response_text = llm_engine.generate(prompt, max_tokens=2048)
 
160
  parsed = self._parse_json(response_text)
161
  processed_map = {item.get("fileName"): item for item in parsed if isinstance(item, dict)}
162
 
 
165
  res = processed_map[fn]
166
  res.setdefault("vulnerabilities", [])
167
  res.setdefault("improvement_suggestions", [])
168
+ res.setdefault("metrics", {"complexity": 5, "maintainability": 5.0})
169
  batch_results.append(res)
170
  else:
171
  batch_results.append({
172
  "fileName": fn,
173
  "vulnerabilities": [],
174
  "improvement_suggestions": [],
175
+ "metrics": {"complexity": 1, "maintainability": 10.0},
176
  })
177
  except Exception as e:
178
  logger.error(f"Batch processing error: {e}")
 
181
  "fileName": fn,
182
  "vulnerabilities": [],
183
  "improvement_suggestions": [],
184
+ "metrics": {"complexity": 0, "maintainability": 0.0},
185
  "error": str(e),
186
  })
187
  return batch_results
 
190
  lines = code.split("\n")
191
  cleaned_lines = []
192
  is_python = filename.endswith(".py")
193
+ is_js_style = filename.endswith((".js", ".ts", ".jsx", ".tsx", ".kt", ".java"))
194
 
195
  for line in lines:
196
  stripped = line.strip()
 
202
  continue
203
  cleaned_lines.append(stripped)
204
 
205
+ return "\n".join(cleaned_lines)
206
 
207
  def _parse_json(self, text: str):
208
  text = text.strip()
209
  if not text:
210
  return []
211
+
212
+ # Strip markdown code blocks safely without corrupting string content
213
+ text = re.sub(r"```(?:json)?", "", text, flags=re.IGNORECASE).strip()
214
+
215
+ start_idx = text.find('[')
216
+ end_idx = text.rfind(']')
217
+
218
+ if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
219
+ text = text[start_idx : end_idx + 1]
220
+
221
  try:
222
  return json.loads(text)
223
  except json.JSONDecodeError: