跳到內容

AI Gateway

查證日期
驗證環境wrangler@4.118.0·compatibility_date: 2026-07-24
前置章節34. Workers AI

⚠️ AI Gateway 走 AI binding,因此永遠是遠端的(第 34 章),本機開發也會計費。

第 34 章結束在一堆散落的問題上:推論很貴、模型會下架、第三方模型要另外計費、而且你沒有任何統一的地方能看到「這個月誰在燒錢」。

AI Gateway 就是那個統一的地方 —— 快取、限流、重試、可觀測性、成本歸因,全部在一層 proxy 上。

而且它不是一個新的 binding。


實測 —— env 上沒有 GATEWAY 之類的東西。入口是第 34 章那個 ai binding:

const gw = env.AI.gateway("default");

型別(實測 worker-configuration.d.ts):

declare abstract class AiGateway {
patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;
getLog(logId: string): Promise<AiGatewayLog>;
run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?): Promise<Response>;
getUrl(provider?: AIGatewayProviders | string): Promise<string>;
}

⚠️ run() 的狀態需要說清楚。 本系列大綱初稿寫「gateway.run() 已從文件消失」。查證的結果比較細緻:它確實從文件消失了,但仍然存在於 shipped 的型別裡。

也就是說它可以編譯、可能還能跑,但已經不是官方指引的路徑。範例的 /gateway-run 路由留著就是為了讓你自己確認它現在的實際行為 —— 但不要在新程式碼裡用它。

從 2026-03 起,第一次帶認證的請求會自動建立一個名為 default 的 gateway,你不需要先去 dashboard 手動建。所以 { gateway: { id: "default" } } 是可以直接寫的。


這是最實用、也最少人知道的一點。你不需要手動設 cf-aig-* header —— 透過 env.AI.run()gateway 選項就能設定全部:

type GatewayOptions = {
id: string;
cacheKey?: string;
cacheTtl?: number;
skipCache?: boolean;
metadata?: Record<string, number | string | boolean | null | bigint>;
collectLog?: boolean;
eventId?: string;
requestTimeoutMs?: number;
retries?: GatewayRetries;
};
await env.AI.run(model, inputs, {
gateway: {
id: "default",
cacheKey: promptHash, // 你自己決定什麼算「一樣」
cacheTtl: 3600,
metadata: { tenant: tenantId, feature: "summarise" },
eventId: crypto.randomUUID(),
requestTimeoutMs: 20_000,
retries: { maxAttempts: 3, retryDelayMs: 500, backoff: "exponential" },
},
});

metadata 是這裡最有價值的欄位。 它會進到 log 與 analytics,讓「哪個租戶、哪個功能花了多少錢」變成一個可查詢的維度 —— 對比第 22 章那個要自己設計 schema 的 Analytics Engine,這是免費的歸因。

如果你不是用 env.AI.run()(例如用 OpenAI SDK 指向 gateway URL),才需要 header。實測型別 AIGatewayHeaders 給了完整清單:

Header用途
cf-aig-cache-key自訂快取鍵
cf-aig-cache-ttl快取秒數
cf-aig-skip-cache跳過快取
cf-aig-metadata歸因資料
cf-aig-event-id自訂 event id
cf-aig-collect-log是否記錄
cf-aig-custom-cost自訂成本{per_token_in, per_token_out}{total_cost}
cf-aig-request-timeout逾時
cf-aig-max-attempts重試次數
cf-aig-retry-delay重試延遲
cf-aig-backoff退避策略

cf-aig-custom-cost 是型別裡才看得清楚的一個 —— 它讓你覆寫 gateway 對成本的估算。用途很具體:你自己談了折扣價、或者你在 gateway 後面接了自架模型,預設的定價表算出來的數字是錯的。

已棄用:cf-cache-ttlcf-skip-cache(沒有 -aig- 的舊版)。舊教學裡看到的話直接換掉。


這是最常見的 401 來源。兩個入口的認證方式不一樣:

入口Cloudflare token 放哪Authorization 給誰
gateway.ai.cloudflare.com/...cf-aig-authorizationprovider 的 key
api.cloudflare.com/client/v4/accounts/{id}/ai/...Authorization: BearerCloudflare

gateway.ai.cloudflare.com 上,Authorization 這個 header 是保留給下游 provider 的 —— 因為 gateway 要把它原封不動轉發給 OpenAI 或 Anthropic。你自己的 Cloudflare token 必須走 cf-aig-authorization

寫錯的話你會得到一個很難懂的 401:可能是 Cloudflare 拒絕你,也可能是 provider 拒絕你的 Cloudflare token。

env.AI.run() 的話這個問題完全不存在 —— binding 幫你處理認證。這是另一個「能用 binding 就用 binding」的理由。

現在建議的路徑是:

POST https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions
(以及 /responses、/messages)

而且有一條兩段式的棄用鏈:

  1. Universal Endpoint 的文件頁標題直接寫著 「(Deprecated)」,指向 OpenAI-compatible endpoint。
  2. 而那個 OpenAI-compatible endpoint 的頁面自己又寫著已棄用,請改用 REST API。

Provider-specific endpoints 並沒有被棄用。

這種「A 棄用指向 B,B 又棄用指向 C」的鏈條,是照著搜尋結果做最容易掉進去的坑。如果你在 Worker 裡,這整段都繞過去了 —— 用 env.AI.run()gateway 選項就好。


推論的成本結構和第 6 章的 HTTP 快取完全不同:一次快取命中省下的不是幾毫秒的延遲,是一次完整的模型呼叫費用。

{ gateway: { id: "default", cacheKey: hash, cacheTtl: 3600 } }

cacheKey 由你決定,這是關鍵設計點。 預設的快取鍵是整個請求,代表 prompt 差一個字就 miss。但很多場景下你想要更寬鬆的等價關係:

// 摘要同一個 URL 的頁面:內容 hash 當鍵,prompt 的細微差異不影響
cacheKey: `summarise:v2:${await sha256(pageContent)}`
// 分類:正規化後的輸入當鍵
cacheKey: `classify:v1:${normalise(text)}`

把版本號寫進鍵裡v2)—— 改了 prompt 或換了模型時,遞增版本號就等於全域失效,不用等 TTL。這和第 12 章 R2 的快取策略是同一個模式。

範例的 /cache-proof 路由用同一個 cacheKey 打兩次,比較延遲與 log 中的 cached 欄位。


AiGatewayLog 的完整欄位(實測型別):

type AiGatewayLog = {
id: string; provider: string; model: string; model_type?: string;
path: string; duration: number;
status_code: number; success: boolean; cached: boolean;
tokens_in?: number; tokens_out?: number;
cost?: number; custom_cost?: boolean;
metadata?: Record<...>;
request_size: number; response_size: number;
request_head?: string; request_head_complete: boolean;
response_head?: string; response_head_complete: boolean;
step?: number; created_at: Date;
};

三個特別有用的:

  • cached: boolean —— 直接告訴你這一筆有沒有命中快取。快取命中率是一個 SQL 查詢的事。
  • costcustom_cost —— gateway 幫你算好了每一次呼叫的成本,custom_cost 標示是否被 cf-aig-custom-cost 覆寫過。
  • step —— 多步驟(例如 fallback 到第二個 provider)時的步驟編號。
patchLog(logId: string, data: {
score?: number | null;
feedback?: -1 | 1 | null;
metadata?: Record<...> | null;
}): Promise<void>;

流程是:呼叫模型 → 從 env.AI.aiGatewayLogId 拿到這次的 log id → 把 id 送給前端 → 使用者按讚/倒讚 → patchLog()

const answer = await env.AI.run(model, inputs, { gateway: { id: "default" } });
const logId = env.AI.aiGatewayLogId; // ← 這一次呼叫的 log id
return Response.json({ answer, logId });
// 使用者回饋
await env.AI.gateway("default").patchLog(logId, { feedback: -1, score: 20 });

feedback 的型別是 -1 | 1 | null —— 就是倒讚 / 讚 / 清除。score 是 0-100 的自由分數。

這件事的價值在於:評估資料集是免費長出來的。 你不需要另外設計一張表、不需要把 prompt 和回應再存一份 —— gateway 已經有完整的請求與回應,你只是把人類的判斷貼上去。之後要做模型比較或 fine-tune,資料就在那裡。


實測型別 AIGatewayProviders恰好 20 個

workers-ai · anthropic · aws-bedrock · azure-openai · google-vertex-ai
huggingface · openai · perplexity-ai · replicate · groq · cohere
google-ai-studio · mistral · grok · openrouter · deepseek · cerebras
cartesia · elevenlabs · adobe-firefly

網路上(含本系列大綱初稿)常見「23 家 provider」的說法。shipped 型別裡是 20 個。 兩者差異可能是文件與型別的發布時間差,也可能是把 custom provider 之類的算進去了。以型別為準,因為那是你的程式碼實際能用的。

注意 getUrl(provider?: AIGatewayProviders | string)| string —— 型別刻意留了逃生口給 custom provider(beta 功能)。


這一節很重要,因為 AI Gateway 的功能清單看起來很豐富,但有相當一部分是 beta。

GABeta
Analytics、LoggingSpend limit
CachingDynamic Routing
Rate limitingDLP
Retries / timeoutsGuardrails
20 家 providerBYOK
Custom costCustom provider
OTel export、LogpushWebSockets

Spend limit 在 beta 這件事值得單獨提醒。 很多人選 AI Gateway 就是為了「設一個上限避免帳單失控」,但那個功能目前是 beta。在它 GA 之前,成本控制的第一道防線仍然是你自己的程式碼(快取 + 便宜模型 + 用量記錄),不是 gateway 的開關。

Guardrails(beta)用的是 @cf/meta/llama-guard-3-8b —— 就是第 34 章 LinkForge 掃描用的那個模型。

兩個必須知道的代價:

  1. 增加約 500 ms 延遲。 因為它是在你的請求前後各跑一次分類。
  2. 不支援 streaming。 這一條是硬傷 —— 如果你的產品是聊天介面,開了 Guardrails 就不能串流輸出,使用者體驗會明顯變差。

替代做法:自己在 Worker 裡呼叫 llama-guard-3-8b,只對輸入做檢查(輸入檢查不影響輸出串流),輸出則用串流 + 前端的漸進式過濾。這樣你拿回了控制權,代價是要自己寫。


AI Gateway 裡沒有任何 MCP 功能。

Cloudflare 的 MCP Server PortalsCloudflare One / Zero Trust 的功能,不是 AI Gateway 的。兩者名字都有「gateway 到 AI 服務」的味道,害很多人在 AI Gateway 的文件裡繞了半天。

第 37 章談 MCP 時走的是 Agents SDK 那條路,和本章無關。


35.9 LinkForge:把所有 AI 呼叫收攏

Section titled “35.9 LinkForge:把所有 AI 呼叫收攏”

第 34 章的掃描與摘要現在全部走 gateway:

src/ai.ts
const GATEWAY = "default";
export async function summarise(env: Env, tenantId: string, content: string) {
const key = `sum:v3:${await sha256(content)}`; // 版本號在鍵裡(35.4)
const out = await env.AI.run(
MODELS.cheap,
{
messages: [
{ role: "system", content: SUMMARY_PROMPT },
{ role: "user", content: content.slice(0, 4000) },
],
max_tokens: 60,
},
{
gateway: {
id: GATEWAY,
cacheKey: key,
cacheTtl: 86400 * 7, // 摘要一週內不會變
metadata: { tenant: tenantId, feature: "summarise" }, // 成本歸因
requestTimeoutMs: 15_000,
retries: { maxAttempts: 3, retryDelayMs: 400, backoff: "exponential" },
},
tags: [`tenant:${tenantId}`, "feature:summarise"], // 第 34 章
},
);
return { text: extractText(out), logId: env.AI.aiGatewayLogId };
}

四層防護疊起來:

機制擋掉什麼
1KV 快取(第 8 章)同一 URL 的重複請求,連 gateway 都不碰
2gateway cacheKey不同 URL 但內容相同的頁面
3retries第 34 章的 3040(容量不足)
4requestTimeoutMs卡住的請求佔用 CPU 時間

metadata.tenant 加上 log 裡的 costcached,就構成了一個完整的成本歸因模型。搭配第 22 章的 Analytics Engine 做雙寫:

const { text, logId } = await summarise(env, tenantId, content);
env.AE.writeDataPoint({
blobs: ["summarise", tenantId, MODELS.cheap],
doubles: [1, /* tokens */ 0],
indexes: [tenantId],
});

為什麼要雙寫? gateway 的 analytics 有自己的保留期與查詢介面,而 Analytics Engine 是你自己的(第 22 章)。把「AI 成本」和你其他的用量指標放在同一個查詢平面上,才做得出「這個租戶整體毛利」這種問題的答案。

app.post("/api/summary/:slug/feedback", async (c) => {
const { logId, thumb } = await c.req.json<{ logId: string; thumb: "up" | "down" }>();
// 驗證 logId 屬於這個租戶,避免任意 patch 別人的 log
if (!(await ownsLog(c.env, c.get("session").tenantId, logId))) {
return c.json({ error: "forbidden" }, 403);
}
await c.env.AI.gateway("default").patchLog(logId, {
feedback: thumb === "up" ? 1 : -1,
metadata: { tenant: c.get("session").tenantId },
});
return c.body(null, 204);
});

注意那個 ownsLog 檢查。 logId 是從前端傳回來的,而 patchLog() 不會替你驗證擁有權 —— 這和第 27 章的原則一致:從外面回來的識別碼都是使用者輸入。


#結論影響
1沒有獨立的 AI Gateway binding,入口是 env.AI.gateway(id)
2AiGatewaypatchLog / getLog / run / getUrl 四個方法
3gateway.run() 從文件消失但仍存在於 shipped 型別可編譯,但不是官方路徑,新程式碼不要用
42026-03 起首次認證請求會自動建立名為 default 的 gateway不必先手動建
5GatewayOptions 能從 binding 設定 cacheKey/cacheTtl/skipCache/metadata/collectLog/eventId/requestTimeoutMs/retries不需要手寫 cf-aig-* header
6AIGatewayHeaders 型別給出完整 header 清單,含未被廣泛記載的 cf-aig-custom-cost可覆寫成本估算
7已棄用:cf-cache-ttlcf-skip-cache(無 -aig- 的舊版)
8認證位置隨入口而異gateway.ai.cloudflare.comcf-aig-authorizationapi.cloudflare.comAuthorization: Bearer最常見的 401 來源;用 binding 則完全不用管
9兩段式棄用鏈:Universal Endpoint →(已棄用)OpenAI-compatible →(也已棄用)REST APIProvider-specific endpoints 棄用
10cacheKey 完全由你決定把版本號寫進鍵裡,改 prompt 時可全域失效
11AiGatewayLogcachedcostcustom_costtokens_in/outstep快取命中率與成本都是現成的
12patchLogfeedback 型別是 -1 | 1 | nullscore 是自由數值評估資料集免費長出來
13env.AI.aiGatewayLogId 是取得本次 log id 的管道回饋迴路的起點
14AIGatewayProviders 型別列出恰好 20 個 provider常見的「23 家」說法與型別不符,以型別為準
15getUrl(provider?: AIGatewayProviders | string) 保留 | string給 custom provider(beta)用的逃生口
16Spend limit 是 beta成本控制第一道防線仍是你自己的程式碼
17Guardrails(beta)用 @cf/meta/llama-guard-3-8b+約 500ms 且不支援 streaming聊天介面要自己實作輸入端檢查
18Dynamic Routing、DLP、BYOK、custom provider、WebSockets 皆為 beta
19AI Gateway 沒有 MCP 功能MCP Server Portals 屬於 Cloudflare One / Zero Trust
20patchLog() 不驗證 log 的擁有權從前端回傳的 logId 必須自己驗證

  1. 用同一個 cacheKey 連打兩次,用 getLog() 確認第二筆的 cachedtrue,並比較 durationcost
  2. cf-aig-custom-cost 設成一個你談好的折扣價,確認 log 裡的 costcustom_cost 都變了。
  3. 實作 35.5 的回饋迴路,然後用 getLog() 讀回來,確認 scorefeedback 真的貼上去了。
  4. 呼叫 /gateway-run,記錄 gateway.run() 現在的實際行為 —— 它從文件消失但還在型別裡,這個缺口值得自己確認。
  5. 開啟 Guardrails,量測同一個請求開/關時的延遲差異,並確認 streaming 真的失效。

下一章(第 36 章):Vectorize 與 RAG —— 以及第 34 章型別裡那個 @deprecated 的 AutoRAG 現在變成了什麼。