社内WikiやWorksのデータを活用してAIエージェントを作れないか頑張ってみた
2024-10-21 勉強会
システム部開発ユニットの濱田です!
8月末に行われた勉強会2週目発表順決めあみだくじで持ち前の強運?を発揮してしまい勉強会2週目のトップバッター枠を引き当ててしまいました!
今回の勉強会では「社内で蓄積される情報を活用する」をテーマに勉強会に取り組んでみました!
最後までお読みいただけますと幸いです!
8月末に行われた勉強会2週目発表順決めあみだくじで持ち前の強運?を発揮してしまい勉強会2週目のトップバッター枠を引き当ててしまいました!
今回の勉強会では「社内で蓄積される情報を活用する」をテーマに勉強会に取り組んでみました!
最後までお読みいただけますと幸いです!
第1章 何作ろう
前回と同じくまたもや題材選びで悩む毎日……。
しかし悩み続けているからこそあるチャットに目が留まりました。(幻の可能性もなくはなりですがそういうことにしておきましょう)「社内Wikiの情報をChatGPTみたいにチャットで取得できたらな~」
はい待ってました、ありがとうございます。
それならば前回植木さんが勉強会で取り上げていたRAGを活用して社内情報に対応したチャットアプリケーションを作成してみましょう!
※RAG(Retrieval-augmented generation)とは、LLMが事前に学習していないデータ(社内情報など)を検索し得た情報をLLMの入力にプラスαすることで回答の精度を高める手法を指します。
しかし悩み続けているからこそあるチャットに目が留まりました。(幻の可能性もなくはなりですがそういうことにしておきましょう)「社内Wikiの情報をChatGPTみたいにチャットで取得できたらな~」
はい待ってました、ありがとうございます。
それならば前回植木さんが勉強会で取り上げていたRAGを活用して社内情報に対応したチャットアプリケーションを作成してみましょう!
※RAG(Retrieval-augmented generation)とは、LLMが事前に学習していないデータ(社内情報など)を検索し得た情報をLLMの入力にプラスαすることで回答の精度を高める手法を指します。
RAGを簡易的に作る方法は…?
そうはいってもRAGに関する専門的な知識を有していないのでどのように実装していけばいいか検索しているとあるサービスを発見しました。
それはAWSが提供している「Amazon Bedrock」というサービスです。
Amazon Bedrockの紹介ページ
サイトには以下の文言が記載されていました。
それはAWSが提供している「Amazon Bedrock」というサービスです。
Amazon Bedrockの紹介ページ
サイトには以下の文言が記載されていました。
引用
Amazon Bedrock は、単一の API を介して AI21 Labs、Anthropic、Cohere、Meta、Mistral AI、Stability AI、および Amazon といった大手 AI 企業からの高性能な基盤モデル (FM) を選択できるフルマネージドサービスで、セキュリティ、プライバシー、責任ある AI を備えた生成 AI アプリケーションを構築するために必要な幅広い機能を提供します。Amazon Bedrock を使用すると、ユースケースに最適な FM を簡単に試して評価したり、微調整や検索拡張生成 (RAG) などの手法を使用してデータに合わせてカスタマイズしたり、エンタープライズシステムとデータソースを使用してタスクを実行するエージェントを構築したりできます。Amazon Bedrock はサーバーレスであるため、インフラストラクチャを管理する必要がありません。また、使い慣れた AWS サービスを使用して、生成 AI 機能をアプリケーションに安全に統合してデプロイできます。
Knowledge Bases for Amazon Bedrockを使えばいいのか!
以上の引用からAmazon Bedrockを利用すれば簡単にRAGが実現できそうだとわかりました。
ですがまだ詳細な実装手順が見えてこないのでもう少し調べてみると、どうやらKnowledge Bases for Amazon Bedrockなるものを利用すればよいことが判明。
また以下の記事でRAGのイメージ図や実装方法がわかりやすくまとまっていたので、こちらを参考に実装を進めていくこととしました。
参考記事
ですがまだ詳細な実装手順が見えてこないのでもう少し調べてみると、どうやらKnowledge Bases for Amazon Bedrockなるものを利用すればよいことが判明。
また以下の記事でRAGのイメージ図や実装方法がわかりやすくまとまっていたので、こちらを参考に実装を進めていくこととしました。
参考記事
画像出典:https://aws.amazon.com/jp/blogs/news/knowledge-bases-for-amazon-bedrock-rag-patent/
第2章 千里の道もローカルから
利用するサービスと大まかな実装イメージを掴むことはできたのでまずはローカルで疑似社内Wikiを作成し、個人で契約しているAWSアカウントでBedrockに慣れ親しんでいくことにしました。
社内Wikiで使用している「Growi」
インターパークでは社内Wikiとして「Growi」というサービスを利用しています。
Growiとは無料で使える、ドキュメント共有・管理プラットフォームです。
Growi
まずはGrowiをローカルで利用できるようにしていきます。
Growiとは無料で使える、ドキュメント共有・管理プラットフォームです。
Growi
まずはGrowiをローカルで利用できるようにしていきます。
環境準備
今回はAWSにおける仮想サーバーを提供するAmazon EC2を利用してサーバーを立ち上げ、そのサーバーにGrowiをインストールしていきます。
サーバーの詳細情報は以下の通りです。
・OS⇒Amazon Linux2023
・インスタンスタイプ⇒t3.small(最初無料利用枠で挑戦したのですが、Growiで利用されているElasticsearchのコンテナが立ち上がらず1段階性能を上げた次第です(涙))
公式ドキュメントを見てみるとdocker-composeを使用してのインストール方法が記載されていたので、その方法を採用することとしました。
Growi公式ドキュメント
以下、AL2023でdockerおよびdocker-composeを利用できる状態にするために実行したコマンドになります。
サーバーの詳細情報は以下の通りです。
・OS⇒Amazon Linux2023
・インスタンスタイプ⇒t3.small(最初無料利用枠で挑戦したのですが、Growiで利用されているElasticsearchのコンテナが立ち上がらず1段階性能を上げた次第です(涙))
公式ドキュメントを見てみるとdocker-composeを使用してのインストール方法が記載されていたので、その方法を採用することとしました。
Growi公式ドキュメント
以下、AL2023でdockerおよびdocker-composeを利用できる状態にするために実行したコマンドになります。
Amazon Linux2023にdocker・docker-composeをインストール
sudo dnf update
sudo dnf install -y docker
sudo systemctl start docker
sudo gpasswd -a $(whoami) docker
sudo service docker restart
sudo systemctl enable docker
sudo curl -L "https://github.com/docker/compose/releases/download/v2.29.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
git・growi-docker-composeをインストール
docker・docker-composeのインストールが完了したところで、続いてgrowi-docker-composeをインストールしていきます。
sudo dnf install git -y
git clone https://github.com/weseek/growi-docker-compose.git growi
インストールが完了したら、上記コマンドで作成したgrowiディレクトリに移動してコンテナを立ち上げます。
dockerコンテナを立ち上げる
docker-compose up -d
立ち上げたサーバーに設定したセキュリティグループ>インバウンドルールで3000番ポートを解放し、docker-compose.ymlファイルのportsを変更することでlocalhost以外のアクセスを許可した状態で、ブラウザ上からサーバーにアクセスすると初回セットアップ画面に遷移することができました。
適当な架空の会社の社内情報を作成してみる
Growiをローカルで動かせるようにしたところでまずは社内情報っぽい「就業規則」と「社内ルール」についてまとめた記事を作成してみることにしました。
架空の企業の就業規則と社内ルールについてはChatGPTに作成してもらいました。
(ChatGPT様様ですね本当に…)
完成した記事は以下の通りです。
架空の企業の就業規則と社内ルールについてはChatGPTに作成してもらいました。
(ChatGPT様様ですね本当に…)
完成した記事は以下の通りです。
就業規則
社内ルール
どちらの内容もイケイケな企業って感じがしますね。(適当)
Growiの記事をmarkdownファイルとして出力する
疑似社内WikiができたのでWiki内の情報をナレッジベースで利用できるようにするために、Wikiの記事をmarkdownファイル化して、最終的にナレッジベースと同期させるAmazon S3の任意のバケットにファイル群を配置するようにしていきます。
当作業においては以下の記事を参考に作業を行っていきました。
参考記事
当作業においては以下の記事を参考に作業を行っていきました。
参考記事
手順1 Growiのデータエクスポート機能を使用してアーカイブデータを作成する
/adminページにアクセスして、「データアーカイブ」>「アーカイブデータの新規作成」を選択して、「MongoDB Page Collections」カテゴリ内の「Pages」「Revisions」を選択してアーカイブデータを作成します。
※「Pages」「Revisions」の説明については公式ドキュメントに記載がありますので割愛させていただきます。
※「Pages」「Revisions」の説明については公式ドキュメントに記載がありますので割愛させていただきます。
手順2 作成されたzipファイルを展開してMarkdownファイルをエクスポートする
記事を参考にMarkdownファイルをエクスポートしていきます。作業にあたってはPythonを使用しました。
Markdownファイルのエクスポート
import json
import os
import re
with open('pages.json', 'r') as f:
pages = json.load(f)
with open('revisions.json', 'r') as f:
revisions = json.load(f)
pages_dict = {page['_id']: page['path'] for page in pages}
revisions_dict = {revision['pageId']: revision['body'] for revision in revisions}
pattern = re.compile(r'/Documents')
for page_id, path in pages_dict.items():
if page_id in revisions_dict:
Documents配下以外のファイルは作成しない
if not pattern.search(path):
continue
ファイルパスを作成
path = path.replace(' ', '_')
file_path = './output' + path + '.md'
os.makedirs(os.path.dirname(file_path), exist_ok=True)
body = revisions_dict[page_id]
ファイルを書き込む
with open(file_path, 'w') as f:
f.write(body)
以上のコードにより無事、先ほど作成した「就業規則」「社内ルール」の情報が記載されたMarkdownファイルを作成することができました。
作成したファイルはナレッジベースと連携させるS3の任意のバケットにアップロードしておきます。
作成したファイルはナレッジベースと連携させるS3の任意のバケットにアップロードしておきます。
それでは作成したファイルをS3の任意のバケットにアップロードしましょう。
第3章 文章さよなら、ベクトルこんにちは
Pinecone…?松ぼっくり…?
S3バケットにファイルを配置することができたので、いよいよナレッジベースの作成に入っていきます。
調べるとRAGにおいては、まずLLMに参照してほしいドキュメントをチャンク化(対象を分ける)し、そのそれぞれに対して埋め込み(テキスト・画像データをベクトルに変換する)を行い、ベクトル情報を保存できるベクトルDBに格納する手法が一般的なようです。
ベクトル化することでユーザーから求められている情報と意味的に似た情報を検索して情報を取得することができるようになり、結果的にLLMの回答の質が上がるという点からナレッジベースの作成にあたってもこのプロセスを踏む必要があるようでした。
今回はベクトルDBにPineconeというサービスを採用してナレッジベースの設定を進めていくことにしました。
(※Pineconeの使用・設定方法については割愛させていただきます。)
Pinecone
設定手順は以下の通りです。
調べるとRAGにおいては、まずLLMに参照してほしいドキュメントをチャンク化(対象を分ける)し、そのそれぞれに対して埋め込み(テキスト・画像データをベクトルに変換する)を行い、ベクトル情報を保存できるベクトルDBに格納する手法が一般的なようです。
ベクトル化することでユーザーから求められている情報と意味的に似た情報を検索して情報を取得することができるようになり、結果的にLLMの回答の質が上がるという点からナレッジベースの作成にあたってもこのプロセスを踏む必要があるようでした。
今回はベクトルDBにPineconeというサービスを採用してナレッジベースの設定を進めていくことにしました。
(※Pineconeの使用・設定方法については割愛させていただきます。)
Pinecone
設定手順は以下の通りです。
手順1 ナレッジベースの詳細を入力
まずはAmazon Bedrockにアクセスしてサイドメニューからナレッジベースを選択しましょう。
データソースの選択ではにS3を利用するようチェックを付けます。
データソースの選択ではにS3を利用するようチェックを付けます。
手順2 データソースを設定
S3 URIにはMarkdownファイルを配置したS3バケットを指定するようにします。
手順3 埋め込みモデルを選択肢、ベクトルストアを設定する
埋め込みモデルは多言語に対応した「Embed Multilingual v3」を使用するようにします。
この際、事前にモデルアクセスから任意のリージョンで「Embed Multilingual v3」のアクセスできるようにしておく必要があります。
(Claude 3.5 Sonnetも後ほど使用するのでアクセスできるようにしてあります。)
ベクトルデータベースについては上述の通りPineconeを使用するよう設定します。
(※設定方法の詳細については割愛させていただきます。)
これにより、ナレッジベースが作成されました!
この際、事前にモデルアクセスから任意のリージョンで「Embed Multilingual v3」のアクセスできるようにしておく必要があります。
(Claude 3.5 Sonnetも後ほど使用するのでアクセスできるようにしてあります。)
ベクトルデータベースについては上述の通りPineconeを使用するよう設定します。
(※設定方法の詳細については割愛させていただきます。)
これにより、ナレッジベースが作成されました!
ナレッジベースは果たして機能するか…?
作成されたナレッジベースが果たして本当に機能するかどうかテストしてみます。
データソースが同期されたことを確認し、「Claude 3.5 Sonnet」モデルを利用して社内ルールに関する質問をしてみたいと思います。
データソースが同期されたことを確認し、「Claude 3.5 Sonnet」モデルを利用して社内ルールに関する質問をしてみたいと思います。
いざ参る…!
質問:TechFusion Inc.のイノベーション・デイはいつでしたっけ?
回答:TechFusion Inc.のイノベーション・デイは毎月第2金曜日に設定されています。この日は従業員が通常の業務から離れ、個人のプロジェクトや新しい技術の習得、社内ハッカソンなどに取り組むことが奨励されています。イノベーション・デイの成果は翌週の全社会議で発表することが求められています。
作成したMarkdownファイルに記載されていた情報通りの回答をしてくれました!どのソースを参照したのかについても表示されているのでナレッジベースが有効に機能しているようです。
回答:TechFusion Inc.のイノベーション・デイは毎月第2金曜日に設定されています。この日は従業員が通常の業務から離れ、個人のプロジェクトや新しい技術の習得、社内ハッカソンなどに取り組むことが奨励されています。イノベーション・デイの成果は翌週の全社会議で発表することが求められています。
作成したMarkdownファイルに記載されていた情報通りの回答をしてくれました!どのソースを参照したのかについても表示されているのでナレッジベースが有効に機能しているようです。
念のため…
念のため、普段利用しているClaude3.5 Sonnetにも同じ質問をしてみます。
回答:申し訳ありませんが、"TechFusion Inc."というのは架空の会社名のように思われます。したがって、そのイノベーション・デイの日程について具体的な情報を持ち合わせていません。もしかしたら、別の会社のイノベーション・デイについてお探しでしょうか?その場合は、会社名を正確に教えていただけますと幸いです。
LLMが学習していない情報なので当然ですが回答できないことが確認できました。
回答:申し訳ありませんが、"TechFusion Inc."というのは架空の会社名のように思われます。したがって、そのイノベーション・デイの日程について具体的な情報を持ち合わせていません。もしかしたら、別の会社のイノベーション・デイについてお探しでしょうか?その場合は、会社名を正確に教えていただけますと幸いです。
LLMが学習していない情報なので当然ですが回答できないことが確認できました。
第4章 もう全部任せた!
社内Wikiの情報を活用することはできましたが、インターパークでは自社製品である「サスケWorks」を活用して、Worksアプリにも様々な情報を蓄積しています。
そこで第2のナレッジベースとしてWorksアプリを採用するべく、Bedrockの「エージェント」機能を使用することにしました。
エージェントとは、ユーザーから与えられた様々なタスクを、自分でどのように対処すればよいかを考えて行動し、結果まで出すことを繰り返す存在を指すようです。
エージェントについてとても詳細かつわかりやすく説明されている記事がありましたので、画像を引用させていただきます。
そこで第2のナレッジベースとしてWorksアプリを採用するべく、Bedrockの「エージェント」機能を使用することにしました。
エージェントとは、ユーザーから与えられた様々なタスクを、自分でどのように対処すればよいかを考えて行動し、結果まで出すことを繰り返す存在を指すようです。
エージェントについてとても詳細かつわかりやすく説明されている記事がありましたので、画像を引用させていただきます。
エージェントに依頼するタスク
今回は先ほどまでに使ったナレッジベースを活用しつつ、エージェントが下記3つのタスクに対応できるように調整していきたいと思います。
①一般的な質問に対する回答
②社内Wikiに記載した情報に関する質問に対して回答
③「SUBLINE」に関する問合せの返答を、「Q&Aまとめ」Worksアプリを検索して情報を取得しつつ回答する
①一般的な質問に対する回答
②社内Wikiに記載した情報に関する質問に対して回答
③「SUBLINE」に関する問合せの返答を、「Q&Aまとめ」Worksアプリを検索して情報を取得しつつ回答する
「Q&Aまとめ」Worksアプリの作成
今回はインターパークの自社サービスであり自分も開発に携わっている「SUBLINE」の公式HPで公開しているよくある質問をいくつかピックアップして簡易的に質問と回答をまとめたアプリを作成することとしました。
レコードの例
レコードの例
また、エージェントがWorksアプリに対して検索をかける、つまりAPIを利用するためにAPIキーとアクセストークンを発行し、「AWS Secrets Manager」でそれらを登録しておきます。
サスケWorksでAPIを利用する際にはAPIドキュメントを参考ください。
(※後ほど説明しますが、エージェントには必要に応じてWorksアプリを検索するよう「アクショングループ」でlambda関数を設定します。その作成したアクショングループが登録したキーたちを利用できるようリソースポリシーを編集する必要がありますが詳細については割愛させていただきます。)
(※後ほど説明しますが、エージェントには必要に応じてWorksアプリを検索するよう「アクショングループ」でlambda関数を設定します。その作成したアクショングループが登録したキーたちを利用できるようリソースポリシーを編集する必要がありますが詳細については割愛させていただきます。)
AIエージェントの作成
簡易的なWorksアプリの準備もできたので、ここからは「エージェント」の作成に進みたいと思います。
AWSマネジメントコンソール上からBedrockにアクセスし、サイドメニューから「エージェント」を選択して「エージェントを作成」から作成画面に進むことができます。
基本的には画面に従って設定していくだけなので、すべての設定について詳細に説明することは割愛しますが、いくつか実際の設定を紹介させていただきます。
AWSマネジメントコンソール上からBedrockにアクセスし、サイドメニューから「エージェント」を選択して「エージェントを作成」から作成画面に進むことができます。
基本的には画面に従って設定していくだけなので、すべての設定について詳細に説明することは割愛しますが、いくつか実際の設定を紹介させていただきます。
エージェントの詳細
ここではエージェントの振る舞いを詳細に設定することができます。今回は以下の指示を与えました。
エージェント向けの指示
あなたは優秀なAIアシスタントです。利用者からの質問や指示に対して、以下の手順で適切な回答を提供してください。
1.利用者の依頼内容を注意深く読み、その内容が以下のどのカテゴリーに該当するか判断してください。
a) ナレッジベース内にあるTechFusion Incに関する質問
b) 一般的な知識に関する質問
c) SUBLINEに関する問い合わせの返答作成
2.依頼内容が一般的な知識に関する質問であると判断した場合、あなたが持つ幅広い知識に基づいて、的確かつ分かりやすい回答を作成してください。また、利用者の質問に正確な回答ができないと判断した場合は、正直にわからないと回答するようにしてください。
3.依頼内容がナレッジベース内にあるTechFusion Incに関する質問であると判断した場合、ナレッジベースに格納されているデータを検索し、関連する情報を抽出して、ユーザーの質問に対する最適な回答を作成してください。
4.依頼内容がSUBLINEに関する問い合わせの返答作成であった場合、入力された問い合わせ内容をよく理解したうえで専用のfunctionを実行し、得られた情報をもとに、カスタマーサポートの担当者としてお客様に対して丁寧かつわかりやすい返答を作成してください。
5.回答する際は、利用者がどのような回答を求めているかを踏まえたうえで、回答するようにしてください。
以上の手順に従って、利用者の期待に応える高品質なアシスタントサービスを提供してください。
1.利用者の依頼内容を注意深く読み、その内容が以下のどのカテゴリーに該当するか判断してください。
a) ナレッジベース内にあるTechFusion Incに関する質問
b) 一般的な知識に関する質問
c) SUBLINEに関する問い合わせの返答作成
2.依頼内容が一般的な知識に関する質問であると判断した場合、あなたが持つ幅広い知識に基づいて、的確かつ分かりやすい回答を作成してください。また、利用者の質問に正確な回答ができないと判断した場合は、正直にわからないと回答するようにしてください。
3.依頼内容がナレッジベース内にあるTechFusion Incに関する質問であると判断した場合、ナレッジベースに格納されているデータを検索し、関連する情報を抽出して、ユーザーの質問に対する最適な回答を作成してください。
4.依頼内容がSUBLINEに関する問い合わせの返答作成であった場合、入力された問い合わせ内容をよく理解したうえで専用のfunctionを実行し、得られた情報をもとに、カスタマーサポートの担当者としてお客様に対して丁寧かつわかりやすい返答を作成してください。
5.回答する際は、利用者がどのような回答を求めているかを踏まえたうえで、回答するようにしてください。
以上の手順に従って、利用者の期待に応える高品質なアシスタントサービスを提供してください。
アクショングループの設定
ここではエージェントが与えられたタスクを解決するために実行することができるアクションを設定することができます。
今回はアクショングループタイプを「関数の詳細で定義」にしたため、関数を実行するのに必要となるパラメータを2つほど追加してみました。
ここで追加したパラメータによって、「Q&Aまとめアプリ」にストックしてある情報から取得する情報の種類を限定していくイメージでパラメータ設定を行いました。
①service⇒質問に関連するサービス
「Q&Aまとめアプリ」にはSUBLINE以外の複数のサービスについてのQ&A情報が格納されている体で考えたときに、まず今回はどのサービスに関連する情報を取得すればよいかを特定するために作成しました。(今回はSUBLINEの情報のみしか登録していないので不要と言えば不要…)
②type_question⇒質問内容の種別
アプリに登録したQ&Aはいくつかの種類に分別することができるため、Worksアプリで設定してある質問種別を関数呼び出しの前に決定させることで、取得する情報の対処を絞ろうという意図のもと作成しました。
質問内容の種別。種別は、①機能・仕様、②お試し利用、③申込・ご契約情報、④お支払い・料金、⑤解約、⑥その他の計6種類あります。
今回はアクショングループタイプを「関数の詳細で定義」にしたため、関数を実行するのに必要となるパラメータを2つほど追加してみました。
ここで追加したパラメータによって、「Q&Aまとめアプリ」にストックしてある情報から取得する情報の種類を限定していくイメージでパラメータ設定を行いました。
①service⇒質問に関連するサービス
「Q&Aまとめアプリ」にはSUBLINE以外の複数のサービスについてのQ&A情報が格納されている体で考えたときに、まず今回はどのサービスに関連する情報を取得すればよいかを特定するために作成しました。(今回はSUBLINEの情報のみしか登録していないので不要と言えば不要…)
②type_question⇒質問内容の種別
アプリに登録したQ&Aはいくつかの種類に分別することができるため、Worksアプリで設定してある質問種別を関数呼び出しの前に決定させることで、取得する情報の対処を絞ろうという意図のもと作成しました。
質問内容の種別。種別は、①機能・仕様、②お試し利用、③申込・ご契約情報、④お支払い・料金、⑤解約、⑥その他の計6種類あります。
実際に実行されるlambda関数
先ほど作成したアクショングループで実際に実行されるlambda関数のコードは以下になります。
下記のコードでは一部の情報についてマスキングしてあります。またコード実行にあたっては事前にレイヤーを追加してrequestsやboto3ライブラリを使用できるようにしています。
(※とにかく動いたらよしを目指していたのでコードがすごく汚いですね…お目汚し申し訳ありません…)
(※とにかく動いたらよしを目指していたのでコードがすごく汚いですね…お目汚し申し訳ありません…)
import json
import requests
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
agent = event['agent']
actionGroup = event['actionGroup']
function = event['function']
parameters = event.get('parameters', [])
result = get_info_from_worksapp(parameters)
responseBody = {
"TEXT": {
"body": json.dumps(result, ensure_ascii=False)
}
}
action_response = {
'actionGroup': actionGroup,
'function': function,
'functionResponse': {
'responseBody': responseBody
}
}
function_response = {'response': action_response, 'messageVersion': event['messageVersion']}
print("Response: {}".format(function_response))
return function_response
def get_info_from_worksapp(parameters):
service = get_value(parameters, 'service')
type_question = get_value(parameters, 'type_question')
WorksのAPIをたたく準備
url = "https://api.works.app/v1/XXXX/records"
api_key, token = get_secret()
APIリクエストの設定
headers = {
"x-token": token,
"x-api-key": api_key,
}
payload = {
"searchParams": {
"service": service,
"sort": "dt_time",
"sortType": "DESC",
"type_question": type_question
}
}
try:
POSTリクエストを送信
response = requests.post(url, json=payload, headers=headers)
except requests.exceptions.RequestException as e:
print(f"エラーが発生しました: {e}")
レスポンスの整形
res_data = response.json()
records = res_data.get("records", [])
新しい辞書を格納するリスト
result = []
ループ処理で 'question' と 'answer' の値を取り出して新しい辞書を作成
for record in records:
question = record.get("question").replace("\n", "")
answer = record.get("answer").replace("\n", "")
新しい辞書を作成してリストに追加
result.append({
"question": question,
"answer": answer
})
return result
def get_secret():
secret_name = "XXXXXX" シークレット名を指定
region_name = "XXXXXX" シークレットが保存されているリージョン
boto3クライアントを作成
client = boto3.client('secretsmanager', region_name=region_name)
try:
Secrets Managerからシークレットを取得
response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
raise Exception(f"Failed to retrieve secret: {e}")
if 'SecretString' in response:
secret = response['SecretString']
else:
secret = base64.b64decode(response['SecretBinary'])
secret_dict = json.loads(secret)
APIキーとアクセストークンを取得
api_key = secret_dict['XXXXXX']
access_token = secret_dict['XXXXXX']
return api_key, access_token
def get_value(parameters, key, default=None):
return next((p['value'] for p in parameters if p['name'] == key), default)
いざ実行…!
ざっくりとした解説になってしまいましたが、準備は整ったのでエージェントの挙動をテストしてみたいと思います。
まずは、一般的な情報で回答できそうなことを聞いてみます。
質問:北海道のおすすめ観光スポットを教えて
回答:北海道には多くの魅力的な観光スポットがあります。以下におすすめの場所をいくつか紹介します: 1. 札幌市:大都市の魅力と自然が調和した街で、札幌時計台、大通公園、すすきのなどが人気です。 2. 小樽市:歴史的な建物が残る運河沿いの街並みが魅力で、ガラス工芸品や寿司が有名です。 3. 富良野・美瑛エリア:夏には広大なラベンダー畑や丘陵地帯の美しい景観が楽しめます。 4. 知床半島:世界自然遺産に登録された原生林や野生動物の宝庫で、自然愛好家に人気です。 5. 函館市:夜景で有名な函館山や、歴史的な建造物が残る元町地区が魅力的です。 6. 登別温泉:様々な泉質の温泉と地獄谷の迫力ある景観が楽しめる温泉地です。 季節や個人の興味によって、さらに多くの魅力的なスポットがありますので、旅行の計画を立てる際にはより詳細な情報を調べることをおすすめします。
時計台はやめ…いえなんでもありません。特にナレッジベースの情報やWorksアプリを検索するようなことはしていないように見えますが実際はどうなのでしょう。
エージェントがどのように考えてどのアクションを実行するのを決めた一連の思考プロセスを「オーケストレーションとナレッジベース」から確認することができるので見てみましょう。
まずは、一般的な情報で回答できそうなことを聞いてみます。
質問:北海道のおすすめ観光スポットを教えて
回答:北海道には多くの魅力的な観光スポットがあります。以下におすすめの場所をいくつか紹介します: 1. 札幌市:大都市の魅力と自然が調和した街で、札幌時計台、大通公園、すすきのなどが人気です。 2. 小樽市:歴史的な建物が残る運河沿いの街並みが魅力で、ガラス工芸品や寿司が有名です。 3. 富良野・美瑛エリア:夏には広大なラベンダー畑や丘陵地帯の美しい景観が楽しめます。 4. 知床半島:世界自然遺産に登録された原生林や野生動物の宝庫で、自然愛好家に人気です。 5. 函館市:夜景で有名な函館山や、歴史的な建造物が残る元町地区が魅力的です。 6. 登別温泉:様々な泉質の温泉と地獄谷の迫力ある景観が楽しめる温泉地です。 季節や個人の興味によって、さらに多くの魅力的なスポットがありますので、旅行の計画を立てる際にはより詳細な情報を調べることをおすすめします。
時計台はやめ…いえなんでもありません。特にナレッジベースの情報やWorksアプリを検索するようなことはしていないように見えますが実際はどうなのでしょう。
エージェントがどのように考えてどのアクションを実行するのを決めた一連の思考プロセスを「オーケストレーションとナレッジベース」から確認することができるので見てみましょう。
「rationale」の「text」の値に「これは一般的な知識に関する質問であり、ナレッジベースや特定のツールを使用する必要はありません。私の持っている情報を基に、北海道の代表的な観光スポットをいくつか紹介しましょう。」とあるので、確かに既に学習済みの知識から回答していることがわかりました。
次は先ほど作成したナレッジベースの情報をもとに回答してもらうことを想定した質問を投げてみましょう。今回は就業規則に関する質問にしてみます。
質問:TechFusion Inc.のフレックスタイム制度について教えてください。
回答:TechFusion Inc.のフレックスタイム制度について、以下のように説明いたします: 1. コアタイムは10:00〜15:00に設定されています。 2. 従業員は月間160時間の勤務を自主的に管理します。 3. 標準労働時間は1日8時間、週40時間です。 4. 従業員は一定の範囲内で自由に勤務時間を調整できます。 また、フレックスタイム制度と併せてリモートワークも推奨されており、以下のガイドラインがあります: - 勤務開始時にオンライン状態を共有 - Slackでの連絡をこまめに確認 これらの制度により、従業員の柔軟な働き方とワークライフバランスの向上がサポートされています。
コアタイムなど社内Wikiに記載されていた情報と一緒なのでこちらも大丈夫そうですが、「オーケストレーションとナレッジベース」を確認してみましょう。
次は先ほど作成したナレッジベースの情報をもとに回答してもらうことを想定した質問を投げてみましょう。今回は就業規則に関する質問にしてみます。
質問:TechFusion Inc.のフレックスタイム制度について教えてください。
回答:TechFusion Inc.のフレックスタイム制度について、以下のように説明いたします: 1. コアタイムは10:00〜15:00に設定されています。 2. 従業員は月間160時間の勤務を自主的に管理します。 3. 標準労働時間は1日8時間、週40時間です。 4. 従業員は一定の範囲内で自由に勤務時間を調整できます。 また、フレックスタイム制度と併せてリモートワークも推奨されており、以下のガイドラインがあります: - 勤務開始時にオンライン状態を共有 - Slackでの連絡をこまめに確認 これらの制度により、従業員の柔軟な働き方とワークライフバランスの向上がサポートされています。
コアタイムなど社内Wikiに記載されていた情報と一緒なのでこちらも大丈夫そうですが、「オーケストレーションとナレッジベース」を確認してみましょう。
見てみると「ナレッジベースから詳細な情報が得られた」と記述があるので期待通りにナレッジベースを利用してくれましたね。
最後にWorksアプリを検索する必要がある質問を投げかけてみたいと思います。
アプリには事前に「SUBLINEの料金体系とSUBLINEの同一スペース(同契約)同士の通話において着信料は発生しない」という情報を登録してあります。
質問:以下の問い合わせに対する回答を、サポート担当出茂が回答する形で作成してください。 問い合わせ内容 SUBLINEにおいて同一スペース同士の通話では着信料は発生しないのでしょうか?
回答:お客様各位 SUBLINEサポートの出茂でございます。 お問い合わせいただき、誠にありがとうございます。 ご質問の「SUBLINEにおいて同一スペース同士の通話では着信料は発生しないのでしょうか?」について、ご回答させていただきます。 結論から申し上げますと、同一スペース内での通話では着信料は発生いたしません。 詳細は以下の通りです: 1. 通常、SUBLINEでの着信料は1分につき2.2円(税込)発生いたします。 2. ただし、同一スペース内での通話に限り、この着信料は発生いたしません。 これにより、同じスペース内のメンバー同士で通話をする際は、着信料を気にすることなくコミュニケーションを取っていただけます。 なお、以下の点にご注意ください: - 通話録音や留守電設定で録音機能をオンにした場合は、別途録音料(2.2円/分、税込)が発生いたします。 - 転送設定を行った場合は、転送元として料金が発生いたします。 ご不明な点やさらなるご質問がございましたら、お気軽にお問い合わせください。SUBLINEを快適にご利用いただけるよう、精一杯サポートさせていただきます。 今後ともSUBLINEをよろしくお願いいたします。 SUBLINEサポート 出茂
最後にWorksアプリを検索する必要がある質問を投げかけてみたいと思います。
アプリには事前に「SUBLINEの料金体系とSUBLINEの同一スペース(同契約)同士の通話において着信料は発生しない」という情報を登録してあります。
質問:以下の問い合わせに対する回答を、サポート担当出茂が回答する形で作成してください。 問い合わせ内容 SUBLINEにおいて同一スペース同士の通話では着信料は発生しないのでしょうか?
回答:お客様各位 SUBLINEサポートの出茂でございます。 お問い合わせいただき、誠にありがとうございます。 ご質問の「SUBLINEにおいて同一スペース同士の通話では着信料は発生しないのでしょうか?」について、ご回答させていただきます。 結論から申し上げますと、同一スペース内での通話では着信料は発生いたしません。 詳細は以下の通りです: 1. 通常、SUBLINEでの着信料は1分につき2.2円(税込)発生いたします。 2. ただし、同一スペース内での通話に限り、この着信料は発生いたしません。 これにより、同じスペース内のメンバー同士で通話をする際は、着信料を気にすることなくコミュニケーションを取っていただけます。 なお、以下の点にご注意ください: - 通話録音や留守電設定で録音機能をオンにした場合は、別途録音料(2.2円/分、税込)が発生いたします。 - 転送設定を行った場合は、転送元として料金が発生いたします。 ご不明な点やさらなるご質問がございましたら、お気軽にお問い合わせください。SUBLINEを快適にご利用いただけるよう、精一杯サポートさせていただきます。 今後ともSUBLINEをよろしくお願いいたします。 SUBLINEサポート 出茂
こちらからの質問に回答したうえで追加で与えた「サポート担当出茂が回答する形で回答を作成してください」というオーダーにも対応してくれました。
今回もトレースを見てみると質問内容がどういうものであるかを判断したうえで、作成しlambda関数を実行してくれていることがわかります。
今回もトレースを見てみると質問内容がどういうものであるかを判断したうえで、作成しlambda関数を実行してくれていることがわかります。
気持ちばかりではありますが…
自分が作成したかったエージェントは作成できたのでBedrockのコンソール上からではなく、ローカルで立ち上げたアプリケーションからエージェントを呼び出してみましょう。
簡易的なアプリケーションを作成するうえでPythonのライブラリ「Streamlit」を使用してみました。事前に仮想環境を作成して各種ライブラリをインストールする作業を行いましたがこちらの詳細については割愛させていただきます。
Streamlit
簡易的なアプリケーションを作成するうえでPythonのライブラリ「Streamlit」を使用してみました。事前に仮想環境を作成して各種ライブラリをインストールする作業を行いましたがこちらの詳細については割愛させていただきます。
Streamlit
import uuid
import json
import boto3
import streamlit as st
Agentの定義
agent_id: str = "XXXXXX"
agent_alias_id: str = "XXXXXX"
セッションIDの生成と保存
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
session_id = st.session_state.session_id
client = boto3.client("bedrock-agent-runtime")
if "history" not in st.session_state:
st.session_state.history = []
st.title("AIエージェント")
for history in st.session_state.history:
with st.chat_message(history["role"]):
st.markdown(history["content"])
def invoke_bedrock_agent(input_text, agent_session_state=None):
invoke_params = {
"inputText": input_text,
"agentId": agent_id,
"agentAliasId": agent_alias_id,
"sessionId": session_id,
"enableTrace": True,
}
return client.invoke_agent(**invoke_params)
Bedrock Agentからのレスポンスを処理する関数
def process_agent_response(response):
full_response = ""
for event in response.get("completion", []):
if "chunk" in event:
chunk = event["chunk"]["bytes"].decode("utf-8")
full_response += chunk
return full_response
ユーザー入力の処理
if user_input := st.chat_input("入力してください"):
ユーザー入力をチャット履歴に追加
st.session_state.history.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
with st.chat_message("assistant"):
response_placeholder = st.empty()
full_response = ""
Bedrock Agentを呼び出し、レスポンスを処理
response = invoke_bedrock_agent(user_input)
full_response = process_agent_response(response)
response_placeholder.markdown(full_response)
st.session_state.history.append({"role": "assistant", "content": full_response})
上記コードを以下のコマンドで実行するとローカルでStreamlitアプリケーションが立ち上がります。
streamlit run sample.py
会話の履歴をDBに登録するようにしてはいないため、セッションが終われば会話の履歴は消えてしまいますがそこはご愛敬ということで…
第5章 さいごに
今回は期間が自分にとっては短かったこともあって書籍やドキュメント、一般の方が公開されている記事をすべて活用させていただきました。有益な情報を公開していただき本当にありがとうございます。
RAGやエージェントについてとても語れるほど知識を得たわけではありませんが、今回の勉強会で社内で実運用する第一歩を踏み出せたのかなと思います。
Bedrockの基本を学習した書籍
Amazon Bedrock 生成AIアプリ開発入門 [AWS深掘りガイド]
RAGやエージェントについてとても語れるほど知識を得たわけではありませんが、今回の勉強会で社内で実運用する第一歩を踏み出せたのかなと思います。
Bedrockの基本を学習した書籍
Amazon Bedrock 生成AIアプリ開発入門 [AWS深掘りガイド]
※参考情報(Bedrockを利用することでかかった費用)
Bedrockの公式ページに料金体系について記載があったので、今回自分が使用したモデルの料金について以下にまとめました。
前提情報
・1ドル149円で計算
・リージョンは東京リージョンを選択している
・Claude 3.5 Sonnet
入力トークン1,000個あたりの価格
0.003 USD≒0.4円(小数第二位を四捨五入)
・入力トークン1,000個あたりの価格
0.015 USD≒2.2円(小数第二位を四捨五入)
⇒今月は現時点で0.67ドルほど
・Embed - Multilingual
入力トークン1,000個あたりの価格
0.0001 USD≒0.01円
このほかベクトルDBとして利用したPineconeが毎日0.01ドル(≒1.5円)、EC2の料金が加算されますが自分一人でそこまでLLMに回答をさせていないので、先月今月と1ドル後半くらいで済んでいる感じでした。
AWSのリソースをむやみに使用することで思わぬ出費が生じたらどうしようとひやひやしていましたが、個人でやる分にはそこまで神経質になる必要はないのかもしれません。
前提情報
・1ドル149円で計算
・リージョンは東京リージョンを選択している
・Claude 3.5 Sonnet
入力トークン1,000個あたりの価格
0.003 USD≒0.4円(小数第二位を四捨五入)
・入力トークン1,000個あたりの価格
0.015 USD≒2.2円(小数第二位を四捨五入)
⇒今月は現時点で0.67ドルほど
・Embed - Multilingual
入力トークン1,000個あたりの価格
0.0001 USD≒0.01円
このほかベクトルDBとして利用したPineconeが毎日0.01ドル(≒1.5円)、EC2の料金が加算されますが自分一人でそこまでLLMに回答をさせていないので、先月今月と1ドル後半くらいで済んでいる感じでした。
AWSのリソースをむやみに使用することで思わぬ出費が生じたらどうしようとひやひやしていましたが、個人でやる分にはそこまで神経質になる必要はないのかもしれません。