この記事はCloudflare Advent Calendar 2024 2日目の記事です🎅
Cloudflareの魅力を知ってもらえるハンズオンを作りました!初めての方で迷わないよう丁寧に解説していきます。Workers AI、楽しいです✨
1. はじめに
この記事は、Cloudflare WorkersとWorkers AIを使って、AI画像生成アプリをサーバーレスで構築するハンズオンです。
1.1 AI画像生成アプリの完成イメージ
日本語プロンプトを英語に翻訳し、英語を使って高品質なAI画像を生成するアプリです。
1.2 ハンズオンで使用する技術
-
Cloudflare Workers
サーバーを持たずにコードを直接エッジネットワーク上で実行できるサーバーレスプラットフォームです。グローバルなエッジロケーションからリクエストを処理するため、高速でスケーラブルなアプリケーションを構築できます -
Workers AI
最新のAIモデルを簡単に統合できるCloudflareのサービスです。テキスト生成・画像生成だけでなく、画像からのオブジェクト抽出や音声処理など様々なAIを簡単に使えます。無料枠があるので気軽に試せます
1.3 ハンズオンの特徴
1. 無料で試せる
Cloudflare WorkersやWorkers AIの無料枠を利用して、コストをかけずに実践的なアプリケーションを構築できます。
2. CLIを使ってサクサク開発
Cloudflareが提供するCLIを活用して、効率的に開発を進められます。
Cloudflare Workersはブラウザーだけで開発することもできますが、使える機能に限りがあります。CLIを使えば、静的コンテンツを無料で配信できるStatic Assetsも使えるようになります。
3. AI技術の基礎と応用を学べる
AIモデルを単体で使う方法だけでなく、英語に翻訳してから画像を生成する、といったAIを組み合わせて使う方法も体験できます。
1.4 ハンズオンのコード
この記事で紹介するソースコードは、GitHubリポジトリにも公開しています。ソースコードをまとめて確認したい場合は、以下のリンクにアクセスしてください。
2. Cloudflare WorkersでHello World
まずはCloudflare Workersを使った開発プロセスを学ぶため、Hello World!
と表示する単純なWebアプリを作っていきます。
以下の手順に従って、CLIでWorkersを作っていきます。
2.1 事前準備
-
- クレジットカードは不要です
-
Node.jsをインストールします
2.2 新しいWorkerプロジェクトを作る
ターミナルで次のコマンドを実行します。
プロジェクト名は好きなものを指定してください。この記事ではai-image-handson
とします。
npm create cloudflare@latest -- ai-image-handson
--
はコマンドと引数を分ける区切りです。
このコマンドはcreate-cloudflare
を実行します。その引数が--
の後のai-image-handson
です。
今回は最もシンプルなWorkerを作ります。次の選択肢を選んでください。
- What would you like to start with?
Hello World example
- Which template would you like to use?
Hello World Worker
- Which language do you want to use?
JavaScript
- Do you want to use git for version control?
Yes
- Do you want to deploy your application?
No
これで新しいプロジェクトを作成できました。プロジェクトのディレクトリーに移動しておきます。
cd ai-image-handson
2.3 ローカルで動作を確認する
次のコマンドでローカルの開発用サーバーを起動します。
npx wrangler dev
npx
を使うと、まだインストールされていないパッケージをその場で実行できます。
パッケージをグローバルにインストールしないで実行するので、システムを汚さずに済みます。
ターミナルに表示されたURLhttp://localhost:8787
をWebブラウザーで開くと、Hello World!
と表示されているのが確認できます。
2.4 コードを修正する
開発用サーバーで実行されているのはsrc
ディレクトリーにあるindex.js
です。
export default {
async fetch(request, env, ctx) {
return new Response('Hello World!');
},
};
表示される文字列をHello Worker!
に変更してみましょう。
export default {
async fetch(request, env, ctx) {
- return new Response('Hello World!');
+ return new Response('Hello Worker!');
},
};
Webブラウザーをリロードすると、文字列がHello Worker!
に変化しました。
2.5 デプロイする
ローカルで動作を確認したら、https://ai-image-handson.<あなたのサブドメイン>.workers.dev
にデプロイします。
ターミナルで次のコマンドを実行します。
npx wrangler deply
なお、デプロイはCLIからだけでなく、Cloudflare Dashboardからも管理できます。
3. AI画像生成アプリを作る
Workers AIを使って、AI画像生成アプリを作っていきましょう。
翻訳機能、画像生成機能、の順番で実装します。
3.1 利用するモデルを選ぶ
Workers AIは様々なモデルを提供しています。
AIの種類(生成する情報)によって、価格が異なります1。日次でリセットされる無料枠があるので、AIの機能を気軽に試すことができます。
今回のAI画像生成アプリでは、次の2つのモデルを使います。
- テキスト生成AI(翻訳)
- llama-3.2-3b-instruct
- マルチリンガルなモデルです。簡単な日本語であれば問題なく英語に翻訳できます(ただ、日本語だけで会話を続けるのは難しかったです)
- 画像生成AI
- flux-1-schnell
- 高品質の2画像を高速に3生成します。出力した画像は商用利用可能です(Hugging Face)
- なお、執筆時点(2024年11月)では同じプロンプトに対して常に同じ画像が生成されます(他の画像生成AIでは同じプロンプトでも異なる画像が生成されます)。Seed値の指定はできません
3.2 翻訳の画面を作る
まずは翻訳機能の画面から作っていきます。
3.2.1 フロントエンドのindex.html
を作成する
ルートディレクトリーにpublic
ディレクトリーを作成します。その配下にindex.html
ファイルを追加して、以下をそれぞれ作成します。
- 日本語を入力する
textarea
- 翻訳する
button
- 翻訳結果を表示する
textarea
なお、見栄えをよくするためnew.cssを使って画面スタイルを整えています4。
`public/index.html`を展開する
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI画像生成ハンズオン</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.3/new.min.css">
</head>
<body>
<h3>何をどんな風に描きたいですか?</h3>
<p>多彩な表現を楽しもう!ポップアート風、油絵風、水彩画風、サイバーパンク風など</p>
<textarea id="prompt" placeholder="例: かわいい猫のイラスト" style="width: 100%"></textarea>
<button id="translate-btn" onclick="translateText()">
英語に翻訳
</button>
<p id="translate-error-message" style="color: red;"></p>
<h3 for="translated-prompt">翻訳結果</h3>
<textarea id="translated-prompt" placeholder="ここに英語の翻訳結果が表示されます"
style="width: 100%; field-sizing: content;"></textarea>
<script></script>
</body>
</html>
3.2.2 バックエンドのindex.js
を修正する
src/index.js
は以下のコードですべて上書きし、Static Assetsを返却するように変更します。
`src/index.js`を展開する
export default {
async fetch(request, env, ctx) {
return env.ASSETS.fetch(request);
},
};
3.2.3 設定wrangler.toml
でStatic Assetsを有効にする
Cloudflare Workersでは静的コンテンツ(HTML、CSS、JavaScriptなど)を無料で配信できます。この機能は非常に便利ですが、ブラウザーベースの開発環境ではwrangler.toml
の設定ができないため利用できません。
CLIを使うことで、wrangler.toml
を簡単に設定し、Static Assetsの無料配信機能を活用することが可能です。これにより、アプリの構築が効率化されるだけでなく、ランニングコストを抑えた運用が可能になります。
Static Assetsを有効にするため、wrangler.toml
に以下を追加します。
# 追加
assets = { directory = "./public/", binding = "ASSETS" }
開発用サーバーを起動して、http://localhost:8787
にアクセスし、画面が変わっているか確認しましょう。
まだフロントエンドから翻訳APIを呼び出す機能を実装していないため、翻訳ボタンをしてもエラーが表示されるだけです。
3.3 翻訳機能を作る
テキスト生成AIを使って、日本語を英語に翻訳します。
ただ単に翻訳するのではなく、画像生成用にアートスタイルの追加もさせています。
3.3.1 フロントエンドから翻訳APIを呼び出す
フロントエンドのロジックをpublic/script.js
に実装していきます。
/public
フォルダーの配下にscript.js
ファイルを追加して、翻訳APIを呼び出します。
`public/script.js`を展開する
async function handleEventStream(response, textarea) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let text = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
chunk.split("\n").forEach((line) => {
if (line.startsWith("data: ")) {
const data = line.replace("data: ", "");
if (data === "[DONE]") return;
try {
const jsonData = JSON.parse(data);
text += jsonData.response;
textarea.value = text;
} catch (error) {
// JSONデータが壊れている場合は無視する
}
}
});
}
}
async function translateText() {
const button = document.getElementById("translate-btn");
const errorMessage = document.getElementById("translate-error-message");
const promptInput = document.getElementById("prompt");
button.disabled = true;
errorMessage.style.display = "none";
const formData = new FormData();
formData.set("prompt", promptInput.value);
try {
const response = await fetch(`/translate`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`エラー ${response.status} ${response.statusText}`);
}
const translatedPrompt = document.getElementById("translated-prompt");
await handleEventStream(response, translatedPrompt);
} catch (error) {
console.error("エラー: ", error);
errorMessage.textContent = "翻訳に失敗しました。もう一度試してください。";
errorMessage.style.display = "block";
} finally {
button.disabled = false;
}
}
3.3.2 index.html
からscript.js
を呼び出す
public/index.html
の<head>
タグ内に、public/script.js
を読み込むコードを追加します。
<head>
...
<!-- 追加 -->
<script src="./script.js" defer></script>
</head>
3.3.3 Workers AIを有効にして翻訳APIを実装する
次にwrangler.toml
ファイルを変更して、Workers AIを有効にします。2行分のコメントを外します。
[ai]
binding = "AI"
そしてバックエンドのsrc/index.js
に翻訳APIを実装します。
`src/index.js`を展開する
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
...
// 翻訳APIの呼び出しを追加
if (request.method === "POST" && url.pathname === "/translate") {
const formData = await request.formData();
const prompt = formData.get("prompt");
return await translatePrompt(prompt, env);
}
return env.ASSETS.fetch(request);
},
};
async function translatePrompt(prompt, env) {
const messages = [
{
role: "system",
content: `Translate the following Japanese text into a concise and descriptive English prompt for image generation. Ensure the prompt adheres to the specified artistic style (e.g., oil painting, watercolor, pop art, or other specified), but explicitly exclude any cartoon or comic-style elements. Focus on clearly describing essential subjects, actions, and visual details for the image without introducing elements beyond the original content or style. Output the result as a single English sentence without quotation marks, comments, or additional formatting.`
},
{ role: "user", content: prompt },
];
try {
const stream = await env.AI.run("@cf/meta/llama-3.2-3b-instruct", {
messages,
stream: true,
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
});
} catch (error) {
console.error("翻訳エラー:", error);
return new Response(JSON.stringify({ error: "翻訳に失敗しました。" }), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
}
}
このコードのポイント:
-
messages
配列でAIに渡す指示を定義しています-
system
ロールで翻訳タスクの詳細を指示しています -
user
ロールで実際に翻訳したい日本語のテキストを渡します
-
-
env.AI.run
メソッドで指定したAIモデルを呼び出し、stream: true
でレスポンスをストリーミングします
開発用サーバーを起動すると、Workers AIが有効になったことが分かります。
npx wrangler dev
⛅️ wrangler 3.90.0
-------------------
Your worker has access to the following bindings:
- AI:
- Name: AI
▲ [WARNING] Using Workers AI always accesses your Cloudflare account in order to run AI models, and so will incur usage charges even in local development.
http://localhost:8787
にアクセスし、例えば最初のテキストエリアに「かわいい猫」と入力して「英語に翻訳」ボタンを押してみましょう。翻訳結果が表示されます。
なお、ローカルで動作させる場合、翻訳結果がまとめて表示され、ストリーミング表示になっていないように見える場合があります。デプロイすると正しく動作します。
3.4 画像生成機能を作る
画像生成AIを使って、翻訳した英語から画像を生成します。
3.4.1 画面を作成する
public/index.html
に画像生成ボタンと生成された画像の表示領域を追加します。
`public/index.html`を展開する
<textarea id="translated-prompt" placeholder="ここに英語の翻訳結果が表示されます"
style="width: 100%; field-sizing: content;"></textarea>
<!-- 画像生成ボタンを追加 -->
<button id="gen-image-btn" onclick="generateImage()">
画像を生成
</button>
<p id="gen-image-error-message" class="error-message" style="color: red;"></p>
<div id="image-container" style="margin-top: 1rem;">
<img id="generated-image" src="" alt="Generated Image" style="display: none;" />
</div>
</body>
3.4.2 フロントエンドから画像生成APIを呼び出す
フロントエンドのpublic/script.js
に以下のコードを追加して、画像生成APIを呼び出します。
`public/script.js`を展開する
<script>
...
async function generateImage() {
const button = document.getElementById("gen-image-btn");
button.disabled = true;
const errorMessage = document.getElementById("gen-image-error-message");
errorMessage.style.display = "none";
const translatedPrompt = document.getElementById("translated-prompt");
const formData = new FormData();
formData.set("prompt", translatedPrompt.value);
try {
const response = await fetch(`/generate-image`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`エラー ${response.status} ${response.statusText}`);
}
const imageBlob = await response.blob();
const imageUrl = URL.createObjectURL(imageBlob);
const img = document.getElementById("generated-image");
img.src = imageUrl;
img.style.display = "block";
} catch (error) {
console.error("エラー: ", error);
errorMessage.textContent =
"画像生成に失敗しました。もう一度試すか、プロンプトを変えてください。";
errorMessage.style.display = "block";
} finally {
button.disabled = false;
}
}
</script>
</body>
3.4.3 画像生成APIを実装する
src/index.js
に画像生成APIを追加します。
`src/index.js`を展開する
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
...
// 画像生成APIの呼び出しを追加
if (request.method === "POST" && url.pathname === "/generate-image") {
const formData = await request.formData();
const prompt = formData.get("prompt");
return await generateImage(prompt, env);
}
return env.ASSETS.fetch(request);
},
};
async function generateImage(prompt, env) {
const inputs = { prompt };
try {
const response = await env.AI.run(
"@cf/black-forest-labs/flux-1-schnell",
inputs
);
const binaryString = atob(response.image);
const img = Uint8Array.from(binaryString, (m) => m.codePointAt(0));
return new Response(img, {
headers: {
'Content-Type': 'image/jpeg',
},
});
} catch (error) {
console.error("画像生成エラー:", error);
return new Response(JSON.stringify({ error: "画像生成に失敗しました。" }), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
}
}
開発用サーバーを起動して、http://localhost:8787
にアクセスし、日本語から英語に翻訳した後、画像を生成ボタンを押しましょう。少し待つと生成された画像が表示されます。
執筆時点(2024年11月)では、画像生成のプロンプトが同じだと、常に同じ画像が生成されます。同じ題材で複数の画像を楽しみたい場合は、もう一度[英語に翻訳]を押して別のプロンプトを生成してから、[画像を生成]を実行してください。
3.5 デプロイする前に
これでAI画像生成アプリは完成です。デプロイすれば全世界から使えるようになります。
しかし、このアプリにはセキュリティの課題が残っています。全世界へ公開する前に、次のようなセキュリティ対策が必要です。
- ボット対策
- Cloudflare Turnstileの導入
- JWT(JSON Web Token)によるセッション管理
- CSRF(Cross Site Request Forgery)対策
- OriginヘッダーとFetch Metadataによる検証
- XSS(Cross Site Scripting)対策5
- CSP(Content Security Policy) やサニタイズによるXSS攻撃防御
これらの対策により、不正なアクセスからアプリケーションを守ることができます。次回の記事で詳しく解説します。
4. まとめ
この記事を通じて、Cloudflare WorkersとWorkers AIを用いたAI画像生成アプリの構築を体験しました。このハンズオンで学べることを以下にまとめます:
-
Cloudflare Workersを使った簡単で効率的な開発・デプロイ
サーバー不要で、ローカルのコードを数秒でデプロイし、即座に世界に公開できます。CLIを使った開発ならStatic Assetsを使って静的コンテンツを無料で配信できます -
Workers AIを活用した多様なAIモデルの利用
翻訳や画像生成をはじめとする、多彩なAI技術を簡単に試し、アプリケーションに統合できます -
AIを利用するアプリケーションの構築体験
翻訳と画像生成を連携させたアプリケーションを設計・実装する流れを体験し、AI技術の応用方法を学べます
次回の記事では、セキュリティを強化してより安全で堅牢なアプリに仕上げていきます。ぜひご覧ください!
参考情報
-
https://blog.cloudflare.com/workers-ai-bigger-better-faster/#new-workers-ai-pricing ではWorkers AIの料金モデルが10月から変更になると記載されています。ただ、執筆時点(2024年11月)では古いモデルのままのようです。 ↩
-
FLUXのパラメーターに
negative_prompt
がありません。 ↩ -
FLUXのパラメーターの推論ステップ数
num_steps
はデフォルトが4
、最大が8
です。Workers AIの一日当たりの無料枠が250ステップなので、デフォルトで62枚までの画像を生成できます ↩ -
https://zenn.dev/yusukebe/articles/59532688053828 を参考にさせていただきました! ↩
-
今回作成したAI画像生成アプリでは、ユーザーの入力データを画面に表示しなおす機会がありません。XSS攻撃は難しいですが、よくある攻撃なので紹介しています。 ↩