CamiloVega commited on
Commit
985ad05
·
verified ·
1 Parent(s): cc3a585

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -111
app.py CHANGED
@@ -12,6 +12,7 @@ from langchain.prompts import PromptTemplate
12
  from langchain_community.llms import HuggingFacePipeline
13
  from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
14
  from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
 
15
 
16
  # Configure logging
17
  logging.basicConfig(
@@ -20,129 +21,128 @@ logging.basicConfig(
20
  )
21
  logger = logging.getLogger(__name__)
22
 
 
23
  MODEL_NAME = "meta-llama/Llama-2-7b-chat-hf"
24
  UPLOAD_FOLDER = "uploaded_docs"
 
25
 
26
- class DocumentManager:
27
- """Class to manage document uploads and processing."""
28
 
29
  def __init__(self):
30
  self.upload_folder = UPLOAD_FOLDER
31
  if os.path.exists(self.upload_folder):
32
  shutil.rmtree(self.upload_folder)
33
  os.makedirs(self.upload_folder, exist_ok=True)
 
34
  self.max_files = 5
35
  self.max_file_size = 10 * 1024 * 1024 # 10 MB
36
  self.supported_formats = ['.pdf', '.txt', '.docx']
 
 
 
 
 
37
  self.documents = []
38
 
39
- def validate_file(self, file_path, file_size):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  if file_size > self.max_file_size:
41
  raise ValueError(f"File size exceeds {self.max_file_size // 1024 // 1024}MB limit")
42
 
43
  ext = os.path.splitext(file_path)[1].lower()
44
  if ext not in self.supported_formats:
45
- raise ValueError(f"Unsupported file format. Supported formats: {', '.join(self.supported_formats)}")
46
-
47
- def load_document(self, file_path: str) -> List:
48
- ext = os.path.splitext(file_path)[1].lower()
 
49
  try:
 
 
 
 
 
 
 
 
 
 
 
50
  if ext == '.pdf':
51
- loader = PyPDFLoader(file_path)
52
  elif ext == '.txt':
53
- loader = TextLoader(file_path)
54
- elif ext == '.docx':
55
- loader = Docx2txtLoader(file_path)
56
- else:
57
- raise ValueError(f"Unsupported file format: {ext}")
58
 
59
  documents = loader.load()
60
  for doc in documents:
61
  doc.metadata.update({
62
- 'source': os.path.basename(file_path),
63
  'type': 'uploaded'
64
  })
65
  return documents
66
 
67
  except Exception as e:
68
- logger.error(f"Error loading {file_path}: {str(e)}")
69
  raise
70
 
71
- def process_upload(self, files: List[gr.File]) -> str:
72
- if not files:
73
- return "No files uploaded"
74
-
75
- current_files = len(os.listdir(self.upload_folder))
76
- if current_files + len(files) > self.max_files:
77
- return f"Maximum number of documents ({self.max_files}) exceeded"
78
-
79
- processed_files = []
80
- for file in files:
81
- try:
82
- file_path = file.name
83
- file_size = os.path.getsize(file_path)
84
-
85
- self.validate_file(file_path, file_size)
86
-
87
- # Copy file to upload folder
88
- filename = os.path.basename(file_path)
89
- save_path = os.path.join(self.upload_folder, filename)
90
- shutil.copy2(file_path, save_path)
91
-
92
- docs = self.load_document(save_path)
93
- self.documents.extend(docs)
94
- processed_files.append(filename)
95
-
96
- except Exception as e:
97
- logger.error(f"Error processing {file_path}: {str(e)}")
98
- return f"Error processing {os.path.basename(file_path)}: {str(e)}"
99
-
100
- return f"Successfully processed files: {', '.join(processed_files)}"
101
-
102
- class RAGSystem:
103
- """Main RAG system class."""
104
-
105
- def __init__(self, model_name: str = MODEL_NAME):
106
- self.model_name = model_name
107
- self.document_manager = DocumentManager()
108
- self.embeddings = None
109
- self.vector_store = None
110
- self.qa_chain = None
111
- self.is_initialized = False
112
-
113
- def initialize_system(self, documents: List = None):
114
- """Initialize RAG system with provided documents."""
115
  try:
116
- if not documents:
117
- raise ValueError("No documents provided for initialization")
118
-
119
- # Initialize text splitter
120
  text_splitter = RecursiveCharacterTextSplitter(
121
  chunk_size=500,
122
  chunk_overlap=50,
123
  separators=["\n\n", "\n", ". ", " ", ""]
124
  )
 
125
 
126
- # Process documents
127
- chunks = text_splitter.split_documents(documents)
128
-
129
- # Initialize embeddings
130
- self.embeddings = HuggingFaceEmbeddings(
131
- model_name="intfloat/multilingual-e5-large",
132
- model_kwargs={'device': 'cuda' if torch.cuda.is_available() else 'cpu'}
133
- )
 
 
 
 
 
 
 
 
 
134
 
135
- # Create vector store
136
- self.vector_store = FAISS.from_documents(chunks, self.embeddings)
137
 
138
- # Initialize LLM pipeline
139
- tokenizer = AutoTokenizer.from_pretrained(self.model_name)
140
  model = AutoModelForCausalLM.from_pretrained(
141
- self.model_name,
142
  torch_dtype=torch.float16,
143
  device_map="auto"
144
  )
145
 
 
146
  pipe = pipeline(
147
  "text-generation",
148
  model=model,
@@ -154,7 +154,7 @@ class RAGSystem:
154
 
155
  llm = HuggingFacePipeline(pipeline=pipe)
156
 
157
- # Create prompt template
158
  prompt_template = """
159
  Context: {context}
160
 
@@ -169,7 +169,6 @@ class RAGSystem:
169
  input_variables=["context", "question"]
170
  )
171
 
172
- # Set up QA chain
173
  self.qa_chain = RetrievalQA.from_chain_type(
174
  llm=llm,
175
  chain_type="stuff",
@@ -178,16 +177,44 @@ class RAGSystem:
178
  chain_type_kwargs={"prompt": PROMPT}
179
  )
180
 
181
- self.is_initialized = True
182
- return "System initialized successfully"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
  except Exception as e:
185
- logger.error(f"Error during system initialization: {str(e)}")
186
  return f"Error: {str(e)}"
187
 
188
  def generate_response(self, question: str) -> Dict:
189
  """Generate response for a given question."""
190
- if not self.is_initialized:
191
  return {"error": "System not initialized. Please upload documents first."}
192
 
193
  try:
@@ -211,25 +238,13 @@ class RAGSystem:
211
  logger.error(f"Error generating response: {str(e)}")
212
  return {"error": str(e)}
213
 
214
- # Initialize RAG system
215
  rag_system = RAGSystem()
216
 
217
- def process_file_upload(files):
218
- """Handle file uploads and system initialization."""
219
- try:
220
- upload_result = rag_system.document_manager.process_upload(files)
221
- if "Error" in upload_result or "Maximum" in upload_result:
222
- return upload_result
223
-
224
- init_result = rag_system.initialize_system(rag_system.document_manager.documents)
225
- return f"{upload_result}\n{init_result}"
226
- except Exception as e:
227
- return f"Error: {str(e)}"
228
-
229
- def process_query(message, history):
230
- """Process user query and generate response."""
231
  try:
232
- if not rag_system.is_initialized:
233
  return history + [(message, "Please upload documents first.")]
234
 
235
  response = rag_system.generate_response(message)
@@ -257,17 +272,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
257
  with gr.Row():
258
  # Sidebar for document upload
259
  with gr.Column(scale=1):
260
- # Removed gr.Box() and using styling directly
261
- with gr.Group(
262
- elem_classes="container",
263
- ):
264
  gr.HTML("""
265
  <div style="padding: 1rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; background-color: white;">
266
- <h3 style="margin-top: 0;">📁 Document Upload</h3>
267
  """)
268
  file_output = gr.File(
269
  file_count="multiple",
270
- label="Upload Documents",
271
  elem_id="file-upload"
272
  )
273
  gr.HTML("""
@@ -277,12 +289,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
277
  <p>• Supported: PDF, TXT, DOCX</p>
278
  </div>
279
  """)
280
- upload_button = gr.Button("📤 Upload and Initialize", variant="primary")
281
  system_output = gr.Textbox(
282
- label="System Status",
283
  interactive=False
284
  )
285
- gr.HTML("</div>") # Closing the styled container
286
 
287
  # Main chat area
288
  with gr.Column(scale=3):
@@ -326,7 +337,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
326
  """)
327
 
328
  # Add custom CSS
329
- css = """
330
  .container {
331
  border-radius: 0.5rem;
332
  margin: 0.5rem;
@@ -335,11 +346,10 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
335
  margin-bottom: 1rem;
336
  }
337
  """
338
- demo.css = css
339
 
340
  # Set up event handlers
341
- upload_button.click(
342
- process_file_upload,
343
  inputs=[file_output],
344
  outputs=[system_output]
345
  )
 
12
  from langchain_community.llms import HuggingFacePipeline
13
  from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
14
  from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
15
+ from huggingface_hub import login
16
 
17
  # Configure logging
18
  logging.basicConfig(
 
21
  )
22
  logger = logging.getLogger(__name__)
23
 
24
+ # Constants
25
  MODEL_NAME = "meta-llama/Llama-2-7b-chat-hf"
26
  UPLOAD_FOLDER = "uploaded_docs"
27
+ EMBEDDING_MODEL = "intfloat/multilingual-e5-large"
28
 
29
+ class RAGSystem:
30
+ """Main RAG system class."""
31
 
32
  def __init__(self):
33
  self.upload_folder = UPLOAD_FOLDER
34
  if os.path.exists(self.upload_folder):
35
  shutil.rmtree(self.upload_folder)
36
  os.makedirs(self.upload_folder, exist_ok=True)
37
+
38
  self.max_files = 5
39
  self.max_file_size = 10 * 1024 * 1024 # 10 MB
40
  self.supported_formats = ['.pdf', '.txt', '.docx']
41
+
42
+ # Initialize components
43
+ self.embeddings = None
44
+ self.vector_store = None
45
+ self.qa_chain = None
46
  self.documents = []
47
 
48
+ # Initialize embeddings once
49
+ self.initialize_embeddings()
50
+
51
+ def initialize_embeddings(self):
52
+ """Initialize embedding model."""
53
+ try:
54
+ self.embeddings = HuggingFaceEmbeddings(
55
+ model_name=EMBEDDING_MODEL,
56
+ model_kwargs={'device': 'cuda' if torch.cuda.is_available() else 'cpu'}
57
+ )
58
+ except Exception as e:
59
+ logger.error(f"Error initializing embeddings: {str(e)}")
60
+ raise
61
+
62
+ def validate_file(self, file_path: str, file_size: int) -> bool:
63
+ """Validate uploaded file."""
64
  if file_size > self.max_file_size:
65
  raise ValueError(f"File size exceeds {self.max_file_size // 1024 // 1024}MB limit")
66
 
67
  ext = os.path.splitext(file_path)[1].lower()
68
  if ext not in self.supported_formats:
69
+ raise ValueError(f"Unsupported format. Supported: {', '.join(self.supported_formats)}")
70
+ return True
71
+
72
+ def process_file(self, file: gr.File) -> List:
73
+ """Process a single file and return documents."""
74
  try:
75
+ file_path = file.name
76
+ file_size = os.path.getsize(file_path)
77
+ self.validate_file(file_path, file_size)
78
+
79
+ # Copy file to upload directory
80
+ filename = os.path.basename(file_path)
81
+ save_path = os.path.join(self.upload_folder, filename)
82
+ shutil.copy2(file_path, save_path)
83
+
84
+ # Load documents based on file type
85
+ ext = os.path.splitext(file_path)[1].lower()
86
  if ext == '.pdf':
87
+ loader = PyPDFLoader(save_path)
88
  elif ext == '.txt':
89
+ loader = TextLoader(save_path)
90
+ else: # .docx
91
+ loader = Docx2txtLoader(save_path)
 
 
92
 
93
  documents = loader.load()
94
  for doc in documents:
95
  doc.metadata.update({
96
+ 'source': filename,
97
  'type': 'uploaded'
98
  })
99
  return documents
100
 
101
  except Exception as e:
102
+ logger.error(f"Error processing {file_path}: {str(e)}")
103
  raise
104
 
105
+ def update_vector_store(self, new_documents: List):
106
+ """Update vector store with new documents."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  try:
108
+ # Process documents
 
 
 
109
  text_splitter = RecursiveCharacterTextSplitter(
110
  chunk_size=500,
111
  chunk_overlap=50,
112
  separators=["\n\n", "\n", ". ", " ", ""]
113
  )
114
+ chunks = text_splitter.split_documents(new_documents)
115
 
116
+ # Create or update vector store
117
+ if self.vector_store is None:
118
+ self.vector_store = FAISS.from_documents(chunks, self.embeddings)
119
+ else:
120
+ self.vector_store.add_documents(chunks)
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error updating vector store: {str(e)}")
124
+ raise
125
+
126
+ def initialize_llm(self):
127
+ """Initialize the language model and QA chain."""
128
+ try:
129
+ # Get Hugging Face token
130
+ hf_token = os.environ.get('HUGGINGFACE_TOKEN')
131
+ if not hf_token:
132
+ raise ValueError("Please set HUGGINGFACE_TOKEN environment variable")
133
 
134
+ # Login to Hugging Face
135
+ login(token=hf_token)
136
 
137
+ # Initialize model and tokenizer
138
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
139
  model = AutoModelForCausalLM.from_pretrained(
140
+ MODEL_NAME,
141
  torch_dtype=torch.float16,
142
  device_map="auto"
143
  )
144
 
145
+ # Create pipeline
146
  pipe = pipeline(
147
  "text-generation",
148
  model=model,
 
154
 
155
  llm = HuggingFacePipeline(pipeline=pipe)
156
 
157
+ # Create QA chain
158
  prompt_template = """
159
  Context: {context}
160
 
 
169
  input_variables=["context", "question"]
170
  )
171
 
 
172
  self.qa_chain = RetrievalQA.from_chain_type(
173
  llm=llm,
174
  chain_type="stuff",
 
177
  chain_type_kwargs={"prompt": PROMPT}
178
  )
179
 
180
+ except Exception as e:
181
+ logger.error(f"Error initializing LLM: {str(e)}")
182
+ raise
183
+
184
+ def process_upload(self, files: List[gr.File]) -> str:
185
+ """Process uploaded files and initialize/update the system."""
186
+ if not files:
187
+ return "Please select files to upload."
188
+
189
+ try:
190
+ current_files = len(os.listdir(self.upload_folder))
191
+ if current_files + len(files) > self.max_files:
192
+ return f"Maximum number of documents ({self.max_files}) exceeded"
193
+
194
+ # Process each file
195
+ processed_files = []
196
+ new_documents = []
197
+ for file in files:
198
+ documents = self.process_file(file)
199
+ new_documents.extend(documents)
200
+ processed_files.append(os.path.basename(file.name))
201
+
202
+ # Update vector store with new documents
203
+ self.update_vector_store(new_documents)
204
+ self.documents.extend(new_documents)
205
+
206
+ # Initialize LLM if not already initialized
207
+ if self.qa_chain is None:
208
+ self.initialize_llm()
209
+
210
+ return f"Successfully processed and initialized: {', '.join(processed_files)}"
211
 
212
  except Exception as e:
 
213
  return f"Error: {str(e)}"
214
 
215
  def generate_response(self, question: str) -> Dict:
216
  """Generate response for a given question."""
217
+ if not self.qa_chain:
218
  return {"error": "System not initialized. Please upload documents first."}
219
 
220
  try:
 
238
  logger.error(f"Error generating response: {str(e)}")
239
  return {"error": str(e)}
240
 
241
+ # Initialize system
242
  rag_system = RAGSystem()
243
 
244
+ def process_query(message: str, history: List) -> List:
245
+ """Process user query and return updated history."""
 
 
 
 
 
 
 
 
 
 
 
 
246
  try:
247
+ if not rag_system.qa_chain:
248
  return history + [(message, "Please upload documents first.")]
249
 
250
  response = rag_system.generate_response(message)
 
272
  with gr.Row():
273
  # Sidebar for document upload
274
  with gr.Column(scale=1):
275
+ with gr.Group():
 
 
 
276
  gr.HTML("""
277
  <div style="padding: 1rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; background-color: white;">
278
+ <h3 style="margin-top: 0;">📁 Upload Documents</h3>
279
  """)
280
  file_output = gr.File(
281
  file_count="multiple",
282
+ label="Select Files",
283
  elem_id="file-upload"
284
  )
285
  gr.HTML("""
 
289
  <p>• Supported: PDF, TXT, DOCX</p>
290
  </div>
291
  """)
 
292
  system_output = gr.Textbox(
293
+ label="Status",
294
  interactive=False
295
  )
296
+ gr.HTML("</div>")
297
 
298
  # Main chat area
299
  with gr.Column(scale=3):
 
337
  """)
338
 
339
  # Add custom CSS
340
+ demo.css = """
341
  .container {
342
  border-radius: 0.5rem;
343
  margin: 0.5rem;
 
346
  margin-bottom: 1rem;
347
  }
348
  """
 
349
 
350
  # Set up event handlers
351
+ file_output.upload(
352
+ rag_system.process_upload,
353
  inputs=[file_output],
354
  outputs=[system_output]
355
  )