Введение
Когда запрос к AI завершается ошибкой, вызывающая сторона видит только итоговый HTTP-ответ. Из этого ответа понятно, что произошла ошибка, но не всегда ясно, был ли запрос сформирован неправильно, отклонён шлюзом или отклонён вышестоящим поставщиком модели. Наблюдаемость означает сбор достаточного количества свидетельств, чтобы отследить запрос после того, как он покинул вызывающую сторону, и определить, какая граница его обработала.
AI Gateway создаёт одну запись журнала для каждого запроса, достигшего шлюза. В журнале могут быть указаны поставщик, модель, HTTP-код, длительность и использование токенов. Кроме того, к запросу можно добавить несколько элементов пользовательских метаданных — небольших меток, которые помогают позднее найти запрос. Метаданные не являются защищённым хранилищем. В этой лабораторной работе используются только случайный идентификатор трассировки, синтетическое имя сценария и логическое значение — никогда не передавайте учётные данные, текст запроса, адрес электронной почты или идентификатор аккаунта.
Вы создадите один временный шлюз с аутентификацией, затем отправите намеренно некорректный запрос Workers AI с безопасной меткой трассировки. Вы найдёте запись о сбое, сравните ошибки аутентификации шлюза и вышестоящего поставщика, исправите входные данные и убедитесь, что тот же идентификатор трассировки теперь связан с успешным запросом. Так устранение неполадок превращается в анализ свидетельств, а не в догадки.
Если вы открыли этот курс напрямую, сначала выполните лабораторную работу Connect LabEx to Your Cloudflare Account. В ней объясняется, как использовать терминал виртуальной машины LabEx, авторизовать устройство Wrangler, подтвердить учебный аккаунт и явно указать идентификатор аккаунта. Также сначала выполните Route Inference Through a Gateway, поскольку эта лабораторная работа использует два отдельных заголовка авторизации.
В лабораторной работе используется размещённая в Cloudflare модель @cf/meta/llama-3.3-70b-instruct-fp8-fast со стандартной тарификацией Workers AI. Workers Paid, Unified Billing и аккаунт внешнего поставщика не требуются. Запросы небольшие и синтетические. Если общая дневная квота Workers AI недоступна, остановитесь и не повторяйте запросы многократно.
Настройка устанавливает Node.js 22.22.0 и локальную для проекта версию Wrangler 4.132.0 в /home/labex/project/ai-gateway-trace. Она подготавливает независимые проверки только для чтения, но не авторизует Wrangler, не создаёт облачные ресурсы и не отправляет запросы к модели. После завершения лабораторной работы LabEx уничтожает временную виртуальную машину. Однако перед выходом вы всё равно удалите шлюз и токен, поскольку уничтожение виртуальной машины само по себе не удаляет облачные ресурсы.
Авторизуйте виртуальную машину и создайте безопасный идентификатор трассировки
На этом шаге вы подключите новую виртуальную машину к учебному аккаунту и создадите имена для одного временного шлюза и одной синтетической трассировки.
Идентификатор трассировки — это метка, общая для связанных наблюдений. Она должна идентифицировать запрос, не раскрывая содержание сообщения пользователя и его личность. В этой лабораторной работе создаётся случайное значение, которое сохраняется вместе с именами ресурсов, а не с учётными данными.
Перейдите в подготовленный проект, проверьте зафиксированную версию CLI и авторизуйте эту виртуальную машину:
cd /home/labex/project/ai-gateway-trace
npx wrangler --version
npx wrangler login --device --browser=false --scopes account:read user:read ai:write
Откройте показанную ссылку, введите код и авторизуйте нужный учебный аккаунт. Проверьте структурированные сведения о пользователе:
npx wrangler whoami --json
Ожидаются Wrangler 4.132.0 и loggedIn: true. Замените YOUR_ACCOUNT_ID ниже фактическим 32-символьным идентификатором, показанным для нужного аккаунта:
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
Идентификатор трассировки содержит безопасные синтетические данные. Идентификатор аккаунта и имена ресурсов сохраняются в локальном файле состояния, чтобы последующие операции очистки затронули только ресурсы этой лабораторной работы.
Создайте наблюдаемый шлюз с аутентификацией
На этом шаге вы создадите шлюз, который записывает запросы после прохождения ими границы аутентификации вызывающей стороны.
Откройте Cloudflare Dashboard и выберите AI → AI Gateway → Create gateway → Custom gateway. В качестве имени шлюза используйте сохранённое значение gatewayId. Оставьте включёнными ведение журналов запросов и аутентификацию шлюза. Оставьте выключенными кэш, ограничения скорости, ограничения расходов и повторы запросов, а для тарификации Workers AI оставьте значение Standard.
После создания проверьте уникальный идентификатор шлюза в цепочке навигации и откройте Settings. Журналирование создаёт свидетельства, используемые в этой лабораторной работе, а аутентификация не позволяет неизвестному вызывающему создавать записи журналов или расходовать ресурсы модели.
Выберите Create an AI Gateway authentication token. Используйте сохранённое значение tokenName, укажите только нужный учебный аккаунт и задайте ровно следующие разрешения:
- AI Gateway — Run — для входа в аутентифицированный шлюз;
- AI Gateway — Edit — для чтения журналов и удаления этого временного шлюза.
Не добавляйте разрешение Workers AI. Wrangler предоставляет отдельные краткосрочные учётные данные для вышестоящего сервиса. После проверки аккаунта и разрешений создайте токен, затем сохраните его одноразовое значение, не выводя его на экран:
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
'
Проверьте именно этот ресурс через аутентифицированный API управления:
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
Ожидается сохранённый идентификатор с collect_logs: true и authentication: true.
Отправьте запрос с меткой и некорректными входными данными
На этом шаге вы создадите контролируемую ошибку входных данных. Учётные данные шлюза и вышестоящего сервиса останутся действительными; некорректными будут только входные данные модели.
Пользовательские метаданные могут содержать не более пяти простых значений типов string, number или Boolean. Ключи, начинающиеся с cf., зарезервированы Cloudflare. В этом запросе используются три безопасных значения: случайный идентификатор трассировки, имя сценария bad-input и значение synthetic: true.
Выбранной модели требуется prompt. Намеренно не указывайте его, одновременно сохранив ответ и 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
Ожидается HTTP 400 или 422. Это ошибка входных данных клиента, а не доказательство проблемы с авторизацией. Тело ответа сохраняется для ограниченной диагностики, но автоматически не выводится.
Свяжите ошибку с журналом шлюза
На этом шаге вы используете идентификатор трассировки, чтобы найти запись запроса, вместо поиска только по времени.
Для появления журналов может потребоваться немного времени. Получите текущий список журналов через API управления, разберите каждый плоский объект метаданных и выведите только поля, необходимые для объяснения ошибки:
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
Ожидаются сохранённый идентификатор трассировки, case: "bad-input", поставщик Workers AI и признак ошибки. Количество токенов может отсутствовать, поскольку некорректные входные данные могут вызвать ошибку до начала генерации. Если запись ещё не отображается, подождите около 20 секунд и повторно выполните этот блок, предназначенный только для чтения.
Откройте представление Logs шлюза в Dashboard. Используйте фильтр метаданных или отображаемую отметку времени, чтобы найти строку с ошибкой, затем откройте панель подробностей. Убедитесь, что модель, статус ошибки и пользовательские метаданные описывают один и тот же синтетический запрос.


Различите ошибки авторизации шлюза и вышестоящего сервиса
На этом шаге вы будете изменять только одни учётные данные за раз. Оба теста могут вернуть 401 или 403, поэтому одного 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-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
Теперь оставьте действительными учётные данные шлюза, но замените только учётные данные Workers AI. Этот запрос войдёт в шлюз, и после него может появиться ошибочная запись поставщика:
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
Ожидайте 401 или 403 в обоих случаях. Немного подождите, затем прочитайте журналы — не отправляйте запросы повторно — и сравните две метки:
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
Для метки gateway-auth записи поставщика быть не должно; для метки upstream-auth должна отображаться ошибочная запись Workers AI. Поэтому схема границ и связанные журналы информативнее одного HTTP-кода.

Исправьте запрос и подтвердите успешное выполнение
На этом шаге вы восстановите действительные учётные данные и укажете обязательный prompt. Исправление завершено только тогда, когда результат выполнения и данные наблюдаемости подтверждают друг друга.
Используйте тот же идентификатор трассировки с новым именем сценария 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)'
Ожидаются HTTP 200 и непустой сгенерированный текст. Если журнал ещё не появился, подождите, затем повторно выполните инвентаризацию журналов только для чтения из предыдущего шага. В Dashboard отфильтруйте записи по идентификатору трассировки и сравните bad-input, bad-upstream-auth и repaired. В записи repaired должны отображаться успешное выполнение, статус 200 и сведения об использовании токенов.

Удалите временный шлюз
На этом шаге вы удалите облачный ресурс, пока учётные данные управления ещё позволяют подтвердить его отсутствие.
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"
Ожидается gateway absent: true. Такой аутентифицированный список отличает настоящее удаление от ситуации, когда страница не открылась из-за выхода из аккаунта или сетевой ошибки.
Удалите токен и выйдите из аккаунта
На этом шаге вы отзовёте оставшиеся облачные учётные данные и отключите виртуальную машину.
В Cloudflare Dashboard откройте My Profile → API Tokens. Найдите точное сохранённое значение tokenName, откройте Actions, выберите Delete, проверьте подтверждение и удалите только этот токен. Теперь токен можно безопасно отозвать, поскольку удаление шлюза уже подтверждено.
Удалите копию с виртуальной машины и завершите отдельную авторизацию Wrangler:
shred -u .labex/gateway-token
npx wrangler logout
npx wrangler whoami --json || true
test ! -e .labex/gateway-token && echo "local gateway token removed"
Ожидаются loggedIn: false и local gateway token removed. Сеанс Dashboard является отдельным и останется активным. По завершении лабораторной работы LabEx уничтожит эту временную виртуальную машину, а не сохранит её.
Итоги
Вы использовали безопасные пользовательские метаданные, чтобы связать некорректный запрос Workers AI с его журналом в AI Gateway. Вы узнали, что HTTP-код требует контекста границы: недействительная аутентификация шлюза отклоняется до появления записи поставщика, а недействительная авторизация у вышестоящего сервиса отображается как ошибочная запись Workers AI. Затем вы исправили входные данные, подтвердили сгенерированный текст и успешную связанную запись журнала, а также удалили все временные учётные данные и ресурсы.
В следующей лабораторной работе используется тот же подход, основанный на свидетельствах, для изучения кэширования. Вы повторите один ограниченный публичный запрос, отличите попадание в кэш от нового вызова модели и отключите кэш, когда требуется свежий результат.



