REMB / src /services /gemini_service.py
Cuong2004's picture
update agent/mcp/tool, add algo jupyter
56e31ec
"""
Gemini AI Service
Google Gemini 2.5 Flash integration for intelligent chat responses
With fallback to hardcoded responses
"""
import os
from typing import Dict, List, Optional
import logging
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logger = logging.getLogger(__name__)
# Try importing Gemini library
try:
import google.generativeai as genai
GEMINI_AVAILABLE = True
except ImportError:
GEMINI_AVAILABLE = False
logger.warning("google-generativeai not installed, using fallback mode")
class GeminiService:
"""
Google Gemini AI integration
Uses Gemini Flash 2.0 for intelligent responses.
Falls back to hardcoded responses if API unavailable.
"""
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
self.model = None
self.is_available = False
if GEMINI_AVAILABLE and self.api_key:
try:
genai.configure(api_key=self.api_key)
self.model = genai.GenerativeModel('gemini-2.5-flash')
self.is_available = True
logger.info("Gemini AI service initialized with gemini-2.5-flash")
except Exception as e:
logger.warning(f"Failed to initialize Gemini: {e}")
def chat(
self,
message: str,
layouts: List[Dict] = None,
boundary_metadata: Dict = None
) -> Dict[str, str]:
"""
Generate chat response
Args:
message: User's question
layouts: Current layout options
boundary_metadata: Site boundary info
Returns:
Dict with 'message' and 'model' keys
"""
if self.is_available and self.model:
try:
response = self._gemini_chat(message, layouts, boundary_metadata)
return {"message": response, "model": "gemini-2.5-flash"}
except Exception as e:
logger.warning(f"Gemini API error: {e}, using fallback")
# Fallback to hardcoded responses
response = self._fallback_chat(message, layouts)
return {"message": response, "model": "fallback"}
def _gemini_chat(
self,
message: str,
layouts: List[Dict] = None,
metadata: Dict = None
) -> str:
"""Call Gemini API with context"""
# Build context from layouts
context = self._build_context(layouts, metadata)
prompt = f"""You are an AI assistant for AIOptimize™, an industrial estate planning system.
CONTEXT:
{context}
USER QUESTION: {message}
Provide a helpful, concise response about the layout options or optimization process.
Focus on practical advice and explain trade-offs clearly.
Keep your response under 150 words.
"""
response = self.model.generate_content(prompt)
return response.text
def _build_context(self, layouts: List[Dict], metadata: Dict) -> str:
"""Build context string from current data"""
parts = []
if metadata:
parts.append(f"Site: {metadata.get('area', 0):.0f} m² area, {metadata.get('perimeter', 0):.0f}m perimeter")
if layouts:
parts.append(f"\nGenerated {len(layouts)} layout options:")
for layout in layouts:
metrics = layout.get('metrics', {})
parts.append(
f"- {layout.get('name', 'Option')}: "
f"{metrics.get('total_plots', 0)} plots, "
f"{metrics.get('total_area', 0):.0f}m² total, "
f"fitness={metrics.get('fitness', 0):.2f}"
)
return "\n".join(parts) if parts else "No site analyzed yet."
def _fallback_chat(self, message: str, layouts: List[Dict] = None) -> str:
"""
Hardcoded fallback responses based on keyword matching
Per MVP-24h.md specification
"""
msg_lower = message.lower()
# Category: Layout differences
if any(word in msg_lower for word in ["difference", "compare", "between", "options"]):
if layouts and len(layouts) >= 3:
return (
f"The three layout options offer different trade-offs:\n\n"
f"💰 **{layouts[0].get('name', 'Option 1')}**: Maximizes sellable area with more plots. "
f"Best for high-density industrial use.\n\n"
f"⚖️ **{layouts[1].get('name', 'Option 2')}**: Balanced approach with medium plot sizes. "
f"Good mix of space efficiency and plot utility.\n\n"
f"🏢 **{layouts[2].get('name', 'Option 3')}**: Premium layout with fewer, larger plots. "
f"Ideal for tenants needing more space per unit."
)
return "Please generate layouts first to compare options."
# Category: Best option recommendation
if any(word in msg_lower for word in ["best", "recommend", "which", "should"]):
if layouts:
best = max(layouts, key=lambda x: x.get('metrics', {}).get('fitness', 0))
return (
f"Based on the optimization analysis, I recommend **{best.get('name', 'Option 1')}** "
f"with a fitness score of {best.get('metrics', {}).get('fitness', 0):.2f}.\n\n"
f"This option offers {best.get('metrics', {}).get('total_plots', 0)} plots "
f"totaling {best.get('metrics', {}).get('total_area', 0):.0f}m² of sellable area.\n\n"
f"However, the 'best' choice depends on your priorities - "
f"maximum revenue, balanced development, or premium positioning."
)
return "Please generate layouts first to get a recommendation."
# Category: Compliance/regulations
if any(word in msg_lower for word in ["compliance", "regulation", "setback", "legal", "zone"]):
return (
"All generated layouts comply with the following requirements:\n\n"
"✅ **50m boundary setback**: All plots maintain minimum distance from site edges\n"
"✅ **Plot spacing**: Adequate spacing between plots for access roads\n"
"✅ **Geometry validation**: All plots have valid rectangular shapes\n\n"
"The genetic algorithm automatically enforces these constraints during optimization."
)
# Category: Metrics explanation
if any(word in msg_lower for word in ["metric", "fitness", "score", "calculate"]):
return (
"The layout metrics are calculated as follows:\n\n"
"📊 **Fitness Score** = (Profit × 0.5) + (Compliance × 0.3) + (Efficiency × 0.2)\n\n"
"- **Profit**: Based on total sellable area (more area = higher profit)\n"
"- **Compliance**: 1.0 if all setback rules met, lower if violated\n"
"- **Efficiency**: Ratio of plots placed vs. target count\n\n"
"Higher fitness scores indicate better overall layouts."
)
# Category: Algorithm explanation
if any(word in msg_lower for word in ["algorithm", "genetic", "how", "work", "optimize"]):
return (
"AIOptimize uses a **Genetic Algorithm (GA)** for optimization:\n\n"
"1️⃣ **Initialize**: Create 10 random layout candidates\n"
"2️⃣ **Evaluate**: Calculate fitness for each layout\n"
"3️⃣ **Select**: Keep top 3 performers (elitism)\n"
"4️⃣ **Mutate**: Create variations of elite layouts\n"
"5️⃣ **Repeat**: Run for 20 generations\n\n"
"This produces diverse, optimized solutions that balance multiple objectives."
)
# Category: Export
if any(word in msg_lower for word in ["export", "dxf", "cad", "download", "autocad"]):
return (
"You can export layouts in **DXF format** for use in CAD software:\n\n"
"📥 **Single Layout**: Click the DXF button on any option card\n"
"📦 **All Layouts**: Use 'Export All as ZIP' for all three options\n\n"
"DXF files include:\n"
"- Site boundary and setback zones\n"
"- Plot geometries with labels\n"
"- Area annotations\n"
"- Professional layer organization\n\n"
"Files work with AutoCAD, LibreCAD, and free online DXF viewers."
)
# Default response
return (
"I'm your AI assistant for industrial estate planning. I can help you understand:\n\n"
"• **Layout options** - Compare the three generated designs\n"
"• **Optimization** - How the genetic algorithm works\n"
"• **Metrics** - What fitness scores mean\n"
"• **Compliance** - Setback and zoning rules\n"
"• **Export** - Download DXF files for CAD software\n\n"
"What would you like to know?"
)
# Global instance
gemini_service = GeminiService()