OLLAMA / database.py
akra35567's picture
Update database.py
d55ae9b verified
# database.py — V24 — COMPLETO COM salvar_embedding() + NUMPY + MIGRAÇÕES
import sqlite3
import time
import os
import json
import numpy as np
from typing import List, Dict, Any, Tuple
from loguru import logger
class Database:
def __init__(self, db_path: str = "/app/akira.db"):
self.db_path = db_path
self.max_retries = 5
self.retry_delay = 0.1
os.makedirs(os.path.dirname(db_path), exist_ok=True)
self._init_db()
self._ensure_all_columns_and_indexes()
def _get_connection(self) -> sqlite3.Connection:
for attempt in range(self.max_retries):
try:
conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
conn.execute('PRAGMA journal_mode=WAL')
conn.execute('PRAGMA synchronous=NORMAL')
conn.execute('PRAGMA busy_timeout=30000')
conn.execute('PRAGMA foreign_keys=ON')
return conn
except sqlite3.OperationalError as e:
if "database is locked" in str(e) and attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (2 ** attempt))
continue
logger.error(f"Falha ao conectar: {e}")
raise
def _execute(self, query: str, params: tuple = (), commit: bool = False):
for attempt in range(self.max_retries):
try:
with self._get_connection() as conn:
c = conn.cursor()
c.execute(query, params)
result = c.fetchall() if query.strip().upper().startswith('SELECT') else None
if commit:
conn.commit()
return result
except sqlite3.OperationalError as e:
if "database is locked" in str(e) and attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (2 ** attempt))
continue
logger.error(f"Erro SQL: {e}")
raise
# ================================================================
# INICIALIZAÇÃO — TODAS AS TABELAS DO V18 + NOVAS
# ================================================================
def _init_db(self):
with self._get_connection() as conn:
c = conn.cursor()
c.executescript('''
-- TABELAS ORIGINAIS (V18)
CREATE TABLE IF NOT EXISTS aprendizado (
id INTEGER PRIMARY KEY AUTOINCREMENT,
usuario TEXT,
dado TEXT,
valor TEXT
);
CREATE TABLE IF NOT EXISTS exemplos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tipo TEXT NOT NULL,
entrada TEXT NOT NULL,
resposta TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS info_geral (
chave TEXT PRIMARY KEY,
valor TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS estilos (
numero_usuario TEXT PRIMARY KEY,
estilo TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS preferencias_tom (
numero_usuario TEXT PRIMARY KEY,
tom TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS afinidades (
numero_usuario TEXT PRIMARY KEY,
afinidade REAL NOT NULL
);
CREATE TABLE IF NOT EXISTS termos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
termo TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS aprendizados (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
chave TEXT NOT NULL,
valor TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS aprendizado_detalhado (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
tipo TEXT NOT NULL,
chave TEXT NOT NULL,
valor TEXT NOT NULL,
fonte TEXT,
confianca REAL DEFAULT 1.0,
contexto TEXT,
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS vocabulario_patenteado (
termo TEXT PRIMARY KEY,
definicao TEXT NOT NULL,
uso TEXT NOT NULL,
exemplo TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
numero_usuario TEXT PRIMARY KEY,
nome TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS whatsapp_ids (
id INTEGER PRIMARY KEY AUTOINCREMENT,
whatsapp_id TEXT NOT NULL,
sender_number TEXT NOT NULL,
UNIQUE (whatsapp_id, sender_number)
);
CREATE TABLE IF NOT EXISTS embeddings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT,
source_type TEXT,
texto TEXT,
embedding BLOB NOT NULL
);
CREATE TABLE IF NOT EXISTS mensagens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
usuario TEXT NOT NULL,
mensagem TEXT NOT NULL,
resposta TEXT NOT NULL,
numero TEXT,
is_reply BOOLEAN DEFAULT 0,
mensagem_original TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS emocao_exemplos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
emocao TEXT NOT NULL,
entrada TEXT NOT NULL,
resposta TEXT NOT NULL,
tom TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS girias_aprendidas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
giria TEXT NOT NULL,
significado TEXT NOT NULL,
contexto TEXT,
frequencia INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tom_usuario (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
tom_detectado TEXT NOT NULL,
intensidade REAL DEFAULT 0.5,
contexto TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS adaptacao_dinamica (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
tipo_adaptacao TEXT NOT NULL,
valor_anterior TEXT,
valor_novo TEXT,
razao TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS pronomes_por_tom (
tom TEXT PRIMARY KEY,
pronomes TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS contexto (
user_key TEXT PRIMARY KEY,
historico TEXT,
emocao_atual TEXT,
termos TEXT,
girias TEXT,
tom TEXT
);
-- TABELAS NOVAS (V22/V23)
CREATE TABLE IF NOT EXISTS abrev_usuarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_usuario TEXT NOT NULL,
abreviacao TEXT NOT NULL,
palavra_completa TEXT,
frequencia INTEGER DEFAULT 1
);
-- DADOS INICIAIS
INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES
('formal', 'Sr., ilustre, boss, maior, homem'),
('rude', 'parvo, estúpido, burro, analfabeto, desperdício de esperma'),
('casual', 'mano, puto, cota, mwangolé, kota'),
('neutro', 'amigo, parceiro, camarada');
''')
conn.commit()
logger.info(f"Banco V24 inicializado: {self.db_path}")
# ================================================================
# MIGRAÇÃO AUTOMÁTICA — GARANTE COLUNAS + ÍNDICES
# ================================================================
def _ensure_all_columns_and_indexes(self):
with self._get_connection() as conn:
c = conn.cursor()
# Migrações para embeddings
c.execute("PRAGMA table_info(embeddings)")
cols = {row[1] for row in c.fetchall()}
if 'numero_usuario' not in cols:
c.execute("ALTER TABLE embeddings ADD COLUMN numero_usuario TEXT")
if 'source_type' not in cols:
c.execute("ALTER TABLE embeddings ADD COLUMN source_type TEXT DEFAULT 'conversa'")
if 'texto' not in cols:
c.execute("ALTER TABLE embeddings ADD COLUMN texto TEXT DEFAULT ''")
# Índices
c.executescript('''
CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero);
CREATE INDEX IF NOT EXISTS idx_girias_usuario ON girias_aprendidas(numero_usuario);
CREATE INDEX IF NOT EXISTS idx_abrev_usuario ON abrev_usuarios(numero_usuario);
CREATE INDEX IF NOT EXISTS idx_aprendizados_usuario ON aprendizados(numero_usuario);
CREATE INDEX IF NOT EXISTS idx_aprendizado_detalhado_usuario ON aprendizado_detalhado(numero_usuario);
''')
conn.commit()
# ================================================================
# MÉTODOS PRINCIPAIS (para app.py)
# ================================================================
def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
cols = ['usuario', 'mensagem', 'resposta']
vals = [usuario, mensagem, resposta]
if numero: cols.append('numero'); vals.append(numero)
if is_reply is not None: cols.append('is_reply'); vals.append(int(is_reply))
if mensagem_original: cols.append('mensagem_original'); vals.append(mensagem_original)
placeholders = ', '.join(['?' for _ in cols])
query = f"INSERT INTO mensagens ({', '.join(cols)}) VALUES ({placeholders})"
self._execute(query, tuple(vals), commit=True)
def carregar_contexto(self, numero: str) -> Dict[str, Any]:
row = self._execute("SELECT historico, girias, tom FROM contexto WHERE user_key=?", (numero,))
if row:
return {
"historico": json.loads(row[0][0] or "[]")[-8:],
"girias": json.loads(row[0][1] or "[]"),
"tom": row[0][2] or "neutro"
}
return {"historico": [], "girias": [], "tom": "neutro"}
def salvar_contexto(self, numero: str, historico: List, girias: List, tom: str):
self._execute(
"INSERT OR REPLACE INTO contexto (user_key, historico, girias, tom) VALUES (?, ?, ?, ?)",
(numero, json.dumps(historico), json.dumps(girias), tom), commit=True
)
def recuperar_girias_usuario(self, numero_usuario: str) -> List[str]:
rows = self._execute("SELECT giria FROM girias_aprendidas WHERE numero_usuario=?", (numero_usuario,))
return [r[0] for r in rows] if rows else []
def recuperar_abreviacoes_usuario(self, numero_usuario: str) -> Dict[str, str]:
rows = self._execute("SELECT abreviacao, palavra_completa FROM abrev_usuarios WHERE numero_usuario=?", (numero_usuario,))
return {r[0]: r[1] for r in rows} if rows else {}
def aprender_giria(self, numero: str, giria: str, significado: str = None):
row = self._execute("SELECT frequencia FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero, giria))
if row:
self._execute("UPDATE girias_aprendidas SET frequencia = frequencia + 1, updated_at = CURRENT_TIMESTAMP WHERE numero_usuario=? AND giria=?", (numero, giria), commit=True)
else:
self._execute("INSERT INTO girias_aprendidas (numero_usuario, giria, significado) VALUES (?, ?, ?)", (numero, giria, significado or ""), commit=True)
def aprender_abreviacao(self, numero: str, abrev: str, completa: str):
row = self._execute("SELECT frequencia FROM abrev_usuarios WHERE numero_usuario=? AND abreviacao=?", (numero, abrev))
if row:
self._execute("UPDATE abrev_usuarios SET frequencia = frequencia + 1 WHERE numero_usuario=? AND abreviacao=?", (numero, abrev), commit=True)
else:
self._execute("INSERT INTO abrev_usuarios (numero_usuario, abreviacao, palavra_completa) VALUES (?, ?, ?)", (numero, abrev, completa), commit=True)
def detectar_tom(self, numero: str, mensagem: str) -> str:
rude = any(w in mensagem.lower() for w in ["parvo", "burro", "merda"])
casual = any(w in mensagem.lower() for w in ["epá", "kandando", "bué", "kota"])
if rude: return "rude"
if casual: return "casual"
return "neutro"
# ================================================================
# NOVO MÉTODO: salvar_embedding() — COM NUMPY
# ================================================================
def salvar_embedding(self, numero_usuario: str, mensagem: str, resposta: str, embedding: list, texto: str = ""):
"""
Salva embedding no banco (tabela 'embeddings').
"""
try:
embedding_blob = np.array(embedding, dtype=np.float32).tobytes()
self._execute(
"""INSERT INTO embeddings
(numero_usuario, source_type, texto, embedding)
VALUES (?, ?, ?, ?)""",
(numero_usuario, 'conversa', texto or f"{mensagem} {resposta}", embedding_blob),
commit=True
)
logger.debug(f"Embedding salvo para {numero_usuario}")
except Exception as e:
logger.error(f"Erro ao salvar embedding: {e}")
# ================================================================
# TESTE RÁPIDO (OPCIONAL)
# ================================================================
if __name__ == "__main__":
db = Database()
db.salvar_mensagem("Zé", "Oi", "Epá!", "244999123456")
print("Banco V24 pronto!")