Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -37,13 +37,8 @@ logging.basicConfig(
|
|
| 37 |
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 38 |
)
|
| 39 |
|
| 40 |
-
# Model configuration
|
| 41 |
-
|
| 42 |
-
"TinyLlama (Fastest)": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
| 43 |
-
"Phi-2 (Balanced)": "microsoft/phi-2",
|
| 44 |
-
"DeepSeek-V3 (Most Powerful)": "deepseek-ai/DeepSeek-V3"
|
| 45 |
-
}
|
| 46 |
-
DEFAULT_MODEL = "TinyLlama (Fastest)"
|
| 47 |
|
| 48 |
# Initialize Hugging Face API
|
| 49 |
if HF_TOKEN:
|
|
@@ -61,11 +56,11 @@ class ModelLoader:
|
|
| 61 |
self.loaded = False
|
| 62 |
self.loading = False
|
| 63 |
self.error = None
|
| 64 |
-
self.
|
| 65 |
|
| 66 |
-
def load_model(self,
|
| 67 |
"""Lazy load the model with progress feedback"""
|
| 68 |
-
if self.loaded
|
| 69 |
return self.model, self.tokenizer
|
| 70 |
|
| 71 |
self.loading = True
|
|
@@ -85,48 +80,48 @@ class ModelLoader:
|
|
| 85 |
# Load with optimized settings
|
| 86 |
model_kwargs = {
|
| 87 |
"trust_remote_code": True,
|
| 88 |
-
"torch_dtype": torch.float16,
|
| 89 |
-
"device_map": "auto",
|
| 90 |
"low_cpu_mem_usage": True
|
| 91 |
}
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
|
|
|
| 95 |
|
| 96 |
if progress:
|
| 97 |
progress(0.3, desc="Loading tokenizer...")
|
| 98 |
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 99 |
-
|
| 100 |
trust_remote_code=True
|
| 101 |
)
|
| 102 |
|
| 103 |
if progress:
|
| 104 |
progress(0.6, desc="Loading model...")
|
| 105 |
self.model = AutoModelForCausalLM.from_pretrained(
|
| 106 |
-
|
| 107 |
**model_kwargs
|
| 108 |
-
)
|
| 109 |
|
| 110 |
# Verify model responsiveness
|
| 111 |
if progress:
|
| 112 |
progress(0.8, desc="Verifying model...")
|
| 113 |
-
test_input = self.tokenizer("Test", return_tensors="pt").to(self.
|
| 114 |
_ = self.model.generate(**test_input, max_new_tokens=1)
|
| 115 |
|
| 116 |
self.model.eval() # Disable dropout
|
| 117 |
if progress:
|
| 118 |
progress(0.9, desc="Finalizing...")
|
| 119 |
self.loaded = True
|
| 120 |
-
self.current_model = model_name
|
| 121 |
return self.model, self.tokenizer
|
| 122 |
|
| 123 |
except torch.cuda.OutOfMemoryError:
|
| 124 |
-
self.error = "Out of GPU memory. Try
|
| 125 |
logging.error(self.error)
|
| 126 |
return None, None
|
| 127 |
except Exception as e:
|
| 128 |
-
self.error = str(e)
|
| 129 |
-
logging.error(
|
| 130 |
return None, None
|
| 131 |
finally:
|
| 132 |
self.loading = False
|
|
@@ -298,30 +293,36 @@ class TranscriptParser:
|
|
| 298 |
}
|
| 299 |
|
| 300 |
def _extract_student_info(self, text: str):
|
| 301 |
-
"""
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
| 305 |
)
|
|
|
|
|
|
|
| 306 |
if header_match:
|
| 307 |
self.student_data = {
|
| 308 |
-
"id": header_match.group(1),
|
| 309 |
-
"name": header_match.group(2).strip(),
|
| 310 |
-
"unweighted_gpa": float(header_match.group(3)),
|
| 311 |
-
"community_service_hours": int(header_match.group(4))
|
| 312 |
}
|
| 313 |
|
| 314 |
-
#
|
| 315 |
-
|
| 316 |
-
r"
|
| 317 |
-
|
| 318 |
)
|
|
|
|
|
|
|
| 319 |
if grade_match:
|
| 320 |
self.student_data.update({
|
| 321 |
-
"current_grade": grade_match.group(1),
|
| 322 |
-
"graduation_year": grade_match.group(2),
|
| 323 |
-
"weighted_gpa": float(grade_match.group(3)),
|
| 324 |
-
"total_credits": float(grade_match.group(4))
|
| 325 |
})
|
| 326 |
|
| 327 |
def _extract_requirements(self, text: str):
|
|
@@ -401,7 +402,7 @@ async def parse_transcript_async(file_obj, progress=gr.Progress()):
|
|
| 401 |
|
| 402 |
def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
|
| 403 |
"""Use AI model to parse transcript text with progress feedback"""
|
| 404 |
-
model, tokenizer = model_loader.load_model(
|
| 405 |
if model is None or tokenizer is None:
|
| 406 |
raise gr.Error(f"Model failed to load. {model_loader.error or 'Please try loading a model first.'}")
|
| 407 |
|
|
@@ -472,7 +473,7 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
|
|
| 472 |
progress(0.1, desc="Processing transcript with AI...")
|
| 473 |
|
| 474 |
# Tokenize and generate response
|
| 475 |
-
inputs = model_loader.tokenizer(prompt, return_tensors="pt").to(model_loader.
|
| 476 |
if progress:
|
| 477 |
progress(0.4)
|
| 478 |
|
|
@@ -502,7 +503,7 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
|
|
| 502 |
return validate_parsed_data(parsed_data)
|
| 503 |
|
| 504 |
except torch.cuda.OutOfMemoryError:
|
| 505 |
-
raise gr.Error("The model ran out of memory. Try with a smaller transcript
|
| 506 |
except Exception as e:
|
| 507 |
logging.error(f"AI parsing error: {str(e)}")
|
| 508 |
raise gr.Error(f"Error processing transcript: {str(e)}")
|
|
@@ -1306,12 +1307,6 @@ def create_interface():
|
|
| 1306 |
background-color: #fff3e0;
|
| 1307 |
color: #e65100;
|
| 1308 |
}
|
| 1309 |
-
.model-selection {
|
| 1310 |
-
margin-bottom: 20px;
|
| 1311 |
-
padding: 15px;
|
| 1312 |
-
background: #f8f9fa;
|
| 1313 |
-
border-radius: 8px;
|
| 1314 |
-
}
|
| 1315 |
"""
|
| 1316 |
|
| 1317 |
gr.Markdown("""
|
|
@@ -1320,20 +1315,6 @@ def create_interface():
|
|
| 1320 |
Complete each step to get customized learning recommendations.
|
| 1321 |
""")
|
| 1322 |
|
| 1323 |
-
# Model selection section
|
| 1324 |
-
with gr.Group(elem_classes="model-selection"):
|
| 1325 |
-
model_selector = gr.Dropdown(
|
| 1326 |
-
choices=list(MODEL_CHOICES.keys()),
|
| 1327 |
-
value=DEFAULT_MODEL,
|
| 1328 |
-
label="Select AI Model",
|
| 1329 |
-
interactive=True
|
| 1330 |
-
)
|
| 1331 |
-
load_model_btn = gr.Button("Load Selected Model", variant="secondary")
|
| 1332 |
-
model_status = gr.HTML(
|
| 1333 |
-
value="<div class='model-loading'>Model not loaded yet. Please select and load a model.</div>",
|
| 1334 |
-
visible=True
|
| 1335 |
-
)
|
| 1336 |
-
|
| 1337 |
# Progress tracker
|
| 1338 |
with gr.Row():
|
| 1339 |
with gr.Column(scale=1):
|
|
@@ -1353,8 +1334,8 @@ def create_interface():
|
|
| 1353 |
# Navigation message
|
| 1354 |
nav_message = gr.HTML(elem_classes="nav-message", visible=False)
|
| 1355 |
|
| 1356 |
-
# Main tabs
|
| 1357 |
-
with gr.Tabs() as tabs:
|
| 1358 |
# ===== TAB 1: Transcript Upload =====
|
| 1359 |
with gr.Tab("Transcript Upload", id=0) as tab1:
|
| 1360 |
with gr.Row():
|
|
@@ -1765,22 +1746,10 @@ def create_interface():
|
|
| 1765 |
outputs=[tabs, nav_message]
|
| 1766 |
)
|
| 1767 |
|
| 1768 |
-
#
|
| 1769 |
-
|
| 1770 |
-
|
| 1771 |
-
|
| 1772 |
-
if model_loader.loaded:
|
| 1773 |
-
return gr.update(value=f"<div class='alert-box'>{model_name} loaded successfully!</div>", visible=True)
|
| 1774 |
-
else:
|
| 1775 |
-
return gr.update(value=f"<div class='nav-message'>Failed to load model: {model_loader.error}</div>", visible=True)
|
| 1776 |
-
except Exception as e:
|
| 1777 |
-
logging.error(f"Model loading error: {str(e)}")
|
| 1778 |
-
return gr.update(value=f"<div class='nav-message'>Error: {str(e)}</div>", visible=True)
|
| 1779 |
-
|
| 1780 |
-
load_model_btn.click(
|
| 1781 |
-
fn=load_selected_model,
|
| 1782 |
-
inputs=model_selector,
|
| 1783 |
-
outputs=model_status
|
| 1784 |
)
|
| 1785 |
|
| 1786 |
return app
|
|
|
|
| 37 |
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 38 |
)
|
| 39 |
|
| 40 |
+
# Model configuration - Only DeepSeek
|
| 41 |
+
MODEL_NAME = "deepseek-ai/DeepSeek-V3"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# Initialize Hugging Face API
|
| 44 |
if HF_TOKEN:
|
|
|
|
| 56 |
self.loaded = False
|
| 57 |
self.loading = False
|
| 58 |
self.error = None
|
| 59 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 60 |
|
| 61 |
+
def load_model(self, progress: gr.Progress = None) -> Tuple[Optional[AutoModelForCausalLM], Optional[AutoTokenizer]]:
|
| 62 |
"""Lazy load the model with progress feedback"""
|
| 63 |
+
if self.loaded:
|
| 64 |
return self.model, self.tokenizer
|
| 65 |
|
| 66 |
self.loading = True
|
|
|
|
| 80 |
# Load with optimized settings
|
| 81 |
model_kwargs = {
|
| 82 |
"trust_remote_code": True,
|
| 83 |
+
"torch_dtype": torch.float16 if self.device == "cuda" else torch.float32,
|
| 84 |
+
"device_map": "auto" if self.device == "cuda" else None,
|
| 85 |
"low_cpu_mem_usage": True
|
| 86 |
}
|
| 87 |
|
| 88 |
+
# Add quantization config for low-memory devices
|
| 89 |
+
if self.device == "cpu":
|
| 90 |
+
model_kwargs["load_in_8bit"] = True
|
| 91 |
|
| 92 |
if progress:
|
| 93 |
progress(0.3, desc="Loading tokenizer...")
|
| 94 |
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 95 |
+
MODEL_NAME,
|
| 96 |
trust_remote_code=True
|
| 97 |
)
|
| 98 |
|
| 99 |
if progress:
|
| 100 |
progress(0.6, desc="Loading model...")
|
| 101 |
self.model = AutoModelForCausalLM.from_pretrained(
|
| 102 |
+
MODEL_NAME,
|
| 103 |
**model_kwargs
|
| 104 |
+
).to(self.device)
|
| 105 |
|
| 106 |
# Verify model responsiveness
|
| 107 |
if progress:
|
| 108 |
progress(0.8, desc="Verifying model...")
|
| 109 |
+
test_input = self.tokenizer("Test", return_tensors="pt").to(self.device)
|
| 110 |
_ = self.model.generate(**test_input, max_new_tokens=1)
|
| 111 |
|
| 112 |
self.model.eval() # Disable dropout
|
| 113 |
if progress:
|
| 114 |
progress(0.9, desc="Finalizing...")
|
| 115 |
self.loaded = True
|
|
|
|
| 116 |
return self.model, self.tokenizer
|
| 117 |
|
| 118 |
except torch.cuda.OutOfMemoryError:
|
| 119 |
+
self.error = "Out of GPU memory. Try using CPU instead."
|
| 120 |
logging.error(self.error)
|
| 121 |
return None, None
|
| 122 |
except Exception as e:
|
| 123 |
+
self.error = f"Model loading error: {str(e)}"
|
| 124 |
+
logging.error(self.error)
|
| 125 |
return None, None
|
| 126 |
finally:
|
| 127 |
self.loading = False
|
|
|
|
| 293 |
}
|
| 294 |
|
| 295 |
def _extract_student_info(self, text: str):
|
| 296 |
+
"""Enhanced student info extraction with more robust regex"""
|
| 297 |
+
# Unified pattern that handles variations in transcript formats
|
| 298 |
+
header_pattern = (
|
| 299 |
+
r"(?:Student\s*[:]?\s*|Name\s*[:]?\s*)?"
|
| 300 |
+
r"(\d{7})\s*[-]?\s*([\w\s,]+?)\s*"
|
| 301 |
+
r"(?:\||Cohort\s*\w+\s*\||Un-weighted\s*GPA\s*([\d.]+)\s*\||Comm\s*Serv\s*Hours\s*(\d+))?"
|
| 302 |
)
|
| 303 |
+
|
| 304 |
+
header_match = re.search(header_pattern, text, re.IGNORECASE)
|
| 305 |
if header_match:
|
| 306 |
self.student_data = {
|
| 307 |
+
"id": header_match.group(1) if header_match.group(1) else "Unknown",
|
| 308 |
+
"name": header_match.group(2).strip() if header_match.group(2) else "Unknown",
|
| 309 |
+
"unweighted_gpa": float(header_match.group(3)) if header_match.group(3) else 0.0,
|
| 310 |
+
"community_service_hours": int(header_match.group(4)) if header_match.group(4) else 0
|
| 311 |
}
|
| 312 |
|
| 313 |
+
# More flexible grade info pattern
|
| 314 |
+
grade_pattern = (
|
| 315 |
+
r"(?:Grade|Level)\s*[:]?\s*(\d+)\s*"
|
| 316 |
+
r"(?:\||YOG\s*[:]?\s*(\d{4})\s*\||Weighted\s*GPA\s*([\d.]+)\s*\||Total\s*Credits\s*Earned\s*([\d.]+))?"
|
| 317 |
)
|
| 318 |
+
|
| 319 |
+
grade_match = re.search(grade_pattern, text, re.IGNORECASE)
|
| 320 |
if grade_match:
|
| 321 |
self.student_data.update({
|
| 322 |
+
"current_grade": grade_match.group(1) if grade_match.group(1) else "Unknown",
|
| 323 |
+
"graduation_year": grade_match.group(2) if grade_match.group(2) else "Unknown",
|
| 324 |
+
"weighted_gpa": float(grade_match.group(3)) if grade_match.group(3) else 0.0,
|
| 325 |
+
"total_credits": float(grade_match.group(4)) if grade_match.group(4) else 0.0
|
| 326 |
})
|
| 327 |
|
| 328 |
def _extract_requirements(self, text: str):
|
|
|
|
| 402 |
|
| 403 |
def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
|
| 404 |
"""Use AI model to parse transcript text with progress feedback"""
|
| 405 |
+
model, tokenizer = model_loader.load_model(progress)
|
| 406 |
if model is None or tokenizer is None:
|
| 407 |
raise gr.Error(f"Model failed to load. {model_loader.error or 'Please try loading a model first.'}")
|
| 408 |
|
|
|
|
| 473 |
progress(0.1, desc="Processing transcript with AI...")
|
| 474 |
|
| 475 |
# Tokenize and generate response
|
| 476 |
+
inputs = model_loader.tokenizer(prompt, return_tensors="pt").to(model_loader.device)
|
| 477 |
if progress:
|
| 478 |
progress(0.4)
|
| 479 |
|
|
|
|
| 503 |
return validate_parsed_data(parsed_data)
|
| 504 |
|
| 505 |
except torch.cuda.OutOfMemoryError:
|
| 506 |
+
raise gr.Error("The model ran out of memory. Try with a smaller transcript.")
|
| 507 |
except Exception as e:
|
| 508 |
logging.error(f"AI parsing error: {str(e)}")
|
| 509 |
raise gr.Error(f"Error processing transcript: {str(e)}")
|
|
|
|
| 1307 |
background-color: #fff3e0;
|
| 1308 |
color: #e65100;
|
| 1309 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1310 |
"""
|
| 1311 |
|
| 1312 |
gr.Markdown("""
|
|
|
|
| 1315 |
Complete each step to get customized learning recommendations.
|
| 1316 |
""")
|
| 1317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1318 |
# Progress tracker
|
| 1319 |
with gr.Row():
|
| 1320 |
with gr.Column(scale=1):
|
|
|
|
| 1334 |
# Navigation message
|
| 1335 |
nav_message = gr.HTML(elem_classes="nav-message", visible=False)
|
| 1336 |
|
| 1337 |
+
# Main tabs (hidden since we're using the button navigation)
|
| 1338 |
+
with gr.Tabs(visible=False) as tabs:
|
| 1339 |
# ===== TAB 1: Transcript Upload =====
|
| 1340 |
with gr.Tab("Transcript Upload", id=0) as tab1:
|
| 1341 |
with gr.Row():
|
|
|
|
| 1746 |
outputs=[tabs, nav_message]
|
| 1747 |
)
|
| 1748 |
|
| 1749 |
+
# Load DeepSeek model automatically
|
| 1750 |
+
app.load(
|
| 1751 |
+
fn=lambda: model_loader.load_model(),
|
| 1752 |
+
outputs=[]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1753 |
)
|
| 1754 |
|
| 1755 |
return app
|