AWS LambdaからAmazon Bedrockを呼び出す際、S3に保存したプロンプトファイルを参照させる

2024.03.05

はじめに

AWS LambdaからAmazon Bedrockを呼び出す際、事前にS3バケットに保存したプロンプトファイルを参照させる方法をまとめました。

プロンプト部分のみをLambdaのコードから切り離すことで、コードの修正することなく、生成AIへのプロンプトを変更することができます。

利用想定シーンとしては、開発者と運用者が異なる場合に、運用者がプロンプトをチューニングしたいケースです。

以下に、構成の概要を示します。

Bedrock

東京リージョンで、利用したいモデルを有効化します。今回は、Claudeが利用できるよう設定しました。

S3バケット

S3バケットに、prompt.txtというファイル名でプロンプトを記載しアップロードします。

プロンプト内容は、以下の通りです。

あなたは、レストランの電話担当者です。
お客様からのお問い合わせ内容を元に、ルール内のリストからもっとも適切な種別を選んでください。
存在しない問い合わせ種別に遭遇した場合は、「その他」を回答してください。
<rule>
1. 返信には、「以下のように変換しました」等の文言は含めないでください。
2. お客様のお問い合わせ内容に最も適した種別を次のリストから選択してください:
- メニューに関する問い合わせ
- 大人数対応に関する問い合わせ
- 予約に関する問い合わせ
- 営業日や時間に関する問い合わせ
3. 回答は、選択した種別のみを返してしてください。
</rule>
お客さんのお問い合わせ内容は次のとおりです。
<question>
{input_text}
</question>

元ネタは、以下のブログです。Amazon Connectを利用した電話でのお問い合わせをAIチャットボットが対応し、Amazon BedrockのClaudeを用いてお問い合わせの種別判定を行うときに利用したプロンプトです。

プロンプトの内容を変更したい場合は、prompt.txtファイルを編集してS3バケットに再アップロードするだけです。

Lambda

Lambdaを以下の設定で作成します。

  • ランタイム:Python 3.12
  • タイムアウト:20秒
  • IAMロールに追加するIAMポリシー
    • AmazonBedrockFullAccess
    • AmazonS3FullAccess
import json
import boto3

s3 = boto3.client('s3')
bedrock_runtime = boto3.client('bedrock-runtime')

def get_prompt_from_s3(bucket_name, file_key):
    response = s3.get_object(Bucket=bucket_name, Key=file_key)
    prompt = response['Body'].read().decode('utf-8')
    return prompt

def lambda_handler(event, context):
    bucket_name = 'バケット名'
    file_key = 'prompt.txt'

    # S3からプロンプトを取得
    prompt_body = get_prompt_from_s3(bucket_name, file_key)
    
    input_text = event['question']

    # S3のプロンプトを組み合わせて最終的なプロンプトを作成
    prompt = f"\n\nHuman: {prompt_body}\n<question>\n{input_text}\n</question>\n\nAssistant:"
    
    print("Received prompt:" + json.dumps(prompt, ensure_ascii=False))

    accept = 'application/json'
    contentType = 'application/json'
    modelId = 'anthropic.claude-instant-v1'
    # modelId = 'anthropic.claude-v2:1'

    body = json.dumps({
        'prompt': prompt,
        'max_tokens_to_sample': 1000,
        'temperature': 0,
    })

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

    response_body = json.loads(response.get('body').read())
    
    return {
        'statusCode': 200,
        'body': response_body.get('completion').strip()
    }
  • 全体の流れとしては、S3バケットのプロンプトファイルのテキストを取得し、そのテキストをプロンプトとしてBedrock Claudeモデルを呼び出し、種別判定を行います。
  • 変数名bucket_nameは、使用するS3バケット名を指定します。
  • Claude 2.1ではなく、Claude Instantモデルを利用しています。
  • 変数名input_textにはユーザーからの問い合わせ内容を挿入します。
  • 変数名promptでは、固定のフレーズ 「\n\nHuman:」と 「\n\nAssistant:」 を追加し、S3から取得したプロンプトと質問(input_text)を組み合わせています。

Lambdaを実行

今回の質問は「二十人で予約したいんですが入りますか」とし、Lambdaを実行します。

テストイベントの設定は以下の通りです。

{
  "question": "二十人で予約したいんですが入りますか"
}

Lambdaを実行すると、結果としてプロンプトは以下のようになります。Lambdaのログからも確認できます。

\n\nHuman:あなたは、レストランの電話担当者です。
お客様からのお問い合わせ内容を元に、ルール内のリストからもっとも適切な種別を選んでください。
存在しない問い合わせ種別に遭遇した場合は、「その他」を回答してください。
<rule>
1. 返信には、「以下のように変換しました」等の文言は含めないでください。
2. お客様のお問い合わせ内容に最も適した種別を次のリストから選択してください:
- メニューに関する問い合わせ
- 大人数対応に関する問い合わせ
- 予約に関する問い合わせ
- 営業日や時間に関する問い合わせ
3. 回答は、選択した種別のみを返してしてください。
</rule>
お客さんのお問い合わせ内容は次のとおりです。
<question>
二十人で予約したいんですが入りますか
</question>
\n\nAssistant:

Lambda実行後のレスポンス内容は以下の通りです。

{
  "statusCode": 200,
  "body": "大人数対応に関する問い合わせ"
}

お問い合わせ内容(「二十人で予約したいんですが入りますか」)に対して「大人数対応に関する問い合わせ」と種別判定されました。これは正しい判定です。

以上のように、コード修正することなく、プロンプトを容易に変更できます。

参考