GASとClaude APIで
LINEのAIチャットボットを作る方法
LINEに届いたメッセージへ、AIが自動で返信する——このボットは、サーバーを借りずにGASだけで作れます。 GASをWebアプリとして公開し、LINEのWebhook(イベントを指定URLへ通知する仕組み)をdoPostで受け、Claude API(Anthropic社の生成AI)に渡して回答を作ります。会話の文脈保持まで、動くコードで解説します。
Table of Contents
サーバー不要でLINEボットが作れる仕組み
結論から言うと、AIが返信するLINEボットは、GAS(Google Apps Script。Googleサービスを操作する無料のプログラム実行環境)と Claude APIだけで作れます。専用サーバーは要りません。仕組みはシンプルです。LINEはメッセージが届くたびに、指定したURLへ通知を送ります。この通知先をWebhookと呼びます。
GASには、スクリプトを外部からアクセスできるURLとして公開する「Webアプリ」機能があります。 そのURLをLINEのWebhookに登録すれば、メッセージが届くたびにGASが起動します。 あとは受け取った本文をClaude APIに渡し、返ってきた回答をLINEに送り返すだけ。 サーバーの契約も、SSL証明書の更新も、24時間の監視も要りません。
受ける
LINEのWebhook通知を doPost で受信。テキストメッセージだけを取り出す
尋ねる
本文と過去の会話をClaude APIに渡し、回答テキストを受け取る
返す
使い捨ての返信トークンで、LINEのトーク画面へ回答を送る
向いているのは、社内の問い合わせ受付や、よくある質問への一次対応といった小〜中規模の用途です。 GASには1回の実行が最大6分、1日の実行回数にも上限があるため、大量のメッセージを高速にさばく用途には向きません。まずは小さく始めて、手応えを見ながら広げるのが現実的です。
準備:LINEチャネルとAPIキーを用意する
最初に、LINE Developers で「Messaging APIチャネル」を作ります。 これがボットの本体です。作成後、チャネルの設定画面からチャネルアクセストークン(ボットとして返信するための鍵)を発行します。あわせて、応答メッセージを使うため、 自動応答メッセージはオフにしておきます。
次にスプレッドシートなどから Apps Script を開き、これから使う鍵を保管します。 APIキーやトークンはコードに直接書かず、PropertiesService(スクリプトごとの設定値を保存するGASの仕組み)に入れてください。
// スクリプトプロパティに以下を保存しておく
// ANTHROPIC_API_KEY : AnthropicのAPIキー
// CHANNEL_ACCESS_TOKEN : LINEのチャネルアクセストークン(長期)
// WEBHOOK_SECRET : Webhook URLに付ける自前の合言葉(任意)
function getProp(name) {
const value = PropertiesService.getScriptProperties().getProperty(name);
if (!value) throw new Error(name + " が未設定です。");
return value;
}
// チャネルアクセストークンは
// LINE Developers → Messaging APIチャネル → 「Messaging API設定」から発行する鍵をコードに直書きすると、スクリプトを共有した相手に鍵ごと渡ることになります。 LINEのチャネルアクセストークンが漏れると、第三者があなたのボットとして勝手にメッセージを送れてしまいます。WEBHOOK_SECRETは必須ではありませんが、URLに付ける自前の合言葉として後で使います。
GASをWebアプリとして公開しWebhookを受ける
LINEからの通知はHTTPのPOSTで届きます。GASでは、Webアプリに対するPOSTをdoPost(e)という決まった名前の関数で受け取ります。届いたイベントの配列を取り出し、 テキストメッセージだけを処理に回します。
// LINEからのWebhookはHTTP POSTで届く。GASでは doPost(e) が受け口
function doPost(e) {
// 任意:URL末尾の ?token=xxx が合言葉と一致するか確認する
// 例) https://script.google.com/.../exec?token=xxxx
const secret = getProp("WEBHOOK_SECRET");
if (secret && (!e.parameter || e.parameter.token !== secret)) {
return ContentService.createTextOutput("forbidden");
}
// LINEはイベントの配列をJSON文字列のボディで送ってくる
const body = JSON.parse(e.postData.contents);
const events = body.events || [];
events.forEach(function (event) {
// テキストメッセージ以外(スタンプ・画像・友だち追加など)は今回は無視
if (event.type !== "message" || event.message.type !== "text") return;
handleTextMessage(event);
});
// LINEには200を返せばよい。中身は問われない
return ContentService
.createTextOutput(JSON.stringify({ status: "ok" }))
.setMimeType(ContentService.MimeType.JSON);
}LINEが送るボディはe.postData.contentsにJSON文字列で入っています。1回の通知に複数のイベントが含まれることがあるため、eventsを配列として順に処理します。冒頭の合言葉チェックは、Webアプリは誰でもURLを叩けることへの簡易的な備えです。GASのWebアプリでは受信ヘッダーを読めず、LINE公式の署名検証(x-line-signature)をそのまま使えないため、 URLを秘密にしたうえで合言葉を添えます。
コードを書いたら、右上の「デプロイ」→「新しいデプロイ」で種類に「ウェブアプリ」を選び、 アクセスできるユーザーを「全員」にして公開します。発行されたURL(末尾が/exec)に?token=合言葉を付けたものを、LINEのWebhook URLに登録します。
受信メッセージをClaude APIに渡して回答を作る
1件のテキストメッセージを処理する流れを、handleTextMessageにまとめます。イベントからユーザーID・本文・返信トークンの3つを取り出し、 過去の会話に今回の発言を足してAIへ渡します。
// 1件のテキストメッセージを処理する
function handleTextMessage(event) {
const userId = event.source.userId; // ユーザーごとの識別子
const userText = event.message.text; // 送られてきた本文
const replyToken = event.replyToken; // 返信に使う使い捨てトークン
try {
// 過去の会話を読み込み、今回の発言を足してAIに渡す
const history = loadHistory(userId);
history.push({ role: "user", content: userText });
const answer = askClaude(history);
// AIの回答も履歴に残し、次の発言の文脈にする
history.push({ role: "assistant", content: answer });
saveHistory(userId, history);
replyToLine(replyToken, answer);
} catch (err) {
console.error(err);
// 失敗しても無言にせず、ユーザーに一言返す
replyToLine(replyToken, "すみません、いま応答できませんでした。少し時間をおいて再度お試しください。");
}
}全体をtryで囲み、失敗しても無言で終わらせないのがポイントです。AIやネットワークの一時的なエラーで返信できないと、 ユーザーには「既読スルー」に見えてしまいます。エラー時も一言返すだけで、体験が大きく変わります。
続いてClaude APIの呼び出しです。会話履歴をmessagesに渡し、ボットの人格や答え方はsystem(システムプロンプト。AIへの前提指示)で決めます。
// 会話履歴をClaude APIに渡し、返答テキストを受け取る
const SYSTEM_PROMPT =
"あなたは丁寧で簡潔なカスタマーサポート担当です。" +
"わからないことは推測で答えず、担当者に確認する旨を案内してください。" +
"回答は3〜4文程度に収めてください。";
function askClaude(history) {
const url = "https://api.anthropic.com/v1/messages";
const payload = {
model: "claude-haiku-4-5", // まずは軽量で低価格なモデルから
max_tokens: 500,
system: SYSTEM_PROMPT,
messages: history // { role, content } の配列をそのまま渡す
};
const res = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": getProp("ANTHROPIC_API_KEY"),
"anthropic-version": "2023-06-01"
},
payload: JSON.stringify(payload),
muteHttpExceptions: true // エラー時も例外にせずレスポンスを受け取る
});
if (res.getResponseCode() !== 200) {
throw new Error("Claude API エラー (" + res.getResponseCode() + "): " + res.getContentText());
}
return JSON.parse(res.getContentText()).content[0].text;
}システムプロンプトで「わからないことは推測で答えない」「回答は3〜4文に収める」と決めているのは、LINEのトークで長文が返ると読みづらいからです。 モデルはまず軽量で低価格なclaude-haiku-4-5から始め、回答の質が足りなければ上位モデルに切り替えると、費用の無駄がありません。
返信トークンでLINEに返す
AIの回答をLINEに送り返します。LINEの返信は、受信時に発行される返信トークン(replyToken)を使います。これは応答専用の使い捨てチケットのようなもので、https://api.line.me/v2/bot/message/replyに投げると、そのトークの相手に返信が届きます。
// 返信トークンを使ってLINEに返信する
function replyToLine(replyToken, text) {
const url = "https://api.line.me/v2/bot/message/reply";
const payload = {
replyToken: replyToken,
messages: [{ type: "text", text: text }] // 1回に最大5件まで
};
UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: {
// チャネルアクセストークンはBearerで渡す
"Authorization": "Bearer " + getProp("CHANNEL_ACCESS_TOKEN")
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
}大事な性質が2つあります。返信トークンは一度しか使えず、受信からしばらくで無効になります。だからAIの応答を待ってから、できるだけ早く返す必要があります。 Claude APIの応答に時間をかけすぎると、返そうとした時点でトークンが期限切れになることがあります。 回答を短めに保つ設定は、読みやすさだけでなく、この期限対策としても効きます。
なお、特定のユーザーへ後から自由なタイミングで送りたい場合は、返信ではなくプッシュメッセージという別のAPIを使います。 今回のように「届いたメッセージにその場で答える」用途では、無料枠が広い返信APIが基本です。
会話の文脈を保持する
ここまでの実装でも1問1答は動きます。ただ、それだけだと「さっきの件だけど」が通じません。前のやり取りを覚えていないからです。そこで、ユーザーごとに直近の会話を保存し、 次のリクエストでまとめてAIに渡します。
// ユーザーごとに会話履歴を一時保存する
// CacheServiceは最大6時間で自動的に消える。個人情報を長く貯めないぶん安全
const HISTORY_TURNS = 8; // 直近の何往復ぶんを覚えておくか
function loadHistory(userId) {
const cache = CacheService.getScriptCache();
const saved = cache.get("hist_" + userId);
return saved ? JSON.parse(saved) : [];
}
function saveHistory(userId, history) {
// 履歴が伸び続けるとトークン消費が増えるので、直近ぶんだけ残す
// 1往復=user+assistantの2件なので TURNS * 2 件に切り詰める
const trimmed = history.slice(-HISTORY_TURNS * 2);
const cache = CacheService.getScriptCache();
cache.put("hist_" + userId, JSON.stringify(trimmed), 21600); // 21600秒 = 6時間
}保存先にはCacheService(一時データを保存するGASの仕組み)を使い、userIdをキーに履歴を6時間だけ持たせます。自動で消えるので、会話ログを長期に貯め込まずに済みます。 履歴はそのままmessagesの形(role と content の配列)で保存しているため、そっくりそのままClaude APIに渡せます。
履歴を無制限に伸ばさないのがコツです。長くなるほど毎回のトークン消費と料金が増えます。 本文では直近8往復ぶんに切り詰めています。もっと長く覚えさせたい、あるいは消えては困るという場合は、 保存先をスプレッドシートやPropertiesServiceに変えると永続化できます。 用途に合わせて、覚えておく長さと消えるまでの時間を決めてください。
動作確認と公開時の注意点
公開したら、自分のスマホでボットを友だち追加し、実際にメッセージを送って確かめます。 返信が来ないときは、Apps Scriptの実行ログを見るのが近道です。doPostが呼ばれているか、Claude APIやLINE APIがエラーを返していないかが分かります。
URLが変わっていないか
「新しいデプロイ」を作るとURLが変わることがある。運用中は既存デプロイの更新で直すと、Webhook URLを付け替えずに済む。
返信トークンの期限切れ
AIの応答が遅いと、返信時にトークンが無効になる。回答を短く保ち、履歴を絞って処理を軽くする。
自動応答メッセージ
LINE側の自動応答がオンだと、ボットの返信と定型文が二重に届く。Messaging APIの応答設定でオフにする。
URLの秘匿
Webアプリは誰でも叩ける。URLを公開の場に貼らない。合言葉トークンを付け、漏れたら作り直す。
個人情報の扱い
会話内容を外部APIに送る旨を、必要に応じて利用者に案内する。履歴の保存期間や範囲を社内で整理してから運用する。
厳密な署名検証が要件になる場合は、GASの前段にCloud Functionsなどをはさむ構成も検討してください。 まずは合言葉付きのURLで小さく始め、必要になったら段階的に堅くするのが現実的です。
まとめ
AIが返信するLINEボットは、GASとClaude APIで作れます。押さえるべきは5つです。 LINEのWebhookをdoPostで受け、テキストメッセージだけを処理する。会話履歴をmessagesでClaude APIに渡す。回答は使い捨ての返信トークンで素早く返す。文脈はCacheServiceで一時保持する。 そしてURLを秘密にして守る。
この構成の良さは、「動かし続けるための運用」がほとんど要らないことです。サーバーもSSLも監視もなく、無料枠の中で小さく試せます。まずは社内FAQへの一次対応から始め、 手応えを見ながら画像対応や外部データ参照へと広げていけます。読み取りや判断をAIに、 最終的な確認や重要な案内は人に——その分担を守れば、日々の問い合わせ対応を無理なく軽くできます。
よくある質問
動きます。GASのWebアプリ機能を使うと、専用サーバーを用意しなくても外部からアクセスできるURLが手に入ります。LINE側にはそのURLをWebhook(イベントが起きたときに指定URLへ通知する仕組み)として登録するだけです。サーバーの契約・監視・SSL証明書の更新といった運用がまるごと不要になります。無料枠の範囲なら費用もかかりません。ただしGASには1回の実行が最大6分、1日あたりの実行回数にも上限があるため、大量のメッセージをさばく用途には向きません。社内問い合わせや小規模な受付ボットに向いた構成です。
GASのWebアプリでは、受信リクエストのHTTPヘッダーを読み取れないという制約があります。そのため、LINEが送る x-line-signature ヘッダーを使った正式な署名検証はそのままでは行えません。現実的な対策は2つです。1つは、WebアプリのURL自体を秘密として扱い、外部に漏らさないこと。もう1つは、URLの末尾にクエリパラメータで自前の合言葉を付け、doPost の冒頭でそれを照合することです。厳密な署名検証が要件になる場合は、GASの前段にCloud FunctionsなどをはさむかSDKのある実行環境を検討してください。要件に応じて判断してください。
ユーザーごとに直近のやり取りを保存し、次のリクエストでまとめてAIに渡します。本文ではCacheService(一時データを保存するGASの仕組み)を使い、userIdをキーに会話履歴を6時間だけ保持しています。Claude APIはmessages配列に過去の発言を並べて渡すと、その流れを踏まえて答えます。履歴が長くなるほど毎回のトークン消費と料金が増えるため、直近の数往復だけに絞るのがコツです。ずっと覚えておく必要がなければ、自動で消えるCacheServiceのほうが個人情報を貯め込まずに済み、安全です。
まず確認するのは返信トークン(replyToken)です。返信トークンは一度しか使えず、受信からしばらくで無効になります。Claude APIの応答に時間がかかりすぎると、返そうとした時点で期限切れになることがあります。次にApps Scriptの実行ログを見て、doPostが呼ばれているか、Claude APIやLINE APIがエラーを返していないかを確かめます。コードを直したあとにWebアプリのURLが変わっていないかも要注意です。GASは「新しいデプロイ」を作るとURLが変わることがあるため、既存デプロイの更新で運用するのが安全です。
本文のコードでは、テキストメッセージ以外は無視するようにしています。LINEのWebhookは、テキストのほかにスタンプ・画像・位置情報・友だち追加など、さまざまな種類のイベントを送ってきます。event.type が message で、message.type が text のものだけを処理し、それ以外は静かに読み飛ばします。画像を読み取らせたい場合は、LINEのコンテンツ取得APIで画像を取ってからClaude APIの画像入力に渡す拡張が可能です。まずはテキスト応答から始め、必要になったら種類を増やすのがおすすめです。
GAS自体は無料枠で動きます。LINEのMessaging APIも、応答(リプライ)メッセージは無料枠が広く、小規模なら費用は実質かかりません。費用が発生するのは主にClaude APIの利用分です。料金はやり取りするトークン量に比例するため、会話履歴を短く保ち、max_tokens(返答の最大の長さ)を必要な範囲に抑えると節約できます。まずは軽量で低価格なモデルから始め、回答の質が足りなければ上位モデルに切り替える進め方が無駄がありません。最新の料金は各サービスの公式ページで必ず確認してください。
関連するサービス・記事
AI×GASの業務自動化を
相談する。
LINEチャットボットのほかにも、問い合わせ分類・要約・翻訳・請求書や名刺の読み取りなど、 AI APIとGASを組み合わせた自動化をご相談いただけます。既存のLINE公式アカウントやスプレッドシート運用にもそのまま組み込めます。