
Constelación de Palabras (II): un fallo real con la “y”, líneas con memoria del texto, y un cielo que por fin no es una rejilla
Esta entrada continúa la anterior sobre Constelación de Palabras, la herramienta que convierte un texto o un tema en un mapa estelar de 5 a 8 palabras. Entre una entrada y la otra ha pasado lo más importante que le puede pasar a un MVP: José Luis la ha probado de verdad, con LM Studio real en ROCKY, no con una respuesta simulada. De ahí ha salido un fallo real que las pruebas automáticas de la primera versión no podían pillar, y tres peticiones de aspecto que han cambiado bastante cómo se ve la constelación.
Un fallo real: la “y” que se comía una palabra
El primer aviso fue directo: “para un texto de 7 líneas solo genera 3 palabras”. El system prompt pide entre 5 y 8, separadas por comas, así que algo no cuadraba. La sospecha, y luego la confirmación, fue esta: aunque se le pide expresamente usar solo comas, un modelo de este tamaño escribe listas en español como se habla, sin coma antes del último elemento: bosque, otoño, niebla y camino. El código anterior solo partía por comas, así que “niebla y camino” se quedaba pegado como una sola palabra rota, y de 4 conceptos reales solo se veían 3.
La solución no fue pedirle otra vez al modelo que lo intentara mejor (el proyecto no reintenta generaciones, igual que Postal IA con la métrica del romance): fue arreglarlo en Python, con cuidado de no romper el caso contrario. Una expresión legítima de varias palabras puede llevar su propia “y” en mitad de la lista —paz y armonía es una expresión de dos palabras tan válida como cualquier otra—, así que el arreglo solo toca la ÚLTIMA aparición de ” y “/” e ” como palabra suelta, y solo si cae después de la última coma:
python
def _normalizar_conjuncion_final(contenido):
coincidencias = list(re.finditer(r"\s+[ye]\s+", contenido, re.IGNORECASE))
if not coincidencias:
return contenido
ultima = coincidencias[-1]
if ultima.start() <= contenido.rfind(","):
return contenido
return contenido[: ultima.start()] + ", " + contenido[ultima.end() :]
Además, se añadió una frase explícita a los dos system prompts pidiendo que no se use “y” ni “e” en absoluto entre los elementos, para que el fallo se dé con menos frecuencia desde el origen. Las dos cosas juntas —pedir mejor y arreglar lo que aun así llegue mal— es el mismo principio que ya aplicaba PINSAPIA con sus propios fallos de formato.
Las conexiones dejan de ser solo decorativas
La otra pregunta de esta sesión fue más de fondo: “¿se puede hacer relación semántica de verdad, o lo dejamos?”. La respuesta honesta tenía dos caminos. Uno era usar embeddings reales —un segundo modelo en LM Studio, especializado en vectores de significado— para medir similitud de verdad; el otro, no instalar nada nuevo y medir en su lugar qué palabras aparecen juntas o cerca en el propio texto, con Python puro. Sobre la mesa, con sus dos precios distintos, se eligió la segunda: sin modelo nuevo, más simple, más del espíritu del proyecto, aunque hay que decirlo tal cual es: cercanía de escritura no es lo mismo que relación de significado.
La regla quedó así: si una palabra aparece literalmente en el texto original, se conecta con la otra palabra (también presente en el texto) que tiene más cerca por escrito. Cuando eso pasa, la línea es continua. Si una palabra no aparece en el texto tal cual —lo habitual en modo evocación, donde las palabras pueden no estar ahí en absoluto—, no hay ninguna señal real que usar para ella, y su conexión cae al vecino más cercano en el propio dibujo, como en la primera versión: decorativa, y ahora se dibuja discontinua para que se note la diferencia a simple vista. El pie de página de la aplicación explica las dos cosas con esas palabras, sin adornarlo.
Una ventaja de cómo quedó escrito: no hace falta que el código sepa en qué modo se está trabajando. Si la palabra está en el texto, la señal real aparece sola; si no, cae al respaldo sola. El modo “resumen” tiende a dar líneas continuas y el modo “evocación” tiende a dar líneas discontinuas, pero es una consecuencia del texto, no una regla escrita a mano por modo.
Estrellas que no son todas iguales
Petición sencilla, con una regla igual de sencilla detrás: las palabras más importantes se dibujan con una estrella más grande. “Importante” se decide sin IA, otra vez con aritmética de texto: cuántas veces aparece cada palabra, literalmente, en el texto original. Una palabra repetida tres veces sale más grande que una que solo aparece una vez. Cuando esa señal no dice nada —modo evocación, palabras que no están en el texto, o no hay texto disponible—, cae al orden en que las devolvió el modelo: la primera de la lista, la más grande.
Un cielo que al principio parecía una rejilla
El primer intento de “fondo no negro, con puntitos” fue el truco de CSS más simple que hay: varias capas de radial-gradient en background-image, cada una repetida (background-size) con un tamaño distinto, para que no se notara la trama. No funcionó como se esperaba: por muy distintos que fueran los tamaños de cada capa, seguían siendo patrones que se repiten, y el ojo los detecta enseguida como una rejilla, casi como el suelo de un centro comercial. Nada que ver con un cielo.
La solución fue dejar de fingir aleatoriedad con CSS y generarla de verdad: una segunda capa SVG, por debajo de la constelación, que se llena con 160 puntos en posiciones calculadas con Math.random() al cargar la página —tamaño y opacidad también al azar, para dar sensación de profundidad—. Es decoración de página, no datos del proyecto: no depende de ninguna semilla ni tiene que ser reproducible, así que aquí sí tiene sentido usar aleatoriedad de verdad en vez de una regla determinista.
javascript
(function generarFondoEstrellado() {
var ns = "http://www.w3.org/2000/svg";
var total = 160;
for (var i = 0; i < total; i++) {
var estrella = document.createElementNS(ns, "circle");
estrella.setAttribute("cx", (Math.random() * 100).toFixed(2));
estrella.setAttribute("cy", (Math.random() * 100).toFixed(2));
estrella.setAttribute("r", (0.1 + Math.random() * 0.3).toFixed(2));
estrella.setAttribute("fill", "#ffffff");
estrella.setAttribute("opacity", (0.2 + Math.random() * 0.65).toFixed(2));
fondoEstrellas.appendChild(estrella);
}
})();
De paso, las líneas de la constelación se hicieron más gruesas —tanto las continuas como las discontinuas—, para que se vieran bien sobre el cielo nuevo sin perder la diferencia entre unas y otras.
Cómo se comprobó
Once pruebas nuevas sobre las catorce de la primera versión, veinticinco en total, todas en verde: cuatro sobre el tamaño de las estrellas por importancia, tres sobre cuándo una conexión es real y cuándo decorativa, y cuatro sobre el fallo de la “y”/”e” — incluida una que comprueba que una “y” en mitad de una expresión legítima (“paz y armonía”) se deja intacta. El cambio visual del fondo, al ser JavaScript puro sin lógica de negocio que probar con pytest, se comprobó de otra forma: un navegador sin cabeza (Playwright) cargó la página, dibujó una constelación de ejemplo y guardó una captura, para ver con los propios ojos que el cielo ya no tenía rejilla y que las líneas continuas y discontinuas se distinguían bien.
Qué no hace todavía (límites honestos)
- Las conexiones “reales” son cercanía de escritura en el texto, no un cálculo de significado: dos palabras pueden aparecer juntas sin estar relacionadas, y dos palabras muy relacionadas pueden no coincidir nunca literalmente. Si en algún momento se quiere relación de significado de verdad, el camino sigue siendo embeddings con un segundo modelo en LM Studio — quedó descartado por ahora, no imposible.
- El arreglo de la “y”/”e” cubre el patrón más habitual (la lista sin coma antes del último elemento), pero un modelo de 3B parámetros puede encontrar otras formas de saltarse el formato que todavía no se han visto.
- Las posiciones de las estrellas siguen siendo decorativas del todo, como desde la primera versión.
- El fondo estelar se genera de nuevo, distinto, cada vez que se recarga la página (no tiene semilla ni falta que le hace, es solo decoración) — la propia constelación de puntos y líneas sí sigue siendo idéntica para el mismo texto y modo.
Siguiente paso
Que José Luis siga usando Constelación de Palabras con textos reales de clase, en los dos modos, y valore si la distinción entre línea continua y discontinua se entiende a simple vista sin tener que leer el aviso de abajo. Si aparece algún otro caso en que el modelo devuelva menos palabras de las esperadas, lo suyo es traer aquí el texto exacto que generó el modelo — como esta vez— en vez de adivinar el arreglo a ciegas.


Etiqueta:AIDARAC, constelación de palabras, coocurrencia, depuración, ies monterroso, lm studio, montesteam, ROCKY, svg



