File size: 10,178 Bytes
488344e
017ba16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488344e
017ba16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488344e
017ba16
 
488344e
017ba16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488344e
 
017ba16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
from config import app_name
from config import website_name
from config import DATABASE
from config import PINECONE_INDEX
from config import CHAT_COLLECTION
from langchain_core.messages import AIMessageChunk
import pinecone
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langgraph.prebuilt import create_react_agent
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.schema import HumanMessage, AIMessage
from utils.tools import get_context
import os
from pymongo import MongoClient
from bson import ObjectId
from dotenv import load_dotenv
from pytz import timezone, utc
from datetime import datetime
import time

st.set_page_config(layout="wide", page_title=app_name, page_icon="πŸ“„")
load_dotenv()

FLASH_API = os.getenv("FLASH_API")
OPENAI_KEY = os.getenv("OPEN_AI")
PINECONE_API = os.getenv("PINECONE_API_KEY")
MONGO_URI = os.getenv("MONGO_URI")

pc = pinecone.Pinecone(api_key=PINECONE_API)
index = pc.Index(PINECONE_INDEX)

client = MongoClient(MONGO_URI)
db = client[DATABASE]
chat_sessions = db[CHAT_COLLECTION]

embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=FLASH_API)
model2 = ChatGoogleGenerativeAI(
    model="gemini-2.5-flash",
    api_key=FLASH_API
)

model2_with_tool = model2.bind_tools([get_context])
tools = [get_context]

system_prompt = f"""
You are a website-specific chatbot specializing in answering user queries about {website_name}.
- To answer the user query you will be provided a get_context tool, which allows you to retrieve data chunks  based on user query.
Follow these instructions carefully:

1. **Tool Usage**
   - You can use this tool as needed to fetch information from the knowledgebase.

2. **History Utilization**: 
   - You will be provided with conversation history to track context. If the user's question relates to prior responses, try to answer from memory without invoking the search tool.
   - If additional information is required, reformulate the query to be self-contained before invoking the search tool again.
   - Never give incomplete or wrong answer based on your personal knowledge, always use tool with proper query

3. **General Messages and Salutations**:
   - If the user says "Hi," "Hello," "How are you?" or similar, respond conversationally without invoking the search tool.

4. **Handling Off-Topic Queries**:
   - If the user sends greetings, introductions, or queries unrelated to {website_name}, respond politely and conversationally without forcing a website-related answer.

5. **Response Formation**:
   - Each retrieved chunk will have a URL(url of the webpage from which information was gathered) associated with it; you must cite that URL if you use any information from that chunk.
   - source URL associated with the chunk should be cited , with mention of it as source.
   - A single source should be cited only once, not again and again. That too at bottom in a seperate sources section.
   - Only cite the correct webpage URL , of the chunk you are using to make the information
   - If the same URL appears in multiple relevant chunks, list that URL only once in the sources section.
   - Only include the URLs that genuinely informed or supported your answer.
   - if the answer itself contains url, then quote it properly.
   - Do not skip any PDF urls or any other relevant url mentioned in the chunk.
   - Respond in a friendly, well-formatted manner without mentioning internal terms like "chunk" or "chunk number."

6. **Clear and Complete Responses**:
   - Provide clear explanations with all relevant details. Never omit important information.
   - If the user query cannot be answered from the available data, politely ask for clarification.

7. **Response language**:
  - User can talk either in English or Gujarati.
  - Use the language in which the user does the conversation.

8. **Structured Responses**:
   - Give the response in well formatted manner, in points instead of long paragraphs.

9. **Very Important** **Response Precision**:
   - Make sure to to give only relevant answer to what user asked, do not just dump the information to user, without relevance.
   - Never respond to user that you do not know the answer, without invoking the tool. 
## List of tools available
1. 'get_context'
"""

agent_executor = create_react_agent(model2_with_tool, tools, state_modifier=system_prompt)


def stream_response_real_time(agent_stream):
    """
    Real-time streaming generator that yields tokens as they arrive
    """
    for chunk in agent_stream:
        # Handle different chunk types from LangGraph
        if isinstance(chunk, dict):
            # Extract messages from the chunk
            messages = chunk.get('messages', [])
            for message in messages:
                if isinstance(message, AIMessageChunk) and message.content:
                    # Skip tool calls, only yield actual content
                    if not hasattr(message, 'tool_calls') or not message.tool_calls:
                        yield message.content
        elif isinstance(chunk, AIMessageChunk):
            # Direct AIMessageChunk
            if chunk.content and (not hasattr(chunk, 'tool_calls') or not chunk.tool_calls):
                yield chunk.content


# Initialize session state
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"] = [AIMessage(
        content="Hello, I can help you with information related to the Ministry of external affairs.")]


# Create new chat session in Mongo
def create_new_chat_session():
    ist_time = datetime.now(timezone("Asia/Kolkata"))
    utc_time = ist_time.astimezone(utc)
    session_id = chat_sessions.insert_one({"created_at": utc_time, "messages": []}).inserted_id
    return str(session_id)


# Load session by ID
def load_chat_session(session_id):
    session = chat_sessions.find_one({"_id": ObjectId(session_id)})
    if session:
        msgs = []
        for m in session.get("messages", []):
            if m["role"] == "user":
                msgs.append(HumanMessage(content=m["content"]))
            else:
                msgs.append(AIMessage(content=m["content"]))
        st.session_state["chat_history"] = msgs


# Update Mongo with new messages
def update_chat_session(session_id, new_messages):
    mongo_msgs = [{"role": "user" if isinstance(m, HumanMessage) else "assistant", "content": m.content} for m in
                  new_messages]
    chat_sessions.update_one({"_id": ObjectId(session_id)}, {"$push": {"messages": {"$each": mongo_msgs}}})


# Sidebar session list
st.sidebar.header("Chat Sessions")
if st.sidebar.button("New Chat"):
    chat_id = create_new_chat_session()
    st.session_state["current_chat_id"] = chat_id
    st.session_state["chat_history"] = [AIMessage(
        content="Hello, I can help you with information related to the Commissionerate of Transport, Gujarat.")]

for session in chat_sessions.find().sort("created_at", -1):
    sid = str(session["_id"])
    ist_time = session["created_at"].replace(tzinfo=utc).astimezone(timezone("Asia/Kolkata"))
    label = ist_time.strftime("%Y-%m-%d %H:%M:%S")
    col1, col2 = st.sidebar.columns([8, 1])
    with col1:
        if st.button(f"Session {label}", key=sid):
            st.session_state["current_chat_id"] = sid
            load_chat_session(sid)
    with col2:
        if st.button("πŸ—‘οΈ", key=f"delete_{sid}"):
            chat_sessions.delete_one({"_id": ObjectId(sid)})
            st.rerun()

# Title
st.markdown(f"<h1 style='text-align:center;'>Welcome To {app_name}</h1><hr>", unsafe_allow_html=True)

# Display previous messages
for msg in st.session_state["chat_history"]:
    role = "user" if isinstance(msg, HumanMessage) else "assistant"
    with st.chat_message(role):
        st.markdown(msg.content)

# Input + Real-Time Streaming
user_question = st.chat_input(f"Ask a Question related to {website_name}")
if user_question:
    user_msg = HumanMessage(content=user_question)
    st.session_state["chat_history"].append(user_msg)

    if st.session_state["current_chat_id"]:
        update_chat_session(st.session_state["current_chat_id"], [user_msg])

    with st.chat_message("user"):
        st.markdown(user_question)

    with st.chat_message("assistant"):
        # Create empty container for streaming text
        response_container = st.empty()
        full_response = ""

        # Get the agent stream
        agent_stream = agent_executor.stream(
            {"messages": st.session_state["chat_history"]},
            stream_mode="messages"
        )

        # Stream the response in real-time
        try:
            for chunk in agent_stream:
                # Handle LangGraph stream format
                if isinstance(chunk, tuple) and len(chunk) == 2:
                    message, metadata = chunk
                    if isinstance(message, AIMessageChunk) and message.content:
                        # Skip tool calls
                        if not hasattr(message, 'tool_calls') or not message.tool_calls:
                            full_response += message.content
                            response_container.markdown(full_response + "β–Œ")  # Add cursor
                elif isinstance(chunk, AIMessageChunk) and chunk.content:
                    if not hasattr(chunk, 'tool_calls') or not chunk.tool_calls:
                        full_response += chunk.content
                        response_container.markdown(full_response + "β–Œ")  # Add cursor

            # Remove cursor and show final response
            response_container.markdown(full_response)

        except Exception as e:
            st.error(f"Error during streaming: {str(e)}")
            full_response = "Sorry, I encountered an error while processing your request."
            response_container.markdown(full_response)

    # Save the complete response
    ai_msg = AIMessage(content=full_response)
    st.session_state["chat_history"].append(ai_msg)

    if st.session_state["current_chat_id"]:
        update_chat_session(st.session_state["current_chat_id"], [ai_msg])