ppsingh commited on
Commit
fe9e08b
·
verified ·
1 Parent(s): 3580bbe

UI updates

Browse files
Files changed (1) hide show
  1. app.py +215 -44
app.py CHANGED
@@ -42,9 +42,9 @@ scheduler = CommitScheduler(
42
  # We need to create the local vectorstore collection once using load_chunks
43
  # vectorestore colection are stored on persistent storage so this needs to be run only once
44
  # hence, comment out line below when creating for first time
45
- vectorstores = load_chunks()
46
  # once the vectore embeddings are created we will use qdrant client to access these
47
- #vectorstores = get_local_qdrant()
48
 
49
  #####---------------------CHAT-----------------------------------------------------
50
  def start_chat(query,history):
@@ -54,12 +54,75 @@ def start_chat(query,history):
54
 
55
  def finish_chat():
56
  return (gr.update(interactive = True,value = ""))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- async def chat(query,history,sources,reports,subtype,year):
 
 
 
 
 
 
59
  """taking a query and a message history, use a pipeline (reformulation, retriever, answering)
60
  to yield a tuple of:(messages in gradio format/messages in langchain format, source documents)
61
  """
62
 
 
 
 
 
 
 
 
 
 
 
63
  print(f">> NEW QUESTION : {query}")
64
  print(f"history:{history}")
65
  print(f"sources:{sources}")
@@ -70,7 +133,7 @@ async def chat(query,history,sources,reports,subtype,year):
70
  output_query = ""
71
 
72
  ##------------------------fetch collection from vectorstore------------------------------
73
- vectorstore = vectorstores["allreports"]
74
 
75
  ##------------------------------get context----------------------------------------------
76
  context_retrieved = get_context(vectorstore=vectorstore,query=query,reports=reports,
@@ -113,6 +176,25 @@ async def chat(query,history,sources,reports,subtype,year):
113
 
114
  ##-----------------------get answer from endpoints------------------------------
115
  answer_yet = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  if model_config.get('reader','TYPE') == 'NVIDIA':
117
  chat_model = nvidia_client()
118
  async def process_stream():
@@ -138,42 +220,61 @@ async def chat(query,history,sources,reports,subtype,year):
138
  async for update in process_stream():
139
  yield update
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  else:
142
- chat_model = dedicated_endpoint()
143
  async def process_stream():
144
- # Without nonlocal, Python would create a new local variable answer_yet inside process_stream(),
145
- # instead of modifying the one from the outer scope.
146
- nonlocal answer_yet # Use the outer scope's answer_yet variable
147
- # Iterate over the streaming response chunks
148
- async for chunk in chat_model.astream(messages):
149
- token = chunk.content
150
- answer_yet += token
151
- parsed_answer = parse_output_llm_with_sources(answer_yet)
152
- history[-1] = (query, parsed_answer)
153
- yield [tuple(x) for x in history], docs_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- # Stream the response updates
156
  async for update in process_stream():
157
  yield update
158
 
159
  # logging the event
160
  try:
161
- timestamp = str(datetime.now().timestamp())
162
- logs = {
163
- "system_prompt": SYSTEM_PROMPT,
164
- "sources":sources,
165
- "reports":reports,
166
- "subtype":subtype,
167
- "year":year,
168
- "question":query,
169
- "sources":sources,
170
- "retriever":model_config.get('retriever','MODEL'),
171
- "endpoint_type":model_config.get('reader','TYPE'),
172
- "raeder":model_config.get('reader','NVIDIA_MODEL'),
173
- "docs":[doc.page_content for doc in context_retrieved],
174
- "answer": history[-1][1],
175
- "time": timestamp,
176
- }
177
  save_logs(scheduler,JSON_DATASET_PATH,logs)
178
  except Exception as e:
179
  logging.error(e)
@@ -314,7 +415,34 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
314
  return [gr.update(visible=visible_bools[i]) for i in range(len(samples))]
315
 
316
  dropdown_samples.change(change_sample_questions,dropdown_samples,samples)
317
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
  # static tab 'about us'
320
  with gr.Tab("About",elem_classes = "max-height other-tabs"):
@@ -380,21 +508,64 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
380
 
381
 
382
 
383
- # using event listeners for 1. query box 2. click on example question
384
- # https://www.gradio.app/docs/gradio/textbox#event-listeners-arguments
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  (textbox
386
- .submit(start_chat, [textbox, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_textbox")
387
- # queue must be set as False (default) so the process is not waiting for another to be finished
388
- .then(chat, [textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year], [chatbot, sources_textbox], queue=True, concurrency_limit=8, api_name="chat_textbox")
389
- .then(finish_chat, None, [textbox], api_name="finish_chat_textbox"))
 
 
 
 
390
 
391
  (examples_hidden
392
  .change(start_chat, [examples_hidden, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_examples")
393
- # queue must be set as False (default) so the process is not waiting for another to be finished
394
- .then(chat, [examples_hidden, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year], [chatbot, sources_textbox], concurrency_limit=8, api_name="chat_examples")
395
- .then(finish_chat, None, [textbox], api_name="finish_chat_examples")
396
- )
397
-
 
 
 
398
  demo.queue()
399
 
400
  demo.launch()
 
42
  # We need to create the local vectorstore collection once using load_chunks
43
  # vectorestore colection are stored on persistent storage so this needs to be run only once
44
  # hence, comment out line below when creating for first time
45
+ #vectorstores = load_chunks()
46
  # once the vectore embeddings are created we will use qdrant client to access these
47
+ vectorstores = get_local_qdrant()
48
 
49
  #####---------------------CHAT-----------------------------------------------------
50
  def start_chat(query,history):
 
54
 
55
  def finish_chat():
56
  return (gr.update(interactive = True,value = ""))
57
+
58
+ def submit_feedback(feedback, logs_data):
59
+ """Handle feedback submission"""
60
+ try:
61
+ if logs_data is None:
62
+ return gr.update(visible=False), gr.update(visible=True)
63
+
64
+ session_id = logs_data.get("session_id")
65
+ if session_id:
66
+ # Update session last_activity to now
67
+ session_manager.update_session(session_id)
68
+ # Compute duration from the session manager and update the log.
69
+ logs_data["session_duration_seconds"] = session_manager.get_session_duration(session_id)
70
+
71
+ # Now save the (feedback) log record
72
+ save_logs(scheduler, JSON_DATASET_PATH, logs_data, feedback)
73
+ return gr.update(visible=False), gr.update(visible=True)
74
+ except Exception as e:
75
+ return gr.update(visible=False), gr.update(visible=True)
76
+
77
+
78
+ # Session Manager added (track session duration, location, and platform)
79
+ class SessionManager:
80
+ def __init__(self):
81
+ self.sessions = {}
82
+
83
+ def create_session(self, client_ip, user_agent):
84
+ session_id = str(uuid4())
85
+ self.sessions[session_id] = {
86
+ 'start_time': datetime.now(),
87
+ 'last_activity': datetime.now(),
88
+ 'client_ip': client_ip,
89
+ 'location_info': get_client_location(client_ip),
90
+ 'platform_info': get_platform_info(user_agent)
91
+ }
92
+ return session_id
93
+
94
+ def update_session(self, session_id):
95
+ if session_id in self.sessions:
96
+ self.sessions[session_id]['last_activity'] = datetime.now()
97
+
98
+ def get_session_duration(self, session_id):
99
+ if session_id in self.sessions:
100
+ start = self.sessions[session_id]['start_time']
101
+ last = self.sessions[session_id]['last_activity']
102
+ return (last - start).total_seconds()
103
+ return 0
104
 
105
+ def get_session_data(self, session_id):
106
+ return self.sessions.get(session_id)
107
+
108
+ # Initialize session manager
109
+ session_manager = SessionManager()
110
+
111
+ async def chat(query,history,sources,reports,subtype,year, client_ip=None, session_id = None, request:gr.Request = None):
112
  """taking a query and a message history, use a pipeline (reformulation, retriever, answering)
113
  to yield a tuple of:(messages in gradio format/messages in langchain format, source documents)
114
  """
115
 
116
+ if not session_id:
117
+ user_agent = request.headers.get('User-Agent','') if request else ''
118
+ session_id = session_manager.create_session(clinet_ip, user_agent)
119
+ else:
120
+ session_manager.update_session(session_id)
121
+
122
+ # Get session id
123
+ session_data = session_manager.get_session_data(session_id)
124
+ session_duration = session_manager.get_session_duration(session_id)
125
+
126
  print(f">> NEW QUESTION : {query}")
127
  print(f"history:{history}")
128
  print(f"sources:{sources}")
 
133
  output_query = ""
134
 
135
  ##------------------------fetch collection from vectorstore------------------------------
136
+ vectorstore = vectorstores["docling"]
137
 
138
  ##------------------------------get context----------------------------------------------
139
  context_retrieved = get_context(vectorstore=vectorstore,query=query,reports=reports,
 
176
 
177
  ##-----------------------get answer from endpoints------------------------------
178
  answer_yet = ""
179
+
180
+ logs_data = {
181
+ "record_id": str(uuid4()), # Add unique record ID
182
+ "session_id": session_id,
183
+ "session_duration_seconds": session_duration,
184
+ "client_location": session_data['location_info'],
185
+ "platform": session_data['platform_info'],
186
+ # "system_prompt": SYSTEM_PROMPT, #REMOVED FOR TESTING
187
+ # "sources": sources, #REMOVED FOR TESTING
188
+ # "reports": reports, #REMOVED FOR TESTING
189
+ # "subtype": subtype, #REMOVED FOR TESTING
190
+ "year": year,
191
+ "question": query,
192
+ "retriever": model_config.get('retriever','MODEL'),
193
+ "endpoint_type": model_config.get('reader','TYPE'),
194
+ "reader": model_config.get('reader','NVIDIA_MODEL'),
195
+ # "docs": [doc.page_content for doc in context_retrieved], #REMOVED FOR TESTING
196
+ }
197
+
198
  if model_config.get('reader','TYPE') == 'NVIDIA':
199
  chat_model = nvidia_client()
200
  async def process_stream():
 
220
  async for update in process_stream():
221
  yield update
222
 
223
+ #else:
224
+ # chat_model = dedicated_endpoint()
225
+ # async def process_stream():
226
+ # # Without nonlocal, Python would create a new local variable answer_yet inside process_stream(),
227
+ # # instead of modifying the one from the outer scope.
228
+ # nonlocal answer_yet # Use the outer scope's answer_yet variable
229
+ # # Iterate over the streaming response chunks
230
+ # async for chunk in chat_model.astream(messages):
231
+ # token = chunk.content
232
+ # answer_yet += token
233
+ # parsed_answer = parse_output_llm_with_sources(answer_yet)
234
+ # history[-1] = (query, parsed_answer)
235
+ # yield [tuple(x) for x in history], docs_html
236
+
237
+ # Stream the response updates
238
+ # async for update in process_stream():
239
+ # yield update
240
+
241
  else:
242
+ chat_model = dedicated_endpoint() # TESTING: ADAPTED FOR HF INFERENCE API (needs to be reverted for production version)
243
  async def process_stream():
244
+ nonlocal answer_yet
245
+ try:
246
+ formatted_messages = [
247
+ {
248
+ "role": msg.type if hasattr(msg, 'type') else msg.role,
249
+ "content": msg.content
250
+ }
251
+ for msg in messages
252
+ ]
253
+
254
+ response = chat_model.chat_completion(
255
+ messages=formatted_messages,
256
+ max_tokens=int(model_config.get('reader', 'MAX_TOKENS'))
257
+ )
258
+
259
+ response_text = response.choices[0].message.content
260
+ words = response_text.split()
261
+ for word in words:
262
+ answer_yet += word + " "
263
+ parsed_answer = parse_output_llm_with_sources(answer_yet)
264
+ history[-1] = (query, parsed_answer)
265
+ # Update logs_data with current answer (and get a new timestamp)
266
+ logs_data["answer"] = parsed_answer
267
+ yield [tuple(x) for x in history], docs_html, logs_data, session_id
268
+ await asyncio.sleep(0.05)
269
+
270
+ except Exception as e:
271
+ raise
272
 
 
273
  async for update in process_stream():
274
  yield update
275
 
276
  # logging the event
277
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  save_logs(scheduler,JSON_DATASET_PATH,logs)
279
  except Exception as e:
280
  logging.error(e)
 
415
  return [gr.update(visible=visible_bools[i]) for i in range(len(samples))]
416
 
417
  dropdown_samples.change(change_sample_questions,dropdown_samples,samples)
418
+
419
+ # ---- New Guidelines Tab ----
420
+ with gr.Tab("Guidelines", elem_classes="max-height other-tabs"):
421
+ gr.Markdown("""
422
+ Welcome to Audit Q&A, your AI-powered assistant for exploring and understanding Uganda's audit reports. This tool leverages advanced language models to help you get clear and structured answers based on audit publications. To get you started, here a few tips on how to use the tool:
423
+ ### Crafting Effective Prompts
424
+ - **Be Clear and Specific**: Frame your questions clearly and focus on what you want to learn.
425
+ - **One Topic at a Time**: Break complex queries into simpler, focused questions.
426
+ - **Be Direct**: Instead of "What are the findings?", try "What were the main issues identified in procurement practices?" or "What challenges were found in revenue collection?"
427
+
428
+ ### Best Practices
429
+ - Start with a simple, focused question.
430
+ - Follow up with additional questions if your initial query doesn't yield the desired results.
431
+ - Experiment with different phrasings to get the most accurate answers.
432
+ - Use the source citations as a reference to validate the provided information.
433
+ ### Utilizing Filters
434
+ - **Report Category & Subtype**: Use the "Reports" tab to choose your preferred report category and refine your query by selecting a specific sub-type. This will help narrow down the context for your question.
435
+ - **Year Selection**: Choose one or more years from the "Year" filter to target your query to specific time periods.
436
+ - **Specific Reports**: Optionally, select specific reports using the dropdown to focus on a particular document or set of documents.
437
+ ### Useful Resources
438
+
439
+ - <ins>[**Short Course: Generative AI for Everyone** (3 hours)](https://www.deeplearning.ai/courses/generative-ai-for-everyone/)</ins>
440
+ - <ins>[**Short Course: Advanced Prompting** (1 hour)](https://www.deeplearning.ai/courses/ai-for-everyone/)</ins>
441
+ - <ins>[**Short Course: Introduction to AI with IBM** (13 hours)](https://www.coursera.org/learn/introduction-to-ai)</ins>
442
+ Enjoy using Audit Q&A and happy prompting!
443
+ """)
444
+
445
+
446
 
447
  # static tab 'about us'
448
  with gr.Tab("About",elem_classes = "max-height other-tabs"):
 
508
 
509
 
510
 
511
+ def show_feedback(logs):
512
+ """Show feedback buttons and store logs in state"""
513
+ return gr.update(visible=True), gr.update(visible=False), logs
514
+
515
+ def submit_feedback_okay(logs_data):
516
+ """Handle 'okay' feedback submission"""
517
+ return submit_feedback("okay", logs_data)
518
+
519
+ def submit_feedback_not_okay(logs_data):
520
+ """Handle 'not okay' feedback submission"""
521
+ return submit_feedback("not_okay", logs_data)
522
+
523
+ okay_btn.click(
524
+ submit_feedback_okay,
525
+ [feedback_state],
526
+ [feedback_row, feedback_thanks]
527
+ )
528
+
529
+ not_okay_btn.click(
530
+ submit_feedback_not_okay,
531
+ [feedback_state],
532
+ [feedback_row, feedback_thanks]
533
+
534
+ #-------------------- Session Management + Geolocation -------------------------
535
+
536
+ # Add these state components at the top level of the Blocks
537
+ session_id = gr.State(None)
538
+ client_ip = gr.State(None)
539
+
540
+ @demo.load(api_name="get_client_ip")
541
+ def get_client_ip_handler(dummy_input="", request: gr.Request = None):
542
+ """Handler for getting client IP in Gradio context"""
543
+ return get_client_ip(request)
544
+ )
545
+
546
+ #-------------------- Gradio voodoo -------------------------
547
+
548
+ # Update the event handlers
549
  (textbox
550
+ .submit(get_client_ip_handler, [textbox], [client_ip], api_name="get_ip_textbox")
551
+ .then(start_chat, [textbox, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_textbox")
552
+ .then(chat,
553
+ [textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
554
+ [chatbot, sources_textbox, feedback_state, session_id],
555
+ queue=True, concurrency_limit=8, api_name="chat_textbox")
556
+ .then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_textbox")
557
+ .then(finish_chat, None, [textbox], api_name="finish_chat_textbox"))
558
 
559
  (examples_hidden
560
  .change(start_chat, [examples_hidden, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_examples")
561
+ .then(get_client_ip_handler, [examples_hidden], [client_ip], api_name="get_ip_examples")
562
+ .then(chat,
563
+ [examples_hidden, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
564
+ [chatbot, sources_textbox, feedback_state, session_id],
565
+ concurrency_limit=8, api_name="chat_examples")
566
+ .then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_examples")
567
+ .then(finish_chat, None, [textbox], api_name="finish_chat_examples"))
568
+
569
  demo.queue()
570
 
571
  demo.launch()