Cloudflare ハンズオン教科書|2026-08 版
第 11 章

Workers AI — エッジで動く AI 推論

GPU 推論をバインディング 1 つで。LLM・画像・埋め込みを試す

この章で学ぶこと

Workers AI の仕組み — サーバーレス GPU とモデルカタログ

Workers AI は、Cloudflare のグローバルネットワーク上に配備された GPU で機械学習モデルを実行するサービスです。GPU の調達・スケーリング・保守はすべて Cloudflare 側が担い、開発者はモデル ID と入力を渡すだけで推論結果を受け取れます。使った分だけ支払う従量課金で、Free プランでも無料枠の範囲で使い始められます。KV や R2 と同じく「バインディングを 1 つ足せばインフラが手に入る」体験が、GPU 推論にもそのまま当てはまるのがこの章の主題です。

呼び出し方法は 3 つあります。

方法概要向いている場面
Workers バインディングWorker のコードから env.AI.run() で直接呼ぶアプリに AI 機能を組み込む(本章の主役)
REST APIAPI トークンを付けて api.cloudflare.com の HTTPS エンドポイントを叩くCloudflare 外のサーバーやスクリプトから使う
ダッシュボードテンプレート(LLM Chat App)から AI アプリを作成し、ブラウザ上で編集・デプロイするコードを書かずにまず動かす
Cloudflare の外 Cloudflare ネットワーク内 Worker デプロイ済み Workers AI サーバーレス GPU GPU の管理は不要 env.AI.run() トークン不要 外部サーバー・CI ローカル / CI / 自社サーバー REST API を直接呼ぶ API トークンが必要
図 11-1 どちらの経路も行き着く先は同じ Workers AI で、違うのは呼び出し元が破線のどちら側にいるかだけです。内側の Worker はバインディングが認証を代行するのでトークンをコードに書かずに済み、境界を越える REST 経路だけが API トークンを必要とします。表のダッシュボードは第 3 の実行系ではなく、右側の Worker を生成する入口なのでこの図には現れません。

実行できるモデルはモデルカタログとして整理されています。50 を超えるオープンソースモデルが提供されており(2026-08 時点のカタログ表示では 81 モデル)、タスクタイプごとに分類されています。

タスクタイプ代表モデル用途
Text Generation@cf/meta/llama-3.1-8b-instruct-fast、@cf/openai/gpt-oss-120bチャット・文章生成・要約
Text Embeddings@cf/baai/bge-m3、@cf/pfnet/plamo-embedding-1b意味検索・RAG・分類
Text-to-Image@cf/black-forest-labs/flux-1-schnell画像生成
Automatic Speech Recognition@cf/openai/whisper-large-v3-turbo音声の文字起こし・翻訳
Text-to-Speech@cf/deepgram/aura-1テキスト読み上げ
Translation@cf/meta/m2m100-1.2b多言語翻訳

モデル ID は @cf/(Cloudflare ホスト)や @hf/ などの接頭辞 + 提供元 + モデル名で構成されます。各モデルの詳細ページには、コンテキストウィンドウ(入出力トークン数の上限)・単価・入出力パラメータの一覧が載っているので、採用前に必ず確認してください。

AI バインディングと env.AI.run()

Worker から Workers AI を使うには、AI バインディングを設定します。wrangler.jsonc に次の 1 ブロックを足すだけです(wrangler.toml なら [ai] セクションに binding = "AI")。

{
  "ai": {
    "binding": "AI" // Worker 内で env.AI として利用可能になる
  }
}

バインディングの実体は env.AI オブジェクトで、中心となるメソッドは env.AI.run() です。第 1 引数にモデル ID、第 2 引数に入力オブジェクトを渡します。

const answer = await env.AI.run("@cf/meta/llama-3.1-8b-instruct-fast", {
  prompt: "What is the origin of the phrase 'Hello, World'",
});

テキスト生成モデルへの入力には 2 つの形式があります。上の prompt は「unscoped プロンプト」で、単発の質問を投げる最短の書き方です。推奨されるのは「scoped プロンプト」、つまり messages 配列でロール付きの会話を渡す形式です。モデルごとに異なるチャットテンプレートの差異は Workers AI が吸収してくれます。

const answer = await env.AI.run("@cf/meta/llama-3.1-8b-instruct-fast", {
  messages: [
    { role: "system", content: "you are a professional computer science assistant" },
    { role: "user", content: "what is WASM?" },
    { role: "assistant", content: "WASM (WebAssembly) is a binary instruction format..." },
    { role: "user", content: "does Python compile to WASM?" },
  ],
});

テキスト生成モデルの主なオプションパラメータは次のとおりです(@cf/meta/llama-3.1-8b-instruct-fast の例、2026-08 時点)。

パラメータ既定値説明
max_tokens256生成する最大トークン数
temperature0.6出力のランダム性(0〜5、高いほど多様)
top_p / top_k—候補トークンの絞り込みによる多様性の調整
seed—再現性のための乱数シード
streamfalsetrue にすると結果を SSE で逐次返す

stream: true を指定すると、env.AI.run() は完成した文字列の代わりにストリームを返します。これをそのまま Response の body にして content-type: text/event-stream を付ければ、生成されたそばからクライアントに届くチャット UI 的な体験を実現できます。LLM の全文生成を待ってから返すとユーザーの体感待ち時間が長くなるため、対話的な用途ではストリーミングが定石です。実装はハンズオン 11-2 で行います。

→ 時間の経過(どちらも総生成時間は同じ) stream なし LLM が生成中(クライアントには何も届かない) 全文 ここで初めて届く stream: true data: data: data: data: data: data: 届いた分から順に画面へ描画できる 最初の断片がここで届く
図 11-2 2 本の帯は同じ長さ、つまり総生成時間は変わりません。変わるのは最初の 1 文字がクライアントに届く時刻で、上段は帯の右端まで画面が空のまま、下段は帯の最初の区切りから描画が始まります。stream: true が縮めるのは処理時間ではなく体感待ち時間です。

テキスト以外のタスク — 埋め込み・画像生成・音声認識

テキスト埋め込み

テキスト埋め込み(text embeddings)は、文章を数値ベクトルに変換するタスクです。意味が近い文章はベクトル空間上でも近くに配置されるため、キーワード一致に頼らない意味検索・レコメンド・RAG の土台になります。多言語対応の @cf/baai/bge-m3(コンテキストウィンドウ 60,000 トークン)や、Preferred Networks 製の日本語特化モデル @cf/pfnet/plamo-embedding-1b が利用できます。入力は text キーに文字列または文字列の配列を渡し、結果はレスポンスの data 配列(文章ごとのベクトル)として返ります。

run() への入力 質問文 安く保存したい 文書 3 件 R2 / D1 / Workers AI 1 回の run() で まとめて渡す @cf/baai/bge-m3 文 → 数値ベクトル 同じ空間へ コサイン類似度 質問 × 各文書 並べ替え スコア降順 R2 の説明 → 0.62 語の一致なしで 1 位
図 11-3 効いているのは「同じモデルに通すこと」です。質問も文書も同じベクトル空間に置いてはじめてコサイン類似度が比較可能になり、「安く保存したい」と「エグレス無料のオブジェクトストレージ」のように単語が 1 つも重ならない組み合わせでも上位に並びます。1 回の run() にまとめるのは往復を減らす工夫であって、比較が成立する条件ではありません。

画像生成

Text-to-Image モデルはプロンプトから画像を生成します。@cf/black-forest-labs/flux-1-schnell は 120 億パラメータの高速モデルで、prompt(最大 2,048 文字)と steps(拡散ステップ数、既定 4・最大 8)を受け取り、Base64 エンコードされた画像を返します。

const response = await env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
  prompt: "a cyberpunk lizard",
});
// response.image は Base64 文字列。data URI としてそのまま <img src> に使える
const dataURI = `data:image/jpeg;charset=utf-8;base64,${response.image}`;

音声認識

Automatic Speech Recognition(ASR)タスクでは @cf/openai/whisper-large-v3-turbo が使えます。音声データを渡すと文字起こし結果(text)に加えて、字幕にそのまま使える WebVTT 形式(vtt)や単語数も返ります。task パラメータで文字起こし(transcribe)と翻訳(translate)を切り替えられ、language で音声の言語を指定できます。料金は音声 1 分あたり $0.0005 です(2026-08 時点)。

埋め込みをベクトルデータベース Vectorize に保存して LLM と組み合わせると、自前ドキュメントに基づいて回答する RAG が構築できます(公式チュートリアル)。これは次章のテーマです。

ニューロン課金と無料枠

Workers AI の課金単位はニューロン(Neuron)です。ニューロンはモデルの種類をまたいで AI の出力を測る Cloudflare 独自の単位で、リクエストの処理に必要な GPU 計算量を表します。ドキュメント上は「1M 入力トークンあたり $◯◯」のようにモデルごとのトークン単価が併記されていますが、裏側の請求はニューロンで行われます(2026-08 時点、以下同)。

1 日のニューロン消費(00:00 UTC にリセット) 無料枠 10,000 ニューロン/日 超過分 10,000 ニューロン到達 Free プラン Workers Paid エラー 3036 HTTP 429 翌 00:00 UTC まで停止 課金して継続 そのまま成功 $0.011 / 1,000 ニューロン
図 11-4 無料枠は「安く使える範囲」ではなく「そこで挙動が切り替わる境界」です。同じ 10,001 個目のニューロンでも、Free プランでは 3036 / HTTP 429 で実行そのものが止まり、Workers Paid では止まらず課金へ移ります。本番運用でどちらが怖いかを踏まえてプランを選んでください。

モデル単価の例を挙げます。同じ「テキスト生成」でもモデル規模で 1 桁以上変わることが分かります。

モデル入力単価出力単価
@cf/meta/llama-3.2-1b-instruct$0.027 / 1M トークン$0.201 / 1M トークン
@cf/meta/llama-3.1-8b-instruct-fp8-fast$0.045 / 1M トークン$0.384 / 1M トークン
@cf/meta/llama-3.3-70b-instruct-fp8-fast$0.293 / 1M トークン$2.253 / 1M トークン
@cf/baai/bge-m3(埋め込み)$0.012 / 1M トークン—
@cf/openai/whisper-large-v3-turbo(ASR)$0.0005 / 音声 1 分—

使用量とは別にレート制限もあります。既定はタスクタイプ単位で、Text Generation は毎分 300 リクエスト、Text Embeddings は毎分 3,000 リクエスト、ASR と Text-to-Image は毎分 720 リクエストです(モデルごとの個別制限もあり)。ニューロンの消費量はダッシュボードの Workers AI ページで確認できます。

ハンズオン 11-1|日本語で答えるチャット API を作る

AI バインディングを設定した Worker で、POST された質問に日本語で答えるチャット API を作ります。所要時間は約 15 分です。

無料枠の消費に注意:Workers AI は wrangler dev によるローカル開発でも Cloudflare アカウント上の GPU でモデルを実行するため、無料枠(10,000 ニューロン/日)を消費します。この章のハンズオン程度なら無料枠内に収まりますが、ループでの連続実行は避けてください。

  1. 新しい Worker プロジェクトを作成する。テンプレートは Hello World example → Worker only → TypeScript を選び、デプロイの質問には No と答える
npm create cloudflare@latest -- workers-ai-chat
cd workers-ai-chat
  1. wrangler.jsonc の末尾に AI バインディングを追加する(前節のとおり "ai": { "binding": "AI" })
  2. src/index.ts を次の内容に置き換える。system ロールで「日本語で答える」制約を与え、既定値 256 では日本語の回答が途切れやすいので max_tokens を 512 に引き上げている
export interface Env {
  AI: Ai;
}

export default {
  async fetch(request, env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response('POST で JSON {"message": "..."} を送ってください', {
        status: 405,
      });
    }
    const { message } = (await request.json()) as { message: string };

    const answer = await env.AI.run("@cf/meta/llama-3.1-8b-instruct-fast", {
      messages: [
        {
          role: "system",
          content:
            "あなたは Cloudflare に詳しいアシスタントです。必ず日本語で、簡潔に答えてください。",
        },
        { role: "user", content: message },
      ],
      max_tokens: 512,
    });

    return Response.json(answer);
  },
} satisfies ExportedHandler<Env>;
  1. npx wrangler dev で起動し(初回はログインを求められる)、別ターミナルから質問を送る
curl http://localhost:8787 -X POST \
  -H "Content-Type: application/json" \
  -d '{"message": "Cloudflare Workers とは何ですか?"}'

# 期待される出力(response の文面は実行ごとに変わる)
{"response":"Cloudflare Workers は、Cloudflare のグローバルネットワーク上で
JavaScript や TypeScript のコードを実行できるサーバーレス実行環境です。..."}

日本語の回答が JSON で返れば成功です。npx wrangler deploy すれば、この API はそのまま workers.dev の URL で世界中から呼べるようになります。

ハンズオン 11-2|ストリーミング応答(SSE)に対応する

11-1 のチャット API を、生成結果を逐次返すストリーミング型に改造します。所要時間は約 10 分です。

  1. src/index.ts の env.AI.run() 呼び出しと return 部分を次のように変更する。stream: true を足し、返り値のストリームをそのまま Response の body にする
    const stream = await env.AI.run("@cf/meta/llama-3.1-8b-instruct-fast", {
      messages: [
        {
          role: "system",
          content:
            "あなたは Cloudflare に詳しいアシスタントです。必ず日本語で、簡潔に答えてください。",
        },
        { role: "user", content: message },
      ],
      max_tokens: 512,
      stream: true,
    });

    return new Response(stream, {
      headers: { "content-type": "text/event-stream" },
    });
  1. npx wrangler dev を起動したまま、curl の -N(バッファリング無効)オプションを付けて呼び出す
curl -N http://localhost:8787 -X POST \
  -H "Content-Type: application/json" \
  -d '{"message": "エッジコンピューティングの利点を 3 つ教えてください"}'

# 期待される出力:data: で始まる SSE イベントが少しずつ流れてくる
data: {...}   ← 生成されたテキストの断片
data: {...}
...(生成が終わるとストリームが閉じる)

全文を待たずに最初の 1 文字目が届き始めることを確認してください。ブラウザ側では EventSource や fetch のリーダーで受け取れば、ChatGPT 風の「文字が流れてくる」UI になります。

ハンズオン 11-3|テキスト埋め込みで意味検索を試す

多言語埋め込みモデル @cf/baai/bge-m3 で日本語文書のベクトルを作り、質問文とのコサイン類似度で並べ替える最小の意味検索を作ります。所要時間は約 15 分です。

  1. 新しい Worker プロジェクト workers-ai-search を 11-1 と同じ手順で作成し、AI バインディングを設定する
  2. src/index.ts を次の内容に置き換える。質問と文書をまとめて 1 回の run() でベクトル化しているのがポイント
export interface Env {
  AI: Ai;
}

const docs = [
  "Workers AI はサーバーレス GPU で AI モデルを実行するサービスです",
  "R2 はエグレス無料のオブジェクトストレージです",
  "D1 は SQLite ベースのサーバーレスデータベースです",
];

function cosine(a: number[], b: number[]): number {
  let dot = 0, na = 0, nb = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    na += a[i] * a[i];
    nb += b[i] * b[i];
  }
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
}

export default {
  async fetch(request, env): Promise<Response> {
    const q =
      new URL(request.url).searchParams.get("q") ?? "GPU で推論したい";
    const { data } = (await env.AI.run("@cf/baai/bge-m3", {
      text: [q, ...docs],
    })) as { data: number[][] };

    const [qv, ...dv] = data;
    const ranked = docs
      .map((text, i) => ({ text, score: cosine(qv, dv[i]) }))
      .sort((x, y) => y.score - x.score);
    return Response.json({ query: q, results: ranked });
  },
} satisfies ExportedHandler<Env>;
  1. npx wrangler dev で起動し、クエリを変えながら呼び出す
curl "http://localhost:8787/?q=画像などのファイルを安く保存したい"

# 期待される出力(score の値は一例):R2 の文書が最上位に来る
{"query":"画像などのファイルを安く保存したい","results":[
  {"text":"R2 はエグレス無料のオブジェクトストレージです","score":0.62},
  ...]}

キーワードが一致していなくても意味の近い文書が上位に来ることを確認してください。文書数が増えたら、この総当たり計算を次章の Vectorize に置き換えます。

ハンズオン 11-4|REST API から Workers AI を呼ぶ

Worker を介さず、curl から REST API で直接モデルを実行します。CI やローカルスクリプトから使うときの形です。所要時間は約 10 分です。

  1. ダッシュボードの Workers AI ページを開き、Use REST API を選択する
  2. Create a Workers AI API Token からトークンを作成してコピーする(自分でカスタムトークンを作る場合は Workers AI - Read と Workers AI - Edit の両権限が必要)
  3. 同じ画面の Get Account ID からアカウント ID をコピーする
  4. 取得した 2 つの値でエンドポイント /accounts/{account_id}/ai/run/{model} を呼び出す
curl https://api.cloudflare.com/client/v4/accounts/<YOUR_ACCOUNT_ID>/ai/run/@cf/meta/llama-3.1-8b-instruct-fast \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -d '{ "messages": [{ "role": "user", "content": "Cloudflare Workers を一言で説明して" }] }'

# 期待される出力
{
  "result": { "response": "Cloudflare Workers は..." },
  "success": true,
  "errors": [],
  "messages": []
}

<YOUR_ACCOUNT_ID> と <YOUR_API_TOKEN> は手順 2〜3 でコピーした値に置き換えます。応答本体が result に包まれるのがバインディング経由との違いです。なお Workers AI は OpenAI 互換エンドポイント(.../ai/v1/chat/completions と .../ai/v1/embeddings)も提供しており、OpenAI SDK の baseURL を差し替えるだけで既存コードから利用できます。

つまずきポイント

ローカル開発でも課金される — wrangler dev のローカルモードでも、モデルの実行は常に Cloudflare アカウント上の GPU にアクセスします。ニューロンの無料枠を消費し、レート制限にもカウントされるので、開発中のループ実行やベンチマークには注意してください。

日本語の回答が途中で切れる — テキスト生成の max_tokens は既定 256 です。長めの回答を求めるなら max_tokens: 512 のように明示的に増やしてください。上限はモデルのコンテキストウィンドウ(各モデルページに記載。llama-3.1-8b-instruct-fast は 128,000 トークン)を超えられません。

429 エラーが返ってくる — 無料枠 10,000 ニューロン/日を使い切ると、エラーコード 3036(HTTP 429)で失敗します。00:00 UTC のリセットを待つか Workers Paid プランへアップグレードしてください。使用量に余裕があるのに 429 になる場合は、タスクタイプごとのレート制限(Text Generation は毎分 300 リクエスト等)に当たっている可能性があります。

「No such model」エラーとモデルの廃止 — モデル ID は @cf/meta/... の接頭辞まで含めて正確に書かないとエラーコード 5007(No such model)になります。またカタログのモデルには Deprecated(廃止予定)マークが付くことがあり、たとえば @cf/meta/llama-3.1-8b-instruct は 2026-05-30 付で Deprecated 扱いです。採用前にモデルページで状態を確認し、古いチュートリアルのモデル ID を鵜呑みにしないでください。

理解度チェック

Q1. Worker から Workers AI を使うために wrangler.jsonc に追加する設定は?

"ai": { "binding": "AI" } です。これで Worker 内から env.AI(binding に指定した名前)が使えるようになり、env.AI.run(モデルID, 入力) で推論を実行できます。

Q2. scoped プロンプト(messages 配列)の system ロールは何のために使う?

AI の人格・ルール・振る舞いを定義するために使います。「必ず日本語で答える」のような制約は user メッセージではなく system メッセージに書くのが定石です。user は実際の質問、assistant は AI 側の過去の応答を表します。

Q3. LLM の応答をストリーミングで返すために必要な 2 つの変更は?

env.AI.run() の入力に stream: true を指定することと、返ってきたストリームを body にした Response に content-type: text/event-stream ヘッダーを付けて返すことです。結果は SSE(Server-Sent Events)として逐次配信されます。

Q4. Workers AI の無料枠と、超過した場合の挙動は?

1 日あたり 10,000 ニューロンまで無料で、毎日 00:00 UTC にリセットされます(2026-08 時点)。Free プランで超過するとエラー(コード 3036、HTTP 429)になり、Workers Paid プランなら超過分が $0.011 / 1,000 ニューロンで課金されます。

Q5. Cloudflare の外から Workers AI のモデルを実行する REST API エンドポイントの形式は?

https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/{model} に、API トークンを Authorization: Bearer ヘッダーで付けて POST します。応答は result キーに包まれて返ります。OpenAI 互換の /ai/v1/chat/completions エンドポイントも利用できます。

まとめと次章へ

次章では、この章で作った埋め込みをベクトルデータベース Vectorize に保存し、自前のドキュメントに基づいて回答する RAG パイプラインを構築します。