Восстановление с резервной моделью

CloudflareBeginner
Практиковаться сейчас

Введение

Модель ИИ может временно быть недоступна, перегружена или не понимать переданные ей данные. Резервная модель даёт приложению один заранее предусмотренный альтернативный вариант вместо немедленного возврата ошибки. Полезный механизм резервирования должен быть ограниченным: он содержит короткий упорядоченный список моделей и чёткую точку остановки. Он не должен бесконечно повторять запросы или вызывать все модели после первого успешного результата.

Вы развернёте небольшой Cloudflare Worker с двумя путями. Путь восстановления намеренно отправляет данные в формате чата модели эмбеддингов, перехватывает предсказуемую несовместимость, а затем вызывает одну чат-модель. Исправный путь вызывает совместимую основную чат-модель и останавливается. Каждый запрос проходит через один и тот же AI Gateway, поэтому его журналы показывают, какая модель завершилась ошибкой и какая модель обработала запрос.

Если вы открыли этот курс напрямую, сначала выполните Подключение LabEx к вашей учётной записи Cloudflare. В этом материале объясняются терминал LabEx, авторизация устройства Wrangler, выбор учётной записи и идентификатор учётной записи. Также сначала выполните лабораторную работу Маршрутизация вывода через шлюз, поскольку эта лабораторная работа использует её понятия AI Gateway и Workers AI.

В лабораторной работе используются размещённые Cloudflare модели Workers AI и привязка Workers AI. Workers Paid, ключ внешнего провайдера и устаревшая Universal Endpoint не требуются. Контролируемый неудачный запрос отклоняется до запуска вывода, а каждый успешный путь создаёт только короткий ответ. Если общая дневная квота Workers AI недоступна, остановитесь и не повторяйте запросы многократно.

В процессе настройки устанавливаются Node.js 22.22.0 и локальная версия Wrangler 4.132.0 в /home/labex/project/ai-gateway-fallback. Подготавливаются независимые проверки, но авторизация Wrangler, создание облачных ресурсов, развёртывание Worker и отправка запросов к моделям не выполняются. После лабораторной работы LabEx уничтожит виртуальную машину. Однако удаление виртуальной машины не удаляет облачные ресурсы, поэтому удалите удалённые Worker, шлюз и API-токен самостоятельно.

Авторизуйте виртуальную машину и задайте имена для пути восстановления

Каждая лабораторная работа начинается с чистой виртуальной машины. На этом шаге вы авторизуете Wrangler для работы с вашей учебной учётной записью и сохраните уникальные имена одного шлюза, одного Worker и одного временного токена.

cd /home/labex/project/ai-gateway-fallback
npx wrangler --version
npx wrangler login --device --browser=false

Откройте показанную ссылку, введите код и авторизуйте нужную учебную учётную запись. Wrangler запрашивает разрешения на развёртывание Worker и использование Workers AI, которые понадобятся далее в этой лабораторной работе. Перед подтверждением проверьте показанную учётную запись. Затем выведите структурированные данные удостоверения:

npx wrangler whoami --json

Ожидаются Wrangler 4.132.0 и loggedIn: true. Замените YOUR_ACCOUNT_ID фактическим 32-символьным идентификатором нужной учётной записи:

GATEWAY_ID="labex-c09-g05-$(openssl rand -hex 6)"
WORKER_NAME="${GATEWAY_ID/g05/g05-worker}"
TOKEN_NAME="$GATEWAY_ID-token"
cat > .labex/state.json <<JSON
{
  "accountId": "YOUR_ACCOUNT_ID",
  "gatewayId": "$GATEWAY_ID",
  "workerName": "$WORKER_NAME",
  "tokenName": "$TOKEN_NAME"
}
JSON
cat .labex/state.json

Эти идентификаторы не являются секретами. Их сохранение позволяет выполнять последующие проверки и очистку только для ресурсов этой лабораторной работы.

Создайте наблюдаемый AI Gateway

На этом шаге вы создадите общий контрольный узел, который записывает оба маршрута к моделям.

AI Gateway — это именованный контрольный узел между приложением и вызовами моделей. Он собирает журналы и метаданные в одном месте для нескольких попыток, даже если приложение меняет модели.

Откройте Cloudflare Dashboard и выберите AI → AI Gateway → Create a custom gateway. Используйте сохранённый gatewayId. Оставьте включёнными Collect Logs и Authenticated Gateway. Не включайте кэширование, ограничения скорости, повторы и ограничения расходов, а для биллинга Workers AI оставьте значение Standard. Затем создайте шлюз.

В сохранённом шлюзе включены ведение журналов и аутентифицированный доступ

Откройте My Profile → API Tokens, выберите Create Token → Create Custom Token и используйте сохранённое имя tokenName. Добавьте разрешения учётной записи AI Gateway — Edit и AI Gateway — Run, ограничив их нужной учебной учётной записью. Этот временный токен позволяет лабораторной работе читать данные только своего шлюза и позднее удалить его. Привязка AI в развёрнутом Worker этот токен не содержит.

После создания токена скопируйте только значение после Bearer из одноразовой команды проверки Cloudflare и сохраните его со скрытым вводом:

bash -c '
while :; do
  read -ersp "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
'

Выведите только важные параметры, не являющиеся секретами:

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" \
  > .labex/gateway.json
unset GATEWAY_TOKEN
node -e 'const g=require("./.labex/gateway.json").result; console.log({id:g.id,collect_logs:g.collect_logs,authentication:g.authentication})'

В результате должен отображаться сохранённый идентификатор шлюза, а оба значения должны быть равны true.

Определите Worker с двумя попытками

На этом шаге вы запишете политику восстановления в обычном коде Worker. Политика содержит два явных вызова вместо цикла, поэтому максимальные затраты и задержку легко определить.

Первый вызов на пути восстановления использует модель эмбеддингов. Модели эмбеддингов преобразуют текст в числовые векторы и не принимают messages чата. Передача входных данных в формате чата создаёт безопасную детерминированную ошибку совместимости до запуска вывода. Блок catch записывает эту ошибку и выполняет один вызов совместимой чат-модели.

GATEWAY_ID=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).gatewayId')
WORKER_NAME=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).workerName')
cat > wrangler.jsonc <<JSON
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-17",
  "ai": { "binding": "AI" }
}
JSON
cat > src/index.js <<JS
export default {
  async fetch(request, env) {
    const healthy = new URL(request.url).pathname === "/healthy";
    const attempts = [];
    const gateway = {
      gateway: {
        id: "$GATEWAY_ID",
        metadata: {
          lab: "g05-fallback",
          mode: healthy ? "healthy" : "fallback",
          synthetic: true
        }
      }
    };

    if (!healthy) {
      try {
        await env.AI.run(
          "@cf/baai/bge-small-en-v1.5",
          { messages: [{ role: "user", content: "Reply with ROUTE OK" }] },
          gateway
        );
        attempts.push({ model: "@cf/baai/bge-small-en-v1.5", status: "unexpected-success" });
      } catch (error) {
        attempts.push({
          model: "@cf/baai/bge-small-en-v1.5",
          status: "failed",
          reason: String(error).slice(0, 180)
        });
      }
    }

    const selectedModel = healthy
      ? "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
      : "@cf/meta/llama-3.2-3b-instruct";
    const result = await env.AI.run(
      selectedModel,
      { prompt: "Reply with exactly: ROUTE OK", max_tokens: 12 },
      gateway
    );
    attempts.push({ model: selectedModel, status: "succeeded" });

    return Response.json({
      mode: healthy ? "healthy-primary" : "fallback-recovery",
      usedFallback: !healthy,
      selectedModel,
      attempts,
      response: result.response
    });
  }
};
JS
npx wrangler deploy --dry-run

Привязка AI предоставляет Worker прямой доступ к Workers AI. Параметр gateway направляет каждый вызов через сохранённый шлюз и добавляет только синтетические метаданные — без запроса, учётных данных или идентификатора человека.

Разверните Worker и проверьте путь восстановления

На этом шаге вы развернёте Worker и один раз запустите контролируемый сценарий восстановления.

Разверните Worker и сохраните вывод Wrangler, чтобы тест использовал точный URL, назначенный вашей учётной записи:

npx wrangler deploy 2>&1 | tee .labex/deploy-output.txt
WORKER_URL=$(grep -Eo 'https://[^ ]+\.workers\.dev' .labex/deploy-output.txt | tail -1)
printf '%s\n' "$WORKER_URL" | tee .labex/worker-url.txt

Маршрут workers.dev может стать доступным через несколько секунд после успешного развёртывания. Проверяйте URL, не отправляя дополнительные запросы к моделям: ответ 404 означает только, что маршрут на границе сети ещё не готов, а цикл завершится при первом ответе 200.

WORKER_URL=$(cat .labex/worker-url.txt)
for attempt in $(seq 1 12); do
  STATUS=$(curl --http1.1 -sS -o .labex/fallback-response.json -w '%{http_code}' "$WORKER_URL/fallback")
  printf 'attempt %s: HTTP %s\n' "$attempt" "$STATUS"
  [ "$STATUS" = 200 ] && break
  [ "$attempt" -eq 12 ] && exit 1
  sleep 5
done
python3 -m json.tool < .labex/fallback-response.json

Ожидаются usedFallback: true, две попытки, статус failed у модели эмбеддингов и статус succeeded у @cf/meta/llama-3.2-3b-instruct. Текст сгенерированного ответа не проверяется дословно; важен выбор маршрута.

Докажите, что исправная основная модель останавливает маршрут

На этом шаге вы покажете, что успешная основная модель предотвращает ненужный вызов резервной модели.

Резервирование работает правильно только тогда, когда не вмешивается при успешной работе основного пути. Путь /healthy начинается с совместимой чат-модели, поэтому он должен выполнить одну попытку и остановиться.

WORKER_URL=$(cat .labex/worker-url.txt)
curl --http1.1 -fsS "$WORKER_URL/healthy" \
  | tee .labex/healthy-response.json \
  | python3 -m json.tool

Ожидаются usedFallback: false, значение @cf/meta/llama-3.3-70b-instruct-fp8-fast в selectedModel и ровно одна успешная попытка. Это поведение короткого замыкания: успешный результат немедленно завершает маршрут.

Прочитайте маршрут в журналах шлюза

На этом шаге вы сопоставите результаты Worker в формате JSON с независимыми данными AI Gateway.

Ответ Worker описывает поведение приложения. Журналы AI Gateway предоставляют независимое свидетельство на стороне провайдера. Журналы могут появиться через несколько секунд, поэтому немного подождите и выведите только поля, относящиеся к маршрутизации:

sleep 8
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/logs?per_page=50" \
  > .labex/logs.json
unset GATEWAY_TOKEN
node - <<'NODE'
const rows=require('./.labex/logs.json').result||[];
const meta=row=>{try{return typeof row.metadata==='string'?JSON.parse(row.metadata):(row.metadata||{})}catch{return {}}};
console.table(rows.filter(row=>meta(row).lab==='g05-fallback').map(row=>({
  mode:meta(row).mode, model:row.model, success:row.success, status:row.status_code
})));
NODE

Откройте страницу Logs шлюза в Dashboard. Группа fallback должна содержать строку с ошибкой модели эмбеддингов и строку с успешным вызовом резервной модели. Группа healthy должна содержать только успешную строку основной чат-модели.

Журналы шлюза показывают неудачную попытку основной модели, за которой следует успешная резервная модель

Исправный запрос содержит один журнал успешного вызова основной модели

Метаданные mode связывают строки между собой, не включая запрос или секрет. Модель, результат и статус объясняют маршрут; одного сгенерированного текста для этого недостаточно.

Проверьте ограниченный контракт восстановления

На этом шаге вы сравните оба пути и определите максимальное количество попыток вызова моделей.

Теперь у вас есть три согласующихся источника данных:

  • в исходном коде есть два явных вызова env.AI.run() и нет цикла повторных попыток;
  • /fallback сообщает об одной ошибке, за которой следует один успешный вызов;
  • /healthy сообщает об одном успешном вызове и останавливается.

Выведите компактное сравнение из сохранённых ответов:

node - <<'NODE'
for (const name of ['fallback','healthy']) {
  const body=require(`./.labex/${name}-response.json`);
  console.log(name, {
    usedFallback: body.usedFallback,
    selectedModel: body.selectedModel,
    attemptCount: body.attempts.length,
    statuses: body.attempts.map(item=>item.status)
  });
}
NODE

Максимальное количество попыток — две. Если резервная модель также завершится ошибкой, Worker вернёт ошибку, а не начнёт маршрут заново. В рабочем приложении можно добавить тайм-аут, автоматический размыкатель или понятное для пользователя сообщение об ошибке, но каждый дополнительный механизм восстановления должен оставаться независимо ограниченным и наблюдаемым.

Удалите временные ресурсы

На этом шаге вы удалите все удалённые ресурсы, принадлежащие этой лабораторной работе, и отмените локальную авторизацию.

Сначала удалите Worker, чтобы он не мог создавать новые запросы к шлюзу. Затем удалите только шлюз, записанный в state.json:

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')
WORKER_NAME=$(node -p 'JSON.parse(require("fs").readFileSync(".labex/state.json")).workerName')
GATEWAY_TOKEN=$(cat .labex/gateway-token)
npx wrangler delete --name "$WORKER_NAME" --force
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" \
  > .labex/delete-gateway.json
unset GATEWAY_TOKEN
node -p 'require("./.labex/delete-gateway.json").success'

Ожидается true. Пока обе временные авторизации ещё существуют на этой виртуальной машине, сохраните независимые подтверждения отсутствия Worker и шлюза:

set +e
npx wrangler deployments list --name "$WORKER_NAME" --json \
  > .labex/worker-after-delete.json 2> .labex/worker-absent.err
printf '%s\n' "$?" > .labex/worker-absent-status.txt
set -e
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
GATEWAY_ID="$GATEWAY_ID" node - <<'NODE'
const rows=require('./.labex/gateways-after-delete.json').result||[];
console.log('gateway absent:', !rows.some(row=>row.id===process.env.GATEWAY_ID));
NODE
grep -Ei '10090|10007|script_not_found|does not exist' .labex/worker-absent.err

Ожидаются gateway absent: true и ответ об отсутствии Worker, например script_not_found, код 10090, код 10007 или does not exist. Wrangler может использовать разные форматы ошибок для одного и того же отсутствующего скрипта; ошибки сети и аутентификации не подтверждают удаление.

Теперь откройте My Profile → API Tokens и удалите токен с точно сохранённым именем tokenName. В завершение удалите его копию с виртуальной машины и выйдите из Wrangler:

shred -u .labex/gateway-token
npx wrangler logout
npx wrangler whoami --json

Ожидается loggedIn: false. Последующее удаление виртуальной машины удалит локальные файлы, но только эти команды удаляют удалённые ресурсы и отменяют авторизацию.

Итоги

Вы создали ограниченный путь восстановления с двумя моделями, размещёнными в Cloudflare. Контролируемая несовместимость основной модели завершилась ошибкой, одна резервная модель восстановила запрос, а исправная основная модель остановилась после одного вызова. Журналы AI Gateway связали решения приложения с данными о модели и статусе на стороне провайдера. Вы также узнали, почему явные ограничения числа попыток, безопасные метаданные и проверенная очистка необходимы для надёжной конструкции резервирования.