12 pontos por GN⁺ 2025-03-20 | 1 comentários | Compartilhar no WhatsApp

Pontos cegos dos LLMs descobertos durante a programação com IA. Com base no Claude Sonnet

  1. Stop Digging → dificuldade para mudar de direção quando surge um problema
  2. Use Static Types → necessidade de configurar tipagem estática
  3. Black Box Testing → dependência excessiva de detalhes de implementação
  4. Use MCP Servers → problemas de configuração e segurança de servidores MCP
  5. Preparatory Refactoring → pode realizar refatorações desnecessárias
  6. Mise en Place → problemas surgem quando a configuração do ambiente falha
  7. Stateless Tools → problemas com ferramentas que dependem de estado
  8. Respect the Spec → alta probabilidade de violar a especificação
  9. Bulldozer Method → execução excessiva de tarefas repetitivas
  10. Memento → problemas por falta de compreensão de contexto
  11. Requirements, not Solutions → necessidade de esclarecer os requisitos
  12. Scientific Debugging → problemas ao corrigir com base em suposições
  13. Use Automatic Code Formatting → inconsistências no estilo do código
  14. The Tail Wagging the Dog → fixação em problemas menores em vez do trabalho importante
  15. Keep Files Small → problemas ao modificar arquivos grandes
  16. Know Your Limits → o modelo reconhece mal suas próprias limitações
  17. Read the Docs → erros em informações fora do conhecimento aprendido
  18. Culture Eats Strategy → falta de consistência no estilo de código
  19. Walking Skeleton → é preciso priorizar o funcionamento do sistema mínimo
  20. Rule of Three → necessidade de refatoração quando há duplicação de código

Não se aprofundar no problema (Stop Digging)

  • Os LLMs atuais têm pouca capacidade de interromper por conta própria uma tarefa e mudar de direção quando surge um problema durante o trabalho
    • Exemplo: ao implementar a funcionalidade X, mesmo que apareça a necessidade de implementar antes a funcionalidade Y, o LLM tenta concluir a tarefa original (X)
    • Isso é uma vantagem no sentido de seguir fielmente as instruções, mas ele tem dificuldade para perceber o problema e mudar de rumo
  • Estratégias para evitar o problema
    • Na etapa de planejamento, usar um modelo de raciocínio para definir prioridades e dependências das tarefas
    • Um LLM agente como o Sonnet lê os arquivos e monta um plano de trabalho → consegue identificar tarefas necessárias mesmo sem instruções explícitas do usuário
  • Idealmente, o LLM deveria conseguir reconhecer o problema e pedir confirmação ao usuário
    • Porém, isso consome contexto, então talvez seja melhor que um LLM de supervisão separado cuide disso
  • Example

    • Após alterar o método de amostragem aleatória de uma simulação de Monte Carlo, foi pedido ao Claude Code para corrigir os testes
      • Como a nova implementação era não determinística, os resultados dos testes passavam ou falhavam aleatoriamente
      • O Claude Code não percebeu isso e tentou resolver o problema afrouxando as condições dos testes
      • Em vez disso, deveria ter proposto uma refatoração para tornar a simulação determinística

Usar tipos estáticos (Use Static Types)

  • O debate entre sistemas de tipagem dinâmica e estática é uma questão de equilíbrio entre facilidade de prototipagem e manutenção de longo prazo
    • Como os LLMs conseguem lidar com código boilerplate e refatoração, diminui a pressão para escolher linguagens apenas por serem melhores para prototipagem
    • Portanto, torna-se possível escolher linguagens mais favoráveis à manutenção de longo prazo
  • Estratégia para corrigir erros de tipo
    • Configurar o agente para que o LLM perceba erros de tipo surgidos após as alterações
    • Isso também facilita identificar outros arquivos que precisam ser corrigidos
  • Pontos de atenção
    • No caso de Python e JavaScript, como usam sistemas de tipagem gradual, é preciso configurar o verificador de tipos de forma rigorosa
    • Rust é, em princípio, adequado para LLMs, mas atualmente não é gerado tão bem quanto Python/JavaScript

Testes de caixa-preta (Black Box Testing)

  • Testes de caixa-preta verificam o funcionamento de um componente sem conhecer sua estrutura interna
    • Como os arquivos de implementação entram no contexto do LLM, é difícil seguir o princípio de testes de caixa-preta
    • No caso do Sonnet 3.7 (usando Cursor), há uma tendência de tentar manter a consistência do código → ele tenta remover duplicação nos arquivos de teste
      • Porém, em testes de caixa-preta, manter a duplicação pode ser melhor para detectar bugs
  • Solução ideal
    • O LLM deveria ser capaz de mascarar ou resumir detalhes de implementação dos arquivos carregados
    • O arquiteto deveria definir com clareza os limites de ocultação de informação
  • Example

    • Ao corrigir um teste que falhava, o Sonnet 3.7 alterou constantes hardcoded para serem calculadas com base no algoritmo original
      • As constantes originais deveriam ter sido mantidas

Usar servidores MCP (Use MCP Servers)

  • Servidores Model Context Protocol (MCP) fornecem uma interface padrão para o LLM interagir com o ambiente
    • O modo agente do Cursor e o Claude Code usam servidores MCP de forma ampla
    • Sem um sistema RAG separado, o LLM pode localizar e modificar os arquivos necessários por meio de chamadas MCP
    • Após executar testes ou builds, o modelo pode corrigir imediatamente os problemas
  • Considerações ao criar um servidor MCP personalizado
    • Com o modo YOLO ativado no Cursor, é possível adicionar comandos de shell às regras do Cursor
      • Isso é perigoso → comandos de shell arbitrários podem danificar o ambiente
    • Alternativa: criar um servidor MCP personalizado que exponha apenas comandos específicos → maior segurança
      • Porém, em março de 2025, a configuração de servidores MCP por projeto no Cursor ainda é insuficiente
  • Example

    • O Sonnet 3.7 usou MCP para verificar tipos e corrigir erros em um projeto TypeScript
      • O processo foi automatizado, sem necessidade de copiar e colar manualmente a saída do terminal
      • No entanto, pode acontecer de ele inferir um comando incorreto (npm run typecheck)

Refatoração preparatória (Preparatory Refactoring)

  • Refatoração preparatória é a estratégia de refatorar antes da alteração principal para facilitar o trabalho
    • Como refatoração é uma operação de preservação de significado, ela é mais fácil de avaliar do que a mudança em si
    • Primeiro refatorar e depois fazer a alteração → fica mais fácil revisar e corrigir erros
  • Problemas dos LLMs atuais
    • Tendência a tentar resolver tudo de uma vez, sem fazer refatoração prévia
    • Também podem fazer limpezas desnecessárias → risco de refatoração excessiva
    • O Cursor Sonnet 3.7 tem baixa precisão no cumprimento de instruções → pode gerar refatorações irrelevantes
  • Formas de melhorar
    • É preciso instruir explicitamente o LLM para modificar o código apenas na etapa de refatoração antes da mudança
    • Definir claramente o escopo do código que o LLM pode editar → evita alterações desnecessárias
  • Example

    • Foi pedido ao LLM para corrigir um erro de import → após a correção, ele adicionou anotações de tipo em funções lambda
      • Algumas dessas anotações foram adicionadas de forma incorreta, causando um loop no agente

Mise en Place

  • Na culinária, mise en place é deixar todos os ingredientes e ferramentas organizados antes do trabalho
  • Em LLMs, mise en place significa configurar completamente as regras, o MCP e o ambiente de desenvolvimento antes da tarefa
    • O Sonnet 3.7 é fraco para consertar ambientes quebrados
    • Tenta resolver problemas copiando e colando comandos do StackOverflow → risco de danificar o ambiente
    • É preciso configurar corretamente o ambiente antes do trabalho para evitar que o Sonnet entre em loop de depuração
  • Example

    • Por causa de um problema com npm link, o VSCode não reconhecia imports de outro projeto local
      • O Cursor ficou obcecado em resolver isso durante a correção de lint e testes, mas não percebeu a necessidade de executar npm unlink

Uso de ferramentas sem estado (Stateless Tools)

  • As ferramentas devem ser executadas de forma independente a cada vez, sem armazenar estado
    • O shell depende do estado do diretório de trabalho atual → isso pode causar confusão devido ao armazenamento de estado
    • O Sonnet 3.7 não consegue rastrear com precisão o estado atual do diretório de trabalho
    • É necessário configurar tudo para que todos os comandos possam ser executados a partir do diretório raiz do projeto
  • Formas de melhorar
    • Minimizar o uso de comandos de ferramenta que exigem mudança de estado
    • Quando o estado for indispensável, fornecer continuamente o estado atual ao modelo para manter a consistência
  • Exemplo

    • Quando um projeto TypeScript é composto por três módulos: common, backend e frontend
      • Se o Cursor for executado a partir da raiz, é necessário usar cd para ir ao diretório adequado → isso gera confusão entre diretórios
      • O problema foi resolvido ao abrir cada módulo como um workspace separado

Respeitar a especificação (Respect the Spec)

  • Ao alterar um sistema, é preciso distinguir com clareza o que pode e o que não pode ser modificado
    • Ao alterar uma API pública, é necessário evitar quebrar a compatibilidade retroativa
    • Ao integrar com sistemas externos, é preciso seguir as APIs que realmente existem → não dá para alterá-las como quiser
    • Se um teste falhar, não se deve apagar o teste → é preciso identificar a causa e corrigir o problema
  • Problemas dos LLMs
    • Há grande chance de violarem a especificação → apagam testes, alteram APIs etc. com liberdade
    • Respeitar a especificação parece algo óbvio, mas talvez seja preciso explicitar isso no prompt
    • Alguns limites só podem ser percebidos por meio de code review
  • Exemplo

    • Depois de falhar ao ajustar um teste, o Sonnet substituiu o conteúdo do teste por assert True
    • Uma função pública retornava um dict contendo a chave pass → o Sonnet tentou mudar para pass_ (por causa da palavra reservada)

Método bulldozer (Bulldozer Method)

  • O método bulldozer é uma estratégia que resolve problemas por meio de trabalho repetitivo simples e ganha velocidade pelo efeito de aprendizado
    • Programação com IA é, por natureza, forte em trabalho repetitivo → com tokens suficientes, grandes refatorações são possíveis
    • Problemas que humanos abandonariam por acharem que “dão trabalho demais” podem ser resolvidos por LLMs
    • No entanto, como LLMs podem repetir a mesma tarefa, é preciso revisar o que de fato estão fazendo
  • Exemplo

    • Em Haskell ou Rust, alterar uma função central pode exigir uma refatoração extensa
      • O LLM pode automatizar o processo de ler erros de compilação → corrigir → compilar novamente
    • Ao ajustar valores hardcoded em testes, o LLM pode reexecutar os testes e aplicar correções automaticamente

Memento

  • LLMs não conseguem se lembrar do estado → precisam entender o codebase do zero a cada tarefa
    • O trabalho é feito apenas com o prompt, o contexto explícito/implícito e os arquivos que o modelo carregou no modo agente
    • Como o codebase é reinterpretado a cada tarefa, falhas na configuração inicial aumentam a chance de comportamento incorreto
  • Estratégias para evitar problemas
    • Fornecer com clareza os documentos que o LLM pode consultar
    • Organizar o ambiente para que o modelo encontre facilmente as informações necessárias
    • Fornecer o contexto geral do projeto antes de pedir mudanças importantes
  • Exemplo

    • Foi pedido ao Sonnet 3.7 para elaborar um plano de testes end-to-end para um projeto existente
      • Ele entendeu por engano que o objetivo inteiro do projeto era teste → e alterou o README para focar em testes

Requisitos, não soluções (Requirements, not Solutions)

  • Um erro comum em engenharia de software é propor uma solução imediatamente sem definir claramente os requisitos
    • Se o espaço do problema estiver suficientemente delimitado, só definir bem os requisitos já pode determinar automaticamente a solução
    • Quando os requisitos não estão claros, podem surgir discussões desnecessárias sobre a solução
  • Problemas dos LLMs
    • LLMs não conhecem os requisitos → geram a resposta mais provável com base em padrões aprendidos
    • Pedidos sem requisitos claros podem produzir resultados completamente fora do esperado
    • É possível corrigir interpretações erradas ajustando o prompt → mas, se a interpretação errada permanecer no contexto, corrigi-la fica difícil
  • Formas de melhorar
    • Se for necessário resolver de uma forma específica, isso deve ser instruído explicitamente
    • Como LLMs seguem instruções com precisão, orientar da forma errada pode gerar resultados imprecisos
  • Exemplo

    • Ao pedir ao Sonnet para criar uma visualização, ele gera SVG por padrão
      • Ao explicitar que ela deve ser “interativa”, ele cria um aplicativo baseado em React → uma única palavra pode fazer grande diferença

Depuração científica (Scientific Debugging)

  • Há duas formas de corrigir bugs
    • Tentar mudanças aleatórias e contar com a sorte
    • Analisar logicamente como o sistema funciona para identificar a causa da divergência entre o estado real e o estado esperado
    • A depuração científica (análise lógica) é a melhor abordagem no longo prazo
  • Problemas dos LLMs
    • LLMs têm limitações de raciocínio, o que dificulta uma abordagem científica
    • Eles “chutam a resposta certa” e tentam corrigir imediatamente → quando falham, repetem mudanças aleatórias (agent loop)
    • Para depuração, modelos de raciocínio como Grok 3 e DeepSeek-R1 são mais adequados
  • Formas de melhorar
    • Mandar o modelo analisar a causa, ou fornecer a causa diretamente, aumenta a taxa de sucesso da correção
    • Se você informar com precisão a causa do problema, o modelo pode propor uma solução melhor
  • Exemplo

    • O Sonnet 3.7 encontrou erro de instalação de pacote em um ambiente base do uv sem pip
      • Como não conseguiu identificar a causa, repetiu tentativas aleatórias → desperdiçando tokens e falhando na depuração

Usar formatação automática de código (Use Automatic Code Formatting)

  • Ferramentas automáticas de formatação de código (gofmt, rustfmt, black etc.) são úteis para manter um estilo consistente
    • LLMs são fracos em seguir regras mecânicas (por exemplo: sem espaços em linhas em branco, limite de 78 caracteres por linha etc.)
    • A formatação deve ficar a cargo das ferramentas, para que o LLM se concentre em tarefas mais complexas
  • O mesmo princípio se aplica à correção de lint
    • É recomendável usar lints com correção automática
    • Os recursos do LLM devem ser concentrados em resolver problemas complexos

O rabo abanando o cachorro (The Tail Wagging the Dog)

  • Refere-se a situações em que um problema trivial passa a dominar um problema mais importante
    • Pode acontecer de alguém ficar obcecado em resolver questões de baixo nível e esquecer o objetivo geral da escrita do código
    • LLMs incluem todas as informações no contexto da sessão de chat → isso dificulta avaliar o que é mais importante
  • Formas de melhorar
    • Fornecer um prompt claro desde o início → ajuda o LLM a se concentrar no trabalho mais importante
    • O Claude Code usa subagentes para executar tarefas específicas, evitando a contaminação do contexto global
  • Exemplo

    • Ao pedir que o LLM pense em como realizar uma tarefa específica, ele pode acabar tentando executar a tarefa de verdade em vez de apenas pensar nela

Manter os arquivos pequenos (Keep Files Small)

  • A discussão sobre o tamanho dos arquivos de código já dura há muito tempo
    • Aplicar o princípio da responsabilidade única (uma classe por arquivo) vs. permitir arquivos grandes dependendo do contexto
    • Se o arquivo for grande demais, pode haver problemas quando sistemas de RAG carregam contexto no nível do arquivo
    • Em IDEs como o Cursor, a aplicação de patches pode falhar → e, mesmo quando funciona, pode levar muito tempo
      • Exemplo: no Cursor 0.45.17, aplicar 55 modificações em um arquivo de 64 KB levou bastante tempo
    • O Sonnet 3.7 tem dificuldade para modificar arquivos acima de 128 KB (limite de janela de contexto de 200 mil tokens)
  • Formas de melhorar
    • Manter os arquivos pequenos → assim o LLM pode lidar automaticamente com imports e afins
  • Exemplo

    • O Sonnet 3.7 tentou mover uma pequena classe de teste em um arquivo Python de 471 KB
      • A alteração era pequena, mas o patcher do Cursor falhou ao aplicar a modificação

Reconhecer limitações (Know Your Limits)

  • É preciso reconhecer o problema e pedir ajuda quando faltam ferramentas ou há limites de capacidade
    • O Sonnet 3.7 é fraco em reconhecer as próprias limitações
    • Com prompts claros, ele consegue reconhecer limites → é necessário configurar avisos para alucinações em tópicos específicos
  • Problemas
    • O Sonnet 3.7 acredita incorretamente que consegue executar comandos de shell
      • Quando não há comandos de shell, ele tenta gerar scripts de shell aleatórios → risco de danificar o ambiente
      • Pode dizer “vou executar X” e depois gerar uma chamada para um Y completamente diferente
  • Formas de melhorar
    • Ajustar o prompt ou fornecer ferramentas dedicadas que façam apenas a tarefa desejada
      • Ao fornecer uma ferramenta específica, é possível evitar chamadas de shell sem sentido
  • Exemplo

    • O Sonnet 3.7 tentou gerar um script de shell sem sentido ao conceder permissão de execução a um arquivo
      • Após um erro no comando, repetiu várias tentativas de correção equivocadas

Ler a documentação (Read the Docs)

  • Ao aprender um novo framework ou biblioteca, é possível fazer tarefas simples modificando código de tutorial
    • Mas, no fim, é necessário ler a documentação do começo ao fim para entender como tudo funciona
  • Vantagens dos LLMs
    • Frameworks populares geralmente já fizeram parte do treinamento, então eles se lembram da maior parte do uso
    • Porém, em ferramentas de nicho ou lançadas depois do corte de conhecimento, podem ocorrer alucinações
    • O Sonnet não oferece suporte a busca na web → é preciso fornecer a documentação manualmente
      • No Cursor, ao fornecer uma URL, ela pode ser incluída automaticamente no contexto
  • Exemplo

    • Ao pedir ao LLM para escrever YAML para chamadas de função em Python, ele gerou uma configuração incorreta
      • Depois de receber a documentação, conseguiu corrigir e melhorar o formato de saída

A cultura vence a estratégia (Culture Eats Strategy)

  • A cultura da equipe afeta de forma decisiva a capacidade de executar a estratégia
    • O LLM gera código com base no estilo previamente aprendido e na janela de contexto
    • Tende a preferir bibliotecas ou estilos que aparecem com frequência no contexto
      • Se nada for especificado, aplica o estilo padrão
  • Estratégias para ajustar o estilo do LLM
    • Alterar as regras do Cursor (mudar o prompt)
    • Refatorar o estilo do código existente para o formato desejado → isso influencia a previsão do próximo token
    • O tamanho do codebase tem mais influência que o prompt → modificar o codebase é a solução mais fundamental
  • Exemplo

    • O Sonnet 3.7 prefere código síncrono em Python
      • Para gerar código assíncrono, foi preciso portar a maior parte do código existente para async, e então deu certo

Esqueleto funcional (Walking Skeleton)

  • Walking Skeleton é uma estratégia de implementar um sistema mínimo de ponta a ponta
    • Mesmo sem estar perfeito, faz-se o sistema inteiro funcionar e depois os detalhes são aprimorados
    • Na era da programação com LLM, ficou mais fácil construir rapidamente o sistema completo
    • Quando o sistema funciona, o próximo passo fica claro → é importante chegar rapidamente a um estado funcional
    • Como o LLM não consegue usar diretamente o código que escreveu, garantir um estado funcional é importante

Regra dos três (Rule of Three)

  • Duplicar o mesmo código é aceitável até duas vezes; na terceira, é preciso refatorar
    • Uma versão aprimorada do princípio DRY (Don't Repeat Yourself)
    • Isso deixa claro o momento de eliminar duplicações → refatorar na terceira cópia
  • Problemas dos LLMs
    • LLMs tendem a gerar código duplicado
    • Ao pedir alterações sem um prompt específico, eles copiam o código inteiro de novo e fazem a modificação
    • A eliminação de duplicação só acontece se o modelo decidir isso por conta própria → é necessária uma instrução clara
  • Formas de melhorar
    • É preciso instruir explicitamente a remoção de duplicação
    • Se já houver muita duplicação no código existente, o modelo pode continuar gerando mais duplicação
  • Exemplo

    • Ao pedir ao LLM para escrever código de teste, a mesma lógica foi duplicada em vários testes
      • Isso foi resolvido após instruir explicitamente a criação de métodos auxiliares
      • modo agente

1 comentários

 
GN⁺ 2025-03-20
Comentários do Hacker News
  • LLMs cometem erros de forma diferente dos humanos, e isso é difícil de detectar

    • Temos muita experiência em identificar erros humanos, mas é difícil entender a forma de pensar dos LLMs
    • É difícil projetar sistemas para detectar erros de LLMs
  • Quando não conhecem os requisitos, os LLMs preenchem com a resposta mais provável a partir dos dados de treinamento

    • Para que a IA substitua programadores, o cliente precisa descrever exatamente o que quer
  • Em engenharia de software, é importante deixar os requisitos claros

    • Quando os requisitos estão claros, a solução se define naturalmente
    • Ao aprender um novo framework ou biblioteca, é bom ler a documentação com atenção
    • Ao corrigir bugs, é importante revisar de forma sistemática as premissas do sistema
    • É melhor refatorar duplicação de código quando ela aparece pela terceira vez
  • LLMs têm capacidade de programação no nível de um "programador júnior muito inteligente"

    • Falta a eles a capacidade de enxergar o panorama geral e fazem apenas o que foi pedido
    • Espera-se que os modelos continuem melhorando
  • LLMs tentam responder demais

    • Se você não fornecer dados suficientes, eles geram respostas erradas
    • Seria bom se os LLMs pudessem dizer "preciso de mais informações"
  • Com o aumento do número de posts no blog, passou a ser necessário organizá-los

    • Ainda não foi encontrado um bom sistema de organização
  • Conselhos úteis para programar com LLMs

    • Há divergências de opinião sobre o uso de tipagem estática
    • Clojure produz resultados melhores do que Typescript
    • LLMs se adaptam melhor a uma abordagem centrada em funções
  • LLMs são fracos em cálculo e aritmética

    • Ao gerar código, é importante pegar os números corretos no lugar exato
    • Leva tempo depurar código gerado por LLMs
  • Pontos a considerar junto com programadores humanos

    • Gerentes de produto também deveriam prestar atenção
  • Caso em que três LLMs encontraram um "bug" que não existia

    • O código não era otimizado, mas não tinha bug
    • A distância entre os blocos de código era curta