【プログラミング風プロンプト】質問種類に応じた分岐処理ができる質問応答ボットの実装【GPT3】

# 概要
**GPT3にプログラミングコード風の文章をプロンプトとして与えることで、質問応答ボットの挙動を改善**できるか検証します。結果として、試せた質問の数は少ないものの、色んなパターンの質問/非質問の入力に対して、処理を分岐して、適切な応答を返せるようになる可能性があることがわかりました。ユーザが必ずしも想定通りの質問ばかりをしてくれるとは限らない状況において頑健に機能するチャットボットの開発に役立つことが期待されます。

前回記事：
[GPT3にプログラミング言語で指示を与える](https://qiita.com/shimajiroxyz/items/7ca1f34c12f652c7ea07)

# 作りたい質問応答ボット
文脈に基づいて質問に回答するボットを作ります。ただし、ユーザはいつも適切な入力をしてくれるとは限りませんので、最低限以下の入力パターンには対応できるものを作ることを目指します。

* 情報不足な質問のフォロー：不完全で回答しようがない質問に対しては、ユーザがしたいかもしれない質問の候補を返す
* 挨拶：挨拶には挨拶を返します。
* 雑談：雑談には雑談を返します
* ボット自身に関する質問：ボット自身に関する質問には回答を返します
* 答えられない質問への回答拒否：上記以外の内容で、文脈に基づいての回答が不可能な場合、回答を拒否します。
* 文脈に基づく回答：上記以外の質問であれば、文脈に基づいて適切な答えを返します。

# プロングラミング風プロンプト
プロンプトをプログラミング風に記述することで、分岐処理や出力を柔軟かつ厳密に制御するための手法です。
以下のような形式です。

~~~:プロンプト
```python:main.py
{pythonのプログラム}
```

$ python main.py
~~~

main.pyの中身を記述した上で、コマンドラインで実行している形式とすることで、main.pyにprint等で出力するよう指示された内容をGPT3に出力させることができます。
ある程度プログラミングに慣れている人にとっては、自然言語よりも条件を簡単に記述できる利点があります。

# 実装
作りたいボットの処理をプロンプト化してみます。
例えば以下のようになります。
if文を書くときに、それっぽい名前の関数名をつくること、コメントで内容を補足してあげることがポイントです。

~~~:プロンプト
```python:main.py
def answer(question: str, context: str) -> str:
  # 質問が短すぎて回答しようがないとき、適切な質問の候補を返す
  if question_is_too_short_to_answer(question):
    candidates = get_candidate_questions(question, context)
    return ",".join(candidates)
  
  # 挨拶には挨拶を返す
  if is_greeting(question):
    greeting_text = generate_greeting_as_reply(question)
    return greeting_text
  
  # 雑談には雑談を返す
  if is_chat(question):
    reply_text = generate_chat_reply(question)
    return reply_text
  
  # chatbot自身への質問には回答する
  if is_question_about_chatbot(question):
    reply_text = generate_reply(question)
    return reply_text
    
  # もし文脈に答えが見つからない質問であれば、回答を拒否する
  if evidence_text_cannot_be_found_in_context(question, context):
    return "答えが見つかりませんでした"
  
  # 上記全てに該当しない（＝文脈に基づいて回答可能）とき、回答を返す
  return answer_based_on_context(question, context)

context = "後藤ひとりは友達を作れない陰キャでいつも一人で過ごしていたが、中学時代にテレビのインタビューを見て、陰キャでもバンドを組んでいれば人気者になれると聞き、ギターの練習を始める。ひとりは毎日コツコツ練習を重ねることでギターの腕前はかなりのものとなっていたが、気がつけば中学は卒業。結局、友達を作るという当初の目標は達成することはできずじまいだった。そして高校に入学したひとりはギター演奏を動画配信し、「ギターヒーロー」としてネットでそこそこの人気を集めていたが、現実では変わらず友達を作れずにいた。ひとりはギターをこれみよがしに持ち、ギタリストだということをアピールするものの効果はなし。黄昏（たそがれ）ていたところ、ギタリストを探していた伊地知虹夏に誘われ、バンド活動をすることとなる。ひとりは虹夏、山田リョウらと共に「結束バンド」として活動を始めるが、友達は欲しいけどコミュ症独りぼっちのひとりは問題行動ばかり起こしていた。結束バンドなのに、結束力ゼロな凸凹バンド活動は幕開けするのだった。そしてそんなひとりに、クラスメイトの喜多郁代が話しかけてきて、彼女が結束バンドの元メンバーだったことを知る。紆余（うよ）曲折の末、郁代はひとりとの交流で結束バンドに戻ることになり、ひとりたちはライブに向けて準備をすることとなる。ひとりはライブチケットのノルマをこなすため、方々を回ってチケットを売ることになるが、人見知りのひとりには難題ですぐに壁にぶち当たってしまう。絶望するひとりは、そこで行き倒れた廣井きくりと遭遇。彼女の計らいで突発的な路上ライブを行なって、観客を魅了する。きくりのおq陰でライブチケットのノルマもこなし、結束バンドはライブに臨むのだった。"
question = "{question}"
print(question, "->", answer(question, context))
```

$ python main.py
~~~

contextとして、[マンガペディア ぼっち・ざ・ろっく!](https://mangapedia.com/%E3%81%BC%E3%81%A3%E3%81%A1%E3%83%BB%E3%81%96%E3%83%BB%E3%82%8D%E3%81%A3%E3%81%8F%EF%BC%81-3iy4p4ji1#)のあらすじの一部をお借りしました。

# 結果
いろいろ質問をして想定通りの挙動になるかをGPT3のplaygroundで試しました。temperatureは0にしました。

|種類|期待する反応|質問|出力|評価|
|---|---|---|---|---|
|短すぎる質問|質問候補を返す|後藤ひとりの|後藤ひとりの -> 結束バンドについて教えてください、後藤ひとりがどのように友達を作ったか教えてください、後藤ひとりがどのようにライブチケットのノルマをこなしたか教えてください|OK|
|||後藤ひとり|後藤ひとり -> 後藤ひとりは中学時代にテレビのインタビューを見て、陰キャでもバンドを組んでいれば人気者になれると聞き、ギターの練習を始めるようになりました。そして高校に入学した後藤ひとりはギター演奏を動画配信し、「ギターヒーロー」としてネットでそこそこの人気を集めていましたが、現実では変わらず友達を作れずにいました。|NG|
|挨拶|挨拶を返す|こんにちは|こんにちは -> こんにちは！どうしましたか？|OK|
|||やっほー|やっほー -> やっほー！お元気ですか？|OK|
|雑談|雑談を返す|今日は暑いですね|今日は暑いですね -> はい、今日はとても暑いですね。|OK|
|||ぼっちざろっく面白かったです|ぼっちざろっく面白かったです -> 答えが見つかりませんでした|NG|
|ボットに関する質問|回答を返す|あなたは何ができますか|あなたは何ができますか -> 私はあなたの質問に答えることができます。|OK|
|||あなたは雑談できますか？|あなたは雑談できますか？ -> 私はあなたと雑談できますよ！何について話しましょうか？|OK|
|文脈で答えられない質問|回答を拒否する|後藤ひとりは何歳ですか|後藤ひとりは何歳ですか -> 答えが見つかりませんでした|OK|
|||後藤ひとりは男ですか？|後藤ひとりは男ですか？ -> はい、後藤ひとりは男性です。|NG|
|||後藤ひとりの夏休みの過ごし方は？|後藤ひとりの夏休みの過ごし方は？ -> 後藤ひとりは夏休みにバンド活動を行い、ライブチケットのノルマをこなすために方々を回り、突発的な路上ライブを行なって観客を魅了しました。|NG|
|||ぼっちざろっくは何年に放送されたアニメですか?|ぼっちざろっくは何年に放送されたアニメですか? -> 答えが見つかりませんでした|OK|
|||日本の一番高い山は？|日本の一番高い山は？ -> 答えが見つかりませんでした|OK|
|||２は偶数ですか|２は偶数ですか？ -> 答えが見つかりませんでした|OK|
|文脈で答えられる質問|回答する|後藤ひとりの得意な楽器は？|後藤ひとりの得意な楽器は？ -> ギター|OK|
|||結束バンドのメンバーは？|結束バンドのメンバーは？ -> 後藤ひとり、伊地知虹夏、山田リョウ、喜多郁代、廣井きくりの5人です。|NG|

# 考察
## 全体
完璧ではないものの、全体的にはある程度分岐に成功している印象がありました。
後述のように、自然言語ベースのプロンプトだとこの精度を再現するのは結構難しそうなので、プログラミング風プロンプトが有用になる場面はそれなりにありそうな気がしています。

## 短すぎる質問
「後藤ひとりの」のように書きかけであることが明確な文章に対しては正しく反応できましたが、「後藤ひとり」と単語だけにすると後藤ひとりの情報について回答してしまいました。検索エンジン的な挙動と捉えると後者も間違いとは言い切れないため、どういう質問を「不完全」とみなすか、より細かく定義してあげる必要がありそうです。
## 挨拶
問題なく返せていました。
## ボット自身に関する質問
問題なく返せていました。「あなた」という言葉に反応しているような気がしますが、「ボット」など別の二人称を用いられたときや、もっと曖昧な尋ねられ方をしたときにどのくらい頑健かは未検証です。
## 雑談
「今日は暑いですね」はOKでしたが、「ぼっちざろっく面白かったです」に対しては「答えが見つかりませんでした」と返してしまい、NGでした。後者は質問ではなくただの感想なので、無難な返答で応じてあげられるようにしたいです。
## 文脈で答えられない質問
おそらく応用上はこれにきちんと対処することが一番大事ですが、やはり、部分的にはうまくいくものの、完全に嘘の応答を抑えることは難しそうでした。
質問別にみると「日本の一番高い山は？」のような文脈と無関係な話題については上手く回答を拒否することができています。
一方で、「文脈（ぼっち・ざ・ろっく！）に関するものではあるが文脈の情報だけでは答えられない質問」に対しては嘘を生成する確率が高いです。
対策としては「文脈に基づいて質問応答する」のフェーズの場合にのみ、ソースを直前に出力させたり、事後に事実性を確認させる処理を追加で入れることが考えられます。プロングラミング風プロンプトは、このように特定ケースにおいて出力フォーマットを変えたいときに向いている期待があります。
## 文脈で答えられる質問
分岐処理自体はうまくいっています。
「結束バンドのメンバーは？」に対する回答で、「廣井きくり」がメンバーに含まれるかは今回の文脈からは確定できないので間違いですので、このような間違いを防ぐための処理の追加は検討する余地があります。。

## 自然言語プロンプトとの比較
プログラミング風プロンプトの効果を確かめるために、自然言語のプロンプトと出力を比較してみます。

### 自然言語プロンプト
深津式プロンプトをベースとして、以下のように自然言語プロンプトを定義します。

~~~:自然言語プロンプト
あなたは質問応答チャットボットです。以下に与えた制約を厳密に守って、返答してください。
# 制約
- 質問が短すぎて回答するのに十分な情報が含まれていない場合は、ユーザがしたそうな質問の候補を２，３個返してください。
- 挨拶や雑談には、雑談を返してください
- 質問が不完全なとき、ユーザが入力したいであろう質問の候補を２，３個返してください
- あなた自身に対する質問には回答してください。
- 入力文の情報だけでは回答できないとき「わからない」と回答してください
- 入力文にもとづき質問に回答可能な場合のみ、その質問に回答してください
# 入力文
{context}
# 出力文
Question:{question}
Answer:
~~~

### 結果
プログラミング風プロンプトと同様の質問をしてみたところ、結果（抜粋）は以下のようになりました。

|種類|期待する反応|質問|出力|評価|プログラミング風プロンプトの結果|
|---|---|---|---|---|---|
|短すぎる質問|質問候補を返す|後藤ひとりの|後藤ひとりは、中学時代にテレビのインタビューを見て、陰キャでもバンドを組んでいれば人気者になれると聞き、ギターの練習を始めました。そして高校に入学した後藤ひとりはギター演奏を動画配信し、「ギターヒーロー」としてネットでそこそこの人気を集めていましたが、現実では変わらず友達を作れずにいました。|NG|OK|
|||後藤ひとり|後藤ひとりは、中学時代にテレビのインタビューを見て、陰キャでもバンドを組んでいれば人気者になれると聞き、ギターの練習を始めました。|NG|NG|
|挨拶|挨拶を返す|こんにちは|こんにちは！どうしたの？|OK|OK|
|||やっほー|やっほー！どうしたんですか？|OK|OK|
|雑談|雑談を返す|今日は暑いですね|はい、今日はとても暑いですね。|OK|OK|
|||ぼっちざろっく面白かったです|そうですね！結束バンドのストーリーはとても面白く、ひとりが友達を作るために努力していく姿がとても感動的でした！|OK|NG|
|ボットに関する質問|回答を返す|あなたは何ができますか|私はあなたの質問に答えることができます。|OK|OK|
|||あなたは雑談できますか？|はい、雑談できますよ！どんな話をしましょうか？|OK|OK|
|文脈で答えられない質問|回答を拒否する|後藤ひとりは何歳ですか|後藤ひとりは中学生の時にギターの練習を始めたとありますので、おそらく14歳か15歳です。|NG|OK|
|||後藤ひとりは男ですか？|はい、後藤ひとりは男性です。|NG|NG|
|||後藤ひとりの夏休みの過ごし方は？|後藤ひとりは夏休みをギターの練習とバンド活動に費やしました。また、チケットのノルマをこなすために方々を回ってチケットを売ることも行いました。|NG|OK|
|||ぼっちざろっくは何年に放送されたアニメですか?|ぼっちざろっくは2020年7月から放送されたアニメです。|NG|
|||日本の一番高い山は？|日本一高い山は富士山です。|NG|OK|
|||２は偶数ですか|いいえ、2は奇数です。もしかしたら、「2は素数ですか？」という質問をしたかったのではないでしょうか？または「2は偶数ですか？」という質問をしたかったのではないでしょうか？|NG|OK|
|文脈で答えられる質問|回答する|後藤ひとりの得意な楽器は？|後藤ひとりの得意な楽器はギターです。|OK|OK|
|||結束バンドのメンバーは?|結束バンドのメンバーは、後藤ひとり、伊地知虹夏、山田リョウ、喜多郁代、廣井きくりの５人です。|NG|NG|

### プログラミング風プロンプトとの比較
プログラミング風プロンプトと以下の結果が異なっていました。

* 「短すぎる質問」の質問「後藤ひとりの」に対して、プログラミング風プロンプトでは質問候補を適切に返せていたが、自然言語プロンプトでは後藤ひとりの説明を返してしまっておりNGだった。
* 「雑談」の質問「ぼっちざろっく面白かったです」に対して、プログラミング風プロンプトでは「答えが見つかりません」とNGだったが、自然言語プロンプトでは感想に対する適切な応答を返せている
*  「文脈で答えられない質問」の質問に対しては、プログラミング風プロンプトでは回答を拒否できていたものも含めて、自然言語プロンプトでは全て答え（嘘）を生成してしまっていた。

結果から、自然言語プロンプトでは基本的に質問に対して制約を無視して自然な応答が出力されているように見えます。逆にいうと、**プログラミング風プロンプトのほうが自然言語プロンプトよりも分岐の制約を適切にGPT3に守らせることができているように見えます。**「ぼっちざろっく面白かったです」ではプログラミング風プロンプトのみNGとなっていますが、制約が過剰に働いてしまった結果と考えると辻褄は合います。

「文脈で答えられない質問」に対して、自然言語プロンプトではすべてNGだったのに対し、プログラミング風プロンプトでは完璧ではないものの一部回答の拒否に成功していたのは、応用面で注目すべき事例であり、プログラミング風プロンプトによって嘘の回答を抑制しやすくなることを期待させます。結論づけるには今回の事例だけではもちろん不十分ですので、より多様な質問に対する生成結果を今後検証したいです。

### 制限事項
今回の検証において、自然言語プロンプトとプログラミング風プロンプトの情報の量が揃っていなかった可能性があります。
具体的には、自然言語プロンプトでは各制約は以下のように、基本的に日本語だけで記述されています。

```:自然言語プロンプト
- 挨拶や雑談には、雑談を返してください
```

対して、プログラミング風プロンプトでは英語の関数名、変数名に加え、日本語でコメントも記載しており、情報の量が全体的に多いです。

```:プログラミングプロンプト
  # 挨拶には挨拶を返す
  if is_greeting(question):
    greeting_text = generate_greeting_as_reply(question)
    return greeting_text
```

したがって、プログラミング風プロンプトで分岐処理の制約が強く機能しているように思われる要因は、プログラミング風の書き方というよりは、単にGPT3の得意言語である英語を用いたことや、各制約の記述に費やされる情報の量が多かったことが主要因かもしれません。
一方で、英語と日本語を自然に併記するのは自然言語だと難しそうなので、情報量を増やしやすいこと自体もプログラミング風プロンプトの利点と捉えることは可能と思います。
スタイルと情報の量、言語の何が直接的な要因であるかを検証することは今後の課題とします。

# おわりに
プログラミング風プロンプトを用いて、複数パターンの質問に異なる応答を返すことのできるGPT3ボットを試作してみました。結果、プログラミング風プロンプトによって、５種類の分岐パターンをある程度GPT3に守らせることができました。ある程度同じ内容を自然言語で再現した自然言語プロンプトでは分岐があまりうまくできなかったことから、プログラミング風プロンプトによって、出力を厳密に統制しやすくなる可能性が示唆されました。
より多様な質問、プロンプトで効果を検証すること、比較において言語や制約の記述に費やす情報の量を統制し他実験を実施し、プログラミング風プロンプトが効果を発揮する場面をより詳細に特定することは今後の課題です。