navid72m commited on
Commit
57b7d5c
·
verified ·
1 Parent(s): 6a93091

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -75
app.py CHANGED
@@ -1,5 +1,6 @@
1
 
2
 
 
3
  import streamlit as st
4
  from PyPDF2 import PdfReader
5
  import requests
@@ -10,14 +11,14 @@ from transformers import AutoTokenizer, AutoModel
10
  import torch
11
  from rank_bm25 import BM25Okapi
12
  import json
13
- from transformers import DPRContextEncoder, DPRContextEncoderTokenizerFast, DPRQuestionEncoder, DPRQuestionEncoderTokenizerFast
 
14
 
15
  my_token = os.getenv('my_repo_token')
16
  # Use Mistral API for serverless architecture
17
  API_URL_MISTRAL = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.3"
18
  headers = {"Authorization": f"Bearer {my_token}"}
19
 
20
- # Query Mistral API
21
  def query_mistral(payload):
22
  response = requests.post(API_URL_MISTRAL, headers=headers, json=payload)
23
  try:
@@ -30,124 +31,102 @@ def query_mistral(payload):
30
  except json.JSONDecodeError:
31
  return "Error: Failed to parse response"
32
 
33
- # Generate response from Mistral
34
  def generate_response(prompt):
35
- payload = {"inputs": prompt, "parameters": {"max_length": 200, "temperature": 0.7}}
 
36
  return query_mistral(payload)
37
 
38
- # Dense Retrieval - FAISS Embeddings
39
  def get_embeddings(texts, model_name='sentence-transformers/all-MiniLM-L6-v2'):
40
  tokenizer = AutoTokenizer.from_pretrained(model_name)
41
  model = AutoModel.from_pretrained(model_name)
42
-
43
- texts = [str(text) for text in texts if text] # Ensure valid text input
44
  inputs = tokenizer(texts, return_tensors='pt', padding=True, truncation=True)
45
-
46
  with torch.no_grad():
47
  outputs = model(**inputs)
48
-
49
  embeddings = outputs.last_hidden_state.mean(dim=1).cpu().numpy()
50
  return embeddings
51
 
52
- # Sparse Retrieval - BM25
53
- def find_most_relevant_context_bm25(contexts, question):
54
- tokenized_corpus = [doc.split() for doc in contexts]
55
- bm25 = BM25Okapi(tokenized_corpus)
56
- tokenized_question = question.split()
57
- return bm25.get_top_n(tokenized_question, contexts, n=min(3, len(contexts)))
58
-
59
- # Dense Retrieval - FAISS
60
  def find_most_relevant_context_faiss(contexts, question, model_name='sentence-transformers/all-MiniLM-L6-v2'):
61
  all_texts = [question] + contexts
62
- all_texts = [str(text) for text in all_texts if text] # Ensure valid input
63
-
64
- if not all_texts:
65
- return []
66
-
67
  embeddings = get_embeddings(all_texts, model_name=model_name)
68
  question_embedding = embeddings[0]
69
  context_embeddings = embeddings[1:]
70
-
71
  dimension = context_embeddings.shape[1]
72
  index = faiss.IndexFlatL2(dimension)
73
  index.add(context_embeddings)
74
-
75
  _, indices = index.search(question_embedding.reshape(1, -1), min(3, len(context_embeddings)))
76
  return [contexts[idx] for idx in indices[0] if idx < len(contexts)]
77
 
78
- # **ColBERT Retrieval - Contextualized Word-Level Matching**
79
- def find_most_relevant_context_colbert(contexts, question):
80
- # Load DPR question and context encoders (ColBERT-like approach)
81
- question_encoder = DPRQuestionEncoder.from_pretrained("facebook/dpr-question_encoder-single-nq-base")
82
- context_encoder = DPRContextEncoder.from_pretrained("facebook/dpr-ctx_encoder-single-nq-base")
83
- question_tokenizer = DPRQuestionEncoderTokenizerFast.from_pretrained("facebook/dpr-question_encoder-single-nq-base")
84
- context_tokenizer = DPRContextEncoderTokenizerFast.from_pretrained("facebook/dpr-ctx_encoder-single-nq-base")
85
-
86
- # Encode question
87
- question_inputs = question_tokenizer(question, return_tensors="pt", truncation=True, padding=True)
88
- with torch.no_grad():
89
- question_embedding = question_encoder(**question_inputs).pooler_output
90
 
91
- # Encode each context
92
- context_embeddings = []
93
  for context in contexts:
94
- context_inputs = context_tokenizer(context, return_tensors="pt", truncation=True, padding=True)
95
- with torch.no_grad():
96
- context_embeddings.append(context_encoder(**context_inputs).pooler_output)
97
-
98
- context_embeddings = torch.stack(context_embeddings)
99
-
100
- # Compute similarity scores
101
- scores = torch.matmul(question_embedding, context_embeddings.T).squeeze(0)
102
- top_k_indices = scores.topk(min(3, len(contexts))).indices.tolist()
103
-
104
- return [contexts[idx] for idx in top_k_indices]
105
-
106
- # Hybrid Retrieval - FAISS + BM25 + ColBERT
107
- def hybrid_search(contexts, question):
108
- faiss_results = find_most_relevant_context_faiss(contexts, question)
109
- bm25_results = find_most_relevant_context_bm25(contexts, question)
110
- colbert_results = find_most_relevant_context_colbert(contexts, question)
111
-
112
- # Combine results, ensuring uniqueness
113
- combined_results = list(set(faiss_results + bm25_results + colbert_results))
114
- return combined_results if combined_results else ["No relevant context found."]
115
 
116
- # Generate answer based on retrieved context
117
  def answer_question_from_pdf(pdf_text, question):
118
- return generate_response(f"{pdf_text} The Question is: {question} Provide the answer with max length of about 100 words.")
 
 
 
 
 
 
119
 
120
- # Extract text from PDF
121
  def extract_text_from_pdf(pdf_file):
122
  pdf_reader = PdfReader(pdf_file)
123
  pdf_arr = [pdf_reader.pages[page_num].extract_text() for page_num in range(len(pdf_reader.pages)) if pdf_reader.pages[page_num].extract_text()]
124
  return pdf_arr if pdf_arr else ["No text extracted from the PDF"]
125
 
 
 
 
 
 
 
 
 
126
  # Streamlit chatbot UI
127
- st.title("PDF Chatbot (Serverless) - Hybrid RAG (FAISS + BM25 + ColBERT)")
128
 
129
  uploaded_file = st.file_uploader("Upload a PDF", type="pdf")
130
 
131
  if uploaded_file is not None:
132
  pdf_arr = extract_text_from_pdf(uploaded_file)
133
- st.write("📄 **PDF Uploaded Successfully. Start chatting!**")
134
-
135
  if "chat_history" not in st.session_state:
136
  st.session_state.chat_history = []
137
-
138
  question = st.text_input("Ask a question about the PDF")
139
  if st.button("Send") and question:
140
- combined_results = hybrid_search(pdf_arr, question) # Hybrid Retrieval
141
-
142
- # Generate response using the best retrieved contexts
143
- response = answer_question_from_pdf(" ".join(combined_results), question)
144
-
145
- # Store the conversation history
146
  st.session_state.chat_history.append((question, response))
147
-
148
- # Display chat history
149
  for q, r in st.session_state.chat_history:
150
  st.write(f"**You:** {q}")
151
  st.write(f"**Bot:** {r}")
152
  else:
153
- st.write("📥 **Please upload a PDF file to start the chat.**")
 
1
 
2
 
3
+
4
  import streamlit as st
5
  from PyPDF2 import PdfReader
6
  import requests
 
11
  import torch
12
  from rank_bm25 import BM25Okapi
13
  import json
14
+ import time
15
+ from transformers import pipeline
16
 
17
  my_token = os.getenv('my_repo_token')
18
  # Use Mistral API for serverless architecture
19
  API_URL_MISTRAL = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.3"
20
  headers = {"Authorization": f"Bearer {my_token}"}
21
 
 
22
  def query_mistral(payload):
23
  response = requests.post(API_URL_MISTRAL, headers=headers, json=payload)
24
  try:
 
31
  except json.JSONDecodeError:
32
  return "Error: Failed to parse response"
33
 
 
34
  def generate_response(prompt):
35
+ prompt += "\nIf the answer is not in the provided text, say 'I don't know'."
36
+ payload = {"inputs": prompt, "parameters": {"max_length": 200, "temperature": 0.3}}
37
  return query_mistral(payload)
38
 
 
39
  def get_embeddings(texts, model_name='sentence-transformers/all-MiniLM-L6-v2'):
40
  tokenizer = AutoTokenizer.from_pretrained(model_name)
41
  model = AutoModel.from_pretrained(model_name)
 
 
42
  inputs = tokenizer(texts, return_tensors='pt', padding=True, truncation=True)
 
43
  with torch.no_grad():
44
  outputs = model(**inputs)
 
45
  embeddings = outputs.last_hidden_state.mean(dim=1).cpu().numpy()
46
  return embeddings
47
 
 
 
 
 
 
 
 
 
48
  def find_most_relevant_context_faiss(contexts, question, model_name='sentence-transformers/all-MiniLM-L6-v2'):
49
  all_texts = [question] + contexts
 
 
 
 
 
50
  embeddings = get_embeddings(all_texts, model_name=model_name)
51
  question_embedding = embeddings[0]
52
  context_embeddings = embeddings[1:]
 
53
  dimension = context_embeddings.shape[1]
54
  index = faiss.IndexFlatL2(dimension)
55
  index.add(context_embeddings)
 
56
  _, indices = index.search(question_embedding.reshape(1, -1), min(3, len(context_embeddings)))
57
  return [contexts[idx] for idx in indices[0] if idx < len(contexts)]
58
 
59
+ def find_most_relevant_context_bm25(contexts, question):
60
+ tokenized_corpus = [doc.split() for doc in contexts]
61
+ bm25 = BM25Okapi(tokenized_corpus)
62
+ tokenized_question = question.split()
63
+ top_docs = bm25.get_top_n(tokenized_question, contexts, n=min(3, len(contexts)))
64
+ return top_docs
 
 
 
 
 
 
65
 
66
+ def rerank_results(contexts, question):
67
+ scores = []
68
  for context in contexts:
69
+ response = generate_response(f"Does this text answer the question: '{question}'? Text: {context}")
70
+ if "yes" in response.lower():
71
+ scores.append((context, 1))
72
+ else:
73
+ scores.append((context, 0))
74
+ ranked_contexts = [context for context, score in scores if score > 0]
75
+ return ranked_contexts if ranked_contexts else ["I don't know."]
76
+
77
+ def is_response_confident(response):
78
+ low_confidence_phrases = [
79
+ "i'm not sure", "i think", "possibly", "maybe", "it seems", "likely",
80
+ "i guess", "i assume", "it's possible", "as far as i know"
81
+ ]
82
+ return not any(phrase in response.lower() for phrase in low_confidence_phrases)
83
+
84
+ def validate_response(prompt):
85
+ response1 = generate_response(prompt)
86
+ response2 = generate_response(prompt)
87
+ if response1.strip().lower() != response2.strip().lower():
88
+ return "I am not confident about the answer. Please refer to the document."
89
+ return response1
90
 
 
91
  def answer_question_from_pdf(pdf_text, question):
92
+ if not pdf_text.strip():
93
+ return "I could not find relevant information in the document."
94
+ prompt = f"{pdf_text} The Question is: {question} Provide the answer with max length of about 100 words."
95
+ response = validate_response(prompt)
96
+ if not is_response_confident(response):
97
+ return "I'm not sure about this. Please refer to the original document."
98
+ return response
99
 
 
100
  def extract_text_from_pdf(pdf_file):
101
  pdf_reader = PdfReader(pdf_file)
102
  pdf_arr = [pdf_reader.pages[page_num].extract_text() for page_num in range(len(pdf_reader.pages)) if pdf_reader.pages[page_num].extract_text()]
103
  return pdf_arr if pdf_arr else ["No text extracted from the PDF"]
104
 
105
+ def hybrid_search(contexts, question):
106
+ faiss_results = find_most_relevant_context_faiss(contexts, question)
107
+ bm25_results = find_most_relevant_context_bm25(contexts, question)
108
+ combined_results = list(set(faiss_results + bm25_results))
109
+ if not combined_results:
110
+ return ["I don't know."]
111
+ return rerank_results(combined_results, question)
112
+
113
  # Streamlit chatbot UI
114
+ st.title("PDF Chatbot (Serverless)")
115
 
116
  uploaded_file = st.file_uploader("Upload a PDF", type="pdf")
117
 
118
  if uploaded_file is not None:
119
  pdf_arr = extract_text_from_pdf(uploaded_file)
120
+ st.write("PDF Uploaded Successfully. Start chatting!")
 
121
  if "chat_history" not in st.session_state:
122
  st.session_state.chat_history = []
 
123
  question = st.text_input("Ask a question about the PDF")
124
  if st.button("Send") and question:
125
+ combined_results = hybrid_search(pdf_arr, question)
126
+ response = answer_question_from_pdf(" ".join(combined_results), question) if combined_results else "No relevant context found."
 
 
 
 
127
  st.session_state.chat_history.append((question, response))
 
 
128
  for q, r in st.session_state.chat_history:
129
  st.write(f"**You:** {q}")
130
  st.write(f"**Bot:** {r}")
131
  else:
132
+ st.write("Please upload a PDF file to start the chat.")