Irakoze commited on
Commit
671d0de
β€’
1 Parent(s): becf7dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -300
app.py CHANGED
@@ -1,322 +1,121 @@
1
- import os
2
  import gradio as gr
 
 
3
  import logging
4
- import asyncio
5
  from dotenv import load_dotenv
6
- from langchain.prompts import PromptTemplate
7
- from langchain_qdrant import QdrantVectorStore
8
- from langchain.chains import RetrievalQA
9
- from langchain_groq import ChatGroq
10
- from qdrant_client.models import PointStruct, VectorParams, Distance
11
- import uuid
12
- from qdrant_client.http import models
13
- from datetime import datetime
14
- from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
15
- from qdrant_client import QdrantClient
16
- import cohere
17
- from langchain.retrievers import ContextualCompressionRetriever
18
- from langchain_cohere import CohereRerank
19
- import re
20
- from translation_service import TranslationService
21
-
22
  # Load environment variables
23
  load_dotenv()
24
-
25
- # Initialize logging with INFO level and detailed format
26
  logging.basicConfig(
27
- filename='app.log',
28
  level=logging.INFO,
29
  format='%(asctime)s - %(levelname)s - %(message)s'
30
  )
31
 
32
- # Initialize services
33
- translator = TranslationService()
34
-
35
- def initialize_database_client():
36
- """Initialize Qdrant client"""
37
- try:
38
- client = QdrantClient(
39
- url=os.getenv("QDURL"),
40
- api_key=os.getenv("API_KEY1"),
41
- verify=True # Set to True if using SSL
42
- )
43
- logging.info("Qdrant client initialized successfully.")
44
- return client
45
- except Exception as e:
46
- logging.error(f"Failed to initialize Qdrant client: {e}")
47
- raise
48
-
49
- def initialize_llm():
50
- """Initialize LLM with fallback"""
51
- try:
52
- llm = ChatGroq(
53
- temperature=0,
54
- model_name="llama3-8b-8192",
55
- api_key=os.getenv("GROQ_API_KEY")
56
- )
57
- logging.info("ChatGroq initialized with model llama3-8b-8192.")
58
- return llm
59
- except Exception as e:
60
- logging.warning(f"Failed to initialize ChatGroq with llama3: {e}. Falling back to mixtral.")
61
- try:
62
- llm = ChatGroq(
63
- temperature=0,
64
- model_name="mixtral-8x7b-32768",
65
- api_key=os.getenv("GROQ_API_KEY")
66
- )
67
- logging.info("ChatGroq initialized with fallback model mixtral-8x7b-32768.")
68
- return llm
69
- except Exception as fallback_e:
70
- logging.error(f"Failed to initialize fallback LLM: {fallback_e}")
71
- raise
72
-
73
- def initialize_services():
74
- """Initialize all services"""
75
  try:
76
- # Initialize Qdrant client
77
- client = initialize_database_client()
78
-
79
- # Initialize embeddings
80
- embeddings = FastEmbedEmbeddings(model_name="nomic-ai/nomic-embed-text-v1.5-Q")
81
- logging.info("FastEmbedEmbeddings initialized successfully.")
82
-
83
- # Initialize Qdrant DB
84
- db = QdrantVectorStore(
85
- client=client,
86
- embedding=embeddings,
87
- collection_name="RR4"
88
- )
89
- logging.info("QdrantVectorStore initialized with collection 'RR4'.")
90
 
91
- # Initialize retriever with reranker
92
- cohere_client = cohere.Client(api_key=os.getenv("COHERE_API_KEY"))
93
- reranker = CohereRerank(
94
- client=cohere_client,
95
- top_n=6,
96
- model="rerank-multilingual-v3.0"
97
- )
98
- base_retriever = db.as_retriever(search_kwargs={"k": 30})
99
- retriever = ContextualCompressionRetriever(
100
- base_compressor=reranker,
101
- base_retriever=base_retriever
102
  )
103
- logging.info("Retriever with reranker initialized successfully.")
104
 
105
- # Initialize LLM
106
- llm = initialize_llm()
107
 
108
- return retriever, llm
109
- except Exception as e:
110
- logging.error(f"Service initialization error: {str(e)}")
111
- raise
112
 
113
- def initialize_feedback_collection():
114
- """Initialize and verify feedback collection"""
115
  try:
116
- client = initialize_database_client()
117
-
118
- # Check if collection exists
119
- collections = client.get_collections().collections
120
- collection_exists = any(c.name == "chat_feedback" for c in collections)
121
-
122
- if not collection_exists:
123
- # Create collection with proper configuration
124
- client.create_collection(
125
- collection_name="chat_feedback",
126
- vectors_config=VectorParams(
127
- size=768, # Ensure this matches the embedding size
128
- distance=Distance.COSINE
129
- )
130
- )
131
- logging.info("Created 'chat_feedback' collection with vector size 768 and Cosine distance.")
132
- else:
133
- logging.info("'chat_feedback' collection already exists.")
134
-
135
- # Verify collection exists and has correct configuration
136
- collection_info = client.get_collection("chat_feedback")
137
- if collection_info.config.params.vectors.size != 768:
138
- raise ValueError("Incorrect vector size in 'chat_feedback' collection.")
139
- logging.info("'chat_feedback' collection verified successfully with correct vector size.")
140
-
141
- return True
142
- except Exception as e:
143
- logging.error(f"Failed to initialize feedback collection: {e}")
144
- raise
145
-
146
- async def submit_feedback(feedback_type, chat_history, language_choice):
147
- """Submit feedback with improved error handling and logging."""
148
- try:
149
- if not chat_history or len(chat_history) < 2:
150
- logging.warning("Attempted to submit feedback with insufficient chat history.")
151
  return "No recent interaction to provide feedback for."
152
 
153
- # Get last question and answer
154
- last_interaction = chat_history[-4:]
155
- question = last_interaction[0].get("content", "").strip()
156
- answer = last_interaction[1].get("content", "").strip()
157
-
158
- if not question or not answer:
159
- logging.warning("Question or answer content is missing.")
160
- return "Incomplete interaction data. Cannot submit feedback."
161
-
162
- logging.info(f"Processing feedback for question: {question[:50]}...")
163
-
164
- # Initialize client
165
- client = initialize_database_client()
166
-
167
- # Create point ID
168
- point_id = str(uuid.uuid4())
169
-
170
- # Create payload
171
  payload = {
172
- "question": question,
173
- "answer": answer,
174
  "language": language_choice,
175
- "timestamp": datetime.utcnow().isoformat(),
176
- "feedback": feedback_type
177
  }
178
 
179
- # Initialize embeddings
180
- embeddings = FastEmbedEmbeddings(model_name="nomic-ai/nomic-embed-text-v1.5-Q")
181
-
182
- # Create embeddings for the Q&A pair
183
- try:
184
- embedding_text = f"{question} {answer}"
185
- vector = await asyncio.to_thread(embeddings.embed_query, embedding_text)
186
- logging.info(f"Generated embedding vector of length {len(vector)}.")
187
- except Exception as embed_error:
188
- logging.error(f"Embedding generation failed: {embed_error}")
189
- return "Failed to generate embeddings for your feedback."
190
-
191
- if not isinstance(vector, list) or not vector:
192
- logging.error("Invalid vector generated from embeddings.")
193
- return "Failed to generate valid embeddings for your feedback."
194
-
195
- # Create point
196
- point = PointStruct(
197
- id=point_id,
198
- payload=payload,
199
- vector=vector
200
- )
201
-
202
- # Store in Qdrant
203
- try:
204
- operation_info = await asyncio.to_thread(
205
- client.upsert,
206
- collection_name="chat_feedback",
207
- points=[point]
208
- )
209
- logging.info(f"Feedback submitted successfully: {point_id}")
210
- return "Thanks for your feedback! Your response has been recorded."
211
- except Exception as db_error:
212
- logging.error(f"Failed to upsert point to Qdrant: {db_error}")
213
- return "Sorry, there was an error submitting your feedback."
214
-
215
- except Exception as e:
216
- logging.error(f"Unexpected error in submit_feedback: {e}")
217
- return "Sorry, there was an unexpected error submitting your feedback."
218
-
219
- # Initialize services and feedback collection
220
- try:
221
- retriever, llm = initialize_services()
222
- initialize_feedback_collection()
223
- except Exception as initialization_error:
224
- logging.critical(f"Initialization failed: {initialization_error}")
225
- raise
226
-
227
- # Prompt template
228
- prompt_template = PromptTemplate(
229
- template="""You are RRA Assistant, created by Cedric to help users get tax related information in Rwanda. Your task is to answer tax-related questions using the provided context.
230
-
231
- Context: {context}
232
-
233
- User's Question: {question}
234
-
235
- Please follow these steps to answer the question:
236
-
237
- Step 1: Analyze the question
238
- Briefly explain your understanding of the question and any key points to address. If it is hi or hello, skip to step 3 and respond with a greeting.
239
-
240
- Step 2: Provide relevant information
241
- Using the context provided, give detailed information related to the question. Include specific facts, figures, or explanations from the context.
242
-
243
- Step 3: Final answer
244
- Provide a clear, concise answer to the original question. Start directly with the relevant information, avoiding phrases like "In summary" or "To conclude".
245
-
246
- Remember:
247
- - If you don't know the answer or can't find relevant information in the context, say so honestly.
248
- - Do not make up information.
249
- - Use the provided context to support your answer.
250
- - Include "For more information, call 3004" at the end of every answer.
251
-
252
- Your response:
253
- """,
254
- input_variables=['context', 'question']
255
- )
256
-
257
- async def process_query(message: str, language: str, chat_history: list) -> str:
258
- try:
259
- # Handle translation based on selected language
260
- if language == "Kinyarwanda":
261
- query = translator.translate(message, "rw", "en")
262
- logging.info(f"Translated query to English: {query}")
263
- else:
264
- query = message
265
-
266
- # Create QA chain
267
- qa = RetrievalQA.from_chain_type(
268
- llm=llm,
269
- chain_type="stuff",
270
- retriever=retriever,
271
- chain_type_kwargs={"prompt": prompt_template},
272
- return_source_documents=True
273
- )
274
-
275
- # Get response
276
- response = await asyncio.to_thread(
277
- lambda: qa.invoke({"query": query})
278
  )
279
- logging.info("QA chain invoked successfully.")
280
 
281
- # Extract final answer
282
- result_text = response.get('result', '')
283
- final_answer_start = result_text.find("Step 3: Final answer")
284
- if final_answer_start != -1:
285
- answer = result_text[final_answer_start + len("Step 3: Final answer"):].strip()
286
- else:
287
- answer = result_text
288
-
289
- # Clean up the answer
290
- answer = re.sub(r'\*\*', '', answer).strip()
291
- answer = re.sub(r'Step \d+:', '', answer).strip()
292
-
293
- # Translate response if needed
294
- if language == "Kinyarwanda":
295
- answer = translator.translate(answer, "en", "rw")
296
- logging.info(f"Translated answer to Kinyarwanda: {answer}")
297
 
298
- return answer
 
 
299
  except Exception as e:
300
- logging.error(f"Query processing error: {str(e)}")
301
- return f"An error occurred: {str(e)}"
302
-
303
- # Define separate feedback submission functions to pass feedback type correctly
304
- async def submit_positive_feedback(chat_history, language_choice):
305
- return await submit_feedback("positive", chat_history, language_choice)
306
-
307
- async def submit_negative_feedback(chat_history, language_choice):
308
- return await submit_feedback("negative", chat_history, language_choice)
309
 
310
  # Create Gradio interface
311
  with gr.Blocks(title="RRA FAQ Chatbot") as demo:
 
 
312
  gr.Markdown(
313
  """
314
  # RRA FAQ Chatbot
315
  Ask tax-related questions in English or Kinyarwanda
316
- > πŸ”’ Your questions and interactions remain private unless you choose to submit feedback, which helps improve our service.
317
  """
318
  )
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  # Add language selector
321
  language = gr.Radio(
322
  choices=["English", "Kinyarwanda"],
@@ -327,8 +126,7 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
327
  chatbot = gr.Chatbot(
328
  value=[],
329
  show_label=False,
330
- height=400,
331
- type='messages'
332
  )
333
 
334
  with gr.Row():
@@ -340,7 +138,8 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
340
  submit = gr.Button("Send")
341
 
342
  # Add feedback section
343
- with gr.Row():
 
344
  with gr.Column(scale=2):
345
  feedback_label = gr.Markdown("Was this response helpful?")
346
  with gr.Column(scale=1):
@@ -351,20 +150,77 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
351
  # Add feedback status message
352
  feedback_status = gr.Markdown("")
353
 
354
- # Connect feedback buttons to their respective functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  feedback_positive.click(
356
  fn=submit_positive_feedback,
357
- inputs=[chatbot, language],
358
  outputs=feedback_status
359
  )
360
 
361
  feedback_negative.click(
362
  fn=submit_negative_feedback,
363
- inputs=[chatbot, language],
364
  outputs=feedback_status
365
  )
 
 
 
 
 
 
 
366
 
367
- # Create two sets of examples
368
  with gr.Row() as english_examples_row:
369
  gr.Examples(
370
  examples=[
@@ -391,12 +247,6 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
391
  label="Kinyarwanda Examples"
392
  )
393
 
394
- async def respond(message, lang, chat_history):
395
- bot_message = await process_query(message, lang, chat_history)
396
- chat_history.append({"role": "user", "content": message})
397
- chat_history.append({"role": "assistant", "content": bot_message})
398
- return "", chat_history
399
-
400
  def toggle_language_interface(language_choice):
401
  if language_choice == "English":
402
  placeholder_text = "Type your tax-related question here..."
@@ -413,10 +263,19 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
413
  kinyarwanda_examples_row: gr.update(visible=True)
414
  }
415
 
416
- msg.submit(respond, [msg, language, chatbot], [msg, chatbot])
417
- submit.click(respond, [msg, language, chatbot], [msg, chatbot])
 
 
 
 
 
 
 
 
 
418
 
419
- # Update both examples visibility and placeholder when language changes
420
  language.change(
421
  fn=toggle_language_interface,
422
  inputs=language,
@@ -431,11 +290,9 @@ with gr.Blocks(title="RRA FAQ Chatbot") as demo:
431
 
432
  **Disclaimer:** This chatbot provides general tax information. For official guidance,
433
  consult RRA or call 3004.
434
- πŸ”’ **Privacy:** Your interactions remain private unless you choose to submit feedback.
435
  """
436
  )
437
 
438
- # Launch the app
439
  if __name__ == "__main__":
440
  try:
441
  demo.launch(share=False)
 
 
1
  import gradio as gr
2
+ import requests
3
+ import json
4
  import logging
5
+ import os
6
  from dotenv import load_dotenv
7
+ from typing import Dict, List, Tuple
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  # Load environment variables
9
  load_dotenv()
10
+ # Configure logging
 
11
  logging.basicConfig(
12
+ filename='gradio_frontend.log',
13
  level=logging.INFO,
14
  format='%(asctime)s - %(levelname)s - %(message)s'
15
  )
16
 
17
+ # API Configuration
18
+ API_BASE_URL=os.getenv("API_BASE_URL")
19
+ async def process_query(message: str, language: str, mode: str, chat_history: List[Dict]) -> Tuple[str, str]:
20
+ """Make API request to get chatbot response"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  try:
22
+ payload = {
23
+ "message": message,
24
+ "language": language,
25
+ "mode": mode
26
+ }
 
 
 
 
 
 
 
 
 
27
 
28
+ response = requests.post(
29
+ f"{API_BASE_URL}/api/query",
30
+ json=payload
 
 
 
 
 
 
 
 
31
  )
32
+ response.raise_for_status()
33
 
34
+ result = response.json()
35
+ return result["answer"], result.get("id")
36
 
37
+ except requests.RequestException as e:
38
+ logging.error(f"API request failed: {str(e)}")
39
+ return f"Sorry, there was an error processing your request: {str(e)}", None
 
40
 
41
+ async def submit_feedback(feedback_type: str, full_chat_history: List[Dict], language_choice: str, mode: str) -> str:
42
+ """Submit feedback via API"""
43
  try:
44
+ if mode == "Private":
45
+ return "Feedback is disabled in private mode."
46
+
47
+ if not full_chat_history or len(full_chat_history) < 2:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  return "No recent interaction to provide feedback for."
49
 
50
+ # Get the last assistant message with an ID
51
+ assistant_messages = [msg for msg in full_chat_history
52
+ if msg.get("role") == "assistant" and "id" in msg]
53
+
54
+ if not assistant_messages:
55
+ logging.error("No assistant messages with ID found in chat history")
56
+ return "No response found to provide feedback for."
57
+
58
+ last_message = assistant_messages[-1]
59
+ question_id = last_message.get("id")
60
+
61
+ if not question_id:
62
+ logging.error(f"No ID found in last message: {last_message}")
63
+ return "Question ID not found. Please try asking another question."
64
+
65
+ logging.info(f"Submitting feedback for question ID: {question_id}")
66
+
67
+ # Submit feedback
68
  payload = {
69
+ "question_id": question_id,
70
+ "feedback_type": feedback_type,
71
  "language": language_choice,
72
+ "mode": mode
 
73
  }
74
 
75
+ response = requests.post(
76
+ f"{API_BASE_URL}/api/feedback",
77
+ json=payload
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  )
79
+ response.raise_for_status()
80
 
81
+ return "Thanks for your feedback! Your response has been recorded."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ except requests.RequestException as e:
84
+ logging.error(f"Failed to submit feedback: {str(e)}")
85
+ return f"Sorry, there was an error submitting your feedback: {str(e)}"
86
  except Exception as e:
87
+ logging.error(f"Unexpected error in submit_feedback: {str(e)}")
88
+ return "An unexpected error occurred. Please try again."
 
 
 
 
 
 
 
89
 
90
  # Create Gradio interface
91
  with gr.Blocks(title="RRA FAQ Chatbot") as demo:
92
+ full_chat_history = gr.State([]) # Add this line
93
+
94
  gr.Markdown(
95
  """
96
  # RRA FAQ Chatbot
97
  Ask tax-related questions in English or Kinyarwanda
 
98
  """
99
  )
100
 
101
+ # Add mode selector
102
+ with gr.Row():
103
+ interaction_mode = gr.Radio(
104
+ choices=["Normal", "Private"],
105
+ value="Normal",
106
+ label="Interaction Mode",
107
+ info="Normal: Stores interactions to improve service | Private: No data storage"
108
+ )
109
+
110
+ with gr.Row():
111
+ gr.Markdown(
112
+ """
113
+ > πŸ“ **Data Storage Notice:**
114
+ > - Normal Mode: Questions and interactions are stored to improve our service
115
+ > - Private Mode: No data is stored, feedback feature is disabled
116
+ """
117
+ )
118
+
119
  # Add language selector
120
  language = gr.Radio(
121
  choices=["English", "Kinyarwanda"],
 
126
  chatbot = gr.Chatbot(
127
  value=[],
128
  show_label=False,
129
+ height=400
 
130
  )
131
 
132
  with gr.Row():
 
138
  submit = gr.Button("Send")
139
 
140
  # Add feedback section
141
+ feedback_container = gr.Row(visible=True)
142
+ with feedback_container:
143
  with gr.Column(scale=2):
144
  feedback_label = gr.Markdown("Was this response helpful?")
145
  with gr.Column(scale=1):
 
150
  # Add feedback status message
151
  feedback_status = gr.Markdown("")
152
 
153
+ async def respond(message, lang, mode, chat_history, full_chat_history):
154
+ """Process a user message and update chat history"""
155
+ try:
156
+ if chat_history is None:
157
+ chat_history = []
158
+ if full_chat_history is None:
159
+ full_chat_history = []
160
+
161
+ # Get response from API
162
+ bot_message, question_id = await process_query(message, lang, mode, chat_history)
163
+ if not bot_message:
164
+ return "", chat_history, full_chat_history
165
+
166
+ # Build new messages
167
+ user_message = {
168
+ "content": message,
169
+ "role": "user"
170
+ }
171
+
172
+ assistant_message = {
173
+ "content": bot_message,
174
+ "role": "assistant",
175
+ "id": question_id # Store ID in the message
176
+ }
177
+
178
+ # Append to full chat history
179
+ new_full_history = full_chat_history + [user_message, assistant_message]
180
+
181
+ # Prepare messages for chatbot display
182
+ new_chat_history = chat_history + [[message, bot_message]]
183
+ return "", new_chat_history, new_full_history
184
+
185
+ except Exception as e:
186
+ logging.error(f"Error in respond function: {e}")
187
+ return "", chat_history, full_chat_history
188
+
189
+ def update_mode(mode: str):
190
+ """Update UI when mode changes"""
191
+ is_private = (mode == "Private")
192
+ return {
193
+ feedback_container: gr.update(visible=not is_private),
194
+ feedback_status: gr.update(value="" if not is_private else "Feedback is disabled in private mode")
195
+ }
196
+
197
+ # Connect feedback buttons
198
+ async def submit_positive_feedback(full_chat_history, language_choice, mode):
199
+ return await submit_feedback("positive", full_chat_history, language_choice, mode)
200
+
201
+ async def submit_negative_feedback(full_chat_history, language_choice, mode):
202
+ return await submit_feedback("negative", full_chat_history, language_choice, mode)
203
+
204
  feedback_positive.click(
205
  fn=submit_positive_feedback,
206
+ inputs=[full_chat_history, language, interaction_mode],
207
  outputs=feedback_status
208
  )
209
 
210
  feedback_negative.click(
211
  fn=submit_negative_feedback,
212
+ inputs=[full_chat_history, language, interaction_mode],
213
  outputs=feedback_status
214
  )
215
+
216
+ # Update UI when mode changes
217
+ interaction_mode.change(
218
+ fn=update_mode,
219
+ inputs=[interaction_mode],
220
+ outputs=[feedback_container, feedback_status]
221
+ )
222
 
223
+ # Example questions
224
  with gr.Row() as english_examples_row:
225
  gr.Examples(
226
  examples=[
 
247
  label="Kinyarwanda Examples"
248
  )
249
 
 
 
 
 
 
 
250
  def toggle_language_interface(language_choice):
251
  if language_choice == "English":
252
  placeholder_text = "Type your tax-related question here..."
 
263
  kinyarwanda_examples_row: gr.update(visible=True)
264
  }
265
 
266
+ # Connect user inputs
267
+ msg.submit(
268
+ respond,
269
+ [msg, language, interaction_mode, chatbot, full_chat_history],
270
+ [msg, chatbot, full_chat_history]
271
+ )
272
+ submit.click(
273
+ respond,
274
+ [msg, language, interaction_mode, chatbot, full_chat_history],
275
+ [msg, chatbot, full_chat_history]
276
+ )
277
 
278
+ # Update interface on language change
279
  language.change(
280
  fn=toggle_language_interface,
281
  inputs=language,
 
290
 
291
  **Disclaimer:** This chatbot provides general tax information. For official guidance,
292
  consult RRA or call 3004.
 
293
  """
294
  )
295
 
 
296
  if __name__ == "__main__":
297
  try:
298
  demo.launch(share=False)