Spaces:
Sleeping
Sleeping
File size: 16,027 Bytes
278dd0a d8d147e 278dd0a d8d147e 278dd0a 4cfe125 278dd0a 4cfe125 2a993af d8d147e 4cfe125 d8d147e 4cfe125 2a993af d8d147e d09d051 d8d147e 278dd0a 036a914 278dd0a d8d147e 278dd0a d8d147e 278dd0a 5be49c1 278dd0a 5be49c1 278dd0a d8d147e 278dd0a d8d147e 278dd0a d8d147e 278dd0a 2a993af 278dd0a d8d147e 278dd0a d8d147e 278dd0a d8d147e 278dd0a 4cfe125 d8d147e 4cfe125 d8d147e a8595ef d8d147e 4cfe125 d8d147e 4cfe125 d8d147e 278dd0a d8d147e 278dd0a d8d147e 278dd0a d8d147e 2329218 2a993af d8d147e 2329218 4cfe125 278dd0a 2329218 278dd0a d8d147e 278dd0a d8d147e e8a6d88 278dd0a d8d147e 278dd0a d09d051 278dd0a d8d147e 278dd0a 4cfe125 d8d147e d09d051 4cfe125 d8d147e 4cfe125 2329218 4cfe125 278dd0a d8d147e 4cfe125 d8d147e 4cfe125 d09d051 d8d147e 4cfe125 d8d147e 4cfe125 d8d147e 4cfe125 278dd0a 2a993af 5be49c1 2a993af 5de3b29 278dd0a d8d147e 278dd0a d8d147e a8595ef d8d147e 2a993af d8d147e 278dd0a d8d147e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
import streamlit as st
import streamlit_chat
import json
import os
from pymongo import MongoClient
from bson import ObjectId
from dotenv import load_dotenv
import pinecone
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
import re
st.set_page_config(layout="wide", page_title="IOCL Chatbot", page_icon="π")
load_dotenv()
import logging
from pytz import timezone, utc
from datetime import datetime
logging.basicConfig(
level=logging.DEBUG, # This is for your application logs
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Suppress pymongo debug logs by setting the pymongo logger to a higher level
pymongo_logger = logging.getLogger('pymongo')
pymongo_logger.setLevel(logging.WARNING)
PINECONE_API = os.getenv("PINECONE_API_KEY")
pc = pinecone.Pinecone(
api_key=PINECONE_API
)
index_name = "iocl2"
index = pc.Index(index_name)
# MongoDB connection setup
MONGO_URI = os.getenv("MONGO_URI")
client = MongoClient(MONGO_URI)
db = client["chatbot_db"]
chat_sessions = db["chat_sessions3"]
# Set LLM models
FLASH_API = os.getenv("FLASH_API")
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=FLASH_API)
llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0, max_tokens=None, google_api_key=FLASH_API)
# Load the extracted JSON data
# Initialize session state for current chat session
if 'current_chat_id' not in st.session_state:
st.session_state['current_chat_id'] = None
if 'chat_history' not in st.session_state:
st.session_state['chat_history'] = []
if 'regenerate' not in st.session_state:
st.session_state['regenerate'] = False # Track regenerate button state
# Function to create a new chat session in MongoDB
def create_new_chat_session():
# Get the current time in IST
ind_time = datetime.now(timezone("Asia/Kolkata"))
# Convert IST time to UTC for storing in MongoDB
utc_time = ind_time.astimezone(utc)
new_session = {
"created_at": utc_time, # Store in UTC
"messages": [] # Empty at first
}
session_id = chat_sessions.insert_one(new_session).inserted_id
return str(session_id)
# Function to load a chat session by MongoDB ID
# Function to load the chat session by MongoDB ID (load full history for display)
def load_chat_session(session_id):
session = chat_sessions.find_one({"_id": ObjectId(session_id)})
if session:
# Load the full chat history (no slicing here)
st.session_state['chat_history'] = session['messages']
# Function to update chat session in MongoDB (store last 15 question-answer pairs)
# Function to update chat session in MongoDB (store entire chat history)
def update_chat_session(session_id, question, answer, improved_question):
# Append the new question-answer pair to the full messages array
chat_sessions.update_one(
{"_id": ObjectId(session_id)},
{"$push": {
"messages": {"$each": [{"question": question, 'improved_question': improved_question, "answer": answer}]}}}
)
# Function to replace the last response in MongoDB
def replace_last_response_in_mongo(session_id, new_answer):
last_message_index = len(st.session_state['chat_history']) - 1
if last_message_index >= 0:
# Replace the last response in MongoDB
chat_sessions.update_one(
{"_id": ObjectId(session_id)},
{"$set": {f"messages.{last_message_index}.answer": new_answer}}
)
# Function to regenerate the response
def regenerate_response():
try:
if st.session_state['chat_history']:
last_question = st.session_state['chat_history'][-1]["question"] # Get the last question
# Exclude the last response from the history when sending the question to LLM
previous_history = st.session_state['chat_history'][:-1] # Exclude the last Q&A pair
with st.spinner("Please wait, regenerating the response!"):
# Generate a new response for the last question using only the previous history
query = get_context_from_messages(last_question, previous_history)
if query:
logging.info(f"Extracted query is :{query}\n")
extracted_query = get_query_from_llm_answer(query)
if extracted_query:
query = extracted_query
else:
query = last_question
query_embedding = embeddings.embed_query(query)
search_results = index.query(vector=query_embedding, top_k=10, include_metadata=True)
matches = search_results['matches']
content = ""
for i, match in enumerate(matches):
chunk = match['metadata']['chunk']
url = match['metadata']['url']
content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
new_reply = generate_summary(content, query, previous_history)
st.session_state['chat_history'][-1]["answer"] = new_reply
# Update MongoDB with the new response
if st.session_state['current_chat_id']:
replace_last_response_in_mongo(st.session_state['current_chat_id'], new_reply)
st.session_state['regenerate'] = False # Reset regenerate flag
st.rerun()
except Exception as e:
st.error("Error occured in Regenerating response, please try again later.")
def generate_summary(chunks, query, chat_history):
try:
# Limit the history sent to the LLM to the latest 3 question-answer pairs
limited_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
# Create conversation history for the LLM, only using the last 15 entries
history_text = "\n".join([f"User: {q['improved_question']}\nLLM: {q['answer']}" for q in limited_history])
# Define the system and user prompts including the limited history
prompt = ChatPromptTemplate.from_messages([
("system", """You are a chatbot specializing in answering queries related to Indian Oil Corporation Limited (IOCL). You will be provided with chunks of data from the IOCL website to answer user queries. Each chunk will include associated URLs, You must give the url of the chunks which you are using to answer the query.
Key Guidelines:
1.If the user query is not clear, or you think multiple answers are possbile, you should ask for clarification with proper reasoning.
2.Do not mention chunk name in any of your replies.
2.Detailed and Clear: Provide thorough, clear, and concise responses without omitting relevant information from the data chunks.
3.Natural Summarization: When answering, you must not directly quote chunk names,formats. Instead, summarize or interpret the data naturally and conversationally.
4.Use Conversation History: Refer back to the conversation history to maintain consistency and build on prior responses, if applicable.
5.Ignore Unanswered Queries: If the conversation history contains previous responses like "The answer is not available in the context," disregard them when formulating your current response.
6.Graceful Handling of General Queries: If a user sends greetings, introduction, salutations, or unrelated questions, respond appropriately and conversationally.
7.Include Source URLs: Always include the URLs from the relevant chunks of data that you're using to answer the query.
8.Thoroughly looks for answer to the query in the provided chunks before replying, if you feel the query is irrelevant or answer is not present then you can ask user to clarify or tell that it cannot be answered.
9.Sometimes chunks might contain very less data still use it if its relevant.
"""),
("human", f'''
"Query":\n {query}\n
Below are the pinecone chunks that should be used to answer the user query:
"Extracted Data": \n{chunks}\n
Below is the previous conversation history:
"Previous Conversation History": \n{history_text}\n
'''
)
])
# Chain the prompt with LLM for response generation
chain = prompt | llm
result = chain.invoke({"Query": query, "Extracted Data": chunks, "Previous Conversation History": history_text})
# Return the generated response
logging.info(f"LLM answer is :{result}")
return result.content
except Exception as e:
st.error(f"Error answering your question: {e}")
return None
def get_context_from_messages(query, chat_history):
try:
logging.info(f"Getting context from original query: {query}")
# Limit the history sent to the LLM to the latest 3 question-answer pairs
limited_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
# Create conversation history for the LLM, only using the last 15 entries
history_text = "\n".join([f"User: {q['question']}\nLLM: {q['answer']}" for q in limited_history])
# Define the system and user prompts including the limited history
prompt = ChatPromptTemplate.from_messages([
("system", """"I will provide you with a user query and up to the last 3 messages from the chat history which includes both questions and answers.Your task is to understand the user query nicely and restructure it if required such that it makes complete sense and is completely self contained.
The provided queries are related to Indian Oil Corporation limited (IOCL).
1. If the query is a follow-up, use the provided chat history to reconstruct a well-defined, contextually complete query that can stand alone."
2. if the query is self contained, if applicable try to improve it to make is coherent.
3. if the user query is salutations, greetings or not relevant in that case give the query back as it is.
I have provided an output format below, stricly follow it. Do not give anything else other than just the output.
expected_output_format: "query: String or None"
"""),
("human", f'''
"Query":\n {query}\n
"Previous Conversation History": \n{history_text}\n
'''
)
])
# Chain the prompt with LLM for response generation
chain = prompt | llm
result = chain.invoke({"Query": query, "Previous Conversation History": history_text})
logging.info(f"llm answer for query extraction is :{result}")
# Return the generated response
return result.content
except Exception as e:
logging.error(f"exception occured in getting query from original query :{e}")
return None
def get_query_from_llm_answer(llm_output):
match = re.search(r'query:\s*(.*)', llm_output)
if match:
query = match.group(1).strip().strip('"') # Remove leading/trailing spaces and quotes
return None if query.lower() == "none" else query
return None
# Sidebar for showing chat sessions and creating new sessions
st.sidebar.header("Chat Sessions")
# Button for creating a new chat
if st.sidebar.button("New Chat"):
new_chat_id = create_new_chat_session()
st.session_state['current_chat_id'] = new_chat_id
st.session_state['chat_history'] = []
# List existing chat sessions with delete button (dustbin icon)
existing_sessions = chat_sessions.find().sort("created_at", -1)
for session in existing_sessions:
session_id = str(session['_id'])
# Retrieve stored UTC time and convert it to IST for display
utc_time = session['created_at']
ist_time = utc_time.replace(tzinfo=utc).astimezone(timezone("Asia/Kolkata"))
session_date = ist_time.strftime("%Y-%m-%d %H:%M:%S") # Format for display
col1, col2 = st.sidebar.columns([8, 1])
with col1:
if st.button(f"Session {session_date}", key=session_id):
st.session_state['current_chat_id'] = session_id
load_chat_session(session_id)
# Display delete icon (dustbin)
with col2:
if st.button("ποΈ", key=f"delete_{session_id}"):
chat_sessions.delete_one({"_id": ObjectId(session_id)})
st.rerun() # Refresh the app to remove the deleted session from the sidebar
# Main chat interface
st.markdown('<div class="fixed-header"><h1>Welcome To IOCL Chatbot</h1></div>', unsafe_allow_html=True)
st.markdown("<hr>", unsafe_allow_html=True)
# Input box for the question
user_question = st.chat_input(f"Ask a Question related to IOCL Website")
if user_question:
# Automatically create a new session if none exists
if not st.session_state['current_chat_id']:
new_chat_id = create_new_chat_session()
st.session_state['current_chat_id'] = new_chat_id
with st.spinner("Please wait, I am thinking!!"):
# Store the user's question and get the assistant's response
query = get_context_from_messages(user_question, st.session_state['chat_history'])
if query:
logging.info(f"Extracted query is :{query}\n")
extracted_query = get_query_from_llm_answer(query)
if extracted_query:
query = extracted_query
else:
query = user_question
query_embedding = embeddings.embed_query(query)
search_results = index.query(vector=query_embedding, top_k=10, include_metadata=True)
matches = search_results['matches']
content = ""
for i, match in enumerate(matches):
chunk = match['metadata']['chunk']
url = match['metadata']['url']
content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
print(f"content being passed is {content}")
reply = generate_summary(content, query, st.session_state['chat_history'])
if reply:
# Append the new question-answer pair to chat history
st.session_state['chat_history'].append(
{"question": user_question, "answer": reply, "improved_question": query})
# Update the current chat session in MongoDB
if st.session_state['current_chat_id']:
update_chat_session(st.session_state['current_chat_id'], user_question, reply, query)
else:
st.error("Error processing your request, Please try again later.")
else:
st.error("Error processing your request, Please try again later.")
# Display the updated chat history (show last 15 question-answer pairs)
for i, pair in enumerate(st.session_state['chat_history']):
question = pair["question"]
answer = pair["answer"]
streamlit_chat.message(question, is_user=True, key=f"chat_message_user_{i}")
streamlit_chat.message(answer, is_user=False, key=f"chat_message_assistant_{i}")
# Display regenerate button under the last response
if st.session_state['chat_history'] and not st.session_state['regenerate']:
if st.button("π Regenerate", key="regenerate_button"):
st.session_state['regenerate'] = True
regenerate_response() |