Spaces:
Sleeping
Sleeping
Update modules/database.py
Browse files- modules/database.py +150 -3
modules/database.py
CHANGED
|
@@ -8,7 +8,7 @@ Banco de dados SQLite para Akira IA - COMPLETAMENTE ATUALIZADO
|
|
| 8 |
✅ Memória emocional BERT GoEmotions
|
| 9 |
✅ Backup e otimização automática
|
| 10 |
✅ ADAPTADO ao payload do index.js
|
| 11 |
-
✅ NOVAS TABELAS: audio_transcricoes, grupo_contexto
|
| 12 |
✅ NOVAS COLUNAS: tipo_mensagem, reply_info_json
|
| 13 |
"""
|
| 14 |
import sqlite3
|
|
@@ -145,6 +145,7 @@ class Database:
|
|
| 145 |
except sqlite3.OperationalError:
|
| 146 |
pass # Coluna já existe
|
| 147 |
|
|
|
|
| 148 |
indexes = [
|
| 149 |
"CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero, deletado)",
|
| 150 |
"CREATE INDEX IF NOT EXISTS idx_mensagens_contexto ON mensagens(contexto_id)",
|
|
@@ -160,7 +161,8 @@ class Database:
|
|
| 160 |
"CREATE INDEX IF NOT EXISTS idx_comandos_numero ON comandos_executados(numero, timestamp)",
|
| 161 |
"CREATE INDEX IF NOT EXISTS idx_reset_numero ON reset_log(numero, timestamp)",
|
| 162 |
"CREATE INDEX IF NOT EXISTS idx_audio_usuario ON audio_transcricoes(numero_usuario, sucesso)",
|
| 163 |
-
"CREATE INDEX IF NOT EXISTS idx_grupo_contexto_id ON grupo_contexto(grupo_id, contexto_id)"
|
|
|
|
| 164 |
]
|
| 165 |
|
| 166 |
for idx_query in indexes:
|
|
@@ -361,6 +363,19 @@ class Database:
|
|
| 361 |
)
|
| 362 |
''')
|
| 363 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
conn.commit()
|
| 365 |
|
| 366 |
logger.info("✅ Todas as tabelas criadas/verificadas (ADAPTADO)")
|
|
@@ -600,7 +615,7 @@ class Database:
|
|
| 600 |
"mensagens", "contexto", "transicoes_humor",
|
| 601 |
"girias_aprendidas", "training_examples",
|
| 602 |
"comandos_executados", "reset_log",
|
| 603 |
-
"audio_transcricoes", "grupo_contexto"
|
| 604 |
]
|
| 605 |
|
| 606 |
for tabela in tabelas_para_resetar:
|
|
@@ -1147,6 +1162,107 @@ class Database:
|
|
| 1147 |
logger.error(f"Erro ao recuperar histórico de áudio: {e}")
|
| 1148 |
return []
|
| 1149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1150 |
# ================================================================
|
| 1151 |
# MÉTODOS FALTANTES ADICIONADOS (RESOLVEM OS ERROS)
|
| 1152 |
# ================================================================
|
|
@@ -1317,11 +1433,20 @@ class Database:
|
|
| 1317 |
)
|
| 1318 |
girias_aprendidas = result[0][0] if result else 0
|
| 1319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1320 |
return {
|
| 1321 |
"total_mensagens": total_mensagens,
|
| 1322 |
"ultima_atividade": ultima_atividade,
|
| 1323 |
"transicoes_humor": transicoes_humor,
|
| 1324 |
"girias_aprendidas": girias_aprendidas,
|
|
|
|
| 1325 |
"privilegiado": self.is_usuario_privilegiado(numero)
|
| 1326 |
}
|
| 1327 |
|
|
@@ -1332,6 +1457,7 @@ class Database:
|
|
| 1332 |
"ultima_atividade": None,
|
| 1333 |
"transicoes_humor": 0,
|
| 1334 |
"girias_aprendidas": 0,
|
|
|
|
| 1335 |
"privilegiado": False
|
| 1336 |
}
|
| 1337 |
|
|
@@ -1354,6 +1480,27 @@ class Database:
|
|
| 1354 |
logger.error(f"Erro ao limpar mensagens antigas: {e}")
|
| 1355 |
return 0
|
| 1356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1357 |
# ================================================================
|
| 1358 |
# FECHAMENTO SEGURO DA CONEXÃO
|
| 1359 |
# ================================================================
|
|
|
|
| 8 |
✅ Memória emocional BERT GoEmotions
|
| 9 |
✅ Backup e otimização automática
|
| 10 |
✅ ADAPTADO ao payload do index.js
|
| 11 |
+
✅ NOVAS TABELAS: audio_transcricoes, grupo_contexto, embeddings
|
| 12 |
✅ NOVAS COLUNAS: tipo_mensagem, reply_info_json
|
| 13 |
"""
|
| 14 |
import sqlite3
|
|
|
|
| 145 |
except sqlite3.OperationalError:
|
| 146 |
pass # Coluna já existe
|
| 147 |
|
| 148 |
+
# ÍNDICES (ATUALIZADO COM NOVA TABELA EMBEDDINGS)
|
| 149 |
indexes = [
|
| 150 |
"CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero, deletado)",
|
| 151 |
"CREATE INDEX IF NOT EXISTS idx_mensagens_contexto ON mensagens(contexto_id)",
|
|
|
|
| 161 |
"CREATE INDEX IF NOT EXISTS idx_comandos_numero ON comandos_executados(numero, timestamp)",
|
| 162 |
"CREATE INDEX IF NOT EXISTS idx_reset_numero ON reset_log(numero, timestamp)",
|
| 163 |
"CREATE INDEX IF NOT EXISTS idx_audio_usuario ON audio_transcricoes(numero_usuario, sucesso)",
|
| 164 |
+
"CREATE INDEX IF NOT EXISTS idx_grupo_contexto_id ON grupo_contexto(grupo_id, contexto_id)",
|
| 165 |
+
"CREATE INDEX IF NOT EXISTS idx_embeddings_numero ON embeddings(numero, timestamp)"
|
| 166 |
]
|
| 167 |
|
| 168 |
for idx_query in indexes:
|
|
|
|
| 363 |
)
|
| 364 |
''')
|
| 365 |
|
| 366 |
+
# NOVA TABELA: EMBEDDINGS (PARA BUSCA SEMÂNTICA)
|
| 367 |
+
c.execute('''
|
| 368 |
+
CREATE TABLE IF NOT EXISTS embeddings (
|
| 369 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 370 |
+
numero TEXT NOT NULL,
|
| 371 |
+
texto TEXT NOT NULL,
|
| 372 |
+
embedding BLOB NOT NULL,
|
| 373 |
+
modelo TEXT DEFAULT 'MiniLM-L12-v2',
|
| 374 |
+
hash_texto TEXT,
|
| 375 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 376 |
+
)
|
| 377 |
+
''')
|
| 378 |
+
|
| 379 |
conn.commit()
|
| 380 |
|
| 381 |
logger.info("✅ Todas as tabelas criadas/verificadas (ADAPTADO)")
|
|
|
|
| 615 |
"mensagens", "contexto", "transicoes_humor",
|
| 616 |
"girias_aprendidas", "training_examples",
|
| 617 |
"comandos_executados", "reset_log",
|
| 618 |
+
"audio_transcricoes", "grupo_contexto", "embeddings"
|
| 619 |
]
|
| 620 |
|
| 621 |
for tabela in tabelas_para_resetar:
|
|
|
|
| 1162 |
logger.error(f"Erro ao recuperar histórico de áudio: {e}")
|
| 1163 |
return []
|
| 1164 |
|
| 1165 |
+
# ================================================================
|
| 1166 |
+
# NOVO MÉTODO: SALVAR EMBEDDINGS (RESOLVE O ERRO)
|
| 1167 |
+
# ================================================================
|
| 1168 |
+
def salvar_embedding(self, numero: str, texto: str, embedding: bytes) -> bool:
|
| 1169 |
+
"""
|
| 1170 |
+
Salva embedding para busca semântica.
|
| 1171 |
+
"""
|
| 1172 |
+
try:
|
| 1173 |
+
if not embedding or len(embedding) == 0:
|
| 1174 |
+
logger.warning(f"Embedding vazio para {numero}")
|
| 1175 |
+
return False
|
| 1176 |
+
|
| 1177 |
+
if len(embedding) > 1000000: # Limite de 1MB
|
| 1178 |
+
logger.warning(f"Embedding muito grande: {len(embedding)} bytes, truncando")
|
| 1179 |
+
embedding = embedding[:1000000]
|
| 1180 |
+
|
| 1181 |
+
# Cria hash do texto para evitar duplicatas
|
| 1182 |
+
hash_texto = hashlib.md5(texto.encode()).hexdigest()
|
| 1183 |
+
|
| 1184 |
+
# Verifica se já existe
|
| 1185 |
+
result = self._execute_with_retry(
|
| 1186 |
+
"SELECT 1 FROM embeddings WHERE numero = ? AND hash_texto = ?",
|
| 1187 |
+
(str(numero).strip(), hash_texto),
|
| 1188 |
+
fetch=True
|
| 1189 |
+
)
|
| 1190 |
+
|
| 1191 |
+
if result:
|
| 1192 |
+
# Atualiza existente
|
| 1193 |
+
self._execute_with_retry(
|
| 1194 |
+
"""
|
| 1195 |
+
UPDATE embeddings
|
| 1196 |
+
SET embedding = ?, timestamp = CURRENT_TIMESTAMP
|
| 1197 |
+
WHERE numero = ? AND hash_texto = ?
|
| 1198 |
+
""",
|
| 1199 |
+
(embedding, str(numero).strip(), hash_texto),
|
| 1200 |
+
commit=True,
|
| 1201 |
+
fetch=False
|
| 1202 |
+
)
|
| 1203 |
+
logger.debug(f"✅ Embedding atualizado: {numero}, texto: {texto[:50]}...")
|
| 1204 |
+
else:
|
| 1205 |
+
# Insere novo
|
| 1206 |
+
self._execute_with_retry(
|
| 1207 |
+
"""
|
| 1208 |
+
INSERT INTO embeddings
|
| 1209 |
+
(numero, texto, embedding, modelo, hash_texto)
|
| 1210 |
+
VALUES (?, ?, ?, ?, ?)
|
| 1211 |
+
""",
|
| 1212 |
+
(
|
| 1213 |
+
str(numero).strip(),
|
| 1214 |
+
texto[:1000],
|
| 1215 |
+
embedding,
|
| 1216 |
+
'MiniLM-L12-v2',
|
| 1217 |
+
hash_texto
|
| 1218 |
+
),
|
| 1219 |
+
commit=True,
|
| 1220 |
+
fetch=False
|
| 1221 |
+
)
|
| 1222 |
+
logger.debug(f"✅ Embedding salvo: {numero}, tamanho: {len(embedding)} bytes, texto: {texto[:50]}...")
|
| 1223 |
+
|
| 1224 |
+
return True
|
| 1225 |
+
|
| 1226 |
+
except Exception as e:
|
| 1227 |
+
logger.error(f"❌ Erro ao salvar embedding: {e}")
|
| 1228 |
+
return False
|
| 1229 |
+
|
| 1230 |
+
# ================================================================
|
| 1231 |
+
# MÉTODO PARA RECUPERAR EMBEDDINGS
|
| 1232 |
+
# ================================================================
|
| 1233 |
+
def recuperar_embeddings(self, numero: str, limite: int = 20) -> List[Dict]:
|
| 1234 |
+
"""Recupera embeddings do usuário"""
|
| 1235 |
+
try:
|
| 1236 |
+
results = self._execute_with_retry(
|
| 1237 |
+
"""
|
| 1238 |
+
SELECT texto, embedding, modelo, timestamp
|
| 1239 |
+
FROM embeddings
|
| 1240 |
+
WHERE numero = ?
|
| 1241 |
+
ORDER BY timestamp DESC
|
| 1242 |
+
LIMIT ?
|
| 1243 |
+
""",
|
| 1244 |
+
(str(numero).strip(), limite),
|
| 1245 |
+
fetch=True
|
| 1246 |
+
)
|
| 1247 |
+
|
| 1248 |
+
embeddings = []
|
| 1249 |
+
for r in results:
|
| 1250 |
+
try:
|
| 1251 |
+
embeddings.append({
|
| 1252 |
+
"texto": r[0],
|
| 1253 |
+
"embedding": r[1], # bytes
|
| 1254 |
+
"modelo": r[2],
|
| 1255 |
+
"timestamp": r[3]
|
| 1256 |
+
})
|
| 1257 |
+
except:
|
| 1258 |
+
continue
|
| 1259 |
+
|
| 1260 |
+
return embeddings
|
| 1261 |
+
|
| 1262 |
+
except Exception as e:
|
| 1263 |
+
logger.error(f"Erro ao recuperar embeddings: {e}")
|
| 1264 |
+
return []
|
| 1265 |
+
|
| 1266 |
# ================================================================
|
| 1267 |
# MÉTODOS FALTANTES ADICIONADOS (RESOLVEM OS ERROS)
|
| 1268 |
# ================================================================
|
|
|
|
| 1433 |
)
|
| 1434 |
girias_aprendidas = result[0][0] if result else 0
|
| 1435 |
|
| 1436 |
+
# Embeddings salvos
|
| 1437 |
+
result = self._execute_with_retry(
|
| 1438 |
+
"SELECT COUNT(*) FROM embeddings WHERE numero = ?",
|
| 1439 |
+
(str(numero).strip(),),
|
| 1440 |
+
fetch=True
|
| 1441 |
+
)
|
| 1442 |
+
embeddings_salvos = result[0][0] if result else 0
|
| 1443 |
+
|
| 1444 |
return {
|
| 1445 |
"total_mensagens": total_mensagens,
|
| 1446 |
"ultima_atividade": ultima_atividade,
|
| 1447 |
"transicoes_humor": transicoes_humor,
|
| 1448 |
"girias_aprendidas": girias_aprendidas,
|
| 1449 |
+
"embeddings_salvos": embeddings_salvos,
|
| 1450 |
"privilegiado": self.is_usuario_privilegiado(numero)
|
| 1451 |
}
|
| 1452 |
|
|
|
|
| 1457 |
"ultima_atividade": None,
|
| 1458 |
"transicoes_humor": 0,
|
| 1459 |
"girias_aprendidas": 0,
|
| 1460 |
+
"embeddings_salvos": 0,
|
| 1461 |
"privilegiado": False
|
| 1462 |
}
|
| 1463 |
|
|
|
|
| 1480 |
logger.error(f"Erro ao limpar mensagens antigas: {e}")
|
| 1481 |
return 0
|
| 1482 |
|
| 1483 |
+
# ================================================================
|
| 1484 |
+
# MÉTODO PARA LIMPAR EMBEDDINGS ANTIGOS
|
| 1485 |
+
# ================================================================
|
| 1486 |
+
def limpar_embeddings_antigos(self, dias: int = 90) -> int:
|
| 1487 |
+
"""Limpa embeddings antigos"""
|
| 1488 |
+
try:
|
| 1489 |
+
result = self._execute_with_retry(
|
| 1490 |
+
"""
|
| 1491 |
+
DELETE FROM embeddings
|
| 1492 |
+
WHERE timestamp < datetime('now', ?)
|
| 1493 |
+
""",
|
| 1494 |
+
(f'-{dias} days',),
|
| 1495 |
+
commit=True,
|
| 1496 |
+
fetch=False
|
| 1497 |
+
)
|
| 1498 |
+
logger.info(f"🧹 Embeddings antigos ({dias} dias) limpos: {result} registros")
|
| 1499 |
+
return result
|
| 1500 |
+
except Exception as e:
|
| 1501 |
+
logger.error(f"Erro ao limpar embeddings antigos: {e}")
|
| 1502 |
+
return 0
|
| 1503 |
+
|
| 1504 |
# ================================================================
|
| 1505 |
# FECHAMENTO SEGURO DA CONEXÃO
|
| 1506 |
# ================================================================
|