12 puntos por GN⁺ 2025-03-20 | 1 comentarios | Compartir por WhatsApp

Los puntos ciegos de los LLM descubiertos al programar con IA. Basado en Claude Sonnet

  1. Stop Digging → Dificultad para cambiar de rumbo cuando surge un problema
  2. Use Static Types → Necesidad de configurar tipos estáticos
  3. Black Box Testing → Dependencia excesiva de los detalles de implementación
  4. Use MCP Servers → Problemas de configuración y seguridad de los servidores MCP
  5. Preparatory Refactoring → Puede realizar refactorizaciones innecesarias
  6. Mise en Place → Surgen problemas cuando falla la configuración del entorno
  7. Stateless Tools → Surgen problemas con herramientas que dependen del estado
  8. Respect the Spec → Alta posibilidad de violar la especificación
  9. Bulldozer Method → Realiza demasiado trabajo repetitivo
  10. Memento → Problemas por falta de comprensión del contexto
  11. Requirements, not Solutions → Es necesario aclarar los requisitos
  12. Scientific Debugging → Surgen problemas cuando corrige basándose en suposiciones
  13. Use Automatic Code Formatting → Aparecen inconsistencias en el estilo del código
  14. The Tail Wagging the Dog → Se obsesiona con problemas menores en lugar de tareas importantes
  15. Keep Files Small → Surgen problemas al modificar archivos grandes
  16. Know Your Limits → El modelo reconoce mal sus propios límites
  17. Read the Docs → Comete errores con información fuera de su conocimiento entrenado
  18. Culture Eats Strategy → Falta de consistencia en el estilo del código
  19. Walking Skeleton → Es necesario priorizar que funcione primero el sistema mínimo
  20. 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

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

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
  • 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

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

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

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 cd al directorio adecuado → se genera confusión con los directorios
      • El problema se resolvió al abrir cada módulo como un espacio de trabajo independiente

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 a pass_ (por ser palabra reservada)

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

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

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

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

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

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
  • 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
  • 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

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

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 async y funcionó

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

1 comentarios

 
GN⁺ 2025-03-20
Opiniones de Hacker News
  • Los LLM cometen errores de una forma distinta a los humanos, y es difícil detectarlos

    • Hay mucha experiencia detectando errores humanos, pero es difícil entender la forma de pensar de un LLM
    • Es difícil diseñar sistemas que detecten errores de los LLM
  • Cuando un LLM no conoce los requisitos, completa con la respuesta más probable a partir de sus datos de entrenamiento

    • Para que la IA pueda reemplazar a un programador, el cliente debe describir exactamente lo que quiere
  • En ingeniería de software, es importante aclarar bien los requisitos

    • Si los requisitos están claros, la solución se determina de forma natural
    • Al aprender un framework o librería nueva, conviene leer la documentación con cuidado
    • Al corregir bugs, es importante revisar de forma sistemática las suposiciones del sistema
    • Cuando hay duplicación de código por tercera vez, conviene refactorizar
  • Los LLM tienen una capacidad de programación del nivel de un "programador junior muy inteligente"

    • Les falta la capacidad de ver el panorama general y solo hacen lo que se les pide
    • Se espera que los modelos sigan mejorando
  • Los LLM intentan responder demasiado

    • Si no se les da suficiente información, generan respuestas incorrectas
    • Sería bueno que un LLM pudiera decir "necesito más información"
  • A medida que aumentan las publicaciones del blog, hace falta organizarlas

    • No se ha encontrado un buen sistema de organización
  • Consejos útiles al programar con LLM

    • Hay diferencias de opinión sobre el uso de tipado estático
    • Clojure da mejores resultados que Typescript
    • Los LLM se adaptan mejor a un enfoque centrado en funciones
  • Los LLM son débiles en cálculo y aritmética

    • Al generar código, es importante tomar los números desde la ubicación correcta
    • Depurar código generado por un LLM lleva tiempo
  • Puntos a considerar junto con programadores humanos

    • Los gerentes de producto también deberían prestar atención
  • Caso en el que tres LLM encontraron un "bug" que no existía

    • No era un código optimizado, pero no era un bug
    • La distancia entre bloques de código era corta