diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 299 |
1 files changed, 163 insertions, 136 deletions
@@ -1,197 +1,224 @@ -# asist-p — asistente de voz local +# asist-p — local voice assistant -Escucha por el micrófono, piensa y contesta hablando. Todo en la máquina: no -sale un byte a internet. +[Español](README.es.md) -Une tres motores que ya existían —**Canary** para oír, **llama.cpp** para -pensar y **qwentts.cpp** para hablar— en un pipeline de hilos y canales en -Rust donde ninguna etapa espera a la siguiente. +It listens through the microphone, thinks and answers out loud. Everything +runs on the machine; only the optional web search goes online. + +It joins three existing engines (**Canary** to hear, **llama.cpp** to think +and **qwentts.cpp** to speak) in a Rust pipeline of threads and channels +where no stage waits for the next. ``` - micrófono ──muestras──> segmentador ──intervención──> ASR ──texto──┐ - (cpal, tiempo real) (VAD, turnos) (Canary) │ - v - altavoz <──muestras── síntesis <──frases── conversación <──────────┘ - (cpal, anillo) (qwentts) (llama.cpp + herramientas) + microphone ──samples──> segmenter ──utterance──> ASR ──text──┐ + (cpal, real time) (VAD, turns) (Canary) │ + v + speaker <──samples── synthesis <──sentences── conversation <─┘ + (cpal, ring) (qwentts) (llama.cpp + tools) ``` -Cada caja es un hilo; cada flecha, un canal. Dos decisiones sostienen el -resto: +Each box is a thread; each arrow, a channel. Two decisions carry the rest: + +- **Nothing blocks the stage ahead.** The microphone never waits for the + recognizer, and the model never waits for the synthesizer. Whoever has + spare capacity drops work instead of accumulating delay. +- **Answers go out per sentence, not per answer.** Each sentence goes to the + synthesizer as soon as it closes, so the assistant starts talking while the + model is still writing. That is what separates half a second from five. -- **Nada bloquea al que va delante.** El micrófono nunca espera al - reconocedor, y el modelo nunca espera al sintetizador. Quien va sobrado - descarta trabajo en lugar de acumular retraso. -- **Se responde por frases, no por respuestas.** Cada frase sale hacia el - sintetizador en cuanto se cierra, así que el asistente empieza a hablar - mientras el modelo sigue escribiendo. Es lo que separa medio segundo de - cinco. +The assistant speaks Spanish: its prompts, tool descriptions and spoken +acknowledgements are in Spanish on purpose. The code is in English. -## Puesta en marcha +## Getting started ```bash -git clone --recursive <este-repo> && cd asist-p -scripts/bootstrap.sh # submódulos, parches, binarios y modelos -cargo run --release -- check # comprueba que está todo en su sitio -cargo run --release -- run # a hablar +git clone --recursive https://git.all.ar/pub/asist-p.git && cd asist-p +scripts/bootstrap.sh # submodules, patches, binaries and models +cargo run --release -- check # check that everything is in place +cargo run --release -- run # start talking ``` -`bootstrap.sh` es idempotente y no copia modelos: los enlaza desde donde ya -estén (`~/GIT-MIRRO`, `~/HF`). Si no encuentra los motores construidos, los -construye, y eso sí tarda. +`bootstrap.sh` is idempotent and does not copy models: it links them from +wherever they already are (`$ASIST_MIRROR`, default `~/GIT-MIRRO`, and +`$ASIST_HF`, default `~/HF`). If it cannot find the engines built, it builds +them, and that does take a while. ```bash -cargo run --release -- devices # listar dispositivos de audio -cargo run --release -- run --barge-in # permitir cortar al asistente -cargo run --release -- run --shell # habilitar órdenes del sistema -cargo run --release -- run --no-manage # no lanzar los servidores +cargo run --release -- devices # list audio devices +cargo run --release -- run --barge-in # allow interrupting the assistant +cargo run --release -- run --shell # enable system commands +cargo run --release -- run --no-manage # do not start the servers ``` -Durante el desarrollo conviene dejar los servidores levantados aparte —cargar -los modelos cuesta más de un minuto— y reiniciar sólo el binario de Rust: +While developing, keep the servers running separately (loading the models +takes more than a minute) and restart only the Rust binary: ```bash -scripts/servidores.sh arrancar +scripts/servers.sh start cargo run --release -- run --no-manage ``` -## Cómo está organizado +## Layout -| Crate | De qué se ocupa | +| Crate | What it does | |---|---| -| `asist-core` | Configuración, eventos, errores, cliente HTTP, telemetría y el registro de herramientas | -| `asist-audio` | Captura y reproducción con cpal, detección de voz, troceado en turnos | -| `asist-asr` | Canary en ONNX: ventana deslizante y transcripción final | -| `asist-llm` | llama-server: streaming SSE, historial, llamadas a herramientas | -| `asist-tts` | qwentts: síntesis en streaming y voces clonadas | -| `asist-tools` | Buscar en internet, mirar por la cámara y ver la pantalla | -| `asist-app` | Supervisor de procesos, orquestador y binario `asistente` | +| `asist-core` | Configuration, events, errors, HTTP client, telemetry and the tool registry | +| `asist-audio` | Capture and playback with cpal, voice detection, splitting into turns | +| `asist-asr` | Canary on ONNX: sliding window and final transcription | +| `asist-llm` | llama-server: SSE streaming, history, tool calls | +| `asist-tts` | qwentts: streaming synthesis and cloned voices | +| `asist-tools` | Web search, looking through the camera and at the screen | +| `asist-app` | Process supervisor, orchestrator and the `asistente` binary | -Los tres motores viven en `vendor/` como submódulos fijados a un commit -concreto. Los cambios locales sobre ellos están en `vendor/patches/`, y -`bootstrap.sh` los aplica: sin ellos el reconocedor no se comporta como el -que se midió. +The three engines live in `vendor/` as submodules pinned to a specific +commit. The local changes on them are in `vendor/patches/`, and +`bootstrap.sh` applies them: without them the recognizer does not behave like +the one that was measured. -## Configuración +## Configuration -Todo está en [`config/asistente.toml`](config/asistente.toml), comentado. Los -valores que llevan una cifra en el comentario salen de una medición concreta, -no de una suposición; el detalle está en -[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). +Everything is in [`config/asistente.toml`](config/asistente.toml), with +comments. Values that carry a number in their comment come from a specific +measurement, not an assumption; the details are in +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md) (in Spanish). -Lo que más se toca: +The most tweaked settings: ```toml [vad] -silence_hold = 0.8 # cuánto silencio cierra tu intervención -barge_in = false # cortar al asistente hablando encima +silence_hold = 0.8 # how much silence ends your utterance +barge_in = false # interrupt the assistant by talking over it [general] -history_turns = 8 # memoria de la conversación -system_prompt = "..." # carácter y estilo +history_turns = 8 # conversation memory +system_prompt = "..." # personality and style [tools] -shell = false # ejecutar órdenes del sistema +shell = false # run system commands ``` -## Interrumpir y el eco del altavoz +## Interrupting and speaker echo -Por defecto el asistente es **media dúplex**: mientras habla, el micrófono -está cerrado. No es pereza: con altavoces abiertos el micrófono se oye a sí -mismo, el asistente se transcribe y se responde solo. +By default the assistant is **half duplex**: while it talks, the microphone +is closed. That is not laziness: with open speakers the microphone hears +itself, and the assistant transcribes itself and answers itself. -Con auriculares, `--barge-in` deja hablar encima para cortarlo. El umbral se -eleva (`vad.barge_in_factor`) para que el eco no baste y una voz de verdad sí. +With headphones, `--barge-in` lets you talk over it to interrupt. The +threshold is raised (`vad.barge_in_factor`) so the echo is not enough but a +real voice is. -## Latencia +## Latency -Un turno real, medido de punta a punta: +A real turn, measured end to end: -| Etapa | Tiempo | +| Stage | Time | |---|---| -| Reconocimiento (2,5 s de voz) | 200 ms | -| Modelo, primer token | 1660 ms | -| Modelo, resto | 1362 ms | -| Herramientas | 1 ms | -| **Del final de tu frase al primer audio** | **3213 ms** | +| Recognition (2.5 s of speech) | 200 ms | +| Model, first token | 1660 ms | +| Model, rest | 1362 ms | +| Tools | 1 ms | +| **From the end of your sentence to the first audio** | **3213 ms** | -El binario mide esto en cada turno y señala la etapa más lenta. Tres ajustes -de configuración valieron más que cualquier cambio de código: +The binary measures this on every turn and flags the slowest stage. A few +configuration settings were worth more than any code change: -- El TTS decodificaba en bloques de 24 s: **4948 ms → 585 ms** al primer audio. -- La plantilla del modelo dejaba `<think>` abierto: **8630 ms → 413 ms** al - primer token. -- El prompt de estilo anulaba las llamadas a herramientas: **0/8 → 8/8**. -- La cámara capturaba a 720p: **7,8 s → 2,9 s** por imagen a 640x480. -- Las herramientas se ejecutaban en silencio: **~8,4 s → 4,1 s** hasta oír algo. -- La pantalla a 640 px inventaba el texto: **1 de 3 aciertos → 3 de 3** a 1280 px. +- The TTS decoded in 24 s blocks: **4948 ms → 585 ms** to first audio. +- The model template left `<think>` open: **8630 ms → 413 ms** to first token. +- The style prompt suppressed tool calls: **0/8 → 8/8**. +- The camera captured at 720p: **7.8 s → 2.9 s** per image at 640x480. +- Tools ran in silence: **~8.4 s → 4.1 s** until something is heard. +- The screen at 640 px made the text up: **1 of 3 right → 3 of 3** at 1280 px. -Los tres, con el porqué y cómo se midieron, en -[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md). +All of them, with the reasons and how they were measured, are in +[docs/RENDIMIENTO.md](docs/RENDIMIENTO.md) (in Spanish). -## Herramientas +## Tools -El asistente puede llamar a funciones. Vienen cuatro: +The assistant can call functions. It ships with these: -| Herramienta | Qué hace | Coste medido | +| Tool | What it does | Measured cost | |---|---|---| -| `hora_actual` | Fecha y hora del sistema | 0 ms | -| `buscar_en_internet` | Busca y resume | 0,5–3,6 s | -| `mirar_por_la_camara` | Hace una foto y responde sobre lo que ve | 4,1–5,2 s | -| `mirar_la_pantalla` | Captura la pantalla y responde sobre lo que hay | 7,6–12,4 s | -| `ejecutar_comando` | Órdenes del sistema, **apagada** por defecto | — | - -Mientras una herramienta trabaja, el asistente dice «Déjame que lo busque» o -«Voy a mirar». No es adorno: sin eso el turno pasa seis segundos en silencio y -se lee como un cuelgue. Con el acuse, el primer audio llega en 3–4 s. - -**Buscar** admite tres buscadores: `tavily` (con clave, devuelve una respuesta -ya redactada), `ddgs` (sin clave, la misma librería que hay bajo -`duckduckgo-mcp`) y `searxng`. El de comando ejecuta un guion que escriba JSON, -así que meter otro es escribir un guion, no tocar Rust. - -**Ver** —cámara y pantalla— aprovecha que el servidor ya carga el proyector -multimodal: el mismo modelo que conversa describe la imagen. Ni la foto ni la -captura se guardan en disco, y no entran en el historial. +| `hora_actual` | System date and time | 0 ms | +| `buscar_en_internet` | Searches and summarizes | 0.5–3.6 s | +| `mirar_por_la_camara` | Takes a photo and answers about what it sees | 4.1–5.2 s | +| `mirar_la_pantalla` | Captures the screen and answers about what is on it | 7.6–12.4 s | +| `ejecutar_comando` | System commands, **off** by default | — | + +The tool names are Spanish because the model reads them in a Spanish +conversation. + +While a tool works, the assistant says «Déjame que lo busque» or «Voy a +mirar». It is not decoration: without it the turn spends six seconds in +silence and reads as a hang. With the acknowledgement, the first audio +arrives in 3–4 s. + +**Search** supports three backends: `tavily` (with a key, returns an already +written answer), `ddgs` (no key, the same library behind `duckduckgo-mcp`) +and `searxng`. The command backend runs a script that writes JSON, so adding +another engine means writing a script, not touching Rust. + +**Vision** (camera and screen) takes advantage of the server already loading +the multimodal projector: the same model that converses describes the image. +Neither the photo nor the capture is saved to disk, and they do not enter the +history. + +The screen uses 1280 px and the camera 640, and the difference matters: a +scene can be understood, but text has to be read. Measured, at 640 px the +model does not say it cannot read the screen, **it makes up what it says** +(it answered that the meeting was «at 10:00» when it said 15:30). Hence the +threefold cost. + +`ejecutar_comando` is off on purpose: giving a shell to a model that obeys +what it hears through the microphone is a change of security posture. When it +is on, only allowlisted commands get through, with no shell to interpret +metacharacters and with a deadline. + +Adding your own takes about twenty lines: [docs/EXTENDER.md](docs/EXTENDER.md) +(in Spanish). + +## Voice + +The repository ships no cloned voice: a voice belongs to someone. To make the +assistant speak with one, record a few seconds of audio with its transcript +and extract the latents: -La pantalla usa 1280 px y la cámara 640, y la diferencia importa: una escena se -entiende, pero un texto hay que leerlo. Medido, a 640 px el modelo no dice que -no lee la pantalla, **se inventa lo que pone** —contestó que la reunión era «a -las 10:00» cuando ponía 15:30—. Por eso el triple de coste. - -`ejecutar_comando` está apagada a conciencia: darle una shell a un modelo que -obedece a lo que oye por el micrófono es un cambio de postura de seguridad. -Cuando se enciende, sólo pasan las órdenes de una lista blanca, sin shell que -interprete metacaracteres y con un plazo máximo. +```bash +scripts/clone-voice.sh recording.wav transcript.txt +``` -Añadir una propia son unas veinte líneas: -[docs/EXTENDER.md](docs/EXTENDER.md). +They end up in `assets/voices/` (outside git). Without a cloned voice, remove +the `[tts.reference]` section from `config/asistente.toml` and the assistant +uses one of the model's own voices. -## Pruebas +## Tests -68 pruebas unitarias que no necesitan ni modelos ni micrófono, y seis de -integración contra los servidores de verdad. +More than a hundred unit tests that need neither models nor a microphone, +plus integration tests against the real servers. ```bash -cargo test # unitarias, sin modelos -scripts/servidores.sh arrancar -cargo test --release -p asist-app --test integracion -- --nocapture -cargo test --release -p asist-app --test integracion -- --ignored # bucle completo +cargo test # unit tests, no models +scripts/servers.sh start +cargo test --release -p asist-app --test integration -- --nocapture +cargo test --release -p asist-app --test integration -- --ignored # full loop ``` -Las de integración se saltan solas si no hay servidores escuchando, y se -turnan la GPU con un mutex: los dos servidores comparten una tarjeta de 4 GB y -en paralelo miden contención en vez de latencia. +The integration tests skip themselves when no server is listening, and they +take turns on the GPU through a mutex: both servers share a 4 GB card, and in +parallel they measure contention instead of latency. + +The `--ignored` test closes the loop without a microphone: it synthesizes a +sentence and checks that the recognizer gets it back. -La prueba marcada `--ignored` cierra el bucle sin micrófono: sintetiza una -frase y comprueba que el reconocedor la recupera. +## Requirements -## Requisitos +- Rust 1.85 or later +- CUDA for the C++ engines (they run on CPU, but barely) +- PipeWire or ALSA +- ~6 GB of disk for the models +- `ffmpeg` for the camera and to scale down captures +- For the screen: `grim` (Wayland) or `maim`/ImageMagick (X11) +- For search: a Tavily key in `$TAVILY_API_KEY`, or `uv tool install ddgs` -- Rust 1.85 o posterior -- CUDA para los motores en C++ (funcionan en CPU, pero muy justos) -- PipeWire o ALSA -- ~6 GB de disco para los modelos -- `ffmpeg` para la cámara y para reducir las capturas -- Para la pantalla: `grim` (Wayland) o `maim`/ImageMagick (X11) -- Para buscar: una clave de Tavily en `$TAVILY_API_KEY`, o `uv tool install ddgs` +## License +MIT |