Sebunya commited on
Commit
8fccc78
·
verified ·
1 Parent(s): 8ac3f9d

updating the ui take 2

Browse files
Files changed (1) hide show
  1. app.py +164 -119
app.py CHANGED
@@ -1,23 +1,21 @@
1
- import os
2
  import uuid
3
- import re
4
- import json
5
- import sqlite3
6
- import random
7
  import torch
8
  import numpy as np
9
- import pandas as pd
10
- import gradio as gr
11
- from datetime import datetime
12
  from sentence_transformers import util
13
- from typing import Tuple
14
-
15
  import google.generativeai as genai
16
  import chromadb
17
  from langchain_chroma import Chroma
18
  import gspread
19
  from google.oauth2.service_account import Credentials
20
  from langgraph.checkpoint.sqlite import SqliteSaver
 
 
 
 
 
21
 
22
  # === Configuration ===
23
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
@@ -31,96 +29,126 @@ def get_google_sheets_credentials():
31
  if not credentials_json:
32
  raise ValueError("GOOGLE_SHEETS_CREDENTIALS environment variable not set.")
33
  credentials_dict = json.loads(credentials_json)
34
- scope = [
35
- "https://spreadsheets.google.com/feeds",
36
- "https://www.googleapis.com/auth/drive"
37
- ]
38
- return Credentials.from_service_account_info(credentials_dict, scopes=scope)
39
 
40
  client_gspread = gspread.authorize(get_google_sheets_credentials())
41
  sheet = client_gspread.open("Response_Log").sheet1
42
 
43
  def log_response(question, answer, source_ids, knowledge_pairs, session_id):
44
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
45
- kq1, ka1 = knowledge_pairs[0] if len(knowledge_pairs) > 0 else ("N/A", "N/A")
46
- kq2, ka2 = knowledge_pairs[1] if len(knowledge_pairs) > 1 else ("N/A", "N/A")
47
-
 
48
  row = [
49
- timestamp, session_id, question, answer, source_ids, kq1, ka1, kq2, ka2
 
 
 
 
 
 
 
 
50
  ]
51
  try:
52
  sheet.append_row(row)
 
53
  except Exception as e:
 
54
  with open("/tmp/response_log.txt", "a") as f:
55
- f.write(",".join(map(str, row)) + "\n")
56
 
57
- # === Memory Setup ===
58
  conn = sqlite3.connect("xeno_memory.db", check_same_thread=False)
59
  memory = SqliteSaver(conn=conn)
60
 
61
  def update_memory(config, user_message, assistant_message):
62
- checkpoint = memory.get(config) or {}
63
- messages = checkpoint.get("channel_values", {}).get("messages", [])
 
64
  messages.append({"role": "user", "content": user_message})
65
  messages.append({"role": "assistant", "content": assistant_message})
66
- memory.put(config, {
 
67
  "v": 1,
68
  "id": str(uuid.uuid4()),
69
  "ts": datetime.now().isoformat(),
70
  "channel_values": {"messages": messages},
71
  "channel_versions": {},
72
- "versions_seen": {}
73
- }, {}, {})
 
 
74
 
75
  # === Intent Classifier ===
76
  class IntentClassifier:
77
  def __init__(self):
78
  self.intent_patterns = {
79
- "greeting": {
80
- "patterns": [r"\b(hi|hello|hey|good morning|good afternoon|good evening)\b"],
81
- "responses": [
82
- "Hello! I'm XENO Assistant. How can I help you today?",
83
- "Hi there! What can I assist you with?",
84
- "Good day! How may I assist you?"
 
 
 
 
85
  ]
86
  },
87
- "thanks": {
88
- "patterns": [r"\b(thank you|thanks|appreciate)\b"],
89
- "responses": [
90
- "You're welcome! Anything else I can help you with?",
91
- "Happy to help!",
92
- "Glad I could assist!"
 
 
 
 
93
  ]
94
  },
95
- "goodbye": {
96
- "patterns": [r"\b(bye|goodbye|see you)\b"],
97
- "responses": [
98
- "Goodbye! Have a great day!",
99
- "Take care! Come back anytime.",
100
- "See you later!"
 
 
 
 
101
  ]
102
  }
103
  }
104
-
105
  def classify_intent(self, message: str) -> Tuple[str, str]:
106
  message_lower = message.lower().strip()
107
  for intent_name, intent_data in self.intent_patterns.items():
108
- for pattern in intent_data["patterns"]:
109
- if re.search(pattern, message_lower):
110
- return intent_name, random.choice(intent_data["responses"])
111
- return "query", ""
 
 
 
 
 
112
 
113
  intent_classifier = IntentClassifier()
114
 
115
  # === Load Knowledge Base ===
116
  df_kb = pd.read_json("XENO_Uganda_KnowledgeBase_Advisory.json")
117
- df_kb.dropna(subset=["Content"], inplace=True)
118
 
119
  def prepare_documents(data):
120
- docs, metas, ids = [], [], []
121
  for item in data:
122
- docs.append(f"Question: {item['Question']}\nAnswer: {item['Content']}")
123
- metas.append({
124
  "question": item["Question"],
125
  "content": item["Content"],
126
  "section": item.get("Section", ""),
@@ -130,85 +158,102 @@ def prepare_documents(data):
130
  "id": item["ID"]
131
  })
132
  ids.append(item["ID"])
133
- return docs, metas, ids
134
 
135
- docs, metas, ids = prepare_documents(df_kb.to_dict("records"))
 
136
 
137
- # === ChromaDB ===
138
- client = chromadb.PersistentClient(path="/tmp/xeno_db")
139
  try:
140
- collection = client.get_collection(name=collection_name)
141
- except:
142
- collection = client.create_collection(name=collection_name)
143
- collection.add(documents=docs, metadatas=metas, ids=ids)
 
 
 
 
 
 
 
144
 
145
- retriever = Chroma(client=client, collection_name=collection_name) \
146
- .as_retriever(search_type="similarity", search_kwargs={"k": 4})
147
 
148
  # === Prompt System ===
149
- SYSTEM_PROMPT = """You are a friendly XENO Support Assistant.
150
- Use only the knowledge base context to answer questions.
151
- Do not hallucinate. If no relevant info, politely decline.
152
- Remember previous conversations."""
153
 
154
- # === Context Processor ===
155
  def process_context(results, cosine_scores, max_results=2):
156
- sorted_idx = np.argsort(cosine_scores)[::-1][:max_results]
157
- ctx, src_ids, kpairs = "", [], []
158
- for i, idx in enumerate(sorted_idx, 1):
159
- q = results[idx].metadata.get("question", "N/A")
160
- a = results[idx].metadata.get("content", "N/A")
161
- ctx += f"Knowledge Entry {i}:\nQ: {q}\nA: {a}\n" + "-" * 40 + "\n"
162
- src_ids.append(results[idx].metadata.get("id", "N/A"))
163
- kpairs.append((q, a))
164
- return ctx, src_ids, kpairs
165
-
166
- # === LLM Generation ===
167
- def generate_xeno_response(context, question, history):
 
 
 
168
  model = genai.GenerativeModel(llm_model_name)
169
- hist_text = "\n".join([f"{m['role'].capitalize()}: {m['content']}" for m in history]) if history else "None"
170
- prompt = f"{SYSTEM_PROMPT}\n### HISTORY ###\n{hist_text}\n### CONTEXT ###\n{context}\n### QUESTION ###\n{question}"
171
- return model.generate_content(prompt).text.strip()
172
-
173
- # === Chat Handler ===
174
- def chat_handler(message, history):
175
- session_id = "default" # could be made dynamic if needed
176
- config = {"configurable": {"thread_id": session_id, "checkpoint_ns": ""}}
177
- checkpoint = memory.get(config) or {}
178
- chat_history = checkpoint.get("channel_values", {}).get("messages", [])
179
-
180
- intent, quick_reply = intent_classifier.classify_intent(message)
181
- answer, src_ids, kpairs = "", "N/A", []
182
-
183
- if intent != "query":
184
- answer = quick_reply
185
- else:
186
- try:
187
- results = retriever.invoke(message)
188
- query_emb = genai.embed_content(model=embedding_model, content=message, task_type="retrieval_query")['embedding']
189
- doc_embs = [genai.embed_content(model=embedding_model, content=doc.page_content, task_type="retrieval_document")['embedding'] for doc in results]
190
- cos_scores = util.cos_sim(torch.tensor(query_emb).float(), torch.tensor(doc_embs).float())[0].tolist()
191
-
192
- if max(cos_scores) < 0.4:
193
- answer = "I'm sorry, I couldn't find specific information for your question."
194
- else:
195
- ctx, src_ids_list, kpairs = process_context(results, cos_scores)
196
- answer = generate_xeno_response(ctx, message, chat_history)
197
- src_ids = ", ".join(src_ids_list)
198
- except Exception as e:
199
- answer = "I’m having a technical issue. Please try again later."
 
 
 
 
 
 
 
 
 
 
200
 
201
  update_memory(config, message, answer)
202
- log_response(message, answer, src_ids, kpairs, session_id)
203
  return answer
204
 
205
  # === Clean ChatInterface UI ===
206
- iface = gr.ChatInterface(
207
- fn=chat_handler,
208
- title="ASKXENO",
209
- description="Ask anything about XENO's financial services.",
210
- theme="soft"
211
- )
212
-
213
  if __name__ == "__main__":
 
 
 
 
 
 
214
  iface.launch(share=False, server_name="0.0.0.0", server_port=7860)
 
 
1
  import uuid
2
+ import os
3
+ import gradio as gr
4
+ import pandas as pd
 
5
  import torch
6
  import numpy as np
 
 
 
7
  from sentence_transformers import util
 
 
8
  import google.generativeai as genai
9
  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 Tuple
19
 
20
  # === Configuration ===
21
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
29
  if not credentials_json:
30
  raise ValueError("GOOGLE_SHEETS_CREDENTIALS environment variable not set.")
31
  credentials_dict = json.loads(credentials_json)
32
+ scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
33
+ creds = Credentials.from_service_account_info(credentials_dict, scopes=scope)
34
+ return creds
 
 
35
 
36
  client_gspread = gspread.authorize(get_google_sheets_credentials())
37
  sheet = client_gspread.open("Response_Log").sheet1
38
 
39
  def log_response(question, answer, source_ids, knowledge_pairs, session_id):
40
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
41
+ knowledge_question_1 = knowledge_pairs[0][0] if len(knowledge_pairs) > 0 else "N/A"
42
+ knowledge_answer_1 = knowledge_pairs[0][1] if len(knowledge_pairs) > 0 else "N/A"
43
+ knowledge_question_2 = knowledge_pairs[1][0] if len(knowledge_pairs) > 1 else "N/A"
44
+ knowledge_answer_2 = knowledge_pairs[1][1] if len(knowledge_pairs) > 1 else "N/A"
45
  row = [
46
+ timestamp,
47
+ session_id,
48
+ question,
49
+ answer,
50
+ source_ids,
51
+ knowledge_question_1,
52
+ knowledge_answer_1,
53
+ knowledge_question_2,
54
+ knowledge_answer_2
55
  ]
56
  try:
57
  sheet.append_row(row)
58
+ print(f"Logged: {question} | Source IDs: {source_ids}")
59
  except Exception as e:
60
+ print(f"Failed to log to Google Sheet: {e}")
61
  with open("/tmp/response_log.txt", "a") as f:
62
+ f.write(f"{timestamp},{question},{answer},{source_ids},{knowledge_question_1},{knowledge_answer_1},{knowledge_question_2},{knowledge_answer_2}\n")
63
 
64
+ # === LangGraph Memory ===
65
  conn = sqlite3.connect("xeno_memory.db", check_same_thread=False)
66
  memory = SqliteSaver(conn=conn)
67
 
68
  def update_memory(config, user_message, assistant_message):
69
+ full_checkpoint = memory.get(config) or {}
70
+ messages = full_checkpoint.get("channel_values", {}).get("messages", [])
71
+
72
  messages.append({"role": "user", "content": user_message})
73
  messages.append({"role": "assistant", "content": assistant_message})
74
+
75
+ checkpoint_to_save = {
76
  "v": 1,
77
  "id": str(uuid.uuid4()),
78
  "ts": datetime.now().isoformat(),
79
  "channel_values": {"messages": messages},
80
  "channel_versions": {},
81
+ "versions_seen": {},
82
+ }
83
+
84
+ memory.put(config, checkpoint_to_save, {}, {})
85
 
86
  # === Intent Classifier ===
87
  class IntentClassifier:
88
  def __init__(self):
89
  self.intent_patterns = {
90
+ 'greeting': {
91
+ 'patterns': [
92
+ r'\b(hi|hello|hey|good morning|good afternoon|good evening|greetings)\b',
93
+ r'^(hi|hello|hey)[\s!.]*$',
94
+ r'\b(how are you|how do you do)\b'
95
+ ],
96
+ 'responses': [
97
+ "Hello! I'm XENO Assistant. How can I help you with XENO financial services today?",
98
+ "Hi there! I'm here to assist you with any questions about XENO services. What can I help you with?",
99
+ "Good day! Welcome to XENO Support. How may I assist you today?"
100
  ]
101
  },
102
+ 'thanks': {
103
+ 'patterns': [
104
+ r'\b(thank you|thanks|thank u|thx|appreciate|grateful)\b',
105
+ r'^(thanks|thank you)[\s!.]*$',
106
+ r'\b(much appreciated|thanks a lot|thank you so much)\b'
107
+ ],
108
+ 'responses': [
109
+ "You're welcome! Is there anything else I can help you with regarding XENO services?",
110
+ "Happy to help! Feel free to ask if you have any other questions about XENO.",
111
+ "Glad I could assist you! Let me know if you need help with anything else."
112
  ]
113
  },
114
+ 'goodbye': {
115
+ 'patterns': [
116
+ r'\b(bye|goodbye|see you|farewell|take care|have a good day)\b',
117
+ r'^(bye|goodbye)[\s!.]*$',
118
+ r'\b(talk to you later|see you later|until next time)\b'
119
+ ],
120
+ 'responses': [
121
+ "Goodbye! Thank you for using XENO services. Have a great day!",
122
+ "Take care! Feel free to return anytime you need help with XENO services.",
123
+ "Have a wonderful day! Don't hesitate to reach out if you need assistance with XENO."
124
  ]
125
  }
126
  }
127
+
128
  def classify_intent(self, message: str) -> Tuple[str, str]:
129
  message_lower = message.lower().strip()
130
  for intent_name, intent_data in self.intent_patterns.items():
131
+ for pattern in intent_data['patterns']:
132
+ if re.search(pattern, message_lower, re.IGNORECASE):
133
+ import random
134
+ response = random.choice(intent_data['responses'])
135
+ return intent_name, response
136
+ return 'query', ''
137
+
138
+ def is_simple_intent(self, intent: str) -> bool:
139
+ return intent in ['greeting', 'thanks']
140
 
141
  intent_classifier = IntentClassifier()
142
 
143
  # === Load Knowledge Base ===
144
  df_kb = pd.read_json("XENO_Uganda_KnowledgeBase_Advisory.json")
145
+ df_kb.dropna(subset=['Content'], inplace=True)
146
 
147
  def prepare_documents(data):
148
+ documents, metadatas, ids = [], [], []
149
  for item in data:
150
+ documents.append(f"Question: {item['Question']}\nAnswer: {item['Content']}")
151
+ metadatas.append({
152
  "question": item["Question"],
153
  "content": item["Content"],
154
  "section": item.get("Section", ""),
 
158
  "id": item["ID"]
159
  })
160
  ids.append(item["ID"])
161
+ return documents, metadatas, ids
162
 
163
+ xeno_data_list = df_kb.to_dict('records')
164
+ documents, metadatas, ids = prepare_documents(xeno_data_list)
165
 
166
+ # === ChromaDB Setup ===
 
167
  try:
168
+ client = chromadb.PersistentClient(path="/tmp/xeno_db")
169
+ try:
170
+ collection = client.get_collection(name=collection_name)
171
+ print(f"Loaded existing ChromaDB collection: {collection_name}")
172
+ except:
173
+ print(f"Creating new ChromaDB collection: {collection_name}")
174
+ collection = client.create_collection(name=collection_name)
175
+ collection.add(documents=documents, metadatas=metadatas, ids=ids)
176
+ except Exception as e:
177
+ print(f"Failed to initialize ChromaDB: {e}")
178
+ raise
179
 
180
+ vector_store = Chroma(client=client, collection_name=collection_name)
181
+ retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 4})
182
 
183
  # === Prompt System ===
184
+ SYSTEM_PROMPT = """You are a friendly XENO Support Assistant...
185
+ """
 
 
186
 
187
+ # === Context Processing ===
188
  def process_context(results, cosine_scores, max_results=2):
189
+ sorted_indices = np.argsort(cosine_scores)[::-1][:max_results]
190
+ formatted_context = ""
191
+ source_ids = []
192
+ knowledge_pairs = []
193
+ for i, idx in enumerate(sorted_indices, 1):
194
+ result = results[idx]
195
+ question = result.metadata.get('question', 'N/A')
196
+ answer = result.metadata.get('content', 'N/A')
197
+ formatted_context += f"Knowledge Entry {i}:\nQ: {question}\nA: {answer}\n" + "-" * 40 + "\n"
198
+ source_ids.append(result.metadata.get('id', 'N/A'))
199
+ knowledge_pairs.append((question, answer))
200
+ return formatted_context, source_ids, knowledge_pairs
201
+
202
+ # === LLM Response ===
203
+ def generate_xeno_response(context, question, chat_history):
204
  model = genai.GenerativeModel(llm_model_name)
205
+ formatted_history = "\n".join(
206
+ [f"{msg['role'].capitalize()}: {msg['content']}" for msg in chat_history]
207
+ ) if chat_history else "None"
208
+ prompt = f"{SYSTEM_PROMPT}\n### HISTORY ###\n{formatted_history}\n### CONTEXT ###\n{context}\n### QUESTION ###\n{question}"
209
+ response = model.generate_content(prompt)
210
+ return response.text.strip()
211
+
212
+ # === Main Chat Logic ===
213
+ def get_context_and_answer(message, history, session_id=None):
214
+ if session_id is None:
215
+ session_id = str(uuid.uuid4())
216
+ config = {"configurable": {"thread_id": str(session_id), "checkpoint_ns": ""}}
217
+ full_checkpoint = memory.get(config) or {}
218
+ chat_history = full_checkpoint.get("channel_values", {}).get("messages", [])
219
+
220
+ intent, direct_response = intent_classifier.classify_intent(message)
221
+ answer = ""
222
+ source_ids = "N/A"
223
+ knowledge_pairs = []
224
+
225
+ if intent != 'query':
226
+ answer = direct_response
227
+ else:
228
+ if len(message.strip()) < 3:
229
+ answer = "I'd be happy to help! Could you please provide more details?"
230
+ else:
231
+ try:
232
+ queried_results = retriever.invoke(message)
233
+ query_embedding = genai.embed_content(model=embedding_model, content=message, task_type="retrieval_query")['embedding']
234
+ doc_embeddings = [genai.embed_content(model=embedding_model, content=doc.page_content, task_type="retrieval_document")['embedding'] for doc in queried_results]
235
+ cosine_scores = util.cos_sim(torch.tensor(query_embedding).float(), torch.tensor(doc_embeddings).float())[0].tolist()
236
+
237
+ if max(cosine_scores) < 0.4:
238
+ answer = "I'm sorry, I couldn't find specific information for your question."
239
+ else:
240
+ context, source_ids_list, knowledge_pairs = process_context(queried_results, cosine_scores)
241
+ answer = generate_xeno_response(context, message, chat_history)
242
+ source_ids = ", ".join(source_ids_list)
243
+ except Exception as e:
244
+ print(f"Error during RAG processing: {e}")
245
+ answer = "I apologize, but I'm having a technical issue."
246
 
247
  update_memory(config, message, answer)
248
+ log_response(message, answer, source_ids, knowledge_pairs, session_id)
249
  return answer
250
 
251
  # === Clean ChatInterface UI ===
 
 
 
 
 
 
 
252
  if __name__ == "__main__":
253
+ iface = gr.ChatInterface(
254
+ fn=lambda message, history: (get_context_and_answer(message, history, session_id=str(uuid.uuid4())), history),
255
+ title="ASKXENO",
256
+ description="Ask anything about XENO's financial services.",
257
+ theme="soft"
258
+ )
259
  iface.launch(share=False, server_name="0.0.0.0", server_port=7860)