Arquitectura
En esta página
El Hub es un único programa de Rust que da a un modelo dos herramientas, execute_script y get_state, y llega a cada aplicación por un módulo en el Hub y un puente en la aplicación. Esta página muestra cómo se conectan las piezas, las tres formas de llegar a una aplicación y un script medido frente a llamadas separadas.
Las piezas
- Cliente MCP: de su lado. Inicia
libremodular-mcp servey llama a las dos herramientas. - Hub: un proceso,
libremodular-mcp. Responde al cliente, comprueba el destino, lee el script antes de enviarlo y escribe el diario de auditoría. - Módulo: dentro del Hub, uno por aplicación. Sabe cómo llegar a la aplicación.
- Puente: en la aplicación, a su lado, o un proceso de Python por script. Ejecuta el script y devuelve lo que imprimió, su
result, o el error propio de la aplicación. - Aplicación: GIMP, Krita, Blender, Inkscape o LibreOffice. Hace el trabajo.
El Hub no conoce la interfaz de ninguna aplicación. Un script se escribe en el Python propio de la aplicación, y el módulo y el puente son las únicas piezas que dependen de ella.
El Hub es un único ejecutable de unos 4,3 MB. Además del cargador, ldd enumera dos bibliotecas: libc.so.6 y libgcc_s.so.1. El paquete instala un segundo ejecutable, libremodular-launch, que inicia LibreOffice y su puente.
Dos herramientas
La respuesta del Hub a tools/list nombra exactamente ["execute_script", "get_state"].
execute_script(target_app, script_content) ejecuta un script de Python completo en la aplicación que indica target_app y devuelve un objeto JSON: status, duration_ms, stdout, stderr, result, error con la traza de error propia de la aplicación, truncated y reason cuando el Hub rechazó el script. get_state(target_app) devuelve el estado de la aplicación: sus documentos, capas u objetos, una ruta a una captura PNG cuando la hay y content_origin, que indica que el texto procede de un documento y no del Hub. El destino hub enumera las aplicaciones configuradas sin ponerse en contacto con ninguna.
Tres formas de llegar a una aplicación
El Hub se inició con todas las aplicaciones activadas y ningún puente iniciado. get_state sobre hub enumeró siete destinos en estos modos:
tcp:gimp,krita,blender. Un puente cargado en la aplicación escucha en un puerto local, y el Hub se conecta a él.uno:libreoffice-writer,libreoffice-calc,libreoffice-impress. Un único puente independiente se conecta al puerto UNO en el que escucha LibreOffice, y los tres destinos lo comparten.cli:inkscape. El Hub inicia un proceso de Python por script; no hay ningún puente que iniciar.
Solo inkscape mostró connected: true: en el modo cli no hace falta tener nada en marcha, y el Hub había encontrado el programa. En los modos tcp y uno, el Hub nunca inicia, cierra ni detiene una aplicación a petición del modelo; el operador inicia el puente, como dice la guía de cada aplicación.
Una aplicación ejecuta un script a la vez. Un script que supera el plazo del Hub recibe la respuesta timeout, y la solicitud siguiente recibe busy hasta que la aplicación queda libre; la página de seguridad lo mide.
Qué necesita un puente
Los cuatro archivos de puente que instala el paquete se generan a partir de un núcleo común y un adaptador por aplicación. Importan la biblioteca estándar de Python y, aparte de ella, solo la interfaz propia de la aplicación. Cada línea da el archivo, su longitud y lo que importa fuera de la biblioteca estándar:
blender_bridge.py, 1091 líneas:bpy.gimp_bridge.py, 1130 líneas:gi.krita_bridge.py, 1094 líneas:krita,PyQt5,PyQt6.uno_bridge.py, 1203 líneas:uno,com.
La lista sale de analizar cada archivo, no de ejecutarlo. La página de compatibilidad da, para cada aplicación, la versión con la que se probaron estos puentes.
Un script frente a llamadas separadas
Un modelo que maneja una aplicación gesto a gesto hace una llamada por gesto. Un modelo que escribe todo el trabajo hace una sola llamada. El Hub trata ambos casos igual, así que se pueden cronometrar los dos con el mismo Hub, la misma aplicación y el mismo trabajo.
El trabajo consiste en añadir N elementos a un documento: N rectángulos en Inkscape, N cubos en Blender. Para cada N, diez repeticiones cronometraron una llamada a execute_script que hace todo el trabajo, y luego N llamadas que hacen un gesto cada una. El documento se vació antes de cada medición, y dos llamadas de calentamiento precedieron a las repeticiones. El tiempo se lee en el cliente, desde que se escribe la solicitud hasta que se lee la respuesta; en Inkscape, el trabajo es este script,
import json, os
import inkex
document = inkex.load_svg(os.environ['LMM_DOCUMENT'])
root = document.getroot()
for i in range(20):
root.add(inkex.Rectangle.new(i * 12, 10, 10, 10))
document.write(os.environ['LMM_DOCUMENT'])
print('LMM_RESULT: ' + json.dumps({'count': len(root)}))y cada una de las veinte llamadas separadas es el mismo script con el bucle sustituido por una línea, por ejemplo la tercera:
import json, os
import inkex
document = inkex.load_svg(os.environ['LMM_DOCUMENT'])
root = document.getroot()
root.add(inkex.Rectangle.new(36, 10, 10, 10))
document.write(os.environ['LMM_DOCUMENT'])
print('LMM_RESULT: ' + json.dumps({'count': len(root)}))Mediana de diez repeticiones, con la menor y la mayor entre paréntesis. El cliente es el de la demostración, con un reloj alrededor de cada llamada.
Inkscape 1.4, modo cli (un proceso de Python por llamada):
- N = 1: un script 141,7 ms (de 141,7 a 151,7); una llamada 141,7 ms (de 141,5 a 161,9); razón ×1,0.
- N = 5: un script 146,7 ms (de 141,6 a 152,0); cinco llamadas 738,7 ms (de 718,6 a 749,3); razón ×5,0.
- N = 20: un script 151,7 ms (de 141,6 a 151,9); veinte llamadas 2933,8 ms (de 2874,0 a 3046,2); razón ×19,3.
Blender 4.3.2, modo tcp (la aplicación sigue en marcha):
- N = 1: un script 1,5 ms (de 1,3 a 2,7); una llamada 1,2 ms (de 1,0 a 2,1); razón ×0,8.
- N = 5: un script 3,5 ms (de 3,3 a 6,1); cinco llamadas 5,9 ms (de 5,5 a 8,3); razón ×1,7.
- N = 20: un script 17,8 ms (de 16,7 a 20,4); veinte llamadas 30,5 ms (de 27,2 a 32,3); razón ×1,7.
En el modo cli, cada llamada inicia un proceso de Python y carga inkex, lo que cuesta unos 0,14 s sea cual sea el trabajo: veinte gestos en llamadas separadas cuestan 19,3 veces la llamada única. En el modo tcp la aplicación ya está en marcha, y las 19 llamadas adicionales sumaron 12,7 ms, unos 0,7 ms cada una. Una segunda pasada de toda la medición dio medianas que diferían como máximo en 10 ms en Inkscape, salvo las veinte llamadas separadas (87 ms), y como máximo en 1,3 ms en Blender; la razón con N = 20 fue ×19,9 en Inkscape y ×1,7 en Blender.