Structured outputs, tool calling, erros
Uma requisição pode retornar uma frase excelente e ainda ser inútil para a aplicação, porque faltam campos ou porque uma ação foi repetida. Nesta semana a fronteira com o modelo ganha contratos verificáveis e comportamento operacional explícito. Vamos distinguir texto, dados estruturados e proposta de ação.
O caso é uma solicitação de reembolso. O modelo poderá organizar a intenção, mas a aplicação verificará identidade, valores e política antes de qualquer efeito financeiro. Essa separação permite aprender JSON Schema, ferramentas, retries, logs e fallback em um mesmo fluxo sem delegar autorização ao texto gerado.
JavaScriptJSONAo terminar esta aula
- Estrutura correta não implica verdade nem permissão.
- O resultado de uma operação pode ser desconhecido após timeout.
- Fallback deve preservar contrato e visibilidade operacional.
Antes de continuar: Laboratório: APIs, streaming, mensagens
JSON válido, schema e regra de negócio
JSONJSON é uma sintaxe para representar dados. JSON Schema descreve restrições como tipos, propriedades obrigatórias e valores permitidos. Um objeto pode ser JSON válido e falhar no schema; pode passar no schema e contrariar o negócio. Por exemplo, {"valor":100} expressa um número, mas não informa se a pessoa tem direito a receber esse valor. Validação sintática, estrutural e semântica são etapas distintas. Misturá-las produz a falsa sensação de que uma resposta formatada é uma resposta correta.
Structured outputs usa mecanismos do provedor para produzir resultados compatíveis com schemas suportados. Confira modelos e subconjunto de schema aceitos, além dos estados de recusa e conclusão. Não presuma que qualquer recurso do padrão completo pode ser enviado. Depois de receber, valide novamente o contrato de negócio no servidor. Para reembolso, o modelo pode extrair pedidoId e motivo; o valor autorizado vem da consulta ao pedido e das regras, não de um campo preenchido livremente pela geração.
Function calling é uma proposta, não execução mágica
FundamentosUma ferramenta possui nome, descrição e contrato de argumentos. O modelo pode devolver uma chamada proposta. A aplicação recebe os argumentos, valida, verifica permissão, executa quando autorizado e devolve o resultado para o modelo continuar. Portanto, a capacidade do modelo de propor emitirReembolso não equivale à capacidade do usuário de realizar a operação. A função deve verificar autorização com contexto confiável, fora dos argumentos livres do modelo.
Separe consultarElegibilidade de emitirReembolso. A consulta permite descobrir o estado sem criar efeito externo. A emissão exige critérios mais fortes, identificação da operação e eventual confirmação humana conforme o produto. Uma descrição útil informa quando usar, o que recebe, o que devolve e quais erros esperar. Evite nomes genéricos como executarTudo, pois escondem efeitos. A resposta da ferramenta também é dado: um serviço externo pode retornar conteúdo malicioso. Nunca permita que esse conteúdo altere as permissões da aplicação.
Timeout e rate limit exigem orçamentos
FundamentosTimeout limita quanto tempo uma operação pode esperar. A aplicação precisa de um orçamento total, considerando fila, chamada, retries e etapas posteriores. Se cada tentativa recebe o timeout máximo do pedido, a soma pode exceder o tempo tolerado pelo usuário. Rate limit restringe frequência ou consumo segundo a conta e o serviço. Uma falha temporária pode ser recuperável, mas tentativas também consomem recursos e podem agravar a carga.
Use número máximo de tentativas, espera progressiva com aleatoriedade quando apropriado e respeito aos sinais do serviço. Diferencie erro de autenticação de indisponibilidade temporária: repetir uma chave inválida não corrige o problema. Verifique também retries já realizados pelo SDK para evitar multiplicação entre camadas. Cancelamento precisa atravessar o fluxo. Uma requisição que perdeu o usuário não deveria continuar indefinidamente gerando custo. O objetivo não é insistir até conseguir qualquer resposta, mas recuperar dentro de um limite operacional conhecido.
Idempotência e resultado desconhecido
FundamentosUma operação idempotente pode ser repetida sem multiplicar seu efeito lógico. Para emissão de reembolso, a aplicação pode associar uma chave estável à intenção e armazenar o resultado. Entretanto, esse padrão só funciona quando o serviço e a persistência foram desenhados para suportá-lo. Não assuma que todo endpoint de LLM ou de pagamento aceita a mesma chave. Verifique o contrato real do sistema que causa o efeito.
↗ Making retries safe with idempotent APIs↗ Function calling
Considere uma chamada que enviou o reembolso e perdeu a conexão antes da resposta. O cliente não sabe se a operação aconteceu. Repetir com uma intenção nova pode duplicar pagamento. A recuperação deve consultar o estado ou reutilizar o identificador da operação conforme o serviço. Esse caso difere de uma falha anterior ao envio, embora ambos possam parecer timeout. Nos logs, preserve identificadores e transições, permitindo distinguir enviado, confirmado e resultado desconhecido. Um prompt dizendo “não duplique” não resolve uma falha de rede entre sistemas.
↗ Making retries safe with idempotent APIs↗ Function calling
Erros, métricas e fallback sem mascarar falhas
FundamentosUm log útil conecta tentativa, configuração, duração, status e identificador de requisição. Evite registrar credenciais, dados financeiros e texto integral sem necessidade. Métricas agregam frequência e duração; logs explicam eventos particulares. Tracing relaciona etapas do fluxo, como extração, consulta e emissão. Nenhuma dessas camadas substitui um critério de qualidade da saída. Um status operacional de sucesso pode conter uma classificação incorreta.
↗ LangSmith Observability↗ Building effective agents↗ Responses API overview
Fallback troca uma execução por outra opção quando uma condição definida ocorre. Pode ajudar com indisponibilidade ou incompatibilidade de recursos, mas também muda comportamento, privacidade e custo. O segundo modelo precisa cumprir o mesmo contrato. Não use fallback para esconder um erro de schema na integração ou repetir automaticamente uma ação financeira. No reembolso, apenas a classificação pode ser refeita com segurança; a emissão usa estado e idempotência próprios. Registre que houve fallback para não atribuir seu resultado ao modelo inicial.
↗ LangSmith Observability↗ Building effective agents↗ Responses API overview
Exemplo comentado e limites
FundamentosO Map demonstra o princípio de deduplicação em um processo único. As duas chamadas reutilizam a mesma intenção e o contador permanece um. Não é uma implementação de pagamentos: falta persistência durável, atomicidade e proteção contra concorrência entre processos. O exemplo ensina o contrato sem prometer segurança de produção.
const operacoes = new Map();
function emitir(chave, pedido) {
if (!pedido.autorizado) throw new Error("Sem autorização");
if (operacoes.has(chave)) return operacoes.get(chave);
const resultado = {status:"emitido",pedidoId:pedido.id};
operacoes.set(chave, resultado);
return resultado;
}
const pedido = {id:"pedido-7",autorizado:true};
console.log(emitir("reembolso-pedido-7",pedido));
console.log(emitir("reembolso-pedido-7",pedido));
console.log("efeitos",operacoes.size);A verificação de autorização ocorre antes do retorno de um resultado anterior. Isso impede que conhecer uma chave permita consultar livremente uma operação protegida. Em produção, também vincule a chave ao conteúdo normalizado e ao usuário: reutilizar a mesma chave para outro pedido deve ser rejeitado, não tratado como repetição legítima.
Exercício aplicado
O cliente recebe timeout após pedir reembolso. Defina como descobrir o estado antes de repetir um efeito externo.
- Execute o caso nominal duas vezes com a mesma chave.
- Rejeite entrada não autorizada e reutilização incompatível da chave.
- Simule perda da resposta após o efeito e recupere o estado.
- Descreva onde ficam logs, orçamento de tentativas e fallback apenas para operações seguras.
Abrir resolução comentada
A solução executa as quatro etapas locais exigidas. normalizar valida tipos sem conceder autorização; autorizar consulta a propriedade do pedido a partir do contexto confiável simulado. A chave deriva de usuário e pedido. A intenção armazena o conteúdo normalizado e o recibo, e rejeita uma chave reutilizada com valor diferente. Os asserts verificam replay, usuário incorreto, chave incompatível e schema inválido.
O segundo pedido grava o efeito e depois perde a resposta deliberadamente. executarComRecuperacao consulta o recibo da intenção antes de qualquer repetição. A emissão continua única para esse pedido. O fallback usa dois mocks de classificação, valida categoria, prioridade, resumo e ação recomendada e respeita o limite de tentativas. Ele não recebe a função de emissão, portanto não pode repetir um pagamento.
Logs registram etapa, código, tentativa e recibo, sem copiar o payload financeiro. O orçamento do exemplo limita a classificação; emissão com resposta perdida é reconciliada, não repetida por um loop genérico. Este processo único não implementa concorrência entre servidores, reinício durável ou um gateway financeiro. Uma integração real precisa de transação e do contrato de idempotência do serviço, além de timeout global e persistência. Os asserts demonstram as garantias locais, não uma transação financeira externa.
import assert from "node:assert/strict";
const pedidos=new Map([["p7",{dono:"u1",valor:100}],["p8",{dono:"u1",valor:60}]]);
const intencoes=new Map();
const logs=[];
let efeitos=0;
function falhar(codigo){throw Object.assign(new Error(codigo),{codigo});}
function autorizar(contexto,pedidoId){
const pedido=pedidos.get(pedidoId);
if(!pedido||pedido.dono!==contexto.usuario) falhar("SEM_PERMISSAO");
return pedido;
}
function normalizar(payload){
if(typeof payload.pedidoId!=="string" || !Number.isSafeInteger(payload.valor) || payload.valor<=0)
falhar("SCHEMA_INVALIDO");
return JSON.stringify({pedidoId:payload.pedidoId,valor:payload.valor});
}
function consultarIntencao(chave,contexto){
const registro=intencoes.get(chave);
if(!registro) return {status:"nao_enviado"};
autorizar(contexto,registro.pedidoId);
if(registro.usuario!==contexto.usuario) falhar("SEM_PERMISSAO");
return registro.resultado??{status:"pendente"};
}
function emitir(chave,payload,contexto,{perderResposta=false}={}){
const normalizado=normalizar(payload);
const pedido=autorizar(contexto,payload.pedidoId);
// A mesma intenção usa chave derivada do usuário e do pedido.
const chaveEsperada="reembolso:"+contexto.usuario+":"+payload.pedidoId;
if(chave!==chaveEsperada) falhar("CHAVE_INCOMPATIVEL");
const anterior=intencoes.get(chave);
if(anterior){
if(anterior.usuario!==contexto.usuario||anterior.payload!==normalizado)
falhar("PAYLOAD_INCOMPATIVEL");
return anterior.resultado;
}
if(payload.valor!==pedido.valor) falhar("VALOR_NAO_AUTORIZADO");
// Simulação síncrona: intenção e recibo estão no mesmo processo.
const registro={usuario:contexto.usuario,pedidoId:payload.pedidoId,payload:normalizado};
intencoes.set(chave,registro);
efeitos++;
registro.resultado={status:"emitido",pedidoId:payload.pedidoId,recibo:"r"+efeitos};
logs.push({etapa:"emissao",codigo:"CONFIRMADO",recibo:registro.resultado.recibo});
if(perderResposta) falhar("RESPOSTA_PERDIDA");
return registro.resultado;
}
function executarComRecuperacao(chave,payload,contexto,opcoes){
try {return emitir(chave,payload,contexto,opcoes);}
catch(erro){
if(erro.codigo!=="RESPOSTA_PERDIDA") throw erro;
// Reconciliar antes de qualquer nova tentativa de efeito.
logs.push({etapa:"reconciliacao",codigo:"CONSULTA_DE_ESTADO"});
const resultado=consultarIntencao(chave,contexto);
if(resultado.status!=="emitido") falhar("RESULTADO_DESCONHECIDO");
return resultado;
}
}
function validarClassificacao(objeto){
const categorias=["financeiro","acesso","revisao"];
const prioridades=["normal","alta"];
if(!categorias.includes(objeto.categoria)||!prioridades.includes(objeto.prioridade)||
typeof objeto.resumo!=="string"||typeof objeto.acaoRecomendada!=="string")
falhar("CLASSIFICACAO_INVALIDA");
return objeto;
}
// Fallback somente nesta operação sem efeito. Cada fornecedor é um mock local.
function classificarComFallback(provedores,maxTentativas=2){
let ultimo;
for(let i=0;i<Math.min(maxTentativas,provedores.length);i++){
try {return {dados:validarClassificacao(provedores[i]()),tentativas:i+1};}
catch(erro){ultimo=erro;logs.push({etapa:"classificacao",tentativa:i+1,codigo:erro.codigo});}
}
throw ultimo??new Error("Nenhum provedor disponível");
}
const contexto={usuario:"u1"};
const p7={pedidoId:"p7",valor:100};
const chave7="reembolso:u1:p7";
const primeiro=emitir(chave7,p7,contexto);
assert.deepEqual(emitir(chave7,p7,contexto),primeiro);
assert.equal(efeitos,1);
assert.throws(()=>emitir(chave7,p7,{usuario:"u2"}),/SEM_PERMISSAO/);
assert.throws(()=>emitir(chave7,{pedidoId:"p7",valor:90},contexto),/PAYLOAD_INCOMPATIVEL/);
assert.throws(()=>emitir("outra-chave",p7,contexto),/CHAVE_INCOMPATIVEL/);
assert.throws(()=>emitir(chave7,{pedidoId:"p7",valor:"100"},contexto),/SCHEMA_INVALIDO/);
const recuperado=executarComRecuperacao("reembolso:u1:p8",{pedidoId:"p8",valor:60},
contexto,{perderResposta:true});
assert.equal(recuperado.status,"emitido");
assert.equal(efeitos,2);
assert.deepEqual(emitir("reembolso:u1:p8",{pedidoId:"p8",valor:60},contexto),recuperado);
assert.equal(efeitos,2);
const fornecedores=[()=>falhar("TEMPORARIO"),()=>({categoria:"financeiro",prioridade:"normal",
resumo:"Cliente pede revisão",acaoRecomendada:"Consultar elegibilidade"})];
assert.equal(classificarComFallback(fornecedores,2).tentativas,2);
assert.throws(()=>classificarComFallback(fornecedores,1),/TEMPORARIO/);
assert.ok(logs.some(x=>x.etapa==="reconciliacao"));
console.log({status:"aprovado",efeitos,intencoes:intencoes.size,logs});Como conferir seu resultado
- Repetição legítima gera um único efeito simulado.
- Schema e autorização são verificações separadas.
- Retries e fallback possuem condições e limites explícitos.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Separar schema de autorização.
- Vincular intenção a usuário/payload.
Reserva 80 usa k; serviço grava recibo e perde resposta. Retry de k propõe 90. Decida recuperação/fallback.
Conferir raciocínio e critérios de domínio
Payload 90 é incompatível; k permanece ligado a 80 e ao usuário.
Consultar k antes de repetir; recuperar recibo confirmado.
Fallback só classifica sem efeito, não decide nova reserva.
Evidências para autoavaliação ou revisão por pares
- Vínculo: Payload 90 é incompatível; k permanece ligado a 80 e ao usuário.
- Reconciliação: Consultar k antes de repetir; recuperar recibo confirmado.
- Efeito: Fallback só classifica sem efeito, não decide nova reserva.
Um erro frequente
Timeout prova ausência de efeito.
Timeout pós-envio deixa o resultado desconhecido.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Schema válido prova elegibilidade?
Não.
O schema valida estrutura; elegibilidade depende do pedido e da política autorizada.
2. Function calling executa automaticamente a função?
3. Timeout sempre significa que nada aconteceu?
Não.
A resposta pode ter sido perdida depois do efeito; o estado pode ser desconhecido.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- Structured model outputs
OpenAI • consulta: 2026-10-06
OpenAIJSON Schema, saída estruturada, strict mode e distinção entre resposta estruturada e function calling.
Limites: Suporte a subconjunto JSON Schema e modelos específicos; JSON válido não garante verdade factual ou regra de negócio.
- OpenAPI Specification 3.1.1
OpenAPI Initiative • consulta: 2026-10-06
FundamentosContrato HTTP, schemas, operações, parâmetros e respostas.
Limites: Versão fixada 3.1.1; suporte das ferramentas a JSON Schema/OpenAPI deve ser verificado.
- Function calling
OpenAI • consulta: 2026-10-06
OpenAICiclo de chamada de funções, schemas, descrições, argumentos, retorno e execução pela aplicação.
Limites: O modelo propõe chamadas; validação, autorização e execução ficam na aplicação. Confirmar efeitos externos fora do prompt.
- Create a Message
Anthropic • consulta: 2026-10-06
FundamentosFormato Messages, mensagens user/assistant, campo system separado, parâmetros e resultados de tool use.
Limites: Não traduzir roles ou parâmetros OpenAI literalmente: Anthropic não usa system como role no array messages.
- OpenAI Python API library
OpenAI • consulta: 2026-10-06
PythonOpenAIAPIsCliente SDK, streaming, erros tipados, retries, timeout, logging e request IDs.
Limites: Defaults do SDK não substituem orçamento global de retries; pin de versão obrigatório.
- Rate limits
OpenAI • consulta: 2026-10-06
OpenAILimites por taxa, backoff e recuperação de erros temporários.
Limites: Limites por conta/modelo mudam; evitar retries ilimitados e tempestades de retry.
- Making retries safe with idempotent APIs
Amazon Web Services • consulta: 2026-10-06
APIsIdempotência, identificador de requisição, retries seguros e efeitos colaterais.
Limites: Padrão de sistemas distribuídos; suporte a chave de idempotência deve ser verificado em cada endpoint, não presumido para todo LLM.
- LangSmith Observability
LangChain • consulta: 2026-10-06
AvaliaçãoTracing de execuções, visualização de chamadas e diagnóstico.
Limites: SaaS/serviço opcional; decidir redação de dados sensíveis antes de enviar traces.
- Building effective agents
Anthropic • consulta: 2026-10-06
AgentesPrompt chaining, decomposição, routing, paralelização, workflows versus agentes.
Limites: Relato de engenharia do fornecedor; ganhos e escolha de padrão são hipóteses a medir, não garantia.
- Responses API overview
OpenAI • consulta: 2026-10-06
OpenAIAPIsInterface HTTP Responses para geração, estado, ferramentas e multimodalidade.
Limites: Documentação dinâmica; fixar a versão usada no laboratório e conferir compatibilidade antes de executar.