Fayza38 commited on
Commit
37a62fe
·
verified ·
1 Parent(s): a8cac7f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +42 -70
main.py CHANGED
@@ -14,7 +14,7 @@ from pydantic import BaseModel
14
  from gradio_client import Client
15
  from google.cloud.firestore_v1.base_query import FieldFilter
16
  import edge_tts
17
- from typing import Optional
18
  from dotenv import load_dotenv
19
  from contextlib import asynccontextmanager
20
 
@@ -67,44 +67,21 @@ class GenerateSessionRequest(BaseModel):
67
  trackName: Optional[int] = None
68
 
69
  class CleanupRequest(BaseModel):
70
- audioUrls: list[str]
71
 
72
  # =========================================
73
- # 4. BACKGROUND TASKS (Auto-Cleaner)
74
- # =========================================
75
- async def auto_clean_invalid_questions():
76
- """Background loop to remove questions with missing or broken audio."""
77
- while True:
78
- try:
79
- print("[Auto-Cleaner] Scanning for broken questions...")
80
- docs = db.collection("questions_pool").get()
81
- deleted_count = 0
82
- for doc in docs:
83
- data = doc.to_dict()
84
- if not data.get("audio_url"):
85
- db.collection("questions_pool").document(doc.id).delete()
86
- deleted_count += 1
87
- if deleted_count > 0:
88
- print(f"[Auto-Cleaner] Removed {deleted_count} broken questions.")
89
- except Exception as e:
90
- print(f"[Auto-Cleaner] Error: {e}")
91
- await asyncio.sleep(600) # Scan every 10 minutes
92
-
93
- # =========================================
94
- # 5. LIFESPAN MANAGEMENT
95
  # =========================================
96
  @asynccontextmanager
97
  async def lifespan(app: FastAPI):
98
  global client
99
- print("Connecting to Hugging Face Space...")
100
  try:
101
  loop = asyncio.get_event_loop()
102
  client = await loop.run_in_executor(None, lambda: Client(HF_SPACE))
103
- print("Connected Successfully!")
104
- # Start the background cleaner
105
- asyncio.create_task(auto_clean_invalid_questions())
106
  except Exception as e:
107
- print(f"Startup Connection failed: {e}")
108
 
109
  yield
110
  print("Shutting down Intervision Service...")
@@ -112,7 +89,7 @@ async def lifespan(app: FastAPI):
112
  app = FastAPI(title="Intervision AI Question Service", lifespan=lifespan)
113
 
114
  # =========================================
115
- # 6. HELPERS
116
  # =========================================
117
  async def generate_audio(text, filename):
118
  try:
@@ -124,12 +101,12 @@ async def generate_audio(text, filename):
124
  if os.path.exists(filename): os.remove(filename)
125
  return upload_result["secure_url"]
126
  except Exception as e:
127
- print(f"Audio Error: {e}")
128
  if os.path.exists(filename): os.remove(filename)
129
  return None
130
 
131
  async def safe_generate(prompt, retries=3):
132
- if client is None: raise Exception("Gradio Client not initialized")
133
  for attempt in range(retries):
134
  try:
135
  loop = asyncio.get_running_loop()
@@ -150,46 +127,43 @@ def parse_question_output(raw_output: str):
150
  except: return None, None
151
  return None, None
152
 
153
- async def refill_specific_pool(track_id: int, difficulty: int, count: int, session_type: int = 1):
154
  while client is None: await asyncio.sleep(5)
155
 
156
  if session_type == 0:
157
- prompt = ("Generate ONE simple Behavioral interview question for a fresh graduate. "
158
- "Focus on soft skills like teamwork or leadership. Strictly NO technical questions. "
159
- "Format: Q: [Question] A: [Answer]")
160
  track_text = "Behavioral"
161
  else:
162
  track_text = TECH_CATEGORIES.get(track_id)
163
  level_text = DIFFICULTY_MAP.get(difficulty)
164
- prompt = f"Generate ONE unique {track_text} interview question for {level_text} level. Format: Q: [Question] A: [Answer]"
165
 
166
  success_count = 0
167
  while success_count < count:
168
  try:
169
  raw_output = await safe_generate(prompt)
170
- q_text, a_text = parse_question_output(raw_output)
171
- if q_text and a_text:
172
- filename = f"{uuid.uuid4()}.mp3"
173
- audio_url = await generate_audio(q_text, filename)
174
  if audio_url:
175
  db.collection("questions_pool").add({
176
  "session_type": session_type,
177
  "track_id": track_id if session_type == 1 else -1,
178
  "difficulty": difficulty if session_type == 1 else 0,
179
- "questionText": q_text,
180
- "questionIdealAnswer": a_text,
181
  "audio_url": audio_url,
182
  "created_at": firestore.SERVER_TIMESTAMP
183
  })
184
  success_count += 1
185
- print(f"Refilled {success_count}/{count} for {track_text}")
186
  await asyncio.sleep(3)
187
  except Exception as e:
188
- print(f"Refill error: {e}")
189
  await asyncio.sleep(5)
190
 
191
  # =========================================
192
- # 7. MAIN ENDPOINTS
193
  # =========================================
194
  @app.post("/generate-session")
195
  async def generate_session(request: GenerateSessionRequest, background_tasks: BackgroundTasks):
@@ -197,12 +171,13 @@ async def generate_session(request: GenerateSessionRequest, background_tasks: Ba
197
  query = db.collection("questions_pool").where(filter=FieldFilter("session_type", "==", s_type))
198
 
199
  if s_type == 1: # Technical
200
- if t_id is None: raise HTTPException(status_code=400, detail="trackName required for technical.")
201
  query = query.where(filter=FieldFilter("track_id", "==", t_id)).where(filter=FieldFilter("difficulty", "==", diff))
202
 
203
- docs_query = query.limit(10).get()
204
  final_questions = []
205
- for index, doc in enumerate(docs_query, start=1):
 
206
  data = doc.to_dict()
207
  final_questions.append({
208
  "question_id": index, "text": data["questionText"],
@@ -210,15 +185,18 @@ async def generate_session(request: GenerateSessionRequest, background_tasks: Ba
210
  })
211
  db.collection("questions_pool").document(doc.id).delete()
212
 
213
- async def maintain_stock():
214
  snap = query.count().get()
215
- current = snap[0][0].value
216
- if current < 50:
217
- await refill_specific_pool(t_id if s_type == 1 else -1, diff, 50 - current, session_type=s_type)
 
218
 
219
- background_tasks.add_task(maintain_stock)
 
220
  if not final_questions:
221
- raise HTTPException(status_code=503, detail="Question pool is currently empty.")
 
222
  return {"session_id": request.sessionId, "questions": final_questions}
223
 
224
  @app.post("/cleanup-audio")
@@ -230,37 +208,31 @@ async def cleanup_audio(request: CleanupRequest, background_tasks: BackgroundTas
230
  cloudinary.uploader.destroy(public_id, resource_type="video")
231
  except: pass
232
  background_tasks.add_task(delete_job, request.audioUrls)
233
- return {"message": "Cleanup started"}
234
 
235
  @app.get("/system-cleanup")
236
  async def system_cleanup(background_tasks: BackgroundTasks):
237
- """Scan and delete all questions with missing or invalid audio URLs"""
238
  def run_cleanup():
239
- print("Starting System Cleanup...")
240
- # Get all documents in the pool
241
  docs = db.collection("questions_pool").get()
242
- deleted_count = 0
243
-
244
  for doc in docs:
245
  data = doc.to_dict()
246
- # Check if audio_url is missing, None, or empty string
247
- if not data.get("audio_url") or data.get("audio_url") == "":
248
  db.collection("questions_pool").document(doc.id).delete()
249
- deleted_count += 1
250
-
251
- print(f"Cleanup finished! Deleted {deleted_count} broken questions.")
252
 
253
  background_tasks.add_task(run_cleanup)
254
- return {"message": "Cleanup started in background. Check your console/logs."}
255
-
256
 
257
  @app.get("/health")
258
  async def health():
259
- return {"status": "active", "hf_connected": client is not None}
260
 
261
  @app.get("/")
262
  async def root():
263
- return {"app": "Intervision AI Service","Status": "Running.."}
264
 
265
  if __name__ == "__main__":
266
  import uvicorn
 
14
  from gradio_client import Client
15
  from google.cloud.firestore_v1.base_query import FieldFilter
16
  import edge_tts
17
+ from typing import Optional, List
18
  from dotenv import load_dotenv
19
  from contextlib import asynccontextmanager
20
 
 
67
  trackName: Optional[int] = None
68
 
69
  class CleanupRequest(BaseModel):
70
+ audioUrls: List[str]
71
 
72
  # =========================================
73
+ # 4. LIFESPAN MANAGEMENT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  # =========================================
75
  @asynccontextmanager
76
  async def lifespan(app: FastAPI):
77
  global client
78
+ print("Connecting to Hugging Face Model...")
79
  try:
80
  loop = asyncio.get_event_loop()
81
  client = await loop.run_in_executor(None, lambda: Client(HF_SPACE))
82
+ print("Model Connected Successfully!")
 
 
83
  except Exception as e:
84
+ print(f"Model Connection Failed: {e}")
85
 
86
  yield
87
  print("Shutting down Intervision Service...")
 
89
  app = FastAPI(title="Intervision AI Question Service", lifespan=lifespan)
90
 
91
  # =========================================
92
+ # 5. CORE LOGIC HELPERS
93
  # =========================================
94
  async def generate_audio(text, filename):
95
  try:
 
101
  if os.path.exists(filename): os.remove(filename)
102
  return upload_result["secure_url"]
103
  except Exception as e:
104
+ print(f"Audio Generation Error: {e}")
105
  if os.path.exists(filename): os.remove(filename)
106
  return None
107
 
108
  async def safe_generate(prompt, retries=3):
109
+ if client is None: raise Exception("AI Client is not initialized.")
110
  for attempt in range(retries):
111
  try:
112
  loop = asyncio.get_running_loop()
 
127
  except: return None, None
128
  return None, None
129
 
130
+ async def refill_specific_pool(track_id: int, difficulty: int, count: int, session_type: int):
131
  while client is None: await asyncio.sleep(5)
132
 
133
  if session_type == 0:
134
+ prompt = "Generate ONE unique simple behavioral interview question. Format: Q: [Question] A: [Answer]"
 
 
135
  track_text = "Behavioral"
136
  else:
137
  track_text = TECH_CATEGORIES.get(track_id)
138
  level_text = DIFFICULTY_MAP.get(difficulty)
139
+ prompt = f"Generate ONE unique {track_text} question for {level_text} level. Format: Q: [Question] A: [Answer]"
140
 
141
  success_count = 0
142
  while success_count < count:
143
  try:
144
  raw_output = await safe_generate(prompt)
145
+ q, a = parse_question_output(raw_output)
146
+ if q and a:
147
+ audio_url = await generate_audio(q, f"{uuid.uuid4()}.mp3")
 
148
  if audio_url:
149
  db.collection("questions_pool").add({
150
  "session_type": session_type,
151
  "track_id": track_id if session_type == 1 else -1,
152
  "difficulty": difficulty if session_type == 1 else 0,
153
+ "questionText": q,
154
+ "questionIdealAnswer": a,
155
  "audio_url": audio_url,
156
  "created_at": firestore.SERVER_TIMESTAMP
157
  })
158
  success_count += 1
159
+ print(f"Successfully added question {success_count}/{count}")
160
  await asyncio.sleep(3)
161
  except Exception as e:
162
+ print(f"Refill logic error: {e}")
163
  await asyncio.sleep(5)
164
 
165
  # =========================================
166
+ # 6. API ENDPOINTS
167
  # =========================================
168
  @app.post("/generate-session")
169
  async def generate_session(request: GenerateSessionRequest, background_tasks: BackgroundTasks):
 
171
  query = db.collection("questions_pool").where(filter=FieldFilter("session_type", "==", s_type))
172
 
173
  if s_type == 1: # Technical
174
+ if t_id is None: raise HTTPException(status_code=400, detail="trackName is required for technical sessions.")
175
  query = query.where(filter=FieldFilter("track_id", "==", t_id)).where(filter=FieldFilter("difficulty", "==", diff))
176
 
177
+ docs = query.limit(10).get()
178
  final_questions = []
179
+
180
+ for index, doc in enumerate(docs, start=1):
181
  data = doc.to_dict()
182
  final_questions.append({
183
  "question_id": index, "text": data["questionText"],
 
185
  })
186
  db.collection("questions_pool").document(doc.id).delete()
187
 
188
+ async def check_and_refill_background():
189
  snap = query.count().get()
190
+ current_count = snap[0][0].value
191
+ if current_count < 50:
192
+ print(f"Stock is low ({current_count}). Starting background refill...")
193
+ await refill_specific_pool(t_id if s_type == 1 else -1, diff, 50 - current_count, s_type)
194
 
195
+ background_tasks.add_task(check_and_refill_background)
196
+
197
  if not final_questions:
198
+ raise HTTPException(status_code=503, detail="The question pool is empty. Please try again in a few minutes.")
199
+
200
  return {"session_id": request.sessionId, "questions": final_questions}
201
 
202
  @app.post("/cleanup-audio")
 
208
  cloudinary.uploader.destroy(public_id, resource_type="video")
209
  except: pass
210
  background_tasks.add_task(delete_job, request.audioUrls)
211
+ return {"message": "Cloudinary cleanup process initiated."}
212
 
213
  @app.get("/system-cleanup")
214
  async def system_cleanup(background_tasks: BackgroundTasks):
215
+ """Manually trigger a cleanup for broken records in Firestore"""
216
  def run_cleanup():
 
 
217
  docs = db.collection("questions_pool").get()
218
+ count = 0
 
219
  for doc in docs:
220
  data = doc.to_dict()
221
+ if not data.get("audio_url"):
 
222
  db.collection("questions_pool").document(doc.id).delete()
223
+ count += 1
224
+ print(f"Manual cleanup finished. Removed {count} broken records.")
 
225
 
226
  background_tasks.add_task(run_cleanup)
227
+ return {"message": "System cleanup task started in background."}
 
228
 
229
  @app.get("/health")
230
  async def health():
231
+ return {"status": "active", "ai_model_connected": client is not None}
232
 
233
  @app.get("/")
234
  async def root():
235
+ return {"app": "Intervision AI Engine", "status": "Running"}
236
 
237
  if __name__ == "__main__":
238
  import uvicorn