Cloudflare Agentsモデル入門——AI SDK経由の推論をFirestoreで監査する
Cloudflare AgentsのAI SDK向けモデルプロバイダを題材に、Workers AIとAI Gateway経由の推論をNext.js + Firestoreで監査する実践設計を解説します。

はじめに
AI機能を本番に入れるとき、モデル呼び出しは「動いたら終わり」では済みません。
| 場面 | よくある困りごと |
|---|---|
| Workers AIと外部モデルを併用したい | 呼び出し方やログの残し方がばらける |
| AI Gatewayでキャッシュやリトライを使いたい | アプリ側のコードがproviderごとに分岐しがち |
| モデル切り替えを安全に進めたい | どのモデルが実際に回答したか後から追えない |
Cloudflareは2026年10月5日更新のAgentsドキュメントで、Agents SDKに Workers AIとAI Gateway向けのモデルプロバイダ が含まれることを説明しています。特に agents/models/ai-sdk はAI SDK v7向けのProviderV4として使え、generateText、streamText、tools、structured outputなどAI SDK側の呼び出し口にそのまま渡せます。
本記事では、架空の問い合わせ管理SaaS「ReplyPort」を題材に、Cloudflare WorkerでAI SDK経由の推論を実行し、Next.js App Router + Firestore + TypeScriptで「どの入力に、どのモデルが、どのGatewayログで応答したか」を監査できる台帳を作ります。この記事を読み終えると、モデル選択をコードに散らさず、推論結果を業務アプリ側で追跡する設計にできます。
参考にした一次情報は次の通りです。
- Cloudflare Docs: Agents Models
- Cloudflare Docs: Agents AI SDK provider
- Cloudflare Blog: AI Gateway posts
- Firebase Admin SDK: Cloud Firestore
何が新しいのか
Cloudflare Agentsのモデルプロバイダは、Workerの AI bindingを中心に置きます。Workers AIモデルなら @cf/ で始まるモデルIDを渡し、外部providerのモデルなら各vendorのAI SDK providerでモデルオブジェクトを作ってからCloudflare側に通します。
公式ドキュメントでは、agents/models/ai-sdk と agents/models/pi-ai の2種類が紹介されています。本記事で扱うのはAI SDK向けの agents/models/ai-sdk です。Providerはbetaで、minor releaseでAPIが変わる可能性がある点は押さえておきます。
ポイントは、Cloudflare側のProviderが「どこへ流すか」を受け持ち、Next.js側は「業務上どう監査するか」に集中できることです。水門で流路を制御するように、Workers AI、AI Gateway、fallback、cacheをWorker側に寄せられます。
事前準備
公式ドキュメントでは、AI SDK向けProviderに agents、ai、@ai-sdk/provider が必要だと説明されています。この記事ではCloudflare Worker側の依存として追加します。
yarn add agents ai @ai-sdk/provider
Wrangler設定には AI bindingを追加します。
{
"ai": {
"binding": "AI"
}
}
ReplyPortでは、Cloudflare WorkerをAI実行口、Next.jsを業務UIと監査台帳の管理口に分けます。
| 役割 | 担当 |
|---|---|
| モデル呼び出し | Cloudflare Worker |
| Workers AI / AI Gateway接続 | agents/models/ai-sdk |
| 業務入力の検証 | Next.js Route Handler |
| 監査ログ | Firestore aiInferenceRuns |
| 画面表示 | Next.js App Router |
ハンズオン1: WorkerでWorkers AIを呼ぶ
まず、Worker側でAI SDK providerを作ります。ここでは公式ドキュメントに掲載されている createAI と generateText の形に合わせ、Workers AIの @cf/zai-org/glm-4.7-flash を呼び出します。
// worker/src/index.ts
import { createAI } from "agents/models/ai-sdk";
import { generateText } from "ai";
type Env = {
AI: Ai;
};
export default {
async fetch(request: Request, env: Env) {
if (request.method !== "POST") {
return Response.json({ error: "method not allowed" }, { status: 405 });
}
const body = (await request.json()) as {
ticketId?: string;
prompt?: string;
};
if (!body.ticketId || !body.prompt || body.prompt.length > 4000) {
return Response.json({ error: "invalid request" }, { status: 400 });
}
const ai = createAI({
binding: env.AI,
id: "replyport-prod",
cacheTtl: 60,
});
const result = await generateText({
model: ai("@cf/zai-org/glm-4.7-flash"),
prompt:
"You are drafting a concise Japanese support reply.\n\n" +
body.prompt,
});
return Response.json({
ticketId: body.ticketId,
text: result.text,
metadata: result.providerMetadata?.cloudflare ?? null,
});
},
};
id: "replyport-prod" はAI Gateway側の識別に使う前提の値です。公式ドキュメントでは、Gateway optionsとしてcache、logging、metadata、timeouts、retriesなどをProvider、model、callの各層に設定できると説明されています。まずは短い cacheTtl から始めると、同じ問い合わせで何度も下書きを試すUIでも無駄な推論を減らせます。
ハンズオン2: Next.jsから呼んでFirestoreへ残す
次に、Next.js側にRoute Handlerを作ります。WorkerがAIを実行し、Next.jsは実行前後の状態をFirestoreへ保存します。
// app/api/tickets/[ticketId]/ai-draft/route.ts
import { getApps, initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { NextRequest, NextResponse } from "next/server";
if (!getApps().length) {
initializeApp();
}
const db = getFirestore();
type WorkerResponse = {
ticketId: string;
text: string;
metadata: {
model?: string;
gateway?: string;
provider?: string;
logId?: string;
eventId?: string;
requestId?: string;
traceId?: string;
} | null;
};
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ ticketId: string }> },
) {
const { ticketId } = await params;
const body = (await request.json()) as { prompt?: string };
if (!body.prompt || body.prompt.length < 10 || body.prompt.length > 4000) {
return NextResponse.json({ error: "invalid prompt" }, { status: 400 });
}
const workerUrl = process.env.CLOUDFLARE_AI_WORKER_URL;
if (!workerUrl) {
return NextResponse.json({ error: "worker url is not configured" }, { status: 500 });
}
const runRef = db.collection("aiInferenceRuns").doc();
await runRef.set({
ticketId,
promptPreview: body.prompt.slice(0, 180),
status: "running",
source: "cloudflare_agents_ai_sdk",
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
});
try {
const response = await fetch(workerUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ticketId, prompt: body.prompt }),
});
if (!response.ok) {
throw new Error(`Worker failed: ${response.status}`);
}
const result = (await response.json()) as WorkerResponse;
await runRef.update({
status: "completed",
answerPreview: result.text.slice(0, 300),
cloudflare: result.metadata,
completedAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
});
return NextResponse.json({
runId: runRef.id,
text: result.text,
metadata: result.metadata,
});
} catch (error) {
await runRef.update({
status: "failed",
errorMessage: error instanceof Error ? error.message : "unknown error",
updatedAt: FieldValue.serverTimestamp(),
});
return NextResponse.json({ error: "draft generation failed" }, { status: 502 });
}
}
ここで保存している metadata は、CloudflareのProviderが返すGateway情報を想定しています。公式ドキュメントでは、model、gateway、provider、logId、eventId、requestId、traceId などのGateway metadataが説明されています。実運用では、本文全文ではなくpreviewと参照IDを保存し、機微情報の保存範囲を小さくするのが扱いやすいです。
ハンズオン3: fallbackを監査に反映する
CloudflareのAI SDK providerは、primary modelが出力を返す前に失敗した場合のfallbackも扱えます。Workers AIと外部providerモデルを混在できると説明されていますが、この記事では実装を単純にするため、Workers AIのモデルIDだけを例にします。
const model = ai("@cf/zai-org/glm-4.7-flash", {
fallback: ["@cf/moonshotai/kimi-k2.7-code"],
metadata: {
app: "replyport",
feature: "support-draft",
},
});
const result = await generateText({
model,
prompt: body.prompt,
});
このときNext.js側では、固定の「呼び出したつもりのモデル」ではなく、Provider metadataの model を保存します。fallbackが動くと、最初に指定したモデルと実際に回答したモデルが違うことがあります。
| 保存する値 | 理由 |
|---|---|
requestedModel | 期待していたモデル設定を後から確認するため |
answeredModel | 実際に回答したモデルを費用・品質分析に使うため |
logId | Cloudflare側のGatewayログへ戻るため |
traceId | 複数turnやツール実行をまとめて追うため |
cacheStatus | 同一入力の再実行やFAQ生成の効率を見るため |
モデルを切り替えるときは、チケット単位でA/Bを混ぜるより、feature や tenantId のmetadataを付けておくと分析しやすくなります。Firestoreには業務側の状態、Cloudflare側には推論側の詳細ログ、という分担です。
実務での注意点
1つ目は、Providerがbetaである点です。APIの形がminor releaseで変わる可能性があるため、agents と ai の更新はGitHub Actions上でbuildを通し、まず検証環境のWorkerにだけ出すのが安全です。
2つ目は、外部providerモデルを使う場合の認証です。公式ドキュメントでは、Cloudflare側のUnified BillingまたはGatewayに保存したkeyを使い、Worker内にはvendorの本物のAPI tokenを置かない設計が説明されています。Next.js側にもvendor tokenを置かず、WorkerとGatewayへ境界を寄せると漏えい面を小さくできます。
3つ目は、エラー処理です。Providerは CloudflareAIError を投げ、AI SDKの APICallError を拡張すると説明されています。Worker側でstatus、code、logId、modelをログに残し、Next.js側ではユーザーに詳細を出さない、という分け方にします。
import { CloudflareAIError } from "agents/models/ai-sdk";
try {
const result = await generateText({ model, prompt: body.prompt });
return Response.json({ text: result.text });
} catch (error) {
if (error instanceof CloudflareAIError) {
console.error({
status: error.status,
code: error.code,
logId: error.logId,
model: error.model,
});
}
return Response.json({ error: "AI request failed" }, { status: 502 });
}
まとめ
Cloudflare AgentsのAI SDK向けモデルプロバイダを使うと、Workers AIとAI GatewayをAI SDKの呼び出し口に寄せられます。アプリ側から見ると、generateText などの使い慣れた形を保ちながら、Gatewayのcache、fallback、metadata、ログへ接続できるのが大きな利点です。
実務では、Cloudflare Workerを推論の水門、Next.js + Firestoreを業務監査の台帳として分けると扱いやすくなります。ReplyPortのような問い合わせ下書き機能なら、入力、実行状態、実際に回答したモデル、Gateway log IDを残すだけで、モデル切り替えや費用分析の足場がかなり安定します。
まずは1機能だけをWorker経由にし、Firestoreへ aiInferenceRuns を残すところから始めるのがおすすめです。モデルの性能比較は、その後で十分です。先に監査の器を作っておくと、AI機能を増やすほど効いてきます。