Kumaria commited on
Commit
11ceb3f
·
verified ·
1 Parent(s): 0b684ba

Create app.py

Browse files

added app.py file

Files changed (1) hide show
  1. app.py +320 -0
app.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ from dotenv import load_dotenv
4
+ import numpy as np
5
+
6
+ from langchain_community.document_loaders import DirectoryLoader, TextLoader
7
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
8
+ from langchain_community.vectorstores import FAISS
9
+ from langchain.embeddings.base import Embeddings
10
+ from huggingface_hub import InferenceClient
11
+
12
+ # Load environment variables if .env file exists
13
+ load_dotenv()
14
+
15
+ st.set_page_config(page_title="RAG Chatbot", layout="wide")
16
+
17
+
18
+ class HuggingFaceAPIEmbeddings(Embeddings):
19
+ """Custom embeddings class using HuggingFace Hub InferenceClient."""
20
+
21
+ def __init__(self, api_key: str, model_name: str):
22
+ self.client = InferenceClient(token=api_key)
23
+ self.model_name = model_name
24
+
25
+ def embed_documents(self, texts: list[str]) -> list[list[float]]:
26
+ """Embed a list of documents."""
27
+ embeddings = []
28
+ for text in texts:
29
+ try:
30
+ # Use feature_extraction which returns embeddings
31
+ result = self.client.feature_extraction(text, model=self.model_name)
32
+
33
+ # Convert to list if it's a numpy array
34
+ if isinstance(result, np.ndarray):
35
+ embeddings.append(result.tolist())
36
+ else:
37
+ embeddings.append(result)
38
+
39
+ except Exception as e:
40
+ st.error(f"Embedding error for text: {text[:50]}... | Error: {e}")
41
+ raise
42
+
43
+ return embeddings
44
+
45
+ def embed_query(self, text: str) -> list[float]:
46
+ """Embed a single query."""
47
+ return self.embed_documents([text])[0]
48
+
49
+
50
+ st.title("🤖 RAG Chatbot")
51
+
52
+ # Sidebar
53
+ with st.sidebar:
54
+ st.header("Configuration")
55
+ hf_token = st.text_input(
56
+ "HuggingFace Token (free)",
57
+ type="password",
58
+ value=os.getenv("HF_TOKEN", ""),
59
+ help="Get a free token at https://huggingface.co/settings/tokens"
60
+ )
61
+
62
+ # Model selection
63
+ embedding_model = st.selectbox(
64
+ "Embedding Model",
65
+ [
66
+ "sentence-transformers/all-MiniLM-L6-v2",
67
+ "BAAI/bge-small-en-v1.5",
68
+ "sentence-transformers/all-mpnet-base-v2"
69
+ ],
70
+ help="Lightweight models that run on HuggingFace's servers"
71
+ )
72
+
73
+ llm_model = st.selectbox(
74
+ "LLM Model",
75
+ [
76
+ "mistralai/Mistral-7B-Instruct-v0.2",
77
+ "HuggingFaceH4/zephyr-7b-beta",
78
+ "microsoft/Phi-3-mini-4k-instruct",
79
+ "meta-llama/Llama-3.2-3B-Instruct",
80
+ "google/gemma-2-2b-it"
81
+ ],
82
+ help="Language model for generating answers (chat-optimized models)"
83
+ )
84
+
85
+ chunk_size = st.slider("Chunk Size", 500, 2000, 1000, 100)
86
+ num_results = st.slider("Number of Retrieved Documents", 1, 5, 3)
87
+
88
+ st.markdown("### Knowledge Base")
89
+ st.info("Ensure your documents are in the `knowledge_base` folder.")
90
+
91
+ if st.button("🔄 Reload Knowledge Base"):
92
+ st.cache_resource.clear()
93
+ st.rerun()
94
+
95
+ st.markdown("---")
96
+ st.markdown("### 📋 Setup Instructions")
97
+ st.markdown(
98
+ "1. Go to [HuggingFace](https://huggingface.co/settings/tokens)\n"
99
+ "2. Create **Fine-grained** token\n"
100
+ "3. ✅ Enable **'Make calls to Inference Providers'**\n"
101
+ "4. Copy and paste token above"
102
+ )
103
+
104
+ # Initialize session state for chat history
105
+ if "messages" not in st.session_state:
106
+ st.session_state.messages = []
107
+
108
+
109
+ # Function to load and process knowledge base
110
+ @st.cache_resource(show_spinner="Loading Knowledge Base...")
111
+ def load_and_process_data(_hf_token, _embedding_model, _chunk_size):
112
+ """Load documents and create vector store using API-based embeddings."""
113
+
114
+ if not os.path.exists("knowledge_base"):
115
+ os.makedirs("knowledge_base")
116
+ st.error("Created 'knowledge_base' folder. Please add some .txt files and refresh.")
117
+ st.stop()
118
+
119
+ # Load documents
120
+ try:
121
+ loader = DirectoryLoader(
122
+ "knowledge_base",
123
+ glob="**/*.txt",
124
+ loader_cls=TextLoader,
125
+ loader_kwargs={"autodetect_encoding": True}
126
+ )
127
+ documents = loader.load()
128
+ except Exception as e:
129
+ st.error(f"Error loading documents: {e}")
130
+ st.stop()
131
+
132
+ if not documents:
133
+ st.error("No documents found in 'knowledge_base'. Please add .txt files.")
134
+ st.stop()
135
+
136
+ # Split text
137
+ text_splitter = RecursiveCharacterTextSplitter(
138
+ chunk_size=_chunk_size,
139
+ chunk_overlap=200,
140
+ separators=["\n\n", "\n", ". ", " ", ""]
141
+ )
142
+ chunks = text_splitter.split_documents(documents)
143
+
144
+ # Create embeddings using custom class
145
+ embeddings = HuggingFaceAPIEmbeddings(
146
+ api_key=_hf_token,
147
+ model_name=_embedding_model
148
+ )
149
+
150
+ # Test the embeddings first
151
+ try:
152
+ st.info("Testing embedding API connection...")
153
+ test_embedding = embeddings.embed_query("test")
154
+ st.success(f"✅ Embedding API working! Vector size: {len(test_embedding)}")
155
+ except Exception as e:
156
+ st.error(f"❌ Embedding API test failed: {e}")
157
+ st.error(
158
+ "**Please check:**\n"
159
+ "1. Your token has 'Make calls to Inference Providers' enabled\n"
160
+ "2. You're using a 'Fine-grained' or 'Write' token type\n"
161
+ "3. The token is correctly copied (no extra spaces)\n"
162
+ "4. The model is available on HuggingFace"
163
+ )
164
+ st.stop()
165
+
166
+ # Create vector store
167
+ vectorstore = FAISS.from_documents(
168
+ documents=chunks,
169
+ embedding=embeddings
170
+ )
171
+
172
+ return vectorstore, len(documents), len(chunks)
173
+
174
+
175
+ def generate_answer(query: str, context: str, token: str, model: str) -> str:
176
+ """Use HuggingFace Inference API to generate an answer."""
177
+
178
+ client = InferenceClient(token=token)
179
+
180
+ # Build system message and user message for chat completion
181
+ system_message = "You are a helpful AI assistant. Answer questions based ONLY on the provided context. If the answer is not in the context, say 'I cannot find this information in the provided documents'."
182
+
183
+ user_message = f"Context:\n{context}\n\nQuestion: {query}"
184
+
185
+ try:
186
+ # Use chat_completion which works with most modern models
187
+ messages = [
188
+ {"role": "system", "content": system_message},
189
+ {"role": "user", "content": user_message}
190
+ ]
191
+
192
+ response = client.chat_completion(
193
+ messages=messages,
194
+ model=model,
195
+ max_tokens=512,
196
+ temperature=0.2,
197
+ top_p=0.9,
198
+ )
199
+
200
+ # Extract the response text
201
+ if hasattr(response, 'choices') and len(response.choices) > 0:
202
+ answer = response.choices[0].message.content.strip()
203
+ return answer if answer else "⚠️ Model returned empty response"
204
+ else:
205
+ return "⚠️ Unexpected response format"
206
+
207
+ except Exception as e:
208
+ error_msg = str(e).lower()
209
+
210
+ if "503" in error_msg or "loading" in error_msg:
211
+ return "⚠️ Model is currently loading. Please wait 20-30 seconds and try again."
212
+ elif "401" in error_msg or "unauthorized" in error_msg:
213
+ return "⚠️ Authentication failed. Please check your HuggingFace token."
214
+ elif "403" in error_msg or "forbidden" in error_msg:
215
+ return "⚠️ Access forbidden. Make sure 'Make calls to Inference Providers' is enabled."
216
+ elif "timeout" in error_msg:
217
+ return "⚠️ Request timed out. Please try again."
218
+ elif "not supported" in error_msg:
219
+ return f"⚠️ This model doesn't support chat completion. Try selecting a different model from the sidebar."
220
+ else:
221
+ return f"⚠️ Error: {str(e)}"
222
+
223
+
224
+ # Main Application Logic
225
+ if not hf_token:
226
+ st.warning("⚠️ Please enter your HuggingFace token in the sidebar.")
227
+ st.info(
228
+ "### 🔑 How to Get Your Token:\n\n"
229
+ "1. Visit [HuggingFace Settings](https://huggingface.co/settings/tokens)\n"
230
+ "2. Click **'Create new token'**\n"
231
+ "3. Select **'Fine-grained'** token type\n"
232
+ "4. ✅ Check **'Make calls to Inference Providers'**\n"
233
+ "5. Create and copy your token\n"
234
+ "6. Paste it in the sidebar ⬅️"
235
+ )
236
+ st.stop()
237
+
238
+ try:
239
+ # Load knowledge base
240
+ vector_store, num_docs, num_chunks = load_and_process_data(
241
+ hf_token,
242
+ embedding_model,
243
+ chunk_size
244
+ )
245
+ retriever = vector_store.as_retriever(search_kwargs={"k": num_results})
246
+
247
+ # Show knowledge base stats
248
+ st.success(f"✅ Knowledge base loaded: {num_docs} documents, {num_chunks} chunks")
249
+
250
+ # Display Chat History
251
+ for message in st.session_state.messages:
252
+ with st.chat_message(message["role"]):
253
+ st.markdown(message["content"])
254
+
255
+ # User Input
256
+ if user_input := st.chat_input("Ask something about your knowledge base..."):
257
+ st.session_state.messages.append({"role": "user", "content": user_input})
258
+ with st.chat_message("user"):
259
+ st.markdown(user_input)
260
+
261
+ with st.chat_message("assistant"):
262
+ with st.spinner("Searching knowledge base..."):
263
+ relevant_docs = retriever.invoke(user_input)
264
+
265
+ if relevant_docs:
266
+ context = "\n\n".join(
267
+ [f"Document {i+1}:\n{doc.page_content}" for i, doc in enumerate(relevant_docs)]
268
+ )
269
+
270
+ with st.spinner("Generating answer..."):
271
+ response = generate_answer(user_input, context, hf_token, llm_model)
272
+
273
+ st.markdown(response)
274
+
275
+ with st.expander("📄 View Source Documents"):
276
+ for i, doc in enumerate(relevant_docs):
277
+ source_file = doc.metadata.get('source', 'Unknown')
278
+ st.markdown(f"**Document {i+1}** (from `{os.path.basename(source_file)}`):")
279
+ st.text(doc.page_content)
280
+ st.markdown("---")
281
+ else:
282
+ response = "❌ No relevant documents found."
283
+ st.markdown(response)
284
+
285
+ st.session_state.messages.append({"role": "assistant", "content": response})
286
+
287
+ except Exception as e:
288
+ st.error(f"❌ Error: {e}")
289
+
290
+ error_str = str(e).lower()
291
+
292
+ if "403" in error_str or "forbidden" in error_str:
293
+ st.error(
294
+ "### 🔑 Token Permission Issue\n\n"
295
+ "This error usually means your token doesn't have the right permissions.\n\n"
296
+ "**Fix:**\n"
297
+ "1. Go to https://huggingface.co/settings/tokens\n"
298
+ "2. **Delete** your old token\n"
299
+ "3. Create a **NEW** token:\n"
300
+ " - Type: **Fine-grained**\n"
301
+ " - ✅ Check **'Make calls to Inference Providers'**\n"
302
+ "4. Copy the NEW token\n"
303
+ "5. Paste it in the sidebar and refresh"
304
+ )
305
+ elif "410" in error_str or "gone" in error_str:
306
+ st.error(
307
+ "### ⚠️ API Endpoint Issue\n\n"
308
+ "The API endpoint has changed or the model is no longer available.\n\n"
309
+ "**Try:**\n"
310
+ "1. Select a different embedding model from the sidebar\n"
311
+ "2. Make sure you have the latest version: `pip install --upgrade huggingface_hub`\n"
312
+ "3. Check if the model exists on HuggingFace"
313
+ )
314
+
315
+ with st.expander("🐛 Full Error Details"):
316
+ st.exception(e)
317
+
318
+ # Footer
319
+ st.markdown("---")
320
+ st.caption("💡 All processing via HuggingFace API - no local model downloads!")