はじめに
ども!最近はDifyとGAS系のブログを執筆している龍ちゃんです。本職では、設計の作業をやっています。そちらも知見が貯まったらブログとして放出していく予定ですが、もう少し先ですね。
さて、今回は自分のブログ内でのコラボ企画となります。内容としては「DifyとLINE Botを接続」となります。LINE BotをGASで作成したことがある方であれば、10分程度で実装できる内容なのでぜひ試してみてください。
GASでLINE Botを作ってみたことがない方は、こちらのブログを参考に取り組んでみてください。全体感は1時間もあれば、作成することができると思います。
GAS×LINEで作るAIチャットボット
今回の実装ではGASとLINE Botの連携なので、GASパートとDifyパートに分かれています。全体の構成としては、以下のような構成になります。

GASの担当領域は、LINEからのWebhookを受け取りイベントごとに処理を割り振ります。テキストメッセージの場合はDify(API)を実行し、それ以外の場合はMessagingAPIを介してLINEの返答を行います。
Difyの担当領域は、テキストとリプライトークンを受け取り、LLMから出力を生成し、Messaging APIを介してLINEの返答を行います。
まず初めにDifyでワークフローを作成してからGASのソースに入っていきます。
Dify:ワークフロー
ワークフローは全体4ステップのシンプルなものになっています。

開始
このノードでは、APIの引数となります。Webhookからの入力はGASで分割するので、LLMに入力するユーザーの入力とLINEのMessaging APIを用いたLINEの応答に必要なreply_tokenを設定します。
キー | 型 | 説明 |
---|---|---|
reply_token | String | LINEのイベントオブジェクトから取得したリプライトークン |
message | String | LINEで送信されたテキストメッセージ |
HTTPリクエスト
ここでは、LLMからの出力をMessaging APIを介してLINEに送信しています。HTTPリクエストには、ボディを直接JSONで定義ができます。ヘッダーには、LINE側の設定から取得できる認証情報を埋め込みます。
ヘッダー情報
キー | 値 |
---|---|
Content-Type | application/json; charset=UTF-8 |
Authorization | Bearer {{LINE_TOKEN}} |
ボディ
{
"replyToken":"{{開始で受け取ったreply_token}}",
"messages":[{
"type":"text",
"text":"{{LLMの出力}}"
}]
}
GAS:ソースコード
ソースコードの機能としては、ざっくり3つに分割することが可能です。
- LINE Webhookからのメッセージイベントを振り分ける処理
- Dify APIにアクセスする処理
- Messaging APIを介してのLINE応答処理
LINEから取得したチャンネルアクセストークンとDifyにアクセスするためのAPI Keyをコード上部で埋め込む必要があります。

本来は!秘密鍵はちゃんと他のユーザーが簡単に覗けない場所に保存しましょうね!
const LINE_TOKEN = "************************";
const DIFY_KEY = "**************************";
//ユーザーがメッセージを送信した時に下記を実行する
function doPost(e) {
const json = JSON.parse(e.postData.contents);
// Webhookイベントは単一で送られてくると仮定して進めます。
// 複数する可能性を考慮して進める必要はあり
// <https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects>
const reply_token = json.events[0].replyToken;
const messageType = json.events[0].message.type;
const messageText = json.events[0].message.text;
// 検証で200を返すための取り組み
if (typeof reply_token === 'undefined') {
return;
}
switch (messageType) {
case "text":
// ここでDifyにアクセスする
difyAccess(messageText, reply_token)
return
case "image":
case "video":
case "audio":
case "file":
case "location":
case "sticker":
default:
const message = "今回のBOTはテキストメッセージにのみ対応しています。";
replyTextMessage(message, reply_token)
return;
}
}
const difyAccess = async (message, reply_token) => {
const data = {
"user": "gas_LINE_automation",
"response_mode": "blocking",
'inputs': {
"reply_token": reply_token,
"message": message
}
};
const headers = {
'Authorization': "Bearer " + DIFY_KEY,
'Content-Type': 'application/json'
};
const request = {
"url": "https://api.dify.ai/v1/workflows/run",
"method": "post",
"payload": JSON.stringify(data),
"headers": headers,
"muteHttpExceptions": true
};
UrlFetchApp.fetchAll([request])
}
const replyTextMessage = async (message, reply_token) => {
const request = {
"url": "https://api.line.me/v2/bot/message/reply",
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + LINE_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': [{
'type': 'text',
'text': message,
}],
}),
}
UrlFetchApp.fetchAll([request])
}
LINEの情報受け取り部分
こちらでは、LINEから送信されてきたメッセージイベントをテキストかそれ以外かを割り振る処理を記載しています。
//ユーザーがメッセージを送信した時に下記を実行する
function doPost(e) {
const json = JSON.parse(e.postData.contents);
// Webhookイベントは単一で送られてくると仮定して進めます。
// 複数する可能性を考慮して進める必要はあり
// <https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects>
const reply_token = json.events[0].replyToken;
const messageType = json.events[0].message.type;
const messageText = json.events[0].message.text;
// 検証で200を返すための取り組み
if (typeof reply_token === 'undefined') {
return;
}
switch (messageType) {
case "text":
// ここでDifyにアクセスする
difyAccess(messageText, reply_token)
return
case "image":
case "video":
case "audio":
case "file":
case "location":
case "sticker":
default:
const message = "今回のBOTはテキストメッセージにのみ対応しています。";
replyTextMessage(message, reply_token)
return;
}
}
Webhookではメッセージイベント以外にも様々なイベントが送付されてきます。特定のイベントの場合の挙動を変更することができます。
グループチャットにBotを導入した場合はイベント配列に複数のイベントが送付されてくる可能性もあります。イベントを落とさない仕組みが必要です。
TextMessageの場合:Dify APIアクセス
こちらでは、Dify APIへアクセスを行っています。こちらの詳細は「Dify入門ガイド:DifyのAPIをGASで使う方法!10分で作るAI連携システム」で解説しています。
const difyAccess = async (message, reply_token) => {
const data = {
"user": "gas_LINE_automation",
"response_mode": "blocking",
'inputs': {
"reply_token": reply_token,
"message": message
}
};
const headers = {
'Authorization': "Bearer " + DIFY_KEY,
'Content-Type': 'application/json'
};
const request = {
"url": "https://api.dify.ai/v1/workflows/run",
"method": "post",
"payload": JSON.stringify(data),
"headers": headers,
"muteHttpExceptions": true
};
UrlFetchApp.fetchAll([request])
}
TextMessage以外の場合:GASからの返答
こちらでは、GASからMessaging APIを介して送信するサンプルとなります。詳細は公式リファレンスを参照しましょう。
const replyTextMessage = async (message, reply_token) => {
const request = {
"url": "https://api.line.me/v2/bot/message/reply",
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + LINE_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': [{
'type': 'text',
'text': message,
}],
}),
}
UrlFetchApp.fetchAll([request])
}
既存実装の問題点・メリット
既存実装で本番運用するには難しい点が何点かあります。GASにおける制限については、「いまさらGASでLineBotを作る【オウム返し】:GASとLINEの処理の流れと制限事項」で解説しています。
メリット:ユーザーの入力タイプによって処理を振り分けることができる
今回の実装では、テキストメッセージを対象にしました。LINEでは、テキストメッセージ以外にも画像や動画の入力なども入力可能です。ワークフローを新規で作成するか、入力によって分岐するように設計することで、入力タイプによって別々の処理を実現することが可能です。
メリット:生成AIの精度に注力できる
生成AIの返答部分はDifyに移譲されています。そのため、プロンプトの変更はDify上で変更を加えることができます。LINE BOTのシステム的エラーなのか、Dify側のエラーなのか切り分けることが可能なのでデバックがやりやすいです。
Difyのワークフロータブで、ワークフローの実行を監視することができるので非常に便利です。

問題点:LLMと単発の会話になっている
現状の実装では、LLMとの単発の会話になっています。過去の会話を引き継いでいないので、一つの入力でLLMに完結させたい処理を入力する必要があります。ここは、ワークフローとデータ部分を改修する必要があります。具体的には、以下の対応が挙げられます。
- Dify APIにアクセス時に過去の会話を送信する(GAS側でデータを保存)
- ワークフローの呼び出しユーザーによって過去の情報を取得するノードを追加(Difyからデータを取得)
この設計は、データをどこで保存するかなどの要素を検討して決定する必要があります。
ここは、ワークフローではなくチャットフローを活用することで解消できそうです。
問題点:流出してはいけない秘密鍵がべた書きになっている
これは、わかりやすさ重視でサンプルでは秘密鍵をソースコードに直接入力しています。以下のような対応が必要となります。
おわり
今回は、DifyとLINE Botを組み合わせた簡単なチャットボットの実装方法を紹介しました。テキストメッセージに対応したシンプルなワークフローを構築することで、生成AIの返答精度に注力できる環境を整えることができます。今後は過去の会話履歴の管理など、より高度な機能の実装にも挑戦していきたいと思います。