소개
AI 모델은 일시적으로 사용할 수 없거나 과부하 상태일 수 있으며, 이해할 수 없는 입력을 받을 수도 있습니다. **폴백 (fallback)**은 애플리케이션이 즉시 오류를 반환하는 대신 미리 정한 대안을 사용하도록 합니다. 유용한 폴백은 제한적이어야 합니다. 짧은 순서 목록과 명확한 종료 지점이 있어야 하며, 무한히 재시도하거나 첫 번째 성공 이후 모든 모델을 호출해서는 안 됩니다.
이 실습에서는 두 가지 경로를 가진 작은 Cloudflare Worker 를 배포합니다. 폴백 경로는 의도적으로 채팅 형식의 입력을 임베딩 모델에 보내 예측 가능한 호환성 오류를 발생시킨 다음, 오류를 처리하고 채팅 모델 하나를 호출합니다. 정상 경로는 호환되는 기본 채팅 모델을 호출한 뒤 중단합니다. 모든 시도는 동일한 AI Gateway 를 통과하므로 로그에서 어떤 모델이 실패했고 어떤 모델이 요청을 완료했는지 확인할 수 있습니다.
이 과정을 직접 시작했다면 먼저 LabEx 를 Cloudflare 계정에 연결을 완료합니다. 이 실습에서는 LabEx 터미널, Wrangler 디바이스 인증, 계정 선택 및 계정 ID 를 학습합니다. 또한 이 실습은 게이트웨이와 Workers AI 개념을 기반으로 하므로 먼저 게이트웨이를 통한 경로 추론을 완료합니다.
이 실습에서는 Cloudflare 가 호스팅하는 Workers AI 모델과 Workers AI 바인딩을 사용합니다. Workers Paid, 외부 프로바이더 키 또는 더 이상 사용되지 않는 Universal Endpoint 는 필요하지 않습니다. 제어된 실패 시도는 추론 전에 거부되며, 각 성공 경로는 짧은 응답만 생성합니다. 공유된 일일 Workers AI 할당량을 사용할 수 없다면 계속 반복해서 재시도하지 말고 중단합니다.
설정 과정에서 /home/labex/project/ai-gateway-fallback에 Node.js 22.22.0 과 프로젝트 로컬 Wrangler 4.132.0 을 설치합니다. 독립적인 확인 작업을 준비하지만 Wrangler 인증, 클라우드 리소스 생성, Worker 배포 또는 모델 트래픽 전송은 수행하지 않습니다. 실습이 끝나면 LabEx 가 VM 을 삭제하지만, VM 삭제만으로는 클라우드 리소스가 삭제되지 않으므로 원격 Worker, 게이트웨이 및 API 토큰은 직접 삭제해야 합니다.
VM 을 인증하고 복구 경로 이름 지정하기
각 실습은 새 VM 에서 시작합니다. 이 단계에서는 Wrangler 가 학습용 계정을 사용하도록 인증하고, 게이트웨이 하나, Worker 하나, 임시 토큰 하나에 사용할 고유한 이름을 저장합니다.
cd /home/labex/project/ai-gateway-fallback
npx wrangler --version
npx wrangler login --device --browser=false
표시된 링크를 열고 코드를 입력한 다음 사용할 학습용 계정을 인증합니다. Wrangler 는 이 실습의 뒷부분에서 필요한 Worker 배포 및 Workers AI 권한을 요청합니다. 승인하기 전에 표시된 계정이 올바른지 확인합니다. 그런 다음 구조화된 ID 데이터를 확인합니다.
npx wrangler whoami --json
Wrangler 4.132.0 과 loggedIn: true가 표시되어야 합니다. 사용할 계정에 표시된 실제 32 자 ID 로 YOUR_ACCOUNT_ID를 바꿉니다.
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는 활성화된 상태로 둡니다. 캐싱, rate limit, 재시도 및 지출 제한은 끄고, Workers AI 결제는 Standard로 유지합니다. 그런 다음 게이트웨이를 생성합니다.

My Profile → API Tokens를 열고 Create Token → Create Custom Token을 선택한 다음 저장해 둔 tokenName을 사용합니다. 사용할 학습용 계정으로 범위를 제한하고, 계정 권한 AI Gateway — Edit와 AI Gateway — Run을 추가합니다. 이 임시 토큰을 사용하면 실습에서 해당 게이트웨이만 읽고 나중에 삭제할 수 있습니다. 배포된 Worker 의 AI 바인딩에는 이 토큰이 포함되지 않습니다.
토큰을 만든 후 Cloudflare 가 한 번만 표시하는 확인 명령에서 Bearer 뒤의 값만 복사하고, 숨겨진 입력으로 저장합니다.
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})'
저장한 게이트웨이 ID 와 함께 두 값이 모두 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 를 배포하고 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, @cf/meta/llama-3.2-3b-instruct는 succeeded로 표시되어야 합니다. 생성된 문구의 정확한 내용은 평가하지 않으며, 경로 결정이 올바른지가 중요합니다.
정상적인 기본 모델이 조기에 중단하는지 검증하기
이 단계에서는 기본 모델이 성공하면 불필요한 폴백 호출이 발생하지 않는다는 것을 확인합니다.
폴백은 기본 경로가 작동할 때 개입하지 않아야 올바르게 동작합니다. /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, selectedModel 값으로 @cf/meta/llama-3.3-70b-instruct-fp8-fast, 그리고 성공한 시도 정확히 한 개가 표시되어야 합니다. 이것이 short-circuit 동작입니다. 성공하면 경로가 즉시 종료됩니다.
Gateway 로그에서 경로 읽기
이 단계에서는 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
Dashboard 에서 게이트웨이의 Logs 페이지를 엽니다. 폴백 그룹에는 실패한 임베딩 모델 행과 성공한 폴백 모델 행이 있어야 합니다. 정상 그룹에는 성공한 기본 채팅 모델 행만 있어야 합니다.


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가 표시되어야 합니다. 이 VM 에 두 가지 임시 인증 정보가 아직 모두 있는 동안 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와 함께 script_not_found, 코드 10090, 코드 10007 또는 does not exist와 같은 Worker 부재 응답이 표시되어야 합니다. Wrangler 는 동일하게 삭제된 스크립트에 대해 서로 다른 오류 형식을 사용할 수 있습니다. 네트워크 오류와 인증 오류는 삭제를 증명하는 근거가 아닙니다.
이제 My Profile → API Tokens를 열고 저장해 둔 정확한 tokenName을 삭제합니다. 마지막으로 VM 에 남은 복사본을 제거하고 로그아웃합니다.
shred -u .labex/gateway-token
npx wrangler logout
npx wrangler whoami --json
loggedIn: false가 표시되어야 합니다. 나중에 VM 을 삭제하면 로컬 파일도 제거되지만, 원격 리소스를 삭제하고 인증을 취소하는 것은 이 명령들뿐입니다.
요약
Cloudflare 가 호스팅하는 두 모델을 사용하는 제한된 복구 경로를 구축했습니다. 제어된 호환성 오류로 기본 모델 시도가 실패했고, 폴백 모델 하나가 요청을 복구했으며, 정상적인 기본 모델은 호출 한 번 후 중단했습니다. AI Gateway 로그를 통해 애플리케이션의 결정과 프로바이더 측 모델 및 상태 증거를 연결했습니다. 또한 명시적인 시도 제한, 안전한 메타데이터 및 검증된 정리가 신뢰할 수 있는 폴백 설계에 필요한 이유를 확인했습니다.



