akshansh36 commited on
Commit
92390a0
·
verified ·
1 Parent(s): ea3dd10

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +384 -0
  2. config.py +6 -0
  3. requirements.txt +113 -0
app.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit_chat
3
+ import os
4
+ from config import app_name
5
+ from config import website_name
6
+ from config import DATABASE
7
+ from config import PINECONE_INDEX
8
+ from config import CHAT_COLLECTION
9
+ from pymongo import MongoClient
10
+ from bson import ObjectId
11
+ from dotenv import load_dotenv
12
+ import pinecone
13
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
14
+ from langchain_google_genai import ChatGoogleGenerativeAI
15
+ from langchain_core.prompts import ChatPromptTemplate
16
+ import re
17
+ import json
18
+
19
+ st.set_page_config(layout="wide", page_title=app_name, page_icon="📄")
20
+ load_dotenv()
21
+ import logging
22
+ from pytz import timezone, utc
23
+ from datetime import datetime
24
+
25
+ logging.basicConfig(
26
+ level=logging.DEBUG, # This is for your application logs
27
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
28
+ datefmt='%Y-%m-%d %H:%M:%S'
29
+ )
30
+
31
+ # Suppress pymongo debug logs by setting the pymongo logger to a higher level
32
+ pymongo_logger = logging.getLogger('pymongo')
33
+ pymongo_logger.setLevel(logging.WARNING)
34
+ FLASH_API = os.getenv("FLASH_API")
35
+ PINECONE_API = os.getenv("PINECONE_API_KEY")
36
+ MONGO_URI = os.getenv("MONGO_URI")
37
+
38
+ pc = pinecone.Pinecone(
39
+ api_key=PINECONE_API
40
+ )
41
+
42
+ index = pc.Index(PINECONE_INDEX)
43
+ # MongoDB connection setup
44
+
45
+ client = MongoClient(MONGO_URI)
46
+ db = client[DATABASE]
47
+ chat_sessions = db[CHAT_COLLECTION]
48
+
49
+ embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=FLASH_API)
50
+ llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0, max_tokens=None, google_api_key=FLASH_API)
51
+
52
+ # Load the extracted JSON data
53
+
54
+ # Initialize session state for current chat session
55
+ if 'current_chat_id' not in st.session_state:
56
+ st.session_state['current_chat_id'] = None
57
+ if 'chat_history' not in st.session_state:
58
+ st.session_state['chat_history'] = []
59
+ if 'regenerate' not in st.session_state:
60
+ st.session_state['regenerate'] = False # Track regenerate button state
61
+
62
+
63
+ # Function to create a new chat session in MongoDB
64
+ def create_new_chat_session():
65
+ # Get the current time in IST
66
+ ind_time = datetime.now(timezone("Asia/Kolkata"))
67
+ # Convert IST time to UTC for storing in MongoDB
68
+ utc_time = ind_time.astimezone(utc)
69
+
70
+ new_session = {
71
+ "created_at": utc_time, # Store in UTC
72
+ "messages": [] # Empty at first
73
+ }
74
+ session_id = chat_sessions.insert_one(new_session).inserted_id
75
+ return str(session_id)
76
+
77
+
78
+ # Function to load a chat session by MongoDB ID
79
+ # Function to load the chat session by MongoDB ID (load full history for display)
80
+ def load_chat_session(session_id):
81
+ session = chat_sessions.find_one({"_id": ObjectId(session_id)})
82
+ if session:
83
+ # Load the full chat history (no slicing here)
84
+ st.session_state['chat_history'] = session['messages']
85
+
86
+
87
+ # Function to update chat session in MongoDB (store last 15 question-answer pairs)
88
+ # Function to update chat session in MongoDB (store entire chat history)
89
+ def update_chat_session(session_id, question, answer, improved_question):
90
+ # Append the new question-answer pair to the full messages array
91
+ chat_sessions.update_one(
92
+ {"_id": ObjectId(session_id)},
93
+ {"$push": {
94
+ "messages": {"$each": [{"question": question, 'improved_question': improved_question, "answer": answer}]}}}
95
+ )
96
+
97
+
98
+ # Function to replace the last response in MongoDB
99
+ def replace_last_response_in_mongo(session_id, new_answer):
100
+ last_message_index = len(st.session_state['chat_history']) - 1
101
+ if last_message_index >= 0:
102
+ # Replace the last response in MongoDB
103
+ chat_sessions.update_one(
104
+ {"_id": ObjectId(session_id)},
105
+ {"$set": {f"messages.{last_message_index}.answer": new_answer}}
106
+ )
107
+
108
+
109
+ # Function to regenerate the response
110
+ def regenerate_response():
111
+ try:
112
+ if st.session_state['chat_history']:
113
+ last_question = st.session_state['chat_history'][-1]["question"] # Get the last question
114
+ # Exclude the last response from the history when sending the question to LLM
115
+ previous_history = st.session_state['chat_history'][:-1] # Exclude the last Q&A pair
116
+
117
+ with st.spinner("Please wait, regenerating the response!"):
118
+ # Generate a new response for the last question using only the previous history
119
+
120
+ query = get_context_from_messages(last_question, previous_history)
121
+ if query:
122
+ logging.info(f"Extracted query is :{query}\n")
123
+ extracted_query = get_query_from_llm_answer(query)
124
+ if extracted_query:
125
+ query = extracted_query
126
+ else:
127
+ query = last_question
128
+
129
+ query_embedding = embeddings.embed_query(query)
130
+ search_results = index.query(vector=query_embedding, top_k=10, include_metadata=True)
131
+ matches = search_results['matches']
132
+
133
+ content = ""
134
+ for i, match in enumerate(matches):
135
+ chunk = match['metadata']['chunk']
136
+ url = match['metadata']['url']
137
+ content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
138
+
139
+ new_reply = generate_summary(content, query, previous_history)
140
+
141
+ st.session_state['chat_history'][-1]["answer"] = new_reply
142
+
143
+ # Update MongoDB with the new response
144
+ if st.session_state['current_chat_id']:
145
+ replace_last_response_in_mongo(st.session_state['current_chat_id'], new_reply)
146
+
147
+ st.session_state['regenerate'] = False # Reset regenerate flag
148
+ st.rerun()
149
+
150
+ except Exception as e:
151
+ st.error("Error occured in Regenerating response, please try again later.")
152
+
153
+
154
+ def generate_summary(chunks, query, chat_history, language):
155
+ try:
156
+ # Limit the history sent to the LLM to the latest 3 question-answer pairs
157
+ limited_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
158
+
159
+ # Create conversation history for the LLM, only using the last 15 entries
160
+ history_text = "\n".join([f"User: {q['improved_question']}\nLLM: {q['answer']}" for q in limited_history])
161
+
162
+ # Define the system and user prompts including the limited history
163
+ prompt = ChatPromptTemplate.from_messages([
164
+ ("system",
165
+ f"""You are a website-specific chatbot specializing in answering user queries about {website_name}. You will be provided with data chunks sourced from {website_name}, and each chunk has an associated URL and the language in which you have to respond. When formulating your responses:
166
+ 1. Clarity and Completeness
167
+ - Always strive to deliver thorough, concise, and direct answers.
168
+ - If the user’s query is ambiguous or there are multiple possible answers, ask for clarification with a clear rationale.
169
+ 2. No Chunk Names
170
+ - Do not reference chunk filenames or mention the term “chunk” in your replies.
171
+ - Instead, present the information in a natural, conversational style.
172
+
173
+ 3. Use of Conversation History
174
+ - Refer back to conversation history for consistency and to get context for a follow up question.
175
+ - If there are previous statements like “The answer is not available,” ignore them unless still relevant to the current query.
176
+
177
+ 4. Handling Off-Topic Queries
178
+ - If the user sends greetings, introductions, or queries unrelated to {website_name}, respond politely and conversationally without forcing a website-related answer.
179
+
180
+ 5. Source URLs
181
+ - Always provide the URLs you used to answer the query under a “Sources” heading at the end of your reply.
182
+ - If the same URL appears in multiple relevant chunks, list that URL only once in the sources section.
183
+ - Only include the URLs that genuinely informed or supported your answer.
184
+ - if the answer itself contains url, then quote it properly
185
+
186
+ 6. No Direct Chunk Quotes
187
+ - Summarize or paraphrase the original content instead of quoting chunk names or raw text verbatim (unless absolutely necessary for clarity).
188
+ 7. Relevance Check
189
+ - Thoroughly check the provided data chunks before replying.
190
+ - If you can’t find an answer in the chunks, or if the query is irrelevant politely ask for clarification or explain that you cannot answer.
191
+
192
+ 8. Response Language
193
+ - You will be provided the language in which you must answer.
194
+ - Note that the chunks will be in english, so you will have to translate them and then respond.
195
+
196
+ 8. Formatting
197
+ - Present your answers in a well-structured format—either bullet points or clear paragraphs—to ensure maximum readability.
198
+
199
+
200
+ """),
201
+
202
+ ("human", f'''
203
+ "Query":\n {query}\n
204
+ "Language": {language}
205
+ Below are the pinecone chunks that should be used to answer the user query:
206
+ "Extracted Data": \n{chunks}\n
207
+ Below is the previous conversation history:
208
+ "Previous Conversation History": \n{history_text}\n
209
+
210
+ '''
211
+ )
212
+ ])
213
+
214
+ # Chain the prompt with LLM for response generation
215
+ chain = prompt | llm
216
+ result = chain.invoke({"Query": query, "Language":language, "Extracted Data": chunks, "Previous Conversation History": history_text})
217
+
218
+ # Return the generated response
219
+ logging.info(f"LLM answer is :{result}")
220
+ return result.content
221
+
222
+ except Exception as e:
223
+ st.error(f"Error answering your question: {e}")
224
+ return None
225
+
226
+
227
+ def get_context_from_messages(query, chat_history):
228
+ try:
229
+
230
+ logging.info(f"Getting context from original query: {query}")
231
+
232
+ # Limit the history sent to the LLM to the latest 3 question-answer pairs
233
+ limited_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
234
+
235
+ # Create conversation history for the LLM, only using the last 15 entries
236
+ history_text = "\n".join([f"User: {q['question']}\nLLM: {q['answer']}" for q in limited_history])
237
+
238
+ # Define the system and user prompts including the limited history
239
+ prompt = ChatPromptTemplate.from_messages([
240
+ ("system", f""""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.
241
+ If the user query is not in english and some other Indian language such as Hindi, Gujarati, Telugu, Tamil, Kannada etc. your task is to translate it to english and then generate the improved query in english.
242
+ The provided queries are related to {website_name}.
243
+ 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."
244
+ 2. if the query is self contained, if applicable try to improve it to make is coherent.
245
+ 3. if the user query is salutations, greetings or not relevant in that case give the query back as it is.
246
+ I have provided an output format below, stricly follow it. Do not give anything else other than just the output.
247
+
248
+ Output the result in JSON
249
+ expected_output_format:
250
+ "query" : "String or None",
251
+ "original_query_language" : "String" #identify the original query language
252
+
253
+ """),
254
+ ("human", f'''
255
+ "Query":\n {query}\n
256
+ "Previous Conversation History": \n{history_text}\n
257
+ '''
258
+ )
259
+ ])
260
+
261
+ # Chain the prompt with LLM for response generation
262
+ chain = prompt | llm
263
+ result = chain.invoke({"Query": query, "Previous Conversation History": history_text})
264
+ logging.info(f"llm answer for query extraction is :{result}")
265
+
266
+ # Return the generated response
267
+ return result.content
268
+
269
+ except Exception as e:
270
+ logging.error(f"exception occured in getting query from original query :{e}")
271
+ return None
272
+
273
+
274
+ def get_query_from_llm_answer(llm_output):
275
+ cleaned_response = re.sub(r"^`*json\s*", "", llm_output)
276
+ cleaned_response = re.sub(r"`*$", "", cleaned_response)
277
+ if cleaned_response:
278
+ try:
279
+ json_data = json.loads(cleaned_response)
280
+ return json_data
281
+ except json.JSONDecodeError as e:
282
+ print(f"JSON decoding error: {e}")
283
+ return None
284
+
285
+ else:
286
+ return None
287
+
288
+
289
+
290
+ # Sidebar for showing chat sessions and creating new sessions
291
+ st.sidebar.header("Chat Sessions")
292
+
293
+ # Button for creating a new chat
294
+ if st.sidebar.button("New Chat"):
295
+ new_chat_id = create_new_chat_session()
296
+ st.session_state['current_chat_id'] = new_chat_id
297
+ st.session_state['chat_history'] = []
298
+
299
+ # List existing chat sessions with delete button (dustbin icon)
300
+ existing_sessions = chat_sessions.find().sort("created_at", -1)
301
+ for session in existing_sessions:
302
+ session_id = str(session['_id'])
303
+
304
+ # Retrieve stored UTC time and convert it to IST for display
305
+ utc_time = session['created_at']
306
+ ist_time = utc_time.replace(tzinfo=utc).astimezone(timezone("Asia/Kolkata"))
307
+ session_date = ist_time.strftime("%Y-%m-%d %H:%M:%S") # Format for display
308
+
309
+ col1, col2 = st.sidebar.columns([8, 1])
310
+ with col1:
311
+ if st.button(f"Session {session_date}", key=session_id):
312
+ st.session_state['current_chat_id'] = session_id
313
+ load_chat_session(session_id)
314
+
315
+ # Display delete icon (dustbin)
316
+ with col2:
317
+ if st.button("🗑️", key=f"delete_{session_id}"):
318
+ chat_sessions.delete_one({"_id": ObjectId(session_id)})
319
+ st.rerun() # Refresh the app to remove the deleted session from the sidebar
320
+
321
+ # Main chat interface
322
+ st.markdown(f'<div class="fixed-header"><h1>Welcome To {website_name} Chatbot</h1></div>', unsafe_allow_html=True)
323
+ st.markdown("<hr>", unsafe_allow_html=True)
324
+
325
+ # Input box for the question
326
+ user_question = st.chat_input(f"Ask a Question related to {website_name}")
327
+
328
+ if user_question:
329
+ # Automatically create a new session if none exists
330
+ if not st.session_state['current_chat_id']:
331
+ new_chat_id = create_new_chat_session()
332
+ st.session_state['current_chat_id'] = new_chat_id
333
+
334
+ with st.spinner("Please wait, I am thinking!!"):
335
+ # Store the user's question and get the assistant's response
336
+ query = get_context_from_messages(user_question, st.session_state['chat_history'])
337
+ if query:
338
+ logging.info(f"Extracted query is :{query}\n")
339
+ extracted_query_json = get_query_from_llm_answer(query)
340
+ extracted_query = extracted_query_json.get("query")
341
+ language = extracted_query_json.get("original_query_language")
342
+ if extracted_query:
343
+ query = extracted_query
344
+ else:
345
+ query = user_question
346
+
347
+ query_embedding = embeddings.embed_query(query)
348
+ search_results = index.query(vector=query_embedding, top_k=10, include_metadata=True)
349
+ matches = search_results['matches']
350
+
351
+ content = ""
352
+ for i, match in enumerate(matches):
353
+ chunk = match['metadata']['chunk']
354
+ url = match['metadata']['url']
355
+ content += f"chunk{i}: {chunk}\n" + f"url{i}: {url}\n"
356
+
357
+ print(f"content being passed is {content}")
358
+ reply = generate_summary(content, query, st.session_state['chat_history'],language)
359
+
360
+ if reply:
361
+ # Append the new question-answer pair to chat history
362
+ st.session_state['chat_history'].append(
363
+ {"question": user_question, "answer": reply, "improved_question": query})
364
+
365
+ # Update the current chat session in MongoDB
366
+ if st.session_state['current_chat_id']:
367
+ update_chat_session(st.session_state['current_chat_id'], user_question, reply, query)
368
+
369
+ else:
370
+ st.error("Error processing your request, Please try again later.")
371
+ else:
372
+ st.error("Error processing your request, Please try again later.")
373
+ # Display the updated chat history (show last 15 question-answer pairs)
374
+ for i, pair in enumerate(st.session_state['chat_history']):
375
+ question = pair["question"]
376
+ answer = pair["answer"]
377
+ streamlit_chat.message(question, is_user=True, key=f"chat_message_user_{i}")
378
+ streamlit_chat.message(answer, is_user=False, key=f"chat_message_assistant_{i}")
379
+
380
+ # Display regenerate button under the last response
381
+ if st.session_state['chat_history'] and not st.session_state['regenerate']:
382
+ if st.button("🔄 Regenerate", key="regenerate_button"):
383
+ st.session_state['regenerate'] = True
384
+ regenerate_response()
config.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ app_name="PM Kisan Samman Nidhi Chatbot"
2
+ website_name="PM Kisan Samman Nidhi"
3
+ PINECONE_INDEX="kisan"
4
+ DATABASE="chatbots"
5
+ CHUNK_COLLECTION="kisan_chunks"
6
+ CHAT_COLLECTION="kisan_pfrda"
requirements.txt ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.4.4
2
+ aiohttp==3.11.11
3
+ aiosignal==1.3.2
4
+ altair==5.5.0
5
+ annotated-types==0.7.0
6
+ anyio==4.7.0
7
+ async-timeout==4.0.3
8
+ attrs==24.3.0
9
+ beautifulsoup4==4.12.3
10
+ blinker==1.9.0
11
+ cachetools==5.5.0
12
+ certifi==2024.12.14
13
+ charset-normalizer==3.4.1
14
+ click==8.1.8
15
+ colorama==0.4.6
16
+ dataclasses-json==0.6.7
17
+ distro==1.9.0
18
+ dnspython==2.7.0
19
+ exceptiongroup==1.2.2
20
+ filetype==1.2.0
21
+ frozenlist==1.5.0
22
+ fuzzywuzzy==0.18.0
23
+ gitdb==4.0.11
24
+ GitPython==3.1.43
25
+ google-ai-generativelanguage==0.6.10
26
+ google-api-core==2.24.0
27
+ google-api-python-client==2.156.0
28
+ google-auth==2.37.0
29
+ google-auth-httplib2==0.2.0
30
+ google-generativeai==0.8.3
31
+ googleapis-common-protos==1.66.0
32
+ greenlet==3.1.1
33
+ grpcio==1.68.1
34
+ grpcio-status==1.68.1
35
+ h11==0.14.0
36
+ httpcore==1.0.7
37
+ httplib2==0.22.0
38
+ httpx==0.28.1
39
+ httpx-sse==0.4.0
40
+ idna==3.10
41
+ Jinja2==3.1.5
42
+ jiter==0.8.2
43
+ jsonpatch==1.33
44
+ jsonpointer==3.0.0
45
+ jsonschema==4.23.0
46
+ jsonschema-specifications==2024.10.1
47
+ langchain==0.3.13
48
+ langchain-community==0.3.13
49
+ langchain-core==0.3.28
50
+ langchain-google-genai==2.0.7
51
+ langchain-text-splitters==0.3.4
52
+ langsmith==0.2.6
53
+ Levenshtein==0.26.1
54
+ markdown-it-py==3.0.0
55
+ markdownify==0.14.1
56
+ MarkupSafe==3.0.2
57
+ marshmallow==3.23.2
58
+ mdurl==0.1.2
59
+ multidict==6.1.0
60
+ mypy-extensions==1.0.0
61
+ narwhals==1.19.1
62
+ numpy==1.26.4
63
+ openai==1.58.1
64
+ orjson==3.10.12
65
+ packaging==24.2
66
+ pandas==2.2.3
67
+ pillow==11.0.0
68
+ pinecone==5.4.2
69
+ pinecone-plugin-inference==3.1.0
70
+ pinecone-plugin-interface==0.0.7
71
+ propcache==0.2.1
72
+ proto-plus==1.25.0
73
+ protobuf==5.29.2
74
+ pyarrow==18.1.0
75
+ pyasn1==0.6.1
76
+ pyasn1_modules==0.4.1
77
+ pydantic==2.10.4
78
+ pydantic-settings==2.7.0
79
+ pydantic_core==2.27.2
80
+ pydeck==0.9.1
81
+ Pygments==2.18.0
82
+ pymongo==4.10.1
83
+ pyparsing==3.2.0
84
+ python-dateutil==2.9.0.post0
85
+ python-dotenv==1.0.1
86
+ python-Levenshtein==0.26.1
87
+ pytz==2024.2
88
+ PyYAML==6.0.2
89
+ RapidFuzz==3.11.0
90
+ referencing==0.35.1
91
+ requests==2.32.3
92
+ requests-toolbelt==1.0.0
93
+ rich==13.9.4
94
+ rpds-py==0.22.3
95
+ rsa==4.9
96
+ six==1.17.0
97
+ smmap==5.0.1
98
+ sniffio==1.3.1
99
+ soupsieve==2.6
100
+ SQLAlchemy==2.0.36
101
+ streamlit==1.41.1
102
+ streamlit-chat==0.1.1
103
+ tenacity==9.0.0
104
+ toml==0.10.2
105
+ tornado==6.4.2
106
+ tqdm==4.67.1
107
+ typing-inspect==0.9.0
108
+ typing_extensions==4.12.2
109
+ tzdata==2024.2
110
+ uritemplate==4.1.1
111
+ urllib3==2.3.0
112
+ watchdog==6.0.0
113
+ yarl==1.18.3