DifyとLINEをプラグインなしで連携してチャットボットを作る方法

本ページはプロモーションが含まれています

お問い合わせ対応や社内ヘルプデスクの効率化を検討している方でLINEとAIの組み合わせに興味をお持ちの方も多いのではないでしょうか。

昨今盛り上がりを見せているDifyとLINEを連携すれば、人間に近い自然な言語でAIを活用したチャットボットを作成することができます。

DifyとLINEの連携にはDify公式プラグインが用意されていますが、色々と制約もあり、真に要求を満たすシステムはなかなか作りづらいのが現状です。

そこで今回は、プラグインを使用せず、DifyとLINEを連携するAPIサーバーを設けることで、より実用的なアーキテクチャを実装する方法をご紹介します。

目次

LINEとDifyを連携させるメリット

LINEとDifyを連携させるメリット
LINEとDifyを連携させるメリット

まず最初にLINEとDifyを連携させるメリットから紹介させてください。

馴染みあるLINEからチャットベースでAIが利用可能

LINEであれば、日本人のほとんどが利用経験があるので、ユーザーに対して新しいアプリのインストールや操作学習を必要としません。

既存のインターフェースでそのままAIサービスを提供でき、導入ハードルを大幅に下げられます。

LLMを活用した人間に近い自然な会話が可能

DifyではChatGPTやGeminiなどの最新LLMを簡単に組み込めます。従来のルールベースのチャットボットと異なり、文脈を理解した自然で柔軟な対話が可能です。

複雑な質問や曖昧な表現にも適切に対応し、会話の流れを汲み取った回答を生成することができるうえ、人間のオペレーターと話すような感覚で対話できます。

RAGを用いた精度の高い回答の生成

DifyのRAG機能により、企業固有の知識ベースやFAQデータベースを参照した正確な回答を生成することができます。LLMの学習データだけでなく、最新情報や専門的な内容も回答に含められます。

製品マニュアルやサービス規約、社内手順書をナレッジベースに登録すれば、具体的で実用的な回答を提供可能。問い合わせ対応の品質向上と時間短縮を同時に実現できます。

LINEとDifyを連携する方法

LINEとDifyを連携する方法
LINEとDifyを連携する方法

LINEとDifyは直接データの受け渡しができません

LINE Messaging APIはWebhookで外部サーバーにメッセージを送信し、Difyは用意されたAPIエンドポイントでメッセージを処理します。

異なる通信プロトコルとデータ形式を使用するため、中継サーバーが必要です。

LINE Messaging APIとは?

LINE Messaging APIは、LINEプラットフォーム上でボットアプリケーションを構築するためのAPIです。ユーザーがボットにメッセージを送信すると、LINEサーバーが指定したWebhook URLにHTTP POSTリクエストを送信します。

基本的な仕組み

  1. Webhook受信:ユーザーメッセージを JSON 形式で受信
  2. 署名検証:リクエストがLINEからの正当なものかHMAC-SHA256で検証
  3. メッセージ処理:受信したメッセージに応じた処理を実行
  4. 返信送信:Reply APIまたはPush APIでユーザーに応答

主要なAPIエンドポイント

  • Reply API:https://api.line.me/v2/bot/message/reply
  • Push API:https://api.line.me/v2/bot/message/push
  • 認証:Channel Access Tokenをヘッダーに設定

今回の実装では、Webhookでメッセージを受信し、Reply APIで応答する基本パターンを使用します。

LINE BotのDifyプラグインを使用する

最も手軽なのは、Dify公式のLINE Botプラグインです。複雑な中継サーバーの構築が不要で、GUI設定だけでLINEとDifyを連携できます。

LINEのChannel Access TokenとChannel Secretをプラグイン設定画面に入力するだけで連携完了。開発コスト抑制と素早い導入に最適です。

中継用のAPIサーバーを実装する

より柔軟で拡張性の高いシステムには、独自の中継サーバー実装があります。

LINE Webhookを受信し、データを加工してDify APIに送信、レスポンスをLINEに返す処理を自前実装します。

AWS Lambda、Google Cloud Functions、GAS、Laravel、Flaskなど様々な技術スタックで実装可能。企業固有要件に合わせたカスタマイズや他システム連携も容易に実現できます。

Difyプラグインを使う場合と使わない場合の違い

LINE Botプラグインのメリット・デメリット

メリットデメリット
設定が簡単で導入が早い
技術知識が少なくても短時間で連携可能
Dify公式サポートあり
カスタマイズ制約あり
企業固有要件(認証連携、詳細ログ、外部 DB 連携など)への対応困難
外部依存によるリスク

プラグインなしでAPIサーバーを作るメリット・デメリット

メリットデメリット
システムの完全制御が可能
メッセージ受信の前処理、Dify送信前のデータ加工、レスポンス後処理など、あらゆる段階でカスタムロジック挿入可能
Slack、Microsoft Teams との連携、複数 AI サービス使い分け、既存システム(CRM、データベース)統合も容易
詳細ログ記録、パフォーマンス監視、エラーハンドリングも可能
開発・運用コストが高くな
署名検証、エラーハンドリング、セキュリティ対策などを全て自前で実装する必要がある

APIサーバーのシステム構成

今回の記事では、Next.js API Routesを中核とした以下のアーキテクチャを採用しています。

┌──────────────────────────────────────┐
│         LINE Messaging API           │
│ (Webhook → your-domain.com)          │
└───────────────┬──────────────────────┘
                │ HTTPS POST /api/line-webhook
                │ Header: x-line-signature
                ▼
┌──────────────────────────────────────┐
│      Next.js API Server              │
│   - ポート 3000 (本プロジェクト)     │
│   - /api/line-webhook エンドポイント │
│   - LINE Bot SDK で受信処理          │
│   - 署名検証 (HMAC-SHA256)           │
└───────────────┬──────────────────────┘
                │ HTTPS API呼び出し
                │ Authorization: Bearer {DIFY_API_KEY}
                ▼
┌──────────────────────────────────────┐
│        Dify AI Platform              │
│   - server-choice-demo.site/v1       │
│   - POST /chat-messages              │
│   - チャットボット AI処理               │
└───────────────┬──────────────────────┘
                │ 
                │ (会話履歴・文脈保存)
                ▼
┌──────────────────────────────────────┐
│          Supabase Database           │
│   - conversation_meta テーブル        │
│   - conversations テーブル            │
│   - 会話ID・メッセージ履歴管理           │
└──────────────────────────────────────┘

この構成により、LINEからのメッセージをNext.jsのAPIエンドポイントで受信し、必要な処理を行った後に Dify APIに転送します。

Difyからのレスポンスを受け取った後は、再びNext.jsで加工処理を行い、最終的にLINE Messaging APIを通じてユーザーに返信するという流れになります。

主要ライブラリと依存関係

実装には以下のライブラリを使用します。

  • @line/bot-sdk: LINE Messaging APIとの連携
  • axios: Dify APIへのHTTPリクエスト送信
  • @supabase/supabase-js: 会話履歴の永続化
  • crypto: LINE Webhook署名検証

環境構築と基本設定

Next.jsアプリの実装に入る前に、必要な環境構築と各サービスの基本設定を行います。

Difyのインストール

Difyのインストールがまだの方は、先にインストールを済ませてください。

今回はLINEのお問い合わせを24時間受け付ける必要があるので、VPSを借りてそこにDifyをインストールしています。

Difyのセルフホストはインストールそのものは簡単なのですが、ドメイン設定やSSL周りがやや面倒なので、今回はその辺を丸っとワンストップでやってくれるXServer VPSを利用しました。

Next.js開発環境の準備

Next.jsプロジェクトの初期化

今回はNext.jsのAPI RoutesでAPIサーバーを作成します。ターミナルから以下のコマンドを実行してください。

npx create-next-app@latest dify-line-bot
cd dify-line-bot

TypeScript + App Routerで作ります。この辺は好みか要件に合わせて自由にしてもらって結構です。

必要なライブラリのインストール

続いて、実装に必要なライブラリをインストールします。

npm install @line/bot-sdk axios @supabase/supabase-js
npm install -D @types/node

環境設定ファイルの作成

各サービスのAPIキーやURLを管理する環境設定ファイル(.env)を用意します。

touch .env

以下で各APIとURLの取得手順を順に解説してきます。

# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx

# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx

LINE公式アカウントの作成

まだ公式アカウントを作成していない方はアカウントを開設しましょう。

LINE公式アカウントを作成するには、LINEの公式アカウントを作成するページに遷移します。

「LINE公式アカウントをはじめる」ボタンをクリックします。

LINE公式アカウントのサービスサイト
LINE公式アカウントのサービスサイト

LINEアカウントで登録もしくはメールアドレスで登録の2つから選べます。お好きな方を選択して登録を進めてください。

LINEアカウントの作成方法の選択
LINEアカウントの作成方法の選択

メールアドレスで登録する場合、登録したメールアドレス宛にアカウント登録用リンクが送付されます。

登録メールアドレスの入力画面
登録メールアドレスの入力画面

「名前」と「パスワード」を入力後、「登録」をクリックしてください。

LINE公式アカウントの情報入力画面
LINE公式アカウントの情報入力画面

確認画面→SMS認証と済ませていくと、LINE公式アカウントのアカウント情報を入力することができます。

必要事項を入力してください。

アカウント情報の入力画面
アカウント情報の入力画面

確認画面を経て、問題がなければLINE公式アカウントが正常に作成されます。

LINE公式アカウントの作成完了画面
LINE公式アカウントの作成完了画面

LINE Messaging API設定

LINE Developersアカウントの作成

LINE Messaging APIの取得にはLINE Developersアカウントへの登録が必要です。以下の手順でアカウントを作成してください。

LINE Developersページにアクセスします。

右上に「コンソール」と表示されるので、クリックしてください。

LINE Developerアカウントの公式ページ
LINE Developerアカウントの公式ページ

LINE公式アカウントと同じように必要事項を入力してアカウントを作成します。

LINE Developerアカウントの作成完了画面
LINE Developerアカウントの作成完了画面

Channelの作成とAccess Tokenを取得

LINE公式アカウントとDeloperコンソールへの登録が完了できたら、次はMessaging APIを有効化していきます。

以下の「LINE Official Account Manager」ページにアクセスしてください。

アクセスすると、先ほど作成したアカウントが表示されますので、そのアカウントを選択します。

コンソールに表示されたアカウントリスト
コンソールに表示されたアカウントリスト

アカウント選択後、画面右上の「設定」ボタンを押します。設定画面に移動したら、メニュー左側の「Messaging API」をクリックしてください。

アカウント設定画面
アカウント設定画面

「Messaging APIを利用する」ボタンが表示されますので、こちらをクリックします。

Messaging APIの設定画面
Messaging APIの設定画面

すると、先ほどLINE Developersで作成したプロバイダーが表示されますので、チェックをつけて「同意する」ボタンを押します。

プロバイダーの選択画面
プロバイダーの選択画面

続いて、プロバイダーのプライバシーポリシーと利用規約を登録する画面が表示されます。

こちらは必要に応じて設定していただければと思いますが、後からでも変更が可能ですので、今回はそのまま「OK」ボタンを押して進めてください。

プライバシーポリシーと利用規約の設定画面
プライバシーポリシーと利用規約の設定画面

確認画面が表示されますので、こちらでも「OK」ボタンを押しましょう。

ここまでの手順で、「Channel ID」と「Channel Secret」が作成されます。これらの情報は後ほど実装で利用しますので、コピーして大切に保管しておきましょう。

Channel IDとChannel secret
Channel IDとChannel secret

次に、チャネルアクセストークンを取得します。

公式アカウントの管理画面(https://developers.line.biz/console/)に戻り、作成したプロバイダーを選択してください。

プロバイダー選択画面
プロバイダー選択画面

先ほど登録した「Messaging API」をクリックします。

Messaging APIの選択画面
Messaging APIの選択画面

ページを一番下までスクロールすると、「チャネルアクセストークン」の項目が表示されますので、「発行」ボタンを押します。

チャネルアクセストークンの発行ボタン
チャネルアクセストークンの発行ボタン

アクセストークンが発行されます。

ここまでの手順で「チャネルアクセストークン」と「チャネルシークレット」を発行することができました。

この2つをNext.jsアプリに作成した環境設定ファイル(.env)に設定しましょう。

# LINE Messaging API
# ここに設定する
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx

# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx

Webhook URLの準備

Messaging APIとデータの受け渡しをするエンドポイントを指定します。

LINE Developer Consoleの「Messaging API設定」タブを開いて、Webhook設定にエンドポイントとするURLを入力してください。

今回は/api/line-webhookをエンドポイントとして指定しました。

Webhook設定のURL入力欄
Webhook設定のURL入力欄

Difyセットアップ

上記の「Difyインストール」でDifyのインストールから管理者アカウントの作成までは完了していると思います。

ここでは、Difyアプリの作成からAPIキーの取得までのセットアップを行っていきます。

Difyアプリの作成

Next.jsアプリから送信されたLINEメッセージデータを処理するチャットフローを作成していきます。

まだアプリを作成していない方はDifyのダッシュボードから作成していきましょう。

「チャットフロー」を選択し、「アプリ名」と「説明」を記入して作成してください。

アプリ作成画面
アプリ作成画面

今回は簡易的なものなので、フロー自体はデフォルトのまま使用します。LLMノードの各種設定を行いましょう。

私の場合は以下のようにしています。

Difyのチャットフロー設定画面
Difyのチャットフロー設定画面
設定項目設定値
AIモデルGemini 2.5 Pro
コンテキストsys.query
SYSTEMあなたはECサイトのサポートスタッフです。
ユーザーからのお問い合わせに対して、丁寧かつ分かりやすく回答してください。

ルール:
– 回答は日本語で行ってください。
– 主な役割は、商品の問い合わせ、注文状況の確認、返品・交換、店舗情報の案内などを行うことです。
– よくある質問に対しては簡潔に即答してください。
– 複雑な問い合わせや判断が必要な内容は「サポート担当者に確認します」と案内してください。
– 個人情報(パスワードやクレジットカード番号)は入力しないように注意喚起してください。
– 文章は丁寧語(です・ます調)を使ってください。

プロンプトの設定では、LINE Botの用途に合わせてシステムメッセージを調整してください。

例えば、カスタマーサポート用途であれば「あなたは親切で丁寧なカスタマーサポートです。お客様の質問に分かりやすくお答えしてください。」といった指示を設定します。

DifyアプリのAPIキーを取得

続いて、Next.jsアプリと連携するために、DifyアプリのAPIキーを発行します。

作成したチャットフローの設定画面の左メニューにある「APIアクセス」をクリックしてください。

「ベースURL」が表示されるので、こちらをNext.jsの環境設定ファイル(.env)に記述してください。

APIアクセスページ
APIアクセスページ

次に、右上の「APIキー」をクリックします。

APIキーの作成ボタン
APIキーの作成ボタン

「新しいシークレットキーを作成」をクリックして、シークレットキーを発行します。

DifyのAPIシークレットキー
DifyのAPIシークレットキー

シークレットキーをNext.jsの環境設定ファイル(.env)に設定してください。

# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx

# Dify
# ここに設定する
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx

シークレットキーは機密性の高いキーです。絶対に外部に漏らさないようにしてください。

Supabaseデータベース設定

今回は会話履歴を保存するためのデータベースとして、Supabaseを使用しました。ここはMySQLでもなんでも問題ありません。

テーブル設計(conversation_meta, conversations)

会話の継続性を保持するため、2つのテーブルを作成します。

-- conversation_meta テーブル
CREATE TABLE conversation_meta (
  user_id TEXT PRIMARY KEY,
  conversation_id TEXT NOT NULL,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- conversations テーブル  
CREATE TABLE conversations (
  id SERIAL PRIMARY KEY,
  user_id TEXT NOT NULL,
  role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
  message TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

conversation_metaテーブルは LINEユーザーIDとDifyのconversation_idの紐付けを管理し、conversationsテーブルは会話履歴を保存します。

接続設定

Next.jsのAPIサーバーと接続するためにSupabaseのプロジェクトURLとAPI Keyを取得します。

作成したSupabaseプロジェクトの左メニューにある「Project Settings」 > 「API Keys」の「API Keys」タブからPublishable KeyをNext.jsの環境設定ファイル(.env)に設定します。

SupabaseのPublic key
SupabaseのPublic key

続いて、「Data API」から「Project URL」をNext.jsの環境設定ファイル(.env)に設定します。

SupabaseのプロジェクトURL
SupabaseのプロジェクトURL

以上で実装に必要な事前設定が完了です。

中継サーバー(Next.js API Routes)の実装

いよいよ中継サーバーの実装に入ります。この章では、LINEからのWebhookを受信し、Dify APIと連携して応答を返すまでの一連の処理を実装します。

Webhookエンドポイントの実装

まず、Next.jsの src/app/ ディレクトリに/api/line-webhook/ ディレクトリを作成し、そこにroute.tsファイルを作成します。

.
├── api
│   └── line-webhook
│       └── route.ts
├── favicon.ico
├── globals.css
├── layout.tsx
├── page.tsx
└── pages

今回はroute.tsに全ての処理を書いちゃってますが、もし参考にされる方はよしなにディレクトリ分けして使ってください。

route.tsの全体像

import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { Client } from '@line/bot-sdk';
import axios from 'axios';
import { createClient } from '@supabase/supabase-js';

// 環境変数の定義
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET!;
const LINE_CHANNEL_ACCESS_TOKEN = process.env.LINE_CHANNEL_ACCESS_TOKEN!;
const DIFY_API_KEY = process.env.DIFY_API_KEY!;
const DIFY_BASE_URL = process.env.DIFY_BASE_URL!;

// LINE Bot クライアントの初期化
const client = new Client({
  channelAccessToken: LINE_CHANNEL_ACCESS_TOKEN,
});

// Supabase クライアントの初期化
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);

// 署名検証関数
function verifySignature(body: string, signature: string): boolean {
  const expectedSignature = crypto
    .createHmac('SHA256', LINE_CHANNEL_SECRET)
    .update(body, 'utf8')
    .digest('base64');
  
  return signature === `SHA256=${expectedSignature}`;
}

// Dify API 呼び出し関数
async function callDifyAPI(message: string, conversationId?: string) {
  const requestData = {
    inputs: {},
    query: message,
    response_mode: 'blocking',
    conversation_id: conversationId || '',
    user: 'line-user'
  };

  const response = await axios.post(
    `${DIFY_BASE_URL}/chat-messages`,
    requestData,
    {
      headers: {
        'Authorization': `Bearer ${DIFY_API_KEY}`,
        'Content-Type': 'application/json',
      },
    }
  );

  return response.data;
}

// 会話ID取得関数
async function getConversationId(userId: string): Promise<string | null> {
  const { data, error } = await supabase
    .from('conversation_meta')
    .select('conversation_id')
    .eq('user_id', userId)
    .single();
    
  if (error || !data) {
    return null;
  }
  
  return data.conversation_id;
}

// 会話ID保存関数
async function saveConversationId(userId: string, conversationId: string) {
  await supabase
    .from('conversation_meta')
    .upsert({
      user_id: userId,
      conversation_id: conversationId,
      updated_at: new Date().toISOString()
    });
}

// 会話履歴保存関数
async function saveConversation(userId: string, role: string, message: string) {
  await supabase
    .from('conversations')
    .insert({
      user_id: userId,
      role: role,
      message: message,
      created_at: new Date().toISOString()
    });
}

// エラーハンドリング関数
async function handleError(error: any, userId: string, replyToken: string) {
  console.error('Error in handleTextMessage:', error);
  
  await client.replyMessage(replyToken, {
    type: 'text',
    text: '申し訳ございません。現在システムに問題が発生しております。しばらく時間をおいて再度お試しください。'
  });
}

// テキストメッセージ処理関数
async function handleTextMessage(userId: string, messageText: string, replyToken: string) {
  try {
    // 1. 既存の会話IDを取得
    const conversationId = await getConversationId(userId);
    
    // 2. Dify API を呼び出し
    const difyResponse = await callDifyAPI(messageText, conversationId);
    
    // 3. 会話IDを保存/更新
    if (difyResponse.conversation_id !== conversationId) {
      await saveConversationId(userId, difyResponse.conversation_id);
    }
    
    // 4. 会話履歴を保存
    await Promise.all([
      saveConversation(userId, 'user', messageText),
      saveConversation(userId, 'assistant', difyResponse.answer)
    ]);
    
    // 5. LINE に返信
    await client.replyMessage(replyToken, {
      type: 'text',
      text: difyResponse.answer
    });
    
    console.log(`Response sent to user ${userId}: ${difyResponse.answer}`);
    
  } catch (error) {
    await handleError(error, userId, replyToken);
  }
}

// メインのPOST処理関数
export async function POST(request: NextRequest) {
  try {
    const body = await request.text();
    const signature = request.headers.get('x-line-signature');
    
    // 署名検証
    if (!signature || !verifySignature(body, signature)) {
      return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
    }
    
    // イベント処理
    const events = JSON.parse(body).events;

    for (const event of events) {
      if (event.type === 'message' && event.message.type === 'text') {
        const userId = event.source.userId;
        const messageText = event.message.text;
        const replyToken = event.replyToken;
        
        // テキストメッセージを処理
        await handleTextMessage(userId, messageText, replyToken);
      }
    }

    return NextResponse.json({ status: 'ok' });
    
  } catch (error) {
    console.error('Webhook error:', error);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}

以下で各処理について簡単に解説します。

署名検証機能

verifySignature関数では、LINEからのWebhookが正当なものかどうかを検証しています。

HMAC-SHA256を使用した署名検証により、LINE以外からの不正なリクエストをブロックし、セキュリティを確保します。この検証はWebhookの改ざんを防ぐ重要な仕組みです。

function verifySignature(body: string, signature: string): boolean {
  const expectedSignature = crypto
    .createHmac('SHA256', LINE_CHANNEL_SECRET)
    .update(body, 'utf8')
    .digest('base64');
  
  return signature === `SHA256=${expectedSignature}`;
}

Dify API クライアント機能

callDifyAPI関数では、Dify APIとの通信を行います。response_mode: 'blocking' を指定することで、Difyからの完全な応答を待ってからレスポンスを受け取ります。これにより、ストリーミング配信ではなく一括での応答を実現しています。

async function callDifyAPI(message: string, conversationId?: string) {
  const requestData = {
    inputs: {},
    query: message,
    response_mode: 'blocking',
    conversation_id: conversationId || '',
    user: 'line-user'
  };

  const response = await axios.post(
    `${DIFY_BASE_URL}/chat-messages`,
    requestData,
    {
      headers: {
        'Authorization': `Bearer ${DIFY_API_KEY}`,
        'Content-Type': 'application/json',
      },
    }
  );

  return response.data;
}

会話履歴管理機能

会話の継続性を保つために、以下の3つの関数で会話情報を管理しています。

async function saveConversation(userId: string, role: string, message: string) {
  await supabase
    .from('conversations')
    .insert({
      user_id: userId,
      role: role,
      message: message,
      created_at: new Date().toISOString()
    });
}
getConversationId

ユーザーごとのconversation_idを取得

saveConversationId

新しいconversation_idを保存・更新

saveConversation

ユーザーとAIの会話内容を履歴として保存

これらの実装により、ユーザーが過去の会話を参照した質問をしても適切に応答できます。

統合処理フロー

handleTextMessage 関数では、メッセージ受信から返信までの一連の処理を統合しています。

既存の会話IDの取得、Dify APIの呼び出し、会話IDの保存・更新、会話履歴の保存、LINEへの返信という5つのステップを順次実行することで、一連の対話システムを実現しています。

async function handleTextMessage(userId: string, messageText: string, replyToken: string) {
  try {
    // 1. 既存の会話IDを取得
    const conversationId = await getConversationId(userId);
    
    // 2. Dify API を呼び出し
    const difyResponse = await callDifyAPI(messageText, conversationId);
    
    // 3. 会話IDを保存/更新
    if (difyResponse.conversation_id !== conversationId) {
      await saveConversationId(userId, difyResponse.conversation_id);
    }
    
    // 4. 会話履歴を保存
    await Promise.all([
      saveConversation(userId, 'user', messageText),
      saveConversation(userId, 'assistant', difyResponse.answer)
    ]);
    
    // 5. LINE に返信
    await client.replyMessage(replyToken, {
      type: 'text',
      text: difyResponse.answer
    });
    
    console.log(`Response sent to user ${userId}: ${difyResponse.answer}`);
    
  } catch (error) {
    await handleError(error, userId, replyToken);
  }
}

VPSへNext.js APIサーバーをデプロイ

作成したNext.js APIサーバーをデプロイします。今回はDifyをデプロイしているVPSにNext.jsも一緒にデプロイしてしまいます。

XServer VPSのDifyテンプレートを使うと、docker-composeを使った方式でDifyがデプロイされているので、Next.jsもコンテナ化してデプロイすることにしました。

この辺りのインフラ設計はみなさん悩まれるところだと思います。ご自身のリソースや要件に合わせて変更していただければと思います。

Next.jsアプリフォルダをアップロード

Next.jsのプロジェクトフォルダをVPSにアップロードします。

ビルドしたものをアップしても良いのですが、VPSのdocker起動時にビルド処理をさせるDockerfileを作成しました。

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Run
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/next.config.s ./
COPY --from=builder /app/public ./public
EXPOSE 3001
CMD ["npm", "start"]

アップロード先は任意のディレクトリで良いですが、私はXServer VPSがDifyを/root/ ディレクトリに置いていたので、同じ階層に/line-dify/ ディレクトリを作成し、そこに設置しました。

tree /root/line-dify/
/root/line-dify/
├── Dockerfile # Next.jsプロジェクトフォルダにDockerfileを作る
├── README.md
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── src
│   └── app
│       ├── api
│       │   └── line-webhook
│       │       └── route.ts
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       ├── page.tsx
│       └── pages
└── tsconfig.json

.envファイルをもこのNext.jsプロジェクトフォルダに置きます。

# LINE Messaging API
LINE_CHANNEL_SECRET=xxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxx

# Dify
DIFY_API_KEY=xxxxx
DIFY_BASE_URL=XXXX

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUVLISHABLE_KEY=xxxxx

docker-compose.override.yamlを作成

Next.jsコンテナを作成するために、docker-compose.override.yamlファイルを作成します。

既存のdocker-compose.yamlを編集しても良いのですが、Difyのアップデート時に上書きされてしまう可能性があるため、docker-compose.override.yamlを作成しています。

まずは、Difyのプロジェクトディレクトリに入ります。XServer VPSの場合は/root/dify/ ですね。

cd /root/dify/

docker composeを使ってDifyをセルフホストした場合、difyディレクトリにdockerディレクトリが作成されています。そこにdocker-compose.override.yamlを作成しましょう。

cd docker
vim docker-compose.override.yaml

中身を以下のようにします。

services:
  certbot:
    restart: always
  nginx:
    volumes:
    - ./nginx/custom.conf.template:/etc/nginx/custom.conf
  nextjs:
    build: ../../line-dify-bot
    container_name: nextjs
    ports:
      - "3001:3000"
    env_file:
      - ../../line-dify/.env
    depends_on:
      - api
    networks:
      - ssrf_proxy_network
      - default

SSL証明書管理のcartbotはXServer VPS側が記述しているものなので、下に続く形でNginxとNext.jsのコンテナの設定を上書きしています。

3000番ポートはDifyのWebコンテナが使っているので、Next.jsの方は3001にしています。

Nginxのルーティングを設定

NgixのコンテナはDifyがデフォルトで用意しているので、そこにNext.jsアプリへのルーティングを追加します。

ただ、そのままdefault.confを編集すると、こちらもDifyのアップデート時に上書きされる可能性があるので、別途confファイルを作成して、includeするようにします。

docker ディレクトリにnginx ディレクトリがあるので、移動して、custom.conf.template を作成します。

cd nginx
vim custom.conf.template

中身を以下のようにします。

location = /api/line-webhook {
  proxy_pass http://nextjs:3000;
  include proxy.conf;
}

locationディレクティブのmodifierには完全一致を指定しています。完全一致にしないと、default.confに定義されている/api/へのルーティングが優先され、Next.jsにルーティングが流れません。

/dify/docker/nginx/conf.d/default.conf.template を編集して、custom.confをincludeするようにします。

この時、指定するconfファイルへのパスはコンテナ内のパスになる点に注意してください。

# Please do not directly edit this file. Instead, modify the .env variables related to NGINX configuration.

server {
    listen ${NGINX_PORT};
    server_name ${NGINX_SERVER_NAME};

    location /console/api {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    # custom.confのlocationをinclude
    include /etc/nginx/custom.conf;

    location /api {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /v1 {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /files {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

    location /explore {
      proxy_pass http://web:3000;
      include proxy.conf;
    }

    location /e/ {
      proxy_pass http://plugin_daemon:5002;
      proxy_set_header Dify-Hook-Url $scheme://$host$request_uri;
      include proxy.conf;
    }

    location / {
      proxy_pass http://web:3000;
      include proxy.conf;
    }

    location /mcp {
      proxy_pass http://api:5001;
      include proxy.conf;
    }

  # placeholder for acme challenge location
  ${ACME_CHALLENGE_LOCATION}

  # placeholder for https config defined in https.conf.template
  ${HTTPS_CONFIG}
}

このままでは、Difyのアップデート時にincludeが上書きされて消えてしまう可能性があるので、アップデート時に自動でinclude文を追記するようにシェルスクリプトを用意しました。

XServer VPSがDifyの更新用スクリプトupdate.shを用意してくれていたので、今回はこちらにinclude文を追記するスクリプトを追加することにします。

vim /root/update.sh
#!/bin/bash
dify_dir="/root/dify/docker"
ENV_FILE="/root/dify/docker/.env"
ENV_EXAMPLE_FILE="/root/dify/docker/.env.example"
ENV_TMP_FILE="/root/dify/docker/.env.tmp"
TIMESTAMP=$(date "+%Y%m%d_%H%M%S")

# dockerサービス停止とコンテナ削除
cd ${dify_dir}
docker compose --profile certbot down

# dify最新ソースコード取得
cd ${dify_dir}
git pull origin main

# .env更新
cp -p ${ENV_FILE} /root/backup_dify_env/${TIMESTAMP}.env
cp -p ${ENV_EXAMPLE_FILE} ${ENV_TMP_FILE}
sed '/^#/d; /^\s*$/d' "${ENV_FILE}" | while IFS= read -r line; do
  KEY=$(echo "${line}" | cut -d'=' -f1)
  VALUE=$(echo "${line}" | cut -d'=' -f2)
  if [[ -n "${VALUE}" ]]; then
    sed -i "s|^${KEY}=.*|${KEY}=${VALUE}|" "${ENV_TMP_FILE}"
  fi
done

# 更新された .env.tmp を .env にコピー
cp -pf "${ENV_TMP_FILE}" "${ENV_FILE}"

# .env.tmpを削除
unlink "${ENV_TMP_FILE}"

# default.conf.templateにcustom.confのincludeを自動追加
DEFAULT_CONF="${dify_dir}/nginx/conf.d/default.conf.template"
CUSTOM_INCLUDE_LINE="    include /etc/nginx/custom.conf;"

# serverブロックの直前の '}' の前にincludeを挿入(なければ追加)
if ! grep -qF "$CUSTOM_INCLUDE_LINE" "$DEFAULT_CONF"; then
  # '}' の直前に挿入
  sed -i "/^}/i $CUSTOM_INCLUDE_LINE" "$DEFAULT_CONF"
  echo "custom.confのincludeをdefault.conf.templateに追加しました。"
else
  echo "custom.confのincludeは既に追加されています。"
fi

# dockerサービス構築・起動
cd ${dify_dir}
docker compose --profile certbot up -d

Dockerを再起動させる

Dockerのコンテナな環境を変更したので、一度Dockerを再起動させます。

docker compose restart

全てのコンテナが正常に起動しているか確認してください。

docker compose ps

問題なく起動していれば、これで一通りデプロイ作業は終了です。お疲れ様でした。

デプロイしたDifyアプリを使ってみる

では、さっそくデプロイしたLINEと連携させたDifyアプリを使ってみましょう。

LINE公式アカウントに何か質問してみてください。違和感のない自然な言語で返答が返ってくると思います。

公式アカウントへのトークデモ
公式アカウントへのトークデモ

まとめ

実装したアーキテクチャの振り返り

今回の記事では、DifyプラグインのLINE Botを使用せず、Next.js API Routesを中継サーバーとしたインテグレーション層を設けることで、LINE Messaging APIとDifyを連携させる方法を解説しました。

公式プラグインでは実現困難な企業固有の要件(詳細なログ記録、カスタム認証、外部システム連携など)にも柔軟に対応できそうです。

今後の拡張可能性

今回構築した中継サーバーの仕組みは、LINE 以外のプラットフォームにも応用できます。Slack、Microsoft Teams、Discord などの Webhook を受け取るエンドポイントを追加することで、マルチプラットフォーム対応の AI チャットボットシステムを構築可能です。

以上で、Next.js API Routes を活用した Dify と LINE の連携システムの構築が完了しました。この記事で紹介したアーキテクチャパターンを参考に、皆さんの企業やプロジェクトに最適なチャットボットシステムを構築していただければ幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コードゲーマーのアバター コードゲーマー サーバーチョイス 編集者

Web開発歴7年のフリーランスエンジニア。大学在学中から教育系Webメディアを運営するスタートアップにて、Webディレクターとして従事。独立からこれまでに多くのコーポレートサイトやCRM、予約システムなどのシステム開発、オウンドメディア運営を経験。20以上のレンタルサーバーの使用経験を持つ。

趣味はゲーム・野球観戦で休日は友人とARKなどオンラインゲームに興じています。

目次