Einführung
Eine für Menschen formulierte KI-Antwort darf unterschiedliche Formulierungen verwenden, ohne Probleme zu verursachen. Anwendungscode benötigt dagegen strengere Vorgaben. Ein Dienst zur Ticketweiterleitung benötigt beispielsweise benannte Felder wie category und priority, deren Werte aus einer bekannten Menge stammen. Strukturierte Ausgabe weist das Modell an, maschinenlesbare Daten statt frei formulierter Texte zurückzugeben.
In diesem Lab verwenden Sie JSON Mode zusammen mit einem JSON Schema. JSON ist das Datenformat. Das Schema ist ein Vertrag, der beschreibt, welche Felder erforderlich sind, welche Werttypen zulässig sind und ob unerwartete Felder verboten werden. Eine Schema-Vorgabe verbessert zwar die Struktur der Modellantwort, bildet aber keine Vertrauensgrenze: Die Modellausgabe bleibt eine externe Datenquelle und kann fehlen, fehlerhaft formatiert oder mit der Anwendung inkompatibel sein.
Sie erstellen POST /extract. Der Worker sendet ein kleines synthetisches Support-Ticket an ein von Cloudflare gehostetes Llama-Modell und fordert vier Felder an: eine Kategorie, eine Priorität, eine kurze Zusammenfassung und eine Entscheidung zur weiteren Bearbeitung. Anschließend wird dasselbe Schema unabhängig mit Ajv geprüft, bevor der Worker einen akzeptierten Datensatz zurückgibt. Deterministische Fixtures schleusen fehlerhafte Modellausgaben ein. So können Sie nachweisen, dass ungültige Daten einem Fehlerpfad folgen, statt in die akzeptierte Antwort zu gelangen.
Dies ist das dritte Lab des Kurses. Sie sollten wissen, dass ein Cloudflare Worker HTTP-Anfragen verarbeitet und dass das AI-Binding Workers AI als env.AI bereitstellt. Wenn Sie direkt in diesen Kurs eingestiegen sind, bearbeiten Sie zuerst Connect LabEx to Your Cloudflare Account. Dort lernen Sie, das VM-Terminal zu verwenden, Wrangler zu autorisieren, Ihr Lernkonto zu bestätigen und dessen Account-ID zu speichern.
Das Lab verwendet @cf/meta/llama-3.3-70b-instruct-fp8-fast, das JSON Mode unterstützt, und hält alle Prompts und Ergebnisse klein. Kostenlose Workers-Konten erhalten derzeit ein gemeinsames tägliches Kontingent von 10.000 Neurons. Workers Paid ist daher nicht erforderlich, solange noch ein kostenloses Kontingent verfügbar ist. Auch lokale Inferenz erreicht Cloudflare und verbraucht dieses Kontingent. Wenn das Modell oder das Kontingent nicht verfügbar ist, brechen Sie ab, statt wiederholt Anfragen zu senden.
Das Setup installiert Node.js 22.22.0, das projektspezifische Wrangler 4.132.0 und Ajv 8.17.1 unter /home/labex/project/ticket-fields. Es stellt deterministische Tests und unabhängige Prüfungen bereit. Das Setup führt keine Anmeldung durch, ruft kein Modell auf, stellt keinen Worker bereit und erstellt keine Cloud-Ressource. Lassen Sie diese VM geöffnet, bis Sie den temporären Worker gelöscht und die Abmeldung überprüft haben.
VM autorisieren und den Extraktions-Worker konfigurieren
In diesem Schritt autorisieren Sie diese frische VM und konfigurieren einen temporären Worker. Eine Anmeldung im Dashboard gilt für den Browser. Wrangler benötigt in einer neuen VM dagegen eine eigene, eingeschränkte Autorisierung, bevor es das Lernkonto verwalten kann.
Wechseln Sie in das vorbereitete Projekt und bestätigen Sie die festgelegte Wrangler-Version:
cd /home/labex/project/ticket-fields
npx wrangler --version
Erwartet wird 4.132.0. Fordern Sie dieselben eingeschränkten Berechtigungen an, die in den vorherigen Workers-AI-Labs verwendet wurden. Wrangler 4.132.0 prüft beim Löschen eines Workers KV-Abhängigkeiten. workers_kv:write verhindert daher einen unabhängigen Fehler beim Aufräumen, obwohl dieses Lab keine KV-Daten erstellt.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write ai:write
Öffnen Sie den angezeigten Link, geben Sie den aktuellen Gerätecode ein, prüfen Sie Konto und Berechtigungen und autorisieren Sie das Lernkonto. Kehren Sie anschließend zum Terminal zurück und prüfen Sie die strukturierten Identitätsdaten:
npx wrangler whoami --json
Bestätigen Sie loggedIn: true und lesen Sie name und id des vorgesehenen Kontos ab. Erzeugen Sie einen eindeutigen Namen für den temporären Worker:
RUN="labex-c07-a03-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
Ersetzen Sie YOUR_ACCOUNT_ID durch die tatsächliche ID dieses Kontos:
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
Das AI-Binding wird als env.AI verfügbar. remote: true bedeutet, dass der lokale Worker-Prozess weiterhin das echte, kontogebundene Modell aufruft. Observability speichert die kleinen Lebenszyklusereignisse, die Sie nach der Bereitstellung untersuchen. Eine Inferenz oder Bereitstellung hat bisher noch nicht stattgefunden.
Den Vertrag für strukturierte Ausgaben lesen
In diesem Schritt untersuchen Sie die beiden Schutzschichten der Anwendung. JSON Mode sendet ein Schema zusammen mit der Modellanfrage. Ajv prüft den zurückgegebenen Wert innerhalb des Workers gegen dieses Schema. Die erste Schicht steuert die Generierung; die zweite entscheidet, ob der Wert sicher akzeptiert werden kann.
Erzeugen Sie die Umgebungstypen des Workers:
npx wrangler types
grep -A4 'interface __BaseEnv_Env' worker-configuration.d.ts
Suchen Sie nach AI: Ai. Dieses Binding wird von der Plattform bereitgestellt und ist kein im Quellcode gespeicherter API-Schlüssel des Modells.
Der Datensatz enthält vier Felder:
category: billing | account | upload | other
priority: low | medium | high
summary: nonempty text, at most 160 characters
needs_follow_up: true or false
Im JSON Schema legt type die Art des Werts fest, enum beschränkt einen Wert auf eine bekannte Liste, required benennt die erforderlichen Felder und additionalProperties: false weist unerwartete Felder zurück. Die letzte Regel ist wichtig, weil ein erfundenes Feld sonst unbemerkt durchgelassen werden könnte. Das Schema beschreibt die Struktur, nicht die objektive Richtigkeit der Interpretation des Modells. Eine Person oder eine spätere Geschäftsregel kann die akzeptierten Felder weiterhin prüfen.
Untersuchen Sie die bereitgestellten fehlerhaften Fixtures für den deterministischen Test:
grep -nE 'security|priority: 1|internal_note|not-an-object' test/worker.test.mjs
Diese Fixtures verbrauchen keine Neurons. Sie ermöglichen es dem Test, Fälle zuverlässig zu prüfen, die bei wiederholten Live-Prompts absichtlich nie erzeugt werden sollten.
Den validierten Extraktionsendpunkt erstellen
In diesem Schritt implementieren Sie das Schema, die Modellanfrage und die anwendungsseitige Validierung. Nur der Zweig, der die Ajv-Prüfung besteht, gibt einen record zurück.
Erstellen Sie den Einstiegspunkt des Workers:
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
Der Worker protokolliert weder das Ticket noch die zurückgegebenen Felder. Die Request-ID verbindet die Clientantwort mit einem akzeptierten, abgelehnten oder fehlgeschlagenen Lebenszyklusereignis, ohne Supportinhalte in die Observability-Daten zu kopieren. Auch Ajv-Fehlerdetails werden nicht an die Clientantwort übergeben, da sie interne Details des Validierungsdesigns offenlegen könnten. Clients erhalten den stabilen Vertrag invalid_model_output.
Führen Sie die deterministischen Tests aus:
node --test test/worker.test.mjs
Erwartet werden fünf erfolgreiche Tests. Ein Test schleust über ein gefälschtes AI-Binding sieben fehlerhafte Kandidaten ein und verlangt, dass jede Antwort keinen record enthält. Bündeln Sie anschließend den echten Worker, ohne ihn bereitzustellen:
npx wrangler deploy --dry-run
Die Fixtures belegen das Zurückweisen fehlerhafter Daten, ohne von variablen Modellausgaben abzuhängen. Der Dry Run bestätigt, dass Quellcode, Ajv-Abhängigkeit und Worker-Konfiguration gemeinsam gebündelt werden können. Im nächsten Schritt führen Sie eine echte strukturierte Inferenz aus.
Ein echtes strukturiertes Ergebnis verarbeiten
In diesem Schritt führen Sie den Worker in der VM aus und senden eine echte Anfrage mit JSON Mode. „Lokal“ beschreibt den Request-Handler. Das AI-Binding verwendet weiterhin das ausgewählte Cloudflare-Konto und verbraucht einen Teil des täglichen Kontingents.
Starten Sie Wrangler im Hintergrund und speichern Sie seine Prozess-ID:
npx wrangler dev --port 8787 > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
Warten Sie auf die AI-unabhängige Health-Route:
for attempt in $(seq 1 30); do
if curl --silent --fail http://127.0.0.1:8787/health; then
break
fi
sleep 1
done
Senden Sie ein klares synthetisches Ticket:
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."}'
Erwartet wird eine JSON-Antwort mit record und requestId. Die genaue Kategorie, Priorität, Formulierung der Zusammenfassung und Entscheidung zur weiteren Bearbeitung können variieren. Entscheidend ist, dass record genau vier Felder enthält und jeder Wert dem Schema entspricht.
Weisen Sie nun nach, dass eine ungültige Anwendungsanfrage abgewiesen wird, bevor das Modell aufgerufen wird:
curl --silent --show-error --write-out '\nHTTP %{http_code}\n' \
http://127.0.0.1:8787/extract \
--header 'Content-Type: application/json' \
--data '{"ticket":""}'
Erwartet werden {"error":"invalid_ticket"} und HTTP 400. Die Eingabevalidierung schützt den Modellaufruf; die Ausgabevalidierung schützt den Anwendungsdatensatz. Das sind zwei getrennte Grenzen.
Den Worker bereitstellen und die akzeptierte Ausgabe prüfen
In diesem Schritt stellen Sie denselben validierten Endpunkt bereit und verbinden den sichtbaren Dashboard-Status mit dem Laufzeitergebnis. Stoppen Sie zuerst nur den gespeicherten Entwicklungsprozess:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
Stellen Sie den Worker bereit:
npx wrangler deploy
Speichern Sie die von Wrangler ausgegebene exakte workers.dev-URL:
WORKER_URL="https://YOUR_WORKER_URL"
Senden Sie eine begrenzte öffentliche Anfrage:
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."}'
Bestätigen Sie, dass die öffentliche Antwort unter record erneut genau die Felder des Schemas enthält. Ein erfolgreicher HTTP-Status allein genügt nicht. Die unabhängige Prüfung validiert zusätzlich jedes zurückgegebene Feld und das bereitgestellte AI-Binding.
Öffnen Sie das Cloudflare Dashboard und gehen Sie zu Workers & Pages → Overview → Ihrem labex-c07-a03-...-Worker. Prüfen Sie dessen Binding und öffnen Sie anschließend Observability → Logs. Suchen Sie nach ticket_output_accepted, klappen Sie das Ereignis auf und bestätigen Sie model, requestId und den Ereignisnamen. Das Protokoll enthält absichtlich weder das Ticket noch den extrahierten Datensatz.
Die folgende Binding-Ansicht stammt aus einem temporären Debug-Lauf. Diagramm und Tabelle verbinden beide den Namen AI mit Workers AI. Das ist das sichtbare Gegenstück im Dashboard zu env.AI im Worker. Ihr eindeutiger Worker-Name wird anders lauten.

Im selben Lauf wurden nach der öffentlichen Anfrage und den unabhängigen Prüfungen 3 Success und 0 Errors aufgezeichnet. Diese Werte sind Beispiele und keine vorgeschriebenen Zählungen. Wichtig ist, dass der ausgewählte Worker die sichtbaren /extract-Anfragen erfolgreich verarbeitet hat.

Nach dem Filtern nach ticket_output_accepted zeigt das aufgeklappte Anwendungsereignis das genaue Llama-Modell, eine Request-ID und den Namen des akzeptierten Ereignisses. Es enthält weder das synthetische Ticket noch den extrahierten Datensatz. Damit ist die Datenschutzgrenze bestätigt. Eine Protokollzeile allein beweist jedoch nicht, dass die Schema-Validierung bestanden wurde; diesen Nachweis liefern die Laufzeitantwort und die unabhängige Prüfung.

Öffnen Sie anschließend Workers AI und prüfen Sie die heutige Modellnutzung. Suchen Sie das Modell Llama 3.3 und bestätigen Sie, dass die begrenzten Übungen innerhalb des Kontingents von 10.000 Neurons für Workers Free bleiben. Die Dashboard-Anzeige kann verzögert erscheinen. Warten Sie daher kurz, statt die Inferenz zu wiederholen, nur um eine Grafik oder Protokollaktualisierung zu erzwingen.
Das Konto im Beispiel zeigte für das Llama-Modell 261.63/10k Neurons. Dieser Gesamtwert umfasst frühere Übungen der Kurserstellung mit demselben Lernkonto. Er entspricht daher nicht ausschließlich den Kosten dieses Labs, und Ihr Wert wird abweichen. Der Prüfpunkt ist, innerhalb des kostenlosen Kontingents zu bleiben – nicht die Beispielzahl zu erreichen.

Die Dashboard-Werte gehören zu diesem temporären Lauf. Die Lernziele sind die exakte Worker-Identität, ihr AI-Binding, ein datenschutzbegrenztes akzeptiertes Ereignis und die Nutzung des kostenlosen Kontingents. CLI-, API- und Laufzeitprüfungen bleiben maßgeblich, wenn eine Dashboard-Ansicht verzögert aktualisiert wird.
Den Worker löschen und sich abmelden
In diesem Schritt löschen Sie den temporären Worker und entfernen anschließend die Autorisierung dieser VM. Die Nutzung von Workers AI wird auf Kontoebene protokolliert. Das Löschen des Workers entfernt daher den Endpunkt, löscht aber weder den Nutzungsdatensatz noch ändert es den Kontotarif.
Löschen Sie exakt den in wrangler.jsonc angegebenen Worker:
npx wrangler delete
Bestätigen Sie den Vorgang erst, wenn Wrangler den eindeutigen Namen dieses Labs labex-c07-a03-... anzeigt. Der Befehl sollte mit Successfully deleted enden. Aktualisieren Sie Workers & Pages → Overview und bestätigen Sie, dass dieser genaue Name nicht mehr vorhanden ist.
Führen Sie die unabhängige Verwaltungsprüfung aus, solange die VM noch autorisiert ist:
python3 .labex/verify.py deleted
Erst wenn PASS: deleted ausgegeben wird, entfernen Sie die gespeicherte Autorisierung der VM:
npx wrangler logout
npx wrangler whoami --json
Es muss loggedIn: false angezeigt werden. Eine fehlende lokale Datei, ein geschlossener Browser-Tab oder ein Netzwerkfehler würde weder die Löschung in der Cloud noch die Abmeldung beweisen.
Zusammenfassung
Sie haben einen Workers-AI-Endpunkt erstellt, der mit JSON Mode und einem JSON Schema strukturierte Ticketfelder anfordert. Sie haben gelernt, warum eine angeforderte Struktur nicht dasselbe ist wie vertrauenswürdige Daten, Ajv als unabhängige Anwendungsschranke eingesetzt und mithilfe fehlerhafter Fixtures nachgewiesen, dass ungültige Modellausgaben niemals zu einem akzeptierten Datensatz werden. Sie haben ein echtes lokales und bereitgestelltes Ergebnis mit Workers Free verarbeitet, das akzeptierte Ereignis mit der Observability-Ansicht im Dashboard verbunden, den temporären Worker entfernt und die frische VM abgemeldet.



