Los puntos ciegos de los LLM descubiertos al programar con IA. Basado en Claude Sonnet
- Stop Digging → Dificultad para cambiar de rumbo cuando surge un problema
- Use Static Types → Necesidad de configurar tipos estáticos
- Black Box Testing → Dependencia excesiva de los detalles de implementación
- Use MCP Servers → Problemas de configuración y seguridad de los servidores MCP
- Preparatory Refactoring → Puede realizar refactorizaciones innecesarias
- Mise en Place → Surgen problemas cuando falla la configuración del entorno
- Stateless Tools → Surgen problemas con herramientas que dependen del estado
- Respect the Spec → Alta posibilidad de violar la especificación
- Bulldozer Method → Realiza demasiado trabajo repetitivo
- Memento → Problemas por falta de comprensión del contexto
- Requirements, not Solutions → Es necesario aclarar los requisitos
- Scientific Debugging → Surgen problemas cuando corrige basándose en suposiciones
- Use Automatic Code Formatting → Aparecen inconsistencias en el estilo del código
- The Tail Wagging the Dog → Se obsesiona con problemas menores en lugar de tareas importantes
- Keep Files Small → Surgen problemas al modificar archivos grandes
- Know Your Limits → El modelo reconoce mal sus propios límites
- Read the Docs → Comete errores con información fuera de su conocimiento entrenado
- Culture Eats Strategy → Falta de consistencia en el estilo del código
- Walking Skeleton → Es necesario priorizar que funcione primero el sistema mínimo
- Rule of Three → Hace falta refactorizar cuando hay duplicación de código
No seguir cavando (Stop Digging)
- Los LLM actuales tienen poca capacidad para detenerse por sí solos y cambiar de dirección cuando surge un problema durante una tarea
- Ejemplo: si al implementar la función X resulta que primero hay que implementar la función Y, el LLM aun así intenta completar la tarea original (X)
- Esto es una ventaja en el sentido de que sigue fielmente las instrucciones, pero le cuesta reconocer el problema y cambiar de rumbo
- Estrategias para evitar el problema
- En la etapa de planificación, usar un modelo de razonamiento para decidir prioridades y tareas previas necesarias
- Los LLM con capacidades de agente, como Sonnet, leen archivos y elaboran un plan de trabajo → pueden identificar tareas necesarias incluso sin que el usuario las indique explícitamente
- Idealmente, el LLM debería poder detectar el problema y pedir confirmación al usuario
- Sin embargo, como eso consume contexto, puede ser mejor que otro LLM de supervisión se encargue de ello
-
Example
- Después de modificar el método de muestreo aleatorio de una simulación Monte Carlo, se le pidió a Claude Code que actualizara las pruebas
- Como la nueva implementación no era determinista, los resultados de las pruebas pasaban o fallaban aleatoriamente
- Claude Code no lo detectó e intentó resolverlo relajando las condiciones de las pruebas
- En cambio, debería haber propuesto una refactorización para que la simulación fuera determinista
- Después de modificar el método de muestreo aleatorio de una simulación Monte Carlo, se le pidió a Claude Code que actualizara las pruebas
Usar tipos estáticos (Use Static Types)
- El debate entre sistemas de tipos dinámicos y estáticos trata del equilibrio entre la facilidad para prototipar y el mantenimiento a largo plazo
- Como los LLM pueden encargarse del código boilerplate y de la refactorización, disminuye la carga de elegir un lenguaje cómodo para prototipar
- Por lo tanto, es posible elegir lenguajes más favorables para el mantenimiento a largo plazo
- Estrategia para corregir errores de tipos
- Configurar el agente para que el LLM detecte los errores de tipos que aparezcan después de hacer cambios
- Esto también facilita identificar otros archivos que necesiten correcciones
- Puntos a tener en cuenta
- En Python y JavaScript se usan sistemas de tipos graduales → es necesario configurar el verificador de tipos de forma estricta
- En principio, Rust es adecuado para los LLM, pero por ahora no se genera tan bien como Python o JavaScript
Pruebas de caja negra (Black Box Testing)
- Las pruebas de caja negra evalúan la funcionalidad de un componente sin conocer su estructura interna
- Como los archivos de implementación están incluidos en el contexto del LLM, le resulta difícil respetar el principio de las pruebas de caja negra
- En el caso de Sonnet 3.7 (usando Cursor), hay una tendencia a mantener la consistencia del código → intenta eliminar duplicación en los archivos de prueba
- Pero en las pruebas de caja negra, mantener esa duplicación ayuda a detectar bugs
- Solución ideal
- El LLM debería poder enmascarar o resumir los detalles de implementación de los archivos que cargó
- El arquitecto debería definir con claridad los límites del ocultamiento de información
-
Example
- Al corregir una prueba fallida, Sonnet 3.7 modificó una constante hardcodeada para calcularla a partir del algoritmo original
- En realidad, la constante original debía mantenerse tal cual
- Al corregir una prueba fallida, Sonnet 3.7 modificó una constante hardcodeada para calcularla a partir del algoritmo original
Usar servidores MCP (Use MCP Servers)
- Los servidores Model Context Protocol (MCP) proporcionan una interfaz estándar para que el LLM interactúe con el entorno
- Los servidores MCP se usan ampliamente en el modo agente de Cursor y en Claude Code
- Sin un sistema RAG aparte, el LLM puede buscar y modificar los archivos necesarios mediante llamadas MCP
- El modelo puede ejecutar pruebas o compilaciones y corregir problemas de inmediato
- Consideraciones al crear un servidor MCP personalizado
- En Cursor, al activar el modo YOLO, se pueden agregar comandos de shell a las reglas de Cursor
- Es riesgoso → un comando de shell arbitrario puede dañar el entorno
- Alternativa: crear un servidor MCP personalizado que exponga solo comandos específicos → mejora la seguridad
- Aun así, a marzo de 2025, la configuración de servidores MCP por proyecto en Cursor sigue siendo limitada
- En Cursor, al activar el modo YOLO, se pueden agregar comandos de shell a las reglas de Cursor
-
Example
- Sonnet 3.7 usó MCP para ejecutar la verificación de tipos y corregir errores en un proyecto TypeScript
- El proceso se hizo automáticamente, sin necesidad de copiar y pegar manualmente la salida de la terminal
- Sin embargo, a veces infiere un comando incorrecto, como
npm run typecheck
- Sonnet 3.7 usó MCP para ejecutar la verificación de tipos y corregir errores en un proyecto TypeScript
Refactorización preparatoria (Preparatory Refactoring)
- La refactorización preparatoria es una estrategia en la que primero se refactoriza para facilitar el trabajo de cambio posterior
- Como refactorizar es una tarea de preservación de significado, su evaluación es más sencilla que la de un cambio real
- Primero refactorizar y luego hacer el cambio → facilita la revisión y la corrección de errores
- Problemas actuales de los LLM
- Tienden a intentar resolver todo de una vez sin hacer una refactorización previa
- Incluso realizan tareas de ordenamiento innecesarias → puede haber refactorización excesiva
- Cursor Sonnet 3.7 tiene baja precisión al ejecutar instrucciones → puede hacer refactorizaciones no relacionadas
- Cómo mejorarlo
- Hace falta indicar explícitamente que el LLM solo modifique el código durante la etapa de refactorización previa al cambio
- Definir con claridad el alcance del código que el LLM puede editar → evita cambios innecesarios
-
Example
- Se le indicó al LLM corregir un error de importación → después de corregirlo, agregó anotaciones de tipo a funciones lambda
- Algunas anotaciones se agregaron incorrectamente, lo que provocó un bucle del agente
- Se le indicó al LLM corregir un error de importación → después de corregirlo, agregó anotaciones de tipo a funciones lambda
Mise en place (Mise en Place)
- En cocina, la mise en place consiste en dejar organizados todos los ingredientes y herramientas antes de trabajar
- En los LLM, la mise en place consiste en configurar por completo las reglas, el MCP y el entorno de desarrollo antes de empezar
- Sonnet 3.7 es débil para arreglar entornos rotos
- Intenta resolver problemas copiando y pegando comandos de StackOverflow → riesgo de dañar el entorno
- Hay que configurar bien el entorno antes de trabajar para evitar que Sonnet caiga en un bucle de depuración
-
Example
- Por un problema con
npm link, VSCode no reconocía imports de otro proyecto local- Cursor se obsesionó con resolver este problema mientras corregía lint y pruebas, pero no detectó que era necesario ejecutar
npm unlink
- Cursor se obsesionó con resolver este problema mientras corregía lint y pruebas, pero no detectó que era necesario ejecutar
- Por un problema con
Uso de herramientas sin estado (Stateless Tools)
- Las herramientas deben ejecutarse de forma independiente cada vez, sin guardar estado
- El shell depende del estado del directorio de trabajo actual → puede generar confusión por guardar estado
- Sonnet 3.7 no puede rastrear con precisión el estado del directorio de trabajo actual
- Es necesario configurar todo para que todos los comandos puedan ejecutarse desde el directorio raíz del proyecto
- Cómo mejorarlo
- Minimizar el uso de comandos de herramientas que requieran cambiar el estado
- Si el estado es indispensable, proporcionar continuamente el estado actual al modelo para mantener la consistencia
-
Ejemplo
- Si un proyecto de TypeScript está compuesto por tres módulos: common, backend y frontend
- Si Cursor se ejecuta desde la raíz, necesita hacer
cdal directorio adecuado → se genera confusión con los directorios - El problema se resolvió al abrir cada módulo como un espacio de trabajo independiente
- Si Cursor se ejecuta desde la raíz, necesita hacer
- Si un proyecto de TypeScript está compuesto por tres módulos: common, backend y frontend
Respetar la especificación (Respect the Spec)
- Al modificar un sistema, hay que distinguir claramente qué partes pueden cambiarse y cuáles no
- Al modificar una API pública, es necesario evitar romper la compatibilidad hacia atrás
- Al integrarse con sistemas externos, hay que ajustarse a las API que realmente existen → no se pueden cambiar a voluntad
- Si una prueba falla, no se debe borrar → hay que identificar la causa y corregirla
- Problemas de los LLM
- Tienen alta probabilidad de violar la especificación → borran pruebas, cambian API, etc. con libertad
- Respetar la especificación parece obvio, pero quizá haya que explicitarlo en el prompt
- Algunos límites solo pueden detectarse mediante revisión de código
-
Ejemplo
- Sonnet, tras no lograr corregir una prueba, reemplazó su contenido por
assert True - Una función pública devolvía un dict con la clave
pass→ Sonnet intentó cambiarla apass_(por ser palabra reservada)
- Sonnet, tras no lograr corregir una prueba, reemplazó su contenido por
Método bulldozer (Bulldozer Method)
- El método bulldozer es una estrategia para resolver problemas mediante trabajo repetitivo simple y acelerar con el efecto de aprendizaje
- Programar con IA es, en esencia, muy bueno para el trabajo repetitivo → si se usan suficientes tokens, se pueden hacer refactorizaciones masivas
- Incluso problemas que una persona abandona por pensar que “es demasiado trabajo” pueden ser resueltos por un LLM
- Aun así, como un LLM puede repetir la misma tarea, es necesario revisar qué está haciendo realmente
-
Ejemplo
- Al modificar una función central en Haskell o Rust, puede requerirse una refactorización amplia
- El LLM puede automatizar el proceso de leer errores de compilación → corregir → volver a compilar
- Al modificar valores hardcodeados en pruebas, el LLM puede volver a ejecutar las pruebas y corregirlas automáticamente
- Al modificar una función central en Haskell o Rust, puede requerirse una refactorización amplia
Memento
- Los LLM no pueden recordar el estado → en cada tarea deben volver a entender la base de código desde cero
- Trabajan solo con el prompt, el contexto explícito/implícito y los archivos que el modelo cargó en modo agente
- Como reinterpretan la base de código en cada tarea, si la configuración inicial falla, la probabilidad de mal funcionamiento es alta
- Estrategias para evitar problemas
- Proporcionar claramente la documentación que el LLM puede consultar
- Configurar el entorno para que el modelo pueda encontrar fácilmente la información que necesita
- Dar el contexto general del proyecto antes de pedir cambios importantes
-
Ejemplo
- Se le pidió a Sonnet 3.7 que planificara pruebas end-to-end para un proyecto existente
- Entendió erróneamente que el objetivo completo del proyecto eran las pruebas → modificó el README para enfocarlo en pruebas
- Se le pidió a Sonnet 3.7 que planificara pruebas end-to-end para un proyecto existente
Requisitos, no soluciones (Requirements, not Solutions)
- Un error común en ingeniería de software es proponer una solución de inmediato sin definir claramente los requisitos
- Si el espacio del problema está suficientemente acotado, definir bien los requisitos puede determinar la solución automáticamente
- Si los requisitos no están claros, pueden surgir discusiones innecesarias sobre la solución
- Problemas de los LLM
- Los LLM no conocen los requisitos → generan la respuesta más probable según los patrones con los que fueron entrenados
- Si se les pide una tarea sin requisitos claros, pueden producir resultados fuera de lugar
- Se puede corregir una mala interpretación modificando el prompt → pero si la interpretación incorrecta queda en el contexto, corregirla se vuelve difícil
- Cómo mejorarlo
- Si se necesita una solución de una forma específica, hay que indicarlo explícitamente
- Como el LLM sigue las instrucciones con precisión, si se le indica una forma equivocada, puede producir resultados inexactos
-
Ejemplo
- Si se le pide a Sonnet que genere una visualización, por defecto crea un SVG
- Si se especifica “interactiva”, genera una aplicación basada en React → una sola palabra produce una gran diferencia
- Si se le pide a Sonnet que genere una visualización, por defecto crea un SVG
Depuración científica (Scientific Debugging)
- Hay dos formas de corregir bugs
- Intentar cambios al azar y dejarlo a la suerte
- Analizar lógicamente cómo funciona el sistema para identificar la causa de la discrepancia entre el estado real y el esperado
- La depuración científica (análisis lógico) es un mejor enfoque a largo plazo
- Problemas de los LLM
- Los LLM carecen de capacidad de razonamiento suficiente, por lo que les cuesta aplicar un enfoque científico
- “Adivinan la respuesta correcta” y enseguida intentan corregir → si falla, repiten cambios aleatorios (bucle de agente)
- Para depuración, los modelos de razonamiento como Grok 3 y DeepSeek-R1 son más adecuados
- Cómo mejorarlo
- Si se le ordena al modelo analizar la causa, o si el usuario le proporciona la causa, aumenta la tasa de éxito de la corrección
- Si se le indica con precisión la causa del problema, el modelo puede proponer una mejor solución
-
Ejemplo
- Sonnet 3.7 encontró un error al instalar paquetes en un entorno base de uv sin pip
- Como no logró identificar la causa, repitió intentos aleatorios → desperdicio de tokens y depuración fallida
- Sonnet 3.7 encontró un error al instalar paquetes en un entorno base de uv sin pip
Usar formateo automático de código (Use Automatic Code Formatting)
- Las herramientas de formateo automático de código (
gofmt,rustfmt,black, etc.) son útiles para mantener un estilo de código consistente- Los LLM son débiles para seguir reglas mecánicas (por ejemplo, no dejar espacios en líneas en blanco, límite de 78 caracteres por línea, etc.)
- Conviene dejar el formateo en manos de herramientas y hacer que el LLM se concentre en tareas complejas
- El mismo principio aplica para corregir lint
- Se recomienda usar lint con autocorrección cuando sea posible
- Hay que concentrar los recursos del LLM en resolver problemas complejos
La cola mueve al perro (The Tail Wagging the Dog)
- Se refiere a una situación donde un problema menor termina condicionando uno más importante
- Puede ocurrir que uno se obsesione con resolver un problema de bajo nivel y olvide el objetivo general de escribir el código
- Los LLM incluyen toda la información en el contexto de la sesión de chat → les cuesta juzgar la importancia relativa
- Cómo mejorarlo
- Dar un prompt claro desde el inicio → orienta al LLM para que se concentre en el trabajo importante
- Claude Code puede realizar tareas específicas mediante subagentes para evitar contaminar el contexto global
-
Ejemplo
- Si se le pide a un LLM que piense en cómo hacer una tarea específica, puede terminar intentando ejecutarla de verdad en lugar de solo pensarla
Mantener los archivos pequeños (Keep Files Small)
- El debate sobre el tamaño de los archivos de código lleva mucho tiempo
- Aplicar el principio de responsabilidad única (una clase por archivo) vs. permitir archivos grandes según la situación
- Si el tamaño del archivo es demasiado grande, pueden surgir problemas cuando un sistema RAG carga contexto por archivo
- En IDE como Cursor puede fallar la aplicación de parches → incluso si funciona, tarda bastante en aplicarlos
- Ejemplo: en Cursor 0.45.17, aplicar 55 modificaciones a un archivo de 64 KB tomó bastante tiempo
- A Sonnet 3.7 le cuesta modificar archivos de más de 128 KB (límite de ventana de contexto de 200K tokens)
- Cómo mejorarlo
- Mantener los archivos pequeños → el LLM puede encargarse automáticamente de cosas como los import
-
Ejemplo
- Sonnet 3.7 intentó mover una pequeña clase de prueba dentro de un archivo Python de 471 KB
- La modificación era pequeña, pero el parche no pudo aplicarse en el patcher de Cursor
- Sonnet 3.7 intentó mover una pequeña clase de prueba dentro de un archivo Python de 471 KB
Reconocer los límites (Know Your Limits)
- En situaciones de falta de herramientas o límites de capacidad, es necesario reconocer el problema y pedir ayuda
- Sonnet 3.7 es débil para reconocer sus propios límites
- Si se le da un prompt claro, puede reconocer sus límites → es necesario configurar advertencias por alucinaciones en temas específicos
- Problemas
- Sonnet 3.7 cree erróneamente que puede ejecutar comandos de shell
- Cuando no hay comandos de shell, intenta generar scripts de shell aleatorios → riesgo de dañar el entorno
- Puede decir "voy a ejecutar X" y luego generar una llamada para algo completamente distinto, Y
- Sonnet 3.7 cree erróneamente que puede ejecutar comandos de shell
- Cómo mejorarlo
- Modificar el prompt o proporcionar una herramienta dedicada que solo haga la tarea deseada
- Si se proporciona una herramienta específica, se pueden evitar llamadas de shell fuera de lugar
- Modificar el prompt o proporcionar una herramienta dedicada que solo haga la tarea deseada
-
Example
- Sonnet 3.7 intentó generar un script de shell equivocado al dar permisos de ejecución a un archivo
- Después de que ocurrió un error de comando, repitió intentos de corrección equivocados
- Sonnet 3.7 intentó generar un script de shell equivocado al dar permisos de ejecución a un archivo
Leer la documentación (Read the Docs)
- Al aprender un framework o una librería nueva, se pueden hacer tareas simples modificando el código del tutorial
- Pero al final es necesario leer la documentación de principio a fin para entender cómo funciona todo
- Ventajas de los LLM
- Los frameworks populares suelen estar incluidos en el preentrenamiento, así que recuerdan la mayoría de los usos
- Pero con herramientas de nicho o herramientas aparecidas después del cutoff de conocimiento, pueden producir alucinaciones
- Sonnet no soporta búsqueda web → hay que proporcionarle la documentación manualmente
- En Cursor, si se da una URL, puede incluirse automáticamente en el contexto
-
Example
- Al pedirle al LLM que escribiera YAML para llamadas de funciones de Python, generó una configuración incorrecta
- Después de proporcionarle la documentación, logró corregirlo y mejorar el formato de salida
- Al pedirle al LLM que escribiera YAML para llamadas de funciones de Python, generó una configuración incorrecta
La cultura se come a la estrategia (Culture Eats Strategy)
- La cultura del equipo influye de forma decisiva en la capacidad de ejecutar la estrategia
- Los LLM generan código según el estilo preentrenado y la ventana de contexto
- Prefieren las librerías o estilos que aparecen con frecuencia en el contexto
- Si no se especifica nada, aplican el estilo predeterminado
- Estrategias para modificar el estilo del LLM
- Modificar las reglas de Cursor (cambiar el prompt)
- Refactorizar el estilo del código existente hacia la forma deseada → influye en la predicción del siguiente token
- El tamaño del codebase influye más que el prompt → modificar el codebase es la solución de fondo
-
Example
- Sonnet 3.7 prefiere código síncrono en Python
- Para lograr que generara código asíncrono, se portó la mayor parte del código existente a
asyncy funcionó
- Para lograr que generara código asíncrono, se portó la mayor parte del código existente a
- Sonnet 3.7 prefiere código síncrono en Python
Walking Skeleton
- Walking Skeleton es una estrategia de implementación mínima de un sistema end-to-end
- Aunque no sea perfecto, primero se hace que todo el sistema funcione y luego se mejoran los detalles
- En la era de programar con LLM, es más fácil construir rápido el sistema completo
- Cuando el sistema funciona, el siguiente paso se vuelve claro → es importante llegar rápido a un estado funcional
- Como los LLM no pueden usar directamente el código que escribieron, es importante asegurar un estado funcional
Regla de tres (Rule of Three)
- Se permite copiar el mismo código hasta dos veces; al tercer duplicado, hace falta refactorizar
- Es una versión mejorada del principio DRY (Don't Repeat Yourself)
- Aclara cuándo eliminar duplicación → se refactoriza en la tercera copia
- Problemas de los LLM
- Los LLM tienden a generar código duplicado
- Si se les pide una modificación sin un prompt claro, vuelven a copiar todo el código y aplican el cambio
- La eliminación de duplicación solo ocurre si el modelo decide hacerlo por su cuenta → hace falta una instrucción explícita
- Cómo mejorarlo
- Es necesario indicar explícitamente que elimine la duplicación
- Si ya hay mucha duplicación en el código existente, el modelo puede seguir generándola
-
Example
- Al pedirle al LLM que escribiera código de pruebas, la misma lógica se duplicó en varias pruebas
- Se resolvió después de indicarle explícitamente que creara métodos auxiliares
- modo agente
- Al pedirle al LLM que escribiera código de pruebas, la misma lógica se duplicó en varias pruebas
1 comentarios
Opiniones de Hacker News
Los LLM cometen errores de una forma distinta a los humanos, y es difícil detectarlos
Cuando un LLM no conoce los requisitos, completa con la respuesta más probable a partir de sus datos de entrenamiento
En ingeniería de software, es importante aclarar bien los requisitos
Los LLM tienen una capacidad de programación del nivel de un "programador junior muy inteligente"
Los LLM intentan responder demasiado
A medida que aumentan las publicaciones del blog, hace falta organizarlas
Consejos útiles al programar con LLM
Los LLM son débiles en cálculo y aritmética
Puntos a considerar junto con programadores humanos
Caso en el que tres LLM encontraron un "bug" que no existía