LoginSignup
22
6

re:Invent 2023でGAされたAgents for Amazon BedrockとAmazon Kendraを組み合わせてRAGを構築

Last updated at Posted at 2023-12-16

この記事はJapan AWS Jr. Champions Advent Calendar 2023 16日目の記事です!

Agents for Amazon Bedrockとは

re:Invent 2023よりGAが発表されたAmazon Bedrockの新機能です。

通常の基盤モデルの場合、質問すると回答してくれるのみのため、実際のタスクはその回答を読んだ人間が行う必要がありました。

これに対し、Agents for Amazon Bedrock では、質問に対して何をすべきかを基盤モデル自身が思考し、計画し、実行してくれます。さらに事前に関数を設定することで、質問に対し必要であれば関数を実行する、ということまで自律して行ってくれます。

LangChain Agentに機能としては似ています。

IMG_9357.jpg
Photo by 石原

また、Knowldge base for Amazon BedrockもAgents for Amazon Bedrockと同時にGAが発表されたBedrockの新機能です。

S3にデータを配置し、「Create Knowledge base」を選択するだけで簡単にRAG(Retrieval Argumented Genaration、検索拡張性) を構築することができます。
情報を参照するためのインデックスとしては、Amazon OpenSearch Serveless, Pinecone, Redis Enterprise Cloud のいずれかから選択が可能です。

さらにAgents for Amazon Bedrockと統合されており、GUIによる設定のみでエージェントを介したRAGを構築することができます。

Amazon Kendraとは

Amazon Kendra とはエンタープライズサーチの機能を有したAWSのサービスです。
昨今Bedrockと組み合わせたRAGの用途ととしても非常に注目されています。

Bedrock+KendraによるRAGと、Knowledge baseの違い

LLMが情報を参照するインデックスが異なるため、コスト、検索能力、データソースの種類などが異なります。

検索機能に関しては、Amazon Kendraは全文検索とセマンティック検索、Knowledge Base for Amazon Bedrockではベクター検索が用いられます。

また、仕様としてKnowledge Baseが参照できるデータソースはS3のみであり、Amazon Kendraは約40種類ものデータソースを提供しております。

さらに、Amazon Kendraは書き込む際に、コンテンツにメタ情報を付与したり、パスを指定のURLに変更したりと自由にコンテンツをカスタマイズすることが可能です。
Knowledge baseがインデックスに書き込む情報はテキストとそのテキストのベクトル、そしてS3パスの3つのみです。

以上のことから、エージェントを介したRAGを構築したい場合にKnowledge baseよりKendraを採用したいケースが存在すると考えられることから、その検証を行った結果をご紹介いたします。

検証

今回はAgents for Amazon BedrockがKendraから検索して情報を取得して回答するRAGを構築しました。
その際に、Kendraの機能を使ってコンテンツのパスを指定のURLに変更し、RAGが回答する際に指定したURLも添えてくれる、ということを目指しました。

Bedrockが参照するコンテンツの用意

今回は私が以前書いた記事のHTMLファイルを使用します。

S3に用意したHTMLファイルと、そのコンテンツをカスタマイズするためのメタデータファイルを用意します。

image.png

メタデータファイル

{
    "Attributes": {
        "_source_uri": "https://qiita.com/Naoki_Ishihara/items/7bfefc5a4750aa50c58e"
    },
    "Title": "Amazon KendraにおけるWebクローラーコネクタの有用な機能とその設定方法(追記:Amazon Q For Business UseにおけるWebクローラーについて)",
    "ContentType": "HTML"
}

そして、KendraのS3コネクタを使用して同期します。

検証としてKendraのインデックスに検索をかけてみると、ちゃんとコンテンツにタイトルが追加されたり、パスが指定のURLに変更されていることが分かります。

image.png

エージェント作成

Bedrockのナビゲーションペインから「Agenta」→「Create Agent」を選択します。

Step1:エージェントの詳細

エージェント名以外の設定はデフォルトを設定し、Nextを選択します。
image.png

Step2:モデル選択

モデルはClaude v2を選び、
「Instructions for the Agent」に、システムプロンプトを入力します。
どのようなプロセスで対応してほしいかをステップバイステップで記述します。

image.png

あなたは、Amazon KendraのWebクローラーの設定に関するエキスパートです。質問に対し、以下の手順で対応してください。各ステップは厳格に実施し、指示に従ってください。
1. 質問に対し、あなたは適切なキーワードを決定し、Amazon KendraのWebクローラーに関するドキュメントに対し検索を行います。検索の実行は、私の指示を仰ぐことなく実行してください。 
2. 検索結果の出力: 検索を行って情報を取得した場合、ソースURLは必ず私に表示をしてください。これは例外なく守ってください。	

Step3:アクショングループ追加

アクショングループとしてLambda関数と、その入力と出力を定義するOpneAPI.ymlテンプレートを設定します。
image.png

Lambda関数

アクショングループでLambdaを呼び出す際のイベントコンテキストは下記ドキュメントで定義されています。

こちらを利用して、Kendraの検索結果を取得する下記Lambda関数を作成しました

import boto3  # boto3ライブラリをインポート
import json  # jsonライブラリをインポート

kendra = boto3.client('kendra')  # AWS Kendraサービスのクライアントを生成

def search(search_keyword):  # 検索キーワードを引数にとる検索関数
    query_text = search_keyword  # 検索キーワードをクエリテキストとして設定
    index_id = '<INDEX_ID>'  # KendraのインデックスIDを設定

    # Kendraにクエリを送信し、レスポンスを受け取る
    response = kendra.retrieve(
        QueryText=query_text,
        IndexId=index_id,
        # 日本語(_language_code = "ja")のドキュメントに絞り込むための属性フィルターを設定
        AttributeFilter={
            "AndAllFilters": [
                {
                    "EqualsTo": {
                        "Key": "_language_code",
                        "Value": {"StringValue": "ja"}
                    }
                },
            ]
        }
    )

    extracted_results = []  # 結果を格納するリストを初期化
    # response['ResultItems']の最初のアイテムだけを抽出して保存
    for item in response['ResultItems'][:1]:
        extracted_results.append({
            'DocumentTitle': item.get('DocumentTitle'),  # ドキュメントのタイトル
            'Content': item.get('Content'),  # ドキュメントの内容
            'DocumentURI': item.get('DocumentURI')  # ドキュメントのURI
        })
        
    # 検索結果を含む辞書を返す
    return {
        "QueryId": response['QueryId'],  # クエリID
        "Results": extracted_results  # 抽出された検索結果
    }
    
def lambda_handler(event, context):  # Lambdaのメインハンドラ
    print(event)  # イベントデータをプリント

    parameters = event.get("parameters", [])  # イベントからパラメータを取得
    if not parameters:  # パラメータがない場合はエラーメッセージを返す
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "No parameters provided."})
        }

    # parametersの最初の要素のvalueをsearch_keywordとして使用
    search_keyword = parameters[0]["value"]
    print(f'Search keyword: {search_keyword}')  # 検索キーワードを出力

    api_path = event.get("apiPath", "")  # イベントからAPIパスを取得
    body = None  # レスポンスボディの初期化

    if api_path == "/search":  # APIパスが/searchの場合は検索関数を実行
        body = search(search_keyword)

    elif api_path == "/submit":  # /submitパスはこの例では実装されていない
        # submitアクションが実装されていないため、エラーメッセージを返す
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "Submit action not implemented."})
        }

    if body is None:  # APIパスが不正な場合はエラーメッセージを返す
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "Invalid API path."})
        }

    # レスポンスボディをJSON形式でエンコーディングして返す
    response_body = {
        "application/json": {
            "body": json.dumps(body, ensure_ascii=False)
        }
    }

    # アクションレスポンスを作成
    action_response = {
        "actionGroup": event.get("actionGroup"),  # アクショングループ
        "apiPath": api_path,  # APIパス
        "httpMethod": event.get("httpMethod"),  # HTTPメソッド
        "httpStatusCode": 200,  # HTTPステータスコード
        "responseBody": response_body,  # レスポンスボディ
    }

    # APIレスポンスを作成
    api_response = {
        "messageVersion": "1.0",  # メッセージバージョン
        "response": action_response  # アクションレスポンス
    }

    return api_response  # 最終的なレスポンスを返す

クエリAPIは戻り値がシンプルなretrieve APIを使用しました。

Kendraのretrieve APIが対応しているboto3のバージョンがデフォルトのpythonランタイムだと対応していなかったので手動でレイヤー追加を行いました。

OpenAPIスキーマ

OpenAPIスキーマはこちらを設定しました。

openapi: 3.0.0
info:
  title: 'Test API'
  description: "APIs for testing Agents' behavior"
  version: '1.0.0'
paths:
  /search:
    get:
      summary: 'Web Crawler Document in Amazon Kendra'
      description: |
        この関数は、Amazon KendraのWebクローラーコネクタに関するドキュメントを検索し、情報を取得するAPIです。検索することで、タイトル、ドキュメントの内容、ドキュメントのURIをJSON形式の配列で必ず返します。
      operationId: searchFaq
      parameters:
        - in: query
          name: q
          description: 検索キーワード
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 検索結果を返します。
          content:
            application/json:
              schema:
                type: object
                properties:
                  QueryId:
                    type: string
                    description: 検索クエリのID
                  Results:
                    type: array
                    description: 検索結果のリスト
                    items:
                      type: object
                      properties:
                        DocumentTitle:
                          type: string
                          description: ドキュメントのタイトル
                        Content:
                          type: string
                          description: ドキュメントの内容
                        DocumentURI:
                          type: string
                          description: ドキュメントのURI

このスキーマに基づきエージェントがどの関数を呼び出すか判断を行います。

Step4:ナレッジベースを追加

Knowledge baseを追加することができますが、今回は必要がないため飛ばします。
image.png
以上の設定によりエージェントを作成します。

テスト

エージェント作成後、右横の画面ですぐにテストすることができます。

image.png

質問:KendraのWebクローラーの設定において、クローラ深さはどのように設定すればよいですか?
image.png

ソースを基に回答をしてくれていることが分かります。
しかしながら、プロンプトで命令していたソースURLを表示してくれません。

どのような推論をしているか「Show trace」→「Orchestration and knowledge base」→「Step 2」を選択して中身を覗いてみます。
image.png

エージェントは「Kendra web crawler クローラ深さ 設定」というキーワードで検索していることが分かります。
そしてタイトル、コンテキスト、ドキュメントURIをきちんと渡せていることが分かります。

Step3を覗いてみます。
image.png

生成された最終的な回答ではURIが表示されていません。

どうやらLLMが取得したURLを表示させることが出来ていないことが分かります。

Advanced Promptsについて(未検証)

Agents for Amazon Bedrockでは、エージェンが推論を行うためのプロンプトテンプレートを編集することができ、LLMの動作をより詳細に制御することが可能なAdvanced Promptsという機能が提供されています。

その設定画面を見ると、エージェントは
Pre-processing → Orchestration → KB response generation → Post-processing という順番で推論していることが分かります。

そして、推論毎にプロンプトを編集したり、Lambda関数を設定することが可能なようです。

image.png

次回は、これをプロンプトエンジニアリングで編集することで、より命令に忠実にLLMが回答させる検証を行ってみようと思います。

22
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
6