precison9 commited on
Commit
6e2b097
·
verified ·
1 Parent(s): 33599e3

Update flask_Character.py

Browse files
Files changed (1) hide show
  1. flask_Character.py +29 -69
flask_Character.py CHANGED
@@ -35,12 +35,14 @@ from bson import ObjectId
35
  MONGO_URI = "mongodb+srv://precison9:P1LhtFknkT75yg5L@cluster0.isuwpef.mongodb.net"
36
  DB_NAME = "email_assistant_db"
37
  EXTRACTED_EMAILS_COLLECTION = "extracted_emails"
38
- GENERATED_REPLIES_COLLECTION = "generated_replies"
39
 
40
  # Global variables for MongoDB client and collections
41
  client: Optional[MongoClient] = None
42
  db: Optional[Any] = None
43
  extracted_emails_collection: Optional[Any] = None
 
 
44
  generated_replies_collection: Optional[Any] = None
45
 
46
  # --- Pydantic ObjectId Handling ---
@@ -158,7 +160,7 @@ class GenerateReplyRequest(BaseModel):
158
  emoji: str = Field("Auto", examples=["Auto", "None", "Occasional", "Frequent"])
159
 
160
  class GeneratedReplyData(BaseModel):
161
- # Use PyObjectId for the _id field
162
  id: Optional[PyObjectId] = Field(alias="_id", default=None)
163
  original_email_text: str
164
  generated_reply_text: str
@@ -180,11 +182,11 @@ class GeneratedReplyData(BaseModel):
180
  data["_id"] = str(data["_id"])
181
  return data
182
 
183
- # NEW: Response Model for /generate-reply endpoint
184
  class GenerateReplyResponse(BaseModel):
185
  reply: str = Field(..., description="The AI-generated reply text.")
186
- stored_id: str = Field(..., description="The MongoDB ID of the stored reply.")
187
- cached: bool = Field(..., description="True if the reply was retrieved from cache, False if newly generated.")
188
 
189
  # --- Query Models for GET Endpoints ---
190
  class ExtractedEmailQuery(BaseModel):
@@ -221,7 +223,7 @@ def extract_last_json_block(text: str) -> Optional[str]:
221
 
222
  def parse_date(date_str: Optional[str], current_date: date) -> Optional[date]:
223
  """
224
- Parses a date string, handling 'today', 'tomorrow', and YYYY-MM-DD format.
225
  Returns None if input is None or cannot be parsed into a valid date.
226
  """
227
  if not date_str:
@@ -311,16 +313,16 @@ Here is the required JSON schema for each category:
311
  Each Appointment object must have:
312
  - `title` (string, short, meaningful title in Italian based on the meeting's purpose)
313
  - `description` (string, summary of the meeting's goal)
314
- - `start_date` (string, YYYY-MM-DD. If not explicitly mentioned, use "{prompt_today_str}" for "today", or "{prompt_tomorrow_str}" for "tomorrow")
315
  - `start_time` (string, optional, e.g., "10:30 AM", null if not present)
316
- - `end_date` (string, YYYY-MM-DD, optional, null if unknown or not applicable)
317
  - `end_time` (string, optional, e.g., "11:00 AM", null if not present)
318
 
319
  - **tasks**: List of Task objects.
320
  Each Task object must have:
321
  - `task_title` (string, short summary of action item)
322
  - `task_description` (string, more detailed explanation)
323
- - `due_date` (string, YYYY-MM-DD. Infer from context, e.g., "entro domani" becomes "{prompt_tomorrow_str}", "today" becomes "{prompt_today_str}")
324
 
325
  ---
326
 
@@ -358,7 +360,7 @@ def _generate_response_internal(
358
  if not email_text:
359
  print(f"[{datetime.now()}] _generate_response_internal: Email text is empty.")
360
  return "Cannot generate reply for empty email text."
361
-
362
  try:
363
  llm = ChatGroq(model="meta-llama/llama-4-scout-17b-16e-instruct", temperature=0.7, max_tokens=800, groq_api_key=api_key)
364
  prompt_template_str="""
@@ -390,7 +392,7 @@ def _generate_response_internal(
390
  traceback.print_exc() # Print full traceback to logs
391
  raise # Re-raise the exception so it can be caught by handle_single_reply_request
392
 
393
- # --- Batching and Caching Configuration ---
394
  MAX_BATCH_SIZE = 20
395
  BATCH_TIMEOUT = 0.5 # seconds (Adjust based on expected LLM response time and desired latency)
396
 
@@ -400,45 +402,16 @@ reply_queue_condition = asyncio.Condition(lock=reply_queue_lock)
400
  batch_processor_task: Optional[asyncio.Task] = None
401
 
402
 
403
- # --- Batch Processor and Handler ---
404
  async def handle_single_reply_request(request_data: GenerateReplyRequest, future: asyncio.Future):
405
- """Handles a single request: checks cache, calls LLM, stores result, and sets future."""
406
  print(f"[{datetime.now()}] Handle single reply: Starting for email_text_start='{request_data.email_text[:50]}'...")
407
  if future.cancelled():
408
  print(f"[{datetime.now()}] Handle single reply: Future cancelled. Aborting.")
409
  return
410
  try:
411
- if generated_replies_collection is None:
412
- print(f"[{datetime.now()}] Handle single reply: DB collection 'generated_replies_collection' is None.")
413
- if not future.done():
414
- future.set_exception(HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Database service not available for caching/storage."))
415
- return
416
-
417
- cache_query = {
418
- "original_email_text": request_data.email_text,
419
- "language": request_data.language,
420
- "length": request_data.length,
421
- "style": request_data.style,
422
- "tone": request_data.tone,
423
- "emoji": request_data.emoji,
424
- }
425
- print(f"[{datetime.now()}] Handle single reply: Checking cache for reply...")
426
- # Use await asyncio.to_thread for blocking MongoDB operations
427
- cached_reply_doc = await asyncio.to_thread(generated_replies_collection.find_one, cache_query)
428
-
429
- if cached_reply_doc:
430
- print(f"[{datetime.now()}] Handle single reply: Reply found in cache. ID: {str(cached_reply_doc['_id'])}")
431
- response = {
432
- "reply": cached_reply_doc["generated_reply_text"],
433
- "stored_id": str(cached_reply_doc["_id"]),
434
- "cached": True
435
- }
436
- if not future.done():
437
- future.set_result(response)
438
- print(f"[{datetime.now()}] Handle single reply: Cache result set on future.")
439
- return
440
-
441
- print(f"[{datetime.now()}] Handle single reply: Reply not in cache. Calling LLM...")
442
  reply_content = await asyncio.to_thread(
443
  _generate_response_internal,
444
  request_data.email_text,
@@ -451,26 +424,10 @@ async def handle_single_reply_request(request_data: GenerateReplyRequest, future
451
  )
452
  print(f"[{datetime.now()}] Handle single reply: LLM call completed. Reply length: {len(reply_content)}.")
453
 
454
- reply_data_to_store = GeneratedReplyData(
455
- original_email_text=request_data.email_text,
456
- generated_reply_text=reply_content,
457
- language=request_data.language,
458
- length=request_data.length,
459
- style=request_data.style,
460
- tone=request_data.tone,
461
- emoji=request_data.emoji
462
- )
463
- print(f"[{datetime.now()}] Handle single reply: Storing reply in DB...")
464
- # Use model_dump for Pydantic v2
465
- reply_data_dict = reply_data_to_store.model_dump(by_alias=True, exclude_none=True, exclude={'id'})
466
-
467
- insert_result = await asyncio.to_thread(generated_replies_collection.insert_one, reply_data_dict)
468
- stored_id = str(insert_result.inserted_id)
469
- print(f"[{datetime.now()}] Handle single reply: Reply stored in DB. ID: {stored_id}")
470
-
471
  final_response = {
472
  "reply": reply_content,
473
- "stored_id": stored_id,
474
  "cached": False
475
  }
476
  if not future.done():
@@ -585,6 +542,7 @@ async def startup_event():
585
  client.admin.command('ping') # Test connection
586
  db = client[DB_NAME]
587
  extracted_emails_collection = db[EXTRACTED_EMAILS_COLLECTION]
 
588
  generated_replies_collection = db[GENERATED_REPLIES_COLLECTION]
589
  print(f"[{datetime.now()}] Successfully connected to MongoDB: {DB_NAME}")
590
 
@@ -739,16 +697,17 @@ async def extract_email_data_excel(request: ProcessEmailRequest):
739
  raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Excel functionality is currently disabled.")
740
 
741
 
742
- @app.post("/generate-reply", response_model=GenerateReplyResponse, summary="Generate a smart reply to an email (batched & cached)")
743
  async def generate_email_reply(request: GenerateReplyRequest):
744
  """
745
  Generates an intelligent email reply based on specified parameters (language, length, style, tone, emoji).
746
- Uses a batch processing system with caching for efficiency.
747
  """
748
  print(f"[{datetime.now()}] /generate-reply: Received request.")
749
- if generated_replies_collection is None or batch_processor_task is None or reply_queue_condition is None:
750
- print(f"[{datetime.now()}] /generate-reply: Service not initialized. gen_replies_coll={generated_replies_collection is not None}, batch_task={batch_processor_task is not None}, queue_cond={reply_queue_condition is not None}")
751
- raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Reply generation service not fully initialized. Check server logs for database or batch processor issues.")
 
752
 
753
  future = asyncio.Future()
754
  current_time = asyncio.get_event_loop().time()
@@ -759,8 +718,8 @@ async def generate_email_reply(request: GenerateReplyRequest):
759
  print(f"[{datetime.now()}] /generate-reply: Request added to queue, notifying batch processor. Queue size: {len(reply_request_queue)}")
760
 
761
  try:
762
- # Debugging: Increase timeout significantly to allow full tracing in logs
763
- client_timeout = BATCH_TIMEOUT + 60.0 # Example: 0.5s batch + 60s LLM response buffer = 60.5s total timeout
764
  print(f"[{datetime.now()}] /generate-reply: Waiting for future result with timeout {client_timeout}s.")
765
  result = await asyncio.wait_for(future, timeout=client_timeout)
766
  print(f"[{datetime.now()}] /generate-reply: Future result received. Returning data.")
@@ -832,6 +791,7 @@ async def query_extracted_emails_endpoint(query_params: ExtractedEmailQuery = De
832
  @app.get("/query-generated-replies", response_model=List[GeneratedReplyData], summary="Query generated replies from MongoDB")
833
  async def query_generated_replies_endpoint(query_params: GeneratedReplyQuery = Depends()):
834
  print(f"[{datetime.now()}] /query-generated-replies: Received request with params: {query_params.model_dump_json()}")
 
835
  if generated_replies_collection is None:
836
  print(f"[{datetime.now()}] /query-generated-replies: MongoDB collection is None.")
837
  raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="MongoDB not available for querying generated replies.")
 
35
  MONGO_URI = "mongodb+srv://precison9:P1LhtFknkT75yg5L@cluster0.isuwpef.mongodb.net"
36
  DB_NAME = "email_assistant_db"
37
  EXTRACTED_EMAILS_COLLECTION = "extracted_emails"
38
+ GENERATED_REPLIES_COLLECTION = "generated_replies" # Still defined, but not used by generate-reply logic
39
 
40
  # Global variables for MongoDB client and collections
41
  client: Optional[MongoClient] = None
42
  db: Optional[Any] = None
43
  extracted_emails_collection: Optional[Any] = None
44
+ # generated_replies_collection is no longer needed for /generate-reply logic,
45
+ # but kept for /query-generated-replies endpoint if that's still desired.
46
  generated_replies_collection: Optional[Any] = None
47
 
48
  # --- Pydantic ObjectId Handling ---
 
160
  emoji: str = Field("Auto", examples=["Auto", "None", "Occasional", "Frequent"])
161
 
162
  class GeneratedReplyData(BaseModel):
163
+ # Use PyObjectId for the _id field (This model is now only used for the query endpoint)
164
  id: Optional[PyObjectId] = Field(alias="_id", default=None)
165
  original_email_text: str
166
  generated_reply_text: str
 
182
  data["_id"] = str(data["_id"])
183
  return data
184
 
185
+ # Response Model for /generate-reply endpoint (simplified)
186
  class GenerateReplyResponse(BaseModel):
187
  reply: str = Field(..., description="The AI-generated reply text.")
188
+ # 'stored_id' and 'cached' are removed as caching/storage is removed
189
+ # from the main generate-reply logic.
190
 
191
  # --- Query Models for GET Endpoints ---
192
  class ExtractedEmailQuery(BaseModel):
 
223
 
224
  def parse_date(date_str: Optional[str], current_date: date) -> Optional[date]:
225
  """
226
+ Parses a date string, handling 'today', 'tomorrow', and APAC-MM-DD format.
227
  Returns None if input is None or cannot be parsed into a valid date.
228
  """
229
  if not date_str:
 
313
  Each Appointment object must have:
314
  - `title` (string, short, meaningful title in Italian based on the meeting's purpose)
315
  - `description` (string, summary of the meeting's goal)
316
+ - `start_date` (string, APAC-MM-DD. If not explicitly mentioned, use "{prompt_today_str}" for "today", or "{prompt_tomorrow_str}" for "tomorrow")
317
  - `start_time` (string, optional, e.g., "10:30 AM", null if not present)
318
+ - `end_date` (string, APAC-MM-DD, optional, null if unknown or not applicable)
319
  - `end_time` (string, optional, e.g., "11:00 AM", null if not present)
320
 
321
  - **tasks**: List of Task objects.
322
  Each Task object must have:
323
  - `task_title` (string, short summary of action item)
324
  - `task_description` (string, more detailed explanation)
325
+ - `due_date` (string, APAC-MM-DD. Infer from context, e.g., "entro domani" becomes "{prompt_tomorrow_str}", "today" becomes "{prompt_today_str}")
326
 
327
  ---
328
 
 
360
  if not email_text:
361
  print(f"[{datetime.now()}] _generate_response_internal: Email text is empty.")
362
  return "Cannot generate reply for empty email text."
363
+
364
  try:
365
  llm = ChatGroq(model="meta-llama/llama-4-scout-17b-16e-instruct", temperature=0.7, max_tokens=800, groq_api_key=api_key)
366
  prompt_template_str="""
 
392
  traceback.print_exc() # Print full traceback to logs
393
  raise # Re-raise the exception so it can be caught by handle_single_reply_request
394
 
395
+ # --- Batching Configuration (Caching/Storage logic removed) ---
396
  MAX_BATCH_SIZE = 20
397
  BATCH_TIMEOUT = 0.5 # seconds (Adjust based on expected LLM response time and desired latency)
398
 
 
402
  batch_processor_task: Optional[asyncio.Task] = None
403
 
404
 
405
+ # --- Batch Processor and Handler (Simplified) ---
406
  async def handle_single_reply_request(request_data: GenerateReplyRequest, future: asyncio.Future):
407
+ """Handles a single request: calls LLM, and sets future with the reply."""
408
  print(f"[{datetime.now()}] Handle single reply: Starting for email_text_start='{request_data.email_text[:50]}'...")
409
  if future.cancelled():
410
  print(f"[{datetime.now()}] Handle single reply: Future cancelled. Aborting.")
411
  return
412
  try:
413
+ # Directly call LLM (no cache check or storage)
414
+ print(f"[{datetime.now()}] Handle single reply: Calling LLM for reply generation...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  reply_content = await asyncio.to_thread(
416
  _generate_response_internal,
417
  request_data.email_text,
 
424
  )
425
  print(f"[{datetime.now()}] Handle single reply: LLM call completed. Reply length: {len(reply_content)}.")
426
 
427
+ # Simplified response as no storage/cache ID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  final_response = {
429
  "reply": reply_content,
430
+ "stored_id": "N/A - Caching disabled", # Indicate that ID is not available
431
  "cached": False
432
  }
433
  if not future.done():
 
542
  client.admin.command('ping') # Test connection
543
  db = client[DB_NAME]
544
  extracted_emails_collection = db[EXTRACTED_EMAILS_COLLECTION]
545
+ # Keep generated_replies_collection definition if /query-generated-replies is still desired
546
  generated_replies_collection = db[GENERATED_REPLIES_COLLECTION]
547
  print(f"[{datetime.now()}] Successfully connected to MongoDB: {DB_NAME}")
548
 
 
697
  raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Excel functionality is currently disabled.")
698
 
699
 
700
+ @app.post("/generate-reply", response_model=GenerateReplyResponse, summary="Generate a smart reply to an email (batched)")
701
  async def generate_email_reply(request: GenerateReplyRequest):
702
  """
703
  Generates an intelligent email reply based on specified parameters (language, length, style, tone, emoji).
704
+ Uses a batch processing system. Caching and database storage for replies are disabled.
705
  """
706
  print(f"[{datetime.now()}] /generate-reply: Received request.")
707
+ # generated_replies_collection check is no longer relevant for this endpoint's logic
708
+ if batch_processor_task is None or reply_queue_condition is None:
709
+ print(f"[{datetime.now()}] /generate-reply: Service not fully initialized. batch_task={batch_processor_task is not None}, queue_cond={reply_queue_condition is not None}")
710
+ raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Reply generation service not fully initialized. Check server logs for batch processor issues.")
711
 
712
  future = asyncio.Future()
713
  current_time = asyncio.get_event_loop().time()
 
718
  print(f"[{datetime.now()}] /generate-reply: Request added to queue, notifying batch processor. Queue size: {len(reply_request_queue)}")
719
 
720
  try:
721
+ # Debugging: Use a very long timeout for now to ensure server-side logs complete
722
+ client_timeout = BATCH_TIMEOUT + 300.0 # 5 minutes (0.5s batch + 300s buffer)
723
  print(f"[{datetime.now()}] /generate-reply: Waiting for future result with timeout {client_timeout}s.")
724
  result = await asyncio.wait_for(future, timeout=client_timeout)
725
  print(f"[{datetime.now()}] /generate-reply: Future result received. Returning data.")
 
791
  @app.get("/query-generated-replies", response_model=List[GeneratedReplyData], summary="Query generated replies from MongoDB")
792
  async def query_generated_replies_endpoint(query_params: GeneratedReplyQuery = Depends()):
793
  print(f"[{datetime.now()}] /query-generated-replies: Received request with params: {query_params.model_dump_json()}")
794
+ # This endpoint still relies on `generated_replies_collection`
795
  if generated_replies_collection is None:
796
  print(f"[{datetime.now()}] /query-generated-replies: MongoDB collection is None.")
797
  raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="MongoDB not available for querying generated replies.")