CodeCommunity commited on
Commit
87e5f9f
·
verified ·
1 Parent(s): 9085dfc

Update app/services/reviewer_service.py

Browse files
Files changed (1) hide show
  1. app/services/reviewer_service.py +22 -10
app/services/reviewer_service.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import json
2
  import logging
3
  import re
@@ -19,7 +20,6 @@ class AIReviewerService:
19
  self._init_db()
20
 
21
  def _init_db(self):
22
- """Initializes SQLite cache table with proper timeout."""
23
  with self.db_lock:
24
  with sqlite3.connect(DB_PATH, timeout=30.0) as conn:
25
  cursor = conn.cursor()
@@ -69,7 +69,6 @@ class AIReviewerService:
69
  uncached_files = []
70
  uncached_indices = []
71
 
72
- # 1. Check SQLite cache
73
  for idx, f in enumerate(files):
74
  file_hash = self._generate_file_hash(f)
75
  cached_res = self._get_cached_result(file_hash)
@@ -83,14 +82,14 @@ class AIReviewerService:
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:
@@ -105,13 +104,12 @@ class AIReviewerService:
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
115
  for idx, f in zip(uncached_indices, uncached_files):
116
  res = new_results_map.get(
117
  f.fileName,
@@ -131,6 +129,22 @@ class AIReviewerService:
131
  self._save_cached_results(to_cache)
132
  return results
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def _process_batch(self, batch: list) -> list:
135
  combined_code = ""
136
  file_names = []
@@ -156,7 +170,7 @@ Return ONLY valid JSON array:
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
 
@@ -182,7 +196,7 @@ Return ONLY valid JSON array:
182
  "vulnerabilities": [],
183
  "improvement_suggestions": [],
184
  "metrics": {"complexity": 0, "maintainability": 0.0},
185
- "error": str(e),
186
  })
187
  return batch_results
188
 
@@ -209,9 +223,7 @@ Return ONLY valid JSON array:
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
 
 
1
+ import time
2
  import json
3
  import logging
4
  import re
 
20
  self._init_db()
21
 
22
  def _init_db(self):
 
23
  with self.db_lock:
24
  with sqlite3.connect(DB_PATH, timeout=30.0) as conn:
25
  cursor = conn.cursor()
 
69
  uncached_files = []
70
  uncached_indices = []
71
 
 
72
  for idx, f in enumerate(files):
73
  file_hash = self._generate_file_hash(f)
74
  cached_res = self._get_cached_result(file_hash)
 
82
  logger.info(f"⚡ Served all {len(files)} files instantly from SQLite cache.")
83
  return results
84
 
 
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
+ # Reduced max_workers to 2 to prevent hitting API Rate Limits
92
+ with ThreadPoolExecutor(max_workers=2) as pool:
93
  future_to_batch = {pool.submit(self._process_batch, batch): batch for batch in batches}
94
  for future in as_completed(future_to_batch):
95
  try:
 
104
  "vulnerabilities": [],
105
  "improvement_suggestions": [],
106
  "metrics": {"complexity": 0, "maintainability": 0.0},
107
+ "error": "RATE_LIMIT_EXCEEDED" if "429" in str(e) else str(e),
108
  })
109
 
110
  new_results_map = {item.get("fileName"): item for item in new_results if isinstance(item, dict)}
111
  to_cache = []
112
 
 
113
  for idx, f in zip(uncached_indices, uncached_files):
114
  res = new_results_map.get(
115
  f.fileName,
 
129
  self._save_cached_results(to_cache)
130
  return results
131
 
132
+ def _call_llm_with_retry(self, prompt: str, max_tokens: int = 2048, retries: int = 3) -> str:
133
+ """Helper to retry LLM generation automatically when 429 rate limits occur."""
134
+ delay = 3.0
135
+ for attempt in range(retries):
136
+ try:
137
+ return llm_engine.generate(prompt, max_tokens=max_tokens)
138
+ except Exception as e:
139
+ err_str = str(e).upper()
140
+ if "429" in err_str or "RATE" in err_str or "LIMIT" in err_str:
141
+ if attempt < retries - 1:
142
+ logger.warning(f"⚠️ Rate limited (429). Retrying in {delay}s... (Attempt {attempt + 1}/{retries})")
143
+ time.sleep(delay)
144
+ delay *= 2 # Exponential backoff
145
+ continue
146
+ raise e
147
+
148
  def _process_batch(self, batch: list) -> list:
149
  combined_code = ""
150
  file_names = []
 
170
  ]"""
171
  batch_results = []
172
  try:
173
+ response_text = self._call_llm_with_retry(prompt, max_tokens=2048)
174
  parsed = self._parse_json(response_text)
175
  processed_map = {item.get("fileName"): item for item in parsed if isinstance(item, dict)}
176
 
 
196
  "vulnerabilities": [],
197
  "improvement_suggestions": [],
198
  "metrics": {"complexity": 0, "maintainability": 0.0},
199
+ "error": "RATE_LIMIT_EXCEEDED" if "429" in str(e) else str(e),
200
  })
201
  return batch_results
202
 
 
223
  if not text:
224
  return []
225
 
 
226
  text = re.sub(r"```(?:json)?", "", text, flags=re.IGNORECASE).strip()
 
227
  start_idx = text.find('[')
228
  end_idx = text.rfind(']')
229