こんにちは。システム開発事業部の中井です。
今回は以前の記事「Knowledge Bases for Amazon Bedrockを触ってみる」にて取り上げた内容の続きで、SlackからBedrockに質問できるようにしてみましたのでご紹介します。
モデルはAnthropic Claude 2.1を利用しました。
はじめに
前回の記事を投稿したあと、Knowledge Bases for Amazon Bedrockを用いて社内ナレッジを有効活用していこうという方針になったのですが、前回のままだとマネジメントコンソール上からの質問しかできませんでしたので、「気軽に質問してみよう」というケースに向いていませんでした。
そこで、弊社がコミュニケーションツールとして日々利用しているSlackのインターフェイス上からBedrockに質問できるようにしてみました。RAGの全般的な内容やSlack Appの使用感については、先日の広報ブログをご覧いただければと思いますが、この記事ではより内部的な仕組みをご紹介します。
Slack Appの追加
Slack API > Your Apps の「Create New App」から、新たなSlack Appを作成します。今回は手順の簡略化のため、「From an app manifest」を選択し、YAMLデータでApp設定を行います。
続けて、Slack Appを導入するワークスペースを選択し、App Manifestの入力画面に遷移します。
以下のYAMLデータを指定します。name や display_name はお好きなものに変更してください。
1display_information:
2 name: Demo App # このAppの名前
3features:
4 bot_user:
5 display_name: demo-app # @demo-appでメンション可能になる
6 always_online: true
7oauth_config:
8 scopes:
9 bot: # このAppに与える権限
10 - app_mentions:read # メンションされたメッセージを読み取る
11 - chat:write # メッセージを投稿する
12 - im:history # 自分へのDMを読み取る
13settings:
14 org_deploy_enabled: false
15 socket_mode_enabled: false
16 token_rotation_enabled: false
問題なければ「Create」でSlack Appを作成します。
作成が完了したあと、「Install to Workspace」でワークスペースに反映しておきます。
AWS Lambda関数の追加
次に、Slackからメッセージイベントを受け取り、回答を生成するLambda関数を追加します。リージョンは us-east-1 (N. Virginia) で作成し、ランタイムとして Node.js 20.x を指定します。
のちほど利用するため、環境変数にSlack AppとKnowledge baseの情報を追加しておきます。
キー | 値として使うもの |
SLACK_BOT_TOKEN | Slack App > OAuth & Permissions > Bot User OAuth Token |
SLACK_SIGNING_SECRET | Slack App > Basic Information > App Credentials > Signing Secret |
KNOWLEDGE_BASE_ID | 前回の記事で追加したKnowledge baseのID |
MODEL_ARN | arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:1 |
また、Knowledge Bases for Amazon BedrockのAPIを利用して回答を生成するためには、Lambda関数の実行ロールに以下のポリシーを含める必要があります。
1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Action": [
7 "bedrock:InvokeModel",
8 "bedrock:Retrieve",
9 "bedrock:RetrieveAndGenerate"
10 ],
11 "Resource": "*"
12 }
13 ]
14}
まず、ユーザーからの入力を受け取り、Amazon BedrockのRetrieveAndGenerate APIを用いて回答を生成する、generateAnswer 関数を作成します。ローカルにて、以下のコードを index.mjs ファイルに追加していきます。
1// AWS SDK - Bedrock Agent Runtime
2import {
3 BedrockAgentRuntimeClient,
4 RetrieveAndGenerateCommand,
5 RetrieveAndGenerateType,
6} from '@aws-sdk/client-bedrock-agent-runtime';
7
8// AWS SDK - Bedrock Agent Runtime - Client
9const bedrock = new BedrockAgentRuntimeClient();
10
11// Knowledge baseを用いてBedrockで回答を生成する
12const generateAnswer = async (text) => {
13 const { output } = await bedrock.send(new RetrieveAndGenerateCommand({
14 input: {
15 text,
16 },
17 retrieveAndGenerateConfiguration: {
18 type: RetrieveAndGenerateType.KNOWLEDGE_BASE,
19 knowledgeBaseConfiguration: {
20 knowledgeBaseId: process.env.KNOWLEDGE_BASE_ID,
21 modelArn: process.env.MODEL_ARN,
22 },
23 },
24 }));
25
26 return output.text;
27};
上記に加えて、Slack App用の公式ライブラリであるBoltを利用し、メンションされた場合やDMが来た場合に、generateAnswer 関数を呼び出して回答する機能を追加します。Boltの詳細については公式ドキュメントをご参照ください。
1// Slack Bolt
2import { App, AwsLambdaReceiver } from '@slack/bolt';
3
4// Slack AWS Lambda Receiver
5const receiver = new AwsLambdaReceiver({
6 signingSecret: process.env.SLACK_SIGNING_SECRET,
7});
8
9// Slack App
10const app = new App({
11 token: process.env.SLACK_BOT_TOKEN,
12 receiver,
13});
14
15// Slack メンションイベント
16app.event('app_mention', async ({ event, say }) => {
17 if (!event.subtype) {
18 await say({
19 text: await generateAnswer(event.text),
20 thread_ts: event.thread_ts ?? event.ts,
21 });
22 }
23});
24
25// Slack メッセージイベント (DM)
26app.event('message', async ({ event, say }) => {
27 if (!event.subtype) {
28 await say({
29 text: await generateAnswer(event.text),
30 thread_ts: event.thread_ts ?? event.ts,
31 });
32 }
33});
34
35// Lambda Handler
36export const handler = async (event, context, callback) => {
37 // Slackからのリトライは無視する
38 if (event.headers['x-slack-retry-num']) {
39 return {
40 statusCode: 200,
41 body: JSON.stringify({
42 message: 'OK',
43 }),
44 };
45 }
46
47 return await receiver.start().then((handler) => {
48 return handler(event, context, callback);
49 });
50};
これらのコードを index.mjs に保存したあと、以下のようなコマンドで依存関係と共にZIP化し、Lambda関数にアップロード・デプロイします。基本的にLambda上では、AWS SDKが標準で利用できますが、2024年1月18日時点では client-bedrock-agent-runtime が含まれていないため、こちらもインストールしておきます。
1# 依存関係のインストール
2$ npm install @aws-sdk/client-bedrock-agent-runtime @slack/bolt
3
4# index.mjs と node_modules が存在することを確認する
5$ ls
6index.mjs node_modules/ package-lock.json package.json
7
8# 依存関係と共にZIP化
9$ zip -r demo_app_package.zip .
最後に、Slackからアクセスするための関数 URLを発行します。Lambda関数の 設定 > 関数 URL から、認証タイプを NONE にして作成することで、パブリックなアクセスを可能にします。
Slack App - Event Subscriptionsの設定
Slackで発生するイベントを上記のLambda関数に受け渡す設定を行うため、Slack App > App Manifest から設定画面を開きます。
App Manifestに以下のような settings.event_subscriptions を追加し、「Save Changes」で変更を反映します。request_url には先ほど発行したLambda関数のURLを指定します。
1display_information:
2 name: Demo App
3features:
4 bot_user:
5 display_name: demo-app
6 always_online: true
7oauth_config:
8 scopes:
9 bot:
10 - app_mentions:read
11 - chat:write
12 - im:history
13settings:
14 event_subscriptions: # 追加
15 request_url: https://hogehoge.lambda-url.us-east-1.on.aws/ # Lambda 関数 URL
16 bot_events: # 購読するイベント
17 - app_mention # メンションされたとき
18 - message.im # DMが来たとき
19 org_deploy_enabled: false
20 socket_mode_enabled: false
21 token_rotation_enabled: false
完成
Slackのチャンネル上でSlack Appにメンションするか、DMで質問すると、Knowledge baseを参照して回答してくれます!
KAIZEN (回答ソースの表示)
実際に弊社内で利用してもらうと、AI からの回答に対するソース提示部分も見れた方がいいという意見が出てきましたので、ハルシネーション対策の意味も込めて機能を追加することにしました。
ソース表示方法
回答に [1] や [2] のような脚注を追加し、「詳細を見る」ボタンでソース内容をモーダルで表示する方法で実現することにしました。
まとめ
このように社内ナレッジを有効活用する仕組みを作ることで、業務効率の向上に繋がります。
今回のLambda関数をCDKでデプロイできる構成をGitHubに置いていますので、もしよろしければご参照ください。
生成AIやRAGについてご相談があれば、お気軽にお問い合わせくださいませ。