011 técnica
Generador fluido · UI por prompt contra corpus publicado
Un endpoint de Netlify, un índice de embeddings estático, un modelo con instrucciones cerradas y un guardrail de paleta. Ni base de datos ni framework cliente. El objetivo editorial es demostrar una tesis, no vender una feature.
Qué resuelve
Permite que un visitante —humano o agente— pida una pieza de UI en lenguaje natural, y obtenga una instancia renderizada en vivo que respeta estrictamente los tokens y reglas publicados en el corpus del sitio. La diferencia con pedírselo a un chatbot cualquiera es que el modelo no puede inventar: cada decisión queda citada contra una ficha, cada color queda validado contra la paleta canónica, cada combinación prohibida queda rechazada explícitamente.
Es la séptima regla del Estado 3 llevada al límite. Si el design system publica reglas, las reglas se ejecutan. Si se quedan en documentación, no eran reglas.
El contrato editorial antes que el contrato técnico
Tres invariantes que definen qué es un generador fluido honesto:
- No inventa tokens. La paleta canónica es un conjunto cerrado extraído del corpus en build. Cualquier valor de color que no pertenezca al conjunto convierte la respuesta en un rechazo con explicación.
- Declara la mutación. La salida lleva siempre un bloque legible con la pieza, variantes, tokens y reglas usadas. No hay generación silenciosa.
- Cita la fuente. Cada decisión tiene su chunk del corpus detrás, recuperado por similitud semántica. Sin citación, no hay instancia válida.
Lo que quedaría fuera es una generación libre sin guardrails. Ese camino existe, se llama ChatGPT en una pestaña al lado, y no es lo mismo.
Arquitectura ligera, deliberadamente
Veintinueve MDX, sesenta a ciento veinte vectores, coste mensual en céntimos.
El corpus de este sitio son veintinueve MDX. Chunkeados a seiscientos tokens quedan entre sesenta y ciento veinte vectores. Para ese volumen, montar Neon Postgres con pgvector es matar moscas a cañonazos. El stack elegido es deliberadamente pequeño:
- Build: un script Node (
scripts/build-embeddings.mjs) recorre los MDX, trocea por H2, embeddea con Voyage-3 y escribepublic/corpus-embeddings.json. Cache por hash en.embeddings-cache/para no quemar tokens en cada deploy. El JSON viaja versionado con el sitio. - Runtime: una única función Netlify (
netlify/functions/generate.ts). Recibe el prompt, lo embeddea como query, hace top-k en memoria (coseno, con boost por tipo y familia), construye contexto citado, llama a Claude con structured output, valida paleta, devuelve JSON. - Cliente: un componente Astro vanilla con un
<iframe sandbox srcdoc>para aislar estilos.
Coste mensual previsto para tráfico editorial: céntimos. Si el sitio creciera un orden de magnitud, migrar a pgvector es un cambio de dos archivos.
El guardrail de paleta
Después de recibir el HTML del modelo, el endpoint parsea los atributos style y extrae todos los valores de color (color, background, border-color, box-shadow, etc.). Cada valor pasa por un validador que acepta:
- Colores neutros (
transparent,currentColor,black,white). var(--token)donde el token aparece en la paleta canónica extraída de los CSS globales y los MDX.color-mix(in oklch, …)cuyos sub-valores son todos válidos (recursión).- Literales exactos que aparezcan ya documentados en el corpus.
Todo lo demás —un #ff00ff que el modelo quiso colar, un fuchsia nombrado, un oklch(0.7 0.4 320) no listado— dispara un refused.reason = "fuera_de_paleta". El error se devuelve al cliente con la lista de valores ofensivos, visible en la interfaz. El lector aprende qué pidió y por qué el sistema se niega. La barandilla se enseña.
El fallback que nadie mira hasta que hace falta
Claude es el proveedor primario. Si la API devuelve 5xx o rate-limit, el cliente reintenta contra Together AI (Llama 3.3 70B Instruct Turbo) con el mismo contrato JSON. El provider usado se devuelve en la respuesta para que el cliente pueda declararlo si quiere. Es la misma estrategia que usamos en LORSCLUB para consultas RAG: doble proveedor, degradación declarada, nunca caída silenciosa.
Qué queda fuera
Por diseño, no por pereza:
- Streaming de respuestas. Las salidas structured-output son cortas; la latencia extra no compensa la complejidad de SSE.
- Multi-turno. Cada prompt es una regeneración nueva. No hay conversación, no hay estado.
- Historia de prompts por usuario. Superficie anónima, sin persistencia.
- Generación de layouts complejos. Un botón, una card, una sección corta. Formularios completos y tablas quedan para una segunda ronda, con sus propios guardrails.
Cuándo tiene sentido portar esto a tu sistema
Si el corpus cabe en memoria, si las reglas son explicables en prosa, si la paleta es cerrada, si te importa más la trazabilidad que el “wow” del demo: ahora. Si ninguno de esos criterios se cumple, probablemente necesites algo más parecido a un agent retrieval system en condiciones, con base de datos vectorial, políticas OPA, y un equipo dedicado. Para la mayoría de design systems editoriales o de producto pequeño-mediano, la versión ligera que este sitio usa es suficiente y, lo que importa más, es auditable.
Para el caso contrario —corpus que ya no cabe en memoria—, la ficha de referencia es Nº 007 · RAG con chunking coherente.