aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorelvis <elvis@claros.ar>2026-09-26 20:20:19 -0300
committerelvis <elvis@claros.ar>2026-09-26 20:20:19 -0300
commit8518a63f55153e7f45fd49ad6caff5555f4e374f (patch)
tree636684eea3fa6f35d78282ab95eb687ef49154af /README.md
parent69de76dc9cbedc6092d1e5ce84094a8030de1470 (diff)
downloadasist-p-main.tar.gz
asist-p-main.zip
Translate code, comments, logs and terminal UI to English; add English README; rename scriptsHEADmain
Diffstat (limited to 'README.md')
-rw-r--r--README.md299
1 files changed, 163 insertions, 136 deletions
diff --git a/README.md b/README.md
index bb7c407..6832f32 100644
--- a/README.md
+++ b/README.md
@@ -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