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.

Resposta curta
- Uma resposta ausente informa o que o cliente recebeu, não o que o servidor fez.
- Persista um
actionIdantes de enviar uma operação com efeito externo.- Consulte uma pós-condição independente e classifique o resultado como
confirmed,not_foundouunknown.- 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:
- Grave ou recupere o registro da ação lógica.
- Envie a tool call com a mesma chave de idempotência quando o serviço aceitar essa chave.
- Guarde o recibo se a resposta chegar.
- Se a resposta for perdida, consulte a pós-condição com
actionIdouexternalId. - Em
confirmed, avance. Emnot_found, repita conforme a política. Emunknown, 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:
- Classifique a tool como leitura, escrita reversível, escrita irreversível ou operação assíncrona.
- Defina o
actionIdno runtime, antes da chamada do modelo ou no primeiro ponto determinístico da intenção. - Persista o hash dos argumentos e rejeite uma mesma chave com uma entrada diferente.
- Defina a pós-condição em termos de estado externo, não de texto gerado pelo modelo.
- Faça o executor devolver um recibo com status e identificador externo quando disponível.
- Modele
confirmed,not_found,unknownerejectedseparadamente. - Consulte o efeito depois de timeout, cancelamento ou perda de conexão.
- Repita apenas com uma chave que o serviço realmente deduplica ou depois de uma verificação confiável de ausência.
- Teste leitura atrasada, concorrência, efeito parcial e conflito de argumentos.
- Mostre o caso
unknownna 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
- arXiv, "Verified Tool Calls Improve LLM Agent Reliability Under Non-Atomic Failures", consultado em 10/09/2026.
- OpenAI Agents SDK, "Tools", consultado em 10/09/2026.
- OpenAI Agents SDK, "Results", consultado em 10/09/2026.
- AWS Well-Architected Framework, "Make mutating operations idempotent", consultado em 10/09/2026.