towardsinnovationlab commited on
Commit
bf3a400
·
verified ·
1 Parent(s): f202de5

Delete advanced_app.py

Browse files
Files changed (1) hide show
  1. advanced_app.py +0 -471
advanced_app.py DELETED
@@ -1,471 +0,0 @@
1
- # ==============================================================================
2
- # ADVANCED RAG WITH GPT-4o, LANGCHAIN, AND RAGAS EVALUATION - MULTI-DOCUMENT VERSION
3
- # ==============================================================================
4
- # Enhanced RAG application with quality metrics using RAGAS framework
5
- # Supports multiple PDF documents
6
- # ==============================================================================
7
-
8
- from langchain.retrievers import EnsembleRetriever
9
- from langchain_community.retrievers import BM25Retriever
10
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
11
- from langchain.retrievers.document_compressors import CrossEncoderReranker
12
- from sentence_transformers import CrossEncoder
13
- from langchain.retrievers import ContextualCompressionRetriever
14
- from langchain_community.document_loaders import PyPDFLoader
15
- from langchain.text_splitter import RecursiveCharacterTextSplitter
16
- from langchain_openai import OpenAIEmbeddings, ChatOpenAI
17
- from langchain_community.vectorstores import FAISS
18
- from langchain.schema import Document
19
- from langchain.prompts import PromptTemplate
20
- from langchain_core.output_parsers import StrOutputParser
21
- from langchain_core.runnables import RunnablePassthrough
22
- from datasets import Dataset
23
- from ragas import evaluate
24
- from ragas.metrics import (
25
- faithfulness,
26
- answer_relevancy,
27
- context_precision,
28
- context_recall,
29
- answer_correctness,
30
- answer_similarity
31
- )
32
- import gradio as gr
33
- import os
34
- import pandas as pd
35
- import json
36
-
37
- # ==============================================================================
38
- # GLOBAL VARIABLES
39
- # ==============================================================================
40
- rag_chain = None
41
- current_documents = [] # Changed to list for multiple documents
42
- openai_api_key = None
43
- retriever = None
44
- evaluation_data = []
45
-
46
- # ==============================================================================
47
- # HELPER FUNCTIONS
48
- # ==============================================================================
49
-
50
- def format_docs(docs):
51
- """Format retrieved documents with source citations"""
52
- out = []
53
- for d in docs:
54
- src = d.metadata.get("source", "unknown")
55
- # Extract just the filename from the full path
56
- src = os.path.basename(src)
57
- page = d.metadata.get("page", d.metadata.get("page_number", "?"))
58
-
59
- try:
60
- page_display = int(page) + 1
61
- except (ValueError, TypeError):
62
- page_display = page
63
-
64
- out.append(f"[{src}:{page_display}] {d.page_content}")
65
- return "\n\n".join(out)
66
-
67
-
68
- def validate_api_key(api_key):
69
- """Validate that API key is provided"""
70
- if not api_key or not api_key.strip():
71
- return False
72
- return True
73
-
74
-
75
- def process_documents(pdf_files, api_key):
76
- """Process uploaded PDFs and create RAG chain"""
77
- global rag_chain, current_documents, openai_api_key, retriever, evaluation_data
78
-
79
- chatbot_clear = None
80
- evaluation_data = [] # Reset evaluation data
81
-
82
- if not validate_api_key(api_key):
83
- return "⚠️ Please provide a valid OpenAI API key.", chatbot_clear, ""
84
-
85
- if pdf_files is None or len(pdf_files) == 0:
86
- return "⚠️ Please upload at least one PDF file.", chatbot_clear, ""
87
-
88
- try:
89
- openai_api_key = api_key.strip()
90
- os.environ["OPENAI_API_KEY"] = openai_api_key
91
-
92
- # Process all uploaded PDFs
93
- all_docs = []
94
- current_documents = []
95
- total_pages = 0
96
-
97
- for pdf_file in pdf_files:
98
- loader = PyPDFLoader(pdf_file.name)
99
- docs = loader.load()
100
- all_docs.extend(docs)
101
- current_documents.append(os.path.basename(pdf_file.name))
102
- total_pages += len(docs)
103
-
104
- # Split all documents
105
- splitter = RecursiveCharacterTextSplitter(
106
- separators=["\n\n", "\n", ". ", " ", ""],
107
- chunk_size=1000,
108
- chunk_overlap=100
109
- )
110
- chunked_docs = splitter.split_documents(all_docs)
111
-
112
- # Create embeddings and vector store
113
- embeddings = OpenAIEmbeddings(
114
- model="text-embedding-3-small",
115
- openai_api_key=openai_api_key
116
- )
117
-
118
- db = FAISS.from_documents(chunked_docs, embeddings)
119
-
120
- retriever_1 = db.as_retriever(search_type="similarity",search_kwargs={'k': 10})
121
-
122
- retriever_2 = BM25Retriever.from_documents(chunked_docs, search_kwargs={"k": 10})
123
-
124
- ensemble_retriever = EnsembleRetriever(retrievers=[retriever_1, retriever_2], weights=[0.7, 0.3])
125
-
126
- cross_encoder_model = HuggingFaceCrossEncoder(model_name="cross-encoder/ms-marco-MiniLM-L-12-v2")
127
-
128
- reranker = CrossEncoderReranker(model=cross_encoder_model,top_n=10)
129
-
130
- reranking_retriever = ContextualCompressionRetriever(base_compressor=reranker,base_retriever=ensemble_retriever)
131
-
132
- retriever=reranking_retriever
133
-
134
- # Create LLM and prompt
135
- llm = ChatOpenAI(
136
- model="gpt-4o-mini",
137
- temperature=0.2,
138
- openai_api_key=openai_api_key
139
- )
140
-
141
- prompt_template = """You are a professional research scientist involved in document data analysis.
142
- Use the following context to answer the question using information provided by the documents.
143
- Answer using ONLY these passages. Cite sources as [filename:page] after each claim.
144
- Provide an answer in bullet points.
145
- If you can't find it, say you don't know.
146
-
147
- Question:
148
- {question}
149
-
150
- Passages:
151
- {context}
152
-
153
- Answer:"""
154
-
155
- prompt = PromptTemplate(
156
- input_variables=["context", "question"],
157
- template=prompt_template,
158
- )
159
-
160
- llm_chain = prompt | llm | StrOutputParser()
161
-
162
- rag_chain = (
163
- {"context": reranking_retriever | format_docs, "question": RunnablePassthrough()}
164
- | llm_chain
165
- )
166
-
167
- # Create status message with document list
168
- doc_list = "\n".join([f" • {doc}" for doc in current_documents])
169
- status_msg = (
170
- f"✅ Documents processed successfully!\n\n"
171
- f"📄 **Documents loaded ({len(current_documents)}):**\n{doc_list}\n\n"
172
- f"📊 Total pages: {total_pages}\n"
173
- f"📦 Chunks created: {len(chunked_docs)}\n\n"
174
- f"You can now ask questions and evaluate responses!"
175
- )
176
-
177
- return status_msg, chatbot_clear, ""
178
-
179
- except Exception as e:
180
- return f"❌ Error processing documents: {str(e)}", chatbot_clear, ""
181
-
182
-
183
- def chat_with_document(message, history):
184
- """Handle chat interactions with the documents"""
185
- global rag_chain, current_documents, retriever, evaluation_data
186
-
187
- history.append({"role": "user", "content": message})
188
-
189
- if rag_chain is None:
190
- history.append({
191
- "role": "assistant",
192
- "content": "⚠️ Please upload and process PDF documents first."
193
- })
194
- return history
195
-
196
- if not message.strip():
197
- history.append({
198
- "role": "assistant",
199
- "content": "⚠️ Please enter a question."
200
- })
201
- return history
202
-
203
- try:
204
- # Retrieve contexts for RAGAS evaluation
205
- retrieved_docs = retriever.invoke(message)
206
- contexts = [doc.page_content for doc in retrieved_docs]
207
-
208
- # Get response from RAG chain
209
- response = rag_chain.invoke(message)
210
-
211
- if isinstance(response, dict):
212
- res_text = response.get("answer", response.get("result", str(response)))
213
- else:
214
- res_text = str(response)
215
-
216
- # Store data for RAGAS evaluation
217
- evaluation_data.append({
218
- "question": message,
219
- "answer": res_text,
220
- "contexts": contexts
221
- })
222
-
223
- history.append({"role": "assistant", "content": res_text})
224
- return history
225
-
226
- except Exception as e:
227
- error_msg = f"❌ Error generating response: {str(e)}"
228
- history.append({"role": "assistant", "content": error_msg})
229
- return history
230
-
231
-
232
- def evaluate_rag_performance():
233
- """Evaluate RAG performance using RAGAS metrics"""
234
- global evaluation_data, openai_api_key
235
-
236
- if not evaluation_data:
237
- return "⚠️ No evaluation data available. Please ask some questions first."
238
-
239
- try:
240
- # Prepare dataset for RAGAS
241
- dataset_dict = {
242
- "question": [item["question"] for item in evaluation_data],
243
- "answer": [item["answer"] for item in evaluation_data],
244
- "contexts": [item["contexts"] for item in evaluation_data],
245
- }
246
-
247
- dataset = Dataset.from_dict(dataset_dict)
248
-
249
- # Run RAGAS evaluation
250
- # Using only metrics that don't require ground truth (reference answers)
251
- result = evaluate(
252
- dataset,
253
- metrics=[
254
- faithfulness,
255
- answer_relevancy,
256
- ],
257
- llm=ChatOpenAI(model="gpt-4o", openai_api_key=openai_api_key),
258
- embeddings=OpenAIEmbeddings(openai_api_key=openai_api_key),
259
- )
260
-
261
- # Convert to DataFrame for better display
262
- df = result.to_pandas()
263
-
264
- # Calculate average scores from the result directly
265
- metrics_summary = "## 📊 RAGAS Evaluation Results\n\n"
266
- metrics_summary += "### Average Scores:\n"
267
-
268
- # Get metric scores safely
269
- metric_cols = ['faithfulness', 'answer_relevancy']
270
- metric_scores = {}
271
-
272
- for col in metric_cols:
273
- if col in df.columns:
274
- # Convert to numeric, handling any non-numeric values
275
- numeric_values = pd.to_numeric(df[col], errors='coerce')
276
- avg_score = numeric_values.mean()
277
- if not pd.isna(avg_score):
278
- metric_scores[col] = avg_score
279
- metrics_summary += f"- **{col.replace('_', ' ').title()}**: {avg_score:.4f}\n"
280
-
281
- metrics_summary += "\n### Metric Explanations:\n"
282
- metrics_summary += "- **Faithfulness** (0-1): Measures if the answer is factually consistent with the retrieved context. Higher scores mean the answer doesn't hallucinate or contradict the source.\n"
283
- metrics_summary += "- **Answer Relevancy** (0-1): Measures how relevant the answer is to the question asked. Higher scores mean better alignment with the user's query.\n"
284
-
285
-
286
- metrics_summary += "\n### Interpretation Guide:\n"
287
- metrics_summary += "- **0.9 - 1.0**: Excellent performance\n"
288
- metrics_summary += "- **0.7 - 0.9**: Good performance\n"
289
- metrics_summary += "- **0.5 - 0.7**: Moderate performance (needs improvement)\n"
290
- metrics_summary += "- **< 0.5**: Poor performance (requires significant optimization)\n"
291
-
292
- metrics_summary += f"\n### Total Questions Evaluated: {len(evaluation_data)}\n"
293
-
294
- # Add document info
295
- if current_documents:
296
- metrics_summary += f"\n### Documents in Index: {len(current_documents)}\n"
297
-
298
- return metrics_summary
299
-
300
- except Exception as e:
301
- return f"❌ Error during evaluation: {str(e)}"
302
-
303
-
304
- def export_evaluation_data():
305
- """Export evaluation data as JSON"""
306
- global evaluation_data, current_documents
307
-
308
- if not evaluation_data:
309
- return None
310
-
311
- try:
312
- # Create a temporary file with metadata
313
- output_data = {
314
- "documents": current_documents,
315
- "evaluation_data": evaluation_data,
316
- "total_questions": len(evaluation_data)
317
- }
318
-
319
- output_path = "ragas_evaluation_data.json"
320
- with open(output_path, 'w') as f:
321
- json.dump(output_data, f, indent=2)
322
- return output_path
323
- except Exception as e:
324
- print(f"Error exporting data: {str(e)}")
325
- return None
326
-
327
-
328
- def clear_chat():
329
- """Clear the chat history and evaluation data"""
330
- global evaluation_data
331
- evaluation_data = [] # Reset evaluation data when clearing chat
332
- return [], "" # Return empty chatbot and empty eval_summary
333
-
334
-
335
-
336
- # ==============================================================================
337
- # GRADIO INTERFACE
338
- # ==============================================================================
339
-
340
- with gr.Blocks(title="RAG with RAGAS Evaluation", theme=gr.themes.Soft()) as demo:
341
-
342
- gr.Markdown(
343
- """
344
- # 📚 Multi-Document Q&A Analysis
345
- ### Advanced RAG System Powered by OpenAI GPT models, LangChain & RAGAS
346
-
347
- Upload multiple PDFs, ask questions across all documents, and evaluate your RAG system's performance with industry-standard metrics.
348
- """
349
- )
350
-
351
- with gr.Row():
352
- with gr.Column(scale=1):
353
- gr.Markdown(
354
- """
355
- ### 📋 How to Use
356
- 1. Enter your OpenAI API key
357
- 2. Upload one or more PDF documents
358
- 3. Process the documents
359
- 4. Ask questions in the chat
360
- 5. Click "Evaluate" to see performance metrics
361
-
362
- ---
363
-
364
- 💡 **RAGAS Metrics**:
365
- - Faithfulness: Factual accuracy
366
- - Answer Relevancy: Question alignment
367
-
368
- 📁 **Multi-Document Support**:
369
- - Upload multiple PDFs at once
370
- - Search across all documents
371
- - Get citations with document names
372
- """
373
- )
374
-
375
- gr.Markdown("### 🔑 API Configuration")
376
- api_key_input = gr.Textbox(
377
- label="OpenAI API Key",
378
- type="password",
379
- placeholder="sk-...",
380
- info="Required for GPT models and RAGAS evaluation"
381
- )
382
-
383
- gr.Markdown("### 📤 Upload Documents")
384
- pdf_input = gr.File(
385
- label="Upload PDF Documents",
386
- file_types=[".pdf"],
387
- type="filepath",
388
- file_count="multiple" # Enable multiple file upload
389
- )
390
- process_btn = gr.Button("📄 Process Documents", variant="primary", size="lg")
391
-
392
- status_output = gr.Textbox(
393
- label="Status",
394
- lines=8, # Increased to show multiple documents
395
- interactive=False,
396
- placeholder="Enter API key, upload PDFs, and click 'Process Documents'..."
397
- )
398
-
399
- gr.Markdown("### 📈 Evaluation")
400
- evaluate_btn = gr.Button("🔍 Evaluate RAG Performance", variant="secondary", size="lg")
401
- export_btn = gr.Button("💾 Export Evaluation Data", size="sm")
402
- export_file = gr.File(label="Download Evaluation Data", visible=True)
403
-
404
- with gr.Column(scale=2):
405
- gr.Markdown("### 💬 Chat with Your Documents")
406
- chatbot = gr.Chatbot(
407
- height=400,
408
- placeholder="Upload and process documents to start...",
409
- show_label=False,
410
- type="messages"
411
- )
412
-
413
- msg = gr.Textbox(
414
- label="Enter your question",
415
- placeholder="Type your question here (searches across all uploaded documents)...",
416
- lines=2
417
- )
418
-
419
- with gr.Row():
420
- submit_btn = gr.Button("📤 Send", variant="primary", scale=4)
421
- clear_btn = gr.Button("🗑️ Clear Chat", scale=1)
422
-
423
- gr.Markdown("### 📊 Evaluation Results")
424
- eval_summary = gr.Markdown(value="")
425
-
426
- # Event handlers
427
- process_btn.click(
428
- fn=process_documents, # Changed function name
429
- inputs=[pdf_input, api_key_input],
430
- outputs=[status_output, chatbot, eval_summary]
431
- )
432
-
433
- submit_btn.click(
434
- fn=chat_with_document,
435
- inputs=[msg, chatbot],
436
- outputs=[chatbot]
437
- ).then(
438
- lambda: "",
439
- outputs=[msg]
440
- )
441
-
442
- msg.submit(
443
- fn=chat_with_document,
444
- inputs=[msg, chatbot],
445
- outputs=[chatbot]
446
- ).then(
447
- lambda: "",
448
- outputs=[msg]
449
- )
450
-
451
- clear_btn.click(
452
- fn=clear_chat,
453
- outputs=[chatbot, eval_summary]
454
- )
455
-
456
- evaluate_btn.click(
457
- fn=evaluate_rag_performance,
458
- outputs=[eval_summary]
459
- )
460
-
461
- export_btn.click(
462
- fn=export_evaluation_data,
463
- outputs=[export_file]
464
- )
465
-
466
- # ==============================================================================
467
- # LAUNCH APPLICATION
468
- # ==============================================================================
469
-
470
- if __name__ == "__main__":
471
- demo.launch(share=False, debug=True)