この記事のポイント
- Cloudflare Workers AIは、Llama 3.3 70BやMistralなどのLLMを「サーバー管理ゼロ」「エッジ実行」「リクエスト課金」で呼べるサーバーレス推論基盤です。
- AI Gatewayをかませることで、レート制限・キャッシュ・コスト可視化・ログ・フォールバックを設定一つで実現でき、OpenAIやAnthropicの呼び出しも同じパイプラインに乗せられます。
- Durable Objects + Agents SDKを使うと、会話履歴・ツール呼び出し・ステート管理を1コンテナ=1エージェントの形で簡潔に書けます。
- Vectorizeでベクトル検索、Workflowsで長時間ジョブ、R2でファイル、D1でメタデータを束ねれば、AIエージェントに必要な「記憶・検索・ジョブ・配信」のフルスタックがCloudflareだけで完結します。
- 本記事はwrangler.tomlとWorkers SDKコードをコピペで動かせる形で15本以上収録した、実装プロンプト集型のガイドです。
なぜ今、AIエージェントを「Cloudflare」で組むのか
AIエージェントを本番運用しようとすると、開発者は同じ壁にぶつかります。LLM APIの呼び出しは動いた、プロトタイプも動いた、しかし「同時接続が増えたら落ちる」「リトライとレート制限が手に負えない」「会話履歴をどこに置けばいいか分からない」「ツール呼び出しが暴走してコストが跳ねた」。この種の問題は、モデルを変えても解けません。インフラ設計の問題だからです。
2025年から2026年にかけてCloudflareが行ってきたのは、まさにこの「AIエージェントを本番で運用するためのインフラ」を一通り揃える動きでした。Workers AIによるサーバーレス推論、AI Gatewayによる呼び出しの統制、VectorizeによるベクトルDB、Workersに統合されたAgents SDK、Durable Objectsによるステートフル実行、Workflowsによる長時間ジョブ、R2によるオブジェクトストレージ、D1によるSQLite、そしてWorkers MCP / Cloudflare MCP Serverによるエージェント間連携。これらが同一のWranglerコマンド、同一のwrangler.toml、同一の課金体系で完結します。
本記事は、それらの構成要素を「実際にwrangler initで動くコード」として並べた、Pattern A1の実践プロンプト集型ガイドです。AWS Bedrock AgentCoreやAWS Agent Toolkitと比較したい読者にはAmazon Bedrock AgentCore完全ガイドとAgent Toolkit for AWS実装ガイド、コスト設計をしたい読者にはAIエージェントのコスト最適化7原則を併読してください。
結論先出し: Cloudflareスタックの全体像
最初に結論をマップで示します。本記事で扱うコンポーネントは7つです。
| レイヤー | サービス | 役割 | 本記事での扱い |
|---|---|---|---|
| 推論 | Workers AI | LLM / Embedding / Whisper等をエッジで実行 | テンプレ #1-#4 |
| 呼び出し統制 | AI Gateway | レート制限・キャッシュ・ログ・フォールバック | テンプレ #5-#7 |
| ベクトルDB | Vectorize | 意味検索・RAGの中核 | テンプレ #8-#9 |
| ステート | Durable Objects + Agents SDK | 1エージェント=1コンテナの永続化 | テンプレ #10-#12 |
| 長時間ジョブ | Workflows | リトライ込みのステップ実行 | テンプレ #13 |
| ストレージ | R2 / D1 / KV | ファイル・SQL・KV | テンプレ #14 |
| 連携 | MCP / Workers Bindings | エージェント外部接続 | テンプレ #15 |
このスタックの強みは、Wrangler一つでデプロイでき、課金が「リクエスト数 + 推論秒 + ストレージ」というシンプルな三軸であること、そしてエッジ実行のため日本のユーザーからのレイテンシが極端に短いことです。Workers AIはCloudflareの200以上のロケーションで実行されるため、東京や大阪のユーザー向けのエージェントを米国リージョンのモデルAPIに丸投げするより、初回トークンまでの体感応答が明確に速くなります。
環境準備: 5分で動く最小セット
まずローカルで動く最小構成を作ります。Node.jsは20以上、npmは10以上を想定します。
# Wrangler CLIのインストール(プロジェクトローカル推奨)
npm create cloudflare@latest -- my-agent
cd my-agent
# ログイン(ブラウザが開く)
npx wrangler login
# Workers AIを有効化(無料枠あり)
npx wrangler ai models
`npm create cloudflare@latest`はCloudflareが公式に提供しているスキャフォルダで、テンプレートを選ぶ画面が出ます。本記事では「Hello World – Worker (TypeScript)」を選んだ前提で進めます。
wrangler.tomlを以下のように書き換えます。これが以後の出発点になります。
name = "my-agent"
main = "src/index.ts"
compatibility_date = "2026-05-20"
compatibility_flags = ["nodejs_compat"]
# Workers AI binding
[ai]
binding = "AI"
# AI Gateway(後述のテンプレ#5で作成)
# [ai.gateway]
# id = "my-agent-gateway"
これで`env.AI.run()`がWorkerコード内で使えるようになります。次節から、本題のコードテンプレートを15本順番に並べます。
テンプレ #1: Workers AIでLlama 3.3 70Bを呼ぶ最小Worker
もっとも基本の形です。HTTPリクエストを受けて、Workers AIのLlama 3.3 70Bにプロンプトを投げ、JSONで返します。
// src/index.ts
export interface Env {
AI: Ai;
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const url = new URL(req.url);
const prompt = url.searchParams.get("q") ?? "Cloudflareとは何ですか?";
const result = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "system", content: "あなたは丁寧で簡潔な日本語アシスタントです。" },
{ role: "user", content: prompt },
],
max_tokens: 512,
});
return Response.json({ answer: (result as any).response });
},
};
`@cf/meta/llama-3.3-70b-instruct-fp8-fast`はWorkers AIで提供される高速版Llama 3.3 70Bのモデル識別子です。Workers AIのモデルカタログは`npx wrangler ai models`で随時確認できます。デプロイは`npx wrangler deploy`の一発で、URLは`https://my-agent.<account>.workers.dev/`の形で発行されます。
このテンプレが重要なのは、AWSやGCPでLLMを動かすときに必須だった「IAMロール」「VPC設定」「リージョン選定」「コンテナビルド」「Lambdaコールドスタート対策」が一切登場しない点です。Workers AIは内部的にCloudflareの200以上のロケーションで分散実行されており、開発者はモデルIDとプロンプトだけ意識すればよい設計です。
テンプレ #2: ストリーミング応答で体感速度を上げる
会話型エージェントでは、最初のトークンが届くまでの体感速度が満足度を決めます。Workers AIはServer-Sent Eventsでのストリーミングに対応しているため、UIが「タイプライター風」に動かせます。
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const stream = (await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [{ role: "user", content: "AIエージェントとは何か、500字で説明して" }],
stream: true,
})) as ReadableStream;
return new Response(stream, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache",
},
});
},
};
フロント側はFetchEvent + ReadableStreamで素直に受けられます。Workers AI自体に「ストリーミング切替」フラグがあるため、追加のSDKは不要です。AWS BedrockのInvokeModelWithResponseStreamと比較すると、認証ヘッダもエンドポイントも不要で、Worker内のbindingが直接トークンを返してくる感覚です。
テンプレ #3: Embeddingモデルでテキストをベクトル化する
RAG(Retrieval-Augmented Generation)の入り口として、テキストをベクトル化します。Workers AIには`@cf/baai/bge-m3`などの多言語Embeddingモデルが用意されています。
async function embed(env: Env, texts: string[]): Promise<number[][]> {
const res = await env.AI.run("@cf/baai/bge-m3", {
text: texts,
});
return (res as any).data as number[][];
}
`@cf/baai/bge-m3`は1024次元のEmbeddingを返します。多言語対応なので日本語と英語が混在するナレッジベースにそのまま使えます。次のVectorizeテンプレと組み合わせると、ベクトル検索基盤が完成します。
テンプレ #4: 構造化出力(JSON mode)でツール引数を取り出す
エージェントは「次に呼ぶツール」「ツールの引数」をJSONで決める場面が頻繁にあります。Workers AIはOpenAI互換の`response_format`を一部のモデルで受け付けますが、確実なのはプロンプトで「JSONだけを返せ」と縛り、コード側でvalidateする方式です。
import { z } from "zod";
const Action = z.object({
tool: z.enum(["search_docs", "send_email", "noop"]),
query: z.string().optional(),
to: z.string().email().optional(),
});
async function decideAction(env: Env, userText: string) {
const sys = `あなたはツール選択AIです。以下のいずれかをJSONだけで返してください。
{"tool":"search_docs","query":"..."} か {"tool":"send_email","to":"x@y.z"} か {"tool":"noop"}。
JSON以外のテキストは絶対に出力しないでください。`;
const res = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "system", content: sys },
{ role: "user", content: userText },
],
max_tokens: 200,
});
const text = (res as any).response.trim();
const json = JSON.parse(text);
return Action.parse(json);
}
Zodでスキーマvalidationをかけているのは、LLMが「JSONっぽい何か」を返してきた場合に早期に弾くためです。本番のエージェントでは、ここで失敗したら「もう一度プロンプトを投げる」「フォールバックモデルに切り替える」のロジックを足します。後者は次のAI Gatewayテンプレで自動化できます。
テンプレ #5: AI Gatewayでレート制限・キャッシュ・ログを一括管理
ここから本番運用の話に入ります。AI Gatewayは、Workers AIだけでなくOpenAI・Anthropic・Google・Mistral・Replicate・HuggingFaceへの呼び出しを「Cloudflare経由」に揃えるリバースプロキシです。設定するだけで以下が手に入ります。
- キャッシュ: 同一プロンプトのレスポンスを設定TTLでキャッシュ。同じFAQが何度も投げられる現場で効きます。
- レート制限: 「1分100リクエスト」「ユーザーIDごとに10リクエスト」など。
- ログ: 全リクエスト・レスポンス・トークン数・コストを保存。
- フォールバック: プライマリが失敗したらセカンダリへ自動切替。
- コスト可視化: モデル別・アプリ別の累計コスト。
セットアップは、Cloudflareダッシュボードの「AI > AI Gateway」で新規ゲートウェイを作成し、IDを発行するだけです。wrangler.tomlに以下を追記します。
[ai]
binding = "AI"
[[ai.gateway]]
id = "my-agent-gateway"
Worker内ではbindingに`gateway`オプションを追加するだけで自動的にゲートウェイ経由になります。
const res = await env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{
messages: [{ role: "user", content: prompt }],
},
{
gateway: {
id: "my-agent-gateway",
skipCache: false,
cacheTtl: 3600, // 1時間キャッシュ
},
}
);
これだけで、同じpromptで来た2回目のリクエストはモデルを実行せずキャッシュから返り、コストはゼロ、レイテンシは数ミリ秒になります。多くのAIエージェントは「Aboutページの質問」「営業時間の質問」など、定型FAQが全体の30〜50%を占めます。AI Gatewayのキャッシュはここを刈り取ります。
テンプレ #6: AI GatewayでOpenAI互換APIをラップする
「Cloudflareスタックに移行したいが、OpenAI SDKを使った既存コードは残したい」というケースは現実に多いです。AI GatewayはOpenAI互換のエンドポイントを提供しており、ベースURLを差し替えるだけで動きます。
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY,
baseURL: `https://gateway.ai.cloudflare.com/v1/${env.ACCOUNT_ID}/my-agent-gateway/openai`,
});
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Cloudflareとは?" }],
});
baseURLを差し替えるだけで、リクエスト全件がAI Gatewayのログ・キャッシュ・レート制限の管轄に入ります。OpenAIからAnthropicへ移行する際の「フォールバック切替」も、Workerコードを触らずダッシュボードのフォールバックポリシーで設定できます。これは、Lambda + Bedrockのモデル切替で必要だったコード書き換えと比べると、運用上のインパクトが大きい違いです。
テンプレ #7: ユーザー別レート制限と監査ログ
SaaSとして提供するAIエージェントでは、ユーザー単位の流量制御と、誰が何を聞いたかの監査ログが必須です。AI Gatewayはリクエスト時にカスタムヘッダを渡すと、それをタグとしてログに記録し、レート制限の単位にも使えます。
const res = await env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{ messages },
{
gateway: {
id: "my-agent-gateway",
metadata: {
userId: ctx.userId,
plan: ctx.plan, // "free" | "pro"
feature: "chat",
},
},
}
);
metadataで渡したフィールドはAI Gatewayのダッシュボードでフィルタとして使えるため、「Proプランのユーザーで、chat featureで、過去24時間に発生したエラー」を一発で抽出できます。SOC 2監査や個人情報の問い合わせ対応でも、この粒度のログがあると工数が劇的に減ります。
テンプレ #8: VectorizeでRAGインデックスを作る
Vectorizeは、Cloudflareが提供するベクトルDBです。次元数とメトリックを決めれば、あとはJSをJSON-RPCで叩く感覚で使えます。Workers AIのEmbeddingと組み合わせると、外部のPineconeやWeaviateを契約しなくてもRAGが組めます。
# bge-m3が1024次元のため、次元数1024、距離はcosineで作成
npx wrangler vectorize create my-knowledge --dimensions=1024 --metric=cosine
wrangler.tomlにbindingを追加します。
[[vectorize]]
binding = "VECTORIZE"
index_name = "my-knowledge"
インデックスへの投入コードはこうなります。
export interface Env {
AI: Ai;
VECTORIZE: VectorizeIndex;
}
async function ingest(env: Env, docs: { id: string; text: string; meta?: any }[]) {
const embeds = await env.AI.run("@cf/baai/bge-m3", {
text: docs.map((d) => d.text),
});
const vectors = (embeds as any).data.map((v: number[], i: number) => ({
id: docs[i].id,
values: v,
metadata: { text: docs[i].text, ...(docs[i].meta ?? {}) },
}));
await env.VECTORIZE.upsert(vectors);
}
検索側はもっとシンプルです。
async function search(env: Env, query: string, k = 5) {
const e = await env.AI.run("@cf/baai/bge-m3", { text: [query] });
const hits = await env.VECTORIZE.query((e as any).data[0], {
topK: k,
returnMetadata: true,
});
return hits.matches.map((m) => ({
score: m.score,
text: m.metadata?.text,
}));
}
Pinecone等の外部サービスと比較したときのVectorizeの利点は、ネットワーク呼び出しがCloudflare内部で完結する点です。Worker → 外部VectorDBの構成では、毎回のクエリで100〜200msのオーバーヘッドが乗りますが、Worker → Vectorizeはほぼ無視できる時間で返ります。RAGはレイテンシの積み重ねで遅くなりがちなので、この差は体感に直結します。
テンプレ #9: チャンク分割つきのRAGパイプライン
長文ドキュメントをそのままEmbeddingするとリコールが悪化します。1チャンク500〜800文字、オーバーラップ80文字程度で切るのが日本語では標準的です。
function chunk(text: string, size = 700, overlap = 80): string[] {
const chunks: string[] = [];
let i = 0;
while (i < text.length) {
chunks.push(text.slice(i, i + size));
i += size - overlap;
}
return chunks;
}
async function ingestDocument(env: Env, docId: string, body: string) {
const parts = chunk(body);
const docs = parts.map((t, i) => ({
id: `${docId}#${i}`,
text: t,
meta: { docId, chunkIndex: i },
}));
await ingest(env, docs);
}
本番では「同じdocIdの旧チャンクを削除してから入れ直す」処理を入れます。`env.VECTORIZE.deleteByIds`が使えるので、`${docId}#0`から`${docId}#N`までを並べてバッチ削除します。
テンプレ #10: Durable Objectsで「1ユーザー=1エージェント」のステートを持つ
ここからがCloudflareの真骨頂です。Durable Objects(DO)は、グローバルにユニークなIDを持つコンテナで、「1ユーザー=1コンテナ」「1会話=1コンテナ」のような粒度でステートを保持できます。会話履歴・ツール呼び出し履歴・タイマー・WebSocket接続を同一オブジェクト内で扱えるので、AIエージェントの実装と相性が良いです。
export class ConversationAgent {
state: DurableObjectState;
env: Env;
history: any[] = [];
constructor(state: DurableObjectState, env: Env) {
this.state = state;
this.env = env;
this.state.blockConcurrencyWhile(async () => {
this.history = (await this.state.storage.get("history")) ?? [];
});
}
async fetch(req: Request): Promise<Response> {
const { message } = await req.json<{ message: string }>();
this.history.push({ role: "user", content: message });
const res = await this.env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{ messages: this.history.slice(-20) }
);
const reply = (res as any).response;
this.history.push({ role: "assistant", content: reply });
await this.state.storage.put("history", this.history);
return Response.json({ reply });
}
}
wrangler.tomlでDOを宣言します。
[[durable_objects.bindings]]
name = "AGENTS"
class_name = "ConversationAgent"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ConversationAgent"]
呼び出し側のWorker:
export default {
async fetch(req: Request, env: Env) {
const userId = new URL(req.url).searchParams.get("uid") ?? "anon";
const id = env.AGENTS.idFromName(userId);
const stub = env.AGENTS.get(id);
return stub.fetch(req);
},
};
これだけで、ユーザーAとユーザーBは完全に別のメモリ空間を持つAIエージェントになります。Lambdaで同じことをしようとすると、DynamoDB + Step Functions + EventBridgeを組み合わせる必要があり、5〜10ファイルのIaCコードが必要でした。Cloudflareでは50行で済みます。
テンプレ #11: Agents SDKでツール呼び出しエージェントを書く
Cloudflareは2025年に`agents`パッケージとしてエージェント抽象を導入しました(Workersのbinding経由で動作)。ツール定義、ループ制御、ステート管理が一段抽象化されています。
import { Agent } from "agents";
export class SupportAgent extends Agent<Env> {
async onMessage(message: string) {
const tools = {
search_docs: {
description: "ドキュメントを意味検索する",
parameters: { query: "string" },
execute: async ({ query }: { query: string }) => {
return await search(this.env, query, 5);
},
},
};
const result = await this.runLLM({
model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
system: "あなたはサポートAIです。必要ならsearch_docsを呼びます。",
messages: [...this.state.history, { role: "user", content: message }],
tools,
maxSteps: 5,
});
this.state.history.push({ role: "user", content: message });
this.state.history.push({ role: "assistant", content: result.text });
await this.saveState();
return result.text;
}
}
このAgents SDKの良いところは、`maxSteps`で「ツール呼び出しを最大5回まで」と上限が引けることです。Bedrock AgentsやLangGraphで起きがちな「ツールを延々と呼び続けてコストが跳ねる」事故が、設計上ブロックされます。AIエージェントのコスト最適化7原則でも触れていますが、エージェントのコスト事故の大半は「ループ上限なし」が原因です。
テンプレ #12: WebSocketで双方向ストリーミング会話
Durable ObjectsはWebSocketサーバーとしてもふるまえます。LLMのトークンストリームをそのままクライアントに流す双方向UIが、別途WebSocketサーバーを立てずに作れます。
export class WSAgent {
async fetch(req: Request) {
if (req.headers.get("Upgrade") !== "websocket") {
return new Response("expected websocket", { status: 426 });
}
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
server.accept();
server.addEventListener("message", async (evt) => {
const userText = String(evt.data);
const stream = (await this.env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{ messages: [{ role: "user", content: userText }], stream: true }
)) as ReadableStream;
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
server.send(decoder.decode(value));
}
server.send("[[END]]");
});
return new Response(null, { status: 101, webSocket: client });
}
}
このコードはCloudflareエッジで実行されるため、東京のユーザーは東京のロケーションに接続し、そこでLLM推論まで完結します。AWSのus-east-1のAPI Gateway WebSocketを使うと往復で200ms以上かかる場面でも、Cloudflareなら数十msに収まります。
テンプレ #13: Workflowsで長時間ジョブを安全に回す
AIエージェントは時々「動画の要約」「PDF 300ページのRAG投入」「夜間バッチでDBを更新」のような長時間ジョブを抱えます。Workerは個別実行で最大30秒(Unboundでも30分)の制限があるため、ここをWorkflowsに任せます。WorkflowsはCloudflare版のStep Functionsで、各ステップが自動でリトライ・冪等化される設計です。
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from "cloudflare:workers";
export class IngestWorkflow extends WorkflowEntrypoint<Env, { url: string }> {
async run(event: WorkflowEvent<{ url: string }>, step: WorkflowStep) {
const body = await step.do("fetch", async () => {
const res = await fetch(event.payload.url);
return await res.text();
});
const chunks = await step.do("chunk", async () => chunk(body));
await step.do("embed-and-upsert", { retries: { limit: 3, delay: "10s" } }, async () => {
const e = await this.env.AI.run("@cf/baai/bge-m3", { text: chunks });
const vectors = (e as any).data.map((v: number[], i: number) => ({
id: `${event.payload.url}#${i}`,
values: v,
metadata: { text: chunks[i] },
}));
await this.env.VECTORIZE.upsert(vectors);
});
}
}
`step.do`で囲まれたブロックは、失敗時に自動でリトライされ、すでに成功したステップは再実行されません。1万件のドキュメントを順次embeddingする処理で、途中の3000件目で外部APIが落ちても、4001件目から再開できる仕組みが標準で備わっています。
テンプレ #14: R2 / D1 / KVを混在させる
エージェントは「ファイル」「メタデータ」「セッション」を別々の保存先で持つのが一般的です。Cloudflareでは以下のように使い分けます。
- R2: PDF・画像・音声などのバイナリ。S3互換APIで、エグレス無料。
- D1: SQLite互換のSQLストレージ。ユーザー・契約・トランザクション。
- KV: セッション・キャッシュ。読み込みが極端に速い。
- Vectorize: ベクトルのみ。
- Durable Objects Storage: 1オブジェクト内で完結するステート(会話履歴)。
例えば「PDFをR2に保存し、メタデータをD1に書き、チャンクをVectorizeに入れる」フローはこうなります。
export interface Env {
AI: Ai;
VECTORIZE: VectorizeIndex;
R2: R2Bucket;
DB: D1Database;
}
async function uploadPdf(env: Env, name: string, file: ArrayBuffer) {
// 1. R2に原本
await env.R2.put(`pdf/${name}`, file);
// 2. D1にメタ
await env.DB.prepare(
"INSERT INTO documents (name, r2_key, created_at) VALUES (?, ?, ?)"
)
.bind(name, `pdf/${name}`, new Date().toISOString())
.run();
// 3. テキスト抽出 → Vectorize(抽出は別Workerに委譲)
await env.WORKFLOW.create({ params: { name } });
}
S3 → DynamoDB → Pinecone → SQS という4サービス・4課金体系を、R2 → D1 → Vectorize → Workflowsという「同一アカウント・同一Wrangler・同一請求書」にまとめられるのが運用上の最大のメリットです。請求書が一本になるだけで経理側のレビュー工数が消える、というのは現場では地味に大きい話です。
テンプレ #15: Cloudflare MCP Serverで外部AIから呼び出される
Model Context Protocol(MCP)は、Anthropic主導でClaude DesktopやCursor、Claude Codeなどが採用しているエージェント間連携プロトコルです。CloudflareはWorkersでMCPサーバーを書ける公式テンプレートを提供しており、Vectorize / R2 / D1のデータをClaude等から直接読めるようになります。
import { McpAgent } from "agents/mcp";
export class KnowledgeMCP extends McpAgent<Env> {
async init() {
this.server.tool(
"search_knowledge",
"社内ナレッジを意味検索する",
{ query: { type: "string" } },
async ({ query }) => {
const hits = await search(this.env, query, 5);
return { content: [{ type: "text", text: JSON.stringify(hits) }] };
}
);
}
}
これをデプロイすると、`https://<your-worker>.workers.dev/sse`がMCPエンドポイントになり、Claude DesktopのMCP設定に追加するだけで、Claudeが社内ナレッジを意味検索できます。社内RAGをClaude/ChatGPT/Cursorから同時に呼びたいケースで威力を発揮します。
本番運用のベストプラクティス: コスト・可観測性・セキュリティ
テンプレを並べただけでは本番に出せないので、運用面で押さえるポイントを整理します。
コスト設計
Workers AIの課金は基本「Neuronsベース」で、モデルごとに1リクエストあたりのNeurons消費が決まっています。重いモデル(Llama 3.3 70B等)と軽いモデル(Llama 3.1 8B等)の選択を間違えると、月次コストが5〜10倍違ってきます。具体的なノウハウはAIエージェントのコスト最適化7原則で詳述しています。
本記事のスタックでは、まずAI Gatewayのキャッシュを有効化し、FAQで30〜50%の呼び出しを削るのが王道です。次に、軽量モデルで判別→必要なときだけ重いモデルへフォールバック、という二段構えにします。Agents SDKの`maxSteps`を必ず指定するのも重要です。
可観測性
AI Gatewayのログ + Workers Logs + Workers Analytics Engineの3点を組み合わせると、エージェントの「全リクエストのプロンプト・トークン数・コスト・レイテンシ・ユーザーID」がダッシュボードで見えるようになります。LangSmithやLangFuseを別途契約しなくても、最低限の可観測性は揃います。
セキュリティ
Workers AI / AI Gatewayはデフォルトでプロンプトを学習に使いません。さらに、AI GatewayはGuardrailsという機能で、PIIの検出・PromptInjection検出・有害コンテンツ検出を入力/出力の両側に挟めます。SaaSとして外部に提供するエージェントでは、`unsafe`カテゴリを出力側で必ず検出してブロックする設定にしておくとリスクを抑えられます。
失敗パターン: ありがちなハマりどころ
1. wrangler.tomlのbindingを忘れる
「AIs.run is not a function」「VECTORIZE is undefined」が出る場合、ほぼ100%`wrangler.toml`にbindingを書き忘れています。本記事の各テンプレで提示したtoml断片を一つずつ確認してください。
2. Durable Objectsのマイグレーションを忘れる
DOクラスを新規追加したら、`[[migrations]]`セクションを必ず追記してから`wrangler deploy`しないとエラーになります。SQLite-backed DOを使う場合は`new_sqlite_classes`、レガシーKV-backed DOなら`new_classes`を指定します。
3. Vectorizeの次元数がEmbeddingモデルと一致しない
`bge-m3`は1024次元、`bge-base-en-v1.5`は768次元、OpenAIの`text-embedding-3-small`は1536次元です。インデックス作成時にこれを間違えると、upsertが全件失敗します。必ず使うモデルの次元数を確認してから`wrangler vectorize create`してください。
4. Agents SDKでmaxStepsを省略する
maxStepsを指定しないとデフォルト値で走りますが、本番では明示的に3〜5に絞ることを推奨します。LLMが「もう一度search_docsしよう」と判断し続けるループに陥ったとき、これが安全装置になります。
5. 全リージョンで動くつもりで時刻処理を書く
Workerはリクエストが来た地点で実行されるため、Date.now()のUTC基準は問題ありませんが、「JSTでの今日の終わり」のような計算をするときはタイムゾーンを明示的に扱う必要があります。Luxon等のライブラリを入れるか、Intl.DateTimeFormatで明示します。
AWS Bedrock / OpenAI Assistantsとの比較
Cloudflareスタックの位置づけを把握するために、他のメジャーなプラットフォームと比較します。詳細な比較はAmazon Bedrock AgentCore完全ガイドとAgent Toolkit for AWSに譲りますが、要点を一表にまとめます。
| 観点 | Cloudflare | AWS Bedrock | OpenAI Assistants |
|---|---|---|---|
| 初期セットアップ | wrangler init一発 | IAM / VPC / Knowledge Base / Agent | API Key一つ |
| エッジ実行 | あり(200+ロケーション) | なし(リージョン単位) | なし |
| モデル | Llama / Mistral / DeepSeek等 | Claude / Llama / Titan等 | GPTファミリーのみ |
| ベクトルDB統合 | Vectorize(同一スタック) | OpenSearch / Aurora | file_search(内部) |
| キャッシュ | AI Gatewayで内蔵 | 別途実装 | 限定的 |
| コスト透過性 | Neurons単位で明瞭 | モデル別に分散 | トークン課金 |
| マルチモデル切替 | Gateway設定だけ | コード書き換え | 不可 |
Cloudflareが特に強いのは「同一アカウントでフルスタックが完結し、エッジで動き、運用ツールが内蔵されている」点です。逆に、Claude Opus 4等の最先端クローズドモデルを最速で触りたい場合はBedrockやAnthropic直APIに優位があります。両者をAI Gateway経由で混在させ、ユースケースごとに振り分けるのが現実的な落としどころです。
ケーススタディ: 社内サポートエージェントを4日で組む
最後に、本記事のテンプレを組み合わせて作る現実的なエージェントの設計例を示します。要件は「社内のConfluenceとPDFマニュアルを意味検索し、Slackで質問に答える」というものです。
1日目: データ収集とR2投入
Confluenceの全ページをエクスポートし、PDF・Markdown・HTMLをR2にアップロードします。Workersのscheduled triggerで毎晩同期する仕組みも合わせて作ります。テンプレ#14の応用です。
2日目: Workflowsでチャンク分割とVectorize投入
テンプレ#13のIngestWorkflowを拡張し、R2のオブジェクトを順次読み込み、チャンク分割→Embedding→Vectorize upsertまでをパイプライン化します。1万ページでも数時間で完了します。
3日目: Durable ObjectsでSlackユーザー別エージェント
Slack Events APIを受けるWorkerを書き、ユーザーIDをキーにDurable Objectを作成します。テンプレ#10の`ConversationAgent`をベースにし、テンプレ#11のAgents SDKでsearch_docsツールを差し込みます。
4日目: AI Gatewayで本番運用化
テンプレ#5-#7のAI Gateway設定を入れ、ユーザー別レート制限、キャッシュ、ログを有効化します。エラー発生時のフォールバック先としてAnthropicのClaude Haikuを設定し、Workers AIが落ちたときの自動切替を仕込みます。
このスケジュールは大げさではありません。同等のものをAWSで組むと、Cognito連携、Bedrock Agentの設定、Lambda関数の作成、OpenSearch Serverlessのインデックス設計、CloudWatch Logs Insightsのダッシュボード設計が必要で、最低でも2週間はかかります。Bedrock AgentCore完全ガイドのほうの手順と見比べると差が明確に分かります。
移行ガイド: OpenAI API中心の構成から乗り換える
既存のOpenAI API中心のアプリをCloudflare側に寄せる場合、いきなり全部を移すのではなく以下の順番で段階的に進めます。
- AI Gateway経由に変える: OpenAI SDKのbaseURLだけ差し替え(テンプレ#6)。コードはほぼそのまま。これだけでログとキャッシュとフォールバックが手に入ります。
- 軽量タスクをWorkers AIに移す: 分類・要約・JSON抽出など、軽いタスクから`@cf/meta/llama-3.1-8b-instruct`等に置き換え。コストが大幅に下がります。
- ベクトルDBをVectorizeに統合: PineconeやWeaviateを使っている場合は、まず新規データの投入先をVectorizeに切り替え、旧データはバックグラウンドで移行。
- ステート管理をDurable Objectsに移す: RedisやDynamoDBで管理していた会話履歴をDOに置き換え。テンプレ#10をそのまま使えます。
- 長時間ジョブをWorkflowsに移す: SQS+Lambda+Step Functionsで組んでいたバッチをWorkflowsに統合。
この順番で進めると、1〜2ステップ進めるたびに「キャッシュが効いた」「コストが下がった」「レイテンシが下がった」と効果が見えるため、組織内の説得もしやすくなります。
FAQ: 開発者からよく聞かれる質問
Q1. Workers AIは日本語LLMで実用品質か?
Llama 3.3 70Bは日本語性能が大きく改善されており、FAQ対応・要約・分類のレベルなら問題なく実用品質です。創作や複雑なコード生成では、Claude Sonnet 4やGPT-4oのほうがまだ優位です。AI Gateway経由でClaudeも併用する構成が現実的です。
Q2. ローカル開発はどうやる?
`npx wrangler dev`でローカル開発サーバーが立ち上がります。Workers AI / AI Gateway / Vectorizeは実APIに繋ぐ`–remote`モードと、ローカルにモックを立てるモードの両方が選べます。CI環境では`–remote`で本番に近い挙動を確認するのがおすすめです。
Q3. 個人情報を扱うエージェントを作れるか?
Cloudflareはエンタープライズ向けにデータレジデンシー機能を提供しており、特定リージョン(例: EU・日本)内に処理を限定できます。個人情報を扱う場合はビジネスプラン以上でのデータレジデンシー設定と、AI GatewayのGuardrailsを必ず併用してください。
Q4. オープンソースLLMをファインチューニングして載せられるか?
Workers AIはLoRAアダプタのアップロードに対応しています。HuggingFaceで学習したLoRAをアップロードし、ベースモデルと組み合わせて推論できます。フルパラメータのカスタムモデルを丸ごとデプロイする用途には現状向きません。
Q5. レスポンスタイムはどのくらい?
Llama 3.3 70B fp8-fastで、東京リージョンからの単発呼び出しでp50が500〜800ms、p95が1.5〜2sといった水準です。AI Gatewayキャッシュにヒットすると数ms〜数十msまで落ちます。WebSocketストリーミング(テンプレ#12)と組み合わせると体感速度はさらに改善します。
まとめ: Cloudflareは「フルスタック・エッジ・統合課金」が武器
Cloudflareスタックの価値を3点に要約すると以下になります。
- フルスタック: 推論(Workers AI)・統制(AI Gateway)・記憶(Vectorize)・ステート(Durable Objects)・ジョブ(Workflows)・ストレージ(R2/D1/KV)が同一アカウントで完結。
- エッジ: 200以上のロケーションで推論が走り、日本ユーザーの体感レイテンシが圧倒的に短い。
- 統合課金: Neurons + リクエスト数 + ストレージという三軸で、コストの予測と切り分けがしやすい。
本記事のテンプレ15本を組み合わせれば、AIエージェントに必要な「会話・検索・ツール呼び出し・長時間ジョブ・監査・コスト管理」を一通り作れるはずです。AWS Bedrockや独自スタックと比較したいときはBedrock AgentCore完全ガイドとAgent Toolkit for AWSを、コスト面の深掘りにはコスト最適化7原則を併読してください。
この記事を読んで導入イメージが固まってきた方へ
UravationではAIエージェント導入の研修・コンサルを行っています。Cloudflare Workers AI / AI Gatewayを用いた本番運用設計、社内RAGの立ち上げ、コスト最適化、既存OpenAI構成からの段階移行まで、現場の事情に合わせて支援します。
