Amazon BedrockのClaudeとAmazon Kendra、AWS Lambdaを利用し、RAGを実装してみた

2023.10.10

はじめに

Amazon BedrockとAmazon Kendra、AWS Lambdaで、Retrieval Augmented Generation(RAG)を実装してみました。

最近、社内の業務効率化などの目的で、AIの言語モデル(以降、LLM)を用いて社内情報を活用するための手法として、RAGがよく話題になっています。

RAGとは具体的には、ユーザーからの問い合わせ(プロンプト)に基づいて外部データから関連するドキュメントを検索し、その結果をもとにLLMが質問への回答を生成するという手法です。

以前の記事で、検索(Retrieval)のフェーズのみをKendraを使い、試してみました。

構成

構成としては、下記の通りです。

Kendra

Kendraのインデックスには、Network Load Balancer(NLB)のAWSドキュメントをウェブクローラーでインポートします。

Kendraのデータソース(取り込み元)としては、ウェブサイトやS3バケットに保存したドキュメントなどが利用できます。

実際には、社内情報のドキュメントをS3バケットなどに保存して、Kendraにインポートさせると思いますが、今回は、検証が簡単なウェブクローラーを利用します。

本記事

今回の構成は、Lambdaにプロンプトを直接渡しています。

しかし実際のシステムでは、API Gatewayやサーバーからのリクエストを介してLambdaにプロンプトが渡されるため、プロンプトを入力するインターフェース(たとえば、ウェブフォームやSlackなど)が必要になります。

それらのインターフェースにRAGを組み込む際に、参考として、本記事をご活用頂けると思います。

前提条件

  • バージニアリージョンで、下記のリソースを作成•設定します。
    • Kendraのインデックス
    • Lambda関数
    • BedrockのClaudeClaude Instantを有効化

バージニアリージョンの理由としては、Bedrockのうち、日本語対応しているAnthropicのClaude V2が東京リージョンで未サポートのためです。

Bedrockの有効化

AnthropicのClaudeClaude Instantを利用可能な状態にしておきます。

Kendraのインデックスを作成

下記の記事通りにKendraのインデックスを作成します。

参考記事と変える点について、データソースは、AWSのNLBのドキュメントURLにします。

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/introduction.html

Lambdaを作成

Lambdaを作成します。以下を参考に、IAMロールやBedrockを利用するためのBoto3ライブラリをアップロードしてください。

加えて、Kendraを利用するため、IAMロールにAmazonKendraFullAccessをアタッチします。

コードは、下記の通りです。

import boto3
import json
kendra = boto3.client('kendra')
bedrock = boto3.client(service_name='bedrock-runtime', region_name="us-east-1")

def get_retrieval_result(query_text,index_id):

    response = kendra.retrieve(
        QueryText=query_text,
        IndexId=index_id,
        AttributeFilter={
            "EqualsTo": {
                "Key": "_language_code",
                "Value": {"StringValue": "ja"},
            },
        },
    )

    # Kendra の応答から最初の5つの結果を抽出
    results = response['ResultItems'][:5] if response['ResultItems'] else []

    extracted_results = []
    for item in results:
        content = item.get('Content')
        document_uri = item.get('DocumentURI')

        extracted_results.append({
            'Content': content,
            'DocumentURI': document_uri,
        })
    print("Kendra extracted_results:" + json.dumps(extracted_results, ensure_ascii=False))
    return extracted_results

def lambda_handler(event, context):
    user_prompt = event.get('user_prompt')
    # Kendra インデックス ID に置き換えてください
    index_id = '<<index ID>>'  
    
    prompt = f"""\n\nHuman:
    [参考]情報をもとに[質問]に適切に答えてください。
    [質問]
    {user_prompt}
    [参考]
    {get_retrieval_result(user_prompt,index_id)}
    Assistant:
    """

    # 各種パラメーターの指定
    # modelId = 'anthropic.claude-instant-v1' 
    modelId = 'anthropic.claude-v2' 
    accept = 'application/json'
    contentType = 'application/json'

    body = json.dumps({
        "prompt": prompt,
        "max_tokens_to_sample":600,
    })

    response = bedrock.invoke_model(
        modelId=modelId,
        accept=accept,
        contentType=contentType,
        body=body
    )

    response_body = json.loads(response.get('body').read())
    # print("Received response_body:" + json.dumps(response_body, ensure_ascii=False))
    return response_body.get('completion')
  • Claude V2を利用しました。Claude Instant v1.2もコメントアウトを外すと利用できます。
  • ユーザーのプロンプトは、外部からLambdaのイベントとして受け取る仕様にしています。
  • Kendraの検索内容から、関連性の高い上位5つのドキュメントを抽出しました
    • 5つというのは、あくまでも参考値とお考えください。LLMの返答内容によって、調整しましょう。
  • KendraのインデックスIDは、Kendraのコンソール画面から確認できます。

    • あくまでも検証なので、ハードコーディングしています。

Lambdaを実行してみる

外部から以下のユーザーのプロンプトをLambdaの「イベント」として渡すようなコードにしています。

{
  "user_prompt": "NLBは、セキュリティグループを適用できますか?いつから、できるようになりましたか?注意点もふまえて教えてください"
}

テストイベントを入力し、Lambdaを実行させます。

Lambdaのコンソール画面から、[Configure test event]を選択後、上記のjsonを入力し、[呼び出す]をクリックすると、user_promptをLambdaに渡して実行できます。

ユーザーのプロンプトに対して、NLBのドキュメントをもとに回答し、15秒程度で結果が以下のように返りました。

NLBは作成時からセキュリティグループを適用できます。
セキュリティグループを適用しないと後から適用することはできないため、作成時に必ずセキュリティグループを設定することが重要です。
\n\nヘルスチェックのトラフィックにはアウトバウンドルールが適用されるため、ヘルスチェックを許可するアウトバウンドルールを設定する必要があります。
\n\nまた、2023年8月のリリースで、NLB作成時にセキュリティグループを関連付ける機能が追加されました。 
NLBをセキュアに利用するために、作成時に適切なセキュリティグループを設定することをおすすめします。

回答を確認すると、ドキュメントにも記載されている通り、NLBがセキュリティグループに適用できるようになったのは、2023年の8月ですので正しいですね。注意点も的を得ており、分かりやすい文章で回答されてました。

ちなみに、コンソール上でAmazon BedrockのClaude V2に全く同じ質問をすると、最新の情報は持っていないため、NLBはセキュリティグループを適用できないと答えていました。

よって、KendraインデックスにインポートしたNLBのドキュメントを参考に返答出来ていることが分かりました。

Claude Instant v1.2

Claude Instant v1.2の場合、レスポンス時間は、5秒程度と早いですが、回答内容が冗長な文章でした。

NLBは、セキュリティグループを適用できるようになりました。
\n\n2023年8月10日から、NLBの作成時にセキュリティグループを関連付けることができる機能が追加されました。
\n\nセキュリティグループを関連付けた後は、いつでもそのセキュリティグループを変更できます。
\n\n注意点は、ヘルスチェックではアウトバウンドルールが適用されるが、インバウンドルールは適用されないことです。
また、アウトバウンドルールによってヘルスチェックトラフィックがブロックされないように注意が必要です。
\n\nこの機能が追加される前は、NLB作成時にセキュリティグループを関連付けることはできませんでした。
\n\n以上のことから、NLBでセキュリティグループを適用できるようになった時期は2023年8月10日からだと答えられます

プロンプトをうまく扱えば、冗長な表現を回避できるかもしれませんが、Claude V2を採用したくなりますね。

最後に

Amazon BedrockとAmazon Kendra、AWS Lambdaで、RAGを実装してみました。

Lambdaのコードは、60行弱で実装できたため、簡易に実現できることが分かります。

ただし、Lambdaコードはあくまでも参考例とご認識下さい。実際の環境で使用する際には、「API呼び出しのレスポンスチェック」や「適切なエラーハンドリング」、「機密情報はハードコーディングしない」などを行う必要があります。

RAGによって、社内の知識共有、質問応答モデルの改善、そして社内での情報検索作業の効率化が期待できますので、参考になれば幸いです。

参考