heerjtdev commited on
Commit
49671b5
·
verified ·
1 Parent(s): 756e34b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -107
app.py CHANGED
@@ -1,169 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import fitz # PyMuPDF
3
  import torch
4
- import numpy as np
 
 
5
  from langchain_text_splitters import RecursiveCharacterTextSplitter
6
  from langchain_community.vectorstores import FAISS
7
  from langchain_core.embeddings import Embeddings
8
 
9
- # --- NEW IMPORTS FOR ONNX ---
10
- from transformers import AutoTokenizer
11
- from optimum.onnxruntime import ORTModelForFeatureExtraction
12
 
13
  # ---------------------------------------------------------
14
- # Custom ONNX Embedding Class for BGE-Large
15
  # ---------------------------------------------------------
16
  class OnnxBgeEmbeddings(Embeddings):
17
  def __init__(self, model_name="BAAI/bge-large-en-v1.5", file_name="model.onnx"):
18
- print(f"🔄 Loading {model_name} with ONNX Runtime...")
19
  self.tokenizer = AutoTokenizer.from_pretrained(model_name)
20
-
21
- # This loads the model and exports it to ONNX format automatically if not already done
22
- self.model = ORTModelForFeatureExtraction.from_pretrained(
23
- model_name,
24
- export=True
25
- )
26
- self.model_name = model_name
27
 
28
  def _process_batch(self, texts):
29
- """Helper to tokenize and run inference via ONNX"""
30
- # Tokenize
31
- inputs = self.tokenizer(
32
- texts,
33
- padding=True,
34
- truncation=True,
35
- max_length=512,
36
- return_tensors="pt"
37
- )
38
-
39
- # Run Inference (ONNX)
40
  with torch.no_grad():
41
  outputs = self.model(**inputs)
42
-
43
- # BGE uses CLS pooling (first token), NOT mean pooling
44
- # outputs.last_hidden_state shape: [batch_size, seq_len, hidden_dim]
45
  embeddings = outputs.last_hidden_state[:, 0]
46
-
47
- # Normalize embeddings (required for Cosine Similarity)
48
  embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
49
-
50
  return embeddings.numpy().tolist()
51
 
52
  def embed_documents(self, texts):
53
- # BGE does NOT need instructions for documents
54
  return self._process_batch(texts)
55
 
56
  def embed_query(self, text):
57
- # BGE REQUIRES this specific instruction for queries to work best
58
- instruction = "Represent this sentence for searching relevant passages: "
59
- return self._process_batch([instruction + text])[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  # ---------------------------------------------------------
62
- # Main Application Logic
63
  # ---------------------------------------------------------
64
  class VectorSystem:
65
  def __init__(self):
66
  self.vector_store = None
67
- # SWITCHED to Custom ONNX Class
68
- self.embeddings = OnnxBgeEmbeddings(model_name="BAAI/bge-large-en-v1.5")
69
  self.all_chunks = []
70
 
71
  def process_file(self, file_obj):
72
- """Extracts text, preserves order, and builds the Vector Index"""
73
- if file_obj is None:
74
- return "No file uploaded."
75
-
76
  try:
77
- # 1. Extract Text
78
  text = ""
79
- file_path = file_obj.name
80
-
81
- if file_path.lower().endswith('.pdf'):
82
- doc = fitz.open(file_path)
83
  for page in doc: text += page.get_text()
84
- elif file_path.lower().endswith('.txt'):
85
- with open(file_path, 'r', encoding='utf-8') as f: text = f.read()
86
  else:
87
- return "❌ Error: Only .pdf and .txt files are supported."
88
-
89
- # 2. Split Text
90
- # Adjusted chunk size slightly for the larger model context, but 800 is still good
91
- text_splitter = RecursiveCharacterTextSplitter(
92
- chunk_size=800,
93
- chunk_overlap=150,
94
- separators=["\n\n", "\n", ".", " ", ""]
95
- )
96
- self.all_chunks = text_splitter.split_text(text)
97
 
98
- if not self.all_chunks:
99
- return "Could not extract text. Is the file empty?"
 
 
100
 
101
- # 3. Build Vector Index
102
  metadatas = [{"id": i} for i in range(len(self.all_chunks))]
103
-
104
- self.vector_store = FAISS.from_texts(
105
- self.all_chunks,
106
- self.embeddings,
107
- metadatas=metadatas
108
- )
109
-
110
- return f"✅ Success! Indexed {len(self.all_chunks)} chunks using BGE-Large (ONNX)."
111
-
112
  except Exception as e:
113
- return f"Error processing file: {str(e)}"
114
 
115
- def retrieve_evidence(self, question, student_answer):
116
- if not self.vector_store:
117
- return "⚠️ Please upload and process a file first."
118
 
119
- if not question:
120
- return "⚠️ Please enter a Question."
121
-
122
- # BGE is very accurate, so we search for top 3
123
  results = self.vector_store.similarity_search_with_score(question, k=3)
124
 
125
- output_text = "### 🔍 Expanded Context Analysis (Powered by BGE-Large ONNX):\n"
 
126
 
 
 
127
  for i, (doc, score) in enumerate(results):
128
- chunk_id = doc.metadata['id']
129
-
130
- prev_chunk = self.all_chunks[chunk_id - 1] if chunk_id > 0 else "(Start of Text)"
131
- next_chunk = self.all_chunks[chunk_id + 1] if chunk_id < len(self.all_chunks) - 1 else "(End of Text)"
132
-
133
- # Note: FAISS returns L2 distance. Lower is better.
134
- # With normalized vectors, L2 = 2 * (1 - CosineSimilarity).
135
-
136
- output_text += f"\n#### 🎯 Match #{i+1} (Score: {score:.4f})\n"
137
- output_text += f"> **Preceding Context:**\n{prev_chunk}\n\n"
138
- output_text += f"> **MATCH:**\n**{doc.page_content}**\n\n"
139
- output_text += f"> **Succeeding Context:**\n{next_chunk}\n"
140
- output_text += "---\n"
141
 
142
- return output_text
143
 
144
- # Initialize System
145
  system = VectorSystem()
146
 
147
- # --- Gradio UI ---
148
- with gr.Blocks(title="EduGenius Context Retriever") as demo:
149
- gr.Markdown("# 🎓 EduGenius: Smart Context Retriever")
150
- gr.Markdown("Upload a Chapter. Powered by **BGE-Large (ONNX Accelerated)** for superior accuracy.")
151
 
152
  with gr.Row():
153
  with gr.Column(scale=1):
154
- pdf_input = gr.File(label="1. Upload File (PDF or TXT)", file_types=[".pdf", ".txt"])
155
- upload_btn = gr.Button("Process File", variant="primary")
156
- upload_status = gr.Textbox(label="Status", interactive=False)
157
 
158
  with gr.Column(scale=2):
159
- question_input = gr.Textbox(label="2. Question", placeholder="e.g., What causes the chemical reaction?")
160
- answer_input = gr.Textbox(label="Student Answer (Optional)", placeholder="e.g., The heat causes it...")
161
- search_btn = gr.Button("Find Context + Neighbors", variant="secondary")
162
 
163
- evidence_output = gr.Markdown(label="Relevant Text Chunks")
 
 
164
 
165
- upload_btn.click(fn=system.process_file, inputs=[pdf_input], outputs=[upload_status])
166
- search_btn.click(fn=system.retrieve_evidence, inputs=[question_input, answer_input], outputs=[evidence_output])
167
 
168
  if __name__ == "__main__":
169
  demo.launch()
 
1
+ # import gradio as gr
2
+ # import fitz # PyMuPDF
3
+ # import torch
4
+ # import numpy as np
5
+ # from langchain_text_splitters import RecursiveCharacterTextSplitter
6
+ # from langchain_community.vectorstores import FAISS
7
+ # from langchain_core.embeddings import Embeddings
8
+
9
+ # # --- NEW IMPORTS FOR ONNX ---
10
+ # from transformers import AutoTokenizer
11
+ # from optimum.onnxruntime import ORTModelForFeatureExtraction
12
+
13
+ # # ---------------------------------------------------------
14
+ # # Custom ONNX Embedding Class for BGE-Large
15
+ # # ---------------------------------------------------------
16
+ # class OnnxBgeEmbeddings(Embeddings):
17
+ # def __init__(self, model_name="BAAI/bge-large-en-v1.5", file_name="model.onnx"):
18
+ # print(f"🔄 Loading {model_name} with ONNX Runtime...")
19
+ # self.tokenizer = AutoTokenizer.from_pretrained(model_name)
20
+
21
+ # # This loads the model and exports it to ONNX format automatically if not already done
22
+ # self.model = ORTModelForFeatureExtraction.from_pretrained(
23
+ # model_name,
24
+ # export=True
25
+ # )
26
+ # self.model_name = model_name
27
+
28
+ # def _process_batch(self, texts):
29
+ # """Helper to tokenize and run inference via ONNX"""
30
+ # # Tokenize
31
+ # inputs = self.tokenizer(
32
+ # texts,
33
+ # padding=True,
34
+ # truncation=True,
35
+ # max_length=512,
36
+ # return_tensors="pt"
37
+ # )
38
+
39
+ # # Run Inference (ONNX)
40
+ # with torch.no_grad():
41
+ # outputs = self.model(**inputs)
42
+
43
+ # # BGE uses CLS pooling (first token), NOT mean pooling
44
+ # # outputs.last_hidden_state shape: [batch_size, seq_len, hidden_dim]
45
+ # embeddings = outputs.last_hidden_state[:, 0]
46
+
47
+ # # Normalize embeddings (required for Cosine Similarity)
48
+ # embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
49
+
50
+ # return embeddings.numpy().tolist()
51
+
52
+ # def embed_documents(self, texts):
53
+ # # BGE does NOT need instructions for documents
54
+ # return self._process_batch(texts)
55
+
56
+ # def embed_query(self, text):
57
+ # # BGE REQUIRES this specific instruction for queries to work best
58
+ # instruction = "Represent this sentence for searching relevant passages: "
59
+ # return self._process_batch([instruction + text])[0]
60
+
61
+ # # ---------------------------------------------------------
62
+ # # Main Application Logic
63
+ # # ---------------------------------------------------------
64
+ # class VectorSystem:
65
+ # def __init__(self):
66
+ # self.vector_store = None
67
+ # # SWITCHED to Custom ONNX Class
68
+ # self.embeddings = OnnxBgeEmbeddings(model_name="BAAI/bge-large-en-v1.5")
69
+ # self.all_chunks = []
70
+
71
+ # def process_file(self, file_obj):
72
+ # """Extracts text, preserves order, and builds the Vector Index"""
73
+ # if file_obj is None:
74
+ # return "No file uploaded."
75
+
76
+ # try:
77
+ # # 1. Extract Text
78
+ # text = ""
79
+ # file_path = file_obj.name
80
+
81
+ # if file_path.lower().endswith('.pdf'):
82
+ # doc = fitz.open(file_path)
83
+ # for page in doc: text += page.get_text()
84
+ # elif file_path.lower().endswith('.txt'):
85
+ # with open(file_path, 'r', encoding='utf-8') as f: text = f.read()
86
+ # else:
87
+ # return "❌ Error: Only .pdf and .txt files are supported."
88
+
89
+ # # 2. Split Text
90
+ # # Adjusted chunk size slightly for the larger model context, but 800 is still good
91
+ # text_splitter = RecursiveCharacterTextSplitter(
92
+ # chunk_size=800,
93
+ # chunk_overlap=150,
94
+ # separators=["\n\n", "\n", ".", " ", ""]
95
+ # )
96
+ # self.all_chunks = text_splitter.split_text(text)
97
+
98
+ # if not self.all_chunks:
99
+ # return "Could not extract text. Is the file empty?"
100
+
101
+ # # 3. Build Vector Index
102
+ # metadatas = [{"id": i} for i in range(len(self.all_chunks))]
103
+
104
+ # self.vector_store = FAISS.from_texts(
105
+ # self.all_chunks,
106
+ # self.embeddings,
107
+ # metadatas=metadatas
108
+ # )
109
+
110
+ # return f"✅ Success! Indexed {len(self.all_chunks)} chunks using BGE-Large (ONNX)."
111
+
112
+ # except Exception as e:
113
+ # return f"Error processing file: {str(e)}"
114
+
115
+ # def retrieve_evidence(self, question, student_answer):
116
+ # if not self.vector_store:
117
+ # return "⚠️ Please upload and process a file first."
118
+
119
+ # if not question:
120
+ # return "⚠️ Please enter a Question."
121
+
122
+ # # BGE is very accurate, so we search for top 3
123
+ # results = self.vector_store.similarity_search_with_score(question, k=3)
124
+
125
+ # output_text = "### 🔍 Expanded Context Analysis (Powered by BGE-Large ONNX):\n"
126
+
127
+ # for i, (doc, score) in enumerate(results):
128
+ # chunk_id = doc.metadata['id']
129
+
130
+ # prev_chunk = self.all_chunks[chunk_id - 1] if chunk_id > 0 else "(Start of Text)"
131
+ # next_chunk = self.all_chunks[chunk_id + 1] if chunk_id < len(self.all_chunks) - 1 else "(End of Text)"
132
+
133
+ # # Note: FAISS returns L2 distance. Lower is better.
134
+ # # With normalized vectors, L2 = 2 * (1 - CosineSimilarity).
135
+
136
+ # output_text += f"\n#### 🎯 Match #{i+1} (Score: {score:.4f})\n"
137
+ # output_text += f"> **Preceding Context:**\n{prev_chunk}\n\n"
138
+ # output_text += f"> **MATCH:**\n**{doc.page_content}**\n\n"
139
+ # output_text += f"> **Succeeding Context:**\n{next_chunk}\n"
140
+ # output_text += "---\n"
141
+
142
+ # return output_text
143
+
144
+ # # Initialize System
145
+ # system = VectorSystem()
146
+
147
+ # # --- Gradio UI ---
148
+ # with gr.Blocks(title="EduGenius Context Retriever") as demo:
149
+ # gr.Markdown("# 🎓 EduGenius: Smart Context Retriever")
150
+ # gr.Markdown("Upload a Chapter. Powered by **BGE-Large (ONNX Accelerated)** for superior accuracy.")
151
+
152
+ # with gr.Row():
153
+ # with gr.Column(scale=1):
154
+ # pdf_input = gr.File(label="1. Upload File (PDF or TXT)", file_types=[".pdf", ".txt"])
155
+ # upload_btn = gr.Button("Process File", variant="primary")
156
+ # upload_status = gr.Textbox(label="Status", interactive=False)
157
+
158
+ # with gr.Column(scale=2):
159
+ # question_input = gr.Textbox(label="2. Question", placeholder="e.g., What causes the chemical reaction?")
160
+ # answer_input = gr.Textbox(label="Student Answer (Optional)", placeholder="e.g., The heat causes it...")
161
+ # search_btn = gr.Button("Find Context + Neighbors", variant="secondary")
162
+
163
+ # evidence_output = gr.Markdown(label="Relevant Text Chunks")
164
+
165
+ # upload_btn.click(fn=system.process_file, inputs=[pdf_input], outputs=[upload_status])
166
+ # search_btn.click(fn=system.retrieve_evidence, inputs=[question_input, answer_input], outputs=[evidence_output])
167
+
168
+ # if __name__ == "__main__":
169
+ # demo.launch()
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
  import gradio as gr
184
  import fitz # PyMuPDF
185
  import torch
186
+ import os
187
+
188
+ # --- LANGCHAIN & RAG IMPORTS ---
189
  from langchain_text_splitters import RecursiveCharacterTextSplitter
190
  from langchain_community.vectorstores import FAISS
191
  from langchain_core.embeddings import Embeddings
192
 
193
+ # --- ONNX & MODEL IMPORTS ---
194
+ from transformers import AutoTokenizer, Pipeline
195
+ from optimum.onnxruntime import ORTModelForFeatureExtraction, ORTModelForCausalLM
196
 
197
  # ---------------------------------------------------------
198
+ # 1. Custom ONNX Embedding Class (BGE-Large)
199
  # ---------------------------------------------------------
200
  class OnnxBgeEmbeddings(Embeddings):
201
  def __init__(self, model_name="BAAI/bge-large-en-v1.5", file_name="model.onnx"):
202
+ print(f"🔄 Loading Embeddings: {model_name}...")
203
  self.tokenizer = AutoTokenizer.from_pretrained(model_name)
204
+ self.model = ORTModelForFeatureExtraction.from_pretrained(model_name, export=True)
 
 
 
 
 
 
205
 
206
  def _process_batch(self, texts):
207
+ inputs = self.tokenizer(texts, padding=True, truncation=True, max_length=512, return_tensors="pt")
 
 
 
 
 
 
 
 
 
 
208
  with torch.no_grad():
209
  outputs = self.model(**inputs)
210
+ # CLS pooling for BGE
 
 
211
  embeddings = outputs.last_hidden_state[:, 0]
 
 
212
  embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
 
213
  return embeddings.numpy().tolist()
214
 
215
  def embed_documents(self, texts):
 
216
  return self._process_batch(texts)
217
 
218
  def embed_query(self, text):
219
+ return self._process_batch(["Represent this sentence for searching relevant passages: " + text])[0]
220
+
221
+ # ---------------------------------------------------------
222
+ # 2. LLM Evaluator Class (Llama-3.2-1B ONNX)
223
+ # ---------------------------------------------------------
224
+ class LLMEvaluator:
225
+ def __init__(self):
226
+ # Using the ONNX Community version of Llama 3.2 1B (Optimized for CPU)
227
+ self.model_id = "onnx-community/Llama-3.2-1B-Instruct"
228
+ print(f"🔄 Loading LLM: {self.model_id}...")
229
+
230
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_id)
231
+
232
+ # Load the ONNX model for text generation
233
+ self.model = ORTModelForCausalLM.from_pretrained(
234
+ self.model_id,
235
+ decoder_file_name="model.onnx", # Standard ONNX filename
236
+ use_cache=True,
237
+ use_io_binding=False # Safer for CPU spaces
238
+ )
239
+
240
+ def evaluate(self, context, question, student_answer):
241
+ # Prompt Engineering for Llama 3
242
+ messages = [
243
+ {"role": "system", "content": "You are a strict but helpful academic grader. You will be given a context, a question, and a student's answer. Your job is to grade the answer based ONLY on the provided context."},
244
+ {"role": "user", "content": f"""
245
+ ### CONTEXT:
246
+ {context}
247
+
248
+ ### QUESTION:
249
+ {question}
250
+
251
+ ### STUDENT ANSWER:
252
+ {student_answer}
253
+
254
+ ### INSTRUCTIONS:
255
+ 1. Determine if the student answer is correct based on the context.
256
+ 2. Give a score out of 10.
257
+ 3. Provide a brief explanation.
258
+ """}
259
+ ]
260
+
261
+ # Format input using the chat template
262
+ input_text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
263
+ inputs = self.tokenizer(input_text, return_tensors="pt")
264
+
265
+ # Generate response
266
+ with torch.no_grad():
267
+ outputs = self.model.generate(
268
+ **inputs,
269
+ max_new_tokens=256,
270
+ temperature=0.3, # Low temp for factual grading
271
+ do_sample=True,
272
+ top_p=0.9
273
+ )
274
+
275
+ # Decode and strip the prompt
276
+ response = self.tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
277
+ return response
278
 
279
  # ---------------------------------------------------------
280
+ # 3. Main Application Logic
281
  # ---------------------------------------------------------
282
  class VectorSystem:
283
  def __init__(self):
284
  self.vector_store = None
285
+ self.embeddings = OnnxBgeEmbeddings()
286
+ self.llm = LLMEvaluator() # Initialize LLM
287
  self.all_chunks = []
288
 
289
  def process_file(self, file_obj):
290
+ if file_obj is None: return "No file uploaded."
 
 
 
291
  try:
 
292
  text = ""
293
+ if file_obj.name.endswith('.pdf'):
294
+ doc = fitz.open(file_obj.name)
 
 
295
  for page in doc: text += page.get_text()
296
+ elif file_obj.name.endswith('.txt'):
297
+ with open(file_obj.name, 'r', encoding='utf-8') as f: text = f.read()
298
  else:
299
+ return "❌ Error: Only .pdf and .txt supported."
 
 
 
 
 
 
 
 
 
300
 
301
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=150)
302
+ self.all_chunks = text_splitter.split_text(text)
303
+
304
+ if not self.all_chunks: return "File empty."
305
 
 
306
  metadatas = [{"id": i} for i in range(len(self.all_chunks))]
307
+ self.vector_store = FAISS.from_texts(self.all_chunks, self.embeddings, metadatas=metadatas)
308
+ return f"✅ Indexed {len(self.all_chunks)} chunks."
 
 
 
 
 
 
 
309
  except Exception as e:
310
+ return f"Error: {str(e)}"
311
 
312
+ def process_query(self, question, student_answer):
313
+ if not self.vector_store: return "⚠️ Please upload a file first.", ""
314
+ if not question: return "⚠️ Enter a question.", ""
315
 
316
+ # 1. Retrieve
 
 
 
317
  results = self.vector_store.similarity_search_with_score(question, k=3)
318
 
319
+ # Prepare context for LLM
320
+ context_text = "\n\n".join([doc.page_content for doc, _ in results])
321
 
322
+ # Prepare Evidence Output for UI
323
+ evidence_display = "### 📚 Retrieved Context:\n"
324
  for i, (doc, score) in enumerate(results):
325
+ evidence_display += f"**Chunk {i+1}** (Score: {score:.4f}):\n> {doc.page_content}\n\n"
326
+
327
+ # 2. Evaluate (if answer provided)
328
+ llm_feedback = "Please enter a student answer to grade."
329
+ if student_answer:
330
+ llm_feedback = self.llm.evaluate(context_text, question, student_answer)
 
 
 
 
 
 
 
331
 
332
+ return evidence_display, llm_feedback
333
 
334
+ # Initialize
335
  system = VectorSystem()
336
 
337
+ # --- GRADIO UI ---
338
+ with gr.Blocks(title="EduGenius AI Grader") as demo:
339
+ gr.Markdown("# 🧠 EduGenius: RAG + LLM Grading")
340
+ gr.Markdown("Powered by **BGE-Large** (Retrieval) and **Llama-3.2-1B** (Evaluation) - All ONNX Optimized.")
341
 
342
  with gr.Row():
343
  with gr.Column(scale=1):
344
+ pdf_input = gr.File(label="1. Upload Chapter (PDF/TXT)")
345
+ upload_btn = gr.Button("Index Content", variant="primary")
346
+ status_msg = gr.Textbox(label="System Status", interactive=False)
347
 
348
  with gr.Column(scale=2):
349
+ q_input = gr.Textbox(label="2. Question")
350
+ a_input = gr.Textbox(label="3. Student Answer")
351
+ run_btn = gr.Button("Retrieve & Grade", variant="secondary")
352
 
353
+ with gr.Row():
354
+ evidence_box = gr.Markdown(label="Context")
355
+ grade_box = gr.Markdown(label="LLM Evaluation")
356
 
357
+ upload_btn.click(system.process_file, inputs=[pdf_input], outputs=[status_msg])
358
+ run_btn.click(system.process_query, inputs=[q_input, a_input], outputs=[evidence_box, grade_box])
359
 
360
  if __name__ == "__main__":
361
  demo.launch()