Spaces:
Runtime error
Runtime error
geolocation + session tracking to log
Browse files- .gitignore +7 -2
- app.py +87 -23
- auditqa/__pycache__/__init__.cpython-310.pyc +0 -0
- auditqa/__pycache__/process_chunks.cpython-310.pyc +0 -0
- auditqa/__pycache__/reader.cpython-310.pyc +0 -0
- auditqa/__pycache__/reports.cpython-310.pyc +0 -0
- auditqa/__pycache__/retriever.cpython-310.pyc +0 -0
- auditqa/__pycache__/sample_questions.cpython-310.pyc +0 -0
- auditqa/__pycache__/utils.cpython-310.pyc +0 -0
- auditqa/utils.py +58 -1
.gitignore
CHANGED
@@ -1,8 +1,13 @@
|
|
1 |
-
.
|
2 |
.env
|
3 |
/testing/
|
4 |
/logs/
|
5 |
logging_config.py
|
6 |
/data/
|
7 |
app_interactions.jsonl
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
.env
|
3 |
/testing/
|
4 |
/logs/
|
5 |
logging_config.py
|
6 |
/data/
|
7 |
app_interactions.jsonl
|
8 |
+
|
9 |
+
# Python cache files
|
10 |
+
__pycache__/
|
11 |
+
*.py[cod]
|
12 |
+
*$py.class
|
13 |
+
.pytest_cache/
|
app.py
CHANGED
@@ -4,7 +4,7 @@ import logging
|
|
4 |
import asyncio
|
5 |
import os
|
6 |
from uuid import uuid4
|
7 |
-
from datetime import datetime
|
8 |
from pathlib import Path
|
9 |
from huggingface_hub import CommitScheduler
|
10 |
from auditqa.sample_questions import QUESTIONS
|
@@ -12,17 +12,20 @@ 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
|
16 |
from dotenv import load_dotenv
|
17 |
from threading import Lock
|
18 |
-
import json
|
19 |
-
from functools import partial
|
|
|
|
|
20 |
|
21 |
# TESTING DEBUG LOG
|
22 |
from auditqa.logging_config import setup_logging
|
23 |
setup_logging()
|
24 |
import logging
|
25 |
logger = logging.getLogger(__name__)
|
|
|
26 |
|
27 |
load_dotenv()
|
28 |
|
@@ -99,11 +102,57 @@ def submit_feedback(feedback, logs_data):
|
|
99 |
# Still need to return the expected outputs even on error
|
100 |
return gr.update(visible=False), gr.update(visible=True)
|
101 |
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
print(f">> NEW QUESTION : {query}")
|
108 |
print(f"history:{history}")
|
109 |
print(f"sources:{sources}")
|
@@ -158,9 +207,12 @@ async def chat(query,history,sources,reports,subtype,year):
|
|
158 |
|
159 |
##-----------------------get answer from endpoints------------------------------
|
160 |
answer_yet = ""
|
161 |
-
#
|
162 |
timestamp = str(datetime.now().timestamp())
|
163 |
logs_data = {
|
|
|
|
|
|
|
164 |
"system_prompt": SYSTEM_PROMPT,
|
165 |
"sources": sources,
|
166 |
"reports": reports,
|
@@ -171,7 +223,7 @@ async def chat(query,history,sources,reports,subtype,year):
|
|
171 |
"endpoint_type": model_config.get('reader','TYPE'),
|
172 |
"reader": model_config.get('reader','NVIDIA_MODEL'),
|
173 |
"docs": [doc.page_content for doc in context_retrieved],
|
174 |
-
"answer": "",
|
175 |
"time": timestamp,
|
176 |
}
|
177 |
|
@@ -196,14 +248,14 @@ async def chat(query,history,sources,reports,subtype,year):
|
|
196 |
history[-1] = (query, parsed_answer)
|
197 |
# Update logs_data with current answer
|
198 |
logs_data["answer"] = parsed_answer
|
199 |
-
yield [tuple(x) for x in history], docs_html, logs_data
|
200 |
|
201 |
# Stream the response updates
|
202 |
async for update in process_stream():
|
203 |
yield update
|
204 |
|
205 |
else:
|
206 |
-
chat_model = dedicated_endpoint() # TESTING: ADAPTED FOR HF INFERENCE API
|
207 |
async def process_stream():
|
208 |
nonlocal answer_yet
|
209 |
try:
|
@@ -228,7 +280,7 @@ async def chat(query,history,sources,reports,subtype,year):
|
|
228 |
history[-1] = (query, parsed_answer)
|
229 |
# Update logs_data with current answer
|
230 |
logs_data["answer"] = parsed_answer
|
231 |
-
yield [tuple(x) for x in history], docs_html, logs_data
|
232 |
await asyncio.sleep(0.05)
|
233 |
|
234 |
except Exception as e:
|
@@ -482,24 +534,36 @@ with gr.Blocks(title="Audit Q&A", css= "style.css", theme=theme,elem_id = "main-
|
|
482 |
|
483 |
#-------------------- Gradio voodoo continued -------------------------
|
484 |
|
485 |
-
#
|
486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
487 |
(textbox
|
488 |
-
.submit(
|
489 |
-
|
490 |
-
.then(chat,
|
|
|
|
|
|
|
491 |
.then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_textbox")
|
492 |
.then(finish_chat, None, [textbox], api_name="finish_chat_textbox"))
|
493 |
|
494 |
(examples_hidden
|
495 |
.change(start_chat, [examples_hidden, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_examples")
|
496 |
-
|
497 |
-
.then(chat,
|
|
|
|
|
|
|
498 |
.then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_examples")
|
499 |
.then(finish_chat, None, [textbox], api_name="finish_chat_examples"))
|
500 |
|
501 |
-
|
502 |
-
|
503 |
demo.queue()
|
504 |
|
505 |
demo.launch()
|
|
|
4 |
import asyncio
|
5 |
import os
|
6 |
from uuid import uuid4
|
7 |
+
from datetime import datetime, timedelta
|
8 |
from pathlib import Path
|
9 |
from huggingface_hub import CommitScheduler
|
10 |
from auditqa.sample_questions import QUESTIONS
|
|
|
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 |
from threading import Lock
|
18 |
+
# import json
|
19 |
+
# from functools import partial
|
20 |
+
# import time
|
21 |
+
from gradio.routes import Request
|
22 |
|
23 |
# TESTING DEBUG LOG
|
24 |
from auditqa.logging_config import setup_logging
|
25 |
setup_logging()
|
26 |
import logging
|
27 |
logger = logging.getLogger(__name__)
|
28 |
+
logger.setLevel(logging.DEBUG) # Ensure debug logging is enabled
|
29 |
|
30 |
load_dotenv()
|
31 |
|
|
|
102 |
# Still need to return the expected outputs even on error
|
103 |
return gr.update(visible=False), gr.update(visible=True)
|
104 |
|
105 |
+
# Session Manager added (track session duration & location)
|
106 |
+
class SessionManager:
|
107 |
+
def __init__(self):
|
108 |
+
self.sessions = {}
|
109 |
+
|
110 |
+
def create_session(self, client_ip):
|
111 |
+
session_id = str(uuid4())
|
112 |
+
self.sessions[session_id] = {
|
113 |
+
'start_time': datetime.now(),
|
114 |
+
'last_activity': datetime.now(),
|
115 |
+
'client_ip': client_ip,
|
116 |
+
'location_info': get_client_location(client_ip)
|
117 |
+
}
|
118 |
+
return session_id
|
119 |
+
|
120 |
+
def update_session(self, session_id):
|
121 |
+
if session_id in self.sessions:
|
122 |
+
self.sessions[session_id]['last_activity'] = datetime.now()
|
123 |
+
|
124 |
+
def get_session_duration(self, session_id):
|
125 |
+
if session_id in self.sessions:
|
126 |
+
start = self.sessions[session_id]['start_time']
|
127 |
+
last = self.sessions[session_id]['last_activity']
|
128 |
+
return (last - start).total_seconds()
|
129 |
+
return 0
|
130 |
+
|
131 |
+
def get_session_data(self, session_id):
|
132 |
+
return self.sessions.get(session_id)
|
133 |
+
|
134 |
+
# Initialize session manager
|
135 |
+
session_manager = SessionManager()
|
136 |
+
|
137 |
+
async def chat(query, history, sources, reports, subtype, year, client_ip=None, session_id=None):
|
138 |
+
"""Update chat function to handle session data"""
|
139 |
+
# TESTING: DEBUG LOG
|
140 |
+
logger.debug(f"Chat function called with query: {query}")
|
141 |
+
logger.debug(f"Client IP: {client_ip}")
|
142 |
+
logger.debug(f"Session ID: {session_id}")
|
143 |
+
|
144 |
+
if not session_id: # Session managment
|
145 |
+
session_id = session_manager.create_session(client_ip)
|
146 |
+
logger.debug(f"Created new session: {session_id}")
|
147 |
+
else:
|
148 |
+
session_manager.update_session(session_id)
|
149 |
+
logger.debug(f"Updated existing session: {session_id}")
|
150 |
+
|
151 |
+
# Get session data
|
152 |
+
session_data = session_manager.get_session_data(session_id)
|
153 |
+
session_duration = session_manager.get_session_duration(session_id)
|
154 |
+
logger.debug(f"Session duration: {session_duration}")
|
155 |
+
|
156 |
print(f">> NEW QUESTION : {query}")
|
157 |
print(f"history:{history}")
|
158 |
print(f"sources:{sources}")
|
|
|
207 |
|
208 |
##-----------------------get answer from endpoints------------------------------
|
209 |
answer_yet = ""
|
210 |
+
# Logs strcuture updated for feedback + session data (moved up here because: feedback)
|
211 |
timestamp = str(datetime.now().timestamp())
|
212 |
logs_data = {
|
213 |
+
"session_id": session_id,
|
214 |
+
"client_location": session_data['location_info'],
|
215 |
+
"session_duration_seconds": session_duration,
|
216 |
"system_prompt": SYSTEM_PROMPT,
|
217 |
"sources": sources,
|
218 |
"reports": reports,
|
|
|
223 |
"endpoint_type": model_config.get('reader','TYPE'),
|
224 |
"reader": model_config.get('reader','NVIDIA_MODEL'),
|
225 |
"docs": [doc.page_content for doc in context_retrieved],
|
226 |
+
"answer": "",
|
227 |
"time": timestamp,
|
228 |
}
|
229 |
|
|
|
248 |
history[-1] = (query, parsed_answer)
|
249 |
# Update logs_data with current answer
|
250 |
logs_data["answer"] = parsed_answer
|
251 |
+
yield [tuple(x) for x in history], docs_html, logs_data, session_id
|
252 |
|
253 |
# Stream the response updates
|
254 |
async for update in process_stream():
|
255 |
yield update
|
256 |
|
257 |
else:
|
258 |
+
chat_model = dedicated_endpoint() # TESTING: ADAPTED FOR HF INFERENCE API (needs to be reverted for production version)
|
259 |
async def process_stream():
|
260 |
nonlocal answer_yet
|
261 |
try:
|
|
|
280 |
history[-1] = (query, parsed_answer)
|
281 |
# Update logs_data with current answer
|
282 |
logs_data["answer"] = parsed_answer
|
283 |
+
yield [tuple(x) for x in history], docs_html, logs_data, session_id
|
284 |
await asyncio.sleep(0.05)
|
285 |
|
286 |
except Exception as e:
|
|
|
534 |
|
535 |
#-------------------- Gradio voodoo continued -------------------------
|
536 |
|
537 |
+
# Add these state components at the top level of the Blocks
|
538 |
+
session_id = gr.State(None)
|
539 |
+
client_ip = gr.State(None)
|
540 |
+
|
541 |
+
@demo.load(api_name="get_client_ip")
|
542 |
+
def get_client_ip_handler(dummy_input="", request: gr.Request = None):
|
543 |
+
"""Handler for getting client IP in Gradio context"""
|
544 |
+
return get_client_ip(request)
|
545 |
+
|
546 |
+
# Update the event handlers
|
547 |
(textbox
|
548 |
+
.submit(get_client_ip_handler, [textbox], [client_ip], api_name="get_ip_textbox")
|
549 |
+
.then(start_chat, [textbox, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_textbox")
|
550 |
+
.then(chat,
|
551 |
+
[textbox, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
552 |
+
[chatbot, sources_textbox, feedback_state, session_id],
|
553 |
+
queue=True, concurrency_limit=8, api_name="chat_textbox")
|
554 |
.then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_textbox")
|
555 |
.then(finish_chat, None, [textbox], api_name="finish_chat_textbox"))
|
556 |
|
557 |
(examples_hidden
|
558 |
.change(start_chat, [examples_hidden, chatbot], [textbox, tabs, chatbot], queue=False, api_name="start_chat_examples")
|
559 |
+
.then(get_client_ip_handler, [examples_hidden], [client_ip], api_name="get_ip_examples")
|
560 |
+
.then(chat,
|
561 |
+
[examples_hidden, chatbot, dropdown_sources, dropdown_reports, dropdown_category, dropdown_year, client_ip, session_id],
|
562 |
+
[chatbot, sources_textbox, feedback_state, session_id],
|
563 |
+
concurrency_limit=8, api_name="chat_examples")
|
564 |
.then(show_feedback, [feedback_state], [feedback_row, feedback_thanks, feedback_state], api_name="show_feedback_examples")
|
565 |
.then(finish_chat, None, [textbox], api_name="finish_chat_examples"))
|
566 |
|
|
|
|
|
567 |
demo.queue()
|
568 |
|
569 |
demo.launch()
|
auditqa/__pycache__/__init__.cpython-310.pyc
DELETED
Binary file (141 Bytes)
|
|
auditqa/__pycache__/process_chunks.cpython-310.pyc
DELETED
Binary file (5.06 kB)
|
|
auditqa/__pycache__/reader.cpython-310.pyc
DELETED
Binary file (1.86 kB)
|
|
auditqa/__pycache__/reports.cpython-310.pyc
DELETED
Binary file (13.3 kB)
|
|
auditqa/__pycache__/retriever.cpython-310.pyc
DELETED
Binary file (2.05 kB)
|
|
auditqa/__pycache__/sample_questions.cpython-310.pyc
DELETED
Binary file (4.91 kB)
|
|
auditqa/__pycache__/utils.cpython-310.pyc
DELETED
Binary file (2.81 kB)
|
|
auditqa/utils.py
CHANGED
@@ -5,6 +5,12 @@ from langchain.schema import (
|
|
5 |
HumanMessage,
|
6 |
SystemMessage,
|
7 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
def save_logs(scheduler, JSON_DATASET_PATH, logs, feedback=None) -> None:
|
10 |
""" Every interaction with app saves the log of question and answer,
|
@@ -73,4 +79,55 @@ def parse_output_llm_with_sources(output):
|
|
73 |
else:
|
74 |
parts.append(part)
|
75 |
content_parts = "".join(parts)
|
76 |
-
return content_parts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
HumanMessage,
|
6 |
SystemMessage,
|
7 |
)
|
8 |
+
import requests
|
9 |
+
from datetime import datetime
|
10 |
+
from uuid import uuid4
|
11 |
+
|
12 |
+
|
13 |
+
|
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,
|
|
|
79 |
else:
|
80 |
parts.append(part)
|
81 |
content_parts = "".join(parts)
|
82 |
+
return content_parts
|
83 |
+
|
84 |
+
def get_client_ip(request=None):
|
85 |
+
"""Get the client IP address from the request context"""
|
86 |
+
try:
|
87 |
+
if request:
|
88 |
+
# Try different headers that might contain the real IP
|
89 |
+
ip = request.client.host
|
90 |
+
# Check for proxy headers
|
91 |
+
forwarded_for = request.headers.get('X-Forwarded-For')
|
92 |
+
if forwarded_for:
|
93 |
+
# X-Forwarded-For can contain multiple IPs - first one is the client
|
94 |
+
ip = forwarded_for.split(',')[0].strip()
|
95 |
+
|
96 |
+
logging.debug(f"Client IP detected: {ip}")
|
97 |
+
return ip
|
98 |
+
except Exception as e:
|
99 |
+
logging.error(f"Error getting client IP: {e}")
|
100 |
+
return "127.0.0.1"
|
101 |
+
|
102 |
+
|
103 |
+
def get_client_location(ip_address) -> dict | None:
|
104 |
+
"""Get geolocation info using ipapi.co"""
|
105 |
+
# Add headers so we don't get blocked...
|
106 |
+
headers = {
|
107 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
108 |
+
}
|
109 |
+
try:
|
110 |
+
response = requests.get(
|
111 |
+
f'https://ipapi.co/{ip_address}/json/',
|
112 |
+
headers=headers,
|
113 |
+
timeout=5
|
114 |
+
)
|
115 |
+
if response.status_code == 200:
|
116 |
+
data = response.json()
|
117 |
+
return {
|
118 |
+
'city': data.get('city'),
|
119 |
+
'region': data.get('region'),
|
120 |
+
'country': data.get('country_name'),
|
121 |
+
'latitude': data.get('latitude'),
|
122 |
+
'longitude': data.get('longitude')
|
123 |
+
}
|
124 |
+
elif response.status_code == 429:
|
125 |
+
logging.warning(f"Rate limit exceeded. Response: {response.text}")
|
126 |
+
return None
|
127 |
+
else:
|
128 |
+
logging.error(f"Error: Status code {response.status_code}. Response: {response.text}")
|
129 |
+
return None
|
130 |
+
|
131 |
+
except requests.exceptions.RequestException as e:
|
132 |
+
logging.error(f"Request failed: {str(e)}")
|
133 |
+
return None
|