Structured outputs, tool calling, erros
O laboratório modela uma operação com efeito e sua repetição. Você separará entrada estruturada, autorização e deduplicação, observando por que um contrato de dados não resolve sozinho erros de rede. O programa local oferece um ponto de partida transparente.
Não será necessário enviar dinheiro ou chamar um provedor. Use registros fictícios e funções locais. A entrega precisa indicar claramente o limite da memória em processo e propor como o comportamento seria preservado com armazenamento durável e um serviço externo que ofereça contrato de idempotência.
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: Leitura: Structured outputs, tool calling, erros
Modelar intenção, validação e autoridade
FundamentosDesenhe o fluxo de reembolso antes de executar o código: extrair pedido e motivo, consultar elegibilidade, confirmar autorização e emitir. Escreva um schema pequeno para a extração e liste regras que o schema não consegue provar, como identidade e saldo disponível. Tente decidir o que fazer com um valor sugerido pelo modelo. A solução deve buscar valor autorizado no sistema, não confiar no número gerado. Retome function calling como proposta: a aplicação ainda decide se executa. Mantenha ferramentas de consulta e emissão separadas para tornar efeitos claros e reduzir capacidades oferecidas durante classificação.
Implementar deduplicação local
FundamentosExecute duas chamadas com a mesma chave e confira o único resultado armazenado. Depois use chave diferente para a mesma intenção e observe por que o efeito pode duplicar. Acrescente um vínculo entre chave, usuário e conteúdo normalizado. Reutilizar uma chave para outro pedido deve ser erro. Teste também pedido não autorizado, inclusive quando um resultado anterior já existe. O Map é suficiente para compreender a regra em processo único, mas não oferece persistência ou atomicidade distribuída. Registre essa limitação na entrega e descreva qual transação ou garantia externa precisaria preservar a intenção entre reinícios.
Simular timeout e resultado desconhecido
FundamentosCrie uma função que armazene o resultado e depois lance um erro simulando perda da resposta. O chamador deve consultar a intenção antes de tentar emitir novamente. Esse caso é diferente de falha antes do envio, embora ambos possam produzir timeout na interface. Adicione um orçamento total de tentativas e um motivo para retry. Não repita erro de autorização. Se houver SDK real, contabilize suas tentativas internas para evitar multiplicação entre camadas. A sequência de estados deve permitir reconhecer confirmado, não enviado e desconhecido, com identificadores suficientes para diagnóstico sem expor dados financeiros nos logs.
↗ OpenAI Python API library↗ Making retries safe with idempotent APIs
Avaliar fallback e compromissos
FundamentosImplemente fallback apenas na classificação fictícia e exija o mesmo contrato de saída. Não deixe a troca de modelo decidir repetir emissão. Compare o benefício de disponibilidade com custo e mudança de comportamento, registrando quando a alternativa foi usada. Antes de ler a solução, escreva os critérios de aprovação: um efeito por intenção, autorização válida, schema correto e recuperação explícita. Depois compare com o exemplo e liste lacunas de produção, principalmente concorrência e reinício. Uma demonstração que declara seus limites é mais confiável que um programa curto apresentado como solução completa de pagamento.
Integrar classificação estruturada ao serviço
FundamentosO reembolso é um caso concreto do serviço de classificação da fase. Complete o resultado de atendimento com categoria, prioridade, resumo e acaoRecomendada. Defina enums para categoria e prioridade e limites de texto segundo o produto. A ação recomendada é uma proposta descritiva, não uma autorização para emitir pagamento. Valide estrutura e depois regras: categoria financeiro não prova elegibilidade, prioridade alta não amplia permissão. Implemente endpoint local que receba pedido, chame o adaptador e devolva objeto validado ou erro explícito. Faça testes com campo ausente, enum inválido e resposta truncada. Quando usar structured outputs externo, confira subconjunto do schema e estados de recusa suportados. A entrega completa une classificação, timeout, retry limitado, logs seguros e fallback documentado, preservando a separação entre classificação refeita e efeito financeiro recuperado.
Execução, inspeção e diagnóstico
FundamentosExecute as duas emissões e confira o contador. Acrescente um pedido não autorizado, uma chave diferente para a mesma intenção e uma reutilização de chave com pedido diferente. Os dois últimos casos revelam contratos ausentes: a chave precisa representar uma intenção estável e não pode ser aceita sem verificar seu vínculo. Crie uma simulação de timeout depois de guardar o resultado para exercitar a consulta de estado.
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);Se o contador aumenta após uma repetição legítima, procure onde a chave muda. Se a deduplicação funciona apenas antes de reiniciar o processo, falta persistência. Se duas emissões concorrentes passam, falta atomicidade. Esses problemas são de aplicação e armazenamento, não podem ser corrigidos exigindo JSON mais estrito do modelo.
Exercício aplicado
Entregue um fluxo de reembolso simulado com autorização, validação, limite de retries e recuperação de resultado desconhecido.
- 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 de vaga: usuário u7 tem pedido p9 de 80 unidades; chave reserva: u7: p9. Teste replay, mesma chave com 90, usuário u8 e perda de resposta depois do recibo. Informe resultados e contagem de efeitos.
Conferir raciocínio e critérios de domínio
Replay de u7/p9/80 devolve o recibo já registrado e mantém um efeito. Payload 90 na mesma chave é PAYLOAD_INCOMPATIVEL.
u8 é SEM_PERMISSAO antes de ler ou criar recibo. Na perda da resposta após o efeito, consultar reserva: u7: p9 retorna confirmado; não se emite novamente.
A contagem final é um efeito para a intenção nominal. Esses resultados são expectativas locais; concorrência/reinício precisam de persistência e garantia próprias.
Evidências para autoavaliação ou revisão por pares
- Replay e conflito: Replay de u7/p9/80 devolve o recibo já registrado e mantém um efeito. Payload 90 na mesma chave é PAYLOAD_INCOMPATIVEL.
- Autorização e reconciliação: u8 é SEM_PERMISSAO antes de ler ou criar recibo. Na perda da resposta após o efeito, consultar reserva: u7: p9 retorna confirmado; não se emite novamente.
- Contagem e limites: A contagem final é um efeito para a intenção nominal. Esses resultados são expectativas locais; concorrência/reinício precisam de persistência e garantia próprias.
Um erro frequente
JSON válido concede elegibilidade.
Schema não verifica identidade nem elegibilidade.
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.