MENU

Dify×Slackで就業規則ChatBotを作成!完全ガイド

当ページのリンクには広告が含まれています。

「就業規則や社内ルールの問い合わせが多くて対応が大変…」
「Slackで気軽に質問できるAIボットを自社にも導入したい」

そんな悩みを解決するために、今回はノーコードAIプラットフォーム「Dify」「Slack」を連携させ、社内規定に答えてくれるチャットボットを構築する方法を解説します。

プログラミング知識がなくても、GAS(Google Apps Script)を活用することで、誰でも簡単に実装可能です。

目次

【Step 1】Difyの設定:AIの頭脳を作る

まずは、Difyを使って就業規則などのドキュメントを学習したAIを作成します。

1. ナレッジ(知識)の登録

Difyにログインし、「ナレッジ」タブから「ナレッジを作成」をクリックします。

社内の就業規則PDFなどをアップロードし、設定は「推奨」のままで保存します。

2. アプリの作成

「スタジオ」タブから「最初から作成」を選び、「チャットボット」を選択します。

アプリ名(例:就業規則ボット)を入力し、以下の設定を行います。

  • 手順(プロンプト): AIへの指示を入力します。「コンテキスト(ナレッジ)に基づいて回答してください」といった内容を記述します。
<instruction>
ユーザからの質問に対して、コンテキストから該当箇所を抜き出してください。その情報を用いて、質問に対する最適な回答を作成してください。XMLタグは出力に含めないでください。

手順は以下の通りです:
1. ユーザの質問を理解し、それに関連するキーワードやフレーズを特定します。
2. コンテキストを検索し、質問に関連する情報を見つけます。
3. 見つけた情報を元に、ユーザの質問に対する最適な回答を作成します。

<example>
例えば、ユーザの質問が「地球は何歳ですか?」で、コンテキストに「地球は約45億年前に形成された」という情報がある場合、回答は「地球は約45億年前に形成されたため、地球の年齢は約45億歳です。」となります。
</example>
</instruction>
  • コンテキスト: 先ほど作成したナレッジを追加します。

設定が完了したら、右上の「公開する」ボタンを押して更新します。

3. APIキーの発行

「APIリファレンスにアクセス」をクリックし、右上の「APIキー」から新しいシークレットキーを作成します。

※このキーは後で使うので、必ず控えておいてください。

【Step 2】Slackの設定:ボットのガワを作る

次に、Slack側でアプリ(ボット)の箱を用意します。

  1. Slack APIにアクセスし、「Create New App」→「From scratch」を選択。
  1. アプリ名とワークスペースを指定して作成。
  2. OAuth & Permissions: app_mention:read(メンション読み取り)とchat:write(書き込み)の権限を付与。
  3. Install App: ワークスペースにインストールし、「Bot User OAuth Token」を控えます。

【Step 3】GASで連携:DifyとSlackを繋ぐ

Google Apps Script(GAS)を中継役として、SlackからのメンションをDifyに送り、回答をSlackに戻す仕組みを作ります。

GASコードの実装

GASで新しいプロジェクトを作成し、以下のコードを貼り付けます。

/**
 * Slackのメンションイベントを受信し、DifyのフローAPIを呼び出してSlackに返信するGAS
 */

// スクリプトプロパティから設定値を取得
const SLACK_BOT_TOKEN = PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN');
const DIFY_API_URL = PropertiesService.getScriptProperties().getProperty('DIFY_API_URL');
const DIFY_API_KEY = PropertiesService.getScriptProperties().getProperty('DIFY_API_KEY');

/**
 * SlackのEvent APIからのHTTPリクエストを処理するメイン関数
 * @param {Object} e - HTTPリクエストイベント
 * @return {Object} - HTTPレスポンス
 */
function doPost(e) {
  try {
    // リクエストボディをパース
    const requestBody = JSON.parse(e.postData.contents);
    
    // URL検証リクエストの場合はchallengeを返す
    if (requestBody.type === 'url_verification') {
      console.log('URL検証リクエストを受信');
      return ContentService
        .createTextOutput(requestBody.challenge)
        .setMimeType(ContentService.MimeType.TEXT);
    }
    
    // イベントコールバックの場合
    if (requestBody.type === 'event_callback') {
      const event = requestBody.event;
      
      // メンションイベントのみ処理
      if (event.type === 'app_mention') {
        // リトライ防止のためのキャッシュチェック
        const cacheKey = `slack_event_${event.client_msg_id || event.ts}`;
        const cache = CacheService.getScriptCache();
        
        if (cache.get(cacheKey)) {
          console.log('重複リクエストをスキップ:', cacheKey);
          return ContentService
            .createTextOutput('OK')
            .setMimeType(ContentService.MimeType.TEXT);
        }
        
        // キャッシュに保存(10分間)
        cache.put(cacheKey, 'processed', 600);
        
        // 非同期でDify APIを呼び出し、Slackに返信
        processSlackMention(event);
      }
    }
    
    // 正常なレスポンスを即座に返す(3秒以内)
    return ContentService
      .createTextOutput('OK')
      .setMimeType(ContentService.MimeType.TEXT);
      
  } catch (error) {
    console.error('doPost エラー:', error);
    return ContentService
      .createTextOutput('ERROR')
      .setMimeType(ContentService.MimeType.TEXT);
  }
}

/**
 * Slackのメンションイベントを処理する関数
 * @param {Object} event - Slackイベント
 */
function processSlackMention(event) {
  try {
    // メンションテキストからボット名を除去して指示を抽出
    const instruction = extractInstruction(event.text);
    
    if (!instruction) {
      console.log('指示が見つかりませんでした');
      return;
    }
    
    console.log('抽出された指示:', instruction);
    
    // DifyのフローAPIを呼び出し
    const difyResponse = callDifyFlow(instruction);
    
    let outputText = null;
    
    // ワークフローAPIの場合
    if (difyResponse && difyResponse.data && difyResponse.data.outputs && difyResponse.data.outputs.outputText) {
      outputText = difyResponse.data.outputs.outputText;
    }
    // チャットボットAPIの場合
    else if (difyResponse && difyResponse.answer) {
      outputText = difyResponse.answer;
    }
    
    if (outputText) {
      // Slackのスレッドに返信
      postToSlackThread(event.channel, event.ts, outputText);
    } else {
      console.error('Difyからの応答が不正です:', difyResponse);
      postToSlackThread(event.channel, event.ts, 'エラー: 処理に失敗しました。');
    }
    
  } catch (error) {
    console.error('processSlackMention エラー:', error);
    // エラー時もSlackに通知
    if (event.channel && event.ts) {
      postToSlackThread(event.channel, event.ts, 'エラーが発生しました。しばらく後にもう一度お試しください。');
    }
  }
}

/**
 * メンションテキストから指示部分を抽出
 * @param {string} text - メンションテキスト
 * @return {string} - 抽出された指示
 */
function extractInstruction(text) {
  // <@UXXXXXXXX> の形式のメンションを除去
  const instruction = text.replace(/<@[UW][A-Z0-9]+>/g, '').trim();
  return instruction;
}

/**
 * DifyのAPIを呼び出す(ワークフローまたはチャットボット対応)
 * @param {string} instruction - 指示内容
 * @return {Object} - Difyからのレスポンス
 */
function callDifyFlow(instruction) {
  try {
    // まずワークフローAPI形式で試行
    let payload = {
      inputs: {
        instruction: instruction
      },
      response_mode: 'blocking',
      user: 'slack-user'
    };
    
    let options = {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${DIFY_API_KEY}`,
        'Content-Type': 'application/json'
      },
      payload: JSON.stringify(payload),
      muteHttpExceptions: true // エラーレスポンスの詳細を取得
    };
    
    console.log('Dify API(ワークフロー形式)を呼び出し中...');
    let response = UrlFetchApp.fetch(DIFY_API_URL, options);
    let responseData = JSON.parse(response.getContentText());
    
    // ワークフローAPIが成功した場合
    if (response.getResponseCode() === 200) {
      console.log('Dify APIレスポンス(ワークフロー):', responseData);
      return responseData;
    }
    
    // queryパラメータエラーの場合、チャットボットAPI形式で再試行
    if (response.getResponseCode() === 400 && 
        responseData.code === 'invalid_param' && 
        responseData.params === 'query') {
      
      console.log('チャットボットAPI形式で再試行...');
      
      payload = {
        inputs: {
          instruction: instruction
        },
        query: instruction, // チャットボットAPIの場合はqueryが必要
        response_mode: 'blocking',
        conversation_id: '',
        user: 'slack-user'
      };
      
      options.payload = JSON.stringify(payload);
      
      response = UrlFetchApp.fetch(DIFY_API_URL, options);
      responseData = JSON.parse(response.getContentText());
      
      if (response.getResponseCode() === 200) {
        console.log('Dify APIレスポンス(チャットボット):', responseData);
        return responseData;
      }
    }
    
    // どちらも失敗した場合
    console.error('Dify API呼び出し失敗:', {
      statusCode: response.getResponseCode(),
      response: responseData
    });
    throw new Error(`Dify API Error: ${responseData.message || 'Unknown error'}`);
    
  } catch (error) {
    console.error('Dify API呼び出しエラー:', error);
    throw error;
  }
}

/**
 * Slackのスレッドに返信を投稿
 * @param {string} channel - チャンネルID
 * @param {string} threadTs - スレッドのタイムスタンプ
 * @param {string} text - 返信内容
 */
function postToSlackThread(channel, threadTs, text) {
  try {
    const payload = {
      channel: channel,
      thread_ts: threadTs,
      text: text
    };
    
    const options = {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${SLACK_BOT_TOKEN}`,
        'Content-Type': 'application/json'
      },
      payload: JSON.stringify(payload)
    };
    
    const response = UrlFetchApp.fetch('https://slack.com/api/chat.postMessage', options);
    const responseData = JSON.parse(response.getContentText());
    
    if (responseData.ok) {
      console.log('Slackへの返信が成功しました');
    } else {
      console.error('Slackへの返信エラー:', responseData.error);
    }
    
  } catch (error) {
    console.error('Slack投稿エラー:', error);
  }
}

/**
 * 手動テスト用の関数
 * スクリプトプロパティの設定確認に使用
 */
function testConfiguration() {
  console.log('=== 設定確認 ===');
  console.log('SLACK_BOT_TOKEN:', SLACK_BOT_TOKEN ? '設定済み' : '未設定');
  console.log('DIFY_API_URL:', DIFY_API_URL ? '設定済み' : '未設定');
  console.log('DIFY_API_KEY:', DIFY_API_KEY ? '設定済み' : '未設定');
  
  if (!SLACK_BOT_TOKEN || !DIFY_API_URL || !DIFY_API_KEY) {
    console.log('⚠️ スクリプトプロパティの設定が不完全です');
    console.log('以下の項目をスクリプトプロパティに設定してください:');
    console.log('- SLACK_BOT_TOKEN: SlackボットのOAuthトークン');
    console.log('- DIFY_API_URL: DifyのフローAPIのURL');
    console.log('- DIFY_API_KEY: DifyのAPIキー');
  } else {
    console.log('✅ 設定は正常です');
  }
}

/**
 * 手動テスト用のDify API呼び出し関数
 */
function testDifyAPI() {
  try {
    const testInstruction = "テスト用の指示です";
    const response = callDifyFlow(testInstruction);
    console.log('Difyテスト結果:', response);
  } catch (error) {
    console.error('Difyテストエラー:', error);
  }
}

環境変数の設定

GASの「スクリプトプロパティ」に以下を設定します。

  • DIFY_API_KEY:Step 1で取得したキー
  • SLACK_BOT_TOKEN:Step 2で取得したトークン
  • DIFY_API_URLhttps://api.dify.ai/v1/chat-messages(※チャットボットの場合)

デプロイとSlackへの登録

  1. GASを「ウェブアプリ」としてデプロイし、発行されたURLをコピーします。
  2. Slack API管理画面に戻り、「Event Subscriptions」をONにして、コピーしたURLを「Request URL」に貼り付けます(VerifiedになればOK)。
  3. 「Subscribe to bot events」にapp_mentionを追加して保存します。

【Step 4】いざ実践!Slackで質問してみる

Slackのチャンネルに作成したアプリを追加(インテグレーション)し、メンションを付けて質問してみましょう。

@就業規則ボット 賞与は貰えるの?

Difyが就業規則PDFの内容を参照し、適切な回答を返してくれれば成功です!

まとめ:社内問い合わせ対応を自動化しよう

DifyとSlackを連携させることで、高精度な社内ヘルプデスクを低コストで構築できました。

一度作ってしまえば、PDFを差し替えるだけで常に最新のルールに対応できます。

ぜひこの仕組みを活用して、社内の「よくある質問」対応を自動化し、業務効率を劇的に向上させましょう!

【推奨】業務システム化に有効なアイテム

生成AIを学ぶ

システム化のパートナー(ミラーマスター合同会社)

VPSサーバの選定

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

コメント

コメントする

目次