Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -28,38 +28,75 @@ MAX_AGE = 120
|
|
| 28 |
SESSION_TOKEN_LENGTH = 32
|
| 29 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# Initialize Hugging Face API
|
| 32 |
if HF_TOKEN:
|
| 33 |
hf_api = HfApi(token=HF_TOKEN)
|
| 34 |
HfFolder.save_token(HF_TOKEN)
|
| 35 |
|
| 36 |
-
# ==========
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
|
| 63 |
|
| 64 |
# ========== UTILITY FUNCTIONS ==========
|
| 65 |
def generate_session_token() -> str:
|
|
@@ -198,13 +235,14 @@ def remove_sensitive_info(text: str) -> str:
|
|
| 198 |
return text
|
| 199 |
|
| 200 |
# ========== TRANSCRIPT PARSING ==========
|
| 201 |
-
def
|
| 202 |
-
"""Use
|
|
|
|
| 203 |
if model is None or tokenizer is None:
|
| 204 |
-
raise gr.Error("
|
| 205 |
|
| 206 |
# Pre-process the text
|
| 207 |
-
text = remove_sensitive_info(text[:15000]) # Limit
|
| 208 |
|
| 209 |
prompt = f"""
|
| 210 |
Analyze this academic transcript and extract structured information:
|
|
@@ -218,57 +256,41 @@ def parse_transcript_with_deepseek(text: str) -> Dict:
|
|
| 218 |
* Credits earned
|
| 219 |
* Year/semester taken
|
| 220 |
* Grade level when taken
|
| 221 |
-
Return the data in
|
| 222 |
-
|
| 223 |
-
"grade_level": "11",
|
| 224 |
-
"gpa": {{
|
| 225 |
-
"weighted": "4.2",
|
| 226 |
-
"unweighted": "3.9"
|
| 227 |
-
}},
|
| 228 |
-
"courses": [
|
| 229 |
-
{{
|
| 230 |
-
"code": "MATH101",
|
| 231 |
-
"name": "Algebra II",
|
| 232 |
-
"grade": "A",
|
| 233 |
-
"credits": "1.0",
|
| 234 |
-
"year": "2023-2024",
|
| 235 |
-
"grade_level": "11"
|
| 236 |
-
}}
|
| 237 |
-
]
|
| 238 |
-
}}
|
| 239 |
Transcript Text:
|
| 240 |
{text}
|
| 241 |
"""
|
| 242 |
|
| 243 |
try:
|
|
|
|
|
|
|
| 244 |
# Tokenize and generate response
|
| 245 |
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
|
|
|
| 246 |
|
| 247 |
outputs = model.generate(
|
| 248 |
**inputs,
|
| 249 |
-
max_new_tokens=
|
| 250 |
temperature=0.1,
|
| 251 |
do_sample=True
|
| 252 |
)
|
|
|
|
| 253 |
|
| 254 |
# Decode the response
|
| 255 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 256 |
|
| 257 |
-
# Extract
|
| 258 |
-
if '```json' in response
|
| 259 |
-
json_str = response.split('```json')[1].split('```')[0].strip()
|
| 260 |
-
elif '```' in response:
|
| 261 |
-
json_str = response.split('```')[1].split('```')[0].strip()
|
| 262 |
-
else:
|
| 263 |
-
json_str = response
|
| 264 |
|
| 265 |
-
# Parse and validate
|
| 266 |
parsed_data = json.loads(json_str)
|
|
|
|
| 267 |
|
| 268 |
return validate_parsed_data(parsed_data)
|
| 269 |
|
| 270 |
except torch.cuda.OutOfMemoryError:
|
| 271 |
-
raise gr.Error("The model ran out of memory. Try with a smaller transcript or
|
| 272 |
except Exception as e:
|
| 273 |
raise gr.Error(f"Error processing transcript: {str(e)}")
|
| 274 |
|
|
@@ -333,7 +355,7 @@ def format_transcript_output(data: Dict) -> str:
|
|
| 333 |
|
| 334 |
return '\n'.join(output)
|
| 335 |
|
| 336 |
-
def parse_transcript(file_obj) -> Tuple[str, Optional[Dict]]:
|
| 337 |
"""Main function to parse transcript files."""
|
| 338 |
try:
|
| 339 |
if not file_obj:
|
|
@@ -345,8 +367,8 @@ def parse_transcript(file_obj) -> Tuple[str, Optional[Dict]]:
|
|
| 345 |
# Extract text from file
|
| 346 |
text = extract_text_from_file(file_obj.name, file_ext)
|
| 347 |
|
| 348 |
-
# Use
|
| 349 |
-
parsed_data =
|
| 350 |
|
| 351 |
# Format output text
|
| 352 |
output_text = format_transcript_output(parsed_data)
|
|
@@ -1069,6 +1091,12 @@ def create_interface():
|
|
| 1069 |
background-color: #fff3e0;
|
| 1070 |
color: #e65100;
|
| 1071 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1072 |
"""
|
| 1073 |
|
| 1074 |
gr.Markdown("""
|
|
@@ -1077,13 +1105,21 @@ def create_interface():
|
|
| 1077 |
Complete each step to get customized learning recommendations.
|
| 1078 |
""")
|
| 1079 |
|
| 1080 |
-
# Model
|
| 1081 |
-
|
| 1082 |
-
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
| 1086 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1087 |
with gr.Row():
|
| 1088 |
with gr.Column(scale=1):
|
| 1089 |
step1 = gr.Button("1. Upload Transcript", elem_classes="incomplete-tab")
|
|
@@ -1132,11 +1168,8 @@ def create_interface():
|
|
| 1132 |
)
|
| 1133 |
transcript_data = gr.State()
|
| 1134 |
|
| 1135 |
-
def process_transcript_and_update(file_obj, current_tab_status):
|
| 1136 |
-
|
| 1137 |
-
return "Error: AI model failed to load. Please try again later.", None, current_tab_status, gr.update(), gr.update(), gr.update()
|
| 1138 |
-
|
| 1139 |
-
output_text, data = parse_transcript(file_obj)
|
| 1140 |
if "Error" not in output_text:
|
| 1141 |
new_status = current_tab_status.copy()
|
| 1142 |
new_status[0] = True
|
|
@@ -1454,15 +1487,20 @@ def create_interface():
|
|
| 1454 |
outputs=[tabs, nav_message, quiz_alert]
|
| 1455 |
)
|
| 1456 |
|
| 1457 |
-
#
|
| 1458 |
-
def
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1462 |
|
| 1463 |
-
|
| 1464 |
-
fn=
|
| 1465 |
-
inputs=
|
| 1466 |
outputs=model_status
|
| 1467 |
)
|
| 1468 |
|
|
@@ -1474,4 +1512,4 @@ app = create_interface()
|
|
| 1474 |
# For Hugging Face Spaces deployment
|
| 1475 |
if __name__ == "__main__":
|
| 1476 |
app.launch()
|
| 1477 |
-
|
|
|
|
| 28 |
SESSION_TOKEN_LENGTH = 32
|
| 29 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 30 |
|
| 31 |
+
# Model configuration
|
| 32 |
+
MODEL_CHOICES = {
|
| 33 |
+
"TinyLlama (Fastest)": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
| 34 |
+
"Phi-2 (Balanced)": "microsoft/phi-2",
|
| 35 |
+
"DeepSeek-V3 (Most Powerful)": "deepseek-ai/DeepSeek-V3"
|
| 36 |
+
}
|
| 37 |
+
DEFAULT_MODEL = "TinyLlama (Fastest)"
|
| 38 |
+
|
| 39 |
# Initialize Hugging Face API
|
| 40 |
if HF_TOKEN:
|
| 41 |
hf_api = HfApi(token=HF_TOKEN)
|
| 42 |
HfFolder.save_token(HF_TOKEN)
|
| 43 |
|
| 44 |
+
# ========== OPTIMIZED MODEL LOADING ==========
|
| 45 |
+
class ModelLoader:
|
| 46 |
+
def __init__(self):
|
| 47 |
+
self.model = None
|
| 48 |
+
self.tokenizer = None
|
| 49 |
+
self.loaded = False
|
| 50 |
+
self.loading = False
|
| 51 |
+
self.error = None
|
| 52 |
+
self.current_model = None
|
| 53 |
+
|
| 54 |
+
def load_model(self, model_name, progress=gr.Progress()):
|
| 55 |
+
"""Lazy load the model with progress feedback"""
|
| 56 |
+
if self.loaded and self.current_model == model_name:
|
| 57 |
+
return self.model, self.tokenizer
|
| 58 |
+
|
| 59 |
+
self.loading = True
|
| 60 |
+
self.error = None
|
| 61 |
+
try:
|
| 62 |
+
progress(0, desc=f"Loading {model_name}...")
|
| 63 |
+
|
| 64 |
+
# Clear previous model if any
|
| 65 |
+
if self.model:
|
| 66 |
+
del self.model
|
| 67 |
+
del self.tokenizer
|
| 68 |
+
torch.cuda.empty_cache()
|
| 69 |
+
|
| 70 |
+
# Load tokenizer first
|
| 71 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 72 |
+
MODEL_CHOICES[model_name],
|
| 73 |
+
trust_remote_code=True
|
| 74 |
+
)
|
| 75 |
+
progress(0.3, desc="Loaded tokenizer...")
|
| 76 |
+
|
| 77 |
+
# Load model with appropriate settings
|
| 78 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 79 |
+
MODEL_CHOICES[model_name],
|
| 80 |
+
trust_remote_code=True,
|
| 81 |
+
torch_dtype=torch.float16,
|
| 82 |
+
device_map="auto" if torch.cuda.is_available() else None,
|
| 83 |
+
low_cpu_mem_usage=True
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
progress(0.9, desc="Finalizing...")
|
| 87 |
+
self.loaded = True
|
| 88 |
+
self.current_model = model_name
|
| 89 |
+
return self.model, self.tokenizer
|
| 90 |
+
|
| 91 |
+
except Exception as e:
|
| 92 |
+
self.error = str(e)
|
| 93 |
+
print(f"Error loading model: {self.error}")
|
| 94 |
+
return None, None
|
| 95 |
+
finally:
|
| 96 |
+
self.loading = False
|
| 97 |
|
| 98 |
+
# Initialize model loader
|
| 99 |
+
model_loader = ModelLoader()
|
| 100 |
|
| 101 |
# ========== UTILITY FUNCTIONS ==========
|
| 102 |
def generate_session_token() -> str:
|
|
|
|
| 235 |
return text
|
| 236 |
|
| 237 |
# ========== TRANSCRIPT PARSING ==========
|
| 238 |
+
def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
|
| 239 |
+
"""Use AI model to parse transcript text with progress feedback"""
|
| 240 |
+
model, tokenizer = model_loader.load_model(model_loader.current_model or DEFAULT_MODEL, progress)
|
| 241 |
if model is None or tokenizer is None:
|
| 242 |
+
raise gr.Error(f"Model failed to load. {model_loader.error or 'Please try loading a model first.'}")
|
| 243 |
|
| 244 |
# Pre-process the text
|
| 245 |
+
text = remove_sensitive_info(text[:15000]) # Limit input size
|
| 246 |
|
| 247 |
prompt = f"""
|
| 248 |
Analyze this academic transcript and extract structured information:
|
|
|
|
| 256 |
* Credits earned
|
| 257 |
* Year/semester taken
|
| 258 |
* Grade level when taken
|
| 259 |
+
Return the data in JSON format.
|
| 260 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
Transcript Text:
|
| 262 |
{text}
|
| 263 |
"""
|
| 264 |
|
| 265 |
try:
|
| 266 |
+
progress(0.1, desc="Processing transcript...")
|
| 267 |
+
|
| 268 |
# Tokenize and generate response
|
| 269 |
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 270 |
+
progress(0.4)
|
| 271 |
|
| 272 |
outputs = model.generate(
|
| 273 |
**inputs,
|
| 274 |
+
max_new_tokens=1500, # Reduced from original
|
| 275 |
temperature=0.1,
|
| 276 |
do_sample=True
|
| 277 |
)
|
| 278 |
+
progress(0.8)
|
| 279 |
|
| 280 |
# Decode the response
|
| 281 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 282 |
|
| 283 |
+
# Extract JSON from response
|
| 284 |
+
json_str = response.split('```json')[1].split('```')[0].strip() if '```json' in response else response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
+
# Parse and validate
|
| 287 |
parsed_data = json.loads(json_str)
|
| 288 |
+
progress(1.0)
|
| 289 |
|
| 290 |
return validate_parsed_data(parsed_data)
|
| 291 |
|
| 292 |
except torch.cuda.OutOfMemoryError:
|
| 293 |
+
raise gr.Error("The model ran out of memory. Try with a smaller transcript or use a smaller model.")
|
| 294 |
except Exception as e:
|
| 295 |
raise gr.Error(f"Error processing transcript: {str(e)}")
|
| 296 |
|
|
|
|
| 355 |
|
| 356 |
return '\n'.join(output)
|
| 357 |
|
| 358 |
+
def parse_transcript(file_obj, progress=gr.Progress()) -> Tuple[str, Optional[Dict]]:
|
| 359 |
"""Main function to parse transcript files."""
|
| 360 |
try:
|
| 361 |
if not file_obj:
|
|
|
|
| 367 |
# Extract text from file
|
| 368 |
text = extract_text_from_file(file_obj.name, file_ext)
|
| 369 |
|
| 370 |
+
# Use AI for parsing
|
| 371 |
+
parsed_data = parse_transcript_with_ai(text, progress)
|
| 372 |
|
| 373 |
# Format output text
|
| 374 |
output_text = format_transcript_output(parsed_data)
|
|
|
|
| 1091 |
background-color: #fff3e0;
|
| 1092 |
color: #e65100;
|
| 1093 |
}
|
| 1094 |
+
.model-selection {
|
| 1095 |
+
margin-bottom: 20px;
|
| 1096 |
+
padding: 15px;
|
| 1097 |
+
background: #f8f9fa;
|
| 1098 |
+
border-radius: 8px;
|
| 1099 |
+
}
|
| 1100 |
"""
|
| 1101 |
|
| 1102 |
gr.Markdown("""
|
|
|
|
| 1105 |
Complete each step to get customized learning recommendations.
|
| 1106 |
""")
|
| 1107 |
|
| 1108 |
+
# Model selection section
|
| 1109 |
+
with gr.Group(elem_classes="model-selection"):
|
| 1110 |
+
model_selector = gr.Dropdown(
|
| 1111 |
+
choices=list(MODEL_CHOICES.keys()),
|
| 1112 |
+
value=DEFAULT_MODEL,
|
| 1113 |
+
label="Select AI Model",
|
| 1114 |
+
interactive=True
|
| 1115 |
+
)
|
| 1116 |
+
load_model_btn = gr.Button("Load Selected Model", variant="secondary")
|
| 1117 |
+
model_status = gr.HTML(
|
| 1118 |
+
value="<div class='model-loading'>Model not loaded yet. Please select and load a model.</div>",
|
| 1119 |
+
visible=True
|
| 1120 |
+
)
|
| 1121 |
+
|
| 1122 |
+
# Progress tracker
|
| 1123 |
with gr.Row():
|
| 1124 |
with gr.Column(scale=1):
|
| 1125 |
step1 = gr.Button("1. Upload Transcript", elem_classes="incomplete-tab")
|
|
|
|
| 1168 |
)
|
| 1169 |
transcript_data = gr.State()
|
| 1170 |
|
| 1171 |
+
def process_transcript_and_update(file_obj, current_tab_status, progress=gr.Progress()):
|
| 1172 |
+
output_text, data = parse_transcript(file_obj, progress)
|
|
|
|
|
|
|
|
|
|
| 1173 |
if "Error" not in output_text:
|
| 1174 |
new_status = current_tab_status.copy()
|
| 1175 |
new_status[0] = True
|
|
|
|
| 1487 |
outputs=[tabs, nav_message, quiz_alert]
|
| 1488 |
)
|
| 1489 |
|
| 1490 |
+
# Model loading functions
|
| 1491 |
+
def load_selected_model(model_name, progress=gr.Progress()):
|
| 1492 |
+
try:
|
| 1493 |
+
model_loader.load_model(model_name, progress)
|
| 1494 |
+
if model_loader.loaded:
|
| 1495 |
+
return gr.update(value=f"<div class='alert-box'>{model_name} loaded successfully!</div>", visible=True)
|
| 1496 |
+
else:
|
| 1497 |
+
return gr.update(value=f"<div class='nav-message'>Failed to load model: {model_loader.error}</div>", visible=True)
|
| 1498 |
+
except Exception as e:
|
| 1499 |
+
return gr.update(value=f"<div class='nav-message'>Error: {str(e)}</div>", visible=True)
|
| 1500 |
|
| 1501 |
+
load_model_btn.click(
|
| 1502 |
+
fn=load_selected_model,
|
| 1503 |
+
inputs=model_selector,
|
| 1504 |
outputs=model_status
|
| 1505 |
)
|
| 1506 |
|
|
|
|
| 1512 |
# For Hugging Face Spaces deployment
|
| 1513 |
if __name__ == "__main__":
|
| 1514 |
app.launch()
|
| 1515 |
+
|