genaibeauty commited on
Commit
53226ae
Β·
verified Β·
1 Parent(s): 37333ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -297
app.py CHANGED
@@ -1,314 +1,92 @@
1
- import streamlit as st
2
  import os
3
  import tempfile
 
 
 
 
 
 
4
 
5
- # Show initial loading message while importing packages
6
- with st.spinner("Loading dependencies..."):
7
- # Optional imports - handle gracefully if missing
8
- try:
9
- from langchain_community.document_loaders import PyPDFLoader
10
- from langchain.text_splitter import RecursiveCharacterTextSplitter
11
- from langchain_community.embeddings import HuggingFaceEmbeddings
12
- from langchain_community.vectorstores import FAISS
13
- from langchain_community.llms import HuggingFaceEndpoint
14
- from langchain.chains import ConversationalRetrievalChain
15
- from langchain.memory import ConversationBufferMemory
16
- except ImportError as e:
17
- st.error(f"Missing dependency: {str(e)}")
18
- st.info("Please add the following to your requirements.txt:\n```\nlangchain\nlangchain-community\npypdf\nfaiss-cpu\nsentence-transformers\nhuggingface-hub\n```")
19
- st.stop()
20
 
21
- # Set page configuration
22
- st.set_page_config(page_title="RAG PDF Chatbot", page_icon="πŸ“š", layout="wide")
23
 
24
- # Check for Hugging Face token
25
- if "HUGGINGFACE_TOKEN" not in os.environ:
26
- try:
27
- os.environ["HUGGINGFACE_TOKEN"] = st.secrets["HUGGINGFACE_TOKEN"]
28
- except:
29
- st.error("⚠️ Hugging Face API token not found. Please add it to your secrets.")
30
- st.info("Add your token in the Hugging Face Space settings under 'Secrets' tab.")
31
- st.stop()
32
 
33
- # Available LLM models (simple options that work well with free tier)
34
- AVAILABLE_MODELS = [
35
- "google/flan-t5-base",
36
- "google/flan-t5-large",
37
- "distilbert/distilbert-base-uncased"
38
- ]
39
 
40
- # Initialize session state
41
- if 'processed_docs' not in st.session_state:
42
- st.session_state.processed_docs = False
43
- if 'conversation' not in st.session_state:
44
- st.session_state.conversation = None
45
- if 'chat_history' not in st.session_state:
46
- st.session_state.chat_history = []
47
- if 'vector_db' not in st.session_state:
48
- st.session_state.vector_db = None
49
- if 'sources' not in st.session_state:
50
- st.session_state.sources = []
51
 
52
- # Sidebar with instructions and options
53
- with st.sidebar:
54
- st.title("πŸ“š RAG PDF Chatbot")
55
- st.markdown("""
56
- ### Instructions:
57
- 1. Upload your PDF document(s)
58
- 2. Click "Process Documents"
59
- 3. Ask questions about your documents
60
-
61
- **Tech Stack:**
62
- - Hugging Face LLMs
63
- - FAISS Vector Store
64
- - LangChain RAG Pipeline
65
- """)
66
-
67
- # Model selection (only shown after document processing)
68
- if st.session_state.processed_docs:
69
- st.markdown("---")
70
- st.subheader("Model Settings")
71
- selected_model = st.selectbox(
72
- "Select Language Model",
73
- options=AVAILABLE_MODELS,
74
- index=0,
75
- key="model_selection"
76
- )
77
-
78
- temperature = st.slider(
79
- "Temperature",
80
- min_value=0.1,
81
- max_value=1.0,
82
- value=0.5,
83
- step=0.1,
84
- help="Higher values make output more random, lower values more deterministic"
85
- )
86
-
87
- max_tokens = st.slider(
88
- "Max Tokens",
89
- min_value=128,
90
- max_value=1024,
91
- value=256,
92
- step=64,
93
- help="Maximum length of generated response"
94
- )
95
-
96
- # Add a button to refresh the conversation with new settings
97
- if st.button("Update Model Settings"):
98
- with st.spinner("Updating settings..."):
99
- try:
100
- st.session_state.chat_history = [] # Reset chat history
101
- st.experimental_rerun()
102
- except Exception as e:
103
- st.error(f"Error updating settings: {str(e)}")
104
 
105
- # Function to process uploaded PDFs and create vector database
106
- def process_pdfs(uploaded_files):
107
- with st.spinner("Processing documents... This may take a few minutes."):
108
- try:
109
- # Create temporary directory for PDFs
110
- temp_dir = tempfile.mkdtemp()
111
- temp_paths = []
112
-
113
- # Save uploaded files to temp directory
114
- for uploaded_file in uploaded_files:
115
- temp_path = os.path.join(temp_dir, uploaded_file.name)
116
- with open(temp_path, "wb") as f:
117
- f.write(uploaded_file.getbuffer())
118
- temp_paths.append(temp_path)
119
-
120
- # Load and process documents
121
- documents = []
122
- for temp_path in temp_paths:
123
- loader = PyPDFLoader(temp_path)
124
- documents.extend(loader.load())
125
-
126
- # Split documents into chunks
127
- text_splitter = RecursiveCharacterTextSplitter(
128
- chunk_size=1000,
129
- chunk_overlap=200,
130
- length_function=len
131
- )
132
- document_chunks = text_splitter.split_documents(documents)
133
-
134
- # Create embeddings
135
- embeddings = HuggingFaceEmbeddings(
136
- model_name="sentence-transformers/all-mpnet-base-v2", # Use a different model
137
- cache_folder="./embedding_cache" # Cache embeddings
138
- )
139
-
140
- # Create vector store
141
- vectorstore = FAISS.from_documents(document_chunks, embeddings)
142
-
143
- # Save to session state
144
- st.session_state.vector_db = vectorstore
145
- st.session_state.processed_docs = True
146
-
147
- # Initialize LLM with default model
148
- initialize_llm("google/flan-t5-base", 0.5, 256)
149
-
150
- return True
151
-
152
- except ImportError as e:
153
- # Special handling for missing dependencies
154
- missing_package = str(e).split("'")[1] if "'" in str(e) else str(e)
155
- st.error(f"Missing dependency: {missing_package}")
156
- st.info(f"Please install the required package: `pip install {missing_package}`")
157
- return False
158
- except Exception as e:
159
- st.error(f"Error processing documents: {str(e)}")
160
- return False
161
 
162
- # Initialize LLM and chain
163
- def initialize_llm(model_name, temp, max_len):
164
- try:
165
- # Create LLM
166
- llm = HuggingFaceEndpoint(
167
- repo_id=model_name,
168
- huggingfacehub_api_token=os.environ["HUGGINGFACE_TOKEN"],
169
- model_kwargs={
170
- "temperature": temp,
171
- "max_length": max_len
172
- }
173
- )
174
-
175
- # Create memory
176
- memory = ConversationBufferMemory(
177
- memory_key="chat_history",
178
- return_messages=True
179
- )
180
-
181
- # Create chain
182
- chain = ConversationalRetrievalChain.from_llm(
183
- llm=llm,
184
- retriever=st.session_state.vector_db.as_retriever(search_kwargs={"k": 3}),
185
- memory=memory,
186
- return_source_documents=True
187
- )
188
-
189
- st.session_state.conversation = chain
190
- return True
191
- except Exception as e:
192
- st.error(f"Error initializing LLM: {str(e)}")
193
- return False
194
 
195
- # Handle conversation
196
- def handle_conversation(question):
197
- try:
198
- # Get response from model
199
- response = st.session_state.conversation({"question": question})
200
- answer = response["answer"]
201
-
202
- # Store sources
203
- sources = response.get("source_documents", [])
204
- st.session_state.sources = sources
205
-
206
- # Update history
207
- st.session_state.chat_history.append((question, answer))
208
-
209
- return True
210
- except Exception as e:
211
- st.error(f"Error generating response: {str(e)}")
212
- return False
213
 
214
- # Main UI
215
- st.title("πŸ“š Question Answering with PDF Documents")
216
 
217
- # Requirements info
218
- with st.expander("πŸ“‹ Requirements and Setup"):
219
- st.markdown("""
220
- ### Required packages:
221
- ```
222
- langchain==0.0.267
223
- langchain-community==0.0.6
224
- pypdf==3.15.1
225
- sentence-transformers==2.2.2
226
- faiss-cpu==1.7.4
227
- huggingface-hub==0.16.4
228
- ```
229
-
230
- ### Hugging Face Space Setup:
231
- 1. Create a new Space with Streamlit SDK
232
- 2. Add your Hugging Face API token in Settings β†’ Secrets as `HUGGINGFACE_TOKEN`
233
- 3. Upload this code and the requirements.txt file
234
- """)
235
 
236
- # File upload section
237
- uploaded_files = st.file_uploader(
238
- "Upload PDF documents",
239
- type=["pdf"],
240
- accept_multiple_files=True
241
- )
242
 
243
- # Process documents button
244
- if uploaded_files:
245
- if not st.session_state.processed_docs:
246
- if st.button("Process Documents"):
247
- success = process_pdfs(uploaded_files)
248
- if success:
249
- st.success("βœ… Documents processed successfully! You can now ask questions.")
250
- st.balloons()
251
- else:
252
- # Show success message if already processed
253
- st.success("βœ… Documents already processed. Ask questions below.")
254
 
255
- # Chat interface
256
- st.markdown("---")
257
- if st.session_state.processed_docs and st.session_state.conversation is not None:
258
- st.subheader("πŸ’¬ Chat with your Documents")
259
-
260
- # Display chat history
261
- for i, (question, answer) in enumerate(st.session_state.chat_history):
262
- st.markdown(f"**You:** {question}")
263
- st.markdown(f"**Assistant:** {answer}")
264
- st.markdown("---")
265
-
266
- # Question input and buttons
267
- col1, col2, col3 = st.columns([3, 1, 1])
268
-
269
- with col1:
270
- question = st.text_input("Ask a question about your documents:", key="question_input")
271
-
272
- with col2:
273
- submit_button = st.button("Submit")
274
-
275
- with col3:
276
- clear_button = st.button("Clear Chat")
277
- if clear_button:
278
- st.session_state.chat_history = []
279
- st.experimental_rerun()
280
-
281
- # Process question
282
- if question and submit_button:
283
- with st.spinner("Generating answer..."):
284
- # Update model settings if changed
285
- model = st.session_state.get("model_selection", AVAILABLE_MODELS[0])
286
- temp = st.session_state.get("temperature", 0.5)
287
- max_len = st.session_state.get("max_tokens", 256)
288
-
289
- # Reinitialize if needed
290
- initialize_llm(model, temp, max_len)
291
-
292
- # Process question
293
- success = handle_conversation(question)
294
- if success:
295
- # Show sources if available
296
- if st.session_state.sources:
297
- with st.expander("πŸ“„ Source Documents"):
298
- for i, doc in enumerate(st.session_state.sources[:3]):
299
- source_page = doc.metadata.get("page", 0) + 1 # Convert to 1-indexed
300
- st.markdown(f"**Source {i+1} (Page {source_page}):**")
301
- st.text_area(f"Content",
302
- value=doc.page_content[:500] + ("..." if len(doc.page_content) > 500 else ""),
303
- height=150,
304
- key=f"source_{i}")
305
-
306
- # Force refresh to show new messages
307
- st.experimental_rerun()
308
- else:
309
- if not st.session_state.processed_docs:
310
- st.info("πŸ“„ Please upload and process PDF documents to start chatting.")
311
 
312
- # Footer
313
- st.markdown("---")
314
- st.caption("RAG PDF Chatbot powered by LangChain and Hugging Face")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
  import os
3
  import tempfile
4
+ import faiss
5
+ import torch
6
+ from transformers import AutoTokenizer
7
+ from ctransformers import AutoModelForCausalLM
8
+ from sentence_transformers import SentenceTransformer
9
+ from pdfminer.high_level import extract_text
10
 
11
+ # Load Sentence Transformer for Embedding
12
+ embedder = SentenceTransformer("all-MiniLM-L6-v2")
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # Load the LLM (Free & Local Inference)
15
+ llm = AutoModelForCausalLM.from_pretrained("TheBloke/Mistral-7B-Instruct-v0.2-GGUF", model_type="mistral", gpu_layers=0)
16
 
17
+ tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
 
 
 
 
 
 
 
18
 
19
+ # Store context and FAISS index
20
+ doc_chunks = []
21
+ index = None
 
 
 
22
 
23
+ def extract_text_from_pdf(pdf_path):
24
+ return extract_text(pdf_path)
 
 
 
 
 
 
 
 
 
25
 
26
+ def chunk_text(text, chunk_size=500, overlap=50):
27
+ words = text.split()
28
+ chunks = []
29
+ for i in range(0, len(words), chunk_size - overlap):
30
+ chunk = " ".join(words[i:i + chunk_size])
31
+ chunks.append(chunk)
32
+ return chunks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ def create_faiss_index(chunks):
35
+ embeddings = embedder.encode(chunks)
36
+ dim = embeddings.shape[1]
37
+ idx = faiss.IndexFlatL2(dim)
38
+ idx.add(embeddings)
39
+ return idx, embeddings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ def retrieve_relevant_chunks(query, chunks, idx, k=3):
42
+ query_embedding = embedder.encode([query])
43
+ scores, indices = idx.search(query_embedding, k)
44
+ return [chunks[i] for i in indices[0]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ def generate_prompt(query, retrieved_chunks):
47
+ context = "\n\n".join(retrieved_chunks)
48
+ prompt = f"""You are a helpful assistant. Use the following context to answer the user's question.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ Context:
51
+ {context}
52
 
53
+ Question:
54
+ {query}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ Answer:"""
57
+ return prompt
 
 
 
 
58
 
59
+ def llm_answer(prompt):
60
+ response = llm(prompt, max_new_tokens=256, temperature=0.7)
61
+ return response
 
 
 
 
 
 
 
 
62
 
63
+ def process_pdf(file):
64
+ global doc_chunks, index
65
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
66
+ tmp.write(file.read())
67
+ tmp.flush()
68
+ text = extract_text_from_pdf(tmp.name)
69
+ doc_chunks = chunk_text(text)
70
+ index, _ = create_faiss_index(doc_chunks)
71
+ return "βœ… PDF uploaded and indexed. You can start chatting now!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ def chat_with_pdf(user_input):
74
+ if not doc_chunks or not index:
75
+ return "❌ Please upload a PDF first."
76
+ retrieved = retrieve_relevant_chunks(user_input, doc_chunks, index)
77
+ prompt = generate_prompt(user_input, retrieved)
78
+ return llm_answer(prompt)
79
+
80
+ # Gradio Interface
81
+ with gr.Blocks() as demo:
82
+ gr.Markdown("# πŸ€– Chat with your PDF (Free & Local LLM)")
83
+
84
+ with gr.Row():
85
+ pdf_input = gr.File(label="Upload PDF", file_types=[".pdf"])
86
+ upload_button = gr.Button("Process PDF")
87
+
88
+ chatbot = gr.ChatInterface(fn=chat_with_pdf, textbox=gr.Textbox(placeholder="Ask something from the PDF...", lines=2))
89
+
90
+ upload_button.click(fn=process_pdf, inputs=[pdf_input], outputs=[chatbot.textbox])
91
+
92
+ demo.launch()