Spaces:
Running
Running
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 |
+
|