diff options
Diffstat (limited to 'docs/EXTENDER.md')
| -rw-r--r-- | docs/EXTENDER.md | 91 |
1 files changed, 72 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 |