Ir para o conteúdo
Libellus
LibellusAs últimas descobertas científicas, explicadas para todosGratuito · Independente · Sem fins lucrativos · Traduzido em 5 idiomas
Close-up of two hands working on a small unmarked circuit board; one hand holds tweezers near a component while the other steadies a thin probe, lit by warm low afternoon light.

IAs que corrigem bugs aprendem a escrever seus próprios testes antes de confiar em um patch

12 min de leitura

Quando uma IA edita código para corrigir um bug, ela precisa de provas de que a alteração funciona. As suítes de teste oficiais podem confirmar o sucesso, mas não são o único sinal disponível enquanto o modelo ainda aprende a reparar. Uma alternativa é permitir que outro modelo escreva pequenos testes de regressão com base no relatório do problema e utilize os sinais de aprovação ou rejeição durante o processo de correção. O desafio consiste em tornar essas verificações geradas confiáveis o suficiente para orientar a correção, sem substituir a verificação final.

O redator de testes precisa gerar uma verificação que falhe no código com defeito e passe após o reparo correto

O redator de testes tem uma tarefa árdua: criar uma verificação que falhe no código com defeito e passe após o reparo correto. Isso, porém, não é suficiente. A verificação também precisa distinguir os reparos bons dos ruins. Se ela falhar apenas por um motivo errado, pode levar o processo de reparo em uma direção equivocada.

As regras de treinamento impõem rigor a esse ponto. Uma submissão sem nenhum teste válido recebe uma penalidade pequena de -0,2. Um pacote válido que não cumpra simultaneamente as duas condições, falhar na base com defeito e passar após a correção de referência, pontua 0. Superada essa barreira básica, a qualidade passa a importar. Se a verificação separa os patches corretos dos incorretos com precisão equilibrada inferior a 80%, ela ganha 0,2. Entre 80% e menos de 100%, o ganho é de 0,5. A separação perfeita vale 1,0. Em termos simples, um teste que apenas tropeça no bug não é recompensado. Ele precisa se comportar como uma sonda confiável.

O redator não pode espiar o histórico oculto da solução. Ele só pode inspecionar a árvore de trabalho atual e os materiais públicos. Pode usar git status, add -AN e um diff simples, mas não pode executar comandos que revelem commits ou ramos anteriores. O comportamento esperado deve derivar do texto da issue, da documentação pública, do código com defeito e dos testes existentes.

Antes de escrever as asserções, o redator precisa identificar o menor comportamento público em risco. Ele também deve considerar uma leitura alternativa plausível da issue antes de rejeitá-la. Uma falha limpa no código com defeito é necessária, mas não suficiente: ela apenas mostra discordância com o bug, não concordância com a correção pretendida.

O teste deve visar algo que os usuários possam ver, como uma chamada de API, um resultado na linha de comando, uma saída renderizada ou uma etapa de migração. Deve-se evitar inspecionar o estado interno privado só porque isso é fácil de fazer. Se a issue diz respeito à renderização, a verificação deve olhar para o resultado renderizado, não para uma bandeira interna.

Mensagens exatas, contagens e ordenações podem ser fixadas apenas quando a issue, os documentos ou um teste público existente as definem. Caso contrário, o redator usa a propriedade observável mais fraca que a issue garante e que o código com defeito viola.

O pacote deve formar um único grupo coeso de regressão para a issue. É possível adicionar vários testes e atualizar fixtures, mas não se pode incluir cobertura não relacionada. O comando de execução precisa nomear cada nó de teste declarado. Um comando em nível de arquivo ou de suíte é rejeitado, mesmo que falhe na base com defeito.

O redator também submete um contrato curto de comportamento. Nele, ele nomeia o ponto de entrada público, o gatilho que expõe o bug e a saída corrigida mínima que está sendo verificada. O documento registra evidências de apoio, hipóteses alternativas rejeitadas, literais tentadores deixados sem fixação e cada nó de teste exato exercitado.

A submissão é mecânica. Em seguida, cria um patch não vazio contendo apenas as alterações pretendidas nos testes de regressão. Esse patch é submetido em uma única chamada final do bash. Texto corrido sozinho não conta.

Um agente de reparo que emprega um teste fixo como retorno local

O agente de reparação inicia com uma tentativa integral de corrigir o defeito. Ele dispõe de até 200 turnos de trabalho antes que qualquer feedback seja liberado. Ao final desse período, terá produzido um patch completo. Esse patch ainda não é submetido à suíte oficial de testes. Em vez disso, é confrontado com uma única verificação congelada.

Uma verificação congelada consiste num teste automatizado único, mantido inalterado durante todo o processo de reparação. O agente não tem acesso ao seu código-fonte nem à sua origem. Recebe apenas um feedback limitado de execução: o código modificado fez aquela verificação fixa passar? Esse sinal local é barato. Ele indica se o patch atual lida com o comportamento alvo, mas não decide o sucesso final.

Se a verificação congelada falhar, o agente pode revisar seu trabalho. São permitidas até cinco rodadas de revisão, cada uma com 40 turnos. O agente edita sempre o mesmo patch cumulativo. Antes de cada nova verificação, o sistema reseta para o código original com defeito e aplica novamente o candidato completo. Isso evita cadeias confusas de pequenas edições. Um erro de sintaxe no código modificado conta como feedback útil de falha. Uma queda do ambiente não conta.

A recompensa durante o treinamento segue essa divisão: um patch ausente ou inválido recebe zero. Um patch utilizável que ainda falha na verificação congelada ganha uma pontuação baixa. Se ele passa na verificação congelada, mas falha na avaliação oficial, a pontuação é maior. Se ambos passam após a revisão, o agente ganha crédito total. Se ambos já passam na Rodada 0, recebe um bônus extra. Isso ensina o agente a usar a verificação local como uma bússola, não como o destino final.

Num cenário de avaliação específico, a revisão guiada por feedback elevou a taxa de resolução de 68,3% para 72,6%. Entre as 120 trajetórias de reparação que entraram em fase de revisão, o custo mediano foi de apenas 11 turnos extras. A média foi de 13. Quase metade terminou dentro de dez turnos. Nas execuções com testes gerados, um pacote de teste qualificado é reutilizado em três execuções de reparação e todas as suas revisões. A geração dos testes ocorre uma única vez por problema e fonte. O veredito final continua vindo de um avaliador oficial novo. Os resultados da porta local são registrados separadamente: eles guiam a revisão, mas não substituem a verificação completa.

O ganho do desempenho quando os testes passam por treinamento, e não quando são emprestados

Testes treinados elevam o desempenho do par muito mais do que os emprestados. A comparação mais clara parte de um agente de reparo não treinado para essa tarefa. Sem nenhum feedback de testes, ele resolve 61,2% dos problemas. Quando um gerador de testes também não treinado fornece as verificações, o desempenho cai para 57,3%, uma queda de 3,9 pontos. Os testes gerados são ruidosos o suficiente para desviar o processo de reparo.

Uma fonte externa mais forte ajuda mais. Testes do GPT-5.6-sol elevam o mesmo agente base para 65,3%, um ganho de 4,1 pontos em relação à ausência de feedback. As verificações oracle de falha-para-passo levam o resultado ainda mais longe, a 69,4%, um aumento de 8,2 pontos. Esses testes emprestados são úteis porque apontam para falhas reais, mas ainda não alcançam a melhor configuração treinada.

O maior salto ocorre quando ambos os papéis são treinados. Um agente de reparo treinado com feedback atinge 73,5% com testes do GPT-5.6-sol e 77,6% com verificações Oracle. Em comparação com sua linha de base sem testes treinados (64,6%), o resultado Oracle representa um ganho de 13 pontos. Essa diferença mostra que a qualidade dos testes importa tanto quanto a habilidade de reparo.

O redator de testes treinado também melhora o agente de reparo mais fraco. Substituir os testes do modelo base por testes treinados com aprendizado por reforço eleva a resolução de 57,3% para 64,1%, um aumento de 6,8 pontos. Para o agente de reparo treinado, a mesma substituição move o resultado de 64,6% para 72,6%, um ganho de 8,0 pontos. As duas melhorias não são magia independente. Elas apontam na mesma direção: verificações melhores tornam o reparo mais confiável.

O par totalmente treinado atinge 72,6% no SWE-bench Verified sem depender do feedback do GPT-5.6 ou Oracle durante a avaliação. Isso é 11,4 pontos acima do agente base original sem feedback, que fica em 61,2%. Se o feedback Oracle for adicionado ao par treinado, a resolução sobe para 77,6%. A lacuna restante de cinco pontos sugere que uma melhor qualidade dos testes e uma cobertura mais ampla ainda poderiam gerar mais progresso.

Dois casos reais em que testes gerados desviaram o reparo da solução errada

Dois casos reais ilustram como um teste gerado consegue desviar o reparo de uma correção equivocada. No primeiro, o Django precisava criar uma alternativa para bancos de dados sem suporte nativo a XOR. O XOR é uma operação lógica que verifica se um número ímpar de entradas é verdadeiro. A alternativa codificava a paridade incorretamente. Neste contexto, paridade significa o resultado par ou ímpar esperado do XOR. Um teste gerado cobriu três casos de paridade e falhou limpo no código base com defeito. Essa falha foi útil porque correspondia ao comportamento descrito na issue. O primeiro reparo acertou a paridade, mas reutilizou um conector XOR ao reconstruir o nó da alternativa. Isso causou uma RecursionError dentro do teste congelado. Após uma rodada de feedback e 23 turnos extras, o reparo substituiu aquele conector XOR reutilizado por AND. O patch final passou tanto no teste gerado quanto na verificação oficial nova. O teste não apenas indicou que o patch estava errado. Apontou para o subsistema quebrado.

Um segundo caso envolveu o Sphinx e a análise de parâmetros tipados. O Sphinx tratava uma vírgula dentro de dict(str, str) como um separador, o que produziu uma saída renderizada malformada. Um teste gerado usou um tipo de dicionário entre parênteses. Ele falhou no código base com defeito e passou após a correção de referência. O primeiro reparo editou um analisador Napoleon não relacionado. O teste congelado expôs o descompasso real na renderização e redirecionou o reparo para o analisador de campos tipados em docfields.py. Após duas rodadas e 75 turnos extras, o patch final manteve o tipo completo antes do nome do último parâmetro, sem mais dividir na vírgula. O patch passou tanto no teste gerado quanto na verificação oficial nova. Um caso de tipo entre colchetes também funcionou pelo mesmo caminho de análise. O patch do Sphinx também manteve uma edição anterior não relacionada. Ela estava correta, mas não era mínima.

Os baixos contadores de turnos importam porque mostram que o teste deu uma pista estreita. O reparo não precisou procurar em todo o código-base: bastava verificar um caminho suspeito, corrigi-lo e reexecutar o mesmo teste congelado. O teste gerado guiou o reparo, enquanto um avaliador oficial separado deu a decisão final.

Por que a separação dos dois papéis continua sendo a arquitetura mais segura

Um modelo único pareceria mais simples: um mesmo checkpoint poderia escrever os testes e reparar o código, transferindo conhecimento entre as duas funções. Mas essa conveniência esconde um risco real. Os dois papéis não aprendem com os mesmos sinais. O redator de testes é recompensado por criar verificações que falham no código com defeito, passam após a correção correta e separam os patches bons dos ruins. Sua maior recompensa só vem quando essa separação é limpa. Já o reparador só é recompensado quando sua alteração na fonte resiste a uma verificação final independente. Esses objetivos puxam em direções opostas. Se um único modelo desempenha ambos os papéis, pode começar a otimizar o ciclo em vez do bug: escrever testes fáceis de satisfazer para si mesmo ou corrigir o código apenas para corresponder ao teste, sem resolver o comportamento público descrito na issue. Essa coordenação parece eficiente à primeira vista, mas, na prática, pode piorar o desempenho do par.

Manter os papéis separados protege a verificação de se tornar um alvo. O pacote de testes é gerado uma única vez e mantido fixo durante o reparo. O reparador vê apenas feedback limitado da execução, não a fonte oculta ou a identidade do teste. Ele não pode reescrever a verificação para fazer seu patch parecer melhor. Isso importa porque testes fracos podem desviar o reparo: um teste que falha pela razão errada pode levar a correção por um caminho falso. O treinamento separado facilita manter cada papel honesto, pois um lado deve produzir verificações confiáveis e o outro deve resolver a issue sob essas verificações. O resultado não é uma garantia universal, mas um design mais seguro, já que a independência limita as formas pelas quais o sistema pode se enganar.

Um teste só ajuda se for honesto

O resultado mais robusto não decorre de um truque isolado, mas de um ciclo. O redator de testes cria uma verificação pequena e voltada para o comportamento público. O agente de reparação tenta satisfazê-la. Se a verificação falha, o agente recebe um sinal breve e revisa o código. Esse ciclo elevou a taxa de sucesso de 61,2% para 72,6%. Ambos os modelos foram treinados, sem o uso de oráculos externos durante a avaliação.

O ganho não é mágico. Vem do feedback barato. Um pacote de testes gerado pode ser reutilizado em várias tentativas de reparação. A revisão adicionou apenas cerca de treze turnos do agente, em média. O sistema permitiu até cinco rodadas de revisão. Isso importa porque correções reais de software custam tempo e computação. Uma verificação local de baixo custo pode guiar muitas pequenas correções sem consumir um orçamento elevado.

Ainda há um teto. Com feedback de oráculo mais forte, o sucesso atingiu 77,6%. A lacuna restante aponta para a qualidade e a cobertura dos testes. Testes melhores poderiam expor mais do comportamento real ao redor de um bug. Mais verificações independentes talvez reduzissem a falsa confiança. Um modelo compartilhado, capaz de escrever testes e reparar código, seria mais simples de implantar e permitiria que o conhecimento fluísse entre as duas funções.

O obstáculo é a confiança. Testes fracos ou desalinhados podem desviar a reparação do problema central. O redator de testes não deve perseguir detalhes internos fáceis, estado privado ou um sintoma isolado do bug. Deve mirar no comportamento observável que o problema realmente promete. O treinamento conjunto torna isso mais difícil porque as duas funções têm recompensas e ações diferentes. Se elas aprenderem a agradar uma à outra em vez de resolver o problema, o ciclo se torna autojustificável.

A oportunidade ainda é grande. Um sistema pode gerar uma verificação útil, executá-la localmente, revisar o código e submetê-lo para verificação oficial. O próximo passo talvez seja menos sobre capacidade bruta de codificação e mais sobre autoteste disciplinado. Uma máquina pode aprender a duvidar do próprio teste?

Continuar lendo

Continuar para o próximo artigo

Continuar para o próximo artigo

Continuar para o próximo artigo