0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Slack+Dify+Azure で作る社内ナレッジボット

Last updated at Posted at 2025-05-16

はじめに

社内のSlackに“神降臨”!
大津神(おおつかみ)BOT 構築ガイド

Slackの片隅に、ひそかに住み着いた社内ナレッジの守り神――その名も「大津神」。
このガイドでは、あなたの質問に(ときどき神託っぽく)即答してくれる「大津神」BOTの作り方を伝授します。

「大津神」は、Difyの神秘的な検索拡張生成(RAG)パワーと、Azureのクラウド雲海から皆さまにご加護(回答)を降臨!
社内ドキュメントの海に埋もれた“知恵”を一瞬で召喚してくれるのが特徴です。

image.png

この神様BOTがいれば、

情報アクセスはまさに“神速”

回答のムラも“神の一声”で一貫

みんなで知識を分かち合う“祭り”の場が誕生

「困ったときは大津神」「時には『それは知らぬ…』もご愛敬」――
神様の気まぐれも楽しみつつ、社内の情報共有がぐっと柔らかく、便利になります。

前提条件

  • Node.js 18以上
  • Slackワークスペースの管理者権限
  • Difyアカウント(無料プランでも可能)
  • Microsoft Azureアカウント
  • Githubアカウント

システム構成

「大津神」ボットは以下のコンポーネントで構成されています:

  1. Slackアプリ: ユーザーとのインターフェース
  2. Azure Web App: ボットのバックエンドサーバー(Node.js)
  3. Dify: RAG機能を提供するAIプラットフォーム
  4. Github: ソースコード管理とCICD

image.png

開発手順

1. Difyアプリケーションの作成

Difyは、LLMを活用したアプリケーション構築を支援するプラットフォームです。ここではRAG機能を持つチャットボットを作成します。

  1. Dify公式サイトにアクセスし、アカウントを作成またはログイン

  2. 新しいアプリケーションを作成

    • 「最初から作成」をクリック
    • アプリの種類として「チャットボット」を選択
    • アプリ名に「大津神」と入力し、説明を記入
    • 「作成する」をクリック
  3. プロンプト設定

    • 「オーケストレーション」タブを選択
    • 以下のようなプロンプトを設定(日本語で入力)
あなたは「大津神」という名前の社内ナレッジアシスタントです。以下のガイドラインに従って質問に回答してください:

【役割】
- 社内ドキュメントやナレッジベースに含まれる情報に基づいて、正確で簡潔な回答を提供する
- 検索された情報源を明示し、透明性を確保する
- 丁寧かつ親しみやすい言葉遣いを心がける

【回答方法】
1. 質問の意図を正確に理解する
2. 関連するドキュメントから最適な情報を抽出する
3. 簡潔かつ構造化された回答を作成する
4. 複数の情報源がある場合は統合して回答する
5. 不明点がある場合は率直に認め、わかる範囲で回答する

【禁止事項】
- 検索結果に含まれない情報の作り話
- 専門用語の過度な使用
- 長すぎる回答(3-4段落以内に収める)
- 曖昧な表現や不確かな情報の断定

【回答形式】
- 質問に直接回答する
- 必要に応じて箇条書きやセクションを使用して読みやすくする
- 情報源を「参照情報:」として回答の最後に記載する

常に礼儀正しく、親しみやすい口調で回答し、ユーザーの時間を尊重して簡潔に情報を提供してください。
  1. コンテキスト設定

    • 「コンテキスト」セクションで「検索設定」をクリック
    • 「ナレッジ」タブから作成した知識ベースを選択(まだ作成していない場合は後で設定)
    • 検索数: 3-5(推奨設定)
    • 類似度: 0.7(必要に応じて調整)
    • 再ランキング: 有効
  2. モデル設定

    • 「モデル」セクションでLLMモデルを選択
      • 高性能モデル: Claude 3 OpusまたはGPT-4(精度重視)
      • バランスモデル: Claude 3 SonnetまたはGPT-3.5 Turbo(コスト/性能バランス)
    • パラメータ設定
      • Max Tokens: 4000-8000(十分な回答長を確保)
      • Temperature: 0.1-0.3(低めに設定して正確性を優先)
  3. APIアクセス設定

    • 左側メニューから「APIアクセス」を選択
    • APIキーをメモ(後でAzure環境変数として使用)
    • エンドポイントURLを確認(通常は https://api.dify.ai/v1/chat-messages
  4. ナレッジベースの作成

    • 左側メニューから「ナレッジ」を選択
    • 「作成」ボタンをクリック
    • ナレッジベース名を入力(例: 「社内ドキュメント」)
    • 「ファイルをアップロード」から社内文書をアップロード
      • サポート形式: PDF、Word、Excel、PowerPoint、TXT、Markdownなど
    • ドキュメントのインデックス化が完了するまで待機

image.png

image.png

2. Slackアプリの作成

  1. Slack APIにアクセス

    • 「Create New App」をクリック
    • 「From scratch」を選択
    • App Name(例: 「大津神」)を入力し、ワークスペースを選択
    • 「Create App」をクリック
  2. Bot Token Scopesの設定

    • 左側メニューから「OAuth & Permissions」を選択
    • 「Scopes」セクションまでスクロール
    • 「Bot Token Scopes」で以下のスコープを追加:
      • app_mentions:read
      • chat:write
      • channels:history
      • channels:read
      • groups:history
      • im:history
      • commands(スラッシュコマンドを使う場合)

image.png

  1. イベントサブスクリプションの設定

    • 左側メニューから「Event Subscriptions」を選択
    • 「Enable Events」をオンに切り替え
    • Request URLは後でAzureデプロイ後に設定します(一時的に空のままでOK)
    • 「Subscribe to bot events」セクションで「Add Bot User Event」をクリック
    • 以下のイベントを追加:
      • message.im(DMでのメッセージ)
      • message.channels(チャンネルでのメッセージ)
      • app_mention(ボットへのメンション)
    • 設定は保存しますが、URL検証は後で行います
  2. App Homeの設定

    • 左側メニューから「App Home」を選択
    • 「Messages Tab」を有効化
    • 「Allow users to send Slash commands and messages from the messages tab」にチェック
  3. アプリのインストール

    • 左側メニューから「OAuth & Permissions」に戻る
    • 「Install to Workspace」ボタンをクリック
    • 権限を確認して「許可する」をクリック
    • Bot User OAuth Token(xoxb- で始まる)をメモ
    • Basic Informationの「App Credentials」セクションから「Signing Secret」もメモ

image.png

3. Node.jsアプリケーションの開発

  1. 新しいGitHubリポジトリを作成

    • リポジトリ名: akitademo-slack-bot(任意)
    • README.md、.gitignoreファイル(Node.js用)を追加
  2. リポジトリをローカルにクローン

    git clone https://github.com/yourusername/akitademo-slack-bot.git
    cd akitademo-slack-bot
    
  3. プロジェクト初期化

    npm init -y
    npm install @slack/bolt axios dotenv
    npm install --save-dev nodemon
    
  4. ファイル構造を作成

    mkdir -p src
    touch .env
    touch src/config.js
    touch src/dify.js
    touch src/slack.js
    touch src/index.js
    
  5. .envファイルを編集

    # Slack設定
    SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
    SLACK_SIGNING_SECRET=your-slack-signing-secret
    
    # Dify設定
    DIFY_API_KEY=your-dify-api-key
    DIFY_API_ENDPOINT=https://api.dify.ai/v1/chat-messages
    
    # サーバー設定
    PORT=8080
    NODE_ENV=production
    
  6. src/config.jsファイルを編集

    // 設定ファイル
    require('dotenv').config();
    
    module.exports = {
      // Slack設定
      slackBotToken: process.env.SLACK_BOT_TOKEN,
      slackSigningSecret: process.env.SLACK_SIGNING_SECRET,
      
      // Dify設定
      difyApiKey: process.env.DIFY_API_KEY,
      difyApiEndpoint: process.env.DIFY_API_ENDPOINT || 'https://api.dify.ai/v1/chat-messages',
      
      // サーバー設定
      port: process.env.PORT || 8080,
      
      // アプリケーション設定
      botName: '大津神',
      maxRetries: 3,
      retryDelay: 1000
    };
    
  7. src/dify.jsファイルを編集

    // Dify API連携モジュール
    const axios = require('axios');
    const config = require('./config');
    
    // Dify APIクライアント
    const difyClient = axios.create({
      baseURL: config.difyApiEndpoint,
      headers: {
        'Authorization': `Bearer ${config.difyApiKey}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000 // 30秒タイムアウト
    });
    
    /**
     * Dify APIにクエリを送信し、回答を取得する
     * @param {string} query - ユーザーからの質問
     * @param {string} userId - ユーザーID
     * @returns {Promise<Object>} - Difyからの応答
     */
    async function queryDify(query, userId) {
      try {
        const response = await difyClient.post('', {
          inputs: {},
          query: query,
          response_mode: "blocking",
          user: userId
        });
        
        return {
          answer: response.data.answer,
          references: response.data.references || []
        };
      } catch (error) {
        console.error('Dify API Error:', error.message);
        if (error.response) {
          console.error('Response status:', error.response.status);
          console.error('Response data:', error.response.data);
        }
        throw new Error(`Dify APIエラー: ${error.message}`);
      }
    }
    
    /**
     * リトライロジックを持つDify API呼び出し
     * @param {string} query - ユーザーからの質問
     * @param {string} userId - ユーザーID
     * @returns {Promise<Object>} - Difyからの応答
     */
    async function queryDifyWithRetry(query, userId) {
      let retries = 0;
      let lastError;
      
      while (retries < config.maxRetries) {
        try {
          return await queryDify(query, userId);
        } catch (error) {
          lastError = error;
          console.log(`APIリクエスト失敗 (${retries + 1}/${config.maxRetries}): ${error.message}`);
          
          // 一時的なエラーの場合のみリトライ
          if (error.response && (error.response.status === 429 || error.response.status >= 500)) {
            retries++;
            // 遅延を入れる(指数バックオフ)
            const delay = config.retryDelay * Math.pow(2, retries - 1);
            await new Promise(resolve => setTimeout(resolve, delay));
          } else {
            // 一時的でないエラーはすぐに失敗
            break;
          }
        }
      }
      
      throw lastError || new Error('最大リトライ回数を超えました');
    }
    
    // モジュールのエクスポート
    module.exports = {
      queryDify,
      queryDifyWithRetry
    };
    
  8. src/slack.jsファイルを編集

    // Slackボット機能モジュール
    const { App } = require('@slack/bolt');
    const config = require('./config');
    const { queryDifyWithRetry } = require('./dify');
    
    // Slackアプリの初期化
    const app = new App({
      token: config.slackBotToken,
      signingSecret: config.slackSigningSecret
    });
    
    /**
     * 参照情報からテキストを作成
     * @param {Array} references - 参照情報の配列
     * @returns {string} - フォーマットされた参照テキスト
     */
    function formatReferences(references) {
      if (!references || references.length === 0) {
        return '';
      }
      
      let referenceText = "\n\n*参照情報:*\n";
      references.forEach((ref, index) => {
        referenceText += `>${index + 1}. ${ref.title || '参照ドキュメント'}\n`;
      });
      
      return referenceText;
    }
    
    /**
     * 処理中メッセージを送信
     * @param {Function} say - Slack say関数
     * @param {string} threadTs - スレッドのタイムスタンプ
     */
    async function sendTypingMessage(say, threadTs) {
      try {
        await say({
          text: "調べています...",
          thread_ts: threadTs
        });
      } catch (error) {
        console.error('処理中メッセージの送信エラー:', error);
      }
    }
    
    // メッセージハンドラ
    app.message(async ({ message, say }) => {
      try {
        // ボットのメッセージは無視
        if (message.subtype === 'bot_message') return;
        
        console.log('メッセージ受信:', JSON.stringify(message, null, 2));
        
        // スレッドの親メッセージも処理対象とする
        const threadTs = message.thread_ts || message.ts;
        
        // 処理中メッセージ
        await sendTypingMessage(say, threadTs);
        
        // Dify APIに問い合わせ (リトライ付き)
        const { answer, references } = await queryDifyWithRetry(message.text, message.user);
        
        // 参照情報の整形
        const referenceText = formatReferences(references);
        
        // 結果を返信
        await say({
          text: answer + referenceText,
          thread_ts: threadTs
        });
        
      } catch (error) {
        console.error('メッセージ処理エラー:', error);
        await say({
          text: "エラーが発生しました。しばらくしてからもう一度お試しください。",
          thread_ts: message.thread_ts || message.ts
        });
      }
    });
    
    // メンション対応
    app.event('app_mention', async ({ event, say }) => {
      try {
        console.log('メンション受信:', JSON.stringify(event, null, 2));
        
        // 処理中メッセージ
        await sendTypingMessage(say, event.ts);
        
        // テキストからメンション部分を削除
        const query = event.text.replace(/<@[A-Z0-9]+>/g, '').trim();
        
        // Dify APIに問い合わせ (リトライ付き)
        const { answer, references } = await queryDifyWithRetry(query, event.user);
        
        // 参照情報の整形
        const referenceText = formatReferences(references);
        
        // 結果を返信
        await say({
          text: answer + referenceText,
          thread_ts: event.ts
        });
        
      } catch (error) {
        console.error('メンション処理エラー:', error);
        await say({
          text: "エラーが発生しました。しばらくしてからもう一度お試しください。",
          thread_ts: event.ts
        });
      }
    });
    
    // アプリを起動する関数
    async function startApp() {
      await app.start(config.port);
      console.log(`⚡️ ${config.botName}ボットが起動しました on port ${config.port}`);
    }
    
    module.exports = {
      app,
      startApp
    };
    
  9. src/index.jsファイルを編集

    // メインのアプリケーションファイル
    const { startApp } = require('./slack');
    
    // エラーハンドリング
    process.on('unhandledRejection', (error) => {
      console.error('未処理の拒否:', error);
    });
    
    // アプリ起動
    (async () => {
      try {
        await startApp();
      } catch (error) {
        console.error('アプリケーション起動エラー:', error);
        process.exit(1);
      }
    })();
    
  10. package.jsonのscriptsセクションを編集

    "scripts": {
      "start": "node src/index.js",
      "dev": "nodemon src/index.js",
      "test": "echo \"Error: no test specified\" && exit 0"
    }
    
  11. 変更をコミットしてプッシュ

    git add .
    git commit -m "初期実装: 大津神Slackボット"
    git push origin main
    

4. Azure Web Appのセットアップ

  1. Azure Portalにログイン

  2. リソースグループの作成

    • 「リソースグループ」を検索
    • 「作成」をクリック
    • 名前(例: akitademo-slack-bot-rg)を入力
    • リージョン(例: Japan East)を選択
    • 「確認と作成」→「作成」をクリック
  3. App Serviceプランの作成

    • 「App Service プラン」を検索
    • 「作成」をクリック
    • リソースグループ、名前、リージョンを設定
    • 「Linuxプラン」を選択
    • 価格プランは「B1」など適切なものを選択
    • 「確認と作成」→「作成」をクリック
  4. Web Appの作成

    • 「App Services」を検索
    • 「作成」→「Webアプリ」を選択
    • 基本情報を設定:
      • リソースグループ: 先ほど作成したもの
      • 名前: グローバルに一意な名前(例: akitademo-slack-bot-webapp
      • 公開: コード
      • ランタイムスタック: Node.js 18 LTS
      • オペレーティングシステム: Linux
      • リージョン: Japan East
      • App Serviceプラン: 先ほど作成したもの
    • 「確認と作成」→「作成」をクリック
  5. GitHubとの連携設定

    • Web Appリソースに移動
    • 「デプロイセンター」を選択
    • ソースとして「GitHub」を選択
    • GitHubアカウントを認証
    • 組織、リポジトリ、ブランチを選択
    • 「保存」をクリック
  6. 環境変数の設定

    • Web Appリソースの「構成」→「アプリケーション設定」を選択
    • 「新しいアプリケーション設定」をクリックして以下を追加:
      • SLACK_BOT_TOKEN: Slackから取得したボットトークン
      • SLACK_SIGNING_SECRET: Slackから取得した署名シークレット
      • DIFY_API_KEY: Difyから取得したAPIキー
      • DIFY_API_ENDPOINT: Dify APIのエンドポイント(https://api.dify.ai/v1/chat-messages
      • NODE_ENV: production
      • PORT: 8080
    • 「保存」をクリック
  7. アプリケーションの再起動

    • Web Appリソースの「概要」ページで「再起動」をクリック
  8. デプロイの確認

    • 「デプロイセンター」でデプロイステータスを確認
    • GitHubからのデプロイが成功したことを確認
  9. Web AppのURLを取得

    • 「概要」ページでデフォルトドメインを確認
    • 通常は https://app-name.azurewebsites.net の形式
    • このURLをメモ(Slackのイベントサブスクリプション設定で使用)

5. SlackとAzureの接続

  1. Slack APIダッシュボードに戻る

  2. Event Subscriptionsの設定を更新

    • 「Event Subscriptions」メニューに移動
    • Request URLを設定: https://your-webapp-url.azurewebsites.net/slack/events
    • URLの検証が成功したことを確認(「Verified」と表示される)
    • 「Save Changes」をクリック
  3. アプリを再インストール

    • 「OAuth & Permissions」に移動
    • 「Install to Workspace」または「Reinstall to Workspace」をクリック
    • 確認ダイアログで「許可する」をクリック

6. ボットのテストと動作確認

  1. Slackでのテスト
    • ボットを特定のチャンネルに招待: /invite @大津神
    • メンションして質問: @大津神 会社の理念について教えて
    • DMで質問: 有給休暇の申請方法は?
    • さまざまなカテゴリの質問を試して、フロー分岐が正しく動作する›か確認

image.png

  1. レスポンスの確認

    • 質問カテゴリに応じて適切な応答パスが選択されているか
    • 知識ベースからの情報が適切に取得・整形されているか
    • 参照情報が表示されているか
  2. ログの確認

    • Azure Web Appの「ログストリーム」で実時間ログを確認
    • リクエストの処理、Dify APIとの通信、応答の送信が正常に行われているか確認
    • エラーや警告メッセージを確認

チャットフローのチューニングとカスタマイズ

1. フローの最適化

質問分類器の改善

  • ユーザーからのフィードバックや実際の質問パターンに基づいて分類条件を調整
  • より詳細なクラス分けが必要な場合はクラスを追加
  • 分類の説明文を明確かつ具体的にする

image.png

知識検索の最適化

  • 各カテゴリに特化した知識ベースを作成(IT関連、HR関連、製品情報など)
  • 検索パラメータの調整:
    • 情報が不足する場合は検索数を増やす(3→5→8)
    • 検索結果が広すぎる場合は類似度閾値を上げる(0.7→0.8)
    • 検索結果が少なすぎる場合は類似度閾値を下げる(0.7→0.6)

応答テンプレートの調整

  • 各分岐パスごとに応答スタイルをカスタマイズ
  • 技術的な質問、HR関連の質問、一般的な会話などカテゴリに応じたトーンを設定
  • 専門用語の使用レベルを調整

2. ドキュメントの拡充

  • 定期的に最新の情報でナレッジベースを更新
  • ドキュメントを適切なサイズにチャンク分割
  • 関連するメタデータを追加して検索精度を向上
  • よくある質問と回答のペアを追加

3. 応答品質のモニタリング

  • フローの各ステップでのパフォーマンスを記録
  • ユーザーからのフィードバックを収集する仕組みを導入
  • 定期的に応答の質をレビュー
  • 誤分類や不適切な応答のパターンを特定し調整

拡張機能の開発

1. 会話履歴の管理

  • 永続的なデータベース(MongoDB、DynamoDB、Azureテーブルストレージなど)で会話履歴を管理
  • ユーザーごとのコンテキスト情報を保持
  • プライバシーとデータ保持ポリシーを設定

2. スラッシュコマンド

Slackのスラッシュコマンドを追加して、ボットの機能を拡張:

// スラッシュコマンド
app.command('/otsugami', async ({ command, ack, respond }) => {
  // コマンドの確認
  await ack();
  
  const { text } = command;
  const args = text.split(' ');
  const subCommand = args[0].toLowerCase();
  
  try {
    switch (subCommand) {
      case 'help':
        // ヘルプ情報を表示
        await respond('*大津神ボット コマンド一覧*\n• `/otsugami help` - このヘルプを表示\n• `/otsugami search キーワード` - 知識ベースを検索\n• `/otsugami feedback フィードバック` - フィードバックを送信');
        break;
        
      case 'search':
        // 直接検索
        const searchQuery = args.slice(1).join(' ');
        await respond(`"${searchQuery}" を検索中...`);
        
        // 検索処理
        const response = await queryDifyWithRetry(searchQuery, command.user_id);
        const referenceText = formatReferences(response.references);
        
        await respond(response.answer + referenceText);
        break;
        
      case 'feedback':
        // フィードバックを受け取る
        const feedback = args.slice(1).join(' ');
        logger.info(`フィードバック受信: ${feedback} from ${command.user_id}`);
        
        // フィードバックの保存処理(実装は省略)
        await respond('フィードバックをありがとうございます!改善に役立てます。');
        break;
        
      default:
        await respond('コマンドが認識できません。`/otsugami help` でコマンド一覧を確認できます。');
    }
  } catch (error) {
    logger.error(`スラッシュコマンド処理エラー: ${error}`);
    await respond('コマンド処理中にエラーが発生しました。しばらくしてからもう一度お試しください。');
  }
});

3. 通知機能

  • 定期的に重要情報を通知
  • ユーザーのアクティビティに基づく推奨情報を提供
  • リマインダー機能を実装

4. 多言語サポート

  • 複数言語での質問と回答をサポート
  • 言語に応じたフロー設計
  • 自動言語検出機能の実装

セキュリティと管理

1. セキュリティ対策

  • 環境変数とシークレットキーの安全な管理
  • API呼び出しの監査とログ記録
  • アクセス制御メカニズムの実装
  • センシティブ情報の取り扱いポリシーの設定

2. 監視とアラート

  • Azure Application Insightsでの詳細なモニタリング
  • エラー率、応答時間などの主要メトリクスの監視
  • しきい値に基づくアラートの設定
  • 定期的な正常性チェック

3. トラブルシューティングガイド

一般的な問題と解決策

ボットが応答しない

  • Azure環境変数を確認
  • Slackトークンが最新か確認
  • ログでエラーメッセージを確認

「invalid_auth」エラー

  • Slackアプリを再インストールして新しいトークンを取得
  • Azure環境変数のSLACK_BOT_TOKENを更新
  • Webアプリを再起動

イベントサブスクリプション検証の失敗

  • URLが正しいか確認
  • アプリが起動しているか確認
  • ネットワーク設定やファイアウォールを確認

Dify API通信エラー

  • APIキーとエンドポイントを確認
  • チャットフロー構成を確認
  • ネットワーク接続をテスト

メモリリークや性能低下

  • Node.jsのメモリ使用量を監視
  • 長時間実行後の性能変化を確認
  • 定期的なアプリ再起動をスケジュール

運用とメンテナンス

1. 定期的なメンテナンス手順

  • 依存パッケージの更新
  • セキュリティパッチの適用
  • ログファイルのローテーション
  • データバックアップとリカバリー手順の確認

2. スケーリング戦略

  • ユーザー数の増加に応じたApp Serviceプランのスケールアップ
  • 地理的に分散したユーザーに対応するためのリージョン展開
  • 負荷分散とフェイルオーバー構成

3. 継続的な改善

  • ユーザーフィードバックの収集と分析
  • 使用パターンの分析と最適化
  • 新機能のテストと導入
  • ドキュメントと知識ベースの定期的な更新

まとめ

Difyのチャットフロー機能を活用した「大津神」ボットは、単純なRAGシステムよりも高度な会話体験を提供します。質問の分類、状況に応じた知識検索、条件分岐による応答生成を組み合わせることで、ユーザーの意図をより正確に理解し、適切な情報を提供できます。

Azure Web AppとSlackの統合により、安定した運用環境とユーザーフレンドリーなインターフェースを実現し、チーム内のナレッジ共有と情報アクセスを大幅に効率化します。

チャットフローの柔軟性を活かし、組織のニーズに合わせてカスタマイズすることで、より価値の高いアシスタントへと発展させていくことができます。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?