Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -171,20 +171,23 @@ class IrisReportEngine:
|
|
| 171 |
|
| 172 |
def synthesize_fallback_response(self, briefing: dict, user_question: str) -> str:
|
| 173 |
fallback_prompt = f"""
|
| 174 |
-
You are Iris, an expert business data analyst.
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
|
|
|
| 180 |
|
| 181 |
-
User's
|
| 182 |
-
Business
|
| 183 |
"""
|
| 184 |
response = self.llm.invoke(fallback_prompt)
|
| 185 |
return response.content if hasattr(response, 'content') else str(response)
|
| 186 |
|
| 187 |
-
#
|
|
|
|
|
|
|
| 188 |
@app.route("/chat", methods=["POST"])
|
| 189 |
@cross_origin()
|
| 190 |
def bot():
|
|
@@ -202,50 +205,79 @@ def bot():
|
|
| 202 |
transactions = response.json().get("transactions")
|
| 203 |
if not transactions: return jsonify({"answer": "No transaction data was found for this profile."})
|
| 204 |
|
| 205 |
-
# --- TIER 1 (DEFAULT): PANDASAI FIRST ---
|
| 206 |
try:
|
| 207 |
-
logger.info("Attempting to answer with Tier 1 (PandasAI)...")
|
| 208 |
df = pd.DataFrame(transactions)
|
| 209 |
|
| 210 |
-
#
|
| 211 |
pandas_agent = SmartDataframe(df, config={
|
| 212 |
-
"llm": llm,
|
|
|
|
| 213 |
"custom_whitelisted_dependencies": [
|
| 214 |
-
"os", "io", "sys", "chr", "glob",
|
| 215 |
-
"
|
| 216 |
-
"
|
|
|
|
| 217 |
],
|
| 218 |
-
"security": "none",
|
| 219 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
})
|
|
|
|
| 221 |
answer = pandas_agent.chat(user_question)
|
| 222 |
|
| 223 |
-
#
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
is_failure = True
|
| 227 |
-
logger.warning("PandasAI returned None. Triggering fallback.")
|
| 228 |
-
if isinstance(answer, str):
|
| 229 |
-
fail_strings = ["i am sorry", "i cannot answer", "an error occurred", "unable to answer"]
|
| 230 |
-
if any(s in answer.lower() for s in fail_strings):
|
| 231 |
-
is_failure = True
|
| 232 |
-
logger.warning(f"PandasAI returned a failure string: '{answer}'. Triggering fallback.")
|
| 233 |
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
logger.info("Successfully answered with Tier 1 (PandasAI).")
|
| 236 |
formatted_answer = str(answer)
|
| 237 |
-
if isinstance(answer, pd.DataFrame):
|
|
|
|
| 238 |
elif isinstance(answer, plt.Figure):
|
| 239 |
buf = io.BytesIO()
|
| 240 |
answer.savefig(buf, format="png")
|
| 241 |
formatted_answer = f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode('utf-8')}"
|
| 242 |
return jsonify({"answer": formatted_answer})
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
except Exception as e:
|
| 245 |
-
|
|
|
|
|
|
|
| 246 |
|
| 247 |
-
# --- TIER 2 (
|
| 248 |
-
logger.info("
|
| 249 |
engine = IrisReportEngine(transactions_data=transactions, llm_instance=llm)
|
| 250 |
briefing = engine.get_business_intelligence_briefing()
|
| 251 |
fallback_answer = engine.synthesize_fallback_response(briefing, user_question)
|
|
|
|
| 171 |
|
| 172 |
def synthesize_fallback_response(self, briefing: dict, user_question: str) -> str:
|
| 173 |
fallback_prompt = f"""
|
| 174 |
+
You are Iris, an expert business data analyst. Answer the user's question using the comprehensive business data below.
|
| 175 |
+
|
| 176 |
+
If their question is specific (like "sales yesterday", "top product", etc.), directly answer it using the data.
|
| 177 |
+
If you cannot find the specific information requested, provide a helpful business intelligence briefing instead.
|
| 178 |
+
|
| 179 |
+
Structure your response with clear markdown headings and focus on actionable insights.
|
| 180 |
+
Always interpret percentage changes as business trends and provide context.
|
| 181 |
|
| 182 |
+
User's Question: "{user_question}"
|
| 183 |
+
Business Data: {json.dumps(briefing, indent=2, ensure_ascii=False)}
|
| 184 |
"""
|
| 185 |
response = self.llm.invoke(fallback_prompt)
|
| 186 |
return response.content if hasattr(response, 'content') else str(response)
|
| 187 |
|
| 188 |
+
# REMOVED: No error detection function needed - Trust PandasAI completely, catch ALL exceptions silently
|
| 189 |
+
|
| 190 |
+
# --- REFACTORED /chat Endpoint with Enhanced Error Detection ---
|
| 191 |
@app.route("/chat", methods=["POST"])
|
| 192 |
@cross_origin()
|
| 193 |
def bot():
|
|
|
|
| 205 |
transactions = response.json().get("transactions")
|
| 206 |
if not transactions: return jsonify({"answer": "No transaction data was found for this profile."})
|
| 207 |
|
| 208 |
+
# --- TIER 1 (DEFAULT): PANDASAI FIRST - WITH COMPREHENSIVE RESPONSE VALIDATION ---
|
| 209 |
try:
|
| 210 |
+
logger.info("Attempting to answer with Tier 1 (PandasAI) - Full Trust Mode...")
|
| 211 |
df = pd.DataFrame(transactions)
|
| 212 |
|
| 213 |
+
# FULL TRUST PANDASAI CONFIGURATION
|
| 214 |
pandas_agent = SmartDataframe(df, config={
|
| 215 |
+
"llm": llm,
|
| 216 |
+
"response_parser": FlaskResponse,
|
| 217 |
"custom_whitelisted_dependencies": [
|
| 218 |
+
"os", "io", "sys", "chr", "glob", "b64decoder", "collections",
|
| 219 |
+
"geopy", "geopandas", "wordcloud", "builtins", "datetime",
|
| 220 |
+
"timedelta", "date", "pandas", "numpy", "math", "statistics",
|
| 221 |
+
"matplotlib", "seaborn", "plotly", "json", "re", "warnings"
|
| 222 |
],
|
| 223 |
+
"security": "none",
|
| 224 |
+
"save_charts_path": user_defined_path,
|
| 225 |
+
"save_charts": False,
|
| 226 |
+
"enable_cache": False,
|
| 227 |
+
"conversational": True,
|
| 228 |
+
"enable_logging": False
|
| 229 |
})
|
| 230 |
+
|
| 231 |
answer = pandas_agent.chat(user_question)
|
| 232 |
|
| 233 |
+
# COMPREHENSIVE RESPONSE VALIDATION - Check if PandasAI actually succeeded
|
| 234 |
+
# PandasAI doesn't raise exceptions, it returns responses that may contain errors
|
| 235 |
+
is_valid_response = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
+
# Check 1: Answer exists and is not empty
|
| 238 |
+
if answer is None or (isinstance(answer, str) and not answer.strip()):
|
| 239 |
+
is_valid_response = False
|
| 240 |
+
|
| 241 |
+
# Check 2: Answer doesn't contain error indicators (PandasAI returns these as strings)
|
| 242 |
+
elif isinstance(answer, str):
|
| 243 |
+
error_patterns = [
|
| 244 |
+
'keyerror', 'traceback', 'exception', 'error occurred',
|
| 245 |
+
'failed', 'unable to', 'cannot', '__import__', 'importerror',
|
| 246 |
+
'modulenotfounderror', 'nameerror', 'syntaxerror',
|
| 247 |
+
'pipeline failed', 'execution failed'
|
| 248 |
+
]
|
| 249 |
+
answer_lower = answer.lower()
|
| 250 |
+
if any(pattern in answer_lower for pattern in error_patterns):
|
| 251 |
+
is_valid_response = False
|
| 252 |
+
|
| 253 |
+
# Also check for stack traces or error messages that slip through
|
| 254 |
+
if 'file "<string>"' in answer_lower or 'line ' in answer_lower and 'error' in answer_lower:
|
| 255 |
+
is_valid_response = False
|
| 256 |
+
|
| 257 |
+
# Check 3: For specific error objects that might be returned
|
| 258 |
+
elif hasattr(answer, '__class__') and 'error' in str(type(answer)).lower():
|
| 259 |
+
is_valid_response = False
|
| 260 |
+
|
| 261 |
+
if is_valid_response:
|
| 262 |
logger.info("Successfully answered with Tier 1 (PandasAI).")
|
| 263 |
formatted_answer = str(answer)
|
| 264 |
+
if isinstance(answer, pd.DataFrame):
|
| 265 |
+
formatted_answer = answer.to_html()
|
| 266 |
elif isinstance(answer, plt.Figure):
|
| 267 |
buf = io.BytesIO()
|
| 268 |
answer.savefig(buf, format="png")
|
| 269 |
formatted_answer = f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode('utf-8')}"
|
| 270 |
return jsonify({"answer": formatted_answer})
|
| 271 |
+
else:
|
| 272 |
+
logger.info("PandasAI response contains error indicators, using analyst layer")
|
| 273 |
+
|
| 274 |
except Exception as e:
|
| 275 |
+
# This catches any actual exceptions that might escape PandasAI
|
| 276 |
+
logger.info(f"PandasAI raised exception, seamlessly switching to analyst layer: {type(e).__name__}")
|
| 277 |
+
pass
|
| 278 |
|
| 279 |
+
# --- TIER 2 (SEAMLESS FALLBACK): COMPREHENSIVE KPI ANALYST ---
|
| 280 |
+
logger.info("Seamlessly providing intelligence via IrisReportEngine analyst layer.")
|
| 281 |
engine = IrisReportEngine(transactions_data=transactions, llm_instance=llm)
|
| 282 |
briefing = engine.get_business_intelligence_briefing()
|
| 283 |
fallback_answer = engine.synthesize_fallback_response(briefing, user_question)
|