This repository was archived by the owner on May 8, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchatbot.py
More file actions
332 lines (287 loc) · 12.1 KB
/
Copy pathchatbot.py
File metadata and controls
332 lines (287 loc) · 12.1 KB
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
import json
import os
import time
from operator import itemgetter
import firebase_admin
import streamlit as st
import yaml
from firebase_admin import credentials, firestore
from langchain_community.chat_message_histories import (
StreamlitChatMessageHistory,
)
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai import ChatOpenAI
from document_manager import DocumentManager
class ChatbotPipeline:
def __init__(self, config, chatbot_instruction, document_manager):
self.config = config
self.chatbot_instruction = chatbot_instruction
self.document_manager = document_manager
self.pipeline = self.initialize_pipeline()
def format_documents(self, docs):
"""Format documents for the chatbot pipeline."""
context = "\n\n".join([f'"""\n{doc.page_content}\n"""' for doc in docs])
return f"Use these documents to answer the query.\n\n{context}"
def process_input(self, input):
"""Process user input to generate context-aware prompts."""
if not input["history"]:
return input["question"]
prompt_template = ChatPromptTemplate.from_messages(
[
("system", self.config["prompts"]["expand"]),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
return prompt_template | ChatOpenAI() | StrOutputParser()
def initialize_pipeline(self):
prompt_template = ChatPromptTemplate.from_messages(
[
("system", f"{self.chatbot_instruction}\n\n{{context}}"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
rag_chain_from_documents = (
RunnablePassthrough.assign(
context=(lambda docs: self.format_documents(docs["context"]))
)
| prompt_template
| ChatOpenAI()
| StrOutputParser()
)
return RunnableParallel(
{
"question": itemgetter("question"),
"history": itemgetter("history"),
"context": self.process_input | self.document_manager.retriever,
}
).assign(answer=rag_chain_from_documents)
def invoke(self, data):
return self.pipeline.invoke(data)
class ChatbotAgent:
def __init__(self):
self.load_env_variables()
self.load_config()
self.initialize_firebase_services()
def load_env_variables(self):
"""Load necessary environment variables for the application."""
os.environ["LANGCHAIN_TRACING_V2"] = st.secrets.get(
"LANGCHAIN_TRACING_V2",
"false",
)
os.environ["LANGCHAIN_ENDPOINT"] = st.secrets.get(
"LANGCHAIN_ENDPOINT",
"https://api.langchain.com",
)
os.environ["LANGCHAIN_API_KEY"] = st.secrets.get(
"LANGCHAIN_API_KEY",
"",
)
os.environ["LANGCHAIN_PROJECT"] = st.secrets.get(
"LANGCHAIN_PROJECT",
"default",
)
os.environ["OPENAI_API_KEY"] = st.secrets.get(
"OPENAI_API_KEY",
"",
)
def load_config(self, config_file_path="config.yaml"):
"""Load the configuration file for the application."""
with open(config_file_path, "r", encoding="utf-8") as file:
self.config = yaml.safe_load(file)
self.app_description = self.config["descriptions"]["app"]
self.feedback_description = self.config["descriptions"]["feedback"]
self.personas = self.config["personas"]
self.initial_message = self.config["prompts"]["initial"].strip()
def initialize_firebase_services(self):
"""Initialize Firebase services."""
if not firebase_admin._apps:
firebase_auth = dict(st.secrets["FIREBASE_AUTH"])
firebase_credentials = credentials.Certificate(firebase_auth)
firebase_admin.initialize_app(
firebase_credentials,
{"storageBucket": "streamlit-chatbot-6ee28.appspot.com"},
)
def configure_persona(self, custom_trait_key="custom"):
"""Display UI elements for persona selection and set the selected persona."""
st.write(self.app_description)
st.subheader("How should I answer your questions?")
column1, column2 = st.columns([0.4, 0.6])
chosen_persona = None
with column2:
custom_persona = {
"traits": custom_trait_key,
"image": "images/custom.png",
}
chosen_persona = st.selectbox(
label="Select the trait of the chatbot:",
options=self.personas + [custom_persona],
format_func=lambda x: x["traits"].capitalize(),
)
with st.container(border=True, height=215):
st.markdown("**Instruction:**")
if custom_trait_key == chosen_persona["traits"]:
self.persona_instruction = st.text_area(
label="Chatbot Persona Instruction:",
placeholder="Enter a custom instruction for the chatbot persona...",
height=120,
label_visibility="collapsed",
)
else:
self.persona_instruction = chosen_persona[
"instruction"
].strip()
st.markdown(self.persona_instruction)
with column1, st.container(border=True, height=300):
st.image(chosen_persona["image"], use_column_width=True)
def initialize_chat(self):
"""Set up the chatbot's components and pipelines."""
st.write("💬 Initializing chat history...")
self.chat_history = StreamlitChatMessageHistory()
if not self.chat_history.messages:
self.chat_history.add_ai_message(self.initial_message)
st.write("📢 Connecting to user feedback database...")
firestore_client = firestore.client()
self.feedback_database = firestore_client.collection("feedback")
st.write("🔍 Setting up document manager...")
document_manager = DocumentManager(self.config)
st.write("🔗 Setting up chatbot pipeline...")
chatbot_instruction = (
self.config["prompts"]["main_instruction"]
+ self.persona_instruction
)
self.chatbot_pipeline = ChatbotPipeline(
self.config,
chatbot_instruction,
document_manager,
)
st.write("✨ Finishing chatbot configuration...")
def format_documents(self, documents):
"""Format documents for the chatbot pipeline."""
context = "\n\n".join(
[
f'Document {i}:\n\n"""\n{doc.page_content}\n"""'
for i, doc in enumerate(documents, start=1)
]
)
return f"Use the following documents to answer the query.\n\n{context}"
def configure_chat(self):
"""Configure the Streamlit UI components for the chat interface."""
for message in self.chat_history.messages:
st.chat_message(message.type).write(message.content)
if user_input := st.chat_input():
st.chat_message("human").write(user_input)
parsed_input = self.parse_user_input(user_input)
response = self.chatbot_pipeline.invoke(
{
"question": parsed_input,
"history": self.chat_history.messages,
}
)
ai_message = st.chat_message("ai")
ai_message.write(response.get("answer"))
file_citations = response.get("context")
citations_container = ai_message.expander(
f"File Citations ({len(file_citations)}):",
expanded=False,
)
for citation in file_citations:
source = citation.metadata.get("source")
content = citation.page_content.replace("#", "")
content = "\n".join(
[f"> {line}" for line in content.split("\n")]
)
citations_container.markdown(f"**{source}**\n{content}")
self.chat_history.add_user_message(parsed_input)
self.chat_history.add_ai_message(response.get("answer"))
def parse_user_input(self, prompt):
"""Interpret user input based on predefined commands."""
valid_commands = self.config["commands"]
if prompt.startswith("/"):
command, argument = prompt.split(" ", 1)
command = command.replace("/", "")
if not command in valid_commands.keys():
return prompt
parsed_input = (
f"{valid_commands[command]['description']}\n\n"
f"Query: {argument}"
)
return parsed_input
return prompt
def display_sidebar(self):
"""Manage sidebar content with helpful information and feedback form."""
with st.sidebar:
st.image("images/dipcy.png")
self.display_helpful_info()
st.divider()
self.display_feedback_form()
def display_helpful_info(self):
st.title("💡 Helpful Information")
st.write(self.config["descriptions"]["app"])
with st.expander("Predefined commands"):
commands = self.config["commands"]
for command_name, command_info in commands.items():
st.markdown(
f":green[**{command_name}**] : "
f"{command_info['description']}\n\n"
"Sample usage:\n\n"
f"\t/{command_name} {command_info['arg']}\n\n"
)
chat_history_json = self.convert_chat_history_to_json()
filename = f"conversation_{int(time.time())}.json"
st.download_button(
label="Download Conversation as JSON",
data=chat_history_json,
file_name=filename,
mime="application/json",
use_container_width=True,
type="primary",
)
def convert_chat_history_to_json(self):
return json.dumps(
[
{"type": message.type, "content": message.content}
for message in self.chat_history.messages
],
indent=4,
)
def display_feedback_form(self):
st.title("📢 Feedback")
st.write(self.feedback_description)
feedback = dict(subject="", content="", history="")
feedback["subject"] = st.selectbox(
"Subject",
options=[
"💭 General feedback",
"🌟 Feature request",
"🚨 Bug report",
"📢 Other",
],
)
feedback["content"] = st.text_area("User Feedback", height=100)
include_chat_history = st.checkbox("Include chat history in feedback")
if include_chat_history:
feedback["history"] = self.convert_chat_history_to_json()
if st.button("Submit", type="primary"):
if feedback:
feedback["timestamp"] = firestore.SERVER_TIMESTAMP
self.feedback_database.add(feedback)
st.success("Feedback submitted successfully!", icon="🚀")
else:
st.error("Give feedback before submitting.", icon="🙀")
def run(self):
"""Main execution logic to run the chatbot application."""
st.session_state.setdefault("is_configured", False)
if not st.session_state.is_configured:
self.configure_persona()
if st.button("Start chatting!"):
with st.status("Initializing chatbot...", expanded=True):
self.initialize_chat()
st.session_state.is_configured = True
st.rerun()
else:
self.configure_chat()
self.display_sidebar()