Ajouter un endpoint de résumé de tickets

ShellBeginner
Pratiquer maintenant

Introduction

Une application suit généralement des règles écrites directement dans son code. L’inférence par intelligence artificielle (IA) ajoute un autre type d’opération : votre application envoie des données à un modèle entraîné, qui génère un résultat. L’instruction et le contexte envoyés au modèle constituent le prompt. Le texte généré peut varier d’une requête à l’autre. Une application fiable contrôle donc les données d’entrée et vérifie le résultat, au lieu d’attendre une phrase exacte.

Cloudflare Workers AI permet à un Worker d’exécuter des modèles AI pris en charge via la plateforme Cloudflare. Un Worker est un code applicatif qui répond aux requêtes sur le réseau Cloudflare. Une liaison AI est la connexion configurée qui rend Workers AI disponible dans le code sous la forme env.AI ; elle évite d’ajouter une clé API distincte dans le projet.

Dans ce lab, une application de support a besoin d’un court résumé d’un ticket avant qu’un agent n’ouvre sa description complète. Vous allez configurer une liaison AI, implémenter un endpoint POST /summaries, rejeter les données inadaptées avant de consommer de l’inférence, tester le même Worker localement, le déployer, puis examiner l’activité réelle du Worker et de l’AI dans le Cloudflare Dashboard. Le lab utilise @cf/meta/llama-3.3-70b-instruct-fp8-fast, un modèle hébergé par Cloudflare et disponible dans l’allocation gratuite standard de Workers AI. La formulation de la réponse n’est pas évaluée ; c’est le contrat de l’application qui l’est.

Avant de commencer ce cours, terminez Connect LabEx to Your Cloudflare Account. Vous y apprendrez à utiliser le terminal de la VM LabEx, l’autorisation de l’appareil, la confirmation du compte et l’enregistrement de l’ID réel du compte. Vous devez également savoir comment un petit Worker JavaScript traite une requête HTTP. Aucune connaissance en apprentissage automatique n’est nécessaire.

Workers AI fournit actuellement aux comptes Workers Free une allocation quotidienne partagée de 10 000 Neurons, l’unité utilisée par Cloudflare pour mesurer le calcul des modèles. Ce lab utilise des prompts et des sorties courts et ne nécessite pas de forfait payant, mais les autres activités du même compte utilisent la même allocation. Consultez la page actuelle du modèle Llama 3.3 et la page Tarification de Workers AI avant de commencer. Si l’allocation quotidienne a déjà été consommée, l’inférence échoue jusqu’à la réinitialisation de la limite ; ne multipliez pas les appels pour essayer de contourner cette limite. Le développement local de Workers AI utilise également le modèle cloud et consomme cette allocation : il ne s’agit pas d’une simulation hors ligne.

La configuration installe Node.js 22.22.0 et Wrangler 4.132.0 local au projet dans /home/labex/project/ticket-summary. Elle fournit également des tests déterministes qui imitent la réponse de l’AI sans effectuer d’appel au modèle. La configuration n’effectue aucune connexion, modification de forfait, aucun déploiement ni aucune inférence. Gardez cette VM ouverte jusqu’à la suppression du Worker temporaire et à la vérification de la déconnexion.

Autoriser la VM et sélectionner un compte

Dans cette étape, vous allez connecter cette nouvelle VM LabEx à votre compte Cloudflare d’apprentissage et créer une configuration de Worker unique. Une session ouverte dans le Dashboard ne donne pas automatiquement aux commandes du terminal de la VM les droits nécessaires.

Accédez au projet préparé et vérifiez la version imposée de Wrangler :

cd /home/labex/project/ticket-summary
npx wrangler --version

La commande doit afficher 4.132.0. Lancez l’autorisation de l’appareil avec uniquement les permissions nécessaires à ce lab. workers_scripts:write permet de déployer, lire et supprimer le Worker temporaire. ai:write autorise le Worker à appeler Workers AI. Wrangler 4.132.0 vérifie également les références aux liaisons KV lors de la suppression d’un Worker ; workers_kv:write permet donc à cette vérification de nettoyage d’aller jusqu’au bout, même si ce lab ne crée aucun espace de noms KV. Les droits de lecture du compte et de l’utilisateur vous permettent de confirmer le compte prévu.

npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write ai:write

Ouvrez le lien affiché dans votre navigateur, saisissez le code d’appareil actuel, examinez les permissions et sélectionnez votre compte d’apprentissage. L’accès en arrière-plan peut également apparaître, car Wrangler doit continuer à fonctionner après le parcours dans le navigateur. N’autorisez l’opération qu’après avoir vérifié que le compte et la liste des permissions correspondent à ce lab. Revenez ensuite au terminal et attendez la confirmation de réussite.

npx wrangler whoami --json

Vérifiez que loggedIn: true est présent, puis lisez les valeurs name et id du compte que vous souhaitez utiliser, même si un seul compte est affiché. Le nom vous aide à éviter d’utiliser le mauvais compte ; l’ID est la valeur stable que Wrangler enregistre dans la configuration.

Générez un nom de Worker unique. openssl rand -hex 6 crée 12 caractères hexadécimaux aléatoires, et $(...) les insère dans la variable du shell.

RUN="labex-c07-a01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"

Copiez l’ID du compte sélectionné dans la configuration ci-dessous en remplaçant YOUR_ACCOUNT_ID. Un document here-doc écrit les lignes situées entre les deux marqueurs JSON dans wrangler.jsonc. Le marqueur non entouré de guillemets permet à $RUN d’être développé, tandis que l’antislash conserve la clé $schema telle quelle.

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",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  },
  "ai": {
    "binding": "AI",
    "remote": true
  }
}
JSON

compatibility_date fixe le comportement d’exécution testé par ce lab. observability conserve les journaux d’invocation et d’application nécessaires à la vérification ultérieure dans le Dashboard. L’écriture du fichier ne déploie pas de Worker et n’effectue aucun appel au modèle.

Examiner la liaison Workers AI

Dans cette étape, vous allez transformer la configuration en description typée de l’environnement du Worker et relier le nom de la liaison au code que vous écrirez ensuite.

Une liaison est une capacité nommée fournie par le runtime Workers. Le nom AI dans wrangler.jsonc signifie que le Worker utilisera env.AI pour exécuter des modèles. Aucun jeton API ne figure dans le code source : Cloudflare connecte le Worker déployé au compte sélectionné. Le paramètre remote: true est important pendant l’exécution de wrangler dev, car l’inférence du modèle a toujours lieu dans le cloud Cloudflare, même si le gestionnaire de requêtes s’exécute depuis cette VM.

Générez la description des types de l’environnement à partir de la configuration du projet :

npx wrangler types

Wrangler crée worker-configuration.d.ts. Recherchez l’entrée Env générée au lieu de lire tout le fichier :

grep -A4 'interface __BaseEnv_Env' worker-configuration.d.ts

La sortie contient une liaison AI similaire à celle-ci :

interface __BaseEnv_Env {
    AI: Ai;
}

Wrangler place les liaisons générées dans une interface de base, puis l’étend avec Env. La ligne AI: Ai constitue la vérification de cohérence utile : si vous modifiez le nom de la liaison dans la configuration sans mettre le code à jour, le déploiement pourrait réussir mais échouer lors de l’exécution. Régénérez les types chaque fois que les liaisons changent. Une simulation complète du déploiement vérifiera ensuite ensemble cette configuration et le bundle du Worker.

Créer un endpoint de résumé avec des limites

Dans cette étape, vous allez implémenter la limite de requête et l’appel au modèle. Un modèle de langage est efficace pour produire une explication concise, mais il ne doit pas décider si une requête arbitraire peut être traitée en toute sécurité. Le code applicatif classique doit rejeter le mauvais type de contenu, le JSON mal formé, les détails manquants et les entrées trop volumineuses avant l’inférence.

L’endpoint enverra deux messages au modèle. Un message système définit le rôle du modèle et la contrainte de réponse. Un message utilisateur contient le ticket synthétique. Les modèles lisent et génèrent des tokens, c’est-à-dire de petites unités de texte pouvant être un mot, une partie de mot ou un signe de ponctuation. max_tokens limite la sortie générée, tandis que l’application limite séparément le nombre de caractères reçus. Ces contrôles sont différents : l’un limite ce que vous envoyez et l’autre limite ce que le modèle peut générer. temperature contrôle le niveau de variation autorisé ; la faible valeur utilisée ici favorise un résumé stable sans garantir une formulation identique.

Créez le point d’entrée du Worker :

cat > src/index.js <<'JS'
const MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
const MAX_DETAILS = 2000;

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 > 4096) {
    return { error: json({ error: "ticket_too_large" }, 413) };
  }

  let body;
  try {
    body = JSON.parse(raw);
  } catch {
    return { error: json({ error: "invalid_json" }, 400) };
  }

  const subject = typeof body?.subject === "string" ? body.subject.trim() : "";
  const details = typeof body?.details === "string" ? body.details.trim() : "";
  if (!details) {
    return { error: json({ error: "invalid_ticket" }, 400) };
  }
  if (subject.length > 120 || details.length > MAX_DETAILS) {
    return { error: json({ error: "ticket_too_large" }, 413) };
  }
  return { ticket: { subject, details } };
}

async function summarize(request, env) {
  const requestId = crypto.randomUUID();
  const parsed = await readTicket(request);
  if (parsed.error) return parsed.error;

  try {
    const result = await env.AI.run(MODEL, {
      messages: [
        {
          role: "system",
          content: "Summarize this support ticket in one plain sentence. Do not invent facts."
        },
        {
          role: "user",
          content: `Subject: ${parsed.ticket.subject || "(none)"}\nDetails: ${parsed.ticket.details}`
        }
      ],
      max_tokens: 120,
      temperature: 0.2
    });

    const summary = result.response?.trim();
    if (!summary) throw new Error("empty model response");

    console.log(JSON.stringify({
      event: "ticket_summarized",
      requestId,
      model: MODEL,
      inputCharacters: parsed.ticket.details.length,
      totalTokens: result.usage?.total_tokens ?? null
    }));

    return json({ summary, model: MODEL, requestId });
  } catch (error) {
    console.error(JSON.stringify({
      event: "ticket_summary_failed",
      requestId,
      model: MODEL,
      reason: error instanceof Error ? error.message : "unknown"
    }));
    return json({ error: "model_unavailable", requestId }, 502);
  }
}

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 === "/summaries") {
      return summarize(request, env);
    }
    return json({ error: "not_found" }, 404);
  }
};
JS

Chaque requête reçoit un ID de requête aléatoire, présent à la fois dans la réponse et dans le journal. Vous pouvez ainsi suivre une requête sans enregistrer son ticket. Le code journalise cet ID, le modèle utilisé et plusieurs compteurs, mais pas le texte du ticket. Cela rend l’observabilité — les enregistrements qui vous aident à comprendre ce que le Worker a fait — utile sans copier le contenu client dans les données de supervision. Le code valide également la chaîne response renvoyée par ce modèle précis, au lieu de supposer que tous les modèles Workers AI renvoient le même objet.

Exécutez les tests déterministes fournis. Ils remplacent env.AI par un petit jeu de données fixe ; ces tests ne consomment donc pas l’utilisation du modèle :

node --test test/worker.test.mjs

Les quatre tests doivent réussir. Demandez ensuite à Wrangler de construire le Worker sans le déployer :

npx wrangler deploy --dry-run

Les tests prouvent les contrats d’entrée et de sortie avec des données de modèle contrôlées. La simulation de déploiement prouve que Wrangler peut créer le bundle du Worker réel. Ni l’un ni l’autre ne prouve que le modèle est actuellement disponible ou que ce compte dispose encore de son allocation gratuite quotidienne ; vous le vérifierez ensuite avec une requête réelle.

Effectuer une inférence locale

Dans cette étape, vous allez exécuter le gestionnaire de requêtes depuis la VM, tandis que sa liaison AI appellera le véritable modèle hébergé par Cloudflare. On parle de développement local, mais seul le processus Worker est local : l’inférence est distante et comptabilisée.

Démarrez Wrangler en arrière-plan sur le port 8787. > enregistre les journaux dans un fichier, 2>&1 envoie les erreurs dans ce même fichier et & rend l’invite du terminal pendant que le serveur continue de fonctionner. L’enregistrement de $! conserve l’ID du processus pour le nettoyage.

npx wrangler dev --port 8787 > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid

Attendez que la route de santé réponde :

for attempt in $(seq 1 30); do
  if curl --silent --fail http://127.0.0.1:8787/health; then
    break
  fi
  sleep 1
done

La réponse de santé doit être {"status":"ok"} et n’appelle pas le modèle. Envoyez maintenant un petit ticket synthétique. --data transforme la requête en POST, tandis que l’en-tête indique au Worker de parser du JSON.

curl --silent --show-error http://127.0.0.1:8787/summaries \
  --header 'Content-Type: application/json' \
  --data '{"subject":"Invoice upload fails","details":"After signing in, the customer selects a PDF invoice. The upload stops before completion and no confirmation appears."}' | jq

La réponse doit contenir un summary non vide, l’ID exact du modèle et un requestId propre à cette exécution. Votre phrase peut différer de l’exemple suivant :

{
  "summary": "The customer cannot complete a PDF invoice upload after signing in.",
  "model": "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
  "requestId": "..."
}

Vérifiez qu’une entrée invalide est rejetée par le code classique avant l’inférence :

curl --silent --show-error --write-out '\nHTTP %{http_code}\n' \
  http://127.0.0.1:8787/summaries \
  --header 'Content-Type: application/json' \
  --data '{"details":""}'

La réponse attendue est {"error":"invalid_ticket"} avec le code HTTP 400. L’application n’envoie pas cette requête au modèle. Si la requête valide renvoie model_unavailable, examinez .labex/dev.log ; une allocation gratuite épuisée, une capacité insuffisante du modèle ou une erreur d’autorisation ne prouve pas que le contrat de l’endpoint est respecté.

Déployer et examiner le Worker AI

Dans cette étape, vous allez arrêter le processus local, déployer le même code sur Cloudflare et relier les éléments visibles dans la ligne de commande à l’état affiché dans le Dashboard.

Arrêtez uniquement le processus de développement enregistré et attendez sa fin :

kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true

Déployez le Worker :

npx wrangler deploy

Wrangler affiche l’URL publique workers.dev. Enregistrez cette URL exacte en remplaçant la valeur d’exemple ci-dessous :

WORKER_URL="https://YOUR_WORKER_URL"

Envoyez un nouveau ticket synthétique à l’endpoint déployé :

curl --silent --show-error "$WORKER_URL/summaries" \
  --header 'Content-Type: application/json' \
  --data '{"subject":"Password reset loop","details":"The customer opens the reset email, chooses a new password, and returns to the sign-in page, but the old password remains active."}' | jq

La phrase générée peut différer, mais model doit identifier Llama 3.3 et requestId doit être présent. Cela prouve que le Worker public a bien atteint sa liaison AI configurée.

Ouvrez le Cloudflare Dashboard et accédez à Workers & Pages → Overview → votre Worker labex-c07-a01-... → Settings → Bindings. Repérez la liaison Workers AI AI. Il s’agit de la connexion visible entre wrangler.jsonc et env.AI dans le code.

Worker connecté à la liaison Workers AI nommée AI

L’exemple affiche le nom de liaison AI, qui correspond au nom utilisé par env.AI. Le nom de votre Worker temporaire sera différent.

Ouvrez ensuite Observability → Logs pour le même Worker. Recherchez une invocation récente réussie et développez le journal structuré ticket_summarized. Comparez son ID de requête avec celui de la réponse du Worker déployé. Le journal doit afficher le modèle et les compteurs, sans afficher le texte du ticket. Si les journaux enregistrés ne sont pas encore disponibles, utilisez Real-time logs, envoyez une petite requête synthétique supplémentaire et examinez cette invocation.

Observabilité Workers affichant des requêtes réussies avec le forfait Free

La vue d’ensemble confirme d’abord que les requêtes ont atteint le Worker sans erreur. L’ouverture d’une requête révèle l’événement applicatif structuré :

Journal ticket_summarized structuré affichant le modèle, le nombre de tokens et l’ID de requête sans le texte du ticket

Remarquez que le journal contient des informations opérationnelles telles que le modèle, le nombre de tokens et l’ID de requête, mais pas le sujet ni les détails du ticket de support. C’est la limite de confidentialité créée par le code de journalisation que vous avez écrit.

Enfin, ouvrez Workers AI depuis la navigation Developer Platform et examinez la vue d’utilisation. Recherchez l’activité récente du modèle ou l’utilisation de Neurons associée à ce test limité. Les données d’utilisation peuvent apparaître après la requête ; un graphique immédiatement vide ne permet pas de conclure et ne doit pas être « corrigé » en générant des appels d’inférence répétés.

Utilisation de Neurons de Workers AI pour le modèle Llama 3.3 dans l’allocation quotidienne Free

Ici, 20.32/10k signifie que cette exécution de validation n’a consommé qu’une petite partie de l’allocation quotidienne Free de ce compte. Votre total inclut les autres activités Workers AI de votre compte d’apprentissage ; il ne correspondra donc pas à la capture d’écran.

Les captures d’écran du Dashboard de ce lab présentent des valeurs d’exemple issues d’une exécution de validation temporaire. Le nom de votre Worker, l’ID de requête, les horodatages, le nombre de tokens et les totaux d’utilisation seront différents.

Supprimer le Worker et se déconnecter

Dans cette étape, vous allez supprimer l’application cloud temporaire, puis révoquer la session Wrangler de cette VM. La suppression du Worker désactive son endpoint public. Elle ne modifie pas votre forfait Workers et n’efface pas les historiques d’utilisation au niveau du compte.

Supprimez le Worker indiqué dans wrangler.jsonc :

npx wrangler delete

Confirmez la suppression lorsque Wrangler affiche le nom unique de ce lab. Ne supprimez aucune autre application. Dans le Dashboard, retournez à Workers & Pages → Overview et vérifiez que le Worker labex-c07-a01-... exact n’est plus présent. Les journaux historiques ou les données d’utilisation peuvent rester disponibles après la suppression du script.

Wrangler vérifie qu’aucun autre Worker ne dépend de celui-ci avant de terminer. C’est pourquoi la connexion précédente incluait les droits de nettoyage KV, même si votre application n’utilisait pas KV ; une suppression réussie doit revenir à l’invite sans erreur d’authentification.

Effectuez la vérification de suppression tant que la VM est encore autorisée :

python3 .labex/verify.py deleted

Uniquement après l’affichage de PASS: deleted, supprimez l’autorisation enregistrée :

npx wrangler logout
npx wrangler whoami --json

La sortie finale doit indiquer loggedIn: false. Une erreur réseau ne prouve pas que la déconnexion a réussi.

Résumé

Vous avez connecté un Worker à un modèle hébergé par Cloudflare au moyen d’une liaison AI, limité les données d’entrée et la sortie générée, testé le comportement déterministe avant de consommer l’utilisation du modèle, effectué une inférence réelle localement puis après le déploiement, et relié la réponse aux éléments de preuve du Dashboard concernant la liaison, les journaux et l’utilisation. Vous avez également supprimé le Worker temporaire et déconnecté correctement la nouvelle VM.