atz21 commited on
Commit
17962e4
Β·
verified Β·
1 Parent(s): 643922c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -22
app.py CHANGED
@@ -16,8 +16,56 @@ from prompts import QP_MS_TRANSCRIPTION_PROMPT, get_grading_prompt
16
  from supabase import create_client, Client
17
 
18
  # ---------------- CONFIG ----------------
19
- # Create client with new SDK
20
- client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  GRID_ROWS, GRID_COLS = 20, 14
22
 
23
  # Supabase configuration
@@ -501,16 +549,18 @@ def compress_pdf(input_path, output_path=None, max_size=20*1024*1024):
501
  def upload_to_gemini(path, display_name=None):
502
  """
503
  Upload a file to Gemini using the NEW google-genai SDK.
 
504
  """
505
  print(f"πŸ“€ Uploading {path} to Gemini...")
506
  try:
507
- uploaded_file = client.files.upload(file=path)
 
508
 
509
  # Wait for processing to complete
510
  print(f"⏳ Waiting for file processing: {uploaded_file.name}")
511
  while uploaded_file.state.name == "PROCESSING":
512
  time.sleep(2)
513
- uploaded_file = client.files.get(name=uploaded_file.name)
514
 
515
  if uploaded_file.state.name == "FAILED":
516
  raise Exception(f"File processing failed: {uploaded_file.name}")
@@ -534,6 +584,7 @@ def merge_pdfs(paths, output_path):
534
  def gemini_generate_content(prompt_text, file_upload_obj=None, image_obj=None, model_name="gemini-2.5-pro", fallback_model="gemini-2.5-flash"):
535
  """
536
  Send prompt_text and optionally an uploaded file (or an image object/list) to the model using NEW SDK.
 
537
  Returns textual response and prints progress.
538
  """
539
  contents = [prompt_text]
@@ -558,29 +609,81 @@ def gemini_generate_content(prompt_text, file_upload_obj=None, image_obj=None, m
558
 
559
  print("πŸ“‘ Sending request to Gemini (prompt length:", len(prompt_text), "chars )")
560
 
561
- try:
562
- response = client.models.generate_content(
563
- model=model_name,
564
- contents=contents
565
- )
566
- raw_text = response.text
567
- print("πŸ“₯ Received response (chars):", len(raw_text))
568
- return raw_text
569
- except Exception as e:
570
- print(f"❌ Generation failed: {e}")
571
- # Try fallback model
572
- print(f"⚑ Trying fallback model: {fallback_model}")
573
  try:
574
- response = client.models.generate_content(
575
- model=fallback_model,
 
576
  contents=contents
577
  )
578
  raw_text = response.text
579
- print("πŸ“₯ Received response (chars):", len(raw_text))
 
 
 
580
  return raw_text
581
- except Exception as e2:
582
- print(f"❌ Fallback also failed: {e2}")
583
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
 
585
  # ---------------- PARSERS ----------------
586
  def extract_question_ids_from_qpms(text: str):
 
16
  from supabase import create_client, Client
17
 
18
  # ---------------- CONFIG ----------------
19
+ # Multi-API Key Configuration for handling RESOURCE_EXHAUSTED errors
20
+ class GeminiClientManager:
21
+ """Manages multiple Gemini API keys with automatic rotation on quota exhaustion."""
22
+
23
+ def __init__(self):
24
+ # Load all three API keys from environment
25
+ self.api_keys = [
26
+ os.getenv("GEMINI_API_KEY_1"),
27
+ os.getenv("GEMINI_API_KEY_2"),
28
+ os.getenv("GEMINI_API_KEY_3")
29
+ ]
30
+
31
+ # Filter out None values
32
+ self.api_keys = [key for key in self.api_keys if key]
33
+
34
+ if not self.api_keys:
35
+ raise ValueError("❌ No API keys found! Please set at least GEMINI_API_KEY_1")
36
+
37
+ print(f"βœ… Loaded {len(self.api_keys)} Gemini API key(s)")
38
+
39
+ # Current key index (0 = primary)
40
+ self.current_key_index = 0
41
+
42
+ # Create clients for all keys
43
+ self.clients = [genai.Client(api_key=key) for key in self.api_keys]
44
+
45
+ def get_current_client(self):
46
+ """Get the currently active client."""
47
+ return self.clients[self.current_key_index]
48
+
49
+ def rotate_to_next_key(self):
50
+ """Rotate to the next available API key."""
51
+ if len(self.api_keys) == 1:
52
+ print("⚠️ Only one API key available, cannot rotate")
53
+ return False
54
+
55
+ old_index = self.current_key_index
56
+ self.current_key_index = (self.current_key_index + 1) % len(self.api_keys)
57
+ print(f"πŸ”„ Rotating from API key #{old_index + 1} to API key #{self.current_key_index + 1}")
58
+ return True
59
+
60
+ def reset_to_primary(self):
61
+ """Reset to primary (first) API key."""
62
+ if self.current_key_index != 0:
63
+ print(f"πŸ”™ Resetting to primary API key #1")
64
+ self.current_key_index = 0
65
+
66
+ # Initialize the client manager
67
+ client_manager = GeminiClientManager()
68
+ client = client_manager.get_current_client() # For backward compatibility
69
  GRID_ROWS, GRID_COLS = 20, 14
70
 
71
  # Supabase configuration
 
549
  def upload_to_gemini(path, display_name=None):
550
  """
551
  Upload a file to Gemini using the NEW google-genai SDK.
552
+ Uses the current active API key from client_manager.
553
  """
554
  print(f"πŸ“€ Uploading {path} to Gemini...")
555
  try:
556
+ current_client = client_manager.get_current_client()
557
+ uploaded_file = current_client.files.upload(file=path)
558
 
559
  # Wait for processing to complete
560
  print(f"⏳ Waiting for file processing: {uploaded_file.name}")
561
  while uploaded_file.state.name == "PROCESSING":
562
  time.sleep(2)
563
+ uploaded_file = current_client.files.get(name=uploaded_file.name)
564
 
565
  if uploaded_file.state.name == "FAILED":
566
  raise Exception(f"File processing failed: {uploaded_file.name}")
 
584
  def gemini_generate_content(prompt_text, file_upload_obj=None, image_obj=None, model_name="gemini-2.5-pro", fallback_model="gemini-2.5-flash"):
585
  """
586
  Send prompt_text and optionally an uploaded file (or an image object/list) to the model using NEW SDK.
587
+ Automatically rotates through available API keys on RESOURCE_EXHAUSTED errors.
588
  Returns textual response and prints progress.
589
  """
590
  contents = [prompt_text]
 
609
 
610
  print("πŸ“‘ Sending request to Gemini (prompt length:", len(prompt_text), "chars )")
611
 
612
+ # Try with all available API keys
613
+ max_attempts = len(client_manager.api_keys)
614
+ attempt = 0
615
+
616
+ while attempt < max_attempts:
617
+ current_client = client_manager.get_current_client()
618
+ current_key_num = client_manager.current_key_index + 1
619
+
 
 
 
 
620
  try:
621
+ print(f"πŸ”‘ Using API key #{current_key_num} with model {model_name}")
622
+ response = current_client.models.generate_content(
623
+ model=model_name,
624
  contents=contents
625
  )
626
  raw_text = response.text
627
+ print(f"πŸ“₯ Received response (chars): {len(raw_text)}")
628
+
629
+ # Success! Reset to primary key for next request
630
+ client_manager.reset_to_primary()
631
  return raw_text
632
+
633
+ except Exception as e:
634
+ error_str = str(e)
635
+ print(f"❌ Generation failed with API key #{current_key_num}: {e}")
636
+
637
+ # Check if it's a RESOURCE_EXHAUSTED error
638
+ if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str:
639
+ print(f"⚠️ Quota exhausted for API key #{current_key_num}")
640
+
641
+ # Try to rotate to next key
642
+ if client_manager.rotate_to_next_key():
643
+ attempt += 1
644
+ print(f"πŸ”„ Retrying with next API key (attempt {attempt + 1}/{max_attempts})...")
645
+ continue
646
+ else:
647
+ # Only one key available, try fallback model
648
+ print(f"⚑ Trying fallback model: {fallback_model}")
649
+ try:
650
+ response = current_client.models.generate_content(
651
+ model=fallback_model,
652
+ contents=contents
653
+ )
654
+ raw_text = response.text
655
+ print(f"πŸ“₯ Received response (chars): {len(raw_text)}")
656
+ client_manager.reset_to_primary()
657
+ return raw_text
658
+ except Exception as e2:
659
+ print(f"❌ Fallback also failed: {e2}")
660
+ raise Exception(f"All API keys exhausted. Error: {e2}")
661
+ else:
662
+ # Not a quota error, try fallback model with same key
663
+ print(f"⚑ Trying fallback model: {fallback_model}")
664
+ try:
665
+ response = current_client.models.generate_content(
666
+ model=fallback_model,
667
+ contents=contents
668
+ )
669
+ raw_text = response.text
670
+ print(f"πŸ“₯ Received response (chars): {len(raw_text)}")
671
+ client_manager.reset_to_primary()
672
+ return raw_text
673
+ except Exception as e2:
674
+ print(f"❌ Fallback also failed: {e2}")
675
+ # If we have more keys, try them
676
+ if attempt < max_attempts - 1:
677
+ client_manager.rotate_to_next_key()
678
+ attempt += 1
679
+ print(f"πŸ”„ Trying next API key (attempt {attempt + 1}/{max_attempts})...")
680
+ continue
681
+ else:
682
+ raise Exception(f"All attempts failed. Last error: {e2}")
683
+
684
+ # If we exhausted all attempts
685
+ raise Exception(f"❌ All {max_attempts} API key(s) exhausted. Please check your quota or try again later.")
686
+
687
 
688
  # ---------------- PARSERS ----------------
689
  def extract_question_ids_from_qpms(text: str):