Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
UI updates
Browse files
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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["
|
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 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
(textbox
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
|
|
|
|
|
|
|
|
390 |
|
391 |
(examples_hidden
|
392 |
.change(start_chat, [examples_hidden, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_examples")
|
393 |
-
|
394 |
-
.then(chat,
|
395 |
-
|
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()
|