Cloudflare Workers AI完全ガイド2026 - エッジAI推論、LLM、画像生成、テキスト分類、RAG構築
Cloudflare Workers AI完全ガイド2026
Cloudflare Workers AIは、エッジでAI推論を実行できるプラットフォームです。本記事では、LLMから画像生成まで実践的な実装方法を解説します。
目次
- Cloudflare Workers AIとは
- セットアップと基本
- LLM推論
- 画像生成
- テキスト分類と埋め込み
- RAG(Retrieval-Augmented Generation)
- ストリーミングレスポンス
- 実践パターン
Cloudflare Workers AIとは
基本概念
/**
* Cloudflare Workers AIの特徴
*
* 1. エッジでのAI推論
* - レイテンシ: ~50ms(従来のAPI: 500ms+)
* - グローバル展開
*
* 2. 従量課金
* - リクエスト単位の課金
* - 無料枠あり
*
* 3. 豊富なモデル
* - LLM(Llama, Mistral等)
* - 画像生成(Stable Diffusion)
* - テキスト埋め込み
* - 分類・翻訳
*
* 4. TypeScript対応
* - 型安全な実装
*/
// Workers AIの環境変数型定義
interface Env {
AI: Ai
// Vectorize(ベクトルデータベース)
VECTORIZE: VectorizeIndex
// D1(SQLデータベース)
DB: D1Database
// R2(オブジェクトストレージ)
BUCKET: R2Bucket
}
利用可能なモデル
// 2026年現在の主要モデル
// LLM
const LLM_MODELS = {
// Meta Llama
llama3_8b: '@cf/meta/llama-3-8b-instruct',
llama3_70b: '@cf/meta/llama-3-70b-instruct',
// Mistral AI
mistral_7b: '@cf/mistral/mistral-7b-instruct-v0.1',
// その他
gemma_7b: '@cf/google/gemma-7b-it'
} as const
// 画像生成
const IMAGE_MODELS = {
sdxl: '@cf/stabilityai/stable-diffusion-xl-base-1.0',
sdxl_lightning: '@cf/bytedance/stable-diffusion-xl-lightning'
} as const
// テキスト埋め込み
const EMBEDDING_MODELS = {
bge_base: '@cf/baai/bge-base-en-v1.5',
bge_large: '@cf/baai/bge-large-en-v1.5'
} as const
// 分類
const CLASSIFICATION_MODELS = {
distilbert: '@cf/huggingface/distilbert-sst-2-int8'
} as const
セットアップと基本
プロジェクト作成
# Cloudflare Workers プロジェクト作成
npm create cloudflare@latest my-ai-worker
# AIバインディングを有効化
npx wrangler types
# 開発サーバー起動
npm run dev
wrangler.toml設定
name = "my-ai-worker"
main = "src/index.ts"
compatibility_date = "2026-02-05"
# Workers AI バインディング
[ai]
binding = "AI"
# Vectorize バインディング(RAG用)
[[vectorize]]
binding = "VECTORIZE"
index_name = "my-embeddings"
dimensions = 768
# D1 データベース(メタデータ用)
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"
# R2 ストレージ(画像保存用)
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-images"
基本的なWorker
// src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { pathname } = new URL(request.url)
if (pathname === '/chat') {
return handleChat(request, env)
}
if (pathname === '/image') {
return handleImageGeneration(request, env)
}
return new Response('Not Found', { status: 404 })
}
}
async function handleChat(request: Request, env: Env): Promise<Response> {
const { message } = await request.json()
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: message }
]
})
return Response.json(response)
}
LLM推論
基本的なチャット
interface ChatMessage {
role: 'system' | 'user' | 'assistant'
content: string
}
interface ChatRequest {
messages: ChatMessage[]
temperature?: number
max_tokens?: number
}
async function chat(request: ChatRequest, env: Env) {
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: request.messages,
temperature: request.temperature ?? 0.7,
max_tokens: request.max_tokens ?? 512
})
return response
}
// 使用例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const body: ChatRequest = await request.json()
const result = await chat(body, env)
return Response.json({
message: result.response,
usage: {
prompt_tokens: result.prompt_tokens,
completion_tokens: result.completion_tokens
}
})
}
}
システムプロンプトの活用
interface AssistantConfig {
name: string
personality: string
rules: string[]
}
function createSystemPrompt(config: AssistantConfig): string {
return `
あなたは${config.name}です。
性格:
${config.personality}
ルール:
${config.rules.map((rule, i) => `${i + 1}. ${rule}`).join('\n')}
これらの設定に従って、ユーザーの質問に答えてください。
`.trim()
}
async function chatWithAssistant(
userMessage: string,
config: AssistantConfig,
env: Env
) {
const systemPrompt = createSystemPrompt(config)
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
]
})
return response.response
}
// 使用例: カスタマーサポートBot
const supportBotConfig: AssistantConfig = {
name: 'サポートアシスタント',
personality: '親切で丁寧、問題解決志向',
rules: [
'常に敬語を使用する',
'技術用語は分かりやすく説明する',
'解決策を具体的に提示する',
'分からない場合は正直に伝える'
]
}
const answer = await chatWithAssistant(
'パスワードを忘れました',
supportBotConfig,
env
)
会話履歴の管理
interface ConversationStore {
[conversationId: string]: ChatMessage[]
}
class ConversationManager {
constructor(
private kv: KVNamespace,
private maxMessages: number = 10
) {}
async getHistory(conversationId: string): Promise<ChatMessage[]> {
const history = await this.kv.get<ChatMessage[]>(
conversationId,
'json'
)
return history || []
}
async addMessage(
conversationId: string,
message: ChatMessage
): Promise<void> {
const history = await this.getHistory(conversationId)
history.push(message)
// 最大メッセージ数を超えたら古いものを削除
if (history.length > this.maxMessages) {
history.splice(0, history.length - this.maxMessages)
}
await this.kv.put(
conversationId,
JSON.stringify(history),
{ expirationTtl: 3600 } // 1時間で期限切れ
)
}
async chat(
conversationId: string,
userMessage: string,
env: Env
): Promise<string> {
// 履歴を取得
const history = await this.getHistory(conversationId)
// ユーザーメッセージを追加
history.push({ role: 'user', content: userMessage })
// AI推論
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: history
})
// アシスタントの応答を履歴に追加
await this.addMessage(conversationId, {
role: 'assistant',
content: response.response
})
return response.response
}
}
Function Calling
interface FunctionDefinition {
name: string
description: string
parameters: {
type: 'object'
properties: Record<string, any>
required: string[]
}
}
const functions: FunctionDefinition[] = [
{
name: 'get_weather',
description: '指定された都市の天気を取得します',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '都市名(例: Tokyo, London)'
}
},
required: ['city']
}
},
{
name: 'calculate',
description: '数式を計算します',
parameters: {
type: 'object',
properties: {
expression: {
type: 'string',
description: '計算式(例: 2 + 2)'
}
},
required: ['expression']
}
}
]
async function chatWithFunctions(
userMessage: string,
env: Env
): Promise<string> {
// 1. AIに関数定義を含めてリクエスト
const functionsPrompt = `
利用可能な関数:
${JSON.stringify(functions, null, 2)}
ユーザーの質問に答えるために必要な関数があれば、以下の形式でJSON出力してください:
{"function": "関数名", "arguments": {...}}
関数が不要な場合は、直接回答してください。
`
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'system', content: functionsPrompt },
{ role: 'user', content: userMessage }
]
})
// 2. 関数呼び出しを検出
try {
const functionCall = JSON.parse(response.response)
if (functionCall.function) {
// 3. 関数を実行
const result = await executeFunction(
functionCall.function,
functionCall.arguments
)
// 4. 結果をAIに渡して最終回答を生成
const finalResponse = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response.response },
{
role: 'user',
content: `関数の実行結果: ${JSON.stringify(result)}\nこの結果を使って、ユーザーに分かりやすく回答してください。`
}
]
})
return finalResponse.response
}
} catch (e) {
// JSONパースエラー = 直接回答
}
return response.response
}
async function executeFunction(name: string, args: any): Promise<any> {
switch (name) {
case 'get_weather':
return {
city: args.city,
temperature: 22,
condition: 'Sunny'
}
case 'calculate':
return {
result: eval(args.expression)
}
default:
throw new Error(`Unknown function: ${name}`)
}
}
画像生成
Stable Diffusion XL
async function generateImage(
prompt: string,
env: Env
): Promise<Response> {
const response = await env.AI.run(
'@cf/stabilityai/stable-diffusion-xl-base-1.0',
{
prompt,
num_steps: 20,
guidance: 7.5,
width: 1024,
height: 1024
}
)
return new Response(response, {
headers: {
'Content-Type': 'image/png'
}
})
}
// 使用例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { searchParams } = new URL(request.url)
const prompt = searchParams.get('prompt') || 'a beautiful sunset'
return generateImage(prompt, env)
}
}
ネガティブプロンプト
interface ImageGenerationOptions {
prompt: string
negativePrompt?: string
steps?: number
guidance?: number
width?: number
height?: number
seed?: number
}
async function generateImageWithOptions(
options: ImageGenerationOptions,
env: Env
): Promise<Uint8Array> {
const {
prompt,
negativePrompt = 'blurry, low quality, distorted',
steps = 30,
guidance = 7.5,
width = 1024,
height = 1024,
seed
} = options
const image = await env.AI.run(
'@cf/stabilityai/stable-diffusion-xl-base-1.0',
{
prompt,
negative_prompt: negativePrompt,
num_steps: steps,
guidance,
width,
height,
...(seed && { seed })
}
)
return image
}
// プロンプトテンプレート
const PROMPT_TEMPLATES = {
portrait: (subject: string) =>
`professional portrait photo of ${subject}, high quality, detailed, 8k, studio lighting`,
landscape: (scene: string) =>
`beautiful landscape photo of ${scene}, golden hour, cinematic, highly detailed`,
illustration: (subject: string) =>
`digital illustration of ${subject}, vibrant colors, detailed, artstation trending`
}
// 使用例
const image = await generateImageWithOptions({
prompt: PROMPT_TEMPLATES.portrait('a young woman'),
negativePrompt: 'blurry, distorted, low quality, bad anatomy',
steps: 40,
guidance: 8.0
}, env)
R2への保存
async function generateAndSaveImage(
prompt: string,
env: Env
): Promise<string> {
// 画像生成
const image = await env.AI.run(
'@cf/stabilityai/stable-diffusion-xl-base-1.0',
{ prompt }
)
// R2に保存
const imageId = crypto.randomUUID()
const key = `images/${imageId}.png`
await env.BUCKET.put(key, image, {
httpMetadata: {
contentType: 'image/png'
},
customMetadata: {
prompt,
createdAt: new Date().toISOString()
}
})
// 公開URLを返す
return `https://your-domain.com/${key}`
}
// 画像取得エンドポイント
async function getImage(imageId: string, env: Env): Promise<Response> {
const key = `images/${imageId}.png`
const object = await env.BUCKET.get(key)
if (!object) {
return new Response('Not Found', { status: 404 })
}
return new Response(object.body, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000'
}
})
}
テキスト分類と埋め込み
テキスト埋め込み
async function generateEmbedding(
text: string,
env: Env
): Promise<number[]> {
const response = await env.AI.run(
'@cf/baai/bge-base-en-v1.5',
{ text }
)
return response.data[0]
}
// バッチ処理
async function generateEmbeddings(
texts: string[],
env: Env
): Promise<number[][]> {
const embeddings = await Promise.all(
texts.map(text => generateEmbedding(text, env))
)
return embeddings
}
ベクトル検索
interface Document {
id: string
text: string
metadata?: Record<string, any>
}
async function indexDocument(
doc: Document,
env: Env
): Promise<void> {
// テキストを埋め込みベクトルに変換
const embedding = await generateEmbedding(doc.text, env)
// Vectorizeに保存
await env.VECTORIZE.upsert([
{
id: doc.id,
values: embedding,
metadata: {
text: doc.text,
...doc.metadata
}
}
])
}
async function searchSimilarDocuments(
query: string,
topK: number,
env: Env
): Promise<Document[]> {
// クエリを埋め込みベクトルに変換
const queryEmbedding = await generateEmbedding(query, env)
// 類似ベクトルを検索
const results = await env.VECTORIZE.query(queryEmbedding, {
topK,
returnValues: true,
returnMetadata: 'all'
})
return results.matches.map(match => ({
id: match.id,
text: match.metadata.text as string,
metadata: match.metadata,
score: match.score
}))
}
テキスト分類
interface ClassificationResult {
label: string
score: number
}
async function classifyText(
text: string,
env: Env
): Promise<ClassificationResult[]> {
const response = await env.AI.run(
'@cf/huggingface/distilbert-sst-2-int8',
{ text }
)
return response
}
// 感情分析
async function analyzeSentiment(
text: string,
env: Env
): Promise<'positive' | 'negative'> {
const results = await classifyText(text, env)
const positive = results.find(r => r.label === 'POSITIVE')
const negative = results.find(r => r.label === 'NEGATIVE')
return (positive?.score || 0) > (negative?.score || 0)
? 'positive'
: 'negative'
}
RAG(Retrieval-Augmented Generation)
RAGシステムの構築
interface RAGSystem {
indexDocument(doc: Document): Promise<void>
search(query: string): Promise<string>
}
class CloudflareRAG implements RAGSystem {
constructor(private env: Env) {}
async indexDocument(doc: Document): Promise<void> {
// 1. ドキュメントをチャンクに分割
const chunks = this.splitIntoChunks(doc.text, 512)
// 2. 各チャンクを埋め込みベクトルに変換してインデックス
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i]
const embedding = await generateEmbedding(chunk, this.env)
await this.env.VECTORIZE.upsert([
{
id: `${doc.id}-chunk-${i}`,
values: embedding,
metadata: {
docId: doc.id,
chunkIndex: i,
text: chunk,
...doc.metadata
}
}
])
}
// 3. メタデータをD1に保存
await this.env.DB.prepare(
'INSERT INTO documents (id, title, created_at) VALUES (?, ?, ?)'
)
.bind(doc.id, doc.metadata?.title || '', new Date().toISOString())
.run()
}
async search(query: string): Promise<string> {
// 1. クエリから関連ドキュメントを検索
const relevantDocs = await searchSimilarDocuments(query, 5, this.env)
// 2. コンテキストを構築
const context = relevantDocs
.map((doc, i) => `[${i + 1}] ${doc.text}`)
.join('\n\n')
// 3. RAGプロンプト
const prompt = `
以下のコンテキストを参考にして、質問に答えてください。
コンテキスト:
${context}
質問: ${query}
回答:
`.trim()
// 4. LLMで回答生成
const response = await this.env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: prompt }
]
})
return response.response
}
private splitIntoChunks(text: string, chunkSize: number): string[] {
const words = text.split(' ')
const chunks: string[] = []
for (let i = 0; i < words.length; i += chunkSize) {
chunks.push(words.slice(i, i + chunkSize).join(' '))
}
return chunks
}
}
// 使用例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const rag = new CloudflareRAG(env)
const { pathname } = new URL(request.url)
if (pathname === '/index') {
const doc: Document = await request.json()
await rag.indexDocument(doc)
return Response.json({ success: true })
}
if (pathname === '/search') {
const { query } = await request.json()
const answer = await rag.search(query)
return Response.json({ answer })
}
return new Response('Not Found', { status: 404 })
}
}
ストリーミングレスポンス
Server-Sent Events
async function streamChat(
message: string,
env: Env
): Promise<Response> {
const { readable, writable } = new TransformStream()
const writer = writable.getWriter()
const encoder = new TextEncoder()
// バックグラウンドでストリーミング
(async () => {
try {
const stream = await env.AI.run(
'@cf/meta/llama-3-8b-instruct',
{
messages: [{ role: 'user', content: message }],
stream: true
}
)
for await (const chunk of stream) {
const data = `data: ${JSON.stringify(chunk)}\n\n`
await writer.write(encoder.encode(data))
}
await writer.write(encoder.encode('data: [DONE]\n\n'))
} catch (error) {
await writer.write(
encoder.encode(`data: ${JSON.stringify({ error: error.message })}\n\n`)
)
} finally {
await writer.close()
}
})()
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
})
}
// クライアント側
const eventSource = new EventSource('/chat?message=Hello')
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close()
return
}
const chunk = JSON.parse(event.data)
console.log(chunk.response)
}
まとめ
Cloudflare Workers AIは、エッジでのAI推論を可能にする革新的なプラットフォームです。
主要ポイント:
- 低レイテンシ: エッジでの推論により50ms以下
- 豊富なモデル: LLM、画像生成、埋め込み等
- 統合されたエコシステム: Vectorize、D1、R2との連携
- コスト効率: 従量課金、無料枠あり
- TypeScript対応: 型安全な実装
2026年のベストプラクティス:
- RAGでより正確な回答を実現
- ストリーミングでUX向上
- Vectorizeで高速なベクトル検索
- R2で生成コンテンツを永続化
- 適切なプロンプトエンジニアリング
Cloudflare Workers AIを活用して、次世代のAIアプリケーションを構築しましょう。