通过网关进行路由推理

CloudflareBeginner
立即练习

简介

在 Workers AI 课程中,应用会直接向 Cloudflare 托管的模型发送提示词。这样做没有问题,但随着应用不断发展,你还需要一个统一的位置来观察和控制模型流量。Cloudflare AI Gateway 就是这个检查点:调用方将请求发送到一个命名网关,网关再把请求转发给上游模型提供商,例如 Workers AI。

本实验会清晰展示三个角色:

  • 调用方是 LabEx 虚拟机中的 curl
  • 网关检查调用方是否可以进入,并记录请求;
  • 上游提供商是 Workers AI,它会检查请求是否有权运行模型。

后面两个检查使用不同的凭据。cf-aig-authorization 用于向 AI Gateway 验证调用方身份。普通的 Authorization 请求头用于向 Workers AI 验证网关请求。有效的网关令牌不会自动成为 Workers AI 凭据,Workers AI 凭据也不能绕过经过身份验证的网关。

你将通过 Cloudflare Dashboard 创建一个临时的、经过身份验证的网关,创建一个权限范围严格受限的 AI Gateway 令牌,向 Cloudflare 托管的 Llama 3.3 模型发送一个简短请求,并检查生成的日志。随后,你只替换网关凭据为无效值,以验证具体是哪个边界拒绝了请求。最后,你将删除网关,删除其令牌,使该令牌无法再授权请求,并退出 Wrangler。

如果你是直接进入本课程的,请先完成将 LabEx 连接到 Cloudflare 账户。该实验会介绍 LabEx 虚拟机终端、Wrangler 设备授权、账户确认以及显式账户 ID。Workers AI 推理实验也很适合作为前置实验。

AI Gateway 可在 Free 计划中使用,其核心日志功能在账户限额内免费。选定的 @cf/meta/llama-3.3-70b-instruct-fp8-fast 模型可以在 Standard 计费模式下使用共享的 Workers AI 免费额度。不需要 Workers Paid 或 Unified Billing。如果账户当天的 Workers AI 配额不可用,请停止操作,不要反复重试。

设置过程会在 /home/labex/project/ai-gateway-route 中安装 Node.js 22.22.0 和项目本地的 Wrangler 4.132.0。实验提供独立的只读检查,但不会为 Wrangler 授权、创建令牌或网关、发送推理请求,也不会修改你的 Cloudflare 账户。实验结束后,LabEx 不会保存这台临时虚拟机。你仍需要显式删除云端令牌并擦除虚拟机中的令牌副本,以便在销毁虚拟机前完成清理。

授权虚拟机并记录资源名称

在本步骤中,你将把刚创建的虚拟机连接到学习账户,并保存资源名称,确保本实验创建的资源清晰可辨。

Wrangler 的设备登录会授权 Workers AI,但不会创建稍后使用的独立 AI Gateway 调用方凭据。将这两类凭据分开,可以更清楚地观察信任边界。

进入准备好的项目目录,并确认固定的 CLI 版本:

cd /home/labex/project/ai-gateway-route
npx wrangler --version

预期输出为 4.132.0。使用账户身份和 Workers AI 访问权限启动设备授权:

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

打开显示的链接,输入代码,并授权目标学习账户。然后检查结构化身份信息:

npx wrangler whoami --json

确认输出中包含 loggedIn: true。创建唯一的网关 ID 及其关联的令牌名称。将 YOUR_ACCOUNT_ID 替换为目标账户显示的实际 32 位 ID:

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

随机后缀可以避免名称冲突。状态文件只包含资源标识符,不包含凭据,后续每条命令都可以据此准确操作本实验创建的资源。

创建经过身份验证的网关和调用方令牌

在本步骤中,你将创建一个检查点,以及一个既能调用该检查点又能查看其信息的凭据。

打开 Cloudflare Dashboard,选择 AI → AI Gateway → Create gateway → Custom gateway。使用 .labex/state.json 中的 gatewayId 作为网关名称。保留以下设置:

  • 请求日志:开启;
  • 网关身份验证:开启;
  • 缓存、速率限制、支出限制和重试:关闭;
  • Workers AI 计费:Standard。

Standard 计费会让 Workers AI 使用正常配额。Unified Billing 是另一种支付路径,不在本初学者实验范围内。

Cloudflare 打开新资源后,使用面包屑导航和当前选中的 Overview 标签,确认你进入的是这个临时网关,而不是账户级的网关列表。

新建网关的 Overview 页面,其中显示唯一 ID 和首次请求指标为空

创建完成后,打开 Settings。确认页面显示的网关 ID 与你保存的 ID 完全一致,并确认日志和身份验证均已启用。

现在选择 Create an AI Gateway authentication token。使用保存的 tokenName 作为令牌名称,只选择目标学习账户,并添加以下权限:

  • AI Gateway — Run 允许调用方进入经过身份验证的网关;
  • AI Gateway — Edit 允许实验通过管理 API 读取和删除 AI Gateway 资源。

不要为此令牌添加 Workers AI 权限。Workers AI 仍由 Wrangler 的另一个短期凭据负责授权。

仅授予 AI Gateway Run 和 Edit 权限的令牌权限表单

确认账户和权限后再创建令牌。Cloudflare 只会显示一次令牌值。请将它私下保存,不要回显到终端:

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
'

准备好的终端会以 zsh 交互运行,因此这段代码启动了一个短暂的 Bash 子进程,以使用 Bash 的隐藏式 read 提示符。如果粘贴内容为空,命令会拒绝该输入,不会返回 shell。令牌只会保留在子进程和私有文件中。

令牌不会写入配置,也不会出现在命令输出中。通过经过身份验证的管理 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

预期看到你创建的 ID,并且 collect_logsauthentication 都设置为 true。输出中不会显示任何秘密信息。

网关 Settings 页面,显示身份验证、日志和 Standard 计费

通过网关路由一个 Workers AI 请求

在本步骤中,你将通过网关发送一个小型请求,而不是直接向 Workers AI 发送请求。

提供商原生网关 URL 包含账户、网关、提供商和模型信息。两个授权请求头有意保持分离:

caller → cf-aig-authorization → AI Gateway → Authorization → Workers AI model

以结构化数据形式获取 Wrangler 当前的短期 Workers AI 令牌,然后发出请求。该命令只会将 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')
MODEL='@cf/meta/llama-3.3-70b-instruct-fp8-fast'
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))')
curl --http1.1 -fsS \
  -H "cf-aig-authorization: Bearer $GATEWAY_TOKEN" \
  -H "Authorization: Bearer $UPSTREAM_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"prompt":"In one sentence, explain why an AI gateway is useful.","max_tokens":64}' \
  "https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL" \
  > .labex/valid-response.json
unset GATEWAY_TOKEN UPSTREAM_TOKEN
node -e 'const b=require("./.labex/valid-response.json"); console.log(b.result?.response ?? b.result)'

由于生成结果具有不确定性,输出措辞可能不同。检查程序只会确认提供商是否通过你创建的网关返回了成功且非空的结果。

隔离网关身份验证边界

在本步骤中,你将保留有效的 Workers AI 凭据,只替换网关凭据。

受控的负面测试一次只应改变一个条件。如果两个凭据都无效,HTTP 失败就无法说明是哪个系统拒绝了请求。下面的请求保留 Wrangler 提供的有效上游令牌,并发送一个明确无效的 cf-aig-authorization 值:

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')
MODEL='@cf/meta/llama-3.3-70b-instruct-fp8-fast'
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/invalid-response.json -w '%{http_code}' \
  -H 'cf-aig-authorization: Bearer deliberately-invalid' \
  -H "Authorization: Bearer $UPSTREAM_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"prompt":"This request must not reach the model.","max_tokens":8}' \
  "https://gateway.ai.cloudflare.com/v1/$ACCOUNT_ID/$GATEWAY_ID/workers-ai/$MODEL")
unset UPSTREAM_TOKEN
printf '%s\n' "$STATUS" | tee .labex/invalid-status.txt

预期状态码为 401403。不要显示响应正文:状态码已经足以提供证据,而限制错误输出也可以降低请求详情意外暴露的可能性。

将请求与网关日志关联起来

在本步骤中,你将使用可观测性,把运行时行为与可见的网关记录关联起来。

可观测性是指在请求离开调用方后,收集足够的证据来解释系统执行了什么。网关日志可以显示提供商、模型、状态、延迟和令牌使用量,而不需要再次调用模型。日志可能需要一小段时间才会出现。

通过经过身份验证的管理 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/logs?per_page=50" \
  > .labex/logs.json
unset GATEWAY_TOKEN
node - <<'NODE'
const body = require('./.labex/logs.json')
const model = '@cf/meta/llama-3.3-70b-instruct-fp8-fast'
const matches = (body.result || []).filter(row =>
  row.provider === 'workers-ai' && row.model === model
)
console.log(matches.map(row => ({
  id: row.id,
  provider: row.provider,
  model: row.model,
  success: row.success,
  created_at: row.created_at
})))
if (!matches.some(row => row.success === true)) process.exit(2)
NODE

预期看到一条记录,其中 provider: "workers-ai"、模型为目标模型,并且 success: true。如果命令结束时还没有这条记录,请等待约 20 秒,然后重新运行同一个只读代码块,不要发送更多推理请求。

在 Dashboard 中打开网关的 Logs 页面。找到 @cf/meta/llama-3.3-70b-instruct-fp8-fast 对应的成功 Workers AI 记录。打开详细信息面板前,确认成功状态、提供商和模型均正确。

网关 Logs 表格,其中突出显示成功的 Workers AI 请求

具体的耗时、令牌数量和生成文本可能有所不同。这些值描述的是本次请求,不是需要精确复现的目标。不要为了让日志更容易查找,而在提示词中放入凭据或个人信息。

日志详细信息面板,显示模型、状态、延迟和令牌使用量

删除临时网关

在本步骤中,你将在管理凭据仍然可用时删除云端资源。

清理操作必须针对准确的资源 ID,并且必须通过经过身份验证的资源清单来证明删除成功。由于退出登录或网络故障导致页面无法打开,不能证明资源已经删除。

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。第二个请求会使用有效授权列出网关;如果仍然存在你创建的 ID,命令就会失败。账户中的其他网关不会受到任何修改。

删除令牌并退出登录

在本步骤中,你将按照与使用凭据相反的顺序,删除两个相互独立的凭据。

在 Cloudflare Dashboard 中打开 My Profile → API Tokens。找到 .labex/state.json 中保存的准确令牌名称,打开其 Actions 菜单,选择 Delete,检查确认信息,然后只删除这个令牌。删除令牌会立即撤销其访问权限。现在删除它是安全的,因为网关已经不存在。

删除令牌的本地副本,然后结束 Wrangler 在虚拟机中的独立授权:

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

预期看到包含 loggedIn: false 的结构化输出。Dashboard 浏览器会话是独立的,仍会保持登录状态。运行最后的本地检查:

test ! -e .labex/gateway-token && echo "local gateway token removed"

这条消息确认虚拟机中的令牌副本已经不存在。LabEx 的 Check 按钮会独立重复检查本地文件和 Wrangler 退出登录状态;其后端脚本不会放在学习者项目中。

现在,你已经删除网关,删除其调用方/管理令牌,擦除本地令牌副本,并断开刚创建的虚拟机。结束实验后,LabEx 会销毁这台临时虚拟机,而不是保存它;但云端清理仍然很重要,因为仅销毁虚拟机无法撤销 Cloudflare 令牌或删除网关。

总结

你创建了一个经过身份验证的 Cloudflare AI Gateway,并通过它路由了一次真实的 Workers AI 推理请求。你将网关授权与上游模型授权分开,只修改一个凭据来确定拒绝请求的边界,并将成功请求与网关日志关联起来。最后,你在删除令牌并退出虚拟机登录前,通过经过身份验证的资源检查证明了资源已被删除。

下一个实验将在这条可观测的请求路径上继续进行。你将添加少量非机密元数据,跟踪一次有意触发的失败,并使用网关中的证据,而不是猜测请求失败的位置。