itsalissonsilva commited on
Commit
4ab2f7b
·
verified ·
1 Parent(s): 19f79a3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +362 -0
app.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pdfplumber
3
+ import os
4
+ import tempfile
5
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
6
+ from langchain_community.vectorstores import FAISS
7
+ from langchain_community.embeddings import HuggingFaceEmbeddings
8
+ from huggingface_hub import InferenceClient
9
+ from langchain.llms.base import LLM
10
+ from typing import Optional, List, Mapping, Any
11
+ import os
12
+ import json
13
+
14
+
15
+ class QwenLLM(LLM):
16
+ client: InferenceClient = None
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+ self.client = InferenceClient(
21
+ provider="hf-inference",
22
+ api_key="colocar_aqui")
23
+
24
+ @property
25
+ def _llm_type(self) -> str:
26
+ return "qwen-llm"
27
+
28
+ def _call(
29
+ self,
30
+ prompt: str,
31
+ stop: Optional[List[str]] = None,
32
+ run_manager: Optional[Any] = None,
33
+ **kwargs: Any,
34
+ ) -> str:
35
+ modified_prompt = prompt + "<think>\n"
36
+ messages = [{"role": "user", "content": modified_prompt}]
37
+ completion = self.client.chat.completions.create(
38
+ model="Qwen/QwQ-32B",
39
+ messages=messages,
40
+ max_tokens=4096,
41
+ )
42
+ return completion.choices[0].message.content
43
+
44
+
45
+ # Custom chat history implementation
46
+ class ChatHistory:
47
+ def __init__(self):
48
+ self.messages = []
49
+
50
+ def add_user_message(self, message):
51
+ self.messages.append({"role": "user", "content": message})
52
+
53
+ def add_assistant_message(self, message, sources=None):
54
+ self.messages.append({
55
+ "role": "assistant",
56
+ "content": message,
57
+ "sources": sources if sources else []
58
+ })
59
+
60
+ def get_conversation_history(self, include_sources=False):
61
+ if include_sources:
62
+ return self.messages
63
+ else:
64
+ # Return messages without sources for sending to LLM
65
+ return [{"role": m["role"], "content": m["content"]} for m in self.messages]
66
+
67
+ def get_messages_for_display(self):
68
+ return self.messages
69
+
70
+ def clear(self):
71
+ self.messages = []
72
+
73
+
74
+ # Set page configuration
75
+ st.set_page_config(page_title="RAG Chat with Qwen/QwQ-32B",
76
+ page_icon="💬",
77
+ layout="wide")
78
+
79
+ # Initialize session state variables if they don't exist
80
+ if 'vector_store' not in st.session_state:
81
+ st.session_state.vector_store = None
82
+ if 'document_processed' not in st.session_state:
83
+ st.session_state.document_processed = False
84
+ if 'file_name' not in st.session_state:
85
+ st.session_state.file_name = None
86
+ if 'document_text' not in st.session_state:
87
+ st.session_state.document_text = ""
88
+ if 'chat_history' not in st.session_state:
89
+ st.session_state.chat_history = ChatHistory()
90
+
91
+
92
+ # Function to extract text from document (PDF or TXT)
93
+ def extract_text_from_document(document_file):
94
+ file_type = document_file.name.split('.')[-1].lower()
95
+
96
+ if file_type == 'txt':
97
+ # For TXT files, simply read the content
98
+ return document_file.getvalue().decode('utf-8')
99
+
100
+ elif file_type == 'pdf':
101
+ # For PDF files, use pdfplumber
102
+ with tempfile.NamedTemporaryFile(delete=False,
103
+ suffix='.pdf') as tmp_file:
104
+ tmp_file.write(document_file.getvalue())
105
+ tmp_file_path = tmp_file.name
106
+
107
+ text = ""
108
+ try:
109
+ with pdfplumber.open(tmp_file_path) as pdf:
110
+ for page in pdf.pages:
111
+ page_text = page.extract_text()
112
+ if page_text:
113
+ text += page_text + "\n\n"
114
+ except Exception as e:
115
+ st.error(f"Error extracting text from PDF: {e}")
116
+ finally:
117
+ # Clean up the temporary file
118
+ if os.path.exists(tmp_file_path):
119
+ os.remove(tmp_file_path)
120
+
121
+ return text
122
+
123
+ else:
124
+ st.error(f"Unsupported file type: {file_type}")
125
+ return ""
126
+
127
+
128
+ # Function to create chunks from text
129
+ def create_chunks(text):
130
+ text_splitter = RecursiveCharacterTextSplitter(
131
+ chunk_size=500,
132
+ chunk_overlap=50,
133
+ length_function=len,
134
+ )
135
+ chunks = text_splitter.split_text(text)
136
+ return chunks
137
+
138
+
139
+ # Function to create vector store
140
+ def create_vector_store(chunks):
141
+ embeddings = HuggingFaceEmbeddings(
142
+ model_name="sentence-transformers/all-mpnet-base-v2",
143
+ model_kwargs={'device': 'cpu'})
144
+ vector_store = FAISS.from_texts(chunks, embeddings)
145
+ return vector_store
146
+
147
+
148
+ # Function to retrieve relevant document chunks
149
+ def retrieve_relevant_chunks(vector_store, query, k=3):
150
+ if not vector_store:
151
+ return []
152
+ docs = vector_store.similarity_search(query, k=k)
153
+ return docs
154
+
155
+
156
+ # Function to generate response using RAG
157
+ def generate_rag_response(query, chat_history, vector_store):
158
+ # Initialize Qwen LLM
159
+ llm = QwenLLM()
160
+
161
+ # Retrieve relevant chunks
162
+ relevant_docs = retrieve_relevant_chunks(vector_store, query, k=3)
163
+
164
+ if not relevant_docs:
165
+ return "I couldn't find any relevant information in the document to answer your question.", []
166
+
167
+ # Prepare context from relevant documents
168
+ context = "\n\n".join([doc.page_content for doc in relevant_docs])
169
+
170
+ # Prepare conversation history for context
171
+ conversation_history = ""
172
+ for msg in chat_history.get_conversation_history():
173
+ role = "User" if msg["role"] == "user" else "Assistant"
174
+ conversation_history += f"{role}: {msg['content']}\n\n"
175
+
176
+ # Create prompt
177
+ prompt = f"""
178
+ You are a helpful assistant that provides accurate information based only on the given context and conversation history.
179
+
180
+ 1. Use only the context below and the conversation history to answer the question.
181
+ 2. If the answer is not in the context, reply with "I don't have enough information to answer this question."
182
+ 3. Be friendly and helpful.
183
+ 4. Maintain continuity with the conversation history.
184
+
185
+ Conversation History:
186
+ {conversation_history}
187
+
188
+ Context from document:
189
+ {context}
190
+
191
+ User's question: {query}
192
+
193
+ Answer:
194
+ """
195
+
196
+ # Generate response
197
+ response = llm(prompt)
198
+
199
+ return response, relevant_docs
200
+
201
+
202
+ # Function to handle user message and get AI response
203
+ def process_user_message(user_message):
204
+ # Add user message to chat history
205
+ st.session_state.chat_history.add_user_message(user_message)
206
+
207
+ # Generate response
208
+ with st.spinner("Thinking..."):
209
+ response, source_docs = generate_rag_response(
210
+ user_message,
211
+ st.session_state.chat_history,
212
+ st.session_state.vector_store
213
+ )
214
+
215
+ # Format sources for storing with the message
216
+ sources = []
217
+ for i, doc in enumerate(source_docs):
218
+ sources.append({"id": i + 1, "content": doc.page_content})
219
+
220
+ # Add assistant response to chat history
221
+ st.session_state.chat_history.add_assistant_message(response, sources)
222
+
223
+ return response, sources
224
+
225
+
226
+ # Main application UI
227
+ st.title("💬 RAG Chat with Qwen/QwQ-32B")
228
+ st.markdown("""
229
+ Upload a PDF or TXT document and chat about its content. This system uses:
230
+ - Document text extraction
231
+ - Text chunking and embedding
232
+ - Qwen/QwQ-32B for answering questions
233
+ - Memory to maintain conversation context
234
+ """)
235
+
236
+ # Sidebar for PDF upload and model selection
237
+ with st.sidebar:
238
+ st.header("Configuration")
239
+
240
+ uploaded_file = st.file_uploader("Upload a document", type=['pdf', 'txt'])
241
+
242
+ # Button to clear chat history
243
+ if st.button("Clear Chat History"):
244
+ st.session_state.chat_history.clear()
245
+ st.success("Chat history cleared!")
246
+
247
+ st.markdown("**Using Qwen/QwQ-32B model**")
248
+
249
+ st.markdown("---")
250
+ st.markdown("### About")
251
+ st.markdown("""
252
+ This is a RAG Chat system that:
253
+ 1. Processes PDF and TXT documents
254
+ 2. Creates a vector database of document content
255
+ 3. Maintains conversation history
256
+ 4. Retrieves relevant information for user queries
257
+ 5. Generates contextual answers using Qwen/QwQ-32B
258
+ """)
259
+
260
+ # Process the uploaded document (PDF or TXT)
261
+ if uploaded_file is not None:
262
+ # Check if we need to process a new file
263
+ if st.session_state.file_name != uploaded_file.name:
264
+ st.session_state.file_name = uploaded_file.name
265
+ st.session_state.document_processed = False
266
+
267
+ if not st.session_state.document_processed:
268
+ file_type = uploaded_file.name.split('.')[-1].lower()
269
+ with st.spinner(f"Processing {file_type.upper()} file..."):
270
+ # Extract text from document
271
+ text = extract_text_from_document(uploaded_file)
272
+ st.session_state.document_text = text
273
+
274
+ # Create chunks from text
275
+ chunks = create_chunks(text)
276
+
277
+ # Create vector store
278
+ st.session_state.vector_store = create_vector_store(chunks)
279
+
280
+ st.session_state.document_processed = True
281
+ st.success(f"Document processed successfully: {uploaded_file.name}")
282
+
283
+ # Display document summary
284
+ num_chunks = len(chunks)
285
+ avg_chunk_size = sum(len(chunk) for chunk in chunks) / num_chunks if num_chunks > 0 else 0
286
+ st.info(f"Document processed into {num_chunks} chunks with average size of {avg_chunk_size:.0f} characters")
287
+
288
+ # Create two columns for the UI layout
289
+ col1, col2 = st.columns([3, 1])
290
+
291
+ # Left column for chat interface
292
+ with col1:
293
+ st.subheader("Chat")
294
+
295
+ # Display chat messages
296
+ chat_container = st.container()
297
+ with chat_container:
298
+ for message in st.session_state.chat_history.get_messages_for_display():
299
+ with st.chat_message(message["role"]):
300
+ st.markdown(message["content"])
301
+
302
+ # If the message is from the assistant and has sources, display them
303
+ if message["role"] == "assistant" and "sources" in message and message["sources"]:
304
+ with st.expander("View Sources"):
305
+ for source in message["sources"]:
306
+ st.markdown(f"**Source {source['id']}**")
307
+ st.text(source["content"])
308
+
309
+ # Chat input
310
+ if st.session_state.document_processed:
311
+ user_input = st.chat_input("Type your message here...")
312
+ if user_input:
313
+ # Display user message
314
+ with st.chat_message("user"):
315
+ st.markdown(user_input)
316
+
317
+ # Get and display assistant response
318
+ response, sources = process_user_message(user_input)
319
+ with st.chat_message("assistant"):
320
+ st.markdown(response)
321
+
322
+ # Display sources in an expander
323
+ if sources:
324
+ with st.expander("View Sources"):
325
+ for source in sources:
326
+ st.markdown(f"**Source {source['id']}**")
327
+ st.text(source["content"])
328
+ else:
329
+ st.info("Please upload a document to start chatting")
330
+
331
+ # Right column for document info
332
+ with col2:
333
+ if st.session_state.document_processed:
334
+ st.subheader("Document Preview")
335
+ with st.expander("View Document Text", expanded=False):
336
+ st.text_area(
337
+ "Extracted Text",
338
+ st.session_state.document_text[:5000] +
339
+ ("..." if len(st.session_state.document_text) > 5000 else ""),
340
+ height=400)
341
+ else:
342
+ st.info("Upload a PDF or TXT document to get started")
343
+
344
+ # Instructions for users if no document is uploaded
345
+ if not st.session_state.document_processed:
346
+ st.markdown("""
347
+ ## Getting Started
348
+
349
+ 1. **Upload a PDF or TXT document** using the file uploader in the sidebar
350
+ 2. Wait for the document to be processed
351
+ 3. Start chatting with the AI about the document
352
+ 4. The chat remembers the conversation context
353
+ 5. Clear the chat history using the button in the sidebar
354
+
355
+ The system uses Qwen/QwQ-32B model and maintains conversation memory.
356
+ """)
357
+
358
+ # Information about the model
359
+ st.sidebar.info("""
360
+ **Using Hugging Face Inference API**
361
+ This application uses the Qwen/QwQ-32B model via Hugging Face's Inference API for generating responses.
362
+ """)