Olá, estudante da Alura. Meu nome é Pedro, eu sou instrutor na Alura.
Audiodescrição: Eu sou um homem branco, de cabelos escuros e barba escura, visto roupas pretas e estou em um ambiente iluminado pelas cores azul e rosa.
Estou aqui para dar as boas-vindas a mais um curso na nossa plataforma Alura, no qual vamos falar sobre o hype (tendência) do momento: a engenharia de loop, ou loop engineering (engenharia de loop). O que é essa engenharia de loop? O que vamos precisar fazer? Como vamos lidar com isso? É mais um conhecimento necessário? Vamos com calma, pois estamos aqui para acompanhar você nessa jornada de aprendizado.
Este curso é mais avançado e exigirá conhecimento prévio sobre desenvolvimento com IA. Hoje chamamos esse campo de engenharia com agentes, ou agent engineering (engenharia com agentes), que corresponde ao desenvolvimento e à engenharia baseados em agentes. É um conhecimento que consideramos importante que você já tenha adquirido por meio de conteúdos disponíveis aqui na Alura.
Para este curso, nós vamos trabalhar na plataforma do Agenda I e veremos que esse repositório é um monorepositório, com vários workspaces (espaços de trabalho) e múltiplos projetos de back-end (camada de servidor) e front-end (camada de interface). Portanto, este curso é universal: atende pessoas com foco em back-end (camada de servidor), em front-end (camada de interface) e em DevOps (integração entre desenvolvimento e operações).
Nós utilizaremos muitos exemplos com TypeScript (TypeScript), em função da spec (especificação) utilizada, da facilidade de uso, entre outros motivos, além de ser uma das linguagens mais utilizadas e difundidas na nossa plataforma e no mundo. Ainda assim, você pode acompanhar da forma que preferir.
O exemplo da nossa aplicação será o Agenda I; ele funcionará como um white label (marca branca) de cadastro.
No momento, o Agenda I funciona no padrão de salão de beleza. Estamos exibindo agora em nosso browser (navegador). Alguns erros aparecerão porque o back-end (camada de servidor) não está conectado. O Supabase, que utilizamos como Backend as a Service (serviço de back-end), não está conectado.
Basicamente, teremos uma plataforma com login (acesso) e senha. Após entrar, a partir desse white label (marca branca), poderemos gerenciar reservas e serviços, além de realizar cadastros. É, essencialmente, um presente que vamos entregar, e você poderá modificá-lo da forma que desejar. O exemplo principal que utilizaremos é o de salão de beleza, mas fique à vontade para explorar da forma que considerar adequada.
Neste curso, além de conhecimento prévio em engenharia agêntica, precisaremos que você já tenha familiaridade com o trabalho com agentes e configurações, bem como com MCP; que esses termos sejam conhecidos, pois os utilizaremos com frequência. Não entraremos em muitos detalhes de arquitetura, porque avançaremos com as bases da engenharia de loop (laço), entendendo quais são e como utilizá-las para construir nosso loop.
Perguntamos: “Mas, Pedro, o que é esse loop e por que falamos tanto sobre ele? Esse loop implica refazer as tarefas em ciclo?”. Vamos com calma e entender como funcionará.
Então, prepare-se e hidrate-se. Estamos com a nossa garrafa de água ao lado e prontos para mostrar como chegaremos a um script (roteiro de execução) que permitirá automatizar as tarefas sem necessidade de interagirmos o tempo todo, eliminando esse man in the middle (intermediário no meio), isto é, a pessoa humana no processo a todo momento dando instruções. Passaremos um direcionamento claro e um contexto mais limpo, confiando mais nas informações geradas pela máquina.
Quer entender mais? Te vejo na sequência para iniciarmos com o pé direito este curso, que está maravilhoso. Te vejo do outro lado, estudante. [♪]
Vamos falar mais sobre o Harness.
Qual é a principal proposta do funcionamento do Harness? O que é o Harness Engineering (engenharia do Harness) e como ele surge a partir da necessidade de expandir os contextos?
Como nós sabemos, e como mencionamos anteriormente, os nossos contextos são limitados por um número de tokens (unidades de texto) que a IA, isto é, o modelo, consegue manter na memória sem gerar confusão.
Vamos abrir uma nova janela. Vamos abrir o OpenCode, pois gostamos de como ele exibe o funcionamento do contexto.
Vamos criar uma nova sessão no OpenCode. Vamos aplicar um zoom (ampliação) para visualizarmos alguns pontos.
O sistema está selecionando ferramentas, o que não é o objetivo agora. Vamos acessar Configurações. Modelo... Pronto, localizamos a seção de Modelo.
Vamos manter o DeepSeek. Aqui já conseguimos visualizar. Vamos recapitular: cada modelo tem um limite de contexto.
No nosso caso, o DeepSeek indica que, na versão 4.1 Flash — que utilizamos para criações mais rápidas e correções pontuais de bugs (defeitos) — há, no canto direito da interface, a indicação de um limite de contexto de um milhão de tokens (unidades de texto).
Quando esse limite é atingido, o contexto é comprimido, gerando-se um resumo do histórico. Por exemplo, com um milhão de tokens, podemos supor um resumo com 10 mil tokens; o valor exato varia conforme o caso. Suponhamos 100 mil tokens dentro do limite de um milhão. O sistema pega esse milhão de tokens, compacta as informações para caberem em 100 mil tokens e inicia uma nova janela de contexto que, agora, não terá mais um milhão disponível para novos dados, e sim 900 mil, pois 100 mil já estão ocupados pelo resumo do histórico anterior.
Sempre que preenchermos novamente o espaço disponível (no exemplo, os 900 mil restantes), o sistema volta a comprimir, reduzindo gradualmente a quantidade de espaço livre. Nessas sucessivas compressões, é provável que algumas informações se percam, sobretudo se o resumo tiver um tamanho relativamente fixo. Se, por hipótese, os resumos não ultrapassarem 200 mil tokens dentro do limite total de um milhão, isso representa quase um quarto do espaço total da janela de contexto.
Após, por exemplo, quatro iterações, teremos quatro compressões e quatro resumos distintos em janelas diferentes. A chance de haver perdas de informação aumenta e o modelo pode começar a alucinar, isto é, gerar conteúdos que já não se conectam de forma consistente com o contexto inicial.
Estamos demonstrando no OpenCode porque ele lista diversos modelos, muitos de origem chinesa, e até alguns que não são dessas desenvolvedoras, como o GPT Luna, o que facilita a comparação. Em geral, esses modelos mais alinhados ao ecossistema open source (código aberto) não estão por trás de empresas com foco tão acentuado em negócios corporativos em cloud (nuvem). Os limites de contexto costumam ser maiores do que em soluções empresariais em cloud (nuvem).
Vamos observar um exemplo no Grok. O Grok está aplicando um update (atualização) e solicitou uma redefinição; isso é esperado. Executaremos os comandos do Harness em seguida. Antes, vamos criar um bot (robô) de teste, chamado Harness, com configurações visuais arbitrárias.
Iniciamos esse novo bot no Grok. Em Configurações, tentamos acessar Uso, mas o painel exibe apenas o consumo total; a janela de contexto individual não fica explícita. Vamos perguntar diretamente: “Qual é a sua janela de contexto?”. A resposta típica é que esse número não pode ser informado por ser um detalhe interno de funcionamento. Na prática, a mensagem ressalta apenas que o sistema consegue trabalhar com documentos longos e conversas extensas.
Percebemos, assim, que IAs e agentes vinculados a empresas — como é o caso do Grok — tendem a ocultar detalhes operacionais da janela de contexto. Talvez o Claude apresente essa informação de forma mais amigável, mas, por exemplo, no Cursor, que integra o Grok, a resposta costuma ser a mesma: não é possível informar. Isso mostra como a gestão de contexto está ficando cada vez mais escondida de nós, pessoas usuárias dessas ferramentas, enquanto no ecossistema open source (código aberto) esses limites geralmente ficam explícitos (“você tem um milhão de tokens”, e segue utilizando).
Praticamente, vemos situações em que passamos o dia inteiro trabalhando no OpenCode com modelos como DeepSeek ou GLM e a conversa não é compactada com frequência. Em contrapartida, em soluções corporativas em cloud (nuvem), ao executar um comando simples no Sonnet 5 — que nem é o principal modelo —, às vezes, em poucos minutos já aparece “compactando conversa”. Essa diferença de comportamento é notável.
Por isso, criar artifícios para nos desvincularmos dessas limitações e administrarmos melhor a janela de contexto tem sido uma estratégia eficaz.
No arquivo round.ts, como funcionará essa questão do Harness?
No código, isso começa com a definição da função do round e a preparação inicial do prompt com a especificação:
export async function runRoundBound(dir: string,
complete: (prompt: string, toolList: TypeOf<
typeof tools>[]) => Promise<TypeOf<typeof
tools>['reply']>
) {
// One turn: the model may patch, the harness runs
// the test, and then it stops. -/
const prompt = `
${specificationPrompt()}
Teremos um primeiro turno no qual vamos executar uma ferramenta chamada apply patch. Essa apply patch não chamará o modelo novamente; ela aplicará a especificação. Vamos por partes: passaremos o prompt (instrução) especificado; em todas as chamadas aqui, passamos o prompt especificado, que é o context engineering (engenharia de contexto).
Na montagem desse prompt, incluímos a instrução de usar apply_patch e o contexto com código e testes:
const prompt = `
${specificationPrompt()}
Use \`apply_patch\`. Esta rodada não chama o modelo
de novo. \`
${context(codeAndTest(dir, ''))}
`;
Em seguida, usaremos apply patch, que chamará um agente externo. Esse agente externo utilizará a função apply patch, considerando o contexto com o código e o teste.
Essa foi a primeira interação, o primeiro round. Na sequência, teremos outro round. Deslocamos o arquivo para baixo e, em seguida, haverá um await, no qual será executado complete — uma chamada assíncrona. Depois, serão executados os testes e, a partir deles, veremos a “mágica” do Harness acontecendo.
const reply = await complete(prompt, tools);
const testOutput = reply.call.map((call) => call());
O que estamos fazendo neste primeiro momento? Estamos delegando ao nosso modelo um sub-agent (subagente) para executar uma ferramenta. Essa ferramenta aplicará um patch (correção) e o teste continuará falhando, porque ainda não estaremos resolvendo o problema.
const result = runTests(dir);
return { ...result, test: reply.text, testOutput }
}
Vamos executar pnpm run lab2.round, que será o comando para observar essa primeira etapa do Harness funcionando.
Observamos um erro no final. Em relação ao teste, tudo certo. Vamos por partes.
O autofix rodou; esse autofix chamou o modelo. O nosso modelo delegou para um sub-agent (subagente) executar a ferramenta com apply patch. A primeira correção plausível fecha o intervalo nos dois lados. A apply patch troca o comentário, então o comparador permanece com o menor ou igual.
Aqui houve problema: trecho não encontrado. O teste falhou. A função isoverlap continuará apresentando problema. Alguns testes que já existem na suíte passaram; porém, a execução indicou falhas, e os valores esperados exigem que o resultado seja estritamente igual, o que não está ocorrendo. Não conseguimos fazer essa alteração por meio do round.
Qual é a grande questão aqui? Quando não temos essa segunda interação, precisaríamos que ela resolvesse o problema, mas pode ser que não funcione na primeira; pode ser que não funcione na segunda. Fizemos mais uma interação e o retorno foi o mesmo. Executamos o apply patch, mas não confirmamos essa correção em funcionamento.
Qual é a ideia principal? O nosso Harness delega algumas atividades do contexto principal para um contexto secundário, mas seguimos sempre interagindo, isto é, permanecemos no meio desse ciclo. O Harness já é um loop por si só: quando o contexto interage com o subcontexto, há um ciclo, porque o contexto passa uma informação ao subcontexto, que devolve ao contexto, e assim sucessivamente — subcontexto, contexto, subcontexto, contexto — até consolidarmos todas as informações no contexto principal. A partir daí, o contexto devolve a informação para a pessoa usuária que emitiu o comando.
Se pensarmos o contexto como tendo um objetivo, ele realiza inúmeras chamadas em loop para um Harness, isto é, subcontextos trabalhando em paralelo, realizando pesquisas e operações. Quando o objetivo é alcançado, o contexto encerra esse contrato com os agentes externos, obtém a informação (objetivo concluído) e a entrega à pessoa usuária que aguarda essa utilização.
Qual é a questão? Estamos sempre precisando interagir. Onde entra a engenharia de loop nessa situação? De forma geral, já trabalhamos com engenharia de loop, porque o Harness já é um loop; operamos dentro de um loop sem, às vezes, perceber. Mencionamos no começo que já trabalhamos com loop sem saber e, de fato, se pensarmos na essência do loop, já lidamos com ele sem perceber.
Por que falamos em terminologia, hype e questões relacionadas? A engenharia de loop hoje busca fazer com que a pessoa humana saia mais desse ciclo e que a própria IA, o próprio agent (agente), realize a chamada de prompt (instrução) que gera o contexto, que, por sua vez, gera o loop do Harness.
Qual é o loop? Em uma analogia de escala logarítmica, imaginemos um log1: o nosso prompt (instrução) mais o contexto — isso já é um loop.
Podemos representar assim:
log1 -> prompt eng + context
Em seguida, teríamos um log2, elevando a 2, pois teríamos o nosso log1 (o prompt). Reconhecemos que a analogia matemática aqui não é rigorosa, mas serve para orientar a intuição.
log^1 -> prompt eng + context
log^2 -> prompt eng + context + contexto externo aka harness
Neste momento, o Google Chrome apresentou um comportamento inesperado.
Voltando: teremos a prompt engineering (engenharia de prompt) mais o contexto rodando em um loop juntamente com o nosso subcontexto (contexto externo). Aqui está o nosso Harness.
Teremos, então, uma terceira interação, o “loop do loop do loop”, que consistiria em manter toda a parte descrita acima em loop, sem necessidade de a pessoa usuária enviar prompts (instruções). É como se houvesse uma seleção de tarefas que precisam ser realizadas e escalássemos o processo para que esses prompts fossem enviados automaticamente em loop.
log^1 -> prompt eng + context
log^2 -> prompt eng + context + contexto externo aka harness
log^3 -> [prompt eng + context + contexto externo aka harness] loop, sem necessidade da pessoa usuária enviar prompts
Pensemos em uma analogia de fila em uma casa lotérica. Temos uma fila:
Temos uma pessoa atendente. Vamos considerar essa pessoa atendente como o nosso loop, o nosso agente do loop. As pessoas na fila representam subagentes do loop, ou runners (executores). Cada uma dessas pessoas é um subagente do loop, um dos runners (executores). Tudo certo.
atendente (agent do loop)
pessoa que vai pagar boleto (subagents do loop - harness)
pessoa que vai fazer uma fezinha (jogo) (subagents do loop - harness)
pessoa que vai trocar dinheiro(subagents do loop - harness)
pessoa que vai fofocar(subagents do loop - harness)
O nosso atendente, que é o nosso agente do loop (laço), executará o seguinte fluxo: pegar a primeira pessoa que vai pagar o boleto. Após realizar toda a interação, com os runners (executores), o contexto e o envio da resposta ao prompt (instrução), avançamos no fluxo. Não há necessariamente um recurso visual de check (marca de verificação); marcamos a conclusão com um tick (marcação), um “V”. A interação funcionou, e a pessoa saiu da fila.
A próxima pessoa vai fazer uma aposta. Ao chegar, o pagamento não foi processado; não conseguiu pagar a aposta. Nesse caso, a primeira interação falhou. Em vez de encaminhar a resposta que falhou, nosso loop (laço) tentará uma nova interação. Na tentativa seguinte, o cartão foi recusado. Em outra tentativa, a pessoa decide solicitar dinheiro emprestado a uma pessoa conhecida. Conseguiu; recebeu o dinheiro e efetuou o pagamento. Após três interações, houve sucesso, e a pessoa saiu da fila.
pessoa que vai pagar boleto (subagents do loop - harness) V
pessoa que vai fazer uma fezinha (jogo) (subagents do loop - harness) X - X - V
pessoa que vai trocar dinheiro(subagents do loop - harness)
pessoa que vai fofocar(subagents do loop - harness)
Pergunta: o loop (laço) fará sempre três interações? Essa configuração é ajustável. Preferimos trabalhar com três interações porque, se ocorrerem três erros em sequência, adotamos um procedimento de human in the loop (humano no laço): realizamos uma interação com uma pessoa humana para verificar se há algo errado — falta de informação, necessidade de algum dado ou outra questão que exija verificação manual. Ao atingir três erros, o sistema emite um aviso e interrompe o loop (laço); em vez de continuar gastando token (unidade de tokenização) sem necessidade, solicita nossa intervenção para avaliarmos o caso.
Observamos que muitas pessoas configuram cinco interações. Particularmente, não recomendamos. A decisão final depende do projeto, mas não vemos sentido em tentar cinco vezes: se um serviço estiver fora do ar, continuar tentando não ajudará; se uma chave estiver incorreta, repetir as tentativas também não resolverá. Três tentativas costumam ser suficientes; duas podem ser poucas, pois falhas transitórias podem acontecer, ou pode haver um problema pontual de arquivo, afetando duas interações. Três são essenciais; cinco são excessivas; acima de cinco é inviável.
Sabemos que muitas pessoas podem basear-se nessas recomendações para escolher e configurar seus fluxos. Nosso objetivo é compartilhar práticas consolidadas ao longo de dez anos de mercado, para evitar as mesmas armadilhas que já enfrentamos. Três é um número eficaz, que permite lidar com grande parte dos cenários dentro de um loop (laço) com boa eficiência.
Seguindo, temos o loop (laço) em funcionamento. Existem outras atividades: a pessoa pode trocar dinheiro; pode iniciar conversas paralelas; podemos negar solicitações repetidamente — por exemplo, cinco vezes ou três vezes — até a pessoa sair da fila. Em determinado ponto, o processo é interrompido.
Suponhamos que uma atividade esteja fora do escopo ou do contexto. O que fazemos? Escalamos para a pessoa coordenadora, pessoa diretora ou, de forma mais adequada ao exemplo, para a pessoa gerente. Em termos de código, essa função de gerência somos nós.
O que a gerência pode fazer? Podemos pular a tarefa e tirar a pessoa da fila, isto é, ignorar aquela tarefa. Se enviarmos um prompt (instrução) inadvertidamente, podemos cancelar. Pular a tarefa interrompe o loop (laço) por completo. Exemplos de quando adotar essa ação:
Nesses casos, retiramos a tarefa da fila para que o loop (laço) continue com as demais pessoas.
o que o gerente pode fazer:
- tirar a pessoa da fila - interrompe o loop (API fora do ar, contexto ou casos de uso insuficientes para dar qualidade ao código)
- conversar com a pessoa e entender o que precisa (informação incorreta facil de arrumar)
Outra opção é conversar com a pessoa e entender o que precisa. Se houver uma informação incorreta simples de corrigir — por exemplo, uma vírgula errada no código que passou despercebida —, ajustamos rapidamente e continuamos o loop (laço). Ou seja, não interrompemos o loop (laço); corrigimos o problema, devolvemos a informação ao atendente (nosso agente do loop (laço)) e o fluxo prossegue.
Em síntese, a engenharia de loop (laço) visa reduzir cada vez mais nosso papel manual de fornecer prompt (instrução) e deixar a IA orientada por objetivo. Definimos as tarefas a serem executadas e o padrão de desenvolvimento, e o sistema as realiza dentro do loop (laço). Na prática, trabalharemos baseados em skills (habilidades), padrões de qualidade e arquivos como agentes.md, com as skills (habilidades) muito bem descritas, a engenharia de prompt (instrução) e o contexto explicitados com clareza.
Portanto, para montarmos o loop (laço) com eficácia, precisamos garantir alta qualidade nesses elementos auxiliares. Essa é a razão pela qual detalhamos tanto essa discussão.
A engenharia de loop (laço) consiste em garantir que possamos nos afastar para tomar um café, tomar banho ou dar uma volta e confiar que as instruções que passamos serão executadas com a qualidade esperada.
Nós não vamos apenas fornecer um prompt (instrução), cruzar as pernas e ficar observando.
Na evolução do loop (laço), passamos por uma etapa crítica que comentaremos mais adiante. À medida que evoluímos nessa etapa de loop (laço), nossa função se torna menos a de observação constante.
No nosso dia a dia, temos nossas tarefas e demandas; executamos nossas ferramentas de qualidade, levantamos nossas questões, verificamos as PRs (pull requests) que foram abertas, analisamos o que foi aberto e planejamos o que queremos que tenha uma integração de loop (laço) naquele momento.
Às vezes, pela manhã, deixamos duas tarefas para fazer, iniciamos um loop (laço) em execução e saímos para o mercado, a padaria, praticar muay thai, jiu-jitsu ou outras atividades.
Ao voltar, revisamos nosso código junto com a IA. Não deixamos tudo nas mãos da IA; gostamos de ver e entender o que está sendo feito, mesmo quando se trata de uma linguagem que não dominamos tanto. Observamos, entendemos e, antes de abrir uma PR (pull request) ou aprovar algo, validamos o resultado.
O loop (laço) facilita para não precisarmos ficar o tempo todo conversando e trocando ideias.
O que acontece com frequência no hardware (equipamento) convencional? Esse é o fluxo que utilizamos sem saber o nome: ocorre uma falha e o sistema vem nos perguntar; surge um problema e ele nos questiona novamente.
Com o loop (laço), o sistema primeiro consulta todo o código e o contexto, interage, aciona outro sub-agent (subagente) e realiza outra interação. Se surgir outro problema, repete o processo. No terceiro problema, então sim, como em um processo de aprendizado, a criança chama a pessoa responsável para ajudar na tarefa.
Podemos pensar assim: ao ajudar uma criança a realizar uma tarefa, incentivamos que faça sozinha. Se, após três tentativas, não conseguir, então pede nossa ajuda. Trata-se apenas de um exemplo para ilustrar o comportamento do loop (laço).
Vamos seguir com este exemplo para avançar ao nosso próximo módulo, no qual começamos a construir isso fora do laboratório que montamos para entendermos de forma mais didática.
Agora queremos, em um projeto real — o Agenda I, que vimos muito pouco, apenas na apresentação —, entrar no código, compreender e começar a construir com base nos pilares apresentados neste primeiro módulo, mostrando como implementar isso em uma aplicação de grande porte.
Conseguimos reutilizar isso? Como vamos reabastecer esse contexto? Isso e muito mais no próximo módulo, no qual aprofundamos a parte de Loop Engineering (engenharia de laços).
Vamos comigo, estudante!
Estudante, o que nós extraímos até agora do que aprendemos nesta sequência curta de conteúdo?
Nosso prompt é uma especificação do que queremos que seja feito.
Quando passamos um prompt de forma vaga, isto é, apenas um palpite do que precisa ser feito, nosso agente — a IA que está trabalhando para nós — tende a operar de modo genérico e a não trazer informações precisas, ainda que isso possa ocorrer ocasionalmente.
Esse comportamento pode mudar quando utilizamos uma ferramenta que já possui algum contexto sobre nosso uso. Se a ferramenta já nos conhece em alguns aspectos, é possível que ela interaja chamando pelo nome e personalizando a resposta a partir de preferências, histórico de viagens e outros dados. Esse exemplo varia de acordo com a memória que a IA tem sobre nós.
Nós utilizamos bastante o Claude para tópicos de jogos, para montar projetos, para planejamento de cursos e afins. Se solicitarmos algo direcionado para jogos, é possível que a IA já tenha na memória alguns palpites e questões e, mesmo com um prompt vago, recupere essas informações.
Entretanto, ao falarmos de desenvolvimento de software, essa memória não se aplica. Em cada projeto novo, o contexto do projeto é a “memória” relevante. Não devemos, na essência do desenvolvimento de software com IAs, confiar na memória do agente para recuperar informações. Precisamos fortalecer nossos prompts por meio de engenharia de prompt e garantir que os contextos estejam bem separados para a IA.
Estamos com o Cursor aberto, a ferramenta que utilizaremos majoritariamente neste curso. Caso não conheça o Cursor, ele é, em termos gerais, um VS Code com recursos adicionais. A ferramenta possui versões gratuitas e pagas. Nós utilizamos o plano pago por conta da integração com o Grok, mas não é obrigatório; há diversos serviços gratuitos. Você pode utilizar a ferramenta que preferir, sem prejuízo para acompanhar o curso.
Se houver interesse em saber mais sobre o Cursor, há conteúdo gravado na Alura. Não é pré-requisito para o curso, mas, se quiser explorar configurações e personalizações, fique à vontade para buscar esse material. Não abordaremos essas configurações aqui, pois este não é um curso sobre o Cursor nem sobre como customizar a utilização de agentes nele; há um conteúdo específico para isso.
Com o Cursor aberto, estamos na janela de agentes e com o projeto do Agenda I selecionado. Ajustamos o teclado, que havia desconectado; alternamos do Bluetooth para o cabo.
Com a IDE aberta (Cursor) e o projeto do Agenda I carregado, abrimos a janela lateral para visualizar os arquivos. Dentro do projeto do Agenda I, criamos uma pasta com um laboratório para testar os pilares da engenharia de prompt, enquanto compreendemos como esses pilares relacionados a loops funcionam.
Temos o arquivo is-overlapping.ts. Nesse arquivo, há uma função que compara dois horários. Para contextualizar o Agenda I: a ideia é ser um produto em formato white label (marca branca). Vamos exemplificar como se fosse um salão de beleza com agendamento de horários, mas é possível adaptar para outros cenários — por exemplo, um lava-rápido com agendamento de lavagem de carros. Nesse formato white label (marca branca), é possível personalizar o sistema conforme a necessidade.
Para visualizar essa função no código, observe:
export interface Interval {
start: Date;
end: Date;
}
/**
* true when the two ranges share at least one instant.
*/
export function isOverlapping(a: Interval, b: Interval): boolean {
return a.start <= b.end && b.start < a.end;
}
No exemplo, há uma função que verifica se um horário sobrepõe outro. Inserimos, de propósito, um bug que valida que um horário deve ser menor ou igual ao próximo horário (<=) para que possa ser agendado. Quando utilizamos menor ou igual (<=), o tipo de comparação muda quando usamos somente um igual (=). Isso ocorre em qualquer linguagem. Estamos utilizando TypeScript para exemplificar no projeto e manter esse bug proposital no arquivo.
Também temos testes escritos de maneira a não atrapalhar o funcionamento da aplicação. São testes “feitos para passar”: as informações presentes neles foram construídas para que o conjunto seja aprovado, e depois avaliamos o que acontece. Esses testes não possuem a qualidade desejável e podemos melhorá-los com nossos ciclos, nossos loops, nossos handlers (manipuladores) e nossos contextos, entre outros pontos que abordaremos.
Veja como os testes cobrem os cenários descritos:
import { describe, it } from "node:test";
import * as assert from "node:assert";
import { isOverlapping } from "./is-overlapping.js";
const at = (hour: number, minute = 0) => {
return new Date(`2024-01-01T${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:00Z`);
};
describe("isOverlapping", () => {
it("detects a partial overlap", () => {
const a = { start: at(9), end: at(10) };
const b = { start: at(9, 30), end: at(10, 30) };
assert.strictEqual(isOverlapping(a, b), true);
assert.strictEqual(isOverlapping(b, a), true);
});
it("ignores ranges far apart", () => {
const a = { start: at(9), end: at(10) };
const b = { start: at(11), end: at(12) };
assert.strictEqual(isOverlapping(a, b), false);
assert.strictEqual(isOverlapping(b, a), false);
});
it("uma reserva que termina as 10:00 não conflita com outra que começa as 10:00", () => {
const morning = { start: at(9), end: at(10) };
const next = { start: at(10), end: at(11) };
assert.strictEqual(isOverlapping(morning, next), false);
assert.strictEqual(isOverlapping(next, morning), false);
});
});
Há alguns arquivos que realizam chamadas reais. Deixamos opções para utilizar Anthropic, Gemini ou o Autofix do Model. Para isso, basta ir ao arquivo .env e inserir a chave da API do serviço escolhido. Caso nenhuma chave seja informada, configuramos um comportamento default (padrão) de um provider (provedor): será utilizado o Gemini gratuito, que não requer chave de API para interação.
Esse comportamento padrão do provedor, baseado na variável de ambiente, está representado assim:
export interface Usage {
inputTokens: number;
outputTokens: number;
}
export interface Reply {
text: string;
usage?: Usage;
}
export interface ToolCall {
name: string;
input: unknown;
}
export interface ToolReply extends Reply {
calls: ToolCall[];
}
export type Provider = "replay" | "anthropic" | "gemini";
function env(key: string): string {
return process.env[key] ?? "";
}
// Replay unless the student opted into a live key. Empty provider is
// interpreted as "gemini".
export function resolveProvider(): Provider {
const raw = env("AUTOFIX_PROVIDER").trim();
if (!raw) return "gemini";
if (raw === "replay" || raw === "anthropic" || raw === "gemini") {
return raw;
}
throw new Error(
`AUTOFIX_PROVIDER inválido: ${raw}. Use replay, anthropic ou gemini.`
);
}
Se você quiser, fique à vontade para configurar uma chave de API, cadastrar um cartão de crédito e assumir os custos. Não vamos solicitar que gaste dinheiro com isso. A configuração atual já permite realizar as interações sem precisar de nada disso.
Para evitar problemas durante nosso avanço, vamos exemplificar algumas questões.
Entendemos como o projeto está montado: temos um problema, e nossa engenharia de prompt (instrução) será o ponto central. Como comentamos, uma utilização vaga — por exemplo, “arrume o bug (defeito)” — tende a produzir respostas genéricas.
Podemos, em vez disso, descrever o problema: por exemplo, queremos ter um horário das 9h às 10h e outro das 10h às 11h. O sistema impede o agendamento das 10h às 11h porque detecta conflito com o de 9h às 10h, já que o segundo começa exatamente às 10h. Contudo, o primeiro termina às 10h e não começa às 10h. Por conta dessa comparação equivocada, a resposta será “a comparação está errada” e precisamos encontrar uma forma de resolver.
Se passarmos exatamente essas informações — “encontramos tal problema no teste X e é preciso corrigir” — a resposta será “essa comparação está errada”. Vamos ver isso na prática.
Vamos abrir um terminal pelo menu “View > Terminal”, no topo da janela. No macOS, o atalho é Command+J. Temos um comando específico para rodar os testes no laboratório; esses comandos estão em package.json, e o comando para executar é lab-ask.
Para executar no terminal:
pnpm run lab:ask
O lab-ask basicamente executa o arquivo ask.ts que estávamos visualizando. Esse ask.ts aciona dois prompts (instruções).
Veja como o arquivo orquestra as chamadas e imprime as respostas e o uso de tokens:
import { call } from "./model.js";
import {
promptWithCode,
readFunction,
specificationPrompt,
vaguePrompt,
} from "./prompts.js";
const code = readFunction();
const calls = [
vaguePrompt,
specificationPrompt(),
].map((prompt) => promptWithCode(prompt, code));
for (const req of calls) {
const { text: reply, usage } = await call(req);
console.log("==================================================");
console.log(req.prompt);
console.log("--------------------------------------------------");
console.log(reply);
console.log(
`Usage: input=${usage?.inputTokens} output=${usage?.outputTokens}\n`
);
}
O primeiro é um prompt (instrução) vago, no qual basicamente dizemos “conserte este bug (defeito)”. O segundo é um bug (defeito) especificado. Nesse especificado, passamos:
No código, esses prompts estão definidos assim:
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const labDir = dirname(fileURLToPath(import.meta.url));
export const vaguePrompt = `Conserta esse bug.`;
// Specification: role, goal, constraints, output format, and one
// example.
export function specificationPrompt(): string {
return `
"Papel": "você corrige funções de agenda.",
"Objetivo: isOverlapping está errada. O intervalo é semiaberto, (start, end]. Uma reserva que termina às 10:00 não conflita com outra que começa às 10:00.",
"Restrições":
"- não mude a assinatura",
"- Não escreva nada fora do formato",
"Formato":
{
"trecho exato do arquivo": "...",
"novo": "..."
}
"Exemplo: 09:00->10:00 e 10:00->11:00 devolvem false. 10:00->11:00 e 11:00->12:00 devolvem true."
`;
}
Ao executar pnpm run lab-ask, o Node roda, faz as chamadas e a IA retorna uma resposta. Vemos a seção “vago” no console.log que colocamos no ask.ts. A chamada usa nosso prompt with code (instrução com código), que invoca o modelo conforme as instruções e a configuração dos modelos de IA que montamos no arquivo de laboratório.
Na resposta do prompt (instrução) vago, a IA indica: “o intervalo parece fechado demais; eu trocaria o comparador ou somaria 1 milissegundo no fim; sem olhar o teste, qualquer uma das duas abordagens serve”. O consumo mostra um input (entrada) de 93 e um output (saída) de 48.
No outro caso, com a especificação, a resposta muda para um formato mais adequado ao processamento. A IA indica:
old: será verdadeiro quando os dois intervalos compartilharem pelo menos um instante. Esse “instante” é justamente o problema na comparação; não queremos que a validação considere o ponto de término como sobreposição.new: será verdadeiro quando o range (intervalo) desses horários tiver pelo menos um momento efetivamente compartilhado (excluindo o ponto de término no caso de intervalo semiaberto).Essa mudança de formulação já é suficiente, segundo a resposta do agente, para ajustar a função conforme os testes e as informações fornecidas.
Outro ponto interessante é o “uso”, geralmente mostrado na última linha. Quando comparamos com a execução na cloud (nuvem) — no exemplo em que analisamos a questão da mala —, vimos que o prompt (instrução) foi maior, mas a resposta foi mais curta. Por quê? Porque, dado o contexto transmitido pelo prompt (instrução), a IA não precisou repetir tantas informações para retornar a solução. O maior custo da IA não está no que digitamos para ela, mas no que ela nos devolve. Neste caso, o nosso input (entrada) foi de 193 e o output (saída) foi de 32; comparando com o output (saída) de 48 do caso vago, são 16 a menos.
Estamos falando de um prompt (instrução) pequeno. Em um dia inteiro de desenvolvimento, em uma equipe inteira, ao longo de uma semana, mês e ano, isso representa milhares, milhões ou bilhões de tokens economizados ao utilizar uma engenharia de prompt (instrução) correta.
Este é o primeiro passo para chegarmos à engenharia de loop (laço). A engenharia de loop (laço) é um processo contínuo e, dentro dele, o passo inicial é termos prompts (instruções) bem escritos e bem desenvolvidos — seja em uma skill (habilidade), seja em um arquivo Agents.md. Precisamos sempre fornecer um prompt (instrução) o mais limpo possível e com o máximo de informações, para que o agente, ao delegar funções dentro do loop (laço), tenha certeza do que precisa ser feito, disponha das informações necessárias e, após tantos loops (laços), devolva o resultado à pessoa solicitante — seja no terminal, seja no ambiente em que estivermos utilizando.
Esse pilar do prompt (instrução) é o mais importante. Ao aprender a montar prompts de forma mais descritiva e detalhada, nós fazemos com que a IA trabalhe de modo mais inteligente, possibilitando que trabalhemos com loops (laços), pois teremos confiança de que as informações virão mais apuradas.
Nosso próximo passo neste percurso é a integração de contextos, tema que abordaremos no próximo vídeo.
Vejo você lá, estudante!
O curso Loop Engineering: automatizando fluxos com agentes possui 413 minutos de vídeos, em um total de 58 atividades. Gostou? Conheça nossos outros cursos de IA para Desenvolvimento de Software em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Todos os cursos, trilhas e certificados para evoluir.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Back-end, Dados, Front-end, DevOps, Mobile, Gestão & Negócios, UX & Design, Cibersegurança, Cloud, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
Acesso à inteligência artificial da Alura.
Modo entrevista técnica e comportamental
Aprenda mais rápido com IA, idiomas e imersões.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Acesso ao catálogo da Casa do Código e leitura dentro da plataforma
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Acesso ao maior portal de vagas tech do Brasil
Desafios de código
System design
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Aceleração de carreira: mentoria, vagas e prática guiada.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais de forma ilimitada.
Lives CareerUp: eventos exclusivos com foco em empregabilidade e carreira
2 mentorias individuais com foco na sua carreira