Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
#
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 =
|
| 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 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 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 |
-
|
| 575 |
-
|
|
|
|
| 576 |
contents=contents
|
| 577 |
)
|
| 578 |
raw_text = response.text
|
| 579 |
-
print("π₯ Received response (chars):
|
|
|
|
|
|
|
|
|
|
| 580 |
return raw_text
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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):
|