-
Notifications
You must be signed in to change notification settings - Fork 24
chatbot #4523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
chatbot #4523
Changes from all commits
5ee7124
fc8c96c
d316b48
6f000e7
5b8b3e4
bafdfa8
c4fa495
7592b68
269e289
65927d4
24270e1
e05ee1c
a66eaf7
6ab8b04
249d763
347cadc
a953ca1
8e58923
c68f031
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import requests | ||
import redis | ||
import json | ||
import google.generativeai as genai | ||
import logging | ||
import re | ||
import threading | ||
from urllib.parse import urlencode | ||
from configure import Config | ||
|
||
# Configure logging | ||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | ||
|
||
# Configure API keys | ||
GOOGLE_API_KEY = Config.GOOGLE_API_KEY | ||
genai.configure(api_key=GOOGLE_API_KEY) | ||
|
||
# Initialize Redis client | ||
try: | ||
redis_client = redis.StrictRedis( | ||
host=Config.REDIS_HOST or 'localhost', | ||
port=Config.REDIS_PORT or 6379, | ||
db=Config.REDIS_DB or 0, | ||
password=Config.REDIS_PASSWORD or None, | ||
decode_responses=True | ||
) | ||
# Test the connection | ||
redis_client.ping() | ||
logging.info("Connected to Redis") | ||
except Exception as e: | ||
logging.error(f"Error connecting to Redis: {e}") | ||
redis_client = None | ||
# lock for thread safety to prevent race conditions when multiple users request the same data. | ||
data_fetch_lock = threading.Lock() | ||
|
||
class DataFetcher: | ||
@staticmethod | ||
def fetch_air_quality_data(grid_id, start_time, end_time): | ||
"""Fetch air quality data and cache it in Redis to avoid redundant API calls.""" | ||
cache_key = f"air_quality:{grid_id}:{start_time}:{end_time}" | ||
|
||
# Check if data is cached in Redis | ||
cached_data = None | ||
if redis_client: | ||
try: | ||
cached_data = redis_client.get(cache_key) | ||
except Exception as e: | ||
logging.error(f"Error retrieving data from Redis: {e}") | ||
else: | ||
logging.error("Redis client not available, skipping cache check") | ||
|
||
if cached_data: | ||
logging.info(f"Retrieved cached data for {cache_key}") | ||
return json.loads(cached_data) | ||
|
||
token = Config.AIRQO_API_TOKEN | ||
analytics_url = Config.ANALTICS_URL | ||
if not token: | ||
Comment on lines
+57
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Implement thread-safety in the LLM response method. A threading lock is declared but never used. To ensure thread safety when making external calls or modifying shared data in Also applies to: 94-114, 164-181 |
||
logging.error("AIRQO_API_TOKEN is not set.") | ||
return None | ||
|
||
query_params = {'token': token} | ||
url = f"{analytics_url}?{urlencode(query_params)}" | ||
payload = {"grid_id": grid_id, "start_time": start_time, "end_time": end_time} | ||
logging.info(f"Fetching air quality data with payload: {payload}") | ||
|
||
try: | ||
response = requests.post(url, json=payload, timeout=10) | ||
response.raise_for_status() | ||
data = response.json() | ||
# Cache response in Redis for 1 hour | ||
Comment on lines
+64
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor or remove duplicated code blocks. The file contains two versions of Also applies to: 121-132, 72-93, 133-163, 94-114, 164-181, 115-120, 182-194 |
||
# setex is redis command to set a key with an expiration time | ||
with data_fetch_lock: | ||
if redis_client: | ||
redis_client.setex(cache_key, 3600, json.dumps(data)) | ||
logging.info(f"Data fetched and cached for grid_id: {grid_id}") | ||
return data | ||
except requests.exceptions.HTTPError as http_err: | ||
logging.error(f"HTTP error: {http_err}") | ||
except requests.exceptions.RequestException as req_err: | ||
logging.error(f"Request error: {req_err}") | ||
except ValueError as json_err: | ||
logging.error(f"JSON error: {json_err}") | ||
return None | ||
|
||
class AirQualityChatbot: | ||
def __init__(self, air_quality_data): | ||
self.data = air_quality_data or {} | ||
self.grid_name = self.data.get('airquality', {}).get('sites', {}).get('grid name', ['Unknown'])[0] | ||
self.annual_data = self.data.get('airquality', {}).get('annual_pm', [{}])[0] or {} | ||
self.daily_mean_data = self.data.get('airquality', {}).get('daily_mean_pm', []) or [] | ||
self.diurnal = self.data.get('airquality', {}).get('diurnal', []) or [] | ||
self.monthly_data = self.data.get('airquality', {}).get('site_monthly_mean_pm', []) or [] | ||
self.site_names = [item.get('site_name', 'Unknown') for item in self.data.get('airquality', {}).get('site_annual_mean_pm', [])] or ['Unknown'] | ||
self.num_sites = self.data.get('airquality', {}).get('sites', {}).get('number_of_sites', 'Unknown') | ||
self.starttime = self.data.get('airquality', {}).get('period', {}).get('startTime', '')[:10] or 'N/A' | ||
self.endtime = self.data.get('airquality', {}).get('period', {}).get('endTime', '')[:10] or 'N/A' | ||
self.annual_pm2_5 = self.annual_data.get("pm2_5_calibrated_value", 'N/A') | ||
self.mean_pm2_5_by_site = self.data.get('airquality', {}).get('site_annual_mean_pm', []) | ||
|
||
# Sort daily_mean_data to get the most recent measurement | ||
if self.daily_mean_data: | ||
sorted_daily = sorted(self.daily_mean_data, key=lambda x: x.get('date', ''), reverse=True) | ||
self.today_pm2_5 = sorted_daily[0].get('pm2_5_calibrated_value', 'N/A') if sorted_daily else 'N/A' | ||
self.today_date = sorted_daily[0].get('date', 'N/A') if sorted_daily else 'N/A' | ||
else: | ||
self.today_pm2_5 = 'N/A' | ||
self.today_date = 'N/A' | ||
|
||
self.peak_diurnal = max(self.diurnal, key=lambda x: x.get('pm2_5_calibrated_value', 0)) if self.diurnal else {} | ||
|
||
try: | ||
# Gemini model | ||
self.gemini_model = genai.GenerativeModel('gemini-2.0-flash') | ||
except Exception as e: | ||
logging.error(f"Failed to initialize Gemini model: {e}") | ||
self.gemini_model = None | ||
self.lock = threading.Lock() | ||
|
||
def _prepare_data_context(self): | ||
"""Prepare a concise data context for the LLM.""" | ||
return ( | ||
f"AirQo data for {self.grid_name} ({self.starttime}-{self.endtime}): " | ||
f"Annual PM2.5={self.annual_pm2_5} µg/m³, Sites={self.num_sites}, " | ||
f"Most recent daily PM2.5={self.today_pm2_5} µg/m³ on {self.today_date}, " | ||
f"Diurnal peak={self.peak_diurnal.get('pm2_5_calibrated_value', 'N/A')} µg/m³ at {self.peak_diurnal.get('hour', 'N/A')}:00, " | ||
f"Site names={self.site_names}." | ||
f"Monthly data={self.monthly_data}." | ||
f"Daily mean data={self.daily_mean_data}." | ||
f"Site annual mean data={self.mean_pm2_5_by_site}." | ||
) | ||
|
||
def _rule_based_response(self, user_prompt): | ||
"""Handle common queries with precomputed responses.""" | ||
prompt = user_prompt.lower() | ||
|
||
if re.search(r"(today|now).*air.*quality", prompt): | ||
if self.today_pm2_5 != 'N/A': | ||
return f"The most recent PM2.5 in {self.grid_name} is {self.today_pm2_5} µg/m³ on {self.today_date}." | ||
return "No recent air quality data available." | ||
|
||
if re.search(r"(worst|highest|peak).*time", prompt): | ||
if self.peak_diurnal: | ||
return f"Pollution peaks at {self.peak_diurnal.get('hour', 'N/A')}:00 with {self.peak_diurnal.get('pm2_5_calibrated_value', 'N/A')} µg/m³." | ||
return "No diurnal data available." | ||
|
||
if re.search(r"how.*many.*(site|sites|monitors)", prompt): | ||
if self.num_sites != 'Unknown': | ||
return f"There are {self.num_sites} monitoring sites in {self.grid_name}." | ||
return "Number of sites is not available." | ||
|
||
if re.search(r"(year|annual).*average", prompt): | ||
if self.annual_pm2_5 != 'N/A': | ||
return f"The annual PM2.5 average in {self.grid_name} is {self.annual_pm2_5} µg/m³." | ||
return "Annual air quality data is not available." | ||
|
||
if re.search(r"(?:where|which|list).*(?:site|sites|locations)", prompt): | ||
if self.site_names != ['Unknown']: | ||
return f"Monitoring sites in {self.grid_name}: {', '.join(self.site_names)}." | ||
return "Site information is not available." | ||
|
||
return None | ||
|
||
def _llm_response(self, user_prompt): | ||
"""Generate a response using the Gemini model for complex queries.""" | ||
if not self.gemini_model: | ||
return "Language model is not available." | ||
|
||
full_prompt = ( | ||
f"Data: {self._prepare_data_context()}\n" | ||
f"User: {user_prompt}\n" | ||
"Respond concisely and accurately based on the data." | ||
) | ||
|
||
try: | ||
response = self.gemini_model.generate_content(full_prompt) | ||
return response.text | ||
except Exception as e: | ||
logging.error(f"LLM error: {e}") | ||
return "Sorry, I couldn't generate a response." | ||
|
||
def chat(self, user_prompt): | ||
"""Process user queries and return appropriate responses.""" | ||
if not self.data: | ||
return "Air quality data is not available for the specified grid and time period." | ||
if not user_prompt or not isinstance(user_prompt, str): | ||
return "Please provide a valid question about air quality." | ||
if len(user_prompt) > 500: | ||
return "Your question is too long. Please keep it under 500 characters." | ||
|
||
rule_response = self._rule_based_response(user_prompt) | ||
if rule_response: | ||
return rule_response | ||
return self._llm_response(user_prompt) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,4 @@ joblib~=1.4.2 | |
lightgbm~=4.1.0 | ||
numpy | ||
google-generativeai | ||
redis |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,93 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# views/chatbot_views.py | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from flask import request, jsonify | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from models.chatbot_model import AirQualityChatbot, DataFetcher | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import uuid # For generating session IDs if not provided | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class ChatbotView: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def chat_endpoint(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Handles chatbot API requests for air quality information with session management. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Expects JSON payload with grid_id, start_time, end_time, prompt, and optional session_id, session_title. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Returns JSON response with chatbot's answer and session metadata. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Validate request payload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
payload = request.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
required_fields = ["grid_id", "start_time", "end_time", "prompt"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not payload or not all(key in payload for key in required_fields): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.error("Invalid payload: missing required fields") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error": "Missing required fields: grid_id, start_time, end_time, prompt", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "failure" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 400 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Extract parameters | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
grid_id = payload["grid_id"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
start_time = payload["start_time"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end_time = payload["end_time"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
user_prompt = payload["prompt"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Session metadata (optional) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id = payload.get("session_id", str(uuid.uuid4())) # Generate if not provided | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Automatically generate session_title from prompt (truncate to reasonable length) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_title = payload.get("session_title") # Check if provided | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not session_title: # If not provided, generate from prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_title = (user_prompt[:50] + "...") if len(user_prompt) > 50 else user_prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Validate prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not user_prompt or not isinstance(user_prompt, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.error(f"Invalid prompt received: {user_prompt}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error": "No valid prompt provided", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "failure" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 400 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add additional validation for time format and grid_id. The current implementation only validates the presence of # After extracting parameters, add:
+ # Validate time formats
+ try:
+ # Use appropriate datetime parsing based on your expected format
+ from datetime import datetime
+ datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%SZ")
+ datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%SZ")
+
+ # Ensure start_time is before end_time
+ if datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%SZ") >= datetime.strptime(end_time, "%Y-%m-%dT%H:%M:%SZ"):
+ logger.error(f"Invalid time range: start_time must be before end_time")
+ return jsonify({
+ "error": "Invalid time range: start_time must be before end_time",
+ "status": "failure"
+ }), 400
+ except ValueError:
+ logger.error(f"Invalid time format for start_time or end_time")
+ return jsonify({
+ "error": "Invalid time format. Expected format: YYYY-MM-DDThh:mm:ssZ",
+ "status": "failure"
+ }), 400
+
+ # Validate grid_id format if needed
+ # For example, if grid_id should be a UUID or follow a specific pattern 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Fetch air quality data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info(f"Fetching data for grid_id: {grid_id}, {start_time} to {end_time}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
air_quality_data = DataFetcher.fetch_air_quality_data(grid_id, start_time, end_time) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not air_quality_data or 'airquality' not in air_quality_data: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.error(f"No valid air quality data returned for grid_id: {grid_id}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error": "Failed to fetch air quality data", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "failure" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 500 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Initialize chatbot and get response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
chatbot = AirQualityChatbot(air_quality_data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
response = chatbot.chat(user_prompt) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.warning(f"Empty response generated for prompt: {user_prompt}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error": "No response generated", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "failure" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 500 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.info(f"Successfully processed request for {grid_id}, session_id: {session_id}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"response": response, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "success", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"grid_id": grid_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"period": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"start_time": start_time, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"end_time": end_time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"session": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"session_id": session_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"session_title": session_title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"timestamp": start_time # Optional: to track when the session started | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.error(f"Unhandled exception: {str(e)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error": "Internal server error", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"status": "failure" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), 500 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+88
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent ❓ Verification inconclusiveEnhance exception handling to prevent information exposure. The current exception handling logs the full exception details but could potentially expose sensitive information in the response. The GitHub security bot previously flagged this issue pattern. except Exception as e:
logger.error(f"Unhandled exception: {str(e)}")
+ # Generate a unique error reference ID that can be used to trace this error in logs
+ error_reference = str(uuid.uuid4())
+ logger.error(f"Error reference: {error_reference}")
return jsonify({
"error": "Internal server error",
+ "error_reference": error_reference,
"status": "failure"
}), 500 The improved version generates a unique error reference ID that logs with the exception details but only returns the reference ID to the client. This allows support staff to locate the specific error in logs without exposing implementation details to users. Refactor exception handling to enhance security and traceability.
except Exception as e:
logger.error(f"Unhandled exception: {str(e)}")
+ # Generate a unique error reference ID that can be used to trace this error in logs
+ error_reference = str(uuid.uuid4())
+ logger.error(f"Error reference: {error_reference}")
return jsonify({
"error": "Internal server error",
+ "error_reference": error_reference,
"status": "failure"
}), 500 📝 Committable suggestion
Suggested change
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove or relocate the Flask app initialization.
The
app = Flask(__name__)
call in a model file is a code smell. Model classes should be framework-agnostic wherever possible. If a centralized Flask application is needed, consider initializing it in a dedicated application or controller module.