Constelación de Palabras (V): los embeddings, por fin, con una medición real antes de decidir
Esta entrada continúa las cuatro anteriores sobre Constelación de Palabras. La sesión 2 dejó una pregunta aparcada con toda intención: ¿merece la pena montar una relación semántica de verdad, con un segundo modelo de embeddings en LM Studio, o la coocurrencia textual es suficiente por ahora? La respuesta entonces fue “por ahora, no” — no porque fuera inviable, sino porque no había datos reales sobre el coste, solo una sospecha razonable de que sería rápido. Esta sesión ha sido, precisamente, medir esa sospecha antes de decidir.
La condición: que no ralentice nada
La petición para retomar los embeddings vino con una condición explícita: adelante, pero solo si no hacen que la aplicación tarde demasiado más. Es una condición razonable y, a la vez, imposible de responder desde aquí sin trampa: la velocidad real depende del hardware de quien lo va a usar — ROCKY, en este caso —, no de una cifra genérica de internet. Así que, en vez de dar una respuesta con una suposición vestida de dato, se hizo lo que corresponde: se dejaron los pasos para que José Luis lo midiera él mismo, en su propia máquina, con su propio LM Studio.
Antes de eso, un rato de búsqueda (con el navegador, porque la herramienta de búsqueda automática volvió a fallar esta sesión) confirmó dos cosas que sí estaban al alcance de comprobar sin acceso a ROCKY: que LM Studio permite cargar y servir un modelo de embeddings a la vez que el modelo de chat desde la versión 0.3.0 (agosto de 2024) — sin tener que descargar uno para cargar el otro —, y que un modelo de embeddings como nomic-embed-text pesa una fracción de lo que pesa un modelo de chat de 3B parámetros. Razones para esperar que fuera rápido, pero razones, no una medición.
La medición real
Con nomic-embed-text-v1.5 descargado (el repositorio original de nomic-ai, no una de las muchas copias de terceros que aparecen al buscarlo) y cargado en LM Studio a la vez que llama-3.2-3b-instruct — confirmado con una captura de la pestaña Developer, con los dos modelos en estado READY —, la prueba fue directa: pedir los embeddings de seis palabras sueltas y medir el tiempo con PowerShell.
powershell
Measure-Command {
Invoke-RestMethod -Uri "http://127.0.0.1:1234/v1/embeddings" -Method Post -ContentType "application/json" -Body '{
"model": "text-embedding-nomic-embed-text-v1.5",
"input": ["bosque", "otoño", "niebla", "camino", "silencio", "hojas"]
}'
} | Select-Object TotalSeconds
Resultado: 0,32 segundos. Frente a los varios segundos que ya tarda la generación de palabras con el modelo de chat, es un añadido que no se nota. Con eso, la condición quedó satisfecha con un dato, no con una promesa — y se pasó a construir la pieza de verdad.
Cómo se ha integrado, sin romper lo que ya había
La coocurrencia textual (sesión 2) sigue exactamente igual: sigue siendo la primera vía, y sigue teniendo prioridad. Lo nuevo es una segunda vía, independiente, para las palabras que se quedan sin ninguna señal textual — típicamente en el modo evocación, donde las palabras pueden no aparecer nunca en el texto. Antes, esas palabras caían directamente al respaldo puramente geométrico (una línea discontinua, sin ningún significado real detrás). Ahora, si hay vectores de embeddings disponibles, primero se intenta conectarlas con la palabra de significado más parecido — medido de verdad por el modelo, no por cercanía de escritura — y esa conexión pasa a contar como “real” también, con línea continua igual que las de coocurrencia textual.
Un módulo nuevo, embeddings.py, es quien habla con ese segundo modelo — mismo patrón que ia.py con el modelo de chat, mismo principio de MODULARIDAD de siempre. Y aquí es donde entra la parte más importante para que la condición inicial (“que no ralentice”) se cumpla siempre, no solo en la prueba de hoy: obtener_embeddings() tiene un tope de cuatro segundos, y si algo falla, tarda más de la cuenta, o el modelo de embeddings simplemente no está cargado, la función no lanza ningún error — devuelve None, sin más, y la constelación se calcula exactamente como si esta pieza no existiera. La aplicación nunca depende de que los embeddings funcionen; en el peor de los casos, se limita a no usarlos.
python
def obtener_embeddings(palabras):
if not palabras:
return None
try:
respuesta = requests.post(
LM_STUDIO_URL,
json={"model": MODEL_NAME, "input": palabras},
timeout=TIMEOUT_SEGUNDOS,
)
respuesta.raise_for_status()
datos = respuesta.json()["data"]
if len(datos) != len(palabras):
return None
vectores = [None] * len(palabras)
for elemento in datos:
vectores[elemento["index"]] = elemento["embedding"]
if any(vector is None for vector in vectores):
return None
return vectores
except (requests.RequestException, KeyError, ValueError, TypeError, IndexError):
return None
El aviso de honestidad de la interfaz se ha actualizado para explicar las dos vías: una línea continua ya no dice solo “estas dos palabras aparecen juntas en tu texto”, dice “esto es real, por una de dos razones posibles” — y las dos son razones legítimas, aunque midan cosas distintas. Se decidió, a propósito, no complicar la interfaz con un tercer estilo de línea para distinguirlas visualmente: sigue habiendo solo continua y discontinua, y “real” ha crecido de significado en vez de multiplicarse en categorías.
Una comprobación real que no tenía que ver con esto
En las primeras pruebas con LM Studio real, la aplicación avisó dos veces de que el modelo no había devuelto suficientes palabras utilizables — el mismo aviso que ya existía desde la sesión 2, sin relación con los embeddings (ese aviso salta al pedir las palabras, antes incluso de tocar el módulo nuevo). Después de esas dos veces, fluido en múltiples pruebas seguidas. Todo apunta a lo esperado: las primeras respuestas de un modelo justo después de cargarlo pueden salir peor, un “calentamiento” habitual, y no algo que esta sesión haya cambiado. El proyecto sigue sin reintentar generaciones por su cuenta (mismo criterio que Postal IA): un aviso claro en un fallo aislado es el comportamiento querido, no un error a corregir.
Cómo se comprobó
Trece pruebas nuevas, treinta y nueve en total: obtención de embeddings simulada (éxito con reordenado por índice, porque la API no promete que los vectores lleguen en el mismo orden en que se pidieron; fallo por tiempo agotado, por error de conexión, y por una respuesta mal formada, los tres cayendo a None sin lanzar ningún error); que una palabra sin señal textual se conecta de verdad con la de significado más parecido cuando hay vectores disponibles; que la señal textual sigue teniendo prioridad sobre la semántica cuando las dos existen para la misma palabra; que sin vectores de embeddings (el caso por defecto) el resultado es idéntico al de antes de esta pieza, palabra por palabra; y que el árbol de expansión mínima sigue dejando la constelación en una sola pieza con embeddings de por medio. Se comprobó además, con un navegador sin cabeza, que la vista de comparación y la descarga de imagen (que no dependen para nada de esta pieza) siguen funcionando exactamente igual que antes.
Qué no hace todavía (límites honestos)
- “Significado parecido” depende de lo que ese modelo concreto (
nomic-embed-text-v1.5) haya aprendido, no es una verdad absoluta sobre el idioma — el mismo límite honesto que ya tenía la coocurrencia textual, solo que por un motivo distinto. - La conexión semántica siempre elige la palabra más parecida disponible, sin ningún umbral mínimo de similitud: si las palabras no tienen nada que ver entre sí, se conecta con la “menos distinta”, no se deja sin conectar. Eso lo resuelve luego el árbol de expansión mínima igual que antes, pero conviene saber que “real” por esta vía no garantiza que la similitud sea alta.
- Los dos modelos (chat y embeddings) tienen que estar cargados a la vez en LM Studio para que esta pieza funcione; si solo se carga el de chat, la aplicación sigue funcionando igual que en las sesiones 2, 3 y 4, sin avisar de que se está perdiendo nada.
- La interfaz no distingue, de momento, cuál de las dos vías hizo real una conexión concreta — decisión deliberada de esta sesión, no una limitación técnica.
Siguiente paso
Que José Luis siga probando con textos reales de clase, ahora en modo evocación sobre todo (donde más se nota la diferencia, al ser el modo con menos señal textual), y valore si las conexiones por significado aportan algo que se note de verdad al alumnado, o si pasan desapercibidas frente a las de coocurrencia. Si en algún momento interesara saber cuál de las dos razones hizo real una línea concreta, sería el momento de plantear ese tercer estilo visual que hoy se decidió no añadir todavía.



