[Desafío] Por qué los LLMs alucinen en la extracción de cuadrículas y cómo analizamos un marcador manuscrito en n8n

:waving_hand: Hey comunidad de n8n,

Los pipelines de extracción de documentos manejan facturas y CVs de maravilla. Pero durante una noche de juegos, le pasamos a nuestro AI algo que se veía simple pero lo rompió completamente: una tarjeta de puntuación de Kniffel (Yahtzee) escrita a mano.

:mouse_trap: La trampa de la cuadrícula

El objetivo: Tomar una foto, encontrar jugadores con un asterisco escrito a mano (*) encima de su nombre, extraer sus puntuaciones, calcular el bono de 35 puntos (si top $\ge$ 63), sumar todo y enviar a Sheets y Telegram.

Mira la foto adjunta:

r/VibeCodersNest - Challenge Why LLMs hallucinate on grid extraction and how we parsed a handwritten scorecard in n8n|562.5xauto

Es obvio para los humanos. Pero el LLM alucinó nombres (“Ivan” se convirtió en “ban”), fusionó columnas y arruinó las matemáticas.

Aquí te explicamos por qué los datos espaciales rompen la IA y la arquitectura de prompts que usamos para arreglarlo:

:eyes: Romper el sesgo “Izquierda-Derecha”

Los LLMs leen como un libro (de izquierda a derecha, fila por fila). Una tarjeta de puntuación exige lo opuesto. Para evitar el “sangrado de filas” entre columnas, forzamos una salida de Array de Objetos ([{player_name, sum_top, sum_bottom}]). Esto obliga a la IA a terminar de leer una columna vertical antes de pasar a la siguiente.

:abacus: Matemáticas determinísticas, cero juicio del LLM

Ya sabemos que el Extractor que estamos usando es increíblemente confiable para clasificar documentos y extraer datos estructurados, así que decidimos probar sus límites y ver si podía manejar cálculos también. Gran error. Los LLMs son motores de predicción de texto, no calculadoras. La solución: Le indicamos que solo extrajera números sin procesar y usamos un nodo de Código JavaScript puro aguas abajo para manejar las matemáticas y el bono condicional.

:stop_sign: Los estados vacíos explícitos detienen las alucinaciones

Los jugadores escribieron guiones (-) en las casillas vacías. Como el LLM no podía encajar un guión en un esquema de Número, entró en pánico y generó -1, rompiendo nuestro JavaScript. Agregar una restricción estricta—“CRÍTICO: Si una celda es un guión o está vacía, DEBES generar 0”—arregló el pipeline al instante.

:anchor: Anclando la vista con etiquetas

Para evitar que la IA se desviara entre columnas, le dimos coordenadas visuales. Le indicamos que mirara las etiquetas impresas en el extremo izquierdo (por ejemplo, “Dreierpasch”), trazara una línea horizontal invisible hasta la columna marcada y extrajera solo esa celda.

:turtle: Una advertencia honesta

Delegar las matemáticas a JS garantiza una lógica perfecta, pero el reconocimiento de escritura manuscrita aún depende de la calidad de la foto. Si necesitas datos impecables de escritura desordenada, la validación humana en el bucle es aún el límite.

:wrench: Cómo ejecutarlo

Utilicé el Extractor de easybits porque maneja el cumplimiento de esquema JSON de forma nativa sin luchar con prompts de nodos HTTP. Tanto cloud como auto-hospedado caben en el nivel gratuito.

  • n8n Cloud: Busca easybits en el panel de nodos.

  • Auto-hospedado: Instala @easybits/n8n-nodes-extractor desde Nodos de Comunidad.

:speech_balloon: Comentarios bienvenidos

He adjuntado la imagen sin procesar. Tengo mucha curiosidad: ¿qué sucede cuando ejecutas esta imagen exacta a través de tu configuración actual de OCR o LLM? ¿Has encontrado una forma más limpia de extraer columnas verticales de cuadrículas horizontales densas?

Saludos,
Felix

1 me gusta

El enfoque de descarga matemática es acertado: mantener toda la aritmética en un nodo Code en lugar de confiar en el LLM hace que la tubería sea determinista independientemente del modelo. La restricción de esquema JSON orientado a columnas ([{player_name, sum_top, sum_bottom}]) es una forma limpia de evitar el sangrado de filas.

Una cosa que vale la pena probar si la precisión sigue siendo deficiente con fotos de baja calidad: recorta cada columna como una región de imagen separada antes de pasarla al modelo de visión. Incluso un recorte basado en coordenadas aproximadas en un nodo Code (usando las dimensiones de imagen que conoces del diseño del marcador) puede reducir dramáticamente las alucinaciones posicionales dándole al modelo un contexto más simple de una sola columna.

1 me gusta

¡Hola @nguyenthieutoan, gracias por el feedback!

La idea de recortar cada columna en una imagen separada es definitivamente interesante. Mi única preocupación es que requeriría que la foto se tomara con un encuadre bastante consistente cada vez. De lo contrario, existe el riesgo de cortar información importante durante el proceso de recorte.

Dicho esto, es una sugerencia excelente, y definitivamente intentaré probarla para ver qué tan práctica es en un configuración del mundo real. ¡Gracias por compartir la idea!

La preocupación sobre el encuadre es válida, pero puedes solucionarla agregando un solapamiento generoso a cada recorte, digamos un búfer del 10-15% a ambos lados de cada límite de columna en lugar de cortar en el borde exacto. El LLM seguirá enfocándose en la columna correcta porque tu esquema + prompt lo bloquea a un jugador a la vez, y un poco de solapamiento es mucho menos perjudicial que cortar los números. Para hojas de puntuación impresas como Kniffel donde los encabezados de columna tienen un ancho fijo, también puedes derivar los límites de recorte una sola vez a partir de una imagen de referencia y reutilizar los mismos desplazamientos de píxeles de manera confiable en todas las fotos tomadas en condiciones similares (misma mesa, misma distancia del teléfono).

1 me gusta

¡Hola @nguyenthieutoan, suena válido! Definitivamente voy a probar este enfoque.

Lo divertido es ver cuán capaz puede ser la IA en general, pero luego llega una sola idea de caso extremo y de repente te das cuenta de que puede romper muchas soluciones. La verdad es que disfruto mucho descubriendo esas situaciones, suelen ser las más esclarecedoras.