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.
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:
-
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. -
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.
-
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.
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.
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.
- 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.
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 % |
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.
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
Tres formas, en orden de fiabilidad:
- Ctrl+Shift+S desde cualquier programa.
- Clic en el avatar.
- 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.
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.
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
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.
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
systemel modelo de 1B los ignoraba) - Que la cadena Groq → Ollama caiga bien y no llame al local si la nube responde
- Que con
PRIVACIDAD_ESTRICTAno se intente siquiera la nube - Que el cuerpo de los correos no salga del ordenador sin permiso
- Que la lista blanca del
.envfrene 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
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 clavesPara el OCR:
winget install UB-Mannheim.TesseractOCREl paquete de español no viene con el instalador. Se descarga
spa.traineddata
en data/tessdata/.
| 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.verificariniciar.batO por separado:
python -m server.app # servidor
cd frontend && npm start # interfazAparece 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 💬.
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.
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.
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.