Slack Bot開発:社内Wikiを検索して回答するQAボットの実装(Bolt.js + Pinecone)

Slack上での情報検索は、リモートワーク時代の必須機能です。しかし、標準の検索機能だけでは不十分な場合が多く、特にNotionやConfluenceといった外部Wikiの内容まではカバーできません。

そこで今回は、SlackのAppフレームワークであるBolt for JavaScript (Bolt.js)と、ベクトルデータベースのデファクトスタンダードであるPineconeを組み合わせて、社内情報を検索して回答するQAボットを実装します。

アーキテクチャ

  1. データ連携 (Ingestion pipeline): Wikiの更新を検知し、テキストをEmbeddingしてPineconeに保存。
  2. Slack (Frontend): ユーザーがメンション付きで質問。
  3. App Server (Backend): Bolt.jsがイベントを受け取り、Pineconeを検索してOpenAIに回答生成を依頼。

実装ステップ

1. Boltアプリのセットアップ

npm install @slack/bolt dotenv openai @pinecone-database/pinecone

app.js:

const { App } = require('@slack/bolt');
const { OpenAI } = require('openai');
const { Pinecone } = require('@pinecone-database/pinecone');
require('dotenv').config();

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

const openai = new OpenAI();
const pinecone = new Pinecone();
const index = pinecone.Index("wiki-index");

// メンションされた時の処理
app.event('app_mention', async ({ event, say }) => {
  try {
    const question = event.text.replace(/<@.*?>/, "").trim(); // メンション除去
    
    // 1. 質問のベクトル化
    const embedding = await openai.embeddings.create({
      input: question,
      model: "text-embedding-3-small"
    });

    // 2. Pinecone検索
    const queryResponse = await index.query({
      vector: embedding.data[0].embedding,
      topK: 3,
      includeMetadata: true
    });

    const context = queryResponse.matches.map(match => match.metadata.text).join("\n\n");

    // 3. 回答生成
    const completion = await openai.chat.completions.create({
      messages: [
        { role: "system", content: "以下のコンテキストに基づいて、簡潔に回答してください。" },
        { role: "user", content: `Context: ${context}\n\nQuestion: ${question}` }
      ],
      model: "gpt-4-turbo"
    });

    await say(completion.choices[0].message.content);

  } catch (error) {
    console.error(error);
    await say("エラーが発生しました。");
  }
});

(async () => {
  await app.start(process.env.PORT || 3000);
  console.log('⚡️ Bolt app is running!');
})();

2. Pineconeへのデータ投入

定期的にWiki(例えばNotion APIなど)からデータを取得し、PineconeへUpsertするバッチ処理が必要です。

// upsert_wiki.js (Concept)
const docs = await fetchFromNotion();
const vectors = [];

for (const doc of docs) {
  const emb = await openai.embeddings.create({ input: doc.content, model: "text-embedding-3-small" });
  vectors.push({
    id: doc.id,
    values: emb.data[0].embedding,
    metadata: { text: doc.content, url: doc.url }
  });
}

await index.upsert(vectors);

Socket Mode vs Public Endpoint

社内セキュリティの関係でパブリックエンドポイント(Webhook URL)を公開できない場合は、Socket Modeを利用しましょう。ファイアウォールの内側からでも安全にSlackイベントを受信できます。

app = new App({ socketMode: true, appToken: process.env.SLACK_APP_TOKEN, ... }); とするだけです。

まとめ

Bolt.jsを使えば、Slackアプリの開発は驚くほど簡単になります。RAGのパイプラインさえ構築できれば、あとは「いつものチャットツール」が「全知全能のアシスタント」に早変わりします。