Por qué genero las figuras de matemática en código y SVG, y no con un modelo de imagen como gpt-image o nano banana?
El prompt perfecto no evita que la IA falle. Genero las figuras de matemática en código y verifico cada una en capas antes de publicarla — acá el cómo y el por qué.
Dioni
Updated:

Estoy construyendo un banco de ítems de matemática con un motor de IA, y ahí las figuras deben ser exactas a lo que la pregunta describe: un ángulo rotulado 60° tiene que medir 60°, un triángulo con dos ángulos dados tiene que dejar el tercero positivo. Ningún modelo de imagen lo logra de forma consistente, pagado o gratis, ni con el prompt más detallado que se me ocurrió... no es un problema de prompt, es de diseño. La solución a la que llegué es que el modelo nunca dibuja: emite una spec tipada, un motor determinista la renderiza, y una verificación en capas la revisa antes de que la figura llegue a un estudiante.
¿Por qué un modelo de imagen no dibuja una figura de matemática exacta?
Lo primero que hice fue asumir que el problema era mío: prompt corto, poco específico, sin ejemplos. Lo alargué, agregué restricciones numéricas, pedí el ángulo exacto con su rótulo. No cambió nada. Y no es una anécdota mía, es un hallazgo publicado: "Text-to-Image Diffusion Models Cannot Count, and Prompt Refinement Cannot Help" (arXiv:2503.06884, 2025) evalúa todos los modelos de difusión de referencia, abiertos y privados, y muestra que ninguno cuenta objetos de forma confiable, y que refinar el prompt no mueve la aguja. Es una limitación arquitectónica, no de prompting.
Lo espacial es todavía peor que lo numérico. T2I-CompBench (arXiv:2307.06350, NeurIPS 2023) mide composicionalidad en 8 subcategorías sobre miles de prompts, y la categoría spatial 2D saca 0,08-0,13 en Stable Diffusion v1-4/v2, contra 0,30-0,50 en color y textura. El propio paper lo dice sin vueltas: la relación espacial es la subcategoría más difícil de todas, 3 a 5 veces peor que color o textura. No es que el modelo dibuje mal el color del triángulo, es que no sabe poner un vértice donde corresponde.
Y en matemática visual específicamente, MathGen (arXiv:2603.27959, 2026) es el benchmark más nuevo y más cercano a lo que yo necesitaba: 900 problemas de conteo, ángulos, fracciones, geometría plana y sólida, funciones y conjuntos, con un verificador ejecutable que corrige cada figura generada. El mejor modelo del mercado, Nano Banana Pro, acierta un 42% global, y en ángulos un 20%... eso es 1 de cada 5. GPT-Image-1.5 saca 35,7% global y 17,1% en ángulos. Ángulos es, para los dos modelos top, el peor o segundo peor dominio del benchmark completo. Es literalmente el tipo de figura que yo necesitaba generar.
Esto no es una condena permanente al modelo de imagen. Nano Banana Pro casi triplica a su predecesor directo (14,9%) y a Imagen 4 Ultra (14,2%) en el mismo benchmark. La frontera se mueve rápido. Pero hoy, para una figura que tiene que ser exacta, ese progreso todavía no alcanza, y un pipeline de producción no puede apostar a que alcance la próxima semana.

¿Cómo resuelve esto la industria: código en vez de píxeles, y verificación encima?
El patrón que aparece una y otra vez en los papers y en los productos que ya existen es el mismo: no le pidas al modelo que dibuje la figura, pídele que emita la descripción semántica de la figura, y deja que un motor determinista la calcule y la dibuje.
GGBench (arXiv:2511.11134, 2025) lo mide directo en construcción geométrica: cuando el modelo genera y ejecuta código para resolver el problema, pass@1 llega a 79,02; cuando genera la imagen directamente, la evaluación visual (VLM-I) cae a 57,08. El humano de referencia está en 83,06. Ejecutar código se acerca al humano. Generar píxel se queda atrás. El paper lo concluye así: los modelos que razonan y ejecutan código logran corrección geométrica, interpretabilidad y calidad mucho más altas que la generación de imagen directa.
MagicGeo (arXiv:2502.13855, 2025) es el ejemplo más cercano a lo que terminé construyendo: un LLM autoformaliza el enunciado en restricciones de coordenadas, un solver formal verifica esas restricciones, y solo entonces se genera el diagrama final. El dato que más me sirvió de este paper es el ablation: agregar el loop de verificación con el solver (el modelo recibe feedback cuando el solver no encuentra solución, hasta 5 iteraciones) sube la precisión de autoformalización de 94,7% a 98,7%. No es el modelo generando mejor a la primera, es el sistema completo verificando antes de aceptar.
AlphaGeometry (Trinh, Wu, Le, He, Luong, Nature, 2024) es la referencia clásica del mismo patrón: un modelo neuronal propone construcciones auxiliares, un motor de deducción simbólica las prueba, y así resolvió 25 de 30 problemas de geometría olímpica contra 10 de 30 del sistema anterior. El modelo aporta semántica. El motor determinista ejecuta y verifica.
Y esto ya es comportamiento de producto, no solo de paper. Cuando le pides a ChatGPT un gráfico de datos, no genera una imagen con DALL-E: ejecuta Python con matplotlib en un sandbox y devuelve el render (OpenAI, "Data analysis with ChatGPT", Centro de Ayuda). Los Artifacts de Claude soportan SVG y diagramas como Mermaid como tipos de primera clase, renderizados de forma determinista, no como imagen de píxeles (Anthropic, "What are artifacts and how do I use them?", Centro de Ayuda). Dos labs grandes, mismo patrón, para lo mismo: cuando la precisión de los datos importa, código y renderer le ganan al pixel.
¿Cómo se construye un pipeline de figuras exactas, de la spec al render verificado?
Con eso decidido, el diseño quedó así: el modelo nunca dibuja. El modelo llena una spec tipada, y todo lo demás es determinista o verificado.
El primer paso decide si el ítem necesita figura, y de qué tipo, antes de redactar el enunciado:

const ItemPlanSchema = z.object({
claim: z.string(),
evidence: z.string(),
task: z.string(),
needsFigure: z.boolean().default(false),
figureKind: z.enum(FIGURE_KINDS).optional(),
});FIGURE_KINDS son 7: bar_chart, partitioned_shape, triangle, angle, number_pyramid, balanza, data_table. Si el plan dice que sí hace falta figura, el paso siguiente emite el enunciado y, junto con él, la FigureSpec correspondiente. No un SVG, no una descripción en texto libre: campos tipados. Para un triángulo:
export const TriangleSpec = z.object({
kind: z.literal('triangle'),
a: z.number().gt(0).lt(180),
b: z.number().gt(0).lt(180),
unknown: z.enum(['A', 'B', 'C']).optional(),
unknownLabel: z.string().optional(),
}).refine((s) => 180 - s.a - s.b > 0, {
message: 'los dos ángulos dados deben dejar un tercero positivo',
});Ese .refine es la primera capa de verificación, y es gratis: un triángulo geométricamente imposible ni siquiera llega a renderizarse, se rechaza en el parseo. El renderer resuelve los vértices con trigonometría real, ley de senos incluida, nunca deja que el modelo calcule un ángulo por su cuenta.
Con la spec ya válida, viene el cross-check numérico: comparar cada valor "duro" de la figura contra el enunciado, con manejo de miles y decimales chilenos.
export function crossCheckFigure(stem: string, spec: FigureSpec): { ok: boolean; issues: string[] } {
const uniqueValues = [...new Set(collectHardNumbers(spec))];
const issues = uniqueValues
.filter((value) => !appearsInStem(stem, value))
.map((value) => `el valor ${numberVariants(value)[0]} de la figura no aparece en el enunciado`);
return { ok: issues.length === 0, issues };
}Si el enunciado dice "un triángulo con ángulos de 40° y 65°" y la spec trae un tercer ángulo de 75° que el modelo mencionó en algún lado del texto pero no en el enunciado que lee el estudiante, crossCheckFigure devuelve { ok: false, issues: ["el valor 75 de la figura no aparece en el enunciado"] }. Es puramente numérico, no entiende rótulos ni relaciones, para eso viene la siguiente capa.
La figura se rasteriza a PNG y se manda, junto con el enunciado y la respuesta correcta, a un juez multimodal que responde dos preguntas distintas: si la figura coincide exactamente con el enunciado, y si la respuesta declarada es derivable leyendo solo la figura.
const { object } = await generateObject({
schema: FigureJudgeSchema,
messages: [{
role: 'user',
content: [
{ type: 'text', text: judgePrompt(stem, correctAnswer) },
{ type: 'image', image: png },
],
}],
});Si cualquiera de las dos capas encuentra un problema, no se descarta el ítem, se repara:
if (plan.needsFigure) {
for (let attempt = 0; ; attempt++) {
const gate = await evaluateFigureGate(item, common, deps.judge);
figure = gate.figure;
if (gate.issues.length === 0 || attempt >= FIGURE_MAX_REPAIRS) break;
item = await call('item', {
...common,
schema: GeneratedItemSchema,
prompt: itemPrompt(ctx, plan, nAlts, gate.issues.join(' ')),
});
}
}El modelo vuelve a intentar el paso item, esta vez con el texto concreto de los issues concatenado al prompt, no con un "vuelve a intentarlo" genérico. FIGURE_MAX_REPAIRS está en 2 por defecto: hasta 2 reparaciones automáticas antes de rendirse. Y cuando se rinde, el ítem igual se guarda, marcado como pending para revisión humana, con el confidence topeado. Nunca se descarta en silencio, y nunca revienta la generación completa por una figura mala.

¿Por qué structured outputs no garantiza que la figura sea correcta?
Acá es donde vale la pena ser preciso, porque es fácil confundir dos cosas distintas. Structured Outputs de OpenAI (documentación oficial, agosto 2024) sube el cumplimiento de un JSON Schema complejo de menos de 40% a 100%, vía constrained decoding: el modelo literalmente no puede emitir un token que rompa el schema. Eso es enorme, y es la base de por qué le pido al modelo una FigureSpec tipada en vez de texto libre.
Pero 100% de cumplimiento de schema es 100% de forma válida, no 100% de significado correcto. JSONSchemaBench (arXiv:2501.10868, 2025) lo mide con 10.000 schemas reales: en el subset "GitHub Hard", schemas anidados y complejos, la cobertura colapsa, Guidance cae a 41%, Outlines a 3%. Y el límite de fondo es aún más directo: un JSON perfectamente válido según el schema puede seguir teniendo un ángulo de 75° que no aparece en ningún lado del enunciado, o un triángulo cuyos ángulos suman 210°. El propio OpenAI lo dice como advertencia: structured outputs no hace determinista el significado del output, no elimina la necesidad de validación en la aplicación.
Por eso la FigureSpec sola no alcanza, ni siquiera con .refine. El schema y el .refine garantizan que la figura sea geométricamente posible. El cross-check garantiza que sus números están en el enunciado. El juez multimodal garantiza que la figura y el enunciado cuentan la misma historia, y que la respuesta se puede derivar de lo que el estudiante realmente ve. Tres capas, costo creciente, cada una verificando algo que la anterior no puede.
¿Dónde sí sirven hoy los modelos de imagen, y cuáles son los límites de este approach?
Nada de esto significa que el modelo de imagen no sirva. Para ilustrar algo donde no hay un número exacto que verificar, una escena, un personaje, un fondo decorativo, el modelo de imagen sigue siendo la herramienta correcta, y con una brecha que se cierra rápido: en GenExam (arXiv:2509.14232, 2025), sobre figuras estilo examen, GPT-Image-1 saca 13,1% en score estricto, y la mayoría de los 17 modelos evaluados queda bajo 15%... pero Nano Banana Pro llega a 70,2% estricto, un salto enorme sobre su antecesor en el mismo benchmark. La frontera se mueve rápido, y el día que un modelo de imagen verifique su propia geometría de forma consistente, este pipeline entero deja de tener sentido. Hoy no es ese día.
Y el approach propio tiene límites que prefiero dejar explícitos en vez de esconder. Son 7 kind, no 70: cualquier figura que no calce en triángulo, ángulo, balanza, gráfico de barras, tabla, pirámide numérica o figura particionada simplemente no se puede pedir, no hay generación de SVG libre. Es una decisión de diseño, control total sobre el render a cambio de cobertura, y JSONSchemaBench ya mostró por qué: un schema que crece en complejidad colapsa su propia cobertura, así que el límite es a propósito. Un ítem con figura puede costar hasta 6 llamados al modelo en el peor caso: plan, ítem, hasta 2 reparaciones del ciclo ítem-juez, y la crítica final. Y el cross-check numérico no entiende rótulos de texto ni relaciones cualitativas, "el ángulo A es agudo", eso descansa completo en el juez multimodal, que es probabilístico y puede tener falsos negativos. Cuando el gate se agota sin pasar, la figura no se publica sola: queda pending, visible en el banco, para que alguien la revise antes de que llegue a un estudiante.
Así que hoy el modelo nunca me dibuja una figura sin que algo determinista la verifique antes. Para lo que tiene que ser exacto, es la única forma en que confío en el output.

