|
|
""" |
|
|
Stable Chat API with multiple fallback layers |
|
|
Ensures 100% availability with Maritaca AI integration |
|
|
""" |
|
|
|
|
|
import os |
|
|
from datetime import datetime |
|
|
from typing import Dict, Any, Optional, List |
|
|
from fastapi import APIRouter, HTTPException, Depends |
|
|
from pydantic import BaseModel, Field |
|
|
import httpx |
|
|
from src.core import get_logger |
|
|
|
|
|
logger = get_logger(__name__) |
|
|
from src.services.maritaca_client import MaritacaClient, MaritacaModel |
|
|
from src.services.chat_service import IntentDetector, IntentType |
|
|
from src.services.chat_data_integration import chat_data_integration |
|
|
|
|
|
router = APIRouter(prefix="/api/v1/chat") |
|
|
|
|
|
@router.get("/health") |
|
|
async def chat_stable_health(): |
|
|
"""Simple health check for chat stable router""" |
|
|
return {"status": "ok", "router": "chat_stable", "portal_integration": "enabled"} |
|
|
|
|
|
|
|
|
maritaca_client = None |
|
|
intent_detector = None |
|
|
|
|
|
def get_maritaca_client(): |
|
|
"""Lazy load Maritaca client""" |
|
|
global maritaca_client |
|
|
if maritaca_client is None: |
|
|
api_key = os.getenv("MARITACA_API_KEY") |
|
|
if api_key: |
|
|
maritaca_client = MaritacaClient( |
|
|
api_key=api_key, |
|
|
model=MaritacaModel.SABIAZINHO_3 |
|
|
) |
|
|
return maritaca_client |
|
|
|
|
|
def get_intent_detector(): |
|
|
"""Lazy load intent detector""" |
|
|
global intent_detector |
|
|
if intent_detector is None: |
|
|
intent_detector = IntentDetector() |
|
|
return intent_detector |
|
|
|
|
|
|
|
|
class ChatRequest(BaseModel): |
|
|
message: str = Field(..., min_length=1, max_length=1000) |
|
|
session_id: Optional[str] = None |
|
|
context: Optional[Dict[str, Any]] = None |
|
|
|
|
|
class ChatResponse(BaseModel): |
|
|
session_id: str |
|
|
agent_id: str |
|
|
agent_name: str |
|
|
message: str |
|
|
confidence: float |
|
|
suggested_actions: Optional[List[str]] = None |
|
|
metadata: Dict[str, Any] = {} |
|
|
|
|
|
|
|
|
FALLBACK_RESPONSES = { |
|
|
IntentType.GREETING: [ |
|
|
"Olá! Sou seu assistente de transparência pública. Como posso ajudar você hoje?", |
|
|
"Oi! Estou aqui para ajudar com informações sobre gastos governamentais. Em que posso ser útil?", |
|
|
"Bem-vindo! Posso ajudar você a entender melhor os gastos públicos. O que gostaria de saber?" |
|
|
], |
|
|
IntentType.INVESTIGATE: [ |
|
|
"Entendi que você quer investigar gastos públicos. Posso ajudar você a buscar informações sobre contratos, licitações e despesas governamentais. Qual área específica você gostaria de investigar?", |
|
|
"Vou ajudar você a investigar os gastos públicos. Temos dados sobre contratos, fornecedores e órgãos públicos. Por onde gostaria de começar?", |
|
|
"Perfeito! Posso analisar contratos e despesas públicas. Me diga qual órgão, período ou tipo de gasto você quer investigar." |
|
|
], |
|
|
IntentType.ANALYZE: [ |
|
|
"Vou analisar essas informações para você. Nossa plataforma examina padrões de gastos, identifica anomalias e fornece insights sobre transparência pública.", |
|
|
"Certo, vou fazer uma análise detalhada. Posso examinar tendências, comparar fornecedores e identificar possíveis irregularidades nos dados públicos.", |
|
|
"Entendido! Farei uma análise completa dos dados solicitados, incluindo padrões de gastos e possíveis pontos de atenção." |
|
|
], |
|
|
IntentType.HELP: [ |
|
|
"Posso ajudar você de várias formas:\n• Investigar contratos e licitações\n• Analisar gastos de órgãos públicos\n• Identificar padrões suspeitos\n• Comparar fornecedores\n• Gerar relatórios de transparência\n\nO que você gostaria de fazer?", |
|
|
"Aqui estão algumas coisas que posso fazer:\n• Buscar informações sobre contratos específicos\n• Analisar gastos por órgão ou período\n• Detectar anomalias em pagamentos\n• Rastrear histórico de fornecedores\n\nComo posso ajudar?", |
|
|
"Sou especializado em transparência pública. Posso:\n• Investigar despesas governamentais\n• Analisar contratos suspeitos\n• Monitorar licitações\n• Gerar insights sobre gastos públicos\n\nQual informação você precisa?" |
|
|
], |
|
|
IntentType.REPORT: [ |
|
|
"Vou preparar um relatório detalhado com as informações solicitadas. O documento incluirá análises, gráficos e recomendações sobre os dados públicos.", |
|
|
"Certo! Prepararei um relatório completo com todos os dados relevantes, incluindo visualizações e insights sobre possíveis irregularidades.", |
|
|
"Entendido! Seu relatório será gerado com análise detalhada dos gastos, comparações relevantes e indicadores de transparência." |
|
|
], |
|
|
IntentType.UNKNOWN: [ |
|
|
"Interessante sua pergunta! Como assistente de transparência pública, posso ajudar com investigações sobre gastos governamentais, contratos e licitações. Como posso ajudar você com isso?", |
|
|
"Não tenho certeza se entendi completamente. Posso ajudar você a investigar gastos públicos, analisar contratos ou buscar informações sobre transparência governamental. O que você gostaria de saber?", |
|
|
"Hmm, deixe-me reformular. Sou especializado em dados de transparência pública. Posso investigar contratos, analisar gastos de órgãos públicos ou identificar padrões suspeitos. Como posso ser útil?" |
|
|
] |
|
|
} |
|
|
|
|
|
def get_fallback_response(intent_type: IntentType, context: Optional[Dict] = None) -> str: |
|
|
"""Get appropriate fallback response based on intent""" |
|
|
import random |
|
|
responses = FALLBACK_RESPONSES.get(intent_type, FALLBACK_RESPONSES[IntentType.UNKNOWN]) |
|
|
return random.choice(responses) |
|
|
|
|
|
async def process_with_maritaca(message: str, intent_type: IntentType, session_id: str, context: Optional[Dict] = None) -> Dict[str, Any]: |
|
|
"""Process message with Maritaca AI with multiple fallback layers and Portal da Transparência integration""" |
|
|
|
|
|
|
|
|
message_lower = message.lower() |
|
|
data_keywords = ["contratos", "gastos", "despesas", "licitação", "fornecedor", "servidor", |
|
|
"órgão", "ministério", "prefeitura", "cnpj", "valor", "empresa"] |
|
|
|
|
|
should_fetch_data = any(keyword in message_lower for keyword in data_keywords) |
|
|
portal_data = None |
|
|
|
|
|
|
|
|
if should_fetch_data and (intent_type in [IntentType.INVESTIGATE, IntentType.ANALYZE, IntentType.UNKNOWN]): |
|
|
try: |
|
|
logger.info(f"Fetching real data from Portal da Transparência for query: {message}") |
|
|
portal_result = await chat_data_integration.process_user_query(message, context) |
|
|
if portal_result and portal_result.get("data"): |
|
|
portal_data = portal_result |
|
|
|
|
|
if portal_result.get("response"): |
|
|
return { |
|
|
"message": portal_result["response"], |
|
|
"agent_used": "portal_transparencia_ai", |
|
|
"model": "sabiazinho-3", |
|
|
"success": True, |
|
|
"data": portal_result.get("data"), |
|
|
"entities": portal_result.get("entities"), |
|
|
"data_type": portal_result.get("data_type") |
|
|
} |
|
|
except Exception as e: |
|
|
logger.warning(f"Portal da Transparência integration failed: {str(e)}") |
|
|
|
|
|
|
|
|
client = get_maritaca_client() |
|
|
if client: |
|
|
try: |
|
|
|
|
|
system_prompt = """Você é um assistente especializado em transparência pública brasileira. |
|
|
Seu papel é ajudar cidadãos a entender e investigar gastos governamentais. |
|
|
Seja claro, objetivo e sempre forneça informações úteis sobre transparência fiscal. |
|
|
Quando apropriado, sugira ações específicas como investigar contratos ou analisar gastos.""" |
|
|
|
|
|
if intent_type == IntentType.INVESTIGATE: |
|
|
system_prompt += "\nO usuário quer investigar gastos. Seja específico sobre como você pode ajudar." |
|
|
elif intent_type == IntentType.ANALYZE: |
|
|
system_prompt += "\nO usuário quer uma análise. Explique que tipo de análise você pode fornecer." |
|
|
|
|
|
|
|
|
if portal_data and portal_data.get("data"): |
|
|
system_prompt += f"\n\nDados reais do Portal da Transparência: {str(portal_data['data'])[:1000]}" |
|
|
|
|
|
messages = [ |
|
|
{"role": "system", "content": system_prompt}, |
|
|
{"role": "user", "content": message} |
|
|
] |
|
|
|
|
|
response = await client.chat_completion( |
|
|
messages=messages, |
|
|
temperature=0.7, |
|
|
max_tokens=300 |
|
|
) |
|
|
|
|
|
if response: |
|
|
result = { |
|
|
"message": response.content if hasattr(response, 'content') else str(response), |
|
|
"agent_used": "maritaca_ai", |
|
|
"model": response.model if hasattr(response, 'model') else "sabiazinho-3", |
|
|
"success": True |
|
|
} |
|
|
|
|
|
if portal_data: |
|
|
result.update({ |
|
|
"data": portal_data.get("data"), |
|
|
"entities": portal_data.get("entities"), |
|
|
"data_type": portal_data.get("data_type") |
|
|
}) |
|
|
return result |
|
|
except Exception as e: |
|
|
logger.warning(f"Maritaca AI failed: {str(e)}") |
|
|
|
|
|
|
|
|
if os.getenv("MARITACA_API_KEY"): |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=5.0) as http_client: |
|
|
response = await http_client.post( |
|
|
"https://chat.maritaca.ai/api/chat/inference", |
|
|
headers={"authorization": f"Bearer {os.getenv('MARITACA_API_KEY')}"}, |
|
|
json={ |
|
|
"messages": [{"role": "user", "content": message}], |
|
|
"model": "sabiazinho-3", |
|
|
"temperature": 0.7 |
|
|
} |
|
|
) |
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
result = { |
|
|
"message": data.get("answer", get_fallback_response(intent_type)), |
|
|
"agent_used": "maritaca_direct", |
|
|
"model": "sabiazinho-3", |
|
|
"success": True |
|
|
} |
|
|
if portal_data: |
|
|
result.update({ |
|
|
"data": portal_data.get("data"), |
|
|
"entities": portal_data.get("entities"), |
|
|
"data_type": portal_data.get("data_type") |
|
|
}) |
|
|
return result |
|
|
except Exception as e: |
|
|
logger.warning(f"Direct Maritaca request failed: {str(e)}") |
|
|
|
|
|
|
|
|
result = { |
|
|
"message": get_fallback_response(intent_type, {"session_id": session_id}), |
|
|
"agent_used": "fallback_intelligent", |
|
|
"model": "rule_based", |
|
|
"success": True |
|
|
} |
|
|
if portal_data: |
|
|
result.update({ |
|
|
"data": portal_data.get("data"), |
|
|
"entities": portal_data.get("entities"), |
|
|
"data_type": portal_data.get("data_type") |
|
|
}) |
|
|
return result |
|
|
|
|
|
@router.post("/stable", response_model=ChatResponse) |
|
|
async def chat_stable(request: ChatRequest) -> ChatResponse: |
|
|
""" |
|
|
Ultra-stable chat endpoint with multiple fallback layers |
|
|
Guarantees response even if all AI services fail |
|
|
""" |
|
|
session_id = request.session_id or f"session_{datetime.now().timestamp()}" |
|
|
|
|
|
try: |
|
|
|
|
|
detector = get_intent_detector() |
|
|
if detector: |
|
|
try: |
|
|
intent = await detector.detect(request.message) |
|
|
intent_type = intent.type |
|
|
confidence = intent.confidence |
|
|
except: |
|
|
|
|
|
message_lower = request.message.lower() |
|
|
if any(word in message_lower for word in ["oi", "olá", "bom dia", "boa tarde", "boa noite"]): |
|
|
intent_type = IntentType.GREETING |
|
|
elif any(word in message_lower for word in ["investigar", "verificar", "buscar", "procurar"]): |
|
|
intent_type = IntentType.INVESTIGATE |
|
|
elif any(word in message_lower for word in ["analisar", "análise", "examinar"]): |
|
|
intent_type = IntentType.ANALYZE |
|
|
elif any(word in message_lower for word in ["ajuda", "help", "como", "o que"]): |
|
|
intent_type = IntentType.HELP |
|
|
else: |
|
|
intent_type = IntentType.UNKNOWN |
|
|
confidence = 0.6 |
|
|
else: |
|
|
intent_type = IntentType.UNKNOWN |
|
|
confidence = 0.5 |
|
|
|
|
|
|
|
|
result = await process_with_maritaca( |
|
|
message=request.message, |
|
|
intent_type=intent_type, |
|
|
session_id=session_id, |
|
|
context=request.context |
|
|
) |
|
|
|
|
|
|
|
|
agent_info = { |
|
|
IntentType.GREETING: ("drummond", "Carlos Drummond"), |
|
|
IntentType.INVESTIGATE: ("zumbi", "Zumbi dos Palmares"), |
|
|
IntentType.ANALYZE: ("anita", "Anita Garibaldi"), |
|
|
IntentType.HELP: ("drummond", "Carlos Drummond"), |
|
|
IntentType.REPORT: ("tiradentes", "Tiradentes"), |
|
|
IntentType.UNKNOWN: ("drummond", "Carlos Drummond") |
|
|
} |
|
|
|
|
|
agent_id, agent_name = agent_info.get(intent_type, ("drummond", "Carlos Drummond")) |
|
|
|
|
|
|
|
|
suggested_actions = { |
|
|
IntentType.GREETING: ["investigate_contracts", "view_recent_expenses", "help"], |
|
|
IntentType.INVESTIGATE: ["filter_by_date", "filter_by_agency", "view_suppliers"], |
|
|
IntentType.ANALYZE: ["generate_report", "view_charts", "compare_periods"], |
|
|
IntentType.HELP: ["start_investigation", "learn_more", "examples"], |
|
|
IntentType.REPORT: ["download_pdf", "share_report", "new_analysis"], |
|
|
IntentType.UNKNOWN: ["help", "examples", "start_investigation"] |
|
|
} |
|
|
|
|
|
|
|
|
metadata = { |
|
|
"intent_type": intent_type.value, |
|
|
"processing_time": 0, |
|
|
"agent_used": result["agent_used"], |
|
|
"model": result["model"], |
|
|
"timestamp": datetime.utcnow().isoformat(), |
|
|
"stable_version": True |
|
|
} |
|
|
|
|
|
|
|
|
if result.get("data"): |
|
|
metadata["portal_data"] = { |
|
|
"type": result.get("data_type"), |
|
|
"entities_found": result.get("entities", {}), |
|
|
"total_records": result.get("data", {}).get("total", 0), |
|
|
"has_data": True |
|
|
} |
|
|
|
|
|
return ChatResponse( |
|
|
session_id=session_id, |
|
|
agent_id=agent_id, |
|
|
agent_name=agent_name, |
|
|
message=result["message"], |
|
|
confidence=confidence, |
|
|
suggested_actions=suggested_actions.get(intent_type, ["help"]), |
|
|
metadata=metadata |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
|
|
|
logger.error(f"Critical error in stable chat: {str(e)}") |
|
|
return ChatResponse( |
|
|
session_id=session_id, |
|
|
agent_id="system", |
|
|
agent_name="Sistema", |
|
|
message="Olá! Sou seu assistente de transparência pública. Estou aqui para ajudar você a investigar gastos governamentais, analisar contratos e entender melhor como o dinheiro público é utilizado. Como posso ajudar?", |
|
|
confidence=1.0, |
|
|
suggested_actions=["investigate_contracts", "view_expenses", "help"], |
|
|
metadata={ |
|
|
"error": str(e), |
|
|
"fallback": "ultimate", |
|
|
"timestamp": datetime.utcnow().isoformat() |
|
|
} |
|
|
) |
|
|
|
|
|
@router.get("/test-portal/{query}") |
|
|
async def test_portal_integration(query: str): |
|
|
""" |
|
|
Test endpoint to verify Portal da Transparência integration |
|
|
Example: /api/v1/chat/test-portal/contratos%20ministerio%20saude |
|
|
""" |
|
|
try: |
|
|
result = await chat_data_integration.process_user_query(query) |
|
|
return { |
|
|
"success": True, |
|
|
"query": query, |
|
|
"data_type": result.get("data_type"), |
|
|
"entities_found": result.get("entities"), |
|
|
"total_records": result.get("data", {}).get("total", 0) if result.get("data") else 0, |
|
|
"response": result.get("response"), |
|
|
"sample_data": result.get("data", {}).get("dados", [])[:3] if result.get("data") else [] |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"success": False, |
|
|
"query": query, |
|
|
"error": str(e) |
|
|
} |
|
|
|
|
|
@router.get("/debug/portal-status") |
|
|
async def debug_portal_status(): |
|
|
"""Debug endpoint to check Portal da Transparência configuration""" |
|
|
import os |
|
|
from src.core.config import settings |
|
|
|
|
|
|
|
|
env_key = os.getenv("TRANSPARENCY_API_KEY") |
|
|
|
|
|
|
|
|
settings_key = None |
|
|
if hasattr(settings, 'transparency_api_key') and settings.transparency_api_key: |
|
|
settings_key = "Configured" |
|
|
|
|
|
|
|
|
service_key = None |
|
|
if hasattr(chat_data_integration, 'portal') and chat_data_integration.portal.api_key: |
|
|
service_key = "Loaded" |
|
|
|
|
|
return { |
|
|
"env_variable": "Found" if env_key else "Not Found", |
|
|
"settings_config": settings_key or "Not Configured", |
|
|
"service_loaded": service_key or "Not Loaded", |
|
|
"portal_base_url": chat_data_integration.portal.BASE_URL if hasattr(chat_data_integration, 'portal') else "Not initialized" |
|
|
} |