aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/EXTENDER.md91
-rw-r--r--docs/RENDIMIENTO.md73
2 files changed, 145 insertions, 19 deletions
diff --git a/docs/EXTENDER.md b/docs/EXTENDER.md
index ea6d937..f0148e2 100644
--- a/docs/EXTENDER.md
+++ b/docs/EXTENDER.md
@@ -65,10 +65,14 @@ Tres cosas que conviene saber antes de escribir la primera:
se lo devuelve al modelo, que lo explica o reintenta.
- **La salida se lee en voz alta.** Devuelve una frase, no una tabla. El
registro recorta a 2000 caracteres, pero mucho antes de eso ya aburre.
-- **Con este modelo, más herramientas es peor.** Con tres declaradas elige
- bien entre ellas (15 de 16 medido), pero le cuesta abstenerse: busca en
- internet cosas que ya sabe. Apartados 3 y 5 de
- [RENDIMIENTO.md](RENDIMIENTO.md).
+- **Con este modelo, más herramientas es peor.** Con cuatro declaradas elige
+ bien entre ellas (18 de 20 medido, sin confundir cámara con pantalla), pero
+ le cuesta abstenerse: busca en internet cosas que ya sabe. Apartados 3 y 5
+ de [RENDIMIENTO.md](RENDIMIENTO.md).
+- **Si lanzas un proceso, usa `asist_core::proc::run`.** Controla el plazo, no
+ pasa por una shell y vacía las tuberías mientras espera. Esto último no es
+ un detalle: un hijo que escriba más de 64 KB se cuelga si nadie las lee, y
+ así es como la captura de pantalla parecía tardar 20 segundos.
## Ejecutar órdenes del sistema
@@ -169,34 +173,82 @@ Los argumentos van al `execve` tal cual, sin shell que los interprete: la
consulta sale de lo que se ha oído por el micrófono y no puede acabar
ejecutándose.
-## Mirar por la cámara
+## Ver: la cámara y la pantalla
-También montado. El servidor carga el proyector multimodal (`--mmproj`), así
-que el mismo modelo que conversa describe la imagen.
+Las dos hacen lo mismo en tres pasos —conseguir un JPEG, preguntarle al modelo
+por él, devolver texto— y comparten `VisionTool`. Lo único que las separa es
+`FrameSource`, que es de dónde salen los píxeles:
+
+```rust
+pub trait FrameSource: Send + Sync {
+ fn label(&self) -> &str;
+ fn capture(&self) -> Result<Vec<u8>>; // JPEG
+ fn available(&self) -> Result<()>; // se consulta antes de registrar
+}
+```
+
+Añadir una fuente nueva —una cámara IP, una ventana concreta, un PDF abierto—
+es implementar eso y llamar a `VisionTool::screen` o escribir una variante.
```toml
[camera]
enabled = true
device = "/dev/video0"
-width = 640 # la resolución manda en la latencia: 2,9 s aquí, 7,8 s a 720p
+width = 640 # escena: 2,9 s aquí, 7,8 s a 720p, misma descripción útil
height = 480
warmup_frames = 5 # deja que se asiente la exposición automática
save_dir = "" # vacío = no se guarda ningún fotograma
+
+[screen]
+enabled = true
+command = ["../scripts/capturar-pantalla.sh", "{ancho}", "{salida}"]
+width = 1280 # texto: NO lo bajes, ver abajo
+output = "" # monitor concreto; vacío = todo
+save_dir = "" # vacío = no se guarda ninguna captura
```
-Tres decisiones que conviene no deshacer sin pensarlo:
+### Por qué la pantalla usa 1280 y la cámara 640
+
+Porque una escena se entiende y un texto se lee, y son cosas distintas. Medido
+con tipografía de interfaz de 13 px:
+
+| Ancho | Tiempo | Aciertos sobre 3 preguntas |
+|---|---|---|
+| 640 px | 2,4 s | 1 — **se inventa el contenido** |
+| 960 px | 4,5 s | 2 |
+| 1280 px | 7,6 s | 3 |
-- **La pregunta viaja hasta la cámara.** El modelo rellena `pregunta` con lo
+A 640 px el modelo no dijo «no lo leo»: dijo que el error era «no se pudo abrir
+el archivo *involution*» y que la reunión era «a las 10:00». Ninguna de las dos
+cosas estaba en la imagen. Si bajas `screen.width`, esto es lo que compras.
+
+El prompt de la pantalla lleva además «lee sólo lo que de verdad pone y no
+completes lo que no se distinga», y funciona: en una prueba real contestó que
+«el texto de la barra de direcciones es ilegible por estar borroso».
+
+### Otro entorno gráfico
+
+`scripts/capturar-pantalla.sh` detecta grim (Wayland wlroots), spectacle (KDE),
+maim, ImageMagick y scrot (X11), reduce con ffmpeg y escribe el JPEG por la
+salida estándar. Está fuera del binario para que añadir un compositor sea
+editar el guion, no recompilar. Cualquier otro programa que respete ese
+contrato vale:
+
+```toml
+command = ["/ruta/a/mi-captura.sh", "{ancho}", "{salida}"]
+```
+
+### Tres decisiones que conviene no deshacer
+
+- **La pregunta viaja hasta la fuente.** El modelo rellena `pregunta` con lo
que quiere averiguar, y esa pregunta acompaña a la imagen. Pedir una
descripción genérica y luego interrogarla pierde el detalle que se buscaba.
-- **La imagen no entra en el historial.** Una foto ocupa cientos de tokens de
- contexto y arrastrarla turno tras turno saldría carísimo para lo poco que
- aporta una vez descrita. Lo que vuelve a la conversación es el texto.
-- **No se guarda nada en disco.** `save_dir` existe sólo para depurar qué está
- viendo el modelo; por defecto está vacío.
-
-Para ver la pantalla en vez de la cámara, `grim` ya está instalado: sería la
-misma herramienta cambiando cómo se obtienen los bytes del JPEG.
+- **La imagen no entra en el historial.** Cientos de tokens de contexto por
+ turno para algo que ya está descrito en una frase. Lo que vuelve a la
+ conversación es el texto.
+- **No se guarda nada en disco.** `save_dir` existe sólo para depurar. En la
+ pantalla pesa más que en la cámara: ahí caben contraseñas, mensajes privados
+ y correo abierto.
## Cambiar de motor
@@ -209,7 +261,8 @@ no toca el resto:
| Modelo | `asist_llm::LlmClient` | `crates/asist-llm/src/client.rs` |
| Texto a voz | `asist_tts::TtsClient` | `crates/asist-tts/src/lib.rs` |
| Visión | `asist_llm::LlmClient::look` | `crates/asist-llm/src/client.rs` |
-| Buscar y mirar | `asist_tools` | `crates/asist-tools/src/` |
+| Buscar y ver | `asist_tools` | `crates/asist-tools/src/` |
+| Lanzar procesos | `asist_core::proc::run` | `crates/asist-core/src/proc.rs` |
El cliente HTTP (`asist_core::http`) es propio y mínimo a propósito: todo es
HTTP plano contra localhost, y lo que ninguna librería genérica da cómodo es
diff --git a/docs/RENDIMIENTO.md b/docs/RENDIMIENTO.md
index b51f392..bed4f33 100644
--- a/docs/RENDIMIENTO.md
+++ b/docs/RENDIMIENTO.md
@@ -19,6 +19,8 @@ Linux 7.2 (CachyOS) · PipeWire.
| El prompt de estilo anulaba las llamadas a herramientas | 0 de 8 aciertos | 8 de 8 | — |
| La cámara capturaba a 1280x720 | 7,8 s por imagen | 2,9 s a 640x480 | **2,7×** |
| Una herramienta se ejecutaba en silencio | ~8,4 s hasta oír algo | 4,1 s con acuse hablado | **2×** |
+| La pantalla se leía a 640 px | 1 acierto de 3, con datos inventados | 3 de 3 a 1280 px | — |
+| Las tuberías no se vaciaban mientras se esperaba | captura de 180 KB colgada 20 s | 0,3 s | — |
Los tres son de configuración, no de código. Ninguno da error: el sistema
funciona, sólo que despacio o mal, que es lo que los hace difíciles de ver.
@@ -171,6 +173,50 @@ sale bien casi siempre —en el pipeline completo devolvió «Hoy en Buenos Aire
hace soleado esta mañana y por la tarde tendremos nubes con temperaturas
alrededor de 9°C», correcto— pero es una oportunidad más de equivocarse.
+## 4 ter. Mirar la pantalla: por debajo de 1280 px el modelo se inventa el texto
+
+Una pantalla no es una escena, es **texto**, y ahí este modelo tiene un modo de
+fallo que no perdona. Medido sobre una captura de 1920x1080 con tipografía de
+interfaz de 13 px, reducida a distintos anchos y preguntando por datos
+concretos (tres preguntas: qué dice el error, a qué hora es la reunión, cuánto
+es el total):
+
+| Ancho | Texto resultante | Tiempo | Aciertos |
+|---|---|---|---|
+| 640 px | 4 px | 2,4 s | **1 de 3** |
+| 960 px | 6 px | 4,5 s | 2 de 3 |
+| 1280 px | 8 px | 7,6 s | **3 de 3** |
+
+Lo grave no es que falle, es **cómo** falla. A 640 px no dijo «no lo leo»:
+
+> El error indica que el programa no pudo abrir el archivo **"involution"** y
+> que el código de error es 13.
+>
+> La reunión es **a las 10:00**.
+
+Ninguna de las dos cosas estaba en la imagen. El error era «no se pudo abrir
+/dev/video0» y la reunión, a las 15:30. Para un asistente de voz, decir una
+hora equivocada con aplomo es peor que tardar cuatro segundos más, así que el
+valor por defecto son **1280 px** aunque cueste el triple que la cámara.
+
+Con letra grande (26 px) la cosa cambia: a 640 px transcribió las seis cifras
+de prueba sin un fallo. Es el tamaño del texto *después de reducir* lo que
+manda, no la resolución en sí.
+
+Dos cosas más que salieron de aquí:
+
+- **La instrucción importa tanto como los píxeles.** Al prompt de la pantalla
+ se le añade «lee sólo lo que de verdad pone y no completes lo que no se
+ distinga». Funciona: en una prueba real el modelo contestó que «el texto de
+ la barra de direcciones es ilegible por estar borroso» en vez de
+ inventárselo.
+- **Una pantalla real cuesta más que una sintética.** La medición de 7,6 s es
+ con una captura de texto sobre fondo liso; con un navegador y un vídeo
+ abiertos subió a 10,8–12,4 s, porque hay mucho más que describir.
+
+Capturar es lo barato: `grim` tarda **0,03 s** en JPEG y el guion entero,
+reducción incluida, **0,30 s**.
+
## 5. Con tres herramientas el modelo elige bien, pero busca de más
Con `hora_actual`, `buscar_en_internet` y `mirar_por_la_camara` declaradas a la
@@ -200,6 +246,13 @@ apartado 3:
Añadir la prohibición no reduce los falsos positivos y sí empeora los
aciertos. Se queda la guía sola.
+Al añadir la cuarta herramienta —mirar la pantalla— el riesgo era que se
+confundiera con la cámara, que se le parece mucho. No pasó: **18 de 20**, con
+cero confusiones entre las dos. «¿Qué ves ahora mismo?» va a la cámara y «Lee
+lo que pone en la ventana que tengo delante» a la pantalla. El único fallo
+sigue siendo el mismo de siempre, buscar en internet «¿por qué el cielo es
+azul?».
+
## 6. Sin acuse hablado, una herramienta son seis segundos de silencio
Una búsqueda tarda unos 2,4 s y la cámara unos 4,5, y a eso hay que sumarle las
@@ -270,6 +323,7 @@ Turnos con las herramientas nuevas, también medidos de punta a punta:
|---|---|---|---|---|---|
| «¿Qué tiempo hace en Buenos Aires?» | 194 ms | 2243 ms | 2132 ms (búsqueda) | 4079 ms | **4122 ms** |
| «¿Qué ves por la cámara?» | 185 ms | 1649 ms | 5229 ms (captura + visión) | 1602 ms | **3153 ms** |
+| «¿Qué hay en mi pantalla?» | 207 ms | 1219 ms | 12434 ms (captura + visión) | 4113 ms | **3032 ms** |
El primer audio llega mucho antes que el final del turno porque suena el acuse
mientras la herramienta trabaja.
@@ -345,3 +399,22 @@ RUST_LOG=info,herramientas=info,camara=info cargo run --release -- run
scripts/servidores.sh arrancar
cargo test --release -p asist-app --test integracion -- --nocapture
```
+
+
+## 8. Un proceso que escribe mucho se cuelga si no le vacías la tubería
+
+La captura de pantalla funcionaba desde la shell en 0,3 s y desde el asistente
+vencía el plazo de 20 s. El guion era el mismo y el entorno también.
+
+La causa estaba en el lanzador de procesos compartido: esperaba con
+`try_wait()` en bucle **sin leer las tuberías**. Un hijo que escribe más de lo
+que cabe en el búfer (64 KB en Linux) se queda bloqueado escribiendo, nunca
+termina, y el bucle acaba matándolo por plazo vencido aunque estuviera
+haciendo su trabajo.
+
+Costó verlo porque los tres primeros usos cabían de sobra: una fecha, un JSON
+de búsqueda y un fotograma de cámara de 9 KB. La primera captura de pantalla,
+de 174 KB, no.
+
+Ahora las dos tuberías se vacían en hilos aparte mientras se espera. Hay dos
+pruebas que lo fijan, una por cada tubería, generando 512 KB y 200 KB.