Spaces:
Sleeping
Sleeping
changed logic, included RAG
Browse files
app.py
CHANGED
|
@@ -6,13 +6,31 @@ import datetime
|
|
| 6 |
from pymongo import MongoClient
|
| 7 |
from bson import ObjectId
|
| 8 |
from dotenv import load_dotenv
|
|
|
|
| 9 |
from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
| 10 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 11 |
from langchain_core.prompts import ChatPromptTemplate
|
| 12 |
-
|
| 13 |
st.set_page_config(layout="wide", page_title="IOCL Chatbot", page_icon="📄")
|
| 14 |
load_dotenv()
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# MongoDB connection setup
|
| 17 |
MONGO_URI = os.getenv("MONGO_URI")
|
| 18 |
client = MongoClient(MONGO_URI)
|
|
@@ -25,9 +43,6 @@ embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_a
|
|
| 25 |
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0, max_tokens=None, google_api_key=FLASH_API)
|
| 26 |
|
| 27 |
# Load the extracted JSON data
|
| 28 |
-
with open('iocl_extracted_2.json', 'r') as file:
|
| 29 |
-
extracted_data = json.load(file)
|
| 30 |
-
reference_data = json.dumps(extracted_data)
|
| 31 |
|
| 32 |
# Initialize session state for current chat session
|
| 33 |
if 'current_chat_id' not in st.session_state:
|
|
@@ -57,14 +72,13 @@ def load_chat_session(session_id):
|
|
| 57 |
st.session_state['chat_history'] = session['messages']
|
| 58 |
|
| 59 |
|
| 60 |
-
|
| 61 |
# Function to update chat session in MongoDB (store last 15 question-answer pairs)
|
| 62 |
# Function to update chat session in MongoDB (store entire chat history)
|
| 63 |
-
def update_chat_session(session_id, question, answer):
|
| 64 |
# Append the new question-answer pair to the full messages array
|
| 65 |
chat_sessions.update_one(
|
| 66 |
{"_id": ObjectId(session_id)},
|
| 67 |
-
{"$push": {"messages": {"$each": [{"question": question,
|
| 68 |
)
|
| 69 |
|
| 70 |
|
|
@@ -78,78 +92,146 @@ def replace_last_response_in_mongo(session_id, new_answer):
|
|
| 78 |
{"$set": {f"messages.{last_message_index}.answer": new_answer}}
|
| 79 |
)
|
| 80 |
|
|
|
|
| 81 |
# Function to regenerate the response
|
| 82 |
def regenerate_response():
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
with st.spinner("Please wait, regenerating the response!"):
|
| 89 |
-
# Generate a new response for the last question using only the previous history
|
| 90 |
-
new_reply = generate_summary(str(reference_data), last_question, previous_history)
|
| 91 |
|
| 92 |
-
|
| 93 |
-
st.session_state['chat_history'][-1]["answer"] = new_reply
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
-
# Function to generate a detailed response based on the query and JSON data
|
| 104 |
|
| 105 |
|
| 106 |
# When generating a response, pass only the latest 15 messages to the LLM
|
| 107 |
-
def generate_summary(
|
| 108 |
try:
|
| 109 |
-
# Escape curly braces in the JSON data to avoid conflicts with prompt placeholders
|
| 110 |
-
escaped_reference_data = reference_data.replace("{", "{{").replace("}", "}}")
|
| 111 |
-
|
| 112 |
# Limit the history sent to the LLM to the latest 15 question-answer pairs
|
| 113 |
-
limited_history = chat_history[-
|
| 114 |
|
| 115 |
# Create conversation history for the LLM, only using the last 15 entries
|
| 116 |
-
history_text = "\n".join([f"User: {q['
|
| 117 |
|
| 118 |
# Define the system and user prompts including the limited history
|
| 119 |
prompt = ChatPromptTemplate.from_messages([
|
| 120 |
-
("system", """You are a chatbot who specialises in answering
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
| 124 |
Key Guidelines:
|
| 125 |
1.Accuracy is paramount: If the extracted data or conversation history does not contain the information required to answer the query, clearly state, "The answer is not available in the context." Do not attempt to provide a speculative or incorrect response.
|
| 126 |
2.Be detailed: Provide clear, concise, and thorough answers without omitting any relevant information from the extracted data.
|
| 127 |
3.Avoid quoting field names: When responding, avoid directly quoting or referencing field names or formats from the extracted data. Instead, present the information naturally, as if summarizing or interpreting the data. Try to give the answer in points.
|
| 128 |
4.Use the conversation history: When applicable, refer to earlier parts of the conversation to ensure consistency and accuracy in your response.
|
| 129 |
-
5.Sometime a query might be a followup question,without proper context in that case try using previous conversation history and try to utilise latest messages to answer it if possible.
|
| 130 |
6.Answer the queries in conversational style.
|
| 131 |
-
7.
|
| 132 |
"""),
|
| 133 |
("human", f'''
|
|
|
|
|
|
|
| 134 |
"Query":\n {query}\n
|
| 135 |
-
"Previous Conversation History": \n{history_text}\n
|
| 136 |
-
"Extracted Data": \n{escaped_reference_data}\n
|
| 137 |
-
|
| 138 |
'''
|
| 139 |
)
|
| 140 |
])
|
| 141 |
|
| 142 |
# Chain the prompt with LLM for response generation
|
| 143 |
chain = prompt | llm
|
| 144 |
-
result = chain.invoke({"Extracted Data":
|
| 145 |
|
| 146 |
# Return the generated response
|
|
|
|
| 147 |
return result.content
|
| 148 |
|
| 149 |
except Exception as e:
|
| 150 |
st.error(f"Error answering your question: {e}")
|
| 151 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
|
| 155 |
# Sidebar for showing chat sessions and creating new sessions
|
|
@@ -195,16 +277,41 @@ if user_question:
|
|
| 195 |
|
| 196 |
with st.spinner("Please wait, I am thinking!!"):
|
| 197 |
# Store the user's question and get the assistant's response
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
|
|
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
|
|
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
# Display the updated chat history (show last 15 question-answer pairs)
|
| 209 |
for i, pair in enumerate(st.session_state['chat_history']):
|
| 210 |
question = pair["question"]
|
|
|
|
| 6 |
from pymongo import MongoClient
|
| 7 |
from bson import ObjectId
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
+
import pinecone
|
| 10 |
from langchain_google_genai import GoogleGenerativeAIEmbeddings
|
| 11 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 12 |
from langchain_core.prompts import ChatPromptTemplate
|
| 13 |
+
import re
|
| 14 |
st.set_page_config(layout="wide", page_title="IOCL Chatbot", page_icon="📄")
|
| 15 |
load_dotenv()
|
| 16 |
+
import logging
|
| 17 |
+
|
| 18 |
+
logging.basicConfig(
|
| 19 |
+
level=logging.DEBUG, # This is for your application logs
|
| 20 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 21 |
+
datefmt='%Y-%m-%d %H:%M:%S'
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
# Suppress pymongo debug logs by setting the pymongo logger to a higher level
|
| 25 |
+
pymongo_logger = logging.getLogger('pymongo')
|
| 26 |
+
pymongo_logger.setLevel(logging.WARNING)
|
| 27 |
+
|
| 28 |
+
PINECONE_API=os.getenv("PINECONE_API_KEY")
|
| 29 |
+
pc = pinecone.Pinecone(
|
| 30 |
+
api_key=PINECONE_API
|
| 31 |
+
)
|
| 32 |
+
index_name = "iocl"
|
| 33 |
+
index = pc.Index(index_name)
|
| 34 |
# MongoDB connection setup
|
| 35 |
MONGO_URI = os.getenv("MONGO_URI")
|
| 36 |
client = MongoClient(MONGO_URI)
|
|
|
|
| 43 |
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0, max_tokens=None, google_api_key=FLASH_API)
|
| 44 |
|
| 45 |
# Load the extracted JSON data
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
# Initialize session state for current chat session
|
| 48 |
if 'current_chat_id' not in st.session_state:
|
|
|
|
| 72 |
st.session_state['chat_history'] = session['messages']
|
| 73 |
|
| 74 |
|
|
|
|
| 75 |
# Function to update chat session in MongoDB (store last 15 question-answer pairs)
|
| 76 |
# Function to update chat session in MongoDB (store entire chat history)
|
| 77 |
+
def update_chat_session(session_id, question, answer,improved_question):
|
| 78 |
# Append the new question-answer pair to the full messages array
|
| 79 |
chat_sessions.update_one(
|
| 80 |
{"_id": ObjectId(session_id)},
|
| 81 |
+
{"$push": {"messages": {"$each": [{"question": question,'improved_question':improved_question,"answer": answer}]}}}
|
| 82 |
)
|
| 83 |
|
| 84 |
|
|
|
|
| 92 |
{"$set": {f"messages.{last_message_index}.answer": new_answer}}
|
| 93 |
)
|
| 94 |
|
| 95 |
+
|
| 96 |
# Function to regenerate the response
|
| 97 |
def regenerate_response():
|
| 98 |
+
try:
|
| 99 |
+
if st.session_state['chat_history']:
|
| 100 |
+
last_question = st.session_state['chat_history'][-1]["question"] # Get the last question
|
| 101 |
+
# Exclude the last response from the history when sending the question to LLM
|
| 102 |
+
previous_history = st.session_state['chat_history'][:-1] # Exclude the last Q&A pair
|
| 103 |
+
|
| 104 |
+
with st.spinner("Please wait, regenerating the response!"):
|
| 105 |
+
# Generate a new response for the last question using only the previous history
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
query = get_context_from_messages(last_question, previous_history)
|
| 109 |
+
if query:
|
| 110 |
+
logging.info(f"Extracted query is :{query}\n")
|
| 111 |
+
extracted_query = get_query_from_llm_answer(query)
|
| 112 |
+
if extracted_query:
|
| 113 |
+
query = extracted_query
|
| 114 |
+
else:
|
| 115 |
+
query = last_question
|
| 116 |
+
|
| 117 |
+
query_embedding = embeddings.embed_query(query)
|
| 118 |
+
search_results = index.query(vector=query_embedding, top_k=5, include_metadata=True)
|
| 119 |
+
matches = search_results['matches']
|
| 120 |
+
|
| 121 |
+
content = ""
|
| 122 |
+
for i, match in enumerate(matches):
|
| 123 |
+
chunk = match['metadata']['chunk']
|
| 124 |
+
url = match['metadata']['url']
|
| 125 |
+
content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
|
| 126 |
+
|
| 127 |
+
new_reply= generate_summary(content, query, previous_history)
|
| 128 |
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
st.session_state['chat_history'][-1]["answer"] = new_reply
|
|
|
|
| 131 |
|
| 132 |
+
# Update MongoDB with the new response
|
| 133 |
+
if st.session_state['current_chat_id']:
|
| 134 |
+
replace_last_response_in_mongo(st.session_state['current_chat_id'], new_reply)
|
| 135 |
|
| 136 |
+
st.session_state['regenerate'] = False # Reset regenerate flag
|
| 137 |
+
st.rerun()
|
| 138 |
+
|
| 139 |
+
except Exception as e:
|
| 140 |
+
st.error("Error occured in Regenerating response, please try again later.")
|
| 141 |
|
| 142 |
|
|
|
|
| 143 |
|
| 144 |
|
| 145 |
# When generating a response, pass only the latest 15 messages to the LLM
|
| 146 |
+
def generate_summary(chunks, query, chat_history):
|
| 147 |
try:
|
|
|
|
|
|
|
|
|
|
| 148 |
# Limit the history sent to the LLM to the latest 15 question-answer pairs
|
| 149 |
+
limited_history = chat_history[-10:] if len(chat_history) > 10 else chat_history
|
| 150 |
|
| 151 |
# Create conversation history for the LLM, only using the last 15 entries
|
| 152 |
+
history_text = "\n".join([f"User: {q['improved_question']}\nLLM: {q['answer']}" for q in limited_history])
|
| 153 |
|
| 154 |
# Define the system and user prompts including the limited history
|
| 155 |
prompt = ChatPromptTemplate.from_messages([
|
| 156 |
+
("system", """You are a chatbot who specialises in answering user queries related to Indan Oil Corporation Limitied(IOCL).
|
| 157 |
+
this is the extracted chunks of data from the Indian Oil Corporation Limited (IOCL) website. You will be provided with a query, and you must use these chunks of data to answer it comprehensively.
|
| 158 |
+
Each chunk of data has website source page urls associated to it, you must include these relevant urls in your answer telling the source.
|
| 159 |
+
Additionally, the conversation history is also provided which may contain relevant context or prior queries and responses. Use this history to ensure your answer is accurate and coherent, building on previous information if necessary.
|
| 160 |
+
|
| 161 |
Key Guidelines:
|
| 162 |
1.Accuracy is paramount: If the extracted data or conversation history does not contain the information required to answer the query, clearly state, "The answer is not available in the context." Do not attempt to provide a speculative or incorrect response.
|
| 163 |
2.Be detailed: Provide clear, concise, and thorough answers without omitting any relevant information from the extracted data.
|
| 164 |
3.Avoid quoting field names: When responding, avoid directly quoting or referencing field names or formats from the extracted data. Instead, present the information naturally, as if summarizing or interpreting the data. Try to give the answer in points.
|
| 165 |
4.Use the conversation history: When applicable, refer to earlier parts of the conversation to ensure consistency and accuracy in your response.
|
|
|
|
| 166 |
6.Answer the queries in conversational style.
|
| 167 |
+
7.It is must to include urls present in the chunk which you are using to formulate your ansewer.
|
| 168 |
"""),
|
| 169 |
("human", f'''
|
| 170 |
+
Previous Conversation History: \n{history_text}\n
|
| 171 |
+
"Extracted Data": \n{chunks}\n
|
| 172 |
"Query":\n {query}\n
|
|
|
|
|
|
|
|
|
|
| 173 |
'''
|
| 174 |
)
|
| 175 |
])
|
| 176 |
|
| 177 |
# Chain the prompt with LLM for response generation
|
| 178 |
chain = prompt | llm
|
| 179 |
+
result = chain.invoke({"Previous Concversation History":history_text,"Extracted Data": chunks, "Query": query})
|
| 180 |
|
| 181 |
# Return the generated response
|
| 182 |
+
logging.info(f"LLM answer is :{result}")
|
| 183 |
return result.content
|
| 184 |
|
| 185 |
except Exception as e:
|
| 186 |
st.error(f"Error answering your question: {e}")
|
| 187 |
+
return None
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def get_context_from_messages(query,chat_history):
|
| 191 |
+
try:
|
| 192 |
+
|
| 193 |
+
logging.info(f"Getting context from original query: {query}")
|
| 194 |
+
|
| 195 |
+
# Limit the history sent to the LLM to the latest 15 question-answer pairs
|
| 196 |
+
limited_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
|
| 197 |
|
| 198 |
+
# Create conversation history for the LLM, only using the last 15 entries
|
| 199 |
+
history_text = "\n".join([f"User: {q['question']}\nLLM: {q['answer']}" for q in limited_history])
|
| 200 |
+
|
| 201 |
+
# Define the system and user prompts including the limited history
|
| 202 |
+
prompt = ChatPromptTemplate.from_messages([
|
| 203 |
+
("system", """"I will provide you with a user query and up to the last 3 messages from the chat history, including both questions and answers. Your task is to determine whether the provided user query is self-contained (i.e., it can be answered directly without relying on prior messages) or a follow-up query that depends on the previous context.
|
| 204 |
+
1. If the query is self-contained, return None
|
| 205 |
+
2. If the query is a follow-up, use the provided chat history to reconstruct a well-defined, contextually complete query that can stand alone."
|
| 206 |
+
|
| 207 |
+
I have provided an output format below, stricly follow it. Do not give anything else other than just the output.
|
| 208 |
+
expected_output_format: "query: String or None"
|
| 209 |
+
"""),
|
| 210 |
+
("human", f'''
|
| 211 |
+
"Query":\n {query}\n
|
| 212 |
+
Previous Conversation History: \n{history_text}\n
|
| 213 |
+
'''
|
| 214 |
+
)
|
| 215 |
+
])
|
| 216 |
+
|
| 217 |
+
# Chain the prompt with LLM for response generation
|
| 218 |
+
chain = prompt | llm
|
| 219 |
+
result = chain.invoke({"Query": query,"Previous Conversation History":history_text})
|
| 220 |
+
logging.info(f"llm answer for query extraction is :{result}")
|
| 221 |
+
|
| 222 |
+
# Return the generated response
|
| 223 |
+
return result.content
|
| 224 |
+
|
| 225 |
+
except Exception as e:
|
| 226 |
+
logging.error(f"exception occured in getting query from original query :{e}")
|
| 227 |
+
return None
|
| 228 |
+
|
| 229 |
+
def get_query_from_llm_answer(llm_output):
|
| 230 |
+
match = re.search(r'query:\s*(.*)', llm_output)
|
| 231 |
+
if match:
|
| 232 |
+
query = match.group(1).strip().strip('"') # Remove leading/trailing spaces and quotes
|
| 233 |
+
return None if query.lower() == "none" else query
|
| 234 |
+
return None
|
| 235 |
|
| 236 |
|
| 237 |
# Sidebar for showing chat sessions and creating new sessions
|
|
|
|
| 277 |
|
| 278 |
with st.spinner("Please wait, I am thinking!!"):
|
| 279 |
# Store the user's question and get the assistant's response
|
| 280 |
+
query=get_context_from_messages(user_question,st.session_state['chat_history'])
|
| 281 |
+
if query:
|
| 282 |
+
logging.info(f"Extracted query is :{query}\n")
|
| 283 |
+
extracted_query=get_query_from_llm_answer(query)
|
| 284 |
+
if extracted_query:
|
| 285 |
+
query=extracted_query
|
| 286 |
+
else:
|
| 287 |
+
query=user_question
|
| 288 |
+
|
| 289 |
+
query_embedding=embeddings.embed_query(query)
|
| 290 |
+
search_results = index.query(vector=query_embedding, top_k=5, include_metadata=True)
|
| 291 |
+
matches=search_results['matches']
|
| 292 |
+
|
| 293 |
+
content=""
|
| 294 |
+
for i,match in enumerate(matches):
|
| 295 |
+
chunk=match['metadata']['chunk']
|
| 296 |
+
url=match['metadata']['url']
|
| 297 |
+
content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
|
| 298 |
+
|
| 299 |
+
|
| 300 |
|
| 301 |
+
reply = generate_summary(content, query, st.session_state['chat_history'])
|
| 302 |
|
| 303 |
+
if reply:
|
| 304 |
+
# Append the new question-answer pair to chat history
|
| 305 |
+
st.session_state['chat_history'].append({"question": user_question, "answer": reply,"improved_question":query})
|
| 306 |
|
| 307 |
+
# Update the current chat session in MongoDB
|
| 308 |
+
if st.session_state['current_chat_id']:
|
| 309 |
+
update_chat_session(st.session_state['current_chat_id'], user_question, reply,query)
|
| 310 |
|
| 311 |
+
else:
|
| 312 |
+
st.error("Error processing your request, Please try again later.")
|
| 313 |
+
else:
|
| 314 |
+
st.error("Error processing your request, Please try again later.")
|
| 315 |
# Display the updated chat history (show last 15 question-answer pairs)
|
| 316 |
for i, pair in enumerate(st.session_state['chat_history']):
|
| 317 |
question = pair["question"]
|