はじめに
本記事ではCloudflare Workersを使ったサーバーレス構成で、AIが自動でチャットの返信をする最小構成のLINE AI Botについて実装手順を紹介します。
想定読者
- LINE BotにAIを組み込みたい人
- Cloudflare Workersを触ったことがある人
全体構成
今回、以下の構成でAI LINE Botを実装しました。
LINE Messaging API(Webhook)
↓
Cloudflare Workers
↓
LLM API(OpenAI / Geminiなど)
↓
LINEへ返信
実装手順
LINEアカウントとCloudflare Workersの設定、実装したコードについて紹介します。
実装の前提条件
- LLMのAPIキー取得済み
- LINE公式アカウント作成済み
- Cloudflareアカウント作成済み
- Wranglerインストール済み
今回の動作環境
- Gemini 2.5 Flash 使用
- Wrangler バージョン 4.56.0
LINE公式アカウントの作成手順については、@frozencatpisces さんの記事などをご参照ください。自分のLINEアカウントから作成した公式アカウントの追加まで行っておくとスムーズです。
📝 LINE: 公式アカウント入門(1) - 公式アカウントを作成してみる
WindowsにWranglerをインストールする手順については、@meadas さんの記事を参考にさせていただきました。
📝 ローカルからCloudflareのWorkersに接続する環境を作る手順
LINE Official Account Manager の設定
LINE公式アカウントが外部システム(今回だとCloudflare Workers)と連携するためには、チャネルの作成が必要になります。チャネルが所属するプロバイダーも必要になるため、この2つを作成します。

出典:【図解あり】LINE APIを使うなら知っておきたい!プロバイダーと認証プロバイダーの違いを解説
設定手順
-
「設定」>「応答設定」で「チャット」を有効化
- 「設定」>「Messaging API」で「Messaging APIを利用する」をクリック
- 任意の名前でプロバイダーを作成
- 「Messaging API」のチャネルを作成
- チャネルが作成できたら、「Channel secret」 をメモしておく
「Webhook URL」には、後ほどデプロイするCloudflare WorkerのURLを入力します。
- LINE Developers にアクセスして、「作成したプロバイダー」>「Messaging API設定」>「チャネルアクセストークン」の「発行」をクリック
- 「チャネルアクセストークン」 をメモしておく
Cloudflareの設定
LINEの設定が一通り完了したので、CloudflareでWorkerを作成します。今回、Cloudflareの設定は基本的にCLIツールのWranglerによって行います。
プロジェクトの作成
以下のコマンドを実行してプロジェクトを作成します。
npx wrangler init ai-chatbot
プロジェクトのカテゴリーなど質問されるため、適宜回答を選択します。今回は以下の設定で作成しました。
| 項目 | 値 |
|---|---|
| category | Hello World example |
| type | Worker Only |
| lang | JavaScript |
| deploy | no |
これでプロジェクトのテンプレートが作成されるため、以下のようにコードを変更していきます。
-
wrangler.tomlの作成 -
wrangler.jsoncの削除 -
src/index.jsの変更
wrangler.toml ではWorkerの名前やエントリポイント(src/index.js)を定義しています。
wrangler.toml コード
name = "ai-chatbot"
main = "src/index.js"
compatibility_date = "2025-12-22"
workers_dev = true
現在wrangler設定ファイルのフォーマットとして toml か json を指定できますが、今回は読みやすい toml フォーマットを使用します。デフォルトで作成される wrangler.jsonc は削除してください。
Workersにデプロイするコードが src/index.js になります。LINEのWebhookリクエストを受け付け、署名検証を行ったうえでLLMのAPIを呼び出し、その結果をLINEへ返信します。
index.js コード
// src/index.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname === "/webhook") {
const bodyText = await request.text();
// 署名検証
const signature = request.headers.get("x-line-signature");
const isValid = await verifyLineSignature(bodyText, signature, env.LINE_CHANNEL_SECRET);
if (!isValid) return new Response("Invalid signature", { status: 401 });
// イベント処理
const body = JSON.parse(bodyText);
const events = body.events || [];
const processing = async () => {
for (const ev of events) {
if (ev.type === "message" && ev.message.type === "text") {
await handleMessage(ev, env);
}
}
};
ctx.waitUntil(processing());
return new Response("OK", { status: 200 });
}
return new Response("Bot is running!");
}
};
// --- メイン処理 ---
async function handleMessage(event, env) {
const userText = event.message.text;
const replyToken = event.replyToken;
// モデル(Gemini 2.5 Flash)を指定して呼び出し
const aiResponse = await callGemini(userText, env.API_KEY, "gemini-2.5-flash");
// LINEに返信
await replyLine(replyToken, aiResponse, env.LINE_CHANNEL_ACCESS_TOKEN);
}
// --- Gemini API 呼び出し関数 ---
async function callGemini(text, apiKey, modelName) {
try {
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?key=${apiKey}`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contents: [{ parts: [{ text: text }] }] })
});
const data = await response.json();
if (!response.ok) {
console.error(`Gemini Error: ${JSON.stringify(data)}`);
return `エラー: ${data.error?.message || "生成失敗"}`;
}
return data?.candidates?.[0]?.content?.parts?.[0]?.text || "応答なし";
} catch (e) {
return `通信エラー: ${e.message}`;
}
}
// --- LINE Reply API ---
async function replyLine(token, text, accessToken) {
await fetch("https://api.line.me/v2/bot/message/reply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
},
body: JSON.stringify({
replyToken: token,
messages: [{ type: "text", text: text }]
})
});
}
// --- 署名検証 ---
async function verifyLineSignature(body, signature, secret) {
if (!signature || !secret) return false;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const signatureBuffer = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
const signatureBase64 = btoa(String.fromCharCode(...new Uint8Array(signatureBuffer)));
return signatureBase64 === signature;
}
環境変数(Secrets)の設定
以下のコマンドで環境変数を設定します。各コマンドを実行すると、環境変数にセットする値を質問されるので適宜入力してください。
npx wrangler secret put LINE_CHANNEL_ACCESS_TOKEN
npx wrangler secret put LINE_CHANNEL_SECRET
npx wrangler secret put API_KEY
wrangler コマンドは、作成したプロジェクトフォルダ(ai-chatbot)に移動してから実行してください
- Cloudflare dashboardにアクセスし、「Compute & AI」>「Workers & Pages」から作成したWorkerを選択
- 「Settings」タブ >
workers.devの「・・・」アイコン > 「Enable Domain」をクリック
デプロイと動作確認
デプロイの準備ができたので、以下のコマンドでデプロイを行います。
npx wrangler deploy
https://<your-worker>.workers.dev/にアクセスして、Bot is running!と表示されていればデプロイできています。
LINE Webhook URLを設定
Workersのデプロイが完了したので、LINEでメッセージを受け取ったらWorkersを呼び出せるようにWebhookの設定を行います。
- LINE Official Account Manager で「設定」>「Messaging API」で「Webhook URL」に以下を入力する
※ WorkersのURLに/webhookパスを追加
https://<your-worker>.workers.dev/webhook
- 「設定」>「応答設定」で「Webhook」を有効化
これで設定は完了です!
動作確認
LINE Botに話しかけてみます。
まとめ
最小構成でLINEボットとAIを連携する実装手順をご紹介しました。この構成だと会話履歴を保持していないため、以前の会話の文脈を汲んだ回答をさせたい場合は、データベース(Cloudflare KV, D1など)との連携が必要になります。Notionと連携させる予定なので、実装したらまた記事にしたいと思います。
関連記事
APIを利用する場合は、予算アラートの設定をおすすめします
WorkerからNotion APIを実行して、Notionをデータベースとして使っている構成










