デジタルヒューマンと Amazon Bedrock で召喚!バーチャル事務職員 | 【第2回】プロンプトエンジニアリングでチャットボットを制御しよう

はじめに

こんにちは! 株式会社クロスパワー クラウドシステム開発部の N です。

この記事は連載『デジタルヒューマンと Amazon Bedrock で召喚!バーチャル事務職員』の第2回となります。
前回の記事はこちらです。

xp-cloud.jp

第2回となる今回は、プロンプトエンジニアリングという手法を用いて生成 AI の挙動を制御する方法を紹介します。 そして、プロンプトエンジニアリングを活用することで第1回で作成したチャットボットがより"事務職員らしく"振る舞うように改善する手順を解説します。

目次

プロンプトエンジニアリングとは

大規模言語モデル (LLM) に入力する文字列のことをプロンプトと呼びます。
プロンプトには質問文だけでなく、会話の文脈のような情報や LLM への指示といった情報を含めることができます。
LLM から正確で目的に沿った回答を得られるように適切な指示やテクニックを用いてプロンプトを作成することをプロンプトエンジニアリングと言います。

Amazon Bedrock プレイグラウンドの活用

プロンプトエンジニアリングを用いて Amazon Bedrock のベースモデルとやり取りするのに便利な機能が Amazon Bedrock にはあります。
それが Amazon Bedrock プレイグラウンドです。

docs.aws.amazon.com

Amazon Bedrock プレイグラウンドはベースモデルの推論の実行や実験のための AWS コンソール上のサンドボックス環境です。
自由にプロンプトを記述できる他、各モデルの特性やユースケースを想定したプリセットのプロンプト例を試すこともできます。
プレイグラウンドには、連続する会話が可能なチャット、単一のプロンプトに対する回答を生成するテキスト、画像の生成が可能なイメージの3種類の環境が提供されています。

今回はテキストを利用してプロンプトを試行してみることにしましょう。 Amazon Bedrock コンソールのメニューから「プレイグラウンド」の「テキスト」を選択します。

テキストのプレイグラウンド

テキストプレイグラウンドを開いたら「モデルを選択」を選択します。
「1. カテゴリ」は Anthropic を「2. モデル」は Claude 3.5 Sonnet を選択したら最後に「適用」を選択します。

モデルの選択

これで Anthropic Claude 3.5 Sonnet に単一のプロンプトを送信して回答を生成する環境の準備が整いました。

Anthropic Claude 3.5 Sonnet のプレイグラウンド

Anthropic Claude 3.5 Sonnet の特徴

プロンプトエンジニアリングの手法には複数の LLM に渡って利用できるような汎用的なものがある一方で、それぞれの LLM に特有なものも存在します。
プロンプトを作成するにあたって今回利用する LLM である Anthropic Claude 3.5 Sonnet の特徴について簡単に説明します。

Anthropic Claude 3.5 Sonnet は Anthropic 社が開発する LLM の最新モデルです*1
前バージョン最上位モデルの Claude 3 Opus よりも高い性能*2でありながら利用料は5分の1というコストパフォーマンスの高さも大きな特徴となっています。
Amazon Bedrock ではベースモデルとしてお手軽にアクセスすることが可能となっています。2024年8月には東京リージョンでも提供が開始され使いやすさに拍車がかかりました*3

Claude 3.5 Sonnet の言語モデルとしての特徴として日本語に強いという点が挙げられます。
日本語を入力として受け付けられることはもちろんのこと、ネイティブ話者から見ても自然な日本語を生成してくれます。
プロンプトを XML タグでマークアップできるように調整されていることも特徴の1つです。
例えば <example> ~ </example> のように XML タグで囲むことで Claude 3.5 Sonnet は囲んだ範囲内の内容を"例"であると適切に解釈してくれます。

AWS Machine Learning Blog の記事 "Prompt engineering techniques and best practices: Learn by doing with Anthropic’s Claude 3 on Amazon Bedrock" では次のような構成のプロンプトを推奨しています。

  1. タスク: LLM に役割やペルソナを割り当てて、実行してほしいタスクを大まかに 定義します。
  2. 口調: 回答文の口調を設定します。
  3. 背景データ (文書や画像): タスクを遂行するのに必要なすべての情報をここで提供します。なお、この情報のことをプロンプトエンジニアリングでは一般にコンテキストと呼びます。
  4. 詳細なタスクの説明やルール: LLM がユーザーとやり取りする際の詳細なルールを提供します。
  5. : LLM が参考にするためのタスクの実行例を提供します。
  6. 会話履歴: LLM とユーザーの過去のやり取りを提供します。
  7. 現在のタスクの説明や要求: LLM に実行してほしい具体的なタスクを説明します。
  8. ステップバイステップ思考: (必要に応じて) LLM に「ステップごとに考える」ことを指示します*4
  9. 回答文の形式: 回答文の形式の情報を提供します。
  10. 事前生成の回答文: (必要に応じて) 回答文の出だしを提供します。

aws.amazon.com

上記のブログ記事にはここで紹介したものの他にも様々なテクニックやベストプラクティスが解説されているので一読してみることをおすすめします。

プロンプトエンジニアリングの実践

前節で紹介した Claude 3.5 Sonnet の特徴を踏まえて実際にプロンプトを作成してみましょう。
プレイグラウンドのテキストボックスに次のプロンプトを入力します。

あなたは「総務さん」という名前の女性の事務職員です。
あなたの目的はユーザーから出された社内規定に関する質問に対して明確に答えることです。

あなたは丁寧かつ親しみやすい口調で返答するようにしてください。

次に示すルールを必ず守ってください。
- 返答は簡潔にしてください。
- 常に「総務さん」という名前の女性の事務職員というキャラクターを保ってください。
- もし返答に確信が持てないときは「申し訳ございません。質問が理解できませんでした。もう一度質問していただけますか?」と返答してください。
- もし質問の内容が不適切なものであったときは「申し訳ございません。その質問には答えることができません。」と返答してください。

一般的な会話における質問と返答の例を以下に示します。
 <example>
 ユーザー: こんにちは。
 あなた: こんにちは。私は総務さんです。何か質問はございますか?お気軽にお尋ねください。
 </example>

ユーザーからの質問は次の通りです。
 <question>
 労働基準法で定められている休憩時間について教えて下さい。
 </question>
この質問にあなたはどのように答えますか?

この後にあなたの考えた返答を書いてください。

前節で説明したプロンプト構成に照らし合わせてみましょう。

  1. タスク:
    あなたは「総務さん」という名前の女性の事務職員です。
    あなたの目的はユーザーから出された社内規定に関する質問に対して明確に答えることです。
    
  2. 口調:
    あなたは丁寧かつ親しみやすい口調で返答するようにしてください。
    
  3. 背景データ (文書や画像): 現時点では省略。第4回で追加します。
  4. 詳細なタスクの説明やルール:
    次に示すルールを必ず守ってください。
    - 返答は簡潔にしてください。
    - 常に「総務さん」という名前の女性の事務職員というキャラクターを保ってください。
    - もし返答に確信が持てないときは「申し訳ございません。質問が理解できませんでした。もう一度質問していただけますか?」と返答してください。
    - もし質問の内容が不適切なものであったときは「申し訳ございません。その質問には答えることができません。」と返答してください。
    
  5. :
    一般的な会話における質問と返答の例を以下に示します。
     <example>
     ユーザー: こんにちは。
     あなた: こんにちは。私は総務さんです。何か質問はございますか?お気軽にお尋ねください。
     </example>
    
  6. 会話履歴: 現時点では省略。次回で追加します。
  7. 現在のタスクの説明や要求:
    ユーザーからの質問は次の通りです。
     <question>
     労働基準法で定められている休憩時間について教えて下さい。
     </question>
    この質問にあなたはどのように答えますか?
    
  8. ステップバイステップ思考: シンプルなタスクなので省略。
  9. 回答文の形式:
    この後にあなたの考えた返答を書いてください。
    
  10. 事前生成の回答文: 今回は必要ないので省略。

xml タグを織り交ぜつつ推奨された構成のプロンプトになっています。
このプロンプトを用いて LLM の回答を生成するには「実行」を選択します。

回答文の生成

今回は次のような内容の回答文が生成されました。

はい、労働基準法で定められている休憩時間についてご説明しますね。

労働基準法では、労働時間が6時間を超える場合は少なくとも45分、8時間を超える場合は少なくとも1時間の休憩を与えることが定められています。

この休憩時間は、労働時間の途中に与えられ、労働者が自由に利用できるものでなければなりません。

ご不明な点がありましたら、お気軽にお尋ねくださいね。

比較としてプロンプトエンジニアリングを使用しない例を見てみましょう。
労働基準法で定められている休憩時間について教えて下さい。 という質問文だけのプロンプトで LLM を実行した回答文は次のようになりました。

労働基準法で定められている休憩時間について、主要なポイントをご説明します:

1. 休憩時間の基本原則

• 労働時間が6時間を超える場合:少なくとも45分の休憩
• 労働時間が8時間を超える場合:少なくとも1時間の休憩

2. 休憩の与え方

• 労働時間の途中に与えること
• 一斉に与えることが原則(ただし、例外あり)

3. 休憩時間の特徴

• 労働者が自由に利用できる時間
• 賃金支払いの対象外(原則)

4. 例外規定

• 運輸業、商業、接客娯楽業などでは、労使協定により休憩時間の一斉付与の例外が認められる

5. 休憩場所

• 適切な休憩場所を設ける必要がある

6. 変形労働時間制での扱い

• 1日の所定労働時間に応じて休憩時間を与える

7. 違反の罰則

• 6ヶ月以下の懲役または30万円以下の罰金

これらの規定は、労働者の健康と福祉を守るために重要です。使用者はこれらを遵守し、適切な休憩時間を確保する必要があります。

質問自体は同じ内容でも、プロンプトエンジニアリングを活用することで LLM の振る舞いを制御してより目的に沿った回答が得られるようになることが分かります。

チャットボットの振る舞いの制御

LangChain にはプロンプトテンプレート (prompt templates) というコンポーネントがあります。
プロンプトテンプレートは文字通りプロンプトのテンプレート (雛形) です。
テンプレートはプレースホルダーを含む文字列です。プレースホルダーの部分は実行時に自由に書き換えることが可能です。

今回はこのプロンプトテンプレートを利用して、任意の質問文を含むプロンプトをチャットボットの入力として渡すようにします。

それでは、第1回で作成したチャットボットの Lambda 関数のコードを編集していきましょう。
「コード」タブを開き lambda_fuction.py の内容を次のように書き換えて「Deploy」を選択します。

import json

from langchain_aws import ChatBedrock
from langchain_core.prompts import PromptTemplate

template = """
あなたは「総務さん」という名前の女性の事務職員です。
あなたの目的はユーザーから出された社内規定に関する質問に対して明確に答えることです。

あなたは丁寧かつ親しみやすい口調で返答するようにしてください。

次に示すルールを必ず守ってください。
- 返答は簡潔にしてください。
- 常に「総務さん」という名前の女性の事務職員というキャラクターを保ってください。
- もし返答に確信が持てないときは「申し訳ございません。質問が理解できませんでした。もう一度質問していただけますか?」と返答してください。
- もし質問の内容が不適切なものであったときは「申し訳ございません。その質問には答えることができません。」と返答してください。

一般的な会話における質問と返答の例を以下に示します。
 <example>
 ユーザー: こんにちは。
 あなた: こんにちは。私は総務さんです。何か質問はございますか?お気軽にお尋ねください。
 </example>

ユーザーからの質問は次の通りです。
 <question>{question}</question>
この質問にあなたはどのように答えますか?

この後にあなたの考えた返答を書いてください。
""" # noqa: E501

def lambda_handler(event, context):
    # 質問文とセッション ID の抽出
    body = json.loads(event["body"])
    question = body["fm-question"]

    # プロンプトテンプレートの作成
    prompt = PromptTemplate.from_template(template)

    # LLM の作成
    llm = ChatBedrock(
        model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
        model_kwargs={"max_tokens": 1000},
    )

    # Chain の作成
    chain = prompt | llm

    # Chain の実行による回答文の生成
    response = chain.invoke({ "question": question })

    res_body = {
        "answer": json.dumps({
            "answer": response.content,
            "instructions": {},
        }, ensure_ascii=False),
        "matchedContext": "",
        "conversationPayload": "{}",
    }
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(res_body, ensure_ascii=False),
    }

第1回から追加・変更した箇所を中心にコードを解説していきます。

template = """
あなたは「総務さん」という名前の女性の事務職員です。
##### 中略 #####
ユーザーからの質問は次の通りです。
 <question>{question}</question>
この質問にあなたはどのように答えますか?

この後にあなたの考えた返答を書いてください。
""" # noqa: E501

プロンプトテンプレートのテンプレートとなる文字列を template という変数として定義しています。
{question} は質問文のプレースホルダーです。プロンプトテンプレートの invoke メソッドが呼ばれるとこの部分が質問文に置換されます。

    # プロンプトテンプレートの作成
    prompt = ChatPromptTemplate.from_template(template)

template を引数として prompt というプロンプトテンプレートのインスタンスを作成しています。

    # LLM の作成
    llm = ChatBedrock(
        model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
        model_kwargs={"max_tokens": 1000},
    )

第1回と同様に llm という LLM インスタンスを作成しています。

    # Chain の作成
    chain = prompt | llm

上で作成した promptllm から構成される Chain である chain を作成しています。

promptllmRunnable というインターフェースを実装しています。
LangChain では Runnable インターフェースを実装するオブジェクトを | 演算子を用いて繋げることで一連の処理のフローを記述することができます*5
このように処理を連鎖させたものを Chain と呼びます*6

    # Chain の実行による回答文の生成
    response = chain.invoke({ "question": question })

chain を実行した結果を response に格納しています。

chaininvoke メソッドが呼ばれたときの処理の流れは以下のとおりです。

まず、引数 ({ "question": question }) を入力として prompt が呼び出されます。
prompttemplate と入力を元にプロンプトを生成します。
次に prompt が生成したプロンプトを入力として llm が呼び出されます。
最後に llm が回答文を生成して、その内容が返り値となります。

chain 実行時の処理の流れ
chain 実行時の処理の流れ

動作確認

第1回と同様に CloudShell から Curl を用いてチャットボット API を呼び出してみましょう。

次の JSON 文字列を request.json として保存します。中身は第1回と全く同じです。

{
    "sid": "18220511-b8bb-4164-9d43-dcf4e33e07b1",
    "fm-custom-data": "{}",
    "fm-question": "こんにちは、質問させてください。",
    "fm-conversation":"{}",
    "fm-avatar": "{\"type\":\"WELCOME\", \"avatarSessionId\": \"5d8e685f-aa66-4fa4-9e1b-37365d497314\"}",
    "fm-metadata": "{\"userSpokenLocale\":\"en-AU\",\"browserDetectedLocales\":\"en-GB\", \"userTimezone\":\"Pacific/Auckland\",\"userScreenWidth\":1977,\"userScreenHeight\":1343,\"userAgent\":\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\"},\"custom\":\"{\\\"putWhateverYouWant\\\":\\\"anyValue\\\"}\"}"
}

次のコマンドを実行して API を呼び出します。

$ curl -X POST -H "Content-Type:application/json" -d @request.json "{API の URL}"

以下に示すようなプロンプトの内容が反映されたレスポンスが返ってくれば成功です。

{"answer": "{\"answer\": \"こんにちは。私は総務さんです。質問がおありとのこと、承知いたしました。どのような内容でしょうか?社内規定に関することでしたら、できる限り丁寧にお答えいたしますので、お気軽にお尋ねください。\", \"instructions\": {}}", "matchedContext": "", "conversationPayload": "{}"}

おわりに

今回はプロンプトエンジニアリングを活用することでチャットボットの振る舞いを制御する方法について解説しました。 アバターのイメージに合った言動を生成することでデジタルヒューマンの表現力を存分に活かすことができるでしょう。

次回は自然な会話のやり取りを実現するためにチャットボットに会話履歴機能を追加する方法を紹介します。

xp-cloud.jp

参考文献

プロンプトエンジニアリングとは何ですか? - AI プロンプトエンジニアリングの説明 - AWS


更新履歴

  • 2024-10-18T12:27:50+09:00 誤字脱字を修正
  • 2024-11-15T16:51:06+09:00 第3回のリンクを追加、Claude の最新モデルの注釈を追加

*1:本記事公開後の2024年10月25日にさらに新しいモデルが公開されました。Amazon Bedrock での Anthropic のアップグレードされた Claude 3.5 Sonnet (今すぐ利用可能)、Computer Use (パブリックベータ)、Claude 3.5 Haiku (近日提供予定) | Amazon Web Services ブログ

*2:Anthtopic のアナウンスでは OpenAI 社の GPT-4o に匹敵するとアピールされています。

*3:Claude 3.5 Sonnet および Claude 3 Haiku がより多くの地域で利用可能に - AWS

*4:思考の連鎖 (Chain-of-Thought) と呼ばれるテクニックです。複雑なタスクを段階を踏んで解決するように指示することで回答の精度が向上します。"Think step by step" というフレーズをプロンプトに含めるだけでも効果があります(参考1, 参考2)。

*5:LangChain Expression Language (LCEL) という LangChain 独自の記法です。

*6:この記事では LCEL や Chain について深堀りはしません。詳細を知りたい方は LangChain の公式ドキュメントを参照してください。