Introdução
Uma resposta de IA escrita para uma pessoa pode variar na escolha das palavras sem causar problemas. O código da aplicação precisa de algo mais rigoroso. Um serviço de roteamento de tickets, por exemplo, precisa de campos nomeados como category e priority, com valores pertencentes a um conjunto conhecido. A saída estruturada solicita que o modelo retorne dados legíveis por máquina, em vez de texto livre.
Neste laboratório, você usará o JSON Mode com um JSON Schema. JSON é o formato dos dados. O schema é um contrato que descreve quais campos são obrigatórios, quais tipos de valor são permitidos e se campos inesperados são proibidos. Pedir ao modelo que siga um schema melhora o formato da resposta, mas isso não constitui uma fronteira de confiança: a saída do modelo continua sendo um dado externo e pode estar incompleta, malformada ou ser incompatível com a aplicação.
Você criará POST /extract. O Worker enviará um pequeno ticket de suporte sintético para um modelo Llama hospedado na Cloudflare e solicitará quatro campos: uma categoria, uma prioridade, um resumo curto e uma decisão de acompanhamento. Em seguida, o mesmo schema será verificado de forma independente com o Ajv antes que o Worker retorne um registro aceito. Fixtures determinísticas injetarão saídas malformadas do modelo para que você possa comprovar que os dados inválidos seguem um caminho de erro, em vez de entrar na resposta aceita.
Este é o terceiro laboratório do curso. Você deve saber que um Cloudflare Worker processa requisições HTTP e que o binding AI disponibiliza o Workers AI como env.AI. Se você entrou diretamente neste curso, conclua primeiro Conectar o LabEx à sua conta da Cloudflare para aprender a usar o terminal da VM, autorizar o Wrangler, confirmar sua conta de aprendizagem e salvar o ID da conta.
O laboratório usa @cf/meta/llama-3.3-70b-instruct-fp8-fast, que oferece suporte ao JSON Mode, e mantém cada prompt e resultado pequenos. Atualmente, contas Workers Free recebem uma alocação diária compartilhada de 10.000 Neurons, portanto o Workers Paid não é necessário enquanto ainda houver alocação gratuita na conta. A inferência local ainda acessa a Cloudflare e consome essa alocação. Se o modelo ou a alocação não estiver disponível, pare em vez de enviar requisições repetidas.
A configuração instala o Node.js 22.22.0, o Wrangler 4.132.0 local do projeto e o Ajv 8.17.1 em /home/labex/project/ticket-fields. Ela fornece testes determinísticos e verificações independentes. A configuração não faz login, não invoca um modelo, não faz deploy de um Worker nem cria um recurso na nuvem. Mantenha esta VM aberta até excluir o Worker descartável e confirmar o logout.
Autorizar a VM e configurar o Worker de extração
Nesta etapa, você autorizará esta VM nova e configurará um Worker descartável. Um login no Dashboard pertence ao navegador; o Wrangler em uma VM nova precisa de sua própria autorização limitada antes de poder gerenciar a conta de aprendizagem.
Entre no projeto preparado e confirme a versão fixada do Wrangler:
cd /home/labex/project/ticket-fields
npx wrangler --version
O resultado esperado é 4.132.0. Solicite as mesmas permissões restritas usadas nos laboratórios anteriores do Workers AI. O Wrangler 4.132.0 verifica dependências do KV ao excluir um Worker, portanto workers_kv:write evita um erro de limpeza não relacionado, embora este laboratório não crie dados no KV.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write ai:write
Abra o link exibido, informe o código do dispositivo atual, confira a conta e as permissões e autorize a conta de aprendizagem. Volte ao terminal e inspecione os dados estruturados de identidade:
npx wrangler whoami --json
Confirme loggedIn: true e leia name e id da conta pretendida. Gere um nome exclusivo para o Worker descartável:
RUN="labex-c07-a03-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
Substitua YOUR_ACCOUNT_ID pelo ID real dessa conta:
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.js",
"compatibility_date": "2026-09-16",
"workers_dev": true,
"preview_urls": false,
"observability": {
"enabled": true,
"head_sampling_rate": 1
},
"ai": {
"binding": "AI",
"remote": true
}
}
JSON
O binding AI ficará disponível como env.AI. remote: true significa que o processo local do Worker ainda chama o modelo real associado à conta. A observabilidade salva os pequenos eventos do ciclo de vida que você inspecionará após o deploy. Nenhuma inferência ou deploy foi realizado ainda.
Ler o contrato da saída estruturada
Nesta etapa, você inspecionará as duas camadas que protegem a aplicação. O JSON Mode envia um schema junto com a requisição ao modelo. O Ajv verifica o valor retornado em relação a esse schema dentro do Worker. A primeira camada orienta a geração; a segunda decide se o valor pode ser aceito com segurança.
Gere os tipos de ambiente do Worker:
npx wrangler types
grep -A4 'interface __BaseEnv_Env' worker-configuration.d.ts
Procure AI: Ai. Esse é um binding fornecido pela plataforma, não uma chave de API do modelo armazenada no código-fonte.
O registro terá quatro campos:
category: billing | account | upload | other
priority: low | medium | high
summary: nonempty text, at most 160 characters
needs_follow_up: true or false
No JSON Schema, type controla o tipo do valor, enum limita um valor a uma lista conhecida, required define os campos que precisam existir e additionalProperties: false rejeita campos inesperados. Essa última regra é importante porque, sem ela, um campo inventado poderia passar despercebido. O schema descreve a estrutura, não se a interpretação do modelo está objetivamente correta; uma pessoa ou uma regra de negócio posterior ainda pode revisar os campos aceitos.
Inspecione as fixtures malformadas fornecidas para o teste determinístico:
grep -nE 'security|priority: 1|internal_note|not-an-object' test/worker.test.mjs
Essas fixtures não consomem Neurons. Elas permitem que o teste exercite de forma confiável casos que não devem ser produzidos intencionalmente por meio de prompts reais repetidos.
Criar o endpoint de extração validado
Nesta etapa, você implementará o schema, a requisição ao modelo e a validação no lado da aplicação. Somente o caminho aprovado pelo Ajv retornará um record.
Crie o ponto de entrada do Worker:
cat > src/index.js <<'JS'
import Ajv from "ajv";
const MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
const MAX_TICKET = 1200;
export const TICKET_SCHEMA = {
type: "object",
properties: {
category: { type: "string", enum: ["billing", "account", "upload", "other"] },
priority: { type: "string", enum: ["low", "medium", "high"] },
summary: { type: "string", minLength: 1, maxLength: 160 },
needs_follow_up: { type: "boolean" }
},
required: ["category", "priority", "summary", "needs_follow_up"],
additionalProperties: false
};
const ajv = new Ajv({ allErrors: true });
const isTicketRecord = ajv.compile(TICKET_SCHEMA);
function json(data, status = 200) {
return Response.json(data, { status });
}
async function readTicket(request) {
const contentType = request.headers.get("content-type") || "";
if (!contentType.toLowerCase().includes("application/json")) {
return { error: json({ error: "json_required" }, 415) };
}
const raw = await request.text();
if (raw.length > 2048) {
return { error: json({ error: "ticket_too_large" }, 413) };
}
let body;
try {
body = JSON.parse(raw);
} catch {
return { error: json({ error: "invalid_json" }, 400) };
}
const ticket = typeof body?.ticket === "string" ? body.ticket.trim() : "";
if (!ticket) return { error: json({ error: "invalid_ticket" }, 400) };
if (ticket.length > MAX_TICKET) {
return { error: json({ error: "ticket_too_large" }, 413) };
}
return { ticket };
}
async function extractTicket(request, env) {
const parsed = await readTicket(request);
if (parsed.error) return parsed.error;
const requestId = crypto.randomUUID();
const details = { requestId, model: MODEL };
let result;
try {
result = await env.AI.run(MODEL, {
messages: [
{
role: "system",
content: "Extract support-ticket fields. Use only evidence in the ticket. Keep the summary short and do not add fields."
},
{ role: "user", content: parsed.ticket }
],
response_format: {
type: "json_schema",
json_schema: TICKET_SCHEMA
},
max_tokens: 160,
temperature: 0
});
} catch {
console.error(JSON.stringify({ event: "ticket_extraction_failed", ...details }));
return json({ error: "model_unavailable", requestId }, 502);
}
const candidate = result?.response;
if (!isTicketRecord(candidate)) {
console.error(JSON.stringify({ event: "ticket_output_rejected", ...details }));
return json({ error: "invalid_model_output", requestId }, 502);
}
console.log(JSON.stringify({ event: "ticket_output_accepted", ...details }));
return json({ record: candidate, requestId });
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/health") {
return json({ status: "ok" });
}
if (request.method === "POST" && url.pathname === "/extract") {
return extractTicket(request, env);
}
return json({ error: "not_found" }, 404);
}
};
JS
O Worker nunca registra o ticket nem os campos retornados. O ID da requisição conecta a resposta do cliente a um evento de ciclo de vida aceito, rejeitado ou com falha sem copiar o conteúdo do suporte para os dados de observabilidade. Os detalhes dos erros do Ajv também ficam fora da resposta do cliente, pois podem revelar o design interno da validação; os clientes recebem o contrato estável invalid_model_output.
Execute os testes determinísticos:
node --test test/worker.test.mjs
O resultado esperado é de cinco testes aprovados. Um teste injeta sete candidatos malformados por meio de um binding de IA falso e exige que todas as respostas não contenham record. Em seguida, faça o bundle do Worker real sem fazer deploy:
npx wrangler deploy --dry-run
As fixtures comprovam o comportamento de rejeição sem depender de uma saída variável do modelo. O dry run comprova que o código-fonte, a dependência do Ajv e a configuração do Worker podem ser agrupados. A próxima etapa executará uma inferência estruturada real.
Executar um resultado estruturado real
Nesta etapa, você executará o Worker a partir da VM e fará uma requisição real usando o JSON Mode. “Local” descreve o manipulador da requisição; o binding de IA ainda usa a conta da Cloudflare selecionada e consome parte da alocação diária.
Inicie o Wrangler em segundo plano e salve o ID do processo:
npx wrangler dev --port 8787 > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
Aguarde a rota de integridade que não usa IA:
for attempt in $(seq 1 30); do
if curl --silent --fail http://127.0.0.1:8787/health; then
break
fi
sleep 1
done
Envie um ticket sintético claro:
curl --silent --show-error http://127.0.0.1:8787/extract \
--header 'Content-Type: application/json' \
--data '{"ticket":"Customer cannot upload a PDF and needs help before today’s deadline."}'
O resultado esperado é uma resposta JSON com record e requestId. A categoria, a prioridade, o texto do resumo e a decisão de acompanhamento exatos podem variar. A evidência importante é que o registro contém exatamente quatro campos e que cada valor satisfaz o schema.
Agora comprove que uma requisição inválida da aplicação é rejeitada antes da chamada ao modelo:
curl --silent --show-error --write-out '\nHTTP %{http_code}\n' \
http://127.0.0.1:8787/extract \
--header 'Content-Type: application/json' \
--data '{"ticket":""}'
O resultado esperado é {"error":"invalid_ticket"} e HTTP 400. A validação da entrada protege a chamada ao modelo; a validação da saída protege o registro da aplicação. São fronteiras separadas.
Fazer o deploy e inspecionar a saída aceita
Nesta etapa, você fará o deploy do mesmo endpoint validado e relacionará o estado visível no Dashboard ao resultado em tempo de execução. Primeiro, pare somente o processo de desenvolvimento salvo:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
Faça o deploy do Worker:
npx wrangler deploy
Salve a URL exata do workers.dev exibida pelo Wrangler:
WORKER_URL="https://YOUR_WORKER_URL"
Envie uma requisição pública limitada:
curl --silent --show-error "$WORKER_URL/extract" \
--header 'Content-Type: application/json' \
--data '{"ticket":"Customer cannot upload a PDF and needs help before today’s deadline."}'
Confirme que a resposta pública novamente contém exatamente os campos do schema dentro de record. Um status HTTP de sucesso, por si só, não é suficiente; a verificação independente também valida cada campo retornado e o binding AI do Worker implantado.
Abra o Cloudflare Dashboard e acesse Workers & Pages → Overview → seu Worker labex-c07-a03-.... Inspecione o binding e depois abra Observability → Logs. Pesquise por ticket_output_accepted, expanda o evento e confirme model, requestId e o nome do evento. O log exclui deliberadamente o ticket e o registro extraído.
A visualização do binding abaixo vem de uma execução de depuração descartável. O diagrama e a tabela relacionam o nome AI ao Workers AI, que é o equivalente visível no Dashboard de env.AI no Worker. O nome exclusivo do seu Worker será diferente.

A mesma execução registrou 3 Success e 0 Errors após a requisição pública e as verificações independentes. Esses totais são exemplos, não quantidades obrigatórias. A relação importante é que o Worker selecionado processou com sucesso as requisições visíveis para /extract.

Depois de filtrar por ticket_output_accepted, o evento expandido da aplicação mostra o modelo Llama exato, um ID de requisição e o nome do evento aceito. Ele não contém o ticket sintético nem o registro extraído. Isso confirma a fronteira de privacidade, mas não trata uma linha de log como prova de que a validação do schema foi aprovada; a resposta em tempo de execução e a verificação independente fornecem essa prova.

Em seguida, abra Workers AI e inspecione o uso de modelos de hoje. Encontre o modelo Llama 3.3 e confirme que os exercícios limitados permanecem dentro da alocação de 10.000 Neurons do Workers Free. Os dados do Dashboard podem atrasar; aguarde um pouco em vez de repetir a inferência apenas para forçar a atualização de um gráfico ou log.
A conta do exemplo mostrou 261.63/10k Neurons para o modelo Llama. Esse total inclui exercícios anteriores de produção do curso na mesma conta de aprendizagem, portanto não representa o custo deste laboratório sozinho e o seu valor será diferente. O ponto de verificação é permanecer dentro da alocação Free, e não corresponder ao número do exemplo.

Os valores do Dashboard pertencem a esta execução descartável. Os objetivos de aprendizagem são a identidade exata do Worker, seu binding de IA, um evento aceito com privacidade preservada e o uso da alocação Free. As verificações feitas pela CLI, pela API e em tempo de execução continuam sendo a fonte de autoridade caso uma visualização do Dashboard demore para atualizar.
Remover o Worker e fazer logout
Nesta etapa, você excluirá o Worker descartável e depois removerá a autorização desta VM. O uso do Workers AI é registrado no nível da conta; portanto, excluir o Worker remove seu endpoint, mas não apaga o registro de uso nem altera o plano da conta.
Exclua exatamente o Worker nomeado em wrangler.jsonc:
npx wrangler delete
Confirme somente quando o Wrangler exibir o nome exclusivo labex-c07-a03-... deste laboratório. O comando deve terminar com Successfully deleted. Atualize Workers & Pages → Overview e confirme que esse nome exato não está mais presente.
Enquanto a VM ainda estiver autorizada, execute a verificação de gerenciamento independente:
python3 .labex/verify.py deleted
Somente depois que o comando informar PASS: deleted, remova a autorização armazenada da VM:
npx wrangler logout
npx wrangler whoami --json
Exija loggedIn: false. Um arquivo local ausente, uma aba do navegador fechada ou um erro de rede não comprovaria a exclusão na nuvem nem o logout.
Resumo
Você criou um endpoint do Workers AI que solicita campos estruturados de tickets usando JSON Mode e JSON Schema. Aprendeu por que um formato solicitado não é o mesmo que um dado confiável, usou o Ajv como uma fronteira independente da aplicação e comprovou, com fixtures malformadas, que uma saída inválida do modelo nunca se torna um registro aceito. Você executou um resultado real, local e implantado, no Workers Free, relacionou o evento aceito à observabilidade do Dashboard, removeu o Worker descartável e fez logout da VM nova.



