Harihareshwar3 commited on
Commit
6aa9d46
ยท
verified ยท
1 Parent(s): 9fd79cf

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -0
app.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from dotenv import load_dotenv
4
+ import PyPDF2
5
+ import faiss
6
+
7
+ # LangChain imports
8
+ from langchain_groq import ChatGroq
9
+ from langchain_community.embeddings import HuggingFaceEmbeddings
10
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
11
+ from langchain.schema import Document
12
+ from langchain.prompts import PromptTemplate
13
+ from langchain.chains import RetrievalQA
14
+ from langchain.vectorstores import FAISS
15
+ from langchain.tools import Tool
16
+ from langchain.agents import initialize_agent, AgentType
17
+ from langchain.memory import ConversationBufferWindowMemory
18
+
19
+ # Load environment variables
20
+ load_dotenv()
21
+
22
+ class SmartAcademicAssistant:
23
+ def __init__(self):
24
+ # Initialize Groq LLM
25
+ self.llm = ChatGroq(
26
+ groq_api_key=os.getenv("GROQ_API_KEY"),
27
+ model_name="llama-3.1-8b-instant",
28
+ temperature=0.3,
29
+ max_tokens=1000
30
+ )
31
+
32
+ # Initialize HuggingFace embeddings
33
+ self.embeddings = HuggingFaceEmbeddings(
34
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
35
+ )
36
+
37
+ # Vector store for uploaded documents
38
+ self.vector_store = None
39
+ self.uploaded_docs = []
40
+ self.qa_chain = None
41
+ self.agent = None
42
+
43
+ # Memory for conversation
44
+ self.memory = ConversationBufferWindowMemory(
45
+ memory_key="chat_history",
46
+ k=3, # Keep last 3 exchanges
47
+ return_messages=True
48
+ )
49
+
50
+ # Text splitter for PDFs
51
+ self.text_splitter = RecursiveCharacterTextSplitter(
52
+ chunk_size=1000,
53
+ chunk_overlap=200
54
+ )
55
+
56
+ def extract_text_from_pdf(self, pdf_file) -> str:
57
+ """Extract text from uploaded PDF file"""
58
+ try:
59
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
60
+ text = ""
61
+ for page in pdf_reader.pages:
62
+ text += page.extract_text() + "\n"
63
+ return text
64
+ except Exception as e:
65
+ return f"Error reading PDF: {str(e)}"
66
+
67
+ def process_uploaded_pdfs(self, files) -> str:
68
+ """Process uploaded PDF files and create vector store"""
69
+ if not files:
70
+ return "No files uploaded."
71
+
72
+ all_documents = []
73
+ processed_files = []
74
+
75
+ for file in files:
76
+ if file.name.endswith('.pdf'):
77
+ # Extract text from PDF
78
+ text = self.extract_text_from_pdf(file.name)
79
+ if not text.startswith("Error"):
80
+ # Split text into chunks
81
+ documents = self.text_splitter.create_documents([text],
82
+ metadatas=[{"source": os.path.basename(file.name)}])
83
+ all_documents.extend(documents)
84
+ processed_files.append(file.name)
85
+
86
+ if all_documents:
87
+ # Create FAISS vector store
88
+ self.vector_store = FAISS.from_documents(all_documents, self.embeddings)
89
+
90
+ # Create QA chain with better prompt
91
+ qa_prompt = PromptTemplate(
92
+ template="""You are a helpful academic assistant. Answer the question based on the provided context from uploaded documents.
93
+
94
+ Context: {context}
95
+
96
+ Question: {question}
97
+
98
+ Important:
99
+ - Give a direct, comprehensive answer based on the context
100
+ - If information is not in the context, say so clearly
101
+ - Do not make up information not present in the documents
102
+ - Keep your answer focused and relevant
103
+
104
+ Answer:""",
105
+ input_variables=["context", "question"]
106
+ )
107
+
108
+ # Create retrieval QA chain
109
+ self.qa_chain = RetrievalQA.from_chain_type(
110
+ llm=self.llm,
111
+ chain_type="stuff",
112
+ retriever=self.vector_store.as_retriever(
113
+ search_type="similarity",
114
+ search_kwargs={"k": 4}
115
+ ),
116
+ chain_type_kwargs={"prompt": qa_prompt},
117
+ return_source_documents=False # Prevent confusion
118
+ )
119
+
120
+ # Create tool for agent
121
+ def document_search_tool(query: str) -> str:
122
+ """Search through uploaded PDF documents to answer questions"""
123
+ try:
124
+ result = self.qa_chain.invoke({"query": query})
125
+ return result["result"]
126
+ except Exception as e:
127
+ return f"Error searching documents: {str(e)}"
128
+
129
+ # Define tools with very specific descriptions
130
+ tools = [
131
+ Tool(
132
+ name="document_search",
133
+ func=document_search_tool,
134
+ description="""Use this tool ONLY when the user asks questions about the uploaded PDF documents.
135
+ This tool searches through the uploaded academic papers, textbooks, or documents to find relevant information.
136
+ Input should be the user's question exactly as asked.
137
+ DO NOT use this tool for general knowledge questions."""
138
+ )
139
+ ]
140
+
141
+ # Create agent with strict instructions
142
+ agent_prompt = """You are a smart academic assistant. You have access to uploaded PDF documents through the document_search tool.
143
+
144
+ IMPORTANT RULES:
145
+ 1. If the user asks about content from uploaded PDFs, use the document_search tool EXACTLY ONCE
146
+ 2. For general knowledge questions, answer directly without using tools
147
+ 3. Do NOT call tools multiple times for the same question
148
+ 4. Do NOT use tools for math problems or general knowledge
149
+ 5. Give your final answer immediately after using a tool
150
+
151
+ Available tools:
152
+ - document_search: Use for questions about uploaded PDF content only
153
+
154
+ Let's think step by step and provide helpful answers."""
155
+
156
+ self.agent = initialize_agent(
157
+ tools=tools,
158
+ llm=self.llm,
159
+ agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
160
+ verbose=False, # Reduce verbosity to prevent loops
161
+ handle_parsing_errors=True,
162
+ max_execution_time=15, # Shorter timeout
163
+ max_iterations=3, # Limit iterations to prevent loops
164
+ early_stopping_method="generate",
165
+ memory=self.memory
166
+ )
167
+
168
+ self.uploaded_docs = processed_files
169
+ return f"โœ… Successfully processed {len(processed_files)} PDF(s): {', '.join([os.path.basename(f) for f in processed_files])}\n\nRAG Agent is ready! You can now ask questions about the content!"
170
+ else:
171
+ return "โŒ No valid PDF files found or error processing files."
172
+
173
+ def tutor_mode_cot(self, math_problem: str) -> str:
174
+ """Tutor mode with Chain-of-Thought reasoning for math problems"""
175
+ if not math_problem.strip():
176
+ return "Please enter a math problem."
177
+
178
+ cot_prompt = PromptTemplate(
179
+ input_variables=["problem"],
180
+ template="""You are an expert math tutor. Solve this math problem using Chain-of-Thought reasoning.
181
+
182
+ Problem: {problem}
183
+
184
+ Please solve this step-by-step:
185
+ 1. First, understand what the problem is asking
186
+ 2. Identify the key information and what needs to be found
187
+ 3. Choose the appropriate method or formula
188
+ 4. Show each step of the calculation clearly
189
+ 5. Verify your answer makes sense
190
+ 6. Provide the final answer
191
+
192
+ Step-by-step solution:"""
193
+ )
194
+
195
+ try:
196
+ # Format the prompt
197
+ formatted_prompt = cot_prompt.format(problem=math_problem)
198
+
199
+ # Get response from LLM
200
+ response = self.llm.invoke(formatted_prompt)
201
+ return response.content
202
+ except Exception as e:
203
+ return f"Error in tutor mode: {str(e)}\n\nPlease check your GROQ_API_KEY in the .env file."
204
+
205
+ def assistant_mode_rag(self, question: str) -> str:
206
+ """Agent-based RAG Q&A from uploaded documents"""
207
+ if not question.strip():
208
+ return "Please enter a question."
209
+
210
+ if not self.vector_store or not self.agent:
211
+ return "โš ๏ธ Please upload at least one PDF file first to initialize the RAG agent."
212
+
213
+ try:
214
+ # Clear any previous conversation context that might cause loops
215
+ self.memory.clear()
216
+
217
+ # Use agent to answer question
218
+ result = self.agent.run(question)
219
+
220
+ return result
221
+
222
+ except Exception as e:
223
+ # Fallback to direct QA if agent fails
224
+ try:
225
+ fallback_result = self.qa_chain.invoke({"query": question})
226
+ return f"๐Ÿ”„ Fallback answer:\n\n{fallback_result['result']}"
227
+ except:
228
+ return f"โŒ Error in agent mode: {str(e)}\n\nPlease try rephrasing your question or check your setup."
229
+
230
+ # Initialize the assistant
231
+ assistant = SmartAcademicAssistant()
232
+
233
+ # Create Gradio interface
234
+ def create_interface():
235
+ with gr.Blocks(title="Smart Academic Assistant", theme=gr.themes.Soft()) as demo:
236
+ gr.HTML("<h1 style='text-align: center; color: #2e86c1;'>๐ŸŽ“ Smart Academic Assistant</h1>")
237
+ gr.HTML("<p style='text-align: center;'>Two modes: <b>Tutor</b> for math problems with CoT reasoning, <b>Assistant</b> for Q&A from your documents</p>")
238
+
239
+ with gr.Tabs():
240
+ # Tutor Mode Tab
241
+ with gr.Tab("๐Ÿงฎ Tutor Mode"):
242
+ gr.HTML("<h3>Math Problem Solver with Chain-of-Thought</h3>")
243
+ gr.HTML("<p>Enter any math problem and get step-by-step solution using CoT reasoning.</p>")
244
+
245
+ with gr.Row():
246
+ with gr.Column():
247
+ math_input = gr.Textbox(
248
+ label="Math Problem",
249
+ placeholder="e.g., Solve for x: 2x + 5 = 13\nor\nFind the derivative of f(x) = xยณ + 2xยฒ - x + 1",
250
+ lines=4
251
+ )
252
+ solve_btn = gr.Button("๐Ÿ” Solve Problem", variant="primary")
253
+
254
+ # Example problems
255
+ gr.HTML("<b>Example problems to try:</b>")
256
+ gr.HTML("โ€ข Solve: 3xยฒ - 12x + 9 = 0<br>โ€ข Find integral of sin(2x)dx<br>โ€ข Calculate: (2+3i)(4-i)")
257
+
258
+ with gr.Column():
259
+ math_output = gr.Textbox(
260
+ label="Step-by-Step Solution",
261
+ lines=15,
262
+ max_lines=20
263
+ )
264
+
265
+ solve_btn.click(
266
+ fn=assistant.tutor_mode_cot,
267
+ inputs=[math_input],
268
+ outputs=[math_output]
269
+ )
270
+
271
+ # Assistant Mode Tab
272
+ with gr.Tab("๐Ÿ“š Assistant Mode"):
273
+ gr.HTML("<h3>Document Q&A with Retrieval-Augmented Generation</h3>")
274
+ gr.HTML("<p><b>Step 1:</b> Upload PDF documents, then <b>Step 2:</b> ask questions about them.</p>")
275
+
276
+ with gr.Row():
277
+ with gr.Column():
278
+ # PDF Upload Section
279
+ gr.HTML("<h4>๐Ÿ“ค Upload Documents</h4>")
280
+ pdf_upload = gr.File(
281
+ label="Upload PDF Documents",
282
+ file_types=[".pdf"],
283
+ file_count="multiple"
284
+ )
285
+ upload_status = gr.Textbox(
286
+ label="Upload Status",
287
+ lines=3,
288
+ interactive=False,
289
+ placeholder="Upload status will appear here..."
290
+ )
291
+
292
+ # Question Section
293
+ gr.HTML("<h4>โ“ Ask Questions</h4>")
294
+ question_input = gr.Textbox(
295
+ label="Your Question",
296
+ placeholder="What is the main topic discussed in the document?\nCan you summarize chapter 2?\nWhat are the key findings?",
297
+ lines=4
298
+ )
299
+ ask_btn = gr.Button("๐Ÿ’ฌ Ask Question", variant="primary")
300
+
301
+ with gr.Column():
302
+ answer_output = gr.Textbox(
303
+ label="Answer from Documents",
304
+ lines=15,
305
+ max_lines=25,
306
+ placeholder="Answers will appear here..."
307
+ )
308
+
309
+ # Handle file upload
310
+ pdf_upload.change(
311
+ fn=assistant.process_uploaded_pdfs,
312
+ inputs=[pdf_upload],
313
+ outputs=[upload_status]
314
+ )
315
+
316
+ # Handle question
317
+ ask_btn.click(
318
+ fn=assistant.assistant_mode_rag,
319
+ inputs=[question_input],
320
+ outputs=[answer_output]
321
+ )
322
+
323
+ # Footer with setup instructions
324
+
325
+
326
+
327
+ return demo
328
+
329
+ if __name__ == "__main__":
330
+ # Create .env file template if it doesn't exist
331
+ if not os.path.exists(".env"):
332
+ with open(".env", "w") as f:
333
+ f.write("# Add your Groq API key here\n")
334
+ f.write("GROQ_API_KEY=your_groq_api_key_here\n")
335
+ print("๐Ÿ“ Created .env file. Please add your GROQ_API_KEY.")
336
+
337
+ # Check if API key exists
338
+ if not os.getenv("GROQ_API_KEY"):
339
+ print("โš ๏ธ Warning: GROQ_API_KEY not found. Please add it to your .env file.")
340
+
341
+ # Launch the app
342
+ demo = create_interface()
343
+ demo.launch(debug=True, share=True)