
みなさん、こんにちは。4月に入社しました、メディアサプライチェーン技術部開発第2グループの山本です。普段の開発やビジネスの業務において、以下のような場面に遭遇したことはないでしょうか?
「あのファイル、どこにあったっけ…?」「情報が点在していてどこからみたらいいか分からない」「探すのに時間がかかるし、もう諦めよう・・・」
今回、Bedrockを使ってナレッジベースを作成し、このような課題を解決したいと思います。
システム構成図
今回作るシステムの構成図は以下の通りです。

Slackアプリを使い、Lambdaとの連携を行います。ベクトルデータストレージにはS3 Vectorsを使います(2025年7月に発表されたサービス)。OpenSearch Serverlessを使った構築も可能ですが、料金を抑える目的からS3 Vectorsを選択しました(詳細は後述)。検索のレイテンシに関して、S3 Vectorsは1秒未満とのことですので、パフォーマンスとしても問題ないと考えました。執筆時点(2025年10月)ではプレビュー機能で東京リージョンでの利用は不可だったため、今回はバージニア北部に構築しました。
レイテンシについての詳細は下記を参照してください。
最終的に下記のようにSlackで社内ナレッジに関する質問を行い、LLMから回答が得られればゴールです。

料金について
補足として、S3 VectorsとOpenSearch Serverlessの料金について記載します。リージョンはバージニア北部での値になります。
料金について(クリックで展開)
S3 Vectors
- ストレージ料金 : 0.06 USD / GB
- PUTリクエスト : 0.20 USD / GB
- GET,LIST,その他全てのリクエスト : 0.055 USD / 1000 リクエスト
- クエリリクエスト : 0.0025 USD / 1000 リクエスト
- クエリで処理するベクターデータ
- 最初の10万ベクター : 0.0040 USD / TB
- 10万ベクター以降 : 0.0020 USD / TB
料金例(公式より抜粋)
Pricing example 1:
You are building a RAG workflow to provide accurate and relevant text responses to customers. You have 10 million vectors, each consisting of 4 KB vector data, 1 KB of filterable metadata, 1 KB of non-filterable metadata, and a key (0.17 KB each), totaling 6.17KB per vector. The 10 million vectors are split into 40 indexes for each of your customers, consisting of 250,000 vectors each. You update the vectors in your vector index every six months, removing old vectors and uploading new ones. This results in PUT of ~16.7% of your data per month. This example uses pricing for the US East (N. Virginia) AWS Region.
合計: $11.38(月あたり)
詳細は下記を参照してください。言語設定「English」から確認できます。
OpenSearch Serverless
OpenSearch Serverlessの場合、料金体系はやや複雑なところがありますので、詳細は省きますが、利用時は最小構成でも$170(月あたり)程になります。
詳細は下記を参照してください。
比較
料金を比較すると、月あたりS3 Vectorsが「$11.38」、OpenSearch Serverlessが「$170」で、S3 Vectorsを使うとかなり費用が抑えられることが分かります。あくまで前述の構成における費用になりますので、本番利用の際は必ずご自身で費用については計算してください。
構築
システム構築にあたり、今回やることの大まかな流れは以下の通りです。
- ナレッジベースを作成
- Lambda 関数を作成
- Slackアプリを作成
ナレッジベースを作成
まずは、社内ナレッジ用S3バケットを作成します。リージョンは「バージニア北部 (us-east-1)」にしてください。ここにtxtやpdfなどのファイル形式の社内ナレッジをアップロードします。
サポートされているファイル形式の詳細については下記を参照してください。
次に、Bedrockのコンソール画面にてモデルアクセスのリクエストを行い、モデルを有効化します。今回はコスト面を考慮し「Amazon Nova Micro」を使います。

モデルの有効化ができたら、ナレッジベースの作成に移ります。
今回は、S3 Vectorsを使うので「ベクトルストアを含むナレッジベース」を選択します。

データソースには前述のS3を指定します。

タソースにはS3を選択
埋め込みモデルには「Titan Text Embeddings V2」を選択します。

ベクトルデータベースには「Amazon S3 Vectors」を選択します。

ナレッジベースの作成ができたら、データソースの同期を行います。これで社内ナレッジがベクトルデータ化され、ベクトルストレージに保存されます。同期は社内ナレッジをS3にアップロードするごとに必要です。

以上で、ナレッジベースの作成は完了です。
Lambda関数の作成
ナレッジベースの作成ができたら、次は Lambda 関数の作成を行います。
Lambda 関数の役割は以下の通りです。
- Slackからのメッセージを取得
- メッセージをLLMへ送信
- LLMからの回答をSlackへ投稿
ランタイムには「Node.js 22.x」、アーキテクチャには「x86_64」を選択します。

タイムアウト時間は3分に変更します。

Lambda 関数の実行ロールに権限を付与します。今回は「AmazonBedrockFullAccess」を付与します。本番利用の際は必要に応じて権限は最小にしてください。
Slackアプリ用に関数URLを作成します。今回は構成をシンプルにするために認証タイプには「NONE」を選択します。ただし、URLを知っている人なら誰でも実行できますので取り扱いには注意してください。

コードは以下の通りです
コード(クリックで展開)
import { WebClient } from '@slack/web-api'; import * as AgentRuntime from '@aws-sdk/client-bedrock-agent-runtime'; const BedrockAgentRuntimeClient = AgentRuntime.BedrockAgentRuntimeClient; const RetrieveAndGenerateCommand = AgentRuntime.RetrieveAndGenerateCommand; const AWS_REGION = "us-east-1"; const MODEL_ID = "amazon.nova-micro-v1:0"; const KNOWLEDGE_BASE_ID = process.env.KNOWLEDGE_BASE_ID; const agentClient = new BedrockAgentRuntimeClient({ region: AWS_REGION }); const TOKEN = process.env.OAUTH_TOKEN; const TARGET_CHANNEL = process.env.TARGET_CHANNEL; const retrieveAndGenerate = async (prompt) => { const command = new RetrieveAndGenerateCommand({ input: { text: prompt }, retrieveAndGenerateConfiguration: { type: "KNOWLEDGE_BASE", knowledgeBaseConfiguration: { knowledgeBaseId: KNOWLEDGE_BASE_ID, modelArn: MODEL_ID } } }); const response = await agentClient.send(command); const output = response.output; const text = output.text; console.log("推論結果: ", text); return text; }; const sendMessage = async (text) => { if (!TOKEN || !TARGET_CHANNEL) { console.log("OAUTH_TOKEN または TARGET_CHANNEL が取得できませんでした"); return; } const message = { channel: TARGET_CHANNEL, text: text }; const slack = new WebClient(TOKEN); await slack.chat.postMessage(message); } export const handler = async (event) => { try { console.log("event: ", event); const headers = event.headers || {}; const retryNum = headers['x-slack-retry-num']; const retryReason = headers['x-slack-retry-reason']; if (retryNum !== undefined || retryReason === 'http_timeout') { console.warn(`Slackリトライリクエストを受信 (Num: ${retryNum}, Reason: ${retryReason})。処理をスキップします。`); return { statusCode: 200, body: JSON.stringify({}),}; } let body = null; try { body = JSON.parse(event.body); } catch (e) { console.log('Invalid JSON format') return { statusCode: 400, body: JSON.stringify({ error: "Invalid JSON format" }) }; } if (body.challenge) { return { statusCode: 200, body: JSON.stringify({ challenge: body.challenge }) }; } let question = body.event.text; const userId = body.authorizations[0].user_id; const mention = `<@${userId}>`; if (question.startsWith(mention)) { question = question.substring(mention.length).trim(); } console.log('ユーザーからの質問:', question); if (!question) { console.log("質問を取得できませんでした") return { statusCode: 400, body: JSON.stringify({ message: "質問を取得できませんでした" }), }; } const prompt = `ユーザーからの質問: ${question}\n\n回答は日本語で行なってください。`; const answer = await retrieveAndGenerate(prompt); const replayMessage = answer ? answer : "推論結果が取得できませんでした。"; if (answer) await sendMessage(replayMessage); return { statusCode: 200, body: JSON.stringify({ answer }) }; } catch (error) { console.log("例外が発生しました。", error); return { statusCode: 500, body: JSON.stringify({ message: "例外が発生しました。" })}; } };
Slackアプリの作成
最後にSlackアプリを作成します。「From scratch」を選択します。

「Subscribe to bot events」に「app_mention」を追加します。Request URLには前項で作成した Lambda 関数の関数URLを指定します。指定するとチャレンジ認証が行われ、認証に成功すると「Verified」が表示されます。

これで本アプリにメンションをつけてメッセージを投稿すると、Lambda 関数にメッセージが送信されます。
次に「OAuth&Permissions」よりSlackアプリの権限を設定します。Slackアプリに対してメンションをつけてメッセージを投稿すると、Lambdaを呼び出すようにしたいため、「app_mentions:read」を付与しています。

最後に本アプリを登録したいSlackチャンネルにインストールします。

質問してみましょう
実際に社内ナレッジに関する質問をしてみましょう。
まずは比較のため、学習させていない状態で質問を行なってみます。

当然、回答は得られません。今度は、質問事項について学習させます。学習させたいデータをPDF化し、社内ナレッジ用S3にアップロードし、ナレッジベースのデータ同期を行います。アップロードごとにデータ同期は必要ですので忘れないようにしてください(大事なことなので再掲)。

今回は時事ネタとして、全日本テニス選手権について学習させました。本番利用の際は社内ナレッジを学習させてください。
それでは、再度質問をしてみましょう。

無事、学習データに関する回答が得られていますね!
おわりに
今回、Bedrockを使ったナレッジベースの作成を行いました。Bedrockのナレッジベース機能を使うことで、ナレッジデータのベクトル化やLLMとナレッジベースの接続などややこしい設定はAWS側が行ってくれるため、かなり容易にシステムを構築できたのではないでしょうか。料金面においても、S3 Vectorsの利用により費用をかなり抑えることができており、実運用におけるハードルが下がっているかと思います。今回紹介したシステムを活用し、社内ナレッジ管理の改善にぜひ役立ててみてください。
Source link
コメント