Nioooor commited on
Commit
d359ffc
·
verified ·
1 Parent(s): f4503f7

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +229 -0
src/streamlit_app.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import requests
4
+ import re # To help clean up leading whitespace
5
+ import sys
6
+
7
+ # Prefer pysqlite3 (if installed) for SQLite; otherwise fall back to stdlib sqlite3.
8
+ # This mirrors the previous behavior but is robust when pysqlite3 isn't available.
9
+ try:
10
+ # Try to import the pysqlite3 package which provides a replacement sqlite3 module
11
+ pysqlite3 = __import__('pysqlite3')
12
+ # If imported, make it available under the name 'sqlite3' to satisfy code expecting
13
+ # a sqlite3 module with pysqlite3's behavior.
14
+ if 'sqlite3' not in sys.modules:
15
+ sys.modules['sqlite3'] = pysqlite3
16
+ except ModuleNotFoundError:
17
+ # pysqlite3 isn't installed; the standard library's sqlite3 will be used.
18
+ import sqlite3 # noqa: F401
19
+
20
+ # Langchain and HuggingFace
21
+ from langchain_community.vectorstores import Chroma
22
+ from langchain_community.embeddings import HuggingFaceEmbeddings
23
+ # Workaround: some versions of the `groq` package (used by `langchain_groq`) may pass
24
+ # a `proxies` keyword into the underlying Client.__init__, which can cause
25
+ # TypeError if the installed groq Client doesn't accept that kwarg. To make the
26
+ # app more tolerant across versions, monkeypatch the groq Client constructor to
27
+ # silently drop `proxies` if present.
28
+ try:
29
+ import groq._base_client as _groq_base
30
+ _groq_client_init = getattr(_groq_base.Client, "__init__", None)
31
+ if _groq_client_init is not None:
32
+ def _client_init_no_proxies(self, *args, **kwargs):
33
+ kwargs.pop('proxies', None)
34
+ return _groq_client_init(self, *args, **kwargs)
35
+ _groq_base.Client.__init__ = _client_init_no_proxies
36
+ except Exception:
37
+ # If groq isn't installed or the internal structure differs, let the import
38
+ # of ChatGroq attempt to initialize and raise its own errors. We don't
39
+ # want to crash here just for the monkeypatch.
40
+ pass
41
+
42
+ from langchain_groq import ChatGroq
43
+ from langchain.chains import RetrievalQA
44
+
45
+ # Load the .env file (if using it)
46
+ groq_api_key = os.getenv("GROQ_API_KEY")
47
+
48
+ # Load embeddings, model, and vector store
49
+ @st.cache_resource # Singleton, prevent multiple initializations
50
+ def init_chain():
51
+ model_kwargs = {'trust_remote_code': True}
52
+ embedding = HuggingFaceEmbeddings(model_name='nomic-ai/nomic-embed-text-v1.5', model_kwargs=model_kwargs)
53
+ llm = ChatGroq(groq_api_key=groq_api_key, model_name="openai/gpt-oss-20b", temperature=0.1)
54
+ vectordb = Chroma(persist_directory='updated_CSPCDB2', embedding_function=embedding)
55
+
56
+ # Create chain
57
+ chain = RetrievalQA.from_chain_type(llm=llm,
58
+ chain_type="stuff",
59
+ retriever=vectordb.as_retriever(k=5),
60
+ return_source_documents=True)
61
+ return chain
62
+
63
+ # Streamlit app layout
64
+ st.set_page_config(
65
+ page_title="CSPC Citizens Charter Conversational Agent",
66
+ page_icon="cspclogo.png"
67
+ )
68
+
69
+ # Custom CSS for styling
70
+ st.markdown(
71
+ """
72
+ <style>
73
+ .subtitle {text-align: center; font-size: 18px; font-weight: bold;}
74
+ .details {font-size: 14px; color: #bbbbbb; padding-left: 20px; margin-bottom: -10px;}
75
+ .team {text-align: center; font-size: 20px; font-weight: bold; color: #777;}
76
+ </style>
77
+ """,
78
+ unsafe_allow_html=True
79
+ )
80
+
81
+ with st.sidebar:
82
+ # App title
83
+ st.title('CSPC Conversational Agent')
84
+ st.markdown('<p class="subtitle">Your go-to assistant for the Citizen’s Charter of CSPC!</p>', unsafe_allow_html=True)
85
+
86
+ # Categories
87
+ st.markdown('''✔️**About CSPC:**''')
88
+ st.markdown('<p class="details">History, Core Values, Mission and Vision</p>', unsafe_allow_html=True)
89
+
90
+ st.markdown('''✔️**Admission & Graduation:**''')
91
+ st.markdown('<p class="details">Apply, Requirements, Process, Graduation</p>', unsafe_allow_html=True)
92
+
93
+ st.markdown('''✔️**Student Services:**''')
94
+ st.markdown('<p class="details">Scholarships, Orgs, Facilities</p>', unsafe_allow_html=True)
95
+
96
+ st.markdown('''✔️**Academics:**''')
97
+ st.markdown('<p class="details">Degrees, Courses, Faculty</p>', unsafe_allow_html=True)
98
+
99
+ st.markdown('''✔️**Officials:**''')
100
+ st.markdown('<p class="details">President, VPs, Deans, Admin</p>', unsafe_allow_html=True)
101
+
102
+ # Links to resources
103
+ st.markdown("### 🔗 Quick Access to Resources")
104
+ st.markdown(
105
+ """
106
+ 📄 [CSPC Citizen’s Charter](https://cspc.edu.ph/governance/citizens-charter/)
107
+ 🏛️ [About CSPC](https://cspc.edu.ph/about/)
108
+ 📋 [College Officials](https://cspc.edu.ph/college-officials/)
109
+ """,
110
+ unsafe_allow_html=True
111
+ )
112
+
113
+ # Store LLM generated responses
114
+ if "messages" not in st.session_state:
115
+ st.session_state.chain = init_chain()
116
+ st.session_state.messages = [{"role": "assistant", "content": "Hello! I am your Conversational Agent for the Citizens Charter of Camarines Sur Polytechnic Colleges (CSPC). How may I assist you today?"}]
117
+ st.session_state.query_counter = 0 # Track the number of user queries
118
+ st.session_state.conversation_history = "" # Keep track of history for the LLM
119
+
120
+ def generate_response(prompt_input):
121
+ try:
122
+ # Retrieve vector database context using ONLY the current user input
123
+ retriever = st.session_state.chain.retriever
124
+ relevant_context = retriever.get_relevant_documents(prompt_input) # Retrieve context only for the current prompt
125
+
126
+ # Format the input for the chain with the retrieved context
127
+ formatted_input = (
128
+ f"You are a Conversational Agent for the Citizens Charter of Camarines Sur Polytechnic Colleges (CSPC). "
129
+ f"Your purpose is to provide accurate and helpful information about CSPC's policies, procedures, and services as outlined in the Citizens Charter. "
130
+ f"When responding to user queries:\n"
131
+ f"1. Always prioritize information from the provided context (Citizens Charter or other CSPC resources).\n"
132
+ f"2. Be concise, clear, and professional in your responses.\n"
133
+ f"3. If the user's question is outside the scope of the Citizens Charter, politely inform them and suggest relevant resources or departments they can contact.\n\n"
134
+ f"Context:\n"
135
+ f"{' '.join([doc.page_content for doc in relevant_context])}\n\n"
136
+ f"Conversation:\n{st.session_state.conversation_history}user: {prompt_input}\n"
137
+ )
138
+
139
+ # Invoke the RetrievalQA chain directly with the formatted input
140
+ res = st.session_state.chain.invoke({"query": formatted_input})
141
+
142
+ # Process the response text
143
+ result_text = res['result']
144
+
145
+ # Clean up prefixing phrases and capitalize the first letter
146
+ if result_text.startswith('According to the provided context, '):
147
+ result_text = result_text[35:].strip()
148
+ elif result_text.startswith('Based on the provided context, '):
149
+ result_text = result_text[31:].strip()
150
+ elif result_text.startswith('According to the provided text, '):
151
+ result_text = result_text[34:].strip()
152
+ elif result_text.startswith('According to the context, '):
153
+ result_text = result_text[26:].strip()
154
+
155
+ # Ensure the first letter is uppercase
156
+ result_text = result_text[0].upper() + result_text[1:] if result_text else result_text
157
+
158
+ # Extract and format sources (if available)
159
+ sources = []
160
+ for doc in relevant_context:
161
+ source_path = doc.metadata.get('source', '')
162
+ formatted_source = source_path[122:-4] if source_path else "Unknown source"
163
+ sources.append(formatted_source)
164
+
165
+ # Remove duplicates and combine into a single string
166
+ unique_sources = list(set(sources))
167
+ source_list = ", ".join(unique_sources)
168
+
169
+ # # Combine response text with sources
170
+ # result_text += f"\n\n**Sources:** {source_list}" if source_list else "\n\n**Sources:** None"
171
+
172
+ # Update conversation history
173
+ st.session_state.conversation_history += f"user: {prompt_input}\nassistant: {result_text}\n"
174
+
175
+ return result_text
176
+
177
+ except Exception as e:
178
+ # Handle rate limit or other errors gracefully
179
+ if "rate_limit_exceeded" in str(e).lower():
180
+ return "⚠️ Rate limit exceeded. Please clear the chat history and try again."
181
+ else:
182
+ return f"❌ An error occurred: {str(e)}"
183
+
184
+
185
+ # Display chat messages
186
+ for message in st.session_state.messages:
187
+ with st.chat_message(message["role"]):
188
+ st.write(message["content"])
189
+
190
+ # User-provided prompt for input box
191
+ if prompt := st.chat_input(placeholder="Ask a question..."):
192
+ # Increment query counter
193
+ st.session_state.query_counter += 1
194
+ # Append user query to session state
195
+ st.session_state.messages.append({"role": "user", "content": prompt})
196
+ with st.chat_message("user"):
197
+ st.write(prompt)
198
+
199
+ # Generate and display placeholder for assistant response
200
+ with st.chat_message("assistant"):
201
+ message_placeholder = st.empty() # Placeholder for response while it's being generated
202
+ with st.spinner("Generating response..."):
203
+ # Use conversation history when generating response
204
+ response = generate_response(prompt)
205
+ message_placeholder.markdown(response) # Replace placeholder with actual response
206
+ st.session_state.messages.append({"role": "assistant", "content": response})
207
+
208
+ # Check if query counter has reached the limit
209
+ if st.session_state.query_counter >= 10:
210
+ st.sidebar.warning("Conversation context has been reset after 10 queries.")
211
+ st.session_state.query_counter = 0 # Reset the counter
212
+ st.session_state.conversation_history = "" # Clear conversation history for the LLM
213
+
214
+ # Clear chat history function
215
+ def clear_chat_history():
216
+ # Clear chat messages (reset the assistant greeting)
217
+ st.session_state.messages = [{"role": "assistant", "content": "Hello! I am your Conversational Agent for the Citizens Charter of Camarines Sur Polytechnic Colleges (CSPC). How may I assist you today?"}]
218
+
219
+ # Reinitialize the chain to clear any stored history (ensures it forgets previous user inputs)
220
+ st.session_state.chain = init_chain()
221
+
222
+ # Clear the query counter and conversation history
223
+ st.session_state.query_counter = 0
224
+ st.session_state.conversation_history = ""
225
+
226
+ st.sidebar.button('Clear Chat History', on_click=clear_chat_history)
227
+
228
+ # Footer
229
+ st.sidebar.markdown('<p class="team">Developed by Team XceptionNet</p>', unsafe_allow_html=True)