Introduction
Lorsqu’une requête IA échoue, l’appelant ne voit que la réponse HTTP finale. Cette réponse indique qu’un problème est survenu, mais ne précise pas toujours si la requête était mal formée, rejetée par la passerelle ou rejetée par le fournisseur de modèle en amont. L’observabilité consiste à collecter suffisamment d’éléments pour suivre la requête après son départ de l’appelant et déterminer quelle limite l’a traitée.
AI Gateway enregistre une entrée de journal pour toute requête qui atteint la passerelle. Un journal peut afficher le fournisseur, le modèle, le statut HTTP, la durée et l’utilisation des jetons. Vous pouvez également ajouter quelques éléments de métadonnées personnalisées : de petites étiquettes qui vous aideront à retrouver une requête ultérieurement. Les métadonnées ne constituent pas un coffre-fort privé. Dans cet atelier, vous utilisez uniquement un identifiant de trace aléatoire, un nom de cas synthétique et une valeur booléenne : jamais un identifiant, une invite, une adresse e-mail ou un identifiant de compte.
Vous allez créer une passerelle authentifiée temporaire, puis envoyer une requête Workers AI volontairement mal formée avec une étiquette de trace sûre. Vous retrouverez son journal d’échec, comparerez les échecs d’authentification de la passerelle et du service en amont, corrigerez les données d’entrée et vérifierez que la même trace contient désormais une requête réussie. Le dépannage repose ainsi sur des éléments concrets plutôt que sur des suppositions.
Si vous avez accédé directement à ce cours, terminez d’abord Connect LabEx to Your Cloudflare Account. Cet atelier présente le terminal de la machine virtuelle LabEx, l’autorisation de l’appareil Wrangler, la confirmation du compte d’apprentissage et l’utilisation explicite des identifiants de compte. Terminez également Route Inference Through a Gateway, car cet atelier réutilise ses deux en-têtes d’autorisation distincts.
L’atelier utilise le modèle hébergé par Cloudflare @cf/meta/llama-3.3-70b-instruct-fp8-fast avec la facturation Workers AI Standard. Workers Paid, Unified Billing et un compte de fournisseur externe ne sont pas nécessaires. Les requêtes sont courtes et synthétiques. Si l’allocation quotidienne Workers AI partagée n’est pas disponible, arrêtez-vous plutôt que de réessayer de manière répétée.
La configuration installe Node.js 22.22.0 et Wrangler 4.132.0 dans /home/labex/project/ai-gateway-trace. Elle prépare des évaluations indépendantes en lecture seule, mais n’autorise pas Wrangler, ne crée pas de ressources cloud et n’envoie pas de trafic vers le modèle. LabEx détruit la machine virtuelle temporaire à la fin de l’atelier ; vous devez toutefois supprimer la passerelle et le jeton avant de vous déconnecter, car la destruction de la machine virtuelle ne peut pas supprimer les ressources cloud.
Autoriser la machine virtuelle et créer un identifiant de trace sûr
Dans cette étape, vous allez connecter la nouvelle machine virtuelle à votre compte d’apprentissage et créer les noms d’une passerelle temporaire et d’une trace synthétique.
Un identifiant de trace est une étiquette commune à plusieurs observations liées. Il doit identifier une requête sans révéler ce que l’utilisateur a dit ni son identité. Cet atelier génère une valeur aléatoire et l’enregistre avec les noms des ressources, mais jamais avec les identifiants.
Accédez au projet préparé, vérifiez la version de l’interface de ligne de commande indiquée et autorisez cette machine virtuelle :
cd /home/labex/project/ai-gateway-trace
npx wrangler --version
npx wrangler login --device --browser=false --scopes account:read user:read ai:write
Ouvrez le lien affiché, saisissez le code et autorisez le compte d’apprentissage souhaité. Vérifiez ensuite l’identité structurée :
npx wrangler whoami --json
Vous devez obtenir Wrangler 4.132.0 et loggedIn: true. Remplacez YOUR_ACCOUNT_ID ci-dessous par l’identifiant réel de 32 caractères affiché pour le compte souhaité :
GATEWAY_ID="labex-c09-g02-$(openssl rand -hex 6)"
TOKEN_NAME="$GATEWAY_ID-token"
TRACE_ID="trace-$(openssl rand -hex 8)"
cat > .labex/state.json <<JSON
{
"accountId": "YOUR_ACCOUNT_ID",
"gatewayId": "$GATEWAY_ID",
"tokenName": "$TOKEN_NAME",
"traceId": "$TRACE_ID"
}
JSON
cat .labex/state.json
L’identifiant de trace contient uniquement des données synthétiques sûres. L’identifiant du compte et les noms des ressources restent dans le fichier d’état local afin que le nettoyage ultérieur cible uniquement les ressources de cet atelier.
Créer une passerelle authentifiée et observable
Dans cette étape, vous allez créer une passerelle qui enregistre les requêtes après leur passage par la limite d’authentification de l’appelant.
Ouvrez le Cloudflare Dashboard et choisissez AI → AI Gateway → Create gateway → Custom gateway. Utilisez la valeur enregistrée dans gatewayId comme nom de la passerelle. Laissez la journalisation des requêtes et l’authentification de la passerelle activées. Laissez le cache, les limites de débit, les limites de dépenses et les nouvelles tentatives désactivés, et conservez la facturation Workers AI sur Standard.
Après la création, vérifiez l’identifiant unique de la passerelle dans le fil d’Ariane, puis ouvrez Settings. La journalisation fournit les éléments utilisés dans cet atelier ; l’authentification empêche un appelant inconnu de créer du volume dans les journaux ou de consommer l’utilisation du modèle.
Choisissez Create an AI Gateway authentication token. Utilisez la valeur enregistrée dans tokenName, sélectionnez uniquement le compte d’apprentissage souhaité et définissez exactement les autorisations suivantes :
- AI Gateway — Run pour accéder à la passerelle authentifiée ;
- AI Gateway — Edit pour lire les journaux et supprimer cette passerelle temporaire.
N’ajoutez pas l’autorisation Workers AI. Wrangler fournit séparément l’identifiant temporaire du service en amont. Après avoir vérifié le compte et les autorisations, créez le jeton, puis stockez sa valeur à usage unique sans l’afficher :
bash -c '
while :; do
read -rsp "Paste the AI Gateway token: " GATEWAY_TOKEN
printf "\n"
[ -n "$GATEWAY_TOKEN" ] && break
printf "Token cannot be empty; paste it again.\n" >&2
done
umask 077
printf "%s" "$GATEWAY_TOKEN" > .labex/gateway-token
unset GATEWAY_TOKEN
chmod 600 .labex/gateway-token
'
Vérifiez la ressource exacte à l’aide de l’API de gestion authentifiée :
ACCOUNT_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).accountId')
GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
GATEWAY_TOKEN=$(cat .labex/gateway-token)
curl --http1.1 -fsS \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways/$GATEWAY_ID" \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const b=JSON.parse(s),g=b.result||{};console.log(JSON.stringify({success:b.success,id:g.id,collect_logs:g.collect_logs,authentication:g.authentication},null,2))})'
unset GATEWAY_TOKEN
Vous devez retrouver l’identifiant enregistré avec collect_logs: true et authentication: true.
Envoyer une requête étiquetée avec une entrée invalide
Dans cette étape, vous allez créer un échec d’entrée contrôlé. Les identifiants de la passerelle et du service en amont restent valides ; seule l’entrée du modèle est mal formée.
Les métadonnées personnalisées acceptent au maximum cinq valeurs simples de type chaîne, nombre ou booléen. Les clés commençant par cf. sont réservées par Cloudflare. Cette requête utilise trois valeurs sûres : l’identifiant de trace aléatoire, le nom de cas bad-input et synthetic: true.
Le modèle sélectionné nécessite une invite. Omettez-la volontairement tout en enregistrant la réponse et le statut HTTP :
ACCOUNT_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).accountId')
GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
TRACE_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).traceId')
MODEL='@cf/meta/llama-3.3-70b-instruct-fp8-fast'
METADATA=$(node -e 'process.stdout.write(JSON.stringify({trace_id:process.argv[1],case:"bad-input",synthetic:true}))' "$TRACE_ID")
GATEWAY_TOKEN=$(cat .labex/gateway-token)
UPSTREAM_TOKEN=$(npx wrangler auth token --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).token))')
STATUS=$(curl --http1.1 -sS -D .labex/bad-input-headers.txt \
-o .labex/bad-input-response.json -w '%{http_code}' \
-H "cf-aig-authorization: Bearer $GATEWAY_TOKEN" \
-H "Authorization: Bearer $UPSTREAM_TOKEN" \
-H "cf-aig-metadata: $METADATA" \
-H 'Content-Type: application/json' \
--data '{"max_tokens":16}' \
"https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL")
unset GATEWAY_TOKEN UPSTREAM_TOKEN METADATA
printf '%s\n' "$STATUS" | tee .labex/bad-input-status.txt
Vous devez obtenir HTTP 400 ou 422. Il s’agit d’un échec lié à l’entrée du client, et non d’une preuve d’un problème d’autorisation. Le corps de la réponse est conservé pour un dépannage limité, mais n’est pas affiché automatiquement.
Corréler l’échec avec son journal de passerelle
Dans cette étape, vous allez utiliser l’identifiant de trace pour retrouver l’enregistrement de la requête au lieu d’effectuer une recherche basée uniquement sur l’heure.
Les journaux peuvent mettre un peu de temps à apparaître. Lisez l’inventaire existant des journaux via l’API de gestion, analysez chaque objet de métadonnées simple et affichez uniquement les champs nécessaires pour expliquer l’échec :
ACCOUNT_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).accountId')
GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
TRACE_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).traceId')
GATEWAY_TOKEN=$(cat .labex/gateway-token)
curl --http1.1 -fsS \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways/$GATEWAY_ID/logs?per_page=50" \
> .labex/logs-after-input.json
unset GATEWAY_TOKEN
node - <<'NODE'
const body = require('./.labex/logs-after-input.json')
const trace = require('./.labex/state.json').traceId
const meta = row => {
try { return typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata || {}) }
catch { return {} }
}
const matches = (body.result || []).filter(row => meta(row).trace_id === trace && meta(row).case === 'bad-input')
console.log(matches.map(row => ({
id: row.id,
provider: row.provider,
model: row.model,
success: row.success,
status_code: row.status_code,
duration: row.duration,
tokens_in: row.tokens_in,
tokens_out: row.tokens_out,
metadata: meta(row)
})))
if (!matches.some(row => row.success === false)) process.exit(2)
NODE
Vous devez retrouver l’identifiant de trace enregistré, case: "bad-input", le fournisseur Workers AI et un statut d’échec. Le nombre de jetons peut être vide, car une entrée invalide peut provoquer l’échec avant le début de la génération. Si l’entrée n’est pas encore visible, attendez environ 20 secondes, puis réexécutez ce même bloc en lecture seule.
Ouvrez la vue Logs de la passerelle dans le Dashboard. Utilisez le filtre de métadonnées ou l’horodatage visible pour trouver la ligne en échec, puis ouvrez son panneau de détails. Vérifiez que le modèle, le statut d’échec et les métadonnées personnalisées décrivent la même requête synthétique.


Distinguer les échecs d’autorisation de la passerelle et du service en amont
Dans cette étape, vous allez modifier un seul identifiant à la fois. Les deux tests peuvent renvoyer 401 ou 403, donc le statut seul ne suffit pas ; la présence ou l’absence du journal fournit le contexte manquant.
Commencez par conserver l’identifiant du service en amont valide, mais utilisez un identifiant de passerelle invalide. Une passerelle authentifiée rejette cette requête avant qu’elle puisse y entrer et créer le journal étiqueté du fournisseur :
ACCOUNT_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).accountId')
GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
TRACE_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).traceId')
MODEL='@cf/meta/llama-3.3-70b-instruct-fp8-fast'
METADATA=$(node -e 'process.stdout.write(JSON.stringify({trace_id:process.argv[1],case:"bad-gateway-auth",synthetic:true}))' "$TRACE_ID")
UPSTREAM_TOKEN=$(npx wrangler auth token --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).token))')
STATUS=$(curl --http1.1 -sS -o .labex/bad-gateway-auth-response.json -w '%{http_code}' \
-H 'cf-aig-authorization: Bearer deliberately-invalid-gateway' \
-H "Authorization: Bearer $UPSTREAM_TOKEN" \
-H "cf-aig-metadata: $METADATA" \
-H 'Content-Type: application/json' \
--data '{"prompt":"This request must not reach Workers AI.","max_tokens":8}' \
"https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL")
unset UPSTREAM_TOKEN METADATA
printf '%s\n' "$STATUS" | tee .labex/bad-gateway-auth-status.txt
Conservez maintenant l’identifiant de passerelle valide, mais remplacez uniquement l’identifiant du service Workers AI en amont. Cette requête entre dans la passerelle et peut laisser un enregistrement de fournisseur en échec :
METADATA=$(node -e 'process.stdout.write(JSON.stringify({trace_id:process.argv[1],case:"bad-upstream-auth",synthetic:true}))' "$TRACE_ID")
GATEWAY_TOKEN=$(cat .labex/gateway-token)
STATUS=$(curl --http1.1 -sS -o .labex/bad-upstream-auth-response.json -w '%{http_code}' \
-H "cf-aig-authorization: Bearer $GATEWAY_TOKEN" \
-H 'Authorization: Bearer deliberately-invalid-upstream' \
-H "cf-aig-metadata: $METADATA" \
-H 'Content-Type: application/json' \
--data '{"prompt":"This request should reach the upstream authorization check.","max_tokens":8}' \
"https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL")
unset GATEWAY_TOKEN METADATA
printf '%s\n' "$STATUS" | tee .labex/bad-upstream-auth-status.txt
Vous devez obtenir 401 ou 403 dans les deux cas. Attendez quelques instants, puis lisez les journaux sans générer de nouvelles requêtes et comparez les deux étiquettes :
GATEWAY_TOKEN=$(cat .labex/gateway-token)
curl --http1.1 -fsS \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways/$GATEWAY_ID/logs?per_page=50" \
> .labex/logs-after-auth.json
unset GATEWAY_TOKEN
node - <<'NODE'
const rows = require('./.labex/logs-after-auth.json').result || []
const trace = require('./.labex/state.json').traceId
const meta = row => { try { return typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata || {}) } catch { return {} } }
for (const name of ['bad-gateway-auth', 'bad-upstream-auth']) {
const found = rows.filter(row => meta(row).trace_id === trace && meta(row).case === name)
console.log(name, found.map(row => ({status_code: row.status_code, success: row.success, provider: row.provider})))
}
NODE
L’étiquette d’authentification de la passerelle ne doit correspondre à aucun journal de fournisseur ; l’étiquette d’authentification en amont doit afficher une ligne Workers AI en échec. C’est pourquoi un schéma des limites et des journaux corrélés sont plus informatifs qu’un statut HTTP pris isolément.

Corriger la requête et confirmer sa réussite
Dans cette étape, vous allez rétablir les deux identifiants valides et fournir l’invite requise. La correction est terminée uniquement lorsque le résultat d’exécution et l’observabilité concordent.
Utilisez le même identifiant de trace avec un nouveau nom de cas repaired :
METADATA=$(node -e 'process.stdout.write(JSON.stringify({trace_id:process.argv[1],case:"repaired",synthetic:true}))' "$TRACE_ID")
GATEWAY_TOKEN=$(cat .labex/gateway-token)
UPSTREAM_TOKEN=$(npx wrangler auth token --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).token))')
STATUS=$(curl --http1.1 -sS -o .labex/repaired-response.json -w '%{http_code}' \
-H "cf-aig-authorization: Bearer $GATEWAY_TOKEN" \
-H "Authorization: Bearer $UPSTREAM_TOKEN" \
-H "cf-aig-metadata: $METADATA" \
-H 'Content-Type: application/json' \
--data '{"prompt":"In one short sentence, explain why trace IDs help debugging.","max_tokens":48}' \
"https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL")
unset GATEWAY_TOKEN UPSTREAM_TOKEN METADATA
printf '%s\n' "$STATUS" | tee .labex/repaired-status.txt
node -e 'const b=require("./.labex/repaired-response.json"); console.log(b.result?.response ?? b.result)'
Vous devez obtenir HTTP 200 et un texte généré non vide. Si nécessaire, attendez l’apparition du journal, puis réexécutez l’inventaire des journaux en lecture seule de l’étape précédente. Dans le Dashboard, filtrez avec l’identifiant de trace et comparez bad-input, bad-upstream-auth et repaired. La ligne corrigée doit indiquer une réussite, un statut 200 et une utilisation des jetons.

Supprimer la passerelle temporaire
Dans cette étape, vous allez supprimer la ressource cloud tout en vérifiant, avec l’identifiant de gestion encore disponible, qu’elle n’existe plus.
ACCOUNT_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).accountId')
GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
GATEWAY_TOKEN=$(cat .labex/gateway-token)
curl --http1.1 -fsS -X DELETE \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways/$GATEWAY_ID" \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const b=JSON.parse(s);if(!b.success)process.exit(1);console.log("gateway deletion accepted")})'
unset GATEWAY_TOKEN
GATEWAY_TOKEN=$(cat .labex/gateway-token)
curl --http1.1 -fsS \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways" \
> .labex/gateways-after-delete.json
unset GATEWAY_TOKEN
node -e 'const b=require("./.labex/gateways-after-delete.json"),id=process.argv[1],found=(b.result||[]).some(g=>g.id===id);console.log("gateway absent:",!found);if(found)process.exit(1)' "$GATEWAY_ID"
Vous devez obtenir gateway absent: true. Cet inventaire authentifié permet de distinguer une suppression réelle d’une page manquante due à une déconnexion ou à un problème réseau.
Supprimer le jeton et se déconnecter
Dans cette étape, vous allez révoquer l’identifiant cloud restant et déconnecter la machine virtuelle.
Dans le Cloudflare Dashboard, ouvrez My Profile → API Tokens. Recherchez la valeur exacte enregistrée dans tokenName, ouvrez Actions, choisissez Delete, examinez la confirmation, puis supprimez uniquement ce jeton. Vous pouvez le révoquer maintenant, car la suppression de la passerelle a déjà été vérifiée.
Effacez la copie présente sur la machine virtuelle et mettez fin à l’autorisation distincte de Wrangler :
shred -u .labex/gateway-token
npx wrangler logout
npx wrangler whoami --json || true
test ! -e .labex/gateway-token && echo "local gateway token removed"
Vous devez obtenir loggedIn: false et local gateway token removed. La session du Dashboard est distincte et reste ouverte. À la fin de l’atelier, LabEx détruit cette machine virtuelle temporaire au lieu de l’enregistrer.
Résumé
Vous avez utilisé des métadonnées personnalisées sûres pour corréler une requête Workers AI mal formée avec son journal AI Gateway. Vous avez appris qu’un statut HTTP nécessite le contexte de la limite concernée : une authentification de passerelle invalide est rejetée avant la création d’un journal de fournisseur, tandis qu’une autorisation en amont invalide apparaît comme un enregistrement Workers AI en échec. Vous avez ensuite corrigé l’entrée, confirmé la génération de texte et la présence d’un journal corrélé réussi, puis supprimé tous les identifiants et ressources temporaires.
L’atelier suivant applique la même approche fondée sur les éléments concrets à la mise en cache. Vous répéterez une requête publique limitée, distinguerez un accès au cache d’un nouvel appel au modèle et contournerez le cache lorsqu’un résultat récent sera nécessaire.



