Pular para o conteúdo
ForceTricks
Voltar ao blog

Agentforce e Sistemas Legados: A Camada de Integração Real

12 min de leitura
SérieAgentforce em ProduçãoParte 3 de 3
  1. 1Agentforce: Orquestração Multi-Agentes — Padrões para Produção
  2. 2Agentforce em Produção: Governança, AWUs e o Que Quebra
  3. 3Agentforce e Sistemas Legados: A Camada de Integração Real

Os demos do Agentforce quase sempre mostram o caminho feliz: o agente invoca uma ação, a ação chama uma API, os dados voltam limpos, o agente responde de forma útil. A camada de integração parece trivial.

Aí você aponta o agente para o seu ERP real — uma instância SAP que fala SOAP, um mainframe que exige um salto via middleware, um sistema Oracle caseiro onde "sucesso" é um HTTP 200 com <STATUS>ERROR</STATUS> no corpo XML — e a arquitetura do demo para de funcionar.

Este post cobre os padrões de integração que resistem em produção. Ele pressupõe que você leu as Partes 1 e 2 desta série e entende o modelo de execução multi-agente e o contexto de governança de AWUs.

Escolhendo o Mecanismo de Callout Correto

O Salesforce oferece três caminhos para callouts iniciados por agentes: External Services, Named Credentials com uma ação Apex customizada, e callouts diretos dentro de ações Apex. A documentação apresenta External Services como a abordagem preferida. A documentação não está errada, mas também não está completa.

External Services funciona bem quando o sistema de destino tem uma especificação OpenAPI bem formada e a integração é essencialmente leitura ou escrita simples (um endpoint, payload previsível, resposta estruturada). A ação invocável gerada automaticamente reduz o boilerplate e o schema é exposto nativamente no agent builder. Onde ele falha: o tratamento de erros é superficial. Se o sistema remoto retorna um status não-2xx ou um corpo malformado, External Services produz uma falha genérica com contexto diagnóstico limitado. Para sistemas que retornam 200 com um flag de erro no payload — muito comum em wrappers SOAP-sobre-REST mais antigos — a ação gerada automaticamente trata a chamada como bem-sucedida e passa o corpo de erro para o agente como se fosse dado válido. O agente tentará agir sobre esse dado.

Named Credentials com uma ação Apex de callout customizada é a escolha correta para a maioria das integrações enterprise. Você tem controle total sobre o ciclo HTTP: construção da requisição, parse da resposta, classificação de erros, lógica de retry e tratamento de timeout. O Named Credential cuida da camada de autenticação de forma limpa (OAuth 2.0, Basic, JWT, certificado) sem credenciais no código. O custo operacional é escrever e manter o Apex, mas para qualquer coisa que toque um ERP de produção, esse controle vale a pena.

Callouts Apex diretos sem Named Credentials raramente são a resposta certa para novas implementações. O único caso legítimo é uma integração legada onde o gerenciamento de credenciais já é tratado externamente e você está encapsulando um padrão de callout existente. Se está começando do zero, use Named Credentials — elimina uma classe inteira de riscos de segurança e rotação.

Uma restrição difícil que molda as três escolhas: o Salesforce permite no máximo 10 callouts por transação síncrona. Um agente que encadeia múltiplas ações em um único contexto de execução pode atingir esse limite mais rápido do que você esperaria se cada ação fizer mais de um callout. Projete ações para serem eficientes em callouts — um callout por ação onde possível, com batching quando a API de destino suportar.

Modos de Falha Sem Usuário na Tela

Quando um callout de UI falha, você exibe um erro para o usuário. Ele clica em tentar novamente. O problema aparece imediatamente e tem um humano para resolvê-lo.

Quando um callout iniciado por um agente falha, não há usuário. O modo de falha depende inteiramente de como você projetou a ação para se comportar quando as coisas dão errado.

O padrão ingênuo é lançar uma exceção e deixar o agente lidar com ela. Agentes conseguem capturar erros de ferramentas e tentar recuperação, mas eles estão raciocinando sobre uma falha que não entendem completamente — sabem que a ação falhou, não por que falhou ou se é seguro tentar novamente. Um agente que tenta novamente uma ação de escrita que falhou três vezes pode criar três registros duplicados se a primeira escrita realmente teve sucesso mas a resposta se perdeu. Cada tentativa consumiu AWUs. Agora você tem inconsistência de dados e uma conta maior.

Um padrão mais confiável: classificar falhas antes de retorná-las ao agente.

public class ErpCalloutAction {
    
    @InvocableMethod(label='Ler Pedido no ERP' description='Busca dados de pedido no ERP pelo número do pedido')
    public static List<Result> execute(List<Request> requests) {
        List<Result> results = new List<Result>();
        
        for (Request req : requests) {
            Result r = new Result();
            try {
                HttpRequest httpReq = buildRequest(req.numeroPedido);
                HttpResponse res = new Http().send(httpReq);
                r = parseResponse(res, req.numeroPedido);
            } catch (CalloutException e) {
                // Falha de rede — seguro tentar novamente
                r.sucesso = false;
                r.codigoErro = 'CALLOUT_TIMEOUT';
                r.mensagemErro = 'ERP inacessível. Retry é seguro.';
                r.retryable = true;
            } catch (Exception e) {
                // Falha desconhecida — não tentar novamente automaticamente
                r.sucesso = false;
                r.codigoErro = 'ERRO_INESPERADO';
                r.mensagemErro = e.getMessage();
                r.retryable = false;
            }
            results.add(r);
        }
        return results;
    }
    
    private static Result parseResponse(HttpResponse res, String numeroPedido) {
        Result r = new Result();
        
        if (res.getStatusCode() == 200) {
            // Verificar erro de aplicação no corpo — comum em APIs SOAP/legadas
            String body = res.getBody();
            if (body.contains('<STATUS>ERROR</STATUS>') || body.contains('"error":true')) {
                r.sucesso = false;
                r.codigoErro = 'ERRO_APLICACAO';
                r.mensagemErro = 'ERP retornou erro de negócio para o pedido ' + numeroPedido;
                r.retryable = false; // erros de aplicação não se resolvem com retry
                return r;
            }
            // Parse dos dados reais
            r.sucesso = true;
            r.dadosPedido = body;
        } else if (res.getStatusCode() == 503 || res.getStatusCode() == 429) {
            r.sucesso = false;
            r.codigoErro = 'SERVICO_INDISPONIVEL';
            r.mensagemErro = 'ERP temporariamente indisponível (HTTP ' + res.getStatusCode() + ')';
            r.retryable = true;
        } else {
            r.sucesso = false;
            r.codigoErro = 'HTTP_' + res.getStatusCode();
            r.mensagemErro = 'Status HTTP inesperado do ERP';
            r.retryable = false;
        }
        return r;
    }
    
    public class Request {
        @InvocableVariable(required=true) public String numeroPedido;
    }
    
    public class Result {
        @InvocableVariable public Boolean sucesso;
        @InvocableVariable public String dadosPedido;
        @InvocableVariable public String codigoErro;
        @InvocableVariable public String mensagemErro;
        @InvocableVariable public Boolean retryable;
    }
}

Atenção — callout dentro de loop: o exemplo faz um callout por iteração porque o ERP legado deste cenário expõe um endpoint por número de pedido — não há como agrupar as requisições em uma única chamada. Quando o serviço de destino suporta operações em lote, a prática correta é bulkificar: montar as requisições e fazer um único callout (ou o mínimo possível). Um callout por iteração pode esgotar o limite de 10 callouts por transação síncrona rapidamente em cenários de volume. Este snippet é um exemplo ilustrativo do padrão de classificação de falhas — não um template de produção pronto para copiar.

O flag retryable dá ao agente informação estruturada para tomar uma decisão, em vez de pedir que ele infira segurança de retry a partir de uma mensagem de exceção. Combine isso com o padrão de circuit-breaker em Custom Metadata (descrito na Parte 2) e você impede que o agente martele um sistema degradado.

Padrões Seguros de Escrita

Operações de leitura são tolerantes a falhas. Operações de escrita não são.

Quando um agente escreve em um sistema externo, você precisa responder duas perguntas antes da escrita acontecer: "Esta escrita vai criar um duplicado se executada duas vezes?" e "Qual é o estado atual deste registro?"

Chaves de idempotência respondem à primeira pergunta. Atribua a cada escrita iniciada por agente um correlation ID que você gera uma vez e reutiliza em retries. No lado do ERP, o endpoint deve verificar se uma requisição com esse correlation ID já foi processada e retornar o resultado existente em vez de executar a operação novamente. Muitas APIs modernas suportam isso nativamente (o header Idempotency-Key do Stripe é o exemplo conhecido). ERPs legados geralmente não. Quando o sistema de destino não suporta chaves de idempotência nativamente, você constrói a verificação do seu lado: armazene o correlation ID e o resultado em um registro Callout_Log__c antes de fazer o callout, e verifique esse registro primeiro em qualquer retry.

Locking otimista responde à segunda pergunta. Antes de escrever, verifique que o registro que você está prestes a modificar está no estado que você espera — tipicamente verificando um campo de versão ou um timestamp. Se o estado mudou desde que você leu o registro pela última vez, falhe explicitamente em vez de sobrescrever uma alteração que você não conhecia. A maioria dos ERPs tem algum mecanismo equivalente. Se o seu não tem, implemente uma leitura pré-escrita e compare nos campos que você está prestes a alterar. Isso adiciona um callout (consumindo seu orçamento de 10 callouts), mas uma sobrescrita silenciosa da alteração de outra pessoa é pior do que um erro explícito de conflito.

O que não funciona: o padrão de double-write, onde você escreve no ERP e imediatamente escreve a confirmação no Salesforce na mesma ação. Se a escrita no ERP tem sucesso mas a escrita no Salesforce falha — ou se a escrita no Salesforce tem sucesso mas o ERP nunca recebe a requisição — você tem inconsistência silenciosa de dados. Ambos os sistemas acreditam ter o registro autoritativo, e eles discordam. O padrão correto é fazer a escrita no ERP primeiro, confirmar o sucesso, e só então escrever no Salesforce. Em caso de falha na escrita do ERP, pare e retorne um erro estruturado. Nunca trate ambas as escritas como uma operação atômica única quando você não pode garantir atomicidade.

Gerenciamento de Volume de Contexto

Uma cadeia multi-agente que lê de um ERP cedo na sequência e depois passa esses dados para agentes downstream tem um problema de tokens. Respostas de ERP são verbosas. Um único registro de pedido do SAP pode facilmente ter 15–20 KB de XML. Passe isso por dois ou três hops de agente e você está consumindo uma porção significativa da janela de contexto antes do agente downstream ter feito qualquer coisa.

O padrão de minimização de dados: transforme as respostas do ERP na fronteira da ação. A ação que lê do ERP deve retornar apenas os campos que o agente precisa para tomar sua próxima decisão, não o registro completo. Implemente isso como uma projeção na ação Apex — faça o parse da resposta completa do ERP, extraia os campos relevantes, retorne um objeto JSON enxuto.

// Em vez de retornar o corpo completo da resposta do ERP
r.dadosPedido = res.getBody(); // pode ter 20KB+

// Projetar para o que o agente realmente precisa
Map<String, Object> erpData = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
Map<String, Object> projecao = new Map<String, Object>{
    'pedidoId'        => erpData.get('ORDER_ID'),
    'status'          => erpData.get('ORDER_STATUS'),
    'total'           => erpData.get('TOTAL_AMOUNT'),
    'qtdItens'        => ((List<Object>) erpData.get('LINE_ITEMS')).size()
};
r.dadosPedido = JSON.serialize(projecao);

Se agentes downstream precisam do registro completo, projete a arquitetura para que eles o busquem usando o ID do pedido, em vez de receber o payload completo pela cadeia. Isso mantém os payloads de contexto enxutos em cada hop e reduz o risco de atingir o limite de 32K tokens em agentes downstream — modo de falha coberto na Parte 1.

Para cadeias de agentes que fazem múltiplas leituras de ERP em uma sessão, faça cache dos resultados onde apropriado e passe identificadores em vez de payloads entre agentes. O coordenador busca contexto uma vez; os workers recebem IDs e buscam o que especificamente precisam.

Um Padrão Concreto de Leitura com Escrita Condicional

O cenário: um agente lê um pedido do ERP, avalia se ele se qualifica para envio expresso com base no nível da conta e — se sim — atualiza a prioridade de envio no Salesforce e escreve o flag de volta no ERP. Dois alvos de escrita. Um caminho de lógica condicional. Um orçamento de callouts a respeitar.

A sequência de ações que funciona dentro do limite de 10 callouts:

  1. Ação 1 — Leitura do pedido no ERP (1 callout). Projeção para campos relevantes. Retorna dados do pedido + pedidoId.
  2. O agente raciocina sobre o nível da conta e a elegibilidade. Sem callout. Sem AWU de I/O externo.
  3. Ação 2 — Ação de escrita condicional. Se a elegibilidade for verdadeira: (a) escreve prioridade de envio no ERP (1 callout), (b) atualiza o registro de Opportunity ou Order no Salesforce via DML (sem callout — DML não é callout). Retorna um resultado estruturado de sucesso/falha com a chave de idempotência usada.
  4. Ação 3 (se a Ação 2 falhou com retryable: true) — Ação de retry, usando a mesma chave de idempotência do passo 3. Verifica o registro Callout_Log__c primeiro para determinar se a escrita no ERP já teve sucesso antes de tentar o callout novamente.

Total de callouts no caminho de sucesso: 2. Total de callouts no caminho de retry: 3. Bem dentro do limite, com margem para ações adicionais na mesma cadeia.

O DML do Salesforce no passo 3b roda dentro da transação da ação. Se o DML falha após uma escrita bem-sucedida no ERP, você escreve um registro de compensação em Agent_Dead_Letter__c (do padrão de governança da Parte 2) com a chave de idempotência da escrita no ERP e o ID do registro Salesforce. Um job de reconciliação processa esses registros e ou completa a escrita no Salesforce ou sinaliza para revisão humana. Você não tenta desfazer a escrita no ERP — você completa o lado Salesforce, que é a direção mais segura de inconsistência.


Isso fecha a série Agentforce em Produção. As Partes 1 a 3 cobrem as decisões de arquitetura que realmente importam quando você leva essas implementações além do sandbox: padrões de orquestração, governança e gerenciamento de AWUs, e a camada de integração que conecta agentes a dados enterprise reais.

Implementando algo similar e encontrando modos de falha diferentes? LinkedIn.

Compartilhar
Gabriel Cruz Ferreira

Gabriel Cruz Ferreira

Salesforce Architect · 15x Certified · Rota para CTA

Este post foi útil?