Pedir Aprovação Antes do Efeito (Add Approval Before Side Effect) é uma refatoração de IA para sistemas com LLM, do grupo Segurança e confiabilidade. Pausar a execução e mostrar a ação para um humano antes de pagar, enviar ou apagar.
O assistente de cobrança agenda um Pix de R$ 1.890 para “pagar a conta de luz”. A chave veio de um PDF anexado. Ninguém confirmou. O dinheiro saiu para um nome que o cliente não reconhece. O agente enviou, pagou ou apagou sem nenhuma confirmação. É o smell Efeito Sem Portão.
Solução
Antes de executar uma ação com efeito, pause a execução, mostre a uma pessoa exatamente o que vai acontecer e só siga com um “sim” explícito registrado pelo sistema. A checagem fica no código da ferramenta, não no prompt.
Antes e depois
def schedule_pix(key: str, amount_cents: int, date: str) -> dict:- return bank.schedule_pix(key, amount_cents, date)+ preview = {+ "acao": "Agendar Pix",+ "destinatario": bank.lookup_key_owner(key), # nome real do dono da chave+ "valor": format_brl(amount_cents),+ "data": date,+ }+ approval = approvals.request(user_id, preview, expires_in="15m")+ if not approval.wait(): # pausa até a resposta+ return {"status": "recusado", "motivo": approval.reason}+ return bank.schedule_pix(key, amount_cents, date,+ idempotency_key=approval.id)
Antes: o modelo chama e o dinheiro sai.
flowchart LR
M(["1. Modelo decide pagar"]):::llm --> T["2. schedule_pix"]:::tool
T --> B[("3. Banco")]:::store
Depois: o modelo propõe, a pessoa decide, o código executa.
flowchart LR
M(["1. Modelo propõe pagar"]):::llm --> P["2. Montar prévia com dados reais"]:::tool
P --> H((("3. Pessoa aprova ou recusa"))):::human
H -->|sim| T["4. Executar com chave de idempotência"]:::tool
T --> B[("5. Banco")]:::store
H -->|não| R(["6. Resultado recusado volta ao modelo"]):::io
O modelo continua decidindo que há algo a fazer.
O código monta a prévia com dados vindos do sistema, não do texto do modelo: o nome real do dono da chave, o valor formatado.
A pessoa vê a prévia num canal confiável (app, push, tela) e decide.
Com o “sim”, o código executa, com uma chave que impede execução duplicada.
O efeito acontece uma vez.
Com o “não”, a ferramenta devolve a recusa, e o modelo segue a conversa sabendo disso.
Por que funciona
Uma regra no prompt depende do modelo lembrar e obedecer. O portão no código não depende de nada: a ferramenta simplesmente não executa sem uma aprovação registrada. Mesmo que o modelo seja enganado por um email malicioso, o pior que ele consegue é pedir. Quem decide é uma pessoa olhando dados reais. O modelo propõe; nunca executa.
A prévia precisa vir do sistema, não do modelo. Se o modelo escrevesse “pagar a conta de luz da Enel”, um atacante poderia fazê-lo escrever isso sobre uma chave Pix qualquer. Buscar o nome do dono da chave no banco mostra a verdade: “Pagar R$ 1.890,00 para Fulano da Silva”.
Isso já saiu do hipotético. Em 2023, o chatbot de uma concessionária Chevrolet em Watsonville “aceitou” vender um Tahoe por 1 dólar. Não havia um humano no circuito olhando o valor real. Em 2024, no caso Moffatt v. Air Canada, um chatbot inventou uma política de reembolso e a empresa foi condenada a cumprir o que a máquina prometeu. Aprovação não apaga responsabilidade jurídica, mas impede o efeito irreversível antes de alguém ver o número.
A chave de idempotência fecha o último buraco: um retry de rede ou um segundo clique não pagam duas vezes. Veja também Classificar Erro Antes de Tentar de Novo.
Passo a passo seguro
Meça. Liste todas as ferramentas com efeito e conte, nos traces, quantas vezes cada uma foi chamada e quantas dessas chamadas o usuário pediu de forma explícita.
Classifique os efeitos por risco: dinheiro, comunicação externa, dados de terceiros, dados do próprio usuário, reversíveis.
Escolha o portão de cada nível: aprovação sempre, aprovação acima de um limite, aprovação pré-autorizada com teto, ou nenhuma para efeitos reversíveis (rascunho, lixeira).
Monte a prévia a partir do sistema, com os dados que a pessoa precisa para decidir. Nome do destinatário, valor, data, o que não tem volta.
Implemente a pausa: síncrona no chat (“confirma?”) ou assíncrona com estado salvo, se a resposta pode demorar minutos. A execução precisa sobreviver a um reload.
Ponha a checagem dentro da ferramenta, para que nenhum caminho de código a pule.
Use chave de idempotência na execução final.
Registre aprovação e recusa com quem, quando e o que foi mostrado. Esse registro é o que você mostra num incidente.
Meça de novo. Efeitos sem aprovação devem ser zero; acompanhe a taxa de recusa e o tempo até a resposta.
Armadilha: confirmar no mesmo chat
Pedir “responda SIM para confirmar” no mesmo fio onde entrou o PDF malicioso não é portão. O conteúdo injetado escreve SIM. Use um canal que só o usuário controla: botão no app, push, tela de revisão. O padrão Humano no Circuito descreve essa pausa.
Fadiga é o outro lado. Confirmar cada rascunho de email treina a pessoa a clicar sem ler. Separe o que é irreversível do que dá para desfazer. Reembolso via Pix e rascunho no Zendesk não compartilham o mesmo portão.
Riscos
Fadiga de aprovação. Peça só onde importa, e deixe a prévia curta e clara.
Prévia enganosa. Mostrar texto gerado pelo modelo em vez de dados do sistema anula o portão.
Canal inseguro. Pedir confirmação no mesmo chat onde um conteúdo malicioso pode escrever “sim” não é aprovação.
Execução que expira. A pessoa aprova horas depois e o contexto mudou (saldo, preço, chave Pix). Revalide antes de executar e ponha prazo na aprovação.
Desafio
Esta implementação tem um portão, mas ele pode ser burlado. Como?
def send_email(to, subject, body, confirmed: bool = False): if not confirmed: return {"status": "precisa_confirmar", "preview": f"Enviar para {to}"} return mailer.send(to, subject, body)
Ver solução
O parâmetro confirmed é preenchido pelo próprio modelo. Basta ele chamar de novo com confirmed=True, e um conteúdo malicioso pode instruí-lo a fazer isso. A confirmação precisa ser um registro do sistema, criado por uma ação da pessoa, que o modelo não consegue forjar.
def send_email(to, subject, body): preview = {"para": to, "assunto": subject, "corpo": body[:500]} approval = approvals.request(current_user, preview) # canal do usuário if not approval.wait(): return {"status": "recusado"} return mailer.send(to, subject, body, idempotency_key=approval.id)
Pedir Aprovação Antes do Efeito (Add Approval Before Side Effect) é uma refatoração de IA para sistemas com LLM, do grupo Segurança e confiabilidade. Pausar a execução e mostrar a ação para um humano antes de pagar, enviar ou apagar.
Que problema Pedir Aprovação Antes do Efeito resolve?