Spaces:
Running
Running
record_id, timestamp & duration fix; user agent platform extract
Browse files- app.py +41 -17
- auditqa/utils.py +47 -6
- requirements.txt +5 -1
app.py
CHANGED
@@ -12,7 +12,7 @@ from auditqa.reports import files, report_list, new_files, new_report_list
|
|
12 |
from auditqa.process_chunks import load_chunks, getconfig, get_local_qdrant, load_new_chunks
|
13 |
from auditqa.retriever import get_context
|
14 |
from auditqa.reader import nvidia_client, dedicated_endpoint
|
15 |
-
from auditqa.utils import make_html_source, parse_output_llm_with_sources, save_logs, get_message_template, get_client_location, get_client_ip
|
16 |
from dotenv import load_dotenv
|
17 |
load_dotenv()
|
18 |
from threading import Lock
|
@@ -22,7 +22,7 @@ import json
|
|
22 |
|
23 |
# # fetch tokens and model config params
|
24 |
SPACES_LOG = os.environ["SPACES_LOG"]
|
25 |
-
SPACES_LOG = os.getenv('SPACES_LOG')
|
26 |
|
27 |
model_config = getconfig("model_params.cfg")
|
28 |
|
@@ -38,8 +38,7 @@ scheduler = CommitScheduler(
|
|
38 |
repo_type="dataset",
|
39 |
folder_path=JSON_DATASET_DIR,
|
40 |
path_in_repo="audit_chatbot",
|
41 |
-
token=SPACES_LOG
|
42 |
-
every=2) # TESTING: every 2 seconds
|
43 |
|
44 |
#####--------------- VECTOR STORE -------------------------------------------------
|
45 |
# reports contain the already created chunks from Markdown version of pdf reports
|
@@ -105,24 +104,32 @@ def submit_feedback(feedback, logs_data):
|
|
105 |
if logs_data is None:
|
106 |
return gr.update(visible=False), gr.update(visible=True)
|
107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
save_logs(scheduler, JSON_DATASET_PATH, logs_data, feedback)
|
109 |
return gr.update(visible=False), gr.update(visible=True)
|
110 |
except Exception as e:
|
111 |
-
# Still need to return the expected outputs even on error
|
112 |
return gr.update(visible=False), gr.update(visible=True)
|
113 |
|
114 |
-
# Session Manager added (track session duration
|
115 |
class SessionManager:
|
116 |
def __init__(self):
|
117 |
self.sessions = {}
|
118 |
|
119 |
-
def create_session(self, client_ip):
|
120 |
session_id = str(uuid4())
|
121 |
self.sessions[session_id] = {
|
122 |
'start_time': datetime.now(),
|
123 |
'last_activity': datetime.now(),
|
124 |
'client_ip': client_ip,
|
125 |
-
'location_info': get_client_location(client_ip)
|
|
|
126 |
}
|
127 |
return session_id
|
128 |
|
@@ -143,11 +150,12 @@ class SessionManager:
|
|
143 |
# Initialize session manager
|
144 |
session_manager = SessionManager()
|
145 |
|
146 |
-
async def chat(query, history, sources, reports, subtype, year, client_ip=None, session_id=None):
|
147 |
"""Update chat function to handle session data"""
|
148 |
|
149 |
-
if not session_id:
|
150 |
-
|
|
|
151 |
else:
|
152 |
session_manager.update_session(session_id)
|
153 |
|
@@ -209,12 +217,13 @@ async def chat(query, history, sources, reports, subtype, year, client_ip=None,
|
|
209 |
|
210 |
##-----------------------get answer from endpoints------------------------------
|
211 |
answer_yet = ""
|
212 |
-
# Logs strcuture updated for
|
213 |
-
timestamp = str(datetime.now().timestamp())
|
214 |
logs_data = {
|
|
|
215 |
"session_id": session_id,
|
216 |
"session_duration_seconds": session_duration,
|
217 |
"client_location": session_data['location_info'],
|
|
|
218 |
# "system_prompt": SYSTEM_PROMPT, #REMOVED FOR TESTING
|
219 |
# "sources": sources, #REMOVED FOR TESTING
|
220 |
# "reports": reports, #REMOVED FOR TESTING
|
@@ -225,8 +234,6 @@ async def chat(query, history, sources, reports, subtype, year, client_ip=None,
|
|
225 |
"endpoint_type": model_config.get('reader','TYPE'),
|
226 |
"reader": model_config.get('reader','NVIDIA_MODEL'),
|
227 |
# "docs": [doc.page_content for doc in context_retrieved], #REMOVED FOR TESTING
|
228 |
-
"answer": "",
|
229 |
-
"time": timestamp,
|
230 |
}
|
231 |
|
232 |
if model_config.get('reader','TYPE') == 'NVIDIA':
|
@@ -280,7 +287,7 @@ async def chat(query, history, sources, reports, subtype, year, client_ip=None,
|
|
280 |
answer_yet += word + " "
|
281 |
parsed_answer = parse_output_llm_with_sources(answer_yet)
|
282 |
history[-1] = (query, parsed_answer)
|
283 |
-
# Update logs_data with current answer
|
284 |
logs_data["answer"] = parsed_answer
|
285 |
yield [tuple(x) for x in history], docs_html, logs_data, session_id
|
286 |
await asyncio.sleep(0.05)
|
@@ -291,6 +298,23 @@ async def chat(query, history, sources, reports, subtype, year, client_ip=None,
|
|
291 |
async for update in process_stream():
|
292 |
yield update
|
293 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
try:
|
295 |
# Save log after streaming is complete
|
296 |
save_logs(scheduler, JSON_DATASET_PATH, logs_data)
|
@@ -337,7 +361,7 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
337 |
avatar_images = (None,"data-collection.png"),
|
338 |
)
|
339 |
|
340 |
-
#
|
341 |
with gr.Column(elem_id="feedback-container"):
|
342 |
with gr.Row(visible=False) as feedback_row:
|
343 |
gr.Markdown("Was this response helpful?")
|
|
|
12 |
from auditqa.process_chunks import load_chunks, getconfig, get_local_qdrant, load_new_chunks
|
13 |
from auditqa.retriever import get_context
|
14 |
from auditqa.reader import nvidia_client, dedicated_endpoint
|
15 |
+
from auditqa.utils import make_html_source, parse_output_llm_with_sources, save_logs, get_message_template, get_client_location, get_client_ip, get_platform_info
|
16 |
from dotenv import load_dotenv
|
17 |
load_dotenv()
|
18 |
from threading import Lock
|
|
|
22 |
|
23 |
# # fetch tokens and model config params
|
24 |
SPACES_LOG = os.environ["SPACES_LOG"]
|
25 |
+
# SPACES_LOG = os.getenv('SPACES_LOG')
|
26 |
|
27 |
model_config = getconfig("model_params.cfg")
|
28 |
|
|
|
38 |
repo_type="dataset",
|
39 |
folder_path=JSON_DATASET_DIR,
|
40 |
path_in_repo="audit_chatbot",
|
41 |
+
token=SPACES_LOG)
|
|
|
42 |
|
43 |
#####--------------- VECTOR STORE -------------------------------------------------
|
44 |
# reports contain the already created chunks from Markdown version of pdf reports
|
|
|
104 |
if logs_data is None:
|
105 |
return gr.update(visible=False), gr.update(visible=True)
|
106 |
|
107 |
+
session_id = logs_data.get("session_id")
|
108 |
+
if session_id:
|
109 |
+
# Update session last_activity to now
|
110 |
+
session_manager.update_session(session_id)
|
111 |
+
# Compute duration from the session manager and update the log.
|
112 |
+
logs_data["session_duration_seconds"] = session_manager.get_session_duration(session_id)
|
113 |
+
|
114 |
+
# Now save the (feedback) log record
|
115 |
save_logs(scheduler, JSON_DATASET_PATH, logs_data, feedback)
|
116 |
return gr.update(visible=False), gr.update(visible=True)
|
117 |
except Exception as e:
|
|
|
118 |
return gr.update(visible=False), gr.update(visible=True)
|
119 |
|
120 |
+
# Session Manager added (track session duration, location, and platform)
|
121 |
class SessionManager:
|
122 |
def __init__(self):
|
123 |
self.sessions = {}
|
124 |
|
125 |
+
def create_session(self, client_ip, user_agent):
|
126 |
session_id = str(uuid4())
|
127 |
self.sessions[session_id] = {
|
128 |
'start_time': datetime.now(),
|
129 |
'last_activity': datetime.now(),
|
130 |
'client_ip': client_ip,
|
131 |
+
'location_info': get_client_location(client_ip),
|
132 |
+
'platform_info': get_platform_info(user_agent)
|
133 |
}
|
134 |
return session_id
|
135 |
|
|
|
150 |
# Initialize session manager
|
151 |
session_manager = SessionManager()
|
152 |
|
153 |
+
async def chat(query, history, sources, reports, subtype, year, client_ip=None, session_id=None, request: gr.Request = None):
|
154 |
"""Update chat function to handle session data"""
|
155 |
|
156 |
+
if not session_id:
|
157 |
+
user_agent = request.headers.get('User-Agent', '') if request else ''
|
158 |
+
session_id = session_manager.create_session(client_ip, user_agent)
|
159 |
else:
|
160 |
session_manager.update_session(session_id)
|
161 |
|
|
|
217 |
|
218 |
##-----------------------get answer from endpoints------------------------------
|
219 |
answer_yet = ""
|
220 |
+
# Logs strcuture updated for session data (feedback and timestamp added separately via save_logs)
|
|
|
221 |
logs_data = {
|
222 |
+
"record_id": str(uuid4()), # Add unique record ID
|
223 |
"session_id": session_id,
|
224 |
"session_duration_seconds": session_duration,
|
225 |
"client_location": session_data['location_info'],
|
226 |
+
"platform": session_data['platform_info'],
|
227 |
# "system_prompt": SYSTEM_PROMPT, #REMOVED FOR TESTING
|
228 |
# "sources": sources, #REMOVED FOR TESTING
|
229 |
# "reports": reports, #REMOVED FOR TESTING
|
|
|
234 |
"endpoint_type": model_config.get('reader','TYPE'),
|
235 |
"reader": model_config.get('reader','NVIDIA_MODEL'),
|
236 |
# "docs": [doc.page_content for doc in context_retrieved], #REMOVED FOR TESTING
|
|
|
|
|
237 |
}
|
238 |
|
239 |
if model_config.get('reader','TYPE') == 'NVIDIA':
|
|
|
287 |
answer_yet += word + " "
|
288 |
parsed_answer = parse_output_llm_with_sources(answer_yet)
|
289 |
history[-1] = (query, parsed_answer)
|
290 |
+
# Update logs_data with current answer (and get a new timestamp)
|
291 |
logs_data["answer"] = parsed_answer
|
292 |
yield [tuple(x) for x in history], docs_html, logs_data, session_id
|
293 |
await asyncio.sleep(0.05)
|
|
|
298 |
async for update in process_stream():
|
299 |
yield update
|
300 |
|
301 |
+
# chat_model = dedicated_endpoint()
|
302 |
+
# async def process_stream():
|
303 |
+
# # Without nonlocal, Python would create a new local variable answer_yet inside process_stream(),
|
304 |
+
# # instead of modifying the one from the outer scope.
|
305 |
+
# nonlocal answer_yet # Use the outer scope's answer_yet variable
|
306 |
+
# # Iterate over the streaming response chunks
|
307 |
+
# async for chunk in chat_model.astream(messages):
|
308 |
+
# token = chunk.content
|
309 |
+
# answer_yet += token
|
310 |
+
# parsed_answer = parse_output_llm_with_sources(answer_yet)
|
311 |
+
# history[-1] = (query, parsed_answer)
|
312 |
+
# yield [tuple(x) for x in history], docs_html
|
313 |
+
|
314 |
+
# # Stream the response updates
|
315 |
+
# async for update in process_stream():
|
316 |
+
# yield update
|
317 |
+
|
318 |
try:
|
319 |
# Save log after streaming is complete
|
320 |
save_logs(scheduler, JSON_DATASET_PATH, logs_data)
|
|
|
361 |
avatar_images = (None,"data-collection.png"),
|
362 |
)
|
363 |
|
364 |
+
# feedback UI
|
365 |
with gr.Column(elem_id="feedback-container"):
|
366 |
with gr.Row(visible=False) as feedback_row:
|
367 |
gr.Markdown("Was this response helpful?")
|
auditqa/utils.py
CHANGED
@@ -14,17 +14,47 @@ import random
|
|
14 |
|
15 |
def save_logs(scheduler, JSON_DATASET_PATH, logs, feedback=None) -> None:
|
16 |
""" Every interaction with app saves the log of question and answer,
|
17 |
-
this is to get the usage statistics of app and evaluate model performances
|
18 |
-
Also
|
19 |
"""
|
20 |
try:
|
21 |
-
|
22 |
-
|
|
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
with scheduler.lock:
|
25 |
with open(JSON_DATASET_PATH, 'a') as f:
|
26 |
-
json.dump(
|
27 |
f.write("\n")
|
|
|
28 |
except Exception as e:
|
29 |
raise
|
30 |
|
@@ -140,4 +170,15 @@ def get_client_location(ip_address) -> dict | None:
|
|
140 |
|
141 |
except requests.exceptions.RequestException as e:
|
142 |
logging.error(f"Request failed: {str(e)}")
|
143 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
def save_logs(scheduler, JSON_DATASET_PATH, logs, feedback=None) -> None:
|
16 |
""" Every interaction with app saves the log of question and answer,
|
17 |
+
this is to get the usage statistics of app and evaluate model performances
|
18 |
+
Also adding feedback and timestamp
|
19 |
"""
|
20 |
try:
|
21 |
+
# We get the timestamp here now because we are simply recording time of logging
|
22 |
+
current_time = datetime.now().timestamp()
|
23 |
+
logs["time"] = str(current_time)
|
24 |
|
25 |
+
# Save feedback (if any)
|
26 |
+
if feedback:
|
27 |
+
logs["feedback"] = feedback
|
28 |
+
logs["record_id"] = str(uuid4())
|
29 |
+
|
30 |
+
# Do some reordering to keep things clean (time up front)
|
31 |
+
field_order = [
|
32 |
+
"record_id",
|
33 |
+
"session_id",
|
34 |
+
"time", # current log time
|
35 |
+
"session_duration_seconds",
|
36 |
+
"client_location",
|
37 |
+
"platform",
|
38 |
+
"system_prompt",
|
39 |
+
"sources",
|
40 |
+
"reports",
|
41 |
+
"subtype",
|
42 |
+
"year",
|
43 |
+
"question",
|
44 |
+
"retriever",
|
45 |
+
"endpoint_type",
|
46 |
+
"reader",
|
47 |
+
"docs",
|
48 |
+
"answer",
|
49 |
+
"feedback"
|
50 |
+
]
|
51 |
+
ordered_logs = {k: logs.get(k) for k in field_order if k in logs}
|
52 |
+
|
53 |
with scheduler.lock:
|
54 |
with open(JSON_DATASET_PATH, 'a') as f:
|
55 |
+
json.dump(ordered_logs, f)
|
56 |
f.write("\n")
|
57 |
+
logging.info("logging done")
|
58 |
except Exception as e:
|
59 |
raise
|
60 |
|
|
|
170 |
|
171 |
except requests.exceptions.RequestException as e:
|
172 |
logging.error(f"Request failed: {str(e)}")
|
173 |
+
return None
|
174 |
+
|
175 |
+
|
176 |
+
def get_platform_info(user_agent: str) -> str:
|
177 |
+
"""Get platform info"""
|
178 |
+
# Make a best guess at the device type
|
179 |
+
if any(mobile_keyword in user_agent.lower() for mobile_keyword in ['mobile', 'android', 'iphone', 'ipad', 'ipod']):
|
180 |
+
platform_info = 'mobile'
|
181 |
+
else:
|
182 |
+
platform_info = 'desktop'
|
183 |
+
|
184 |
+
return platform_info
|
requirements.txt
CHANGED
@@ -158,4 +158,8 @@ wcwidth==0.2.13
|
|
158 |
websockets==11.0.3
|
159 |
wheel==0.44.0
|
160 |
xxhash==3.4.1
|
161 |
-
yarl==1.9.4
|
|
|
|
|
|
|
|
|
|
158 |
websockets==11.0.3
|
159 |
wheel==0.44.0
|
160 |
xxhash==3.4.1
|
161 |
+
yarl==1.9.4
|
162 |
+
# Platform info
|
163 |
+
user-agents==2.2.0
|
164 |
+
ua-parser==1.0.1
|
165 |
+
ua-parser-builtins==0.18.0.post1
|