Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
"""Las medidas de eficiencia de JARVIS, sacadas del diario.
|
|
|
|
El diario ya apunta por cada frase: cuanto tardo (`seg`), por donde fue (`via`)
|
|
y cuando (`t`). De ahi salen las metricas de rendimiento sin instrumentar nada
|
|
del camino caliente: esto solo LEE, y se calcula fuera del hilo que contesta.
|
|
|
|
## Que se mide y por que importa cada una
|
|
|
|
latencia p50/p95/max del tiempo de respuesta. La mediana dice como va de
|
|
normal; el p95 dice como va cuando va mal, que es lo que se nota.
|
|
reparto que fraccion va al CEREBRO (lento, ~2-3 s) y cual al CATALOGO
|
|
(instantaneo, salta el modelo). Es la palanca de eficiencia mas
|
|
grande: cuanto mas catalogo, mas rapido el conjunto.
|
|
ruido que fraccion de lo que oye es Whisper oyendose a si mismo. No es
|
|
velocidad, es calidad de la escucha, y envenena todo lo demas.
|
|
volumen frases en las ultimas 24 h. Contexto para lo anterior: un p95 malo
|
|
con 4 frases no dice nada.
|
|
rag hit@5/hit@1 del ultimo benchmark del RAG (informe_rag.txt). Es la
|
|
unica que no sale del diario: es una medida de test, periodica.
|
|
|
|
Todo sobre una VENTANA reciente (las ultimas MUESTRA frases), no sobre el diario
|
|
entero: interesa como va AHORA, no la media historica que nunca cambia.
|
|
"""
|
|
import os
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
|
|
from manos.diario import lee
|
|
|
|
AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
INFORME_RAG = os.path.join(AQUI, "datos", "informe_rag.txt")
|
|
|
|
MUESTRA = 600 # las ultimas N frases: "como va ahora"
|
|
CEREBRO = "cerebro"
|
|
|
|
|
|
def _percentil(valores, q):
|
|
if not valores:
|
|
return 0.0
|
|
v = sorted(valores)
|
|
k = (len(v) - 1) * q
|
|
lo, hi = int(k), min(int(k) + 1, len(v) - 1)
|
|
return v[lo] + (v[hi] - v[lo]) * (k - lo)
|
|
|
|
|
|
def _rag():
|
|
"""hit@5 y hit@1 del ultimo informe del RAG, si existe."""
|
|
try:
|
|
txt = open(INFORME_RAG, encoding="utf-8").read()
|
|
except OSError:
|
|
return None
|
|
# el bloque CANDIDATO es el que se promociono; si no, el ultimo que haya
|
|
def pct(clave):
|
|
# " hit@5 43/45 (95%)" -> 95
|
|
ms = re.findall(clave + r"\s+\d+/\d+\s+\((\d+)%\)", txt)
|
|
return int(ms[-1]) if ms else None
|
|
h5, h1 = pct("hit@5"), pct("hit@1")
|
|
if h5 is None:
|
|
return None
|
|
return {"hit5": h5, "hit1": h1 or 0}
|
|
|
|
|
|
def lee_medidas():
|
|
"""El dict de medidas, con las unidades ya listas para el HUD."""
|
|
diario = lee()[-MUESTRA:]
|
|
if not diario:
|
|
return {"n": 0}
|
|
|
|
segs = [d["seg"] for d in diario if isinstance(d.get("seg"), (int, float)) and d["seg"] > 0]
|
|
seg_cerebro = [d["seg"] for d in diario
|
|
if d.get("via") == CEREBRO and isinstance(d.get("seg"), (int, float))]
|
|
seg_catalogo = [d["seg"] for d in diario
|
|
if d.get("via") == "catalogo" and isinstance(d.get("seg"), (int, float))]
|
|
|
|
vias = {}
|
|
for d in diario:
|
|
vias[d.get("via", "?")] = vias.get(d.get("via", "?"), 0) + 1
|
|
total = len(diario)
|
|
|
|
def pct_via(v):
|
|
return round(100 * vias.get(v, 0) / total) if total else 0
|
|
|
|
# volumen: cuantas en las ultimas 24 h, y por hora las ultimas 12 para la traza
|
|
ahora = datetime.now()
|
|
hace24 = ahora - timedelta(hours=24)
|
|
vol24 = 0
|
|
porhora = [0] * 12
|
|
for d in diario:
|
|
try:
|
|
t = datetime.fromisoformat(d.get("t", ""))
|
|
except (ValueError, TypeError):
|
|
continue
|
|
if t >= hace24:
|
|
vol24 += 1
|
|
h = int((ahora - t).total_seconds() // 3600)
|
|
if 0 <= h < 12:
|
|
porhora[11 - h] += 1
|
|
|
|
medidas = {
|
|
"n": total,
|
|
"lat_p50": round(_percentil(segs, 0.50), 2),
|
|
"lat_p95": round(_percentil(segs, 0.95), 2),
|
|
"lat_max": round(max(segs), 2) if segs else 0.0,
|
|
"lat_cerebro": round(sum(seg_cerebro) / len(seg_cerebro), 2) if seg_cerebro else 0.0,
|
|
"lat_catalogo": round(sum(seg_catalogo) / len(seg_catalogo), 2) if seg_catalogo else 0.0,
|
|
"pct_cerebro": pct_via("cerebro"),
|
|
"pct_catalogo": pct_via("catalogo"),
|
|
"pct_ruido": pct_via("ruido"),
|
|
"vol24": vol24,
|
|
"porhora": porhora,
|
|
}
|
|
rag = _rag()
|
|
if rag:
|
|
medidas["rag"] = rag
|
|
return medidas
|