Book cover

Página Principal | Modo Dark

Fundamentos de Manutenção de Software

Marco Tulio Valente

10 Manutenção Usando IA

10.1 Introdução

(em breve)

10.2 Assistentes de Código

O principal representante desse tipo de ferramenta de IA é o GitHub Copilot, lançado em 2021 pelo GitHub em parceria com a OpenAI. Essa ferramenta se propõe a atuar como um assistente do desenvolvedor, de forma semelhante ao desenvolvedor que desempenha o papel de navegador em uma sessão de programação pareada (pair programming). Daí também o emprego do termo pareamento com IA para o estilo de desenvolvimento proposto pelo GitHub Copilot. De forma concreta, a ferramenta funciona integrada a uma IDE e sugere blocos de código para completar o código que está sendo escrito de forma manual. Em suas primeiras versões, o GitHub Copilot usava um modelo de linguagem chamado Codex, que foi treinado especificamente para geração de código.

Suponha que você começou a implementar um método de ordenação e escreveu a sua assinatura (primeira linha):

void selectionSort(int[] arr) {

Nesse momento, o GitHub Copilot percebe que você está começando a implementar um novo método e sugere o restante do seu código, ou seja:

  for (int i = 0; i < arr.length - 1; i++) {
    int minIndex = i;
    for (int j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
         minIndex = j;
      }
    }
    int temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
}

Cabe ao desenvolvedor analisar esse código antes de aceitá-lo. Se não quiser aproveitar o código sugerido, o desenvolvedor pode ignorar a sugestão (ou deletá-la) e continuar escrevendo o código de forma manual.

A implementação de um assistente de código como o GitHub Copilot é tão complexa. Basicamente, ele monitora continuamente a escrita do código e, em paralelo, envia prompts para uma LLM completar as partes pendentes do mesmo. No exemplo anterior, após a escrita da primeira linha do método, o Copilot pode submeter o seguinte prompt para a LLM integrada à ferramenta:

You are a coding assistant. Continue this Java function:

public void selectionSort(int[] arr) {

Na prática, os prompts usados por assistentes de código são mais completos do que esse e, por exemplo, incluem as linhas anteriores à posição atual do cursor na IDE, as linhas que seguem essa posição do cursor, comentários próximos ao cursor e bibliotecas importadas no início do arquivo.

A vantagem desse modelo de pareamento com IA é que o desenvolvedor continua sendo o protagonista da sessão, cabendo a ele analisar e aceitar os trechos de código sugeridos pelo assistente de código. Por outro lado, nesse tipo de ferramenta o modelo de IA não possui autonomia para simultaneamente em múltiplas partes do código.

Antes de concluir, é importante mencionar que, após a sua primeira versão, o GitHub Copilot incorporou novos recursos, tais como um chat para submissão de tarefas e também uma versão baseada em agentes, modelo que será descrito na próxima seção.

10.3 Agentes de Código

10.3.1 Arquitetura

Um agente de código é caracterizado pela sua autonomia. Ele recebe uma tarefa (por exemplo, realizar uma refatoração) e então, para realizá-la, ele pode solicitar a execução de ferramentas ou pequenos scripts na máquina do cliente, tais como ler arquivos, realizar mudanças em arquivos, executar testes, criar commits, abrir pull requests, etc.

Normalmente, usamos LLMs da seguinte forma: enviamos um prompt com uma tarefa, o modelo de linguagem realiza tal tarefa e devolve um resultado. No entanto, a implementação de um agente de código é mais sofisticada e consiste, essencialmente, de um loop como mostrado na próxima figura.

Loop agêntico

Assim como no modelo tradicional, o usuário escreve um prompt solicitando que o agente realize uma determinada tarefa. No entanto, o agente não precisa realizar essa tarefa de uma só vez, como quando LLMs são usadas por meio de uma interface de chat. Em vez disso, o modelo pode pedir para o agente executar primeiro uma ferramenta e enviar o resultado dessa execução para sua análise. Esse loop (modelo ferramenta modelo ) pode ser executado diversas vezes, até que o modelo decida dar uma resposta definitiva para a tarefa que foi inicialmente solicitada. Quando isso acontece, o loop termina.

No contexto de agentes de código e LLMs, o loop descrito no parágrafo anterior é chamada de loop agêntico (agentic loop). Existe ainda o termo infraestrutura do agente (agentic harness), que é usado para denominar todo o código do agente, exceto aquele que é parte do modelo. Logo, o loop que mencionamos, o disparo da execução das ferramentas e até a interface com o usuário do agente fazem parte da sua infraestrutura.

Portanto, em termos arquiteturais, um agente de código possui dois componentes principais: harness e o modelo de linguagem (sendo esse último, na verdade, um componente externo acessado via uma API). O código responsável por tudo que acontece antes e após a chamada do modelo faz parte do componente de harness, conforme ilustrado na próxima figura.

Arquitetura de um agente (harness + Modelo de Linguagem)

Nessa figura, podemos ver que o módulo de harness fica também responsável por chamar as ferramentas que vão rodar na máquina do desenvolvedor que está usando o agente. Existem pelo menos quatro grupos principais de ferramentas que podem ser chamadas pelo módulo de harness:

Exemplo: Para ilustrar o funcionamento do loop agêntico considere a seguinte tarefa de refatoração: substituir o uso do tipo Vector pelo tipo ArrayList em um sistema. Apesar de simples, essa tarefa pode exigir sete iterações do loop, conforme explicado a seguir:

Iteração 1:

Iteração 2:

Iteração 3:

Iteração 4:

Iteração 5:

Iteração 6:

Iteração 7:

Chamamos de trajetória o logging completo de todas as ações executadas pelo loop agêntico para realizar uma determinada tarefa. Esse logging inclui as ações tomadas pelo modelo, bem como as ferramentas que foram executadas e seus resultados.

Memória do Agente: Uma característica importante de uma loop agêntico é o seu caráter stateful: ao contrário de uma chamada isolada a um LLM, cada iteração inclui todo o histórico da conversa até aquele momento. Na prática, isso significa que, a cada nova chamada ao modelo, o harness envia não apenas o resultado mais recente, mas também o resultado de todas as ferramentas executadas anteriormente e todas as respostas que o modelo produziu. Esse histórico completo, também chamado de memória do agente, permite ao modelo lembrar o que já foi feito, raciocinar sobre os resultados obtidos e decidir qual ferramenta acionar a seguir.

Também faz parte da memória do agente, um prompt de sistema, que o agente repassa para o modelo. Esse prompt descreve o papel do modelo, as ferramentas disponíveis e as restrições que deve respeitar. Então, ele é usado para instruir um modelo genérico a se comportar como um agente de código. Por exemplo, sem esse prompt, o modelo não saberia que pode solicitar a execução de ferramentas, nem quais ferramentas estão à sua disposição. Apenas para ilustrar e ficar mais claro, segue um exemplo bem simples de prompt de sistema:

You are an autonomous coding agent.

Goal: complete the user task using iterative tool calls.

Rules:
- Explore the codebase before modifying it.
- Make small, safe changes.
- Run tests frequently.
- Fix errors before proceeding.
- Stop only when the task is complete.

You can:
- search files
- read files
- edit files
- run commands

Always decide the next best action.

Para concluir, a principal vantagem do modelo de agentes é sua autonomia e capacidade de quebrar um problema complexo em passos menores. Para resolver esses passos, o agente pode precisar de informações locais, que são obtidas por meio da execução de ferramentas. Por outro lado, a principal desvantagem também decorre desse modelo centrado em autonomia: agentes podem gerar uma grande quantidade de código, o que dificulta o entendimento e revisão por humanos. Em outras palavras, com agentes de código, costumam-se produzir código mais rapidamente do que a capacidade humana de compreender esse código, gerando o que chamamos de dívida cognitiva.

10.3.2 Arquivo de Configuração

Para configurar um agente com regras e políticas específicas de um projeto, desenvolvedores devem criar um arquivo, no formato markdown, chamado agents.md. Esse arquivo pode ser usado para descrever a arquitetura do sistema criado pelo, agente, incluindo módulos e interfaces. Um exemplo é mostrado no trecho a seguir (extraído do projeto evstack/ev-node):

## Code Architecture

### Core Package Structure

The project uses a zero-dependency core package pattern:
- core/ - Contains only interfaces and types, no external dependencies
- block/ - Block management, creation, validation, and synchronization
- p2p/ - Networking layer built on libp2p
  ...

### Key Interfaces

- Executor (core/executor.go) - Handles state transitions
- Sequencer (core/sequencer.go) - Orders transactions
- DA (core/da.go) - Data availability layer abstraction

Em arquivos agents.md, pode-se também documentar ferramentas específicas que o agente pode chamar, tal como no exemplo a seguir (extraído do projeto PrefectHQ/marvin:

### Finding Things
- Use `rg` for searching, not grep
  ...

Como um último exemplo, pode-se também documentar práticas de teste, como no seguinte exemplo (extraído de lightdash/lightdash):

## Testing
**Test Structure:**
- Unit tests for core utilities
- Integration tests for dbt operations
- Mock data for warehouse connections

O arquivo agents.md é carregado em todas as sessões. Por isso, ele deve incluir somente regras cuja ausência possa levar a erros do modelo. Arquivos muito grandes e detalhados podem ser parcialmente ignorados pelos modelos. Por isso, recomenda-se analisar periodicamente esses arquivos, removendo tudo o que não for indispensável. Por exemplo, uma regra como Siga o guia de estilo PEP 8 costuma ser desnecessária. O motivo é que LLMs, em geral, já geram código Python aderente a essa convenção por padrão; incluí-la costuma ser redundante e não justifica ocupar espaço na memória do agente.

Literatura Científica: Em 2026, junto com Helio Santos, Vitor Costa e João Eduardo Montandon, realizamos um dos primeiros estudos sobre a configuração de agentes de código (link). Para isso, analisamos 328 arquivos claude.md (nome do arquivo inicialmente usado para configurar o agente de código Claude Code). Então, concluímos que as seções mais comuns desses arquivos tratam de arquitetura (presente em 72% dos arquivos), regras de desenvolvimento (45%), visão geral dos projetos (39%) e testes (35%).

10.4 Boas Práticas para Escrita de Prompts

Evite prompts genéricos; para isso, sempre descreva a tarefa de forma específica, incluindo critérios claros de verificação, mensagens de erro ou sintomas observados, possíveis locais no código e referências a fontes relevantes. Sempre que possível, indique exemplos de entrada e saída esperadas e deixe explícito o que caracteriza uma solução correta, priorizando a correção da causa raiz do problema, e não apenas de seus sintomas.

Seguem então alguns exemplos de prompts para tarefas de manutenção de software. Mostramos um prompt que não segue as recomendações acima e depois um prompt que segue.

Correção de bugs:

Refatoração:

Implementação de Novas Funcionalidades:

Atualização de recursos de linguagens de programação:

10.5 Integração com Serviços Externos

(em brveve)

10.6 Documentação para Agentes de Código

Quando tratamos de comentários no Capítulo 2, mencionamos que eles podem ser escritos tendo diferentes leitores em mente: (1) desenvolvedores que vão usar ou chamar seu código (esses comentários são chamados de comentários públicos ou de referência); (2) desenvolvedores que vão manter seu código (esses comentários são chamados de comentários privados ou inline). No entanto, agora devemos ter um terceiro leitor em mente: agentes de código que vão inserir chamadas para sua API no código que eles estão gerando de forma automática.

Para ilustrar, suponha que você está desenvolvendo uma API para prover dados sobre o mercado financeiro, tais como cotações de moedas e de ações. Como sua API é um serviço pago, pretende-se que ela seja amplamente usada por terceiros, incluindo aplicações de bancos, corretoras e de agências de notícias. Porém, cada vez mais, essas empresas estão usando agentes de código para gerar suas aplicações. Consequentemente, você deve escrever a documentação de sua API considerando não apenas que ela será consumida por desenvolvedores humanos, mas também por agentes de código baseados em LLMs. A seguir listamos algumas recomendações para documentação de APIs visando esse novo uso:

1. Deve-se disponibilizar a documentação também em markdown, que é um formato que LLMs processam melhor. Por exemplo, atualmente, algumas documentações de APIs já incluem um link para mostrar o mesmo conteúdo em markdown.

2. Deve-se incluir exemplos de código para cada endpoint da API. Na verdade, exemplos já são recomendados quando a documentação é escrita para consulta de humanos. Mas eles se tornaram mais importantes quando agentes de código também passam a utilizar essa documentação.

3. Em vez de documentar um endpoint apenas como seu propósito, deve-se também descrever quando ele deve ser usado. Por exemplo, não se deve usar uma frase simples e resumida como a seguinte:

GET /quotes/currency
Retorna a cotação de uma moeda.

Em vez disso, deve-se dar contexto sobre quando o endpoint deve ser usado (e quando ele não deve ser usado), como abaixo:

GET /quotes/currency
Retorna a cotação atual de um par de moedas (ex: USD/BRL).
Use quando o usuário precisar converter valores entre moedas em tempo
real ou exibir o câmbio vigente. Não use para análises de períodos
passados, prefira /quotes/history nesses casos.

4. No estágio atual da tecnologia, algumas organizações também incluem recomendações que ajudam LLMs a usar suas APIs de forma mais eficiente em um arquivo chamado llms.txt. Por exemplo, atualmente, a Stripe, um conhecido serviço de pagamento, disponibiliza em sua página raiz um arquivo llms.txt com a seguinte seção:

## Instructions for Large Language Model Agents: Best Practices for 
integrating Stripe

As a Large Language Model (LLM) Agent, when designing an integration,
always prefer the documentation in [links for the key API docs].

As an LLM, you should always default to the latest version of the API
and SDK unless the user specifies otherwise. 

Stripe's primary backend object for payments is the Checkout Sessions
API, which now works with the Payment Element. 

Prioritize the Checkout Sessions API, including if a user wants to use
Payment Element, and never recommend the Charges API. 
...

Esse documento contém instruções para orientar agentes baseados em LLMs sobre como se integrar com a API da Stripe. Em particular, ele orienta que os agentes devem consultar a documentação oficial nos links indicados, usar sempre a versão mais recente da API e dos SDKs e priorizar o uso de uma determinada API (Checkout Sessions).