rairo commited on
Commit
240a91b
·
verified ·
1 Parent(s): c15c780

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +66 -34
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. Your primary role is to provide intelligent insights and help the user with their business.
175
- You were unable to process a complex user query. Do not mention the error. Instead, gracefully pivot by presenting a "Business Intelligence Briefing".
176
- Structure your response with clear markdown headings for each section of the briefing data.
177
- Crucially, interpret the data in the "Performance Snapshot" - highlight the percentage changes as indicators of trends (e.g., "Revenue is up by 15.2%...").
178
-
179
- using the business data also provide insight and suggest improvements and ideas where necessary.
 
180
 
181
- User's Original Question: "{user_question}"
182
- Business Intelligence Briefing Data: {json.dumps(briefing, indent=2, ensure_ascii=False)}
183
  """
184
  response = self.llm.invoke(fallback_prompt)
185
  return response.content if hasattr(response, 'content') else str(response)
186
 
187
- # --- REFACTORED /chat Endpoint with Correct Tiered Logic ---
 
 
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
- # START: PRESERVED PANDASAI IMPLEMENTATION
211
  pandas_agent = SmartDataframe(df, config={
212
- "llm": llm, "response_parser": FlaskResponse,
 
213
  "custom_whitelisted_dependencies": [
214
- "os", "io", "sys", "chr", "glob",
215
- "b64decoder", "collections", "geopy",
216
- "geopandas", "wordcloud", "builtins"
 
217
  ],
218
- "security": "none", "save_charts_path": user_defined_path,
219
- "save_charts": False, "enable_cache": False, "conversational":True
 
 
 
 
220
  })
 
221
  answer = pandas_agent.chat(user_question)
222
 
223
- # ROBUSTNESS CHECK: Actively inspect the answer for soft failures.
224
- is_failure = False
225
- if answer is None:
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
- if not is_failure:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  logger.info("Successfully answered with Tier 1 (PandasAI).")
236
  formatted_answer = str(answer)
237
- if isinstance(answer, pd.DataFrame): formatted_answer = answer.to_html()
 
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
- logger.warning(f"Tier 1 (PandasAI) failed with exception: '{e}'. Proceeding to Tier 2 Fallback.")
 
 
246
 
247
- # --- TIER 2 (GRACEFUL FALLBACK): COMPREHENSIVE KPI ANALYST ---
248
- logger.info("Executing Tier 2 Fallback: IrisReportEngine.")
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)