Lição 6: Mão na massa: construindo um pipeline de revisão de dois agentes
Objetivos de aprendizado:
- Escrever um pipeline de dois agentes produtor-revisor genuinamente executável com a API do Claude
- Fazer o revisor devolver um resultado de revisão estruturado e verificável em vez de um genérico “parece bom”
- Colocar uma válvula de segurança no loop para que o produtor e o revisor não fiquem polindo um para o outro para sempre
Pré-requisitos: concluir as Lições 1 a 5, ser capaz de ler JavaScript/Node.js básico e ter uma chave da API do Claude funcionando | Anterior: Lição 5 <<
Primeiro, o resultado: uma execução completa
Isto é o que você terá rodando ao final da lição. Você entrega uma tarefa ao terminal, e dois agentes se revezam até a revisão passar ou você atingir o limite de rodadas:
A primeira versão é rejeitada pelo revisor, com razões atreladas a cada critério específico; o produtor revisa em uma segunda versão, o revisor olha de novo e, desta vez, ela passa. Este é o padrão produtor-revisor da Lição 4 transformado em código: "one LLM call generates a response while another provides evaluation and feedback in a loop."1
A forma geral: o mesmo esqueleto de um loop de execução
Se você fez o curso Tool calling de agentes: fazendo agentes agirem de verdade desta série, o esqueleto deste pipeline vai parecer familiar: um loop, um julgamento por rodada, um resultado que decide se continua, mais uma válvula de segurança contra o loop infinito. A única diferença é o que o julgamento julga — o loop de execução de ferramentas daquele curso julga “o modelo ainda quer chamar uma ferramenta” (a semântica do loop está na lição A ida e volta completa de uma chamada de ferramenta daquele curso e em suas fontes oficiais), enquanto aqui ele julga “o revisor disse que passou”. Mesmo esqueleto, conteúdos diferentes no corpo do loop.
O pipeline inteiro são três funções costuradas juntas: runProducer gera ou revisa o texto, runReviewer o pontua em relação a critérios e dá notas específicas, e runPipeline liga as duas em um loop com um teto de rodadas como válvula de segurança.
Passo 1: O produtor — receber a tarefa, produzir o texto
Na primeira execução, o produtor tem apenas a própria tarefa; em uma segunda execução após uma rejeição, ele também carrega a versão anterior completa e as notas de revisão, de modo que o produtor revisa em cima da última versão de acordo com as notas em vez de improvisar do zero:
O prompt do produtor é autocontido. Como a Lição 3 cobriu, um subagente não consegue ver o que aconteceu do lado do orquestrador, e também não consegue ver como foi revisado da última vez2. Então cada chamada escreve “qual é a tarefa”, “o que a versão anterior dizia” e “(se houver) quais foram os problemas da última rodada” no prompt desta chamada, literalmente. Note que até o próprio rascunho anterior do produtor precisa ser repassado explicitamente — esta é a metade do princípio do autocontido que é mais fácil de esquecer: a Messages API é sem estado, cada requisição precisa carregar todo o histórico de que precisa, e o servidor não guarda nada entre requisições3. “Revise sua versão anterior” só significa algo quando a versão anterior foi de fato escrita neste prompt.
Passo 2: O revisor — pontuar em relação a critérios concretos, sem veredictos vagos
O revisor não apenas pergunta ao modelo “isso é bom”. Como a Lição 5 cobriu, a verificação precisa pousar em critérios concretos e verificáveis em vez de uma nota baseada em impressão4. Aqui o revisor recebe uma lista de verificação explícita e é obrigado a responder em um formato JSON fixo:
Juntos, os campos approved e issues formam um resultado de revisão estruturado: não um único “está ok”, mas “passou ou falhou” mais “o problema específico por trás de cada critério que falhou”. Uma vez que o produtor tem issues, ele revisa esses problemas específicos em vez de adivinhar para onde ir a partir de um veredicto vago.
Passo 3: Não confie cegamente no resultado da revisão — trate uma falha de parse como uma rejeição
runReviewer devolve uma string, não um objeto JSON de verdade, então ainda precisa ser feito o parse. Mesmo que o revisor seja instruído a “responder estritamente em JSON”, sem uma restrição de saída estruturada o modelo ainda pode produzir JSON sintaticamente inválido, omitir campos ou embrulhar o JSON em um bloco de código com algumas linhas de explicação ao redor5. A armadilha aqui é: o que acontece quando o parse falha? Tomar o caminho preguiçoso — deixar passar por padrão numa falha de parse — silenciosamente transforma uma falha de “o revisor não fez seu trabalho” em “revisão passou”. Esse é exatamente o ponto que a Lição 5 fez: uma saída que “parece” pronta não é o mesmo que uma saída que de fato está correta, e o que você não consegue verificar não deveria entregar6. Aqui fazemos o oposto: uma falha de parse sempre conta como uma rejeição, nunca como um passe:
As linhas typeof parsed.approved !== "boolean" e !Array.isArray(parsed.issues) estendem a mesma ideia — mesmo quando JSON.parse tem sucesso, você ainda confirma que os campos parseados têm a forma certa, e um tipo de campo errado também conta como uma rejeição. Não baixe a guarda só porque é “pelo menos JSON válido”.
Um à parte: existe um recurso oficial de saídas estruturadas que garante, no nível da amostragem, que a resposta corresponde estritamente a um schema5. Esta lição usa deliberadamente o estilo “chamada crua mais seu próprio parse defensivo” para você sentir na pele que a saída do modelo não pode ser confiada cegamente; em produção, você pode usar saídas estruturadas para remover essa cova por completo.
Passo 4: Ligue tudo em um loop, adicione a válvula de segurança
Com runProducer, runReviewer e parseReview em mãos, runPipeline liga os três, e MAX_ROUNDS é a única válvula de segurança aqui — o produtor e o revisor poderiam, em teoria, polir para sempre, então precisa haver um teto:
Quando atinge MAX_ROUNDS ainda sem passar, runPipeline não força um veredicto de “passou”. Ele honestamente entrega o último rascunho e os problemas ainda não resolvidos para revisão humana — este também é o ponto da Lição 5 aplicado na etapa de fechamento: quando a etapa de integração de resultados esbarra em algo que não consegue julgar, ela não deveria disfarçar isso decidindo por si mesma no código.
Recapitulação
- O esqueleto do pipeline produtor-revisor é a mesma coisa que um loop de execução: um loop, um julgamento por rodada, um resultado que decide se continua, mais uma válvula de segurança contra o loop infinito. A definição oficial deste padrão é exatamente "one LLM call generates a response while another provides evaluation and feedback in a loop"1 — aqui o julgamento muda de “uma ferramenta deveria ser chamada” para “o revisor disse que passou”.
- O prompt do produtor é autocontido: cada chamada escreve a tarefa, a versão anterior completa e (se houver) os problemas específicos da última rodada no prompt, literalmente — a Messages API é sem estado, cada requisição precisa carregar todo o histórico, e nada é guardado entre requisições3, então você não pode contar com o modelo lembrando por conta própria o que aconteceu na última rodada2.
- O revisor pontua em relação a critérios concretos e verificáveis, item a item, e devolve um
{approved, issues} estruturado em vez de um veredicto genérico4.
- O que o revisor devolve também não pode ser confiado cegamente — uma falha de parse ou uma forma de campo errada deveria contar como uma rejeição, não um passe silencioso6; esse princípio se aplica não só a “confiar no que um subagente diz”, mas também a “confiar no formato de dados que um subagente devolve”.
- Quando atinge a contagem máxima de rodadas ainda sem passar, o pipeline deveria honestamente entregar o último rascunho e os problemas não resolvidos para revisão humana, em vez de decidir um passe por si mesmo no código.
E é isso, as seis lições deste curso: de “por que vários agentes”, passando por como o orquestrador e os subagentes dividem o trabalho, como escrever prompts de delegação, qual padrão de colaboração se encaixa em qual cenário e como lidar com falhas, terminando com a construção à mão de um pipeline produtor-revisor funcionando. A coisa mais valiosa a fazer em seguida não é reler as explicações — é pegar uma tarefa pequena e real que você tem em mãos, jogá-la neste esqueleto de pipeline, ajustar os critérios de revisão e rodá-lo para ver se ele rejeita o rascunho e quantas vezes. Ajustar você mesmo os critérios de revisão uma vez vale mais que reler a teoria dez vezes.