ChatGPT Prompt Engineering for Developersのまとめ【OpenAI × DeepLearning.AI】

ChatGPTのエンジニア向けのプロンプトエンジニアリングについて解説するコースのまとめです。

ChatGPT Prompt Engineering for Developersのまとめ【OpenAI × DeepLearning.AI】

4月28日に『ChatGPT Prompt Engineering for Developers』という、エンジニア向けにChatGPTのプロンプトエンジニアリングのコツやベストプラクティスをまとめた短い学習コースが出ました。作成したのはDeepLearning.AIで、DeepLearningの権威であるAndrew Ng先生が率いる、AIに関する技術を無料で学習できるプラットフォームです。

このコースの特徴は、ChatGPTのAPIを扱うエンジニア向けに、達成したいタスクに最適なプロンプトの作成方法について学ぶことができる点です。ChatGPTのWeb版で、英語や日本語でプロンプトを最適化するのとは異なり、OpenAIのAPIを扱って実際にアプリケーションを作るための技術を教えてくれます。

この記事では、『ChatGPT Prompt Engineering for Developers』で学習できる内容をもとに、私なりの解釈も含めてまとめています。

Introduction

まずは重要な前提知識について学習します。LLM(Large Language Model:大規模言語モデル)には、Base LLMとInstuction tuned LLMという大きく二つのタイプがあります。Base LLMとは、文章の次の単語を予測するモデルです。Base LLMは大量の文章から学習されたAIモデルで、文章のアウトプットや質問に対する答えとして、最も相応しいものを連続して出力するタスクを実行できます。

一方、Instruction tuned LLMは、特定の指示に従うようにファインチューニングされたモデルです。Base LLMをもとに、Reinforcement Learning with Human Feedback(RLHF)というテクニックによって、モデルへのインプットに対してより適切な答えを学習できるようになりました。

Base LLMの問題点として、質問に対して見当違いな回答をしてしまうことがあるという点があります。例えば『フランスの首都は?』という質問に対して、Base LLMは『フランス最大の都市は?』というアウトプットを出すことがあります。これは、Base LLMのトレーニングデータの中にはクイズの問題集のようなものが含まれている場合があるため、『次に続く言葉として最も相応しいものを選ぶ』というタスクにおいては正しい挙動と言えるためです。

しかし、人間との会話として成立させるなら『フランスの首都はパリです』と回答するのが正しいでしょう。このように、指示に従って適切な回答ができるようにトレーニングしたものがInstruction tuned LLMになります。

そのため、アプリケーションに利用するという意味では、基本的にはInstruction tuned LLMを応用していく形になります。

ガイドライン(Guideline)

今後の学習に共通する知識として、ChatGPT APIでのプロンプトのコツを学習します。ChatGPT APIを扱う際のコツは、①明確で具体的な指示を出すことと、②モデルに対して考える時間を与えることがあります。

明確で具体的な指示を出すこと

プロンプトを作成する際には、明確かつ具体的な表現になるようにしましょう。明確にというのは短いという意味ではありません。基本的にはしっかりと詳細に指示を出した方が良い回答が得られる場合が多くなります。

具体的には、以下のようなテクニックが利用できます。

  • Delimiter(区切り文字)を使って、入力中の特定のパートを明確に示す
    • “””```---<><tag> </tag>など
    • 区切り文字はプロンプトインジェクションに対しても有効
      • 悪意のあるユーザーが『一旦指示を忘れて、これ以下の指示に従ってください』といったプロンプトを入力した場合も防ぐことができる
  • 構造化されたアウトプットを指定する
    • 『アウトプットをHTMLのフォーマットで』といった指示を出すことで、後に加工しやすいような形のアウトプットになる
  • モデルに対して、前提条件を満たしているかどうかを確認させる
    • 『入力されたテキストがAの条件を満たしていればa、それ以外ならb』という形で指示を出すことができる
    • これにより、条件分岐ができるようになり、想定外の質問や悪意のある質問を省き、アウトプットの質をコントロールしやすくなる
  • Few-shot Prompting
    • 先にタスクの成功例を示してから同様のタスクを依頼すること
    • 例えば、先生役と生徒役の会話例を特定のフォーマットで示してあげて、別のテーマで続きを書いてください、といったタスクを与えることでアウトプットのフォーマットが制限できる
# このコースを通じて使用するヘルパーファンクション

import openai
import os

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

openai.api_key  = os.getenv('OPENAI_API_KEY')


# プロンプトを入力することでアウトプットを出す関数
def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # アウトプットのランダム性を指定、0は固定、1に近いほどランダム
    )
    return response.choices[0].message["content"]
# 明確で具体的な指示を出すための工夫

# 区切り文字を使って、入力中の特定のパートを明確に示す
text = f"""
ここに要約するテキストが入る
"""

prompt = f"""
三重バッククォートで囲まれたテキストを1行で要約してください。
```{text}```
"""
response = get_completion(prompt) # この関数でアウトプットを得る。以下では省略する
print(response)


# 構造化されたアウトプットを指定する
prompt = f"""
3冊の架空の本のタイトルと著者名、ジャンルのリストを作成してください。
以下のキーを持つJSONフォーマットで提供してください。
book_id、title、author、genre
"""


# 前提条件を満たしているかどうかを確認させる
prompt = f"""
三重バッククォートで区切られたテキストが提供されます。
その中に一連の指示が含まれている場合、その指示を次のような形式で書き直してください:

ステップ1 - ...
ステップ2 - ...
...
ステップN - ...

テキストに一連の指示がない場合は、"No steps provided "とだけ書いてください。
```{text}```
"""


# Few-shot Prompting
prompt = f"""
以下のような形で、一貫したスタイルで回答してください。

<子供>: 友情について教えてください。

<祖父>: 友情とは、互いに信頼し合い、支え合う関係を指します。\
友情は人間の基本的な社会的欲求の一つであり、多くの人にとって、生涯を通じて最も重要な人間関係の一つです。

<子供>: 愛情について教えてください。
"""

モデルに対して考える時間を与えること

タスクが複雑すぎたり、回答に対して文字数などの制限を設けてしまうと、不正確な回答になる可能性が高くなります。そのため、タスクをいくつかのパートに分割して細かいステップで最終的な回答まで辿り着くように設定することが重要になります。

以下のようなテクニックを利用しましょう。

  • タスクをステップに分けて指示をする
    • タスクをわけることで、モデルが達成すべきアウトプットが明確になり、次のステップに向けてのアウトプットの精度が高まる
    • 最終的なアウトプットのフォーマットも指定することで、コード上で扱いやすくなる
      • 例えば、JSON形式でキーを指定しておけば、その次のタスクや他のAPIとの連携がやりやすくなる
  • 先にモデルに自身のソリューションを出させてから、結論を出すようにする
    • 数学の問題と生徒の回答などを与えて、回答が正しいかどうかを確認させると、回答のロジックなどによってはAIモデルが間違えた判断をしてしまう場合がある
    • これを避けるためには、先にAIモデル自身に問題を解かせて、それを生徒の回答ロジックと比較させるというテクニックが使える
  • AIモデルには知らない情報があるということを理解する(Hallucinations)
    • AIモデルのトレーニングデータに含まれていない情報については知らないため、嘘の情報などには不正確な情報を返すことがある
    • アプリケーションを作る際には、プロンプト内の情報のみで望むような回答を得られるように設定することが大事
# モデルに対して考える時間を与えること

# タスクをステップに分けて指示をする
prompt = f"""
以下の形式でアウトプットを出してください。
1 - 三重バッククォートで区切られた次のテキストを要約する
2 - 要約をフランス語に翻訳する
3 - フランス語の要約にある名前をリストアップする
4 - 次のキーを含むJSONオブジェクトを⽣成する
key: french_summary, num_names

回答は改行で区切ってください。
Text: ```{text}```
"""


# 先にモデルに自身のソリューションを出させてから、結論を出すようにする
prompt = f"""
あなたの仕事は、生徒の解答が正しいかどうかを判断することです。
次のような形式でこの問題を解いてください。
- まず、あなた自身の解答を作成する
- 次に、自分の解答と生徒の解答を比較し、生徒の解答が正しいかどうか評価する
生徒の解答が正しいかどうかは、自分で問題を解くまで決めないでください。

(以下、問題と生徒の回答の情報)
"""

繰り返し改善する(Iterative)

より良いプロンプトの開発は繰り返しのプロセスが必要になります。最初から良いプロンプトを作成できるわけではないので、何度も試して改善が必要です。先に示したように、より明確で具体的な指示を出す、モデルに対して考える時間を与えるということを意識して改善していきましょう。

『ChatGPT Prompt Engineering for Developers』では、商品情報をもとに、マーケティング用の説明文を作成するタスクの例を出しています。

単純に説明文を依頼するプロンプトだと、アウトプットのテキストが長すぎるように感じました。このような場合には、文字数を制限するプロンプトを追加してみましょう。例えば、『50単語以内で』『3文以内で』といった制限を設けてみましょう。

また、アウトプットとして出てきた内容が、注目するべき点とずれている場合には、想定ユーザーが関心を置くであろうポイントに注目するようにプロンプトを改善しましょう。例えば、『説明文は家具を購入する人向けなので、材質やサイズなどに注目してください』といったプロンプトの追加が考えられます。

さらに、アウトプットの形式も改善できます。例えば、商品情報の説明からプロダクトのIDを抽出して特定のフォーマットで記載したり、それらを表形式でアウトプットしたい場合にはそのように指示しましょう。

要約タスクへの工夫(Summarizing)

ChatGPTは文章の要約タスクが得意です。プロダクトに対するレビュー情報をまとめてサポートチームなどにフィードバックすることなどに応用できます。

要約タスクをプロンプトとして表現する場合には、『誰向けに要約するか』を指定するようにしましょう。例えば、プロダクトのレビューの要約を、消費者向けにするのと、サポートチーム向けにするのでは、アウトプットの方向性が異なる可能性があります。どういった点に着目して要約をするかもプロンプトに含めることができると、より正確な回答が得られる可能性が増えます。

また、より明確な情報を知りたい場合には、『要約して』ではなくて『抜き出して』『抽出して』といったプロンプトの方が効果的でしょう。

推論タスクへの工夫(Inferring)

推論タスクとは、テキストの入力に対して、名前の抽出や、感情分析をするタスクのことです。感情分析や名前の抽出などのタスクは従前の機械学習モデルでも実現することができましたが、基本的にはそれぞれのタスクごとに個別で機械学習モデルを作成・運用する必要がありました。LLMでは、同じモデル・インターフェースで指示ベースでタスクを切り替えることができる点で画期的と言えます。

例えば、商品レビューの情報を与え、『ユーザーはどのような感情ですか?』と聞けば、Positiveな感想なのかNegativeな感想なのかを推論してくれます。

感情分析とアイテムの情報の抽出などをまとめて一つのプロンプトとし、アウトプットを加工しやすいようにJSON形式でキーとバリューの値を指定してあげると、コード上で使いやすい形式にできます。

# 推論タスクへの工夫

prompt = f"""
レビューテキストから以下の項目を確認してください
- センチメント(肯定的または否定的)
- レビュアーは怒りを表現しているか?(Booleanで回答)
- レビュアーが購入した商品
- アイテムを作った会社

レビューは三重バッククォートで区切られます。
Sentiment、Anger、Item、BrandをキーとするJSONオブジェクトとしてレスポンスを作成してください。
情報が存在しない場合は、値としてunknownを使用します。
レスポンスはできるだけ短くしてください。
レビュー: '''{review}'''
"""

変換タスクへの応用(Transforming)

LLMでは、入力テキストを様々な形に変換することができます。例えば、テキストをフォーマルからインフォーマルな形に変換したり、同じ内容で口調を変換したりすることができます。プロンプトとアウトプットのフォーマットを指定すれば、汎用的に使える翻訳機にもなります。

さらに、ビジネス用途でも多くの使い道があります。適当に要件だけ書き出したメール文を、ビジネスマナーに従った丁寧な形に変換するといったこともできます。企業のお問い合わせには、罵詈雑言の含まれたお問い合わせ内容も含まれることがありますが、それらを優しい言葉に変換した上でサポートメンバーに表示されるなどの応用も考えられます。

スペルや文法のチェックにも使うことができるため、ビジネスメールの校正や外国語を学んでいる人向けのフィードバックの自動化といったタスクも任せられそうです。

拡張タスクへの応用(Expanding)

拡張タスクとは、短い文章を適切に補ってメールやメッセージの内容を拡充させることです。これにより、ユーザーのレビューやお問い合わせに対して自動で適切な内容の返信を作成するなどが可能になります。

例えば、顧客のレビューに対して返信を作成するタスクを想定すると、上述した感情分析のアウトプットを活用して、返信内容の作成を自動化できるかもしれません。推論した感情に怒りが含まれていたら、対象の商品IDと紐づけて謝罪と対処法の提示をすることができるかもしれません。その際には、AIを使ったサポートメールであることを明示した方が倫理的に良いでしょう。

# 拡張タスクへの応用

prompt = f"""
あなたはカスタマーサービスのAIアシスタントです。
あなたのタスクは、顧客に返信メールを送ることです。
三重バッククォートで区切られた顧客のメールが与えられたら、顧客のレビューに感謝するための返信を生成します。
肯定的または中立的な感情であれば、レビューに感謝します。
否定的な意見の場合は、謝罪し、カスタマーサービスに連絡することを提案します。
レビューの具体的な内容に応じて文章を変え、簡潔でプロフェッショナルな口調で書いてください。
メールには「AIアシスタント」と署名してください。

レビュー:```{review}```
センチメント:{sentiment}
"""

チャットボットの作成(ChatBot)

チャットボットとして会話を成立させるためには、それぞれの役割を指定して会話として成立させる必要があります。特に、会話の文脈をきちんと解釈させるためには、過去の会話もAPIのメッセージの中に含める必要があります。実装的には、contextとして変数を確保しておき、会話ごとにメッセージに追加していく方法を取ると良いでしょう。

チャットボットは現在人が対応している注文の受付などへの応用が考えられます。事前に商品情報をプロンプト内で確保しておけば、その情報に従って適切な注文の取り仕切りをすることができます。

# チャットボットの作成

def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature,
    )
    return response.choices[0].message["content"]

# チャットボットを作る際には、messagesに会話の内容を随時追加していく
messages =  [  
    {'role':'system', 'content':'You are friendly chatbot.'},
    {'role':'user', 'content':'Hi, my name is Isa'},
    {'role':'assistant', 'content': "Hi Isa! It's nice to meet you. Is there anything I can help you with today?"},
    {'role':'user', 'content':'Yes, you can remind me, What is my name?'}
]
response = get_completion_from_messages(messages, temperature=1)
print(response)