A tool call pode atingir a API e perder a resposta no caminho de volta. O agente vê um timeout e conclui que nada aconteceu. Se repetir a operação, pode criar um segundo pedido, enviar outra mensagem ou gravar o mesmo dado duas vezes.

A correção é tratar a resposta e o efeito como fatos diferentes. Antes de executar, o runtime cria um identificador estável para a ação. Depois, guarda o recibo retornado pelo executor ou consulta a pós-condição no sistema externo. O estado final precisa dizer se o efeito foi confirmado, não foi encontrado ou continua desconhecido.

Este problema é mais específico que evitar tool calls duplicadas no fallback de LLM. O fallback decide quando trocar a tentativa de modelo. Aqui, a pergunta é o que fazer quando a operação externa talvez já tenha começado. Para escolher a camada que conserva esse estado entre tentativas, veja também execução durável para agentes de IA. O código é ilustrativo e não foi executado contra um provedor ou sistema de produção.

Diagrama mostra uma tool call cruzando uma fronteira de evidência externa antes de seguir ou ser verificada.

Resposta curta

  • Uma resposta ausente informa o que o cliente recebeu, não o que o servidor fez.
  • Persista um actionId antes de enviar uma operação com efeito externo.
  • Consulte uma pós-condição independente e classifique o resultado como confirmed, not_found ou unknown.
  • Só repita depois de not_found, ou quando a API usar a mesma chave de idempotência para deduplicar a tentativa.

Por que o timeout não diz se a tool call funcionou?

Um timeout separa o envio da confirmação. A requisição pode ter sido descartada antes do servidor agir, aceita e processada sem resposta, ou aplicada apenas em parte. Por isso, o runtime não deve converter todo erro de transporte em failed. Em 2026, o artigo de Isham Kalappurackal Mansoor, Abhishek Phadke e Pratip Rana, "Verified Tool Calls Improve LLM Agent Reliability Under Non-Atomic Failures", descreve essa lacuna entre o canal de resposta e o canal de efeito (arXiv, 2026).

O erro fica perigoso quando a tool muda estado. Uma busca sem efeito pode ser repetida com pouca preocupação. Já criar uma cobrança, liberar acesso, enviar um e-mail ou alterar uma linha exige saber qual operação lógica está sendo retomada. O texto que o modelo recebeu não é um recibo do serviço.

Em 2026, no cenário simulado record_invoice do mesmo artigo, o método sem verificação duplicou efeitos em 32%, 52% e 76% das execuções nos níveis baixo, médio e alto de falha. O wrapper com verificação registrou 0%, 16% e 20%. Esses números vêm de um ambiente controlado com falhas injetadas. Eles não são uma taxa de produção, mas mostram por que a ordem "verificar, depois repetir" merece um contrato próprio.

O ponto que costuma faltar é uma terceira saída. success e failure descrevem o que o cliente observou. unknown descreve o limite do conhecimento: a ação pode ter acontecido, mas ainda não há evidência suficiente para repetir ou encerrar.

O que o runtime precisa registrar antes da operação?

Registre a intenção antes do envio, com uma chave que permaneça a mesma em todas as tentativas da mesma ação lógica. O registro mínimo precisa ligar a tarefa do agente ao efeito esperado, sem depender do identificador de uma mensagem específica do provedor.

Uma ferramenta do domínio pode receber um actionId, um conjunto de argumentos normalizados e a pós-condição que será verificada. O identificador da chamada do modelo continua útil para rastrear a conversa, mas não deve ser a única chave. Se o modelo produzir outra mensagem durante um retry, a ação lógica ainda precisa ser reconhecida.

type EffectState = "planned" | "sent" | "confirmed" | "not_found" | "unknown";

type ActionRecord = {
  actionId: string;
  toolName: string;
  inputHash: string;
  expectedEffect: string;
  state: EffectState;
  externalId?: string;
  receiptId?: string;
  updatedAt: string;
};

async function prepareAction(
  actionId: string,
  toolName: string,
  inputHash: string,
  expectedEffect: string,
): Promise<ActionRecord> {
  const current = await actionStore.get(actionId);
  if (current) return current;

  const record: ActionRecord = {
    actionId,
    toolName,
    inputHash,
    expectedEffect,
    state: "planned",
    updatedAt: new Date().toISOString(),
  };

  await actionStore.insertIfAbsent(record);
  return record;
}

O insertIfAbsent precisa ser atômico no armazenamento que coordena os workers. Um Map dentro do processo não cobre reinício, duas réplicas ou uma nova entrega da fila. O exemplo também não resolve concorrência por si só. Ele apenas torna explícito o registro que a implementação real precisa proteger.

O AWS Well-Architected Framework, "Make mutating operations idempotent", recomenda tokens únicos e o acompanhamento desses tokens para que mensagens repetidas não produzam a mesma ação duas vezes. A chave ajuda o serviço a reconhecer a repetição. Ela não substitui uma consulta quando o primeiro resultado é ambíguo.

Como representar um recibo confiável?

Um recibo deve dizer o que o executor conseguiu observar. Ele não precisa guardar o payload secreto nem repetir a narrativa do modelo. Precisa conter a identidade da ação, a decisão do serviço, o identificador externo quando existir e a hora em que essa evidência foi registrada.

Um resultado útil separa o transporte do efeito:

Estado O que sabemos Próximo passo
confirmed A pós-condição ou o recibo prova o efeito Avançar sem executar a ação de novo
not_found Uma consulta confiável não encontrou o efeito Reavaliar a política e tentar com a mesma chave
unknown A consulta não permite concluir Parar, aguardar, reconciliar ou pedir revisão
rejected A operação foi recusada antes do efeito Corrigir entrada ou autorização

Não transforme um HTTP 200 em confirmed sem definir o que foi confirmado. Uma API pode aceitar um comando para processamento assíncrono, devolver um identificador e aplicar o efeito depois. Nesse caso, o recibo confirma aceitação, enquanto a pós-condição confirma o resultado que o agente precisa.

O SDK de agentes da OpenAI expõe itens separados para chamadas e resultados de tools, além de requestId quando o provedor o fornece. A documentação de Results, publicada pelo OpenAI Agents SDK, também trata o estado serializável como uma superfície para retry e retomada. Isso ajuda na auditoria do run, mas a aplicação continua responsável por provar o efeito no serviço que sofreu a mutação.

Como verificar a pós-condição antes de repetir?

Consulte uma condição que não dependa apenas da resposta perdida. A consulta pode procurar um identificador externo, uma linha com a chave da operação, um status de workflow ou outro fato que o serviço reconheça. Quando a leitura é eventualmente consistente, espere a janela definida pelo contrato ou marque o estado como desconhecido. Uma leitura vazia imediata não é sempre prova de ausência.

O fluxo deve ter uma ordem rígida:

  1. Grave ou recupere o registro da ação lógica.
  2. Envie a tool call com a mesma chave de idempotência quando o serviço aceitar essa chave.
  3. Guarde o recibo se a resposta chegar.
  4. Se a resposta for perdida, consulte a pós-condição com actionId ou externalId.
  5. Em confirmed, avance. Em not_found, repita conforme a política. Em unknown, não invente um resultado.
type Verification =
  | { state: "confirmed"; externalId?: string }
  | { state: "not_found" }
  | { state: "unknown"; reason: string };

async function retryIfAbsent(actionId: string): Promise<Verification> {
  const action = await actionStore.get(actionId);
  if (!action) throw new Error("action was not prepared");

  const observed = await verifyPostcondition(action);

  if (observed.state === "confirmed") {
    await actionStore.markConfirmed(actionId, observed.externalId);
    return observed;
  }

  if (observed.state === "unknown") {
    await actionStore.markUnknown(actionId, observed.reason);
    return observed;
  }

  const result = await executeTool({
    actionId,
    idempotencyKey: actionId,
    inputHash: action.inputHash,
  });

  await actionStore.saveReceipt(actionId, result.receiptId);
  return { state: "confirmed", externalId: result.externalId };
}

Esse código é uma fronteira didática. Em produção, a verificação e o envio podem competir com outro worker, e o armazenamento pode mudar entre as duas etapas. A operação remota precisa aceitar a chave ou o executor precisa manter uma forma de propriedade e reconciliação que cubra a janela de corrida.

Não há um benchmark próprio sendo alegado neste artigo. A regra prática é um limite de revisão: se a equipe não consegue apontar qual fato externo transforma unknown em confirmed, o retry ainda depende de uma suposição.

Quando a chave de idempotência resolve o problema?

A chave resolve a deduplicação quando o serviço receptor a inclui no seu contrato e persiste o resultado associado a ela. A mesma chave deve representar a mesma intenção lógica. Não gere uma chave nova só porque o modelo foi consultado de novo.

O serviço ainda precisa definir o que ocorre quando a mesma chave chega com argumentos diferentes. Uma política segura rejeita o conflito ou devolve o resultado original. Aceitar a segunda carga como se fosse uma nova intenção destrói a utilidade da chave.

A orientação da AWS sobre operações idempotentes afirma que serviços e consumidores devem repassar o token para os serviços seguintes e evitar repetir um efeito ao processar a mesma mensagem (AWS Well-Architected Framework, 2026). Isso é uma responsabilidade de cada fronteira, não uma garantia automática do agente.

Se a API não oferece idempotência, a verificação ainda pode reduzir duplicatas. Porém, existe uma janela entre a consulta que diz not_found e duas tentativas concorrentes. Nesse caso, serialize a posse da ação, use uma restrição única no sistema que recebe a escrita ou deixe o estado unknown para reconciliação. Não prometa exatamente uma execução quando a infraestrutura não oferece essa semântica.

Como testar timeout, leitura atrasada e efeitos parciais?

Teste o contrato com um executor falso que separa o momento do envio do momento em que o estado se torna observável. O caso importante não é só uma exceção lançada antes da chamada. É a interrupção depois do envio e antes do recibo.

Uma matriz pequena cobre as decisões principais:

Cenário Observação simulada Resultado esperado
Falha antes do envio Não existe efeito externo Pode repetir com a mesma ação
Timeout depois do envio O efeito existe, mas não há recibo Consultar antes de repetir
Leitura atrasada A primeira consulta não enxerga o efeito Manter unknown ou aguardar a janela contratada
Efeito parcial Apenas parte da pós-condição foi cumprida Reconciliar, compensar ou parar
Duas tentativas concorrentes Dois workers usam o mesmo actionId Um executor possui a ação; o outro não duplica
Mesma chave com entrada diferente O hash não coincide Rejeitar conflito

O teste deve observar o efeito do domínio, não apenas a quantidade de chamadas a um mock. Se a tool cria uma linha e publica uma mensagem, verifique os dois fatos ou documente qual deles é a pós-condição principal. Um mock que sempre retorna "ok" não exercita a fronteira que causa a falha.

O estudo de 2026 usado acima avaliou o wrapper em um ambiente simulado com falhas injetadas e tarefas de tool diferentes. Ele é evidência de que a técnica é testável, não uma prova de que qualquer arquitetura terá a mesma taxa. Use a ideia para montar seus próprios cenários com o banco, a fila ou a API que o agente realmente chama.

O que registrar quando o efeito continua desconhecido?

Registre unknown como um estado operacional visível. Guarde o motivo, o último instante de consulta, a chave da ação, o tipo de efeito e a próxima ação permitida. O operador precisa conseguir encontrar o caso sem perguntar ao modelo o que ele acha que aconteceu.

O OpenAI Agents SDK, "Tools" documenta timeout por tool, resultados de erro e metadados de execução. Esses recursos ajudam a delimitar a execução local. Eles não confirmam uma alteração em um serviço externo. A aplicação deve combinar o evento da tool com o recibo ou a leitura da pós-condição.

Não esconda o estado em um resumo de conversa. O transcript pode dizer que o agente pediu create_invoice, mas não prova se a fatura foi criada. Para investigar, uma pessoa precisa de actionId, hash dos argumentos, status observado, identificador externo e tentativas feitas. Remova segredos e dados pessoais dos logs.

Uma boa observabilidade de agentes não responde apenas "qual tool foi chamada?". Ela responde "qual efeito foi provado, por qual evidência, e qual ação ficou proibida enquanto o estado era desconhecido?". Essa pergunta conecta o runtime ao sistema que realmente pode causar dano.

Checklist para colocar a verificação no runtime

Use esta sequência ao adicionar uma tool com efeito externo:

  1. Classifique a tool como leitura, escrita reversível, escrita irreversível ou operação assíncrona.
  2. Defina o actionId no runtime, antes da chamada do modelo ou no primeiro ponto determinístico da intenção.
  3. Persista o hash dos argumentos e rejeite uma mesma chave com uma entrada diferente.
  4. Defina a pós-condição em termos de estado externo, não de texto gerado pelo modelo.
  5. Faça o executor devolver um recibo com status e identificador externo quando disponível.
  6. Modele confirmed, not_found, unknown e rejected separadamente.
  7. Consulte o efeito depois de timeout, cancelamento ou perda de conexão.
  8. Repita apenas com uma chave que o serviço realmente deduplica ou depois de uma verificação confiável de ausência.
  9. Teste leitura atrasada, concorrência, efeito parcial e conflito de argumentos.
  10. Mostre o caso unknown na observabilidade e defina quem pode reconciliá-lo.

Para persistir esse estado entre processos, veja como pausar e retomar um agente de IA sem reiniciar. Para decidir a fronteira entre uma tentativa do modelo e uma ação do domínio, consulte também como validar tool calls em TypeScript.

Perguntas frequentes

Um timeout significa que devo repetir a tool call?

Não. O timeout prova apenas que o cliente não recebeu uma resposta dentro do prazo. Consulte a pós-condição com o identificador da ação ou use a mesma chave de idempotência do serviço. Se a consulta não for conclusiva, mantenha unknown e não crie uma segunda ação por impulso.

Um HTTP 200 prova que o efeito aconteceu?

Não necessariamente. O 200 pode confirmar que o serviço aceitou um comando, não que o processamento assíncrono terminou. Defina qual recibo ou estado externo representa a pós-condição. Só marque confirmed quando essa evidência estiver disponível e associada à ação correta.

Posso usar o ID da tool call do provedor como chave?

Use-o para rastrear a mensagem do provedor, mas não o trate como a única chave da ação do domínio. Um retry ou fallback pode produzir outro ID para a mesma intenção. Crie uma chave no seu runtime e encaminhe-a ao executor quando o contrato do serviço permitir.

E se a API não tiver idempotência?

Consulte o efeito antes de repetir, serialize a posse da ação e mantenha unknown quando a consulta não for confiável. Ainda existe uma janela de corrida sem deduplicação no receptor. Portanto, explique a garantia real e não prometa execução exatamente uma vez.

Conclusão

Uma tool call não termina quando o modelo recebe uma resposta. Para operações com efeito, o runtime precisa distinguir a confirmação do transporte da confirmação do estado externo. Um actionId, um recibo e uma pós-condição tornam essa diferença verificável.

O caminho seguro é curto: registrar a intenção, enviar com uma chave estável, guardar o resultado, consultar depois de uma resposta perdida e repetir apenas quando a ausência for comprovada ou a API deduplicar a operação. Quando nada disso for possível, unknown é um estado honesto. Ele pode interromper o fluxo, mas não cria uma duplicata silenciosa.

Nota de produção

Samuel Fajreldines é o responsável editorial por este artigo. A pesquisa combinou uma publicação primária recente, documentação oficial do OpenAI Agents SDK e orientação de confiabilidade da AWS. O modelo de estados, a tabela de decisão e os fixtures são síntese original. O código é ilustrativo e não foi executado contra um provedor ou sistema de produção. A assistência de IA ajudou na descoberta, comparação de fontes, redação, imagem, tradução e revisão de consistência. Não forneceu teste de produção nem experiência própria. Para manter sessões longas de agentes visíveis, uso o RemoteCode, uma ferramenta minha; isso não é uma medição deste artigo.

Fontes consultadas