Skip to content

About

Asistente de escritorio en español: le hablas y te responde con voz y avatar VTuber. Busca en internet, pone música, lee tu pantalla y te resume el correo. Corre en un portátil normal y sin coste mensual.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Sakura

tests licencia python

Página del proyecto y descarga

Asistente personal de escritorio que se controla por voz, busca en internet, lee la pantalla y gestiona el correo. Corre en un portátil normal y no cuesta nada al mes.

Escrito en Python (FastAPI) con interfaz en Electron.


El problema de partida

La primera versión usaba un modelo de 7B en local con Ollama. Medido en el equipo de desarrollo (Intel i5-7200U, 2 núcleos, sin GPU dedicada):

Antes Ahora
Generación 3,25 tok/s 18,4 tok/s en local · ~300 tok/s en la nube
Respuesta típica ~40 s 3-5 s con Groq · 27-117 s solo en local
Arranque por mensaje Proceso Python nuevo cada vez Servidor persistente
Primera letra en pantalla Al terminar la respuesta entera Inmediata (streaming)

Tres cambios explican la diferencia:

  1. Servidor persistente. Antes el frontend hacía spawn('python') en cada mensaje, así que se recargaban Python, ChromaDB y la conexión a Ollama cada vez. Ahora arranca una vez y se habla por WebSocket.

  2. Streaming. La respuesta se pinta y se pronuncia según se genera, por frases. Reduce la latencia percibida mucho más que cualquier optimización del modelo.

  3. Cerebro con cadena de reserva. Groq (gratis, ~300 tok/s) como principal y Ollama en local como reserva automática si no hay internet o se agota la cuota.

Un detalle que no era obvio

En una CPU sin GPU, el procesado del prompt resultó ser el cuello de botella real, no la generación: 56 tok/s medidos, o sea ~18 ms por cada token de prompt. Un prompt de 700 tokens son 13 segundos antes de empezar a responder.

Por eso el prompt se adapta al proveedor: completo cuando responde la nube, recortado al mínimo cuando responde el modelo local (core/llm/router.py).

Relacionado: al inyectar datos de una habilidad (resultados de búsqueda, texto de la pantalla) como mensaje de system, el modelo local de 1B los ignoraba y contestaba "no puedo buscar en internet" teniendo los resultados delante. Metiéndolos dentro del turno del usuario sí los usa.

Otro que costó encontrar

Las ventanas de Electron se cargan como file://, y desde ese origen un fetch a http://127.0.0.1 falla en silencio en Chromium. No es solo CORS: el origen es opaco y entran en juego las reglas de acceso a red privada. El síntoma era que el avatar ignoraba la imagen elegida y la pantalla de configuración aparecía siempre vacía, sin ningún error.

La solución no es pelearse con las cabeceras, sino no usar el navegador para eso: las peticiones las hace Node desde el proceso principal (pedirAlServidor en frontend/main.js) y las ventanas se lo piden por IPC.


Qué hace

  • Voz completa y local. Palabra clave (openWakeWord) → transcripción (Vosk) → respuesta hablada (Piper). El audio no sale del ordenador.
  • Internet. Búsqueda en DuckDuckGo, sin clave ni cuota.
  • YouTube. "Ponme tal cosa" busca y abre el vídeo.
  • Abre programas. "Abre Firefox", "abre VS Code con el proyecto tal". Los busca en el menú de inicio, así que valen los que tengas instalados. Si no encuentra el programa lo dice, en vez de fingir que lo ha abierto.
  • Control del sistema. Volumen, silencio, bloquear el equipo y abrir los ajustes de Windows.
  • Pantalla. Captura y lectura por OCR local (~3,6 s).
  • Correo. Resume lo que hay sin leer, repasa el spam por si se ha colado algo bueno y etiqueta en Gmail lo que parece de clientes.
  • Modo reposo. Libera el modelo de la RAM y suelta Vosk y Piper. Solo queda el detector de palabra clave (~50 MB). Entra solo tras unos minutos sin uso.
  • Memoria. Conversaciones pasadas en ChromaDB, recuperadas por similitud.
  • Avatar flotante en una esquina, siempre encima y con fondo transparente, en tres sabores:
    • Dibujo de serie en SVG, sin dependencias, con respiración, parpadeo y balanceo.
    • Tu propia imagen: PNG con transparencia, GIF animado o WebP. Opcionalmente una segunda imagen para cuando habla.
    • VRM (tipo VTuber): carga cualquier avatar .vrm, en 3D, con pelo y ropa que se mueven, parpadeo, pose natural y la boca sincronizada con el volumen real de la voz, no con un movimiento inventado. Es la interfaz principal: la ventana de conversación está oculta salvo que se pida, porque un asistente que te tapa la pantalla con un chat no sirve de mucho.

Avatar VRM (tipo VTuber)

Un avatar VRM es un solo archivo .vrm. Se elige en la configuración y ya. No hay nada que instalar aparte: three.js y three-vrm son MIT y vienen incluidos.

Te puedes hacer el tuyo gratis con VRoid Studio, o descargar uno de VRoid Hub o Booth.

No se incluye ningún modelo. Los personajes de Hololive y demás VTubers son propiedad de sus empresas; en TERCEROS.md está el detalle.

Al hablar no solo mueve la boca: el cuerpo se balancea más y la cabeza asiente al ritmo de lo que dice. Al dormirse cierra los ojos, ladea la cabeza y se atenúa.

Medido en un i5-7200U sin GPU, a 30 fps:

Despierto Dormido
Avatar VRM 3,4 % 0,1 %
Dibujo SVG 4,4 % 0 %

La ventana transparente es cara

Esto costó encontrarlo. La ventana del avatar es transparente y siempre encima, y en Windows recomponer una ventana así es caro: da igual qué se anime, mientras haya una sola animación viva el sistema la redibuja continuamente.

El dibujo SVG tenía animaciones infinitas (respiración, balanceo, coletas) que no paraban nunca. Dormido gastaba 9,2 % de CPU, más que despierto. Ahora al entrar en reposo se paran todas, incluidos los zzz, que se quedan quietos a propósito.

La lección: en modo reposo no puede quedar viva ni una animación. Con eso baja a cero.

Dos detalles que hubo que resolver: los VRM vienen en pose de T (brazos en cruz, como un maniquí) porque es el estándar para montar el modelo, así que hay que bajarle los brazos a mano; y la cámara se encuadra sobre el hueso de la cabeza, porque cada modelo tiene una altura distinta.

La voz

Piper, en local. La voz se elige en la configuración y se puede escuchar antes de decidir. Medido en un i5-7200U sin GPU:

Voz Calidad Velocidad
es_MX-claude-high alta 4,0× tiempo real
es_ES-sharvard-medium media 4,4×
es_AR-daniela-high alta 1,0× — se queda al borde de cortarse

Por eso la de serie es es_MX-claude-high: calidad alta al mismo precio que la media. daniela suena bien pero a 1× tiempo real no deja margen, y en un momento de carga la frase se entrecorta.

También hay ajuste de tono, para que la voz pegue con el avatar. Como Piper no permite cambiar el tono, se hace generando el habla más lenta en la misma proporción y remuestreando después: el resultado dura lo mismo pero suena más agudo. Verificado que sigue siendo inteligible pasándolo por Vosk.

python -m scripts.voces --lista     # ver las disponibles
python -m scripts.voces --probar    # generar muestras y compararlas

Cómo se activa

Tres formas, en orden de fiabilidad:

  1. Ctrl+Shift+S desde cualquier programa.
  2. Clic en el avatar.
  3. Palabra clave, diciendo "hey máicroft".

Sobre la tercera: los modelos de openWakeWord están entrenados en inglés, y eso se nota mucho según quién hable. Medido sintetizando cada frase con pronunciación española y pasándola por el detector:

Palabra Dicho como Puntuación
hey_mycroft "hey máicroft" 1.000
hey_rhasspy "hey raspi" 0.905
alexa "alexa" 0.021
hey_jarvis "hey jarvis" 0.000

De ahí que la palabra por defecto sea hey_mycroft y no la habitual hey_jarvis, que con acento español no se activa nunca. Se cambia en el .env con PALABRA_CLAVE.


Privacidad

Las capturas de pantalla y los correos pueden contener datos sensibles, y el tier gratuito de Google permite a Google usar lo que se le envía para entrenar sus modelos. Por eso:

Dato Por defecto
Audio (voz) Nunca sale. Vosk y Piper son locales
Pantalla Nunca sale. OCR local. La visión en la nube es opt-in
Correo Se procesa en local; el cuerpo no sale salvo opt-in
Texto del chat Va a Groq, salvo en modo estricto

Tres interruptores en el .env: PRIVACIDAD_ESTRICTA, PERMITIR_VISION_NUBE y PERMITIR_CORREO_NUBE. Con el primero a 1 todo se procesa en local y nada sale del equipo.


Arquitectura

Electron  ──WebSocket──►  FastAPI (servidor persistente)
                              │
                              ├── Habilidades   internet · youtube · pantalla · correo
                              │                 (se detectan con reglas: instantáneo,
                              │                  gratis y funciona sin internet)
                              │
                              ├── Cerebro       Groq ──► Ollama (reserva)
                              │
                              ├── Voz           openWakeWord → Vosk → Piper
                              │
                              ├── Memoria       ChromaDB
                              │
                              └── Modo          ACTIVO / REPOSO
core/     config, cerebro (router + proveedores), memoria, estado
skills/   habilidades
voice/    palabra clave, transcripción, síntesis
server/   API y WebSocket
frontend/ interfaz Electron
scripts/  verificación

Arrancar al encender el ordenador

python -m scripts.arranque --activar
python -m scripts.arranque --estado
python -m scripts.arranque --desactivar

O con la casilla de la pantalla de configuración.

Pone un acceso directo en la carpeta de Inicio de Windows: no toca el registro, no necesita permisos de administrador y se puede quitar a mano desde el Explorador. Arranca sin ventana de consola y espera 30 segundos tras iniciar sesión, para no pelearse con el arranque de Windows.

Con ARRANCAR_EN_REPOSO=1 arranca ya dormida: solo queda el detector de palabra clave (~50 MB) hasta que la llamas. Recomendado si se abre sola al encender.

Tests

pip install -r requirements-dev.txt
python -m pytest

146 tests, ~9 segundos. No necesitan micrófono, altavoces, internet, Ollama ni ninguna clave de API: todo lo que sale fuera está sustituido por dobles. Verificado bloqueando pyaudio, vosk, openwakeword, yt-dlp, ddgs, mss, pytesseract y ollama a la vez — la suite sigue pasando. Por eso corren en CI sin más.

Tampoco tocan tu .env ni tus conversaciones: cada test trabaja sobre una carpeta temporal.

Cobertura por zonas:

core/config.py 99 %
core/asistente.py 94 %
core/state/modo.py 93 %
core/llm/router.py 92 %
server/app.py 72 %
skills/correo.py 59 %
Proveedores (Groq/Gemini/Ollama) ~30 %
Total 61 %

Lo que falta por cubrir es, a propósito, lo que no se puede probar sin hardware o sin red: las llamadas HTTP reales a los proveedores, la captura de pantalla y las conexiones IMAP. La lógica de todo eso sí está cubierta con dobles; lo que no está es el viaje real.

Lo que los tests protegen en concreto:

  • Que el prompt se recorte cuando responde el modelo local
  • Que los datos de una habilidad vayan dentro del turno del usuario (como system el modelo de 1B los ignoraba)
  • Que la cadena Groq → Ollama caiga bien y no llame al local si la nube responde
  • Que con PRIVACIDAD_ESTRICTA no se intente siquiera la nube
  • Que el cuerpo de los correos no salga del ordenador sin permiso
  • Que la lista blanca del .env frene campos inventados
  • Que las claves nunca se devuelvan enteras
  • Que las 22 frases de ejemplo activen la habilidad correcta, incluso con los errores de transcripción reales de Vosk

Instalación

La forma fácil: descarga desde la web, ejecuta instalar.bat y luego iniciar.bat. El instalador se baja solo el motor de voz, el reconocimiento y los modelos.

A mano, si prefieres: Requisitos: Python 3.11+, Node 18+, y Ollama si se quiere reserva sin internet.

pip install -r requirements.txt
cd frontend && npm install && cd ..

ollama pull llama3.2:1b

cp .env.example .env      # y rellenar las claves

Para el OCR:

winget install UB-Mannheim.TesseractOCR

El paquete de español no viene con el instalador. Se descarga spa.traineddata en data/tessdata/.

Claves (todas con tier gratuito y sin tarjeta)

Servicio Para qué Dónde
Groq Cerebro principal console.groq.com/keys
Gemini Visión (opcional) aistudio.google.com/apikey
Gmail Correo (opcional) Contraseña de aplicación

Comprobar que todo está bien conectado:

python -m scripts.verificar

Uso

iniciar.bat

O por separado:

python -m server.app          # servidor
cd frontend && npm start      # interfaz

Aparece el avatar en una esquina. Se le habla con Ctrl+Shift+S, haciendo clic en él, o diciendo "hey máicroft". La conversación escrita se abre con Ctrl+Shift+C o con el botón 💬.

Verificación de la voz sin micrófono

La cadena de voz se puede probar en bucle cerrado, sin hablar: se sintetiza una frase con Piper y se le pasa a Vosk. Si lo que sale coincide con lo que entró, ambos extremos funcionan.

dicho: hola sakura pon musica de rosalia
oido : la sakura pon música de rosalía        -> habilidad youtube ('rosalía')

dicho: busca en internet el tiempo en madrid
oido : busca en internet del tiempo en madrid -> habilidad internet

Las habilidades se detectan bien incluso con los errores de transcripción, que es lo que importa en la práctica.


La web del proyecto

Vive en docs/ y se publica en https://erlanders177.github.io/sakura-asistente/.

GitHub Pages la sirve desde la rama gh-pages, asi que para actualizarla no basta con tocar docs/:

python -m scripts.publicar_web

Ese comando regenera el zip de descarga, copia docs/ a gh-pages y lo sube. Antes de subir comprueba que en el zip no se haya colado el .env, los modelos ni las conversaciones.

Licencia

PolyForm Noncommercial 1.0.0 — libre para uso personal, educativo, investigación y organizaciones sin ánimo de lucro. El uso comercial no está permitido por esta licencia.

¿Uso comercial? Se puede negociar. Escribe a aaron.aranda.t@gmail.com.

Ver LICENSE.md. Las dependencias son todas permisivas (MIT, BSD y Apache 2.0), sin ninguna copyleft que entre en conflicto; el detalle está en TERCEROS.md.

About

Asistente de escritorio en español: le hablas y te responde con voz y avatar VTuber. Busca en internet, pone música, lee tu pantalla y te resume el correo. Corre en un portátil normal y sin coste mensual.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages