Nioooor commited on
Commit
f0177e7
·
verified ·
1 Parent(s): 278315f

Update src/streamlit_app.py

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