Slack Bot開発:社内Wikiを検索して回答するQAボットの実装(Bolt.js + Pinecone)
Slack上での情報検索は、リモートワーク時代の必須機能です。しかし、標準の検索機能だけでは不十分な場合が多く、特にNotionやConfluenceといった外部Wikiの内容まではカバーできません。
そこで今回は、SlackのAppフレームワークであるBolt for JavaScript (Bolt.js)と、ベクトルデータベースのデファクトスタンダードであるPineconeを組み合わせて、社内情報を検索して回答するQAボットを実装します。
アーキテクチャ
- データ連携 (Ingestion pipeline): Wikiの更新を検知し、テキストをEmbeddingしてPineconeに保存。
- Slack (Frontend): ユーザーがメンション付きで質問。
- 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のパイプラインさえ構築できれば、あとは「いつものチャットツール」が「全知全能のアシスタント」に早変わりします。