Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,314 +1,92 @@
|
|
1 |
-
import
|
2 |
import os
|
3 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
-
#
|
6 |
-
|
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 |
-
#
|
22 |
-
|
23 |
|
24 |
-
|
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 |
-
#
|
34 |
-
|
35 |
-
|
36 |
-
"google/flan-t5-large",
|
37 |
-
"distilbert/distilbert-base-uncased"
|
38 |
-
]
|
39 |
|
40 |
-
|
41 |
-
|
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 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
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 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
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 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
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 |
-
|
196 |
-
|
197 |
-
|
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 |
-
|
215 |
-
|
216 |
|
217 |
-
|
218 |
-
|
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 |
-
|
237 |
-
|
238 |
-
"Upload PDF documents",
|
239 |
-
type=["pdf"],
|
240 |
-
accept_multiple_files=True
|
241 |
-
)
|
242 |
|
243 |
-
|
244 |
-
|
245 |
-
|
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 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
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 |
-
|
313 |
-
|
314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|