Padrões assíncronos para chamar agentes do Amazon Bedrock AgentCore em pipelines serverless

O problema: agentes de IA pensam antes de responder

Integrar um agente de Inteligência Artificial (IA) a um pipeline serverless parece simples à primeira vista — basta invocar o agente e aguardar a resposta. O problema é que agentes de IA, ao contrário de funções tradicionais, levam tempo para raciocinar. Dependendo do prompt, do modelo e do documento processado, esse tempo raramente é instantâneo. E essa latência muda completamente a forma como você deveria chamar o agente.

A implementação mais comum — e mais ingênua — é usar uma função AWS Lambda que invoca o agente e fica bloqueada aguardando a resposta. Enquanto a função espera, ela não faz absolutamente nada, mas continua sendo cobrada por cada segundo de execução. É exatamente esse desperdício que a AWS aborda em um post técnico recente sobre o Amazon Bedrock AgentCore.

Por que o custo ocioso acontece — e onde ele realmente cai

Para entender o problema, é importante separar os dois lados da chamada, pois cada um tem um modelo de cobrança diferente.

O Amazon Bedrock AgentCore Runtime opera com um modelo baseado em consumo: enquanto o agente aguarda a resposta de um modelo de linguagem de grande escala (LLM) ou de uma chamada de ferramenta via Protocolo de Contexto de Modelo (MCP), você paga pela memória alocada, mas não pelo processamento central (CPU). O agente, portanto, não desperdiça compute enquanto espera.

O problema está do outro lado: a função Lambda, o container ou a instância do Amazon Elastic Compute Cloud (Amazon EC2) que fez a chamada síncrona fica completamente bloqueada. Ela mantém toda a sua alocação de compute — e você paga por isso — até o agente responder. O custo do chamador acaba rastreando diretamente o tempo de processamento do agente.

A solução é liberar o compute do chamador durante a espera e retomar o pipeline apenas quando o agente tiver um resultado. A AWS apresenta três padrões que fazem exatamente isso.

O pipeline de exemplo

Para comparar os padrões em condições iguais, a AWS usa um pipeline de validação de documentos para financiamento imobiliário — um cenário simples e deliberadamente genérico, que serve como substituto para qualquer fluxo que chame um agente lento e precise agir sobre o resultado.

O pipeline tem cinco estágios:

  • Extração: uma função Lambda realiza o reconhecimento óptico de caracteres (OCR) e extrai o texto do documento.
  • Identificação: outra função Lambda classifica o documento e define flags de roteamento (shouldOrganize, shouldValidate).
  • Roteamento: um estado Choice direciona o fluxo com base nessas flags.
  • Organização e Validação: um estado Parallel organiza o documento enquanto, em um ramo separado, o agente do AgentCore valida o conteúdo. Esse ramo de validação é o único que muda entre os padrões.
  • Resultado: uma função Lambda processa o veredicto do agente e decide a próxima ação.

Um único agente do Amazon Bedrock AgentCore atende todos os quatro cenários. Ele inspeciona cada invocação e escolha como responder: se receber um task token do AWS Step Functions, acorda aquela execução ao terminar. Se receber um callback ID de uma durable function, acorda a função. Se não receber nenhum dos dois, retorna o veredicto diretamente na resposta. Isso significa que é possível trocar o padrão de orquestração sem precisar reimplantar o agente.

O anti-padrão: chamada bloqueante

A implementação mais direta chama o agente e aguarda a resposta na mesma função Lambda. Funciona e é fácil de implementar — por isso é tão comum. Mas a função permanece ativa e sendo cobrada durante todo o tempo em que o agente está processando.

// The Lambda function blocks here until the agent responds
const response = await agentcore.send(
  new InvokeAgentRuntimeCommand({
    agentRuntimeArn: AGENT_RUNTIME_ARN,
    payload: new TextEncoder().encode(JSON.stringify(payload)),
    runtimeSessionId: sessionId,
  })
);
// The function stays alive and billed for the entire time the agent is thinking.

A duração cobrada da função acaba sendo aproximadamente igual ao tempo de processamento do agente. Os três padrões a seguir eliminam esse custo ocioso.

Padrão 1: Task-token callback com função despachante

Este padrão mantém uma função Lambda no caminho para lógica customizada, mas remove o custo ocioso. O Step Functions invoca a função com a integração waitForTaskToken, que passa um task token e pausa a execução. A função usa o token para iniciar o agente e retorna em poucos segundos. A execução permanece pausada — sem cobrar nada por compute — até que o agente chame SendTaskSuccess com aquele token para retomá-la.

// Start the agent, pass the task token, and return without waiting
const response = await agentcore.send(
  new InvokeAgentRuntimeCommand({
    agentRuntimeArn: AGENT_RUNTIME_ARN,
    payload: new TextEncoder().encode(JSON.stringify({
      ...payload,
      taskToken
    })),
    runtimeSessionId: sessionId,
  })
);
// Returning here does not complete the step. Step Functions stays paused until
// the agent calls SendTaskSuccess with this task token.
return { dispatched: true };

O estado correspondente no Step Functions passa o token do contexto e define um timeout e heartbeat como proteção, para que um agente silencioso falhe a execução de forma limpa em vez de deixá-la pausada indefinidamente:

"ValidateDispatch": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
  "Parameters": {
    "FunctionName": "${ValidateDispatcherFunctionArn}",
    "Payload": {
      "taskToken.$": "$$.Task.Token",
      "document.$": "$.document",
      "extractedText.$": "$.extract.extractedText",
      "executionId.$": "$$.Execution.Id"
    }
  },
  "TimeoutSeconds": 120,
  "HeartbeatSeconds": 60,
  "Next": "AgentCoreValidation"
}

Custo: a função Lambda roda apenas o tempo necessário para iniciar o agente e retornar — alguns segundos, independentemente de quanto tempo o agente leva depois. Você paga pelo despacho breve, não pela espera.

Padrão 2: Integração direta de serviço

Quando não há necessidade de código customizado ao redor da chamada ao agente, é possível remover a função despachante completamente. O Step Functions pode chamar o Amazon Bedrock AgentCore diretamente por meio de sua integração com o SDK da AWS — especificamente o AgentCore Harness (InvokeHarness) — e a resposta do agente flui diretamente para o próximo estado. O ramo de validação se torna um único estado Task:

"ValidateDirect": {
  "Type": "Task",
  "Resource": "arn:aws:states:::aws-sdk:bedrockagentcore:invokeAgentRuntime",
  "Parameters": {
    "AgentRuntimeArn": "${AgentRuntimeArn}",
    "RuntimeSessionId.$": "States.Hash($$.Execution.Id, 'SHA-256')",
    "Payload.$": "States.JsonToString($.prep.agentInput)"
  },
  "ResultSelector": {
    "raw.$": "$.Response"
  },
  "TimeoutSeconds": 120,
  "Next": "ParseVerdict"
}

Custo: não há função Lambda no caminho, logo não há compute ocioso de Lambda para pagar. O Step Functions segura a espera, e um workflow Standard cobra por transição de estado — não pela duração da espera. O custo significativo durante o processamento é o próprio agente.

Padrão 3: Lambda durable function

Se você prefere expressar a orquestração como código em um único lugar em vez de uma máquina de estados, uma Lambda durable function oferece o mesmo comportamento de custo. Com o SDK @aws/durable-execution-sdk-js, os estágios do pipeline se tornam chamadas context.step, o trabalho paralelo se torna context.parallel, e a espera pelo agente se torna context.waitForCallback. Durante essa espera, a função é suspensa e não cobra por compute. O agente a retoma com SendDurableExecutionCallbackSuccess.

// Suspend the function until the agent calls back
const result = await ctx.waitForCallback(
  "validate-agentcore",
  async (callbackId) =>
    dispatchAgentCore(callbackId, document, extractedText, executionId),
  { timeout: { seconds: 120 } }
);

Custo: uma única função mantém todo o pipeline, mas não cobra por compute enquanto está suspensa aguardando o agente. Você paga pelos curtos períodos de execução entre as suspensões — a mesma economia do padrão task-token.

Medindo a diferença na prática

A AWS apresenta um exemplo concreto de uma execução real com o padrão task-token para ilustrar a relação entre os valores:

  • Estado ValidateDispatch no Step Functions ficou ativo por 19,6 segundos
  • Duração cobrada da função Lambda despachante: 4,8 segundos
  • Tempo de espera sem nenhuma função Lambda rodando: ~14,8 segundos

O ponto não é o número em si — o tempo de processamento do agente varia com o prompt, o modelo e o documento. O que importa é a relação: o tempo cobrado do despachante permanece fixo e curto, enquanto o tempo de processamento do agente pode crescer. Com a chamada bloqueante, esses dois valores seriam praticamente iguais.

Como escolher o padrão certo

A AWS resume os trade-offs da seguinte forma:

  • Anti-padrão bloqueante: adequado apenas para protótipos ou agentes com tempo de resposta muito curto. Custo do chamador equivale ao tempo total do agente.
  • Padrão 1 — Task-token: ideal quando você precisa de lógica customizada antes e depois da chamada ao agente. Requer configuração de Gerenciamento de Identidade e Acesso (IAM), heartbeat e timeout. Custo do chamador: apenas o despacho.
  • Padrão 2 — Integração direta: melhor para orquestração pura, sem código customizado. Um único estado Task, sem Lambda no caminho. Custo: zero de Lambda.
  • Padrão 3 — Durable function: preferível quando você quer toda a orquestração como código em um único lugar. Bom para fluxos assíncronos complexos. Custo do chamador: apenas os curtos períodos de execução.

Boas práticas recomendadas

A AWS lista algumas práticas importantes para quem for adotar esses padrões em produção:

  • Proteja-se contra agentes que nunca respondem: defina TimeoutSeconds em todo estado waitForTaskToken para que a execução falhe com States.Timeout em vez de ficar pausada indefinidamente. Se o agente envia heartbeats, configure também HeartbeatSeconds para detecção mais rápida de falhas.
  • Use um ID de sessão estável entre tentativas: defina o sessionId com um valor derivado do contexto de execução (como o nome da execução no Step Functions) para que novas tentativas retomem a mesma sessão do agente em vez de iniciar uma nova.
  • Ative o AWS X-Ray: habilite o rastreamento ativo no Step Functions e no Lambda. O X-Ray mostra exatamente quanto tempo o agente passou processando versus quanto tempo o chamador ficou esperando, confirmando que o padrão realmente liberou compute durante o intervalo.
  • Dimensione a função despachante para velocidade, não para a carga do agente: o despachante apenas serializa uma requisição e invoca um endpoint. 256 MB de memória e 30 segundos de timeout geralmente são suficientes. O trabalho pesado acontece no lado do agente.

Considerações de custo

Esses padrões geram cobranças por compute Lambda, transições de estado no Step Functions e armazenamento de execução de durable functions. O custo do Amazon Bedrock AgentCore Runtime e da inferência do modelo é o mesmo em todos os quatro casos — a arquitetura muda apenas a sobrecarga de orquestração e, no anti-padrão bloqueante, o compute ocioso desperdiçado do Lambda.

Código completo disponível no GitHub

O exemplo completo, incluindo o agente, as definições da máquina de estados e a durable function, está disponível no repositório sample-bedrock-agentcore-async-stepfunctions no GitHub. Para implantações em produção, a AWS recomenda o uso do Amazon Bedrock Guardrails para aplicar controles de IA responsável nas entradas e saídas do agente.

Fonte

Asynchronous patterns for calling Amazon Bedrock AgentCore agents in serverless pipelines (https://aws.amazon.com/blogs/machine-learning/asynchronous-patterns-for-calling-amazon-bedrock-agentcore-agents-in-serverless-pipelines/)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *