Sebunya commited on
Commit
89841b6
·
verified ·
1 Parent(s): 3f95d52

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -119
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import gradio as gr
3
  import pandas as pd
@@ -9,13 +10,12 @@ import chromadb
9
  from langchain_chroma import Chroma
10
  import gspread
11
  from google.oauth2.service_account import Credentials
12
- from datetime import datetime
 
13
  import json
 
14
  import re
15
- import random
16
- import sqlite3
17
- from langgraph.checkpoint.sqlite import SqliteSaver
18
- from typing import Tuple # Added import for Tuple
19
 
20
  # === Configuration ===
21
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
@@ -39,7 +39,7 @@ client_gspread = gspread.authorize(get_google_sheets_credentials())
39
  # Open the Google Sheet
40
  sheet = client_gspread.open("Response_Log").sheet1
41
 
42
- def log_response(question, answer, source_ids, knowledge_pairs):
43
  """
44
  Log a question, answer, source IDs, and knowledge base question-answer pairs to the Google Sheet.
45
 
@@ -56,6 +56,7 @@ def log_response(question, answer, source_ids, knowledge_pairs):
56
  knowledge_answer_2 = knowledge_pairs[1][1] if len(knowledge_pairs) > 1 else "N/A"
57
  row = [
58
  timestamp,
 
59
  question,
60
  answer,
61
  source_ids,
@@ -72,49 +73,32 @@ def log_response(question, answer, source_ids, knowledge_pairs):
72
  with open("/tmp/response_log.txt", "a") as f:
73
  f.write(f"{timestamp},{question},{answer},{source_ids},{knowledge_question_1},{knowledge_answer_1},{knowledge_question_2},{knowledge_answer_2}\n")
74
 
75
- # === Load and Clean Knowledge Base ===
76
- df_kb = pd.read_json("XENO_Uganda_KnowledgeBase_Advisory.json")
77
- df_kb.dropna(subset=['Content'], inplace=True)
78
-
79
- def prepare_documents(data):
80
- documents, metadatas, ids = [], [], []
81
- for item in data:
82
- documents.append(f"Question: {item['Question']}\nAnswer: {item['Content']}")
83
- metadatas.append({
84
- "question": item["Question"],
85
- "content": item["Content"],
86
- "section": item.get("Section", ""),
87
- "source": item.get("Source", ""),
88
- "owner": item.get("Owner", ""),
89
- "tag": item.get("Tag", ""),
90
- "id": item["ID"]
91
- })
92
- ids.append(item["ID"])
93
- return documents, metadatas, ids
94
-
95
- xeno_data_list = df_kb.to_dict('records')
96
- documents, metadatas, ids = prepare_documents(xeno_data_list)
97
-
98
- # === Setup ChromaDB ===
99
- try:
100
- client = chromadb.PersistentClient(path="/tmp/xeno_db")
101
- try:
102
- collection = client.get_collection(name=collection_name)
103
- print(f"Loaded existing ChromaDB collection: {collection_name}")
104
- except:
105
- print(f"Creating new ChromaDB collection: {collection_name}")
106
- collection = client.create_collection(name=collection_name)
107
- collection.add(documents=documents, metadatas=metadatas, ids=ids)
108
- except Exception as e:
109
- print(f"Failed to initialize ChromaDB: {e}")
110
- raise
111
-
112
- vector_store = Chroma(client=client, collection_name=collection_name)
113
- retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 4})
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  # === Intent Classification System ===
116
  class IntentClassifier:
117
  def __init__(self):
 
118
  self.intent_patterns = {
119
  'greeting': {
120
  'patterns': [
@@ -164,6 +148,7 @@ class IntentClassifier:
164
  for intent_name, intent_data in self.intent_patterns.items():
165
  for pattern in intent_data['patterns']:
166
  if re.search(pattern, message_lower, re.IGNORECASE):
 
167
  response = random.choice(intent_data['responses'])
168
  return intent_name, response
169
 
@@ -177,29 +162,45 @@ class IntentClassifier:
177
  # Initialize intent classifier
178
  intent_classifier = IntentClassifier()
179
 
180
- # === LangGraph Memory Setup ===
181
- conn = sqlite3.connect("xeno_memory.db", check_same_thread=False)
182
- memory = SqliteSaver(conn=conn)
183
 
184
- def update_memory(config, user_message, assistant_message):
185
- current_checkpoint = memory.get(config) or {
186
- "v": 1,
187
- "ts": datetime.now().isoformat(),
188
- "channel_values": {"messages": []},
189
- "channel_versions": {},
190
- "versions_seen": {},
191
- "pending_sends": [],
192
- }
193
- messages = current_checkpoint["channel_values"].get("messages", [])
194
-
195
- messages.append({"role": "user", "content": user_message})
196
- messages.append({"role": "assistant", "content": assistant_message})
197
-
198
- current_checkpoint["channel_versions"]["messages"] = current_checkpoint["channel_versions"].get("messages", 0) + 1
199
- current_checkpoint["channel_values"]["messages"] = messages
200
- current_checkpoint["ts"] = datetime.now().isoformat()
201
-
202
- memory.put(config, current_checkpoint, {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  # === Prompt System ===
205
  SYSTEM_PROMPT = """You are a friendly XENO Support Assistant, an AI-powered helpful and professional customer service representative.
@@ -228,68 +229,101 @@ def process_context(results, cosine_scores, max_results=2):
228
  knowledge_pairs.append((question, answer))
229
  return formatted_context, source_ids, knowledge_pairs
230
 
231
- # === LLM Generation ===
232
- def generate_xeno_response(context, question, conversation_history=""):
 
233
  model = genai.GenerativeModel(llm_model_name)
234
- prompt = f"""{SYSTEM_PROMPT}
235
-
236
- ### PREVIOUS CONVERSATION ###
237
- {conversation_history}
238
-
239
- ### CONTEXT ###
240
- {context}
241
-
242
- ### QUESTION ###
243
- {question}"""
244
  response = model.generate_content(prompt)
245
  return response.text.strip()
246
 
247
- # === Main Interface Logic ===
248
- def get_context_and_answer(message, history):
249
- config = {"configurable": {"thread_id": "xeno_default"}}
250
-
251
- intent, predefined_response = intent_classifier.classify_intent(message)
252
-
253
- if predefined_response:
254
- log_response(message, predefined_response, "N/A", [])
255
- update_memory(config, message, predefined_response)
256
- return predefined_response
257
 
258
- current_checkpoint = memory.get(config) or {"channel_values": {"messages": []}}
259
- previous_messages = current_checkpoint["channel_values"].get("messages", [])
260
- conversation_history = "\n".join([f"{msg['role'].capitalize()}: {msg['content']}" for msg in previous_messages])
261
 
262
- queried_results = retriever.invoke(message)
263
- query_embedding = genai.embed_content(model=embedding_model,
264
- content=message,
265
- task_type="retrieval_query")['embedding']
266
- cosine_scores = []
267
- for doc in queried_results:
268
- doc_embedding = genai.embed_content(model=embedding_model,
269
- content=doc.page_content,
270
- task_type="retrieval_document")['embedding']
271
- cos_sim = util.cos_sim(torch.tensor(query_embedding).float(), torch.tensor(doc_embedding).float())[0][0].item()
272
- cosine_scores.append(cos_sim)
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
- if max(cosine_scores) < 0.6:
275
- answer = "I'm sorry, I couldn't find the specific information you're looking for in my knowledge base."
276
- log_response(message, answer, "N/A", [])
277
- update_memory(config, message, answer)
278
- return answer
279
 
280
- context, source_ids, knowledge_pairs = process_context(queried_results, cosine_scores)
281
- answer = generate_xeno_response(context, message, conversation_history)
282
- log_response(message, answer, ", ".join(source_ids), knowledge_pairs)
283
  update_memory(config, message, answer)
 
 
284
  return answer
285
 
286
- # === Gradio UI ===
287
- iface = gr.ChatInterface(
288
- fn=get_context_and_answer,
289
- title="ASKXENO",
290
- description="Ask anything about XENO's financial services.",
291
- theme="soft"
292
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
  if __name__ == "__main__":
295
- iface.launch(share=False)
 
 
1
+ import uuid
2
  import os
3
  import gradio as gr
4
  import pandas as pd
 
10
  from langchain_chroma import Chroma
11
  import gspread
12
  from google.oauth2.service_account import Credentials
13
+ from langgraph.checkpoint.sqlite import SqliteSaver
14
+ import sqlite3
15
  import json
16
+ from datetime import datetime
17
  import re
18
+ from typing import Dict, List, Tuple
 
 
 
19
 
20
  # === Configuration ===
21
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
39
  # Open the Google Sheet
40
  sheet = client_gspread.open("Response_Log").sheet1
41
 
42
+ def log_response(question, answer, source_ids, knowledge_pairs, session_id):
43
  """
44
  Log a question, answer, source IDs, and knowledge base question-answer pairs to the Google Sheet.
45
 
 
56
  knowledge_answer_2 = knowledge_pairs[1][1] if len(knowledge_pairs) > 1 else "N/A"
57
  row = [
58
  timestamp,
59
+ session_id,
60
  question,
61
  answer,
62
  source_ids,
 
73
  with open("/tmp/response_log.txt", "a") as f:
74
  f.write(f"{timestamp},{question},{answer},{source_ids},{knowledge_question_1},{knowledge_answer_1},{knowledge_question_2},{knowledge_answer_2}\n")
75
 
76
+ # === LangGraph Memory Setup ===
77
+ conn = sqlite3.connect("xeno_memory.db", check_same_thread=False)
78
+ memory = SqliteSaver(conn=conn)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ def update_memory(config, user_message, assistant_message):
81
+ full_checkpoint = memory.get(config) or {}
82
+ messages = full_checkpoint.get("channel_values", {}).get("messages", [])
83
+
84
+ messages.append({"role": "user", "content": user_message})
85
+ messages.append({"role": "assistant", "content": assistant_message})
86
+
87
+ checkpoint_to_save = {
88
+ "v": 1,
89
+ "id": str(uuid.uuid4()),
90
+ "ts": datetime.now().isoformat(),
91
+ "channel_values": {"messages": messages},
92
+ "channel_versions": {},
93
+ "versions_seen": {},
94
+ }
95
+
96
+ memory.put(config, checkpoint_to_save, {}, {})
97
+
98
  # === Intent Classification System ===
99
  class IntentClassifier:
100
  def __init__(self):
101
+ # Define intent patterns and responses
102
  self.intent_patterns = {
103
  'greeting': {
104
  'patterns': [
 
148
  for intent_name, intent_data in self.intent_patterns.items():
149
  for pattern in intent_data['patterns']:
150
  if re.search(pattern, message_lower, re.IGNORECASE):
151
+ import random
152
  response = random.choice(intent_data['responses'])
153
  return intent_name, response
154
 
 
162
  # Initialize intent classifier
163
  intent_classifier = IntentClassifier()
164
 
165
+ # === Load and Clean Knowledge Base ===
166
+ df_kb = pd.read_json("XENO_Uganda_KnowledgeBase_Advisory.json")
167
+ df_kb.dropna(subset=['Content'], inplace=True)
168
 
169
+ def prepare_documents(data):
170
+ documents, metadatas, ids = [], [], []
171
+ for item in data:
172
+ documents.append(f"Question: {item['Question']}\nAnswer: {item['Content']}")
173
+ metadatas.append({
174
+ "question": item["Question"],
175
+ "content": item["Content"],
176
+ "section": item.get("Section", ""),
177
+ "source": item.get("Source", ""),
178
+ "owner": item.get("Owner", ""),
179
+ "tag": item.get("Tag", ""),
180
+ "id": item["ID"]
181
+ })
182
+ ids.append(item["ID"])
183
+ return documents, metadatas, ids
184
+
185
+ xeno_data_list = df_kb.to_dict('records')
186
+ documents, metadatas, ids = prepare_documents(xeno_data_list)
187
+
188
+ # === Setup ChromaDB ===
189
+ try:
190
+ client = chromadb.PersistentClient(path="/tmp/xeno_db")
191
+ try:
192
+ collection = client.get_collection(name=collection_name)
193
+ print(f"Loaded existing ChromaDB collection: {collection_name}")
194
+ except:
195
+ print(f"Creating new ChromaDB collection: {collection_name}")
196
+ collection = client.create_collection(name=collection_name)
197
+ collection.add(documents=documents, metadatas=metadatas, ids=ids)
198
+ except Exception as e:
199
+ print(f"Failed to initialize ChromaDB: {e}")
200
+ raise
201
+
202
+ vector_store = Chroma(client=client, collection_name=collection_name)
203
+ retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 4})
204
 
205
  # === Prompt System ===
206
  SYSTEM_PROMPT = """You are a friendly XENO Support Assistant, an AI-powered helpful and professional customer service representative.
 
229
  knowledge_pairs.append((question, answer))
230
  return formatted_context, source_ids, knowledge_pairs
231
 
232
+ # === LLM Generation (Refactored) ===
233
+ def generate_xeno_response(context, question, chat_history):
234
+ """Generates a response but does NOT handle memory."""
235
  model = genai.GenerativeModel(llm_model_name)
236
+ formatted_history = "\n".join(
237
+ [f"{msg['role'].capitalize()}: {msg['content']}" for msg in chat_history]
238
+ ) if chat_history else "None"
239
+
240
+ prompt = f"{SYSTEM_PROMPT}\n### HISTORY ###\n{formatted_history}\n### CONTEXT ###\n{context}\n### QUESTION ###\n{question}"
241
+
 
 
 
 
242
  response = model.generate_content(prompt)
243
  return response.text.strip()
244
 
245
+
246
+ # === Main Interface Logic (Refactored) ===
247
+ def get_context_and_answer(message, history, session_id="default"):
248
+ """
249
+ Handles intent classification, RAG, and memory updates in one place.
250
+ """
251
+ config = {"configurable": {"thread_id": str(session_id), "checkpoint_ns": ""}}
 
 
 
252
 
253
+ full_checkpoint = memory.get(config) or {}
254
+ chat_history = full_checkpoint.get("channel_values", {}).get("messages", [])
255
+ intent, direct_response = intent_classifier.classify_intent(message)
256
 
257
+ answer = ""
258
+ source_ids = "N/A"
259
+ knowledge_pairs = []
260
+
261
+ if intent != 'query':
262
+ answer = direct_response
263
+ else:
264
+ if len(message.strip()) < 3:
265
+ answer = "I'd be happy to help! Could you please provide more details about what you'd like to know?"
266
+ else:
267
+ try:
268
+ queried_results = retriever.invoke(message)
269
+ query_embedding = genai.embed_content(model=embedding_model, content=message, task_type="retrieval_query")['embedding']
270
+
271
+ doc_embeddings = [genai.embed_content(model=embedding_model, content=doc.page_content, task_type="retrieval_document")['embedding'] for doc in queried_results]
272
+
273
+ cosine_scores = util.cos_sim(torch.tensor(query_embedding).float(), torch.tensor(doc_embeddings).float())[0].tolist()
274
+
275
+ if max(cosine_scores) < 0.4:
276
+ answer = "I'm sorry, I couldn't find specific information for your question. Could you try rephrasing it, or contact XENO support directly?"
277
+ else:
278
+ context, source_ids_list, knowledge_pairs = process_context(queried_results, cosine_scores)
279
+ answer = generate_xeno_response(context, message, chat_history)
280
+ source_ids = ", ".join(source_ids_list)
281
 
282
+ except Exception as e:
283
+ print(f"Error during RAG processing: {e}")
284
+ answer = "I apologize, but I'm having a technical issue. Please try again shortly or contact XENO support."
 
 
285
 
 
 
 
286
  update_memory(config, message, answer)
287
+ log_response(message, answer, source_ids, knowledge_pairs, session_id)
288
+
289
  return answer
290
 
291
+ # === Enhanced Gradio UI ===
292
+ def respond(message, history, session_id):
293
+ """Gradio's main response function."""
294
+ if not session_id:
295
+ session_id = str(uuid.uuid4())
296
+
297
+ response = get_context_and_answer(message, history, session_id)
298
+
299
+ config = {"configurable": {"thread_id": str(session_id), "checkpoint_ns": ""}}
300
+ updated_messages = (memory.get(config) or {}).get("messages", [])
301
+
302
+ history.append({"role": "user", "content": message})
303
+ history.append({"role": "assistant", "content": response})
304
+
305
+ return "", history
306
+ def create_interface():
307
+ with gr.Blocks() as demo:
308
+ gr.Markdown("""ASKXENO
309
+
310
+ **Welcome to XENO AI Support!**
311
+ I can help you with questions about XENO financial services including:
312
+ • Account management and setup
313
+ • Transaction processes and fees
314
+ • Platform features and troubleshooting
315
+ • General service information
316
+ *Simply type your question below to get started!*
317
+ """)
318
+
319
+ session_id_box = gr.Textbox(label="Session ID", value=str(uuid.uuid4()), interactive=True)
320
+
321
+ chatbot = gr.Chatbot(label="XENO Assistant", bubble_full_width=False, height=500, type="messages")
322
+ msg = gr.Textbox(label="Your Message", placeholder="Type your question here...")
323
+
324
+ msg.submit(respond, [msg, chatbot, session_id_box], [msg, chatbot])
325
+ return demo
326
 
327
  if __name__ == "__main__":
328
+ iface = create_interface()
329
+ iface.launch(share=False, server_name="0.0.0.0", server_port=7860)