Aniket2006 commited on
Commit
a66f549
·
1 Parent(s): 1f7cc04

Add cascading fallback for 4 Groq API keys

Browse files
Files changed (1) hide show
  1. app.py +105 -85
app.py CHANGED
@@ -350,62 +350,69 @@ def generate_heatmap_image(data: np.ndarray, index_type: str, gaussian_sigma: fl
350
  # ============================================================================
351
  # LLM ANALYSIS (for risk metrics)
352
  # ============================================================================
 
 
 
 
 
 
 
 
 
353
  def run_llm_analysis(metric: str, stress_context: dict, indices_data: dict,
354
  time_series_data: dict = None, weather_data: dict = None) -> dict:
355
- """Call Groq LLM with full context from stress detection, timeseries, and weather."""
356
- try:
357
- from groq import Groq
358
-
359
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "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")
360
- GROQ_MODEL = "llama-3.3-70b-versatile"
361
-
362
- client = Groq(api_key=GROQ_API_KEY)
363
-
364
- # Format stress context
365
- stress_text = format_stress_context(stress_context)
366
-
367
- # Format time series data for all indices
368
- ts_text = ""
369
- if time_series_data:
370
- ts_text = "\n\nTIME SERIES DATA (ALL INDICES - HISTORICAL + FORECAST):\n"
371
- ts_text += "=" * 50 + "\n"
372
- for index_name, ts_data in time_series_data.items():
373
- ts_text += f"\n{index_name}:\n"
374
- # Historical
375
- if ts_data.get('historical'):
376
- hist = ts_data['historical']
377
- if len(hist) > 0:
378
- first_val = hist[0].get('value', 0) if isinstance(hist[0], dict) else 0
379
- last_val = hist[-1].get('value', 0) if isinstance(hist[-1], dict) else 0
380
- ts_text += f" Historical ({len(hist)} points): from {first_val:.4f} to {last_val:.4f} (change: {last_val-first_val:+.4f})\n"
381
- # Forecast
382
- if ts_data.get('forecast'):
383
- fcast = ts_data['forecast']
384
- if len(fcast) > 0:
385
- first_val = fcast[0].get('value', 0) if isinstance(fcast[0], dict) else 0
386
- last_val = fcast[-1].get('value', 0) if isinstance(fcast[-1], dict) else 0
387
- ts_text += f" Forecast ({len(fcast)} days): from {first_val:.4f} to {last_val:.4f} (predicted: {last_val-first_val:+.4f})\n"
388
-
389
- # Format weather data
390
- weather_text = ""
391
- if weather_data:
392
- weather_text = "\n\nWEATHER CONDITIONS:\n"
393
- weather_text += "=" * 30 + "\n"
394
- if 'temperature' in weather_data:
395
- weather_text += f"- Temperature: {weather_data['temperature']}°C\n"
396
- if 'humidity' in weather_data:
397
- weather_text += f"- Humidity: {weather_data['humidity']}%\n"
398
- if 'precipitation' in weather_data:
399
- weather_text += f"- Precipitation: {weather_data['precipitation']} mm\n"
400
- if 'wind_speed' in weather_data:
401
- weather_text += f"- Wind Speed: {weather_data['wind_speed']} km/h\n"
402
- if 'conditions' in weather_data:
403
- weather_text += f"- Conditions: {weather_data['conditions']}\n"
404
- if 'forecast' in weather_data:
405
- weather_text += f"- Forecast: {weather_data['forecast']}\n"
406
-
407
- # Create targeted prompt based on metric
408
- prompt = f"""CROP STRESS ANALYSIS REQUEST
409
 
410
  {stress_text}
411
  {ts_text}
@@ -424,37 +431,50 @@ Respond with ONLY a valid JSON object (no markdown):
424
  "recommendations": ["action 1", "action 2", "action 3"]
425
  }}
426
  """
427
-
428
- chat_completion = client.chat.completions.create(
429
- messages=[
430
- {"role": "system", "content": "You are an expert agricultural AI. Provide detailed, data-driven analysis. Respond with valid JSON only."},
431
- {"role": "user", "content": prompt}
432
- ],
433
- model=GROQ_MODEL,
434
- temperature=0.7,
435
- max_tokens=1500,
436
- )
437
-
438
- response_text = chat_completion.choices[0].message.content.strip()
439
-
440
- # Clean markdown if present
441
- if response_text.startswith("```"):
442
- lines = response_text.split("\n")
443
- response_text = "\n".join(lines[1:-1])
444
- if response_text.startswith("json"):
445
- response_text = response_text[4:].strip()
446
-
447
- import json
448
- return json.loads(response_text)
449
-
450
- except Exception as e:
451
- logger.error(f"LLM analysis failed: {e}")
452
- return {
453
- "level": "Moderate",
454
- "analysis": "Analysis unavailable",
455
- "detailed_analysis": "Unable to generate detailed analysis due to processing error. Please try refreshing.",
456
- "recommendations": ["Manual inspection recommended"]
457
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
458
 
459
  # ============================================================================
460
  # API ENDPOINTS
 
350
  # ============================================================================
351
  # LLM ANALYSIS (for risk metrics)
352
  # ============================================================================
353
+
354
+ # Groq API keys with cascading fallback
355
+ GROQ_API_KEYS = [
356
+ "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",
357
+ "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",
358
+ "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",
359
+ "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",
360
+ ]
361
+
362
  def run_llm_analysis(metric: str, stress_context: dict, indices_data: dict,
363
  time_series_data: dict = None, weather_data: dict = None) -> dict:
364
+ """Call Groq LLM with full context from stress detection, timeseries, and weather.
365
+ Uses cascading fallback through 4 API keys if one fails."""
366
+ from groq import Groq
367
+ import json
368
+
369
+ GROQ_MODEL = "llama-3.3-70b-versatile"
370
+
371
+ # Format stress context
372
+ stress_text = format_stress_context(stress_context)
373
+
374
+ # Format time series data for all indices
375
+ ts_text = ""
376
+ if time_series_data:
377
+ ts_text = "\n\nTIME SERIES DATA (ALL INDICES - HISTORICAL + FORECAST):\n"
378
+ ts_text += "=" * 50 + "\n"
379
+ for index_name, ts_data in time_series_data.items():
380
+ ts_text += f"\n{index_name}:\n"
381
+ # Historical
382
+ if ts_data.get('historical'):
383
+ hist = ts_data['historical']
384
+ if len(hist) > 0:
385
+ first_val = hist[0].get('value', 0) if isinstance(hist[0], dict) else 0
386
+ last_val = hist[-1].get('value', 0) if isinstance(hist[-1], dict) else 0
387
+ ts_text += f" Historical ({len(hist)} points): from {first_val:.4f} to {last_val:.4f} (change: {last_val-first_val:+.4f})\n"
388
+ # Forecast
389
+ if ts_data.get('forecast'):
390
+ fcast = ts_data['forecast']
391
+ if len(fcast) > 0:
392
+ first_val = fcast[0].get('value', 0) if isinstance(fcast[0], dict) else 0
393
+ last_val = fcast[-1].get('value', 0) if isinstance(fcast[-1], dict) else 0
394
+ ts_text += f" Forecast ({len(fcast)} days): from {first_val:.4f} to {last_val:.4f} (predicted: {last_val-first_val:+.4f})\n"
395
+
396
+ # Format weather data
397
+ weather_text = ""
398
+ if weather_data:
399
+ weather_text = "\n\nWEATHER CONDITIONS:\n"
400
+ weather_text += "=" * 30 + "\n"
401
+ if 'temperature' in weather_data:
402
+ weather_text += f"- Temperature: {weather_data['temperature']}°C\n"
403
+ if 'humidity' in weather_data:
404
+ weather_text += f"- Humidity: {weather_data['humidity']}%\n"
405
+ if 'precipitation' in weather_data:
406
+ weather_text += f"- Precipitation: {weather_data['precipitation']} mm\n"
407
+ if 'wind_speed' in weather_data:
408
+ weather_text += f"- Wind Speed: {weather_data['wind_speed']} km/h\n"
409
+ if 'conditions' in weather_data:
410
+ weather_text += f"- Conditions: {weather_data['conditions']}\n"
411
+ if 'forecast' in weather_data:
412
+ weather_text += f"- Forecast: {weather_data['forecast']}\n"
413
+
414
+ # Create targeted prompt based on metric
415
+ prompt = f"""CROP STRESS ANALYSIS REQUEST
 
 
416
 
417
  {stress_text}
418
  {ts_text}
 
431
  "recommendations": ["action 1", "action 2", "action 3"]
432
  }}
433
  """
434
+
435
+ # Try each API key in sequence (cascading fallback)
436
+ last_error = None
437
+ for i, api_key in enumerate(GROQ_API_KEYS):
438
+ try:
439
+ logger.info(f"Trying Groq API key {i+1}/{len(GROQ_API_KEYS)}")
440
+ client = Groq(api_key=api_key)
441
+
442
+ chat_completion = client.chat.completions.create(
443
+ messages=[
444
+ {"role": "system", "content": "You are an expert agricultural AI. Provide detailed, data-driven analysis. Respond with valid JSON only."},
445
+ {"role": "user", "content": prompt}
446
+ ],
447
+ model=GROQ_MODEL,
448
+ temperature=0.7,
449
+ max_tokens=1500,
450
+ )
451
+
452
+ response_text = chat_completion.choices[0].message.content.strip()
453
+
454
+ # Clean markdown if present
455
+ if response_text.startswith("```"):
456
+ lines = response_text.split("\n")
457
+ response_text = "\n".join(lines[1:-1])
458
+ if response_text.startswith("json"):
459
+ response_text = response_text[4:].strip()
460
+
461
+ result = json.loads(response_text)
462
+ logger.info(f"Groq API key {i+1} succeeded")
463
+ return result
464
+
465
+ except Exception as e:
466
+ last_error = e
467
+ logger.warning(f"Groq API key {i+1} failed: {e}")
468
+ continue
469
+
470
+ # All keys failed
471
+ logger.error(f"All {len(GROQ_API_KEYS)} Groq API keys failed. Last error: {last_error}")
472
+ return {
473
+ "level": "Moderate",
474
+ "analysis": "Analysis unavailable",
475
+ "detailed_analysis": "Unable to generate detailed analysis due to API errors. All API keys exhausted. Please try refreshing.",
476
+ "recommendations": ["Manual inspection recommended"]
477
+ }
478
 
479
  # ============================================================================
480
  # API ENDPOINTS