Sebunya commited on
Commit
fae5677
·
verified ·
1 Parent(s): 0716774

edited app.py file

Browse files
Files changed (1) hide show
  1. app.py +126 -152
app.py CHANGED
@@ -1,4 +1,3 @@
1
- import uuid
2
  import os
3
  import gradio as gr
4
  import pandas as pd
@@ -10,12 +9,12 @@ import chromadb
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,7 +38,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, session_id):
43
  """
44
  Log a question, answer, source IDs, and knowledge base question-answer pairs to the Google Sheet.
45
 
@@ -50,13 +49,13 @@ def log_response(question, answer, source_ids, knowledge_pairs, session_id):
50
  knowledge_pairs (list): List of tuples containing (question, answer) from the knowledge base.
51
  """
52
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
53
  knowledge_question_1 = knowledge_pairs[0][0] if len(knowledge_pairs) > 0 else "N/A"
54
  knowledge_answer_1 = knowledge_pairs[0][1] if len(knowledge_pairs) > 0 else "N/A"
55
  knowledge_question_2 = knowledge_pairs[1][0] if len(knowledge_pairs) > 1 else "N/A"
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,28 +72,46 @@ def log_response(question, answer, source_ids, knowledge_pairs, session_id):
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):
@@ -148,7 +165,6 @@ class IntentClassifier:
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,45 +178,32 @@ class IntentClassifier:
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,101 +232,72 @@ def process_context(results, cosine_scores, max_results=2):
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)
 
 
1
  import os
2
  import gradio as gr
3
  import pandas as pd
 
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
 
19
  # === Configuration ===
20
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
38
  # Open the Google Sheet
39
  sheet = client_gspread.open("Response_Log").sheet1
40
 
41
+ def log_response(question, answer, source_ids, knowledge_pairs):
42
  """
43
  Log a question, answer, source IDs, and knowledge base question-answer pairs to the Google Sheet.
44
 
 
49
  knowledge_pairs (list): List of tuples containing (question, answer) from the knowledge base.
50
  """
51
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
52
+ # Prepare row with user question, answer, source IDs, and knowledge base pairs
53
  knowledge_question_1 = knowledge_pairs[0][0] if len(knowledge_pairs) > 0 else "N/A"
54
  knowledge_answer_1 = knowledge_pairs[0][1] if len(knowledge_pairs) > 0 else "N/A"
55
  knowledge_question_2 = knowledge_pairs[1][0] if len(knowledge_pairs) > 1 else "N/A"
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
  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):
 
165
  for intent_name, intent_data in self.intent_patterns.items():
166
  for pattern in intent_data['patterns']:
167
  if re.search(pattern, message_lower, re.IGNORECASE):
 
168
  response = random.choice(intent_data['responses'])
169
  return intent_name, response
170
 
 
178
  # Initialize intent classifier
179
  intent_classifier = IntentClassifier()
180
 
181
+ # === LangGraph Memory Setup ===
182
+ conn = sqlite3.connect("xeno_memory.db", check_same_thread=False)
183
+ memory = SqliteSaver(conn=conn)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ def update_memory(config, user_message, assistant_message):
186
+ current_checkpoint = memory.get(config) or {
187
+ "v": 1,
188
+ "ts": datetime.now().isoformat(),
189
+ "channel_values": {"messages": []},
190
+ "channel_versions": {},
191
+ "versions_seen": {},
192
+ "pending_sends": [],
193
+ }
194
+ messages = current_checkpoint["channel_values"].get("messages", [])
195
+
196
+ messages.append({"role": "user", "content": user_message})
197
+ messages.append({"role": "assistant", "content": assistant_message})
198
+
199
+ # Update version for messages channel
200
+ current_version = current_checkpoint["channel_versions"].get("messages", 0) + 1
201
+ current_checkpoint["channel_versions"]["messages"] = current_version
202
+
203
+ current_checkpoint["channel_values"]["messages"] = messages
204
+ current_checkpoint["ts"] = datetime.now().isoformat()
205
+
206
+ memory.put(config, current_checkpoint, {})
207
 
208
  # === Prompt System ===
209
  SYSTEM_PROMPT = """You are a friendly XENO Support Assistant, an AI-powered helpful and professional customer service representative.
 
232
  knowledge_pairs.append((question, answer))
233
  return formatted_context, source_ids, knowledge_pairs
234
 
235
+ # === LLM Generation ===
236
+ def generate_xeno_response(context, question, conversation_history=""):
 
237
  model = genai.GenerativeModel(llm_model_name)
238
+ prompt = f"""{SYSTEM_PROMPT}
239
+
240
+ ### PREVIOUS CONVERSATION ###
241
+ {conversation_history}
242
+
243
+ ### CONTEXT ###
244
+ {context}
245
+
246
+ ### QUESTION ###
247
+ {question}"""
248
  response = model.generate_content(prompt)
249
  return response.text.strip()
250
 
251
+ # === Main Interface Logic ===
252
+ def get_context_and_answer(message, history):
253
+ config = {"configurable": {"thread_id": "xeno_default"}} # Fixed thread ID for simplicity; consider per-session in production
 
 
 
 
254
 
255
+ # Classify intent first
256
+ intent, predefined_response = intent_classifier.classify_intent(message)
 
257
 
258
+ if predefined_response:
259
+ # Handle simple intents with predefined responses
260
+ log_response(message, predefined_response, "N/A", [])
261
+ update_memory(config, message, predefined_response)
262
+ return predefined_response
263
+
264
+ # Load conversation history for queries
265
+ current_checkpoint = memory.get(config) or {"channel_values": {"messages": []}}
266
+ previous_messages = current_checkpoint["channel_values"].get("messages", [])
267
+ conversation_history = "\n".join([f"{msg['role'].capitalize()}: {msg['content']}" for msg in previous_messages])
268
+
269
+ # For queries, proceed with RAG
270
+ queried_results = retriever.invoke(message)
271
+ query_embedding = genai.embed_content(model=embedding_model,
272
+ content=message,
273
+ task_type="retrieval_query")['embedding']
274
+ cosine_scores = []
275
+ for doc in queried_results:
276
+ doc_embedding = genai.embed_content(model=embedding_model,
277
+ content=doc.page_content,
278
+ task_type="retrieval_document")['embedding']
279
+ cos_sim = util.cos_sim(torch.tensor(query_embedding).float(), torch.tensor(doc_embedding).float())[0][0].item()
280
+ cosine_scores.append(cos_sim)
 
281
 
282
+ if max(cosine_scores) < 0.6:
283
+ answer = "I'm sorry, I couldn't find the specific information you're looking for in my knowledge base."
284
+ log_response(message, answer, "N/A", [])
285
+ update_memory(config, message, answer)
286
+ return answer
287
 
288
+ context, source_ids, knowledge_pairs = process_context(queried_results, cosine_scores)
289
+ answer = generate_xeno_response(context, message, conversation_history)
290
+ log_response(message, answer, ", ".join(source_ids), knowledge_pairs)
291
  update_memory(config, message, answer)
 
 
292
  return answer
293
 
294
+ # === Gradio UI ===
295
+ iface = gr.ChatInterface(
296
+ fn=get_context_and_answer,
297
+ title="ASKXENO",
298
+ description="Ask anything about XENO's financial services.",
299
+ theme="soft"
300
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  if __name__ == "__main__":
303
+ iface.launch(share=False)