Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
27 |
-
"""
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
49 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
if ext == '.pdf':
|
51 |
-
loader = PyPDFLoader(
|
52 |
elif ext == '.txt':
|
53 |
-
loader = TextLoader(
|
54 |
-
|
55 |
-
loader = Docx2txtLoader(
|
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':
|
63 |
'type': 'uploaded'
|
64 |
})
|
65 |
return documents
|
66 |
|
67 |
except Exception as e:
|
68 |
-
logger.error(f"Error
|
69 |
raise
|
70 |
|
71 |
-
def
|
72 |
-
|
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 |
-
|
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 |
-
#
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
-
#
|
136 |
-
|
137 |
|
138 |
-
# Initialize
|
139 |
-
tokenizer = AutoTokenizer.from_pretrained(
|
140 |
model = AutoModelForCausalLM.from_pretrained(
|
141 |
-
|
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
|
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 |
-
|
182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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
|
215 |
rag_system = RAGSystem()
|
216 |
|
217 |
-
def
|
218 |
-
"""
|
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.
|
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 |
-
|
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;">📁
|
267 |
""")
|
268 |
file_output = gr.File(
|
269 |
file_count="multiple",
|
270 |
-
label="
|
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="
|
283 |
interactive=False
|
284 |
)
|
285 |
-
gr.HTML("</div>")
|
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 |
-
|
342 |
-
|
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 |
)
|