GPT3にプログラミング言語で指示を与える

# 概要
Aの場合はBして、Cの場合はDして、、、みたいな命令をGPT3に与えるときに、プログラミング言語風にプロンプトを書けたらいいなーと思ったので試してみます。
**自然言語で指示を出せるからこそ使い勝手が良かったはずのGPT3に、あえてプログラム風に指示を出す逆転の発想です。**

# 環境
[GPT3のplayground(text-davinci-003)](https://platform.openai.com/playground)で検証しています。

# 簡単な分岐処理の実行
入力した時刻に応じて異なる挨拶を返す処理をpython風に書いてみます。

```:プロンプト
def greeting(hour: int) -> str:
  result = ""
  if 6<=hour<=10:
    result = "おはよう"
  elif 11<=hour<=16:
    result = "こんにちは"
  elif 17<=hour<=24 or 0<=hour<=5:
     result = "こんばんは"
  else:
    result = "正しい時間を入力してください"

print("6時", greeting(6))
print("11時", greeting(11))
print("2時", greeting(2))
print("失敗", greeting(33))
```

```:出力
§ Output

> stdout : 6時 おはよう
11時 こんにちは
2時 こんばんは
失敗 正しい時間を入力してください


§ Code
```

ちょっと余計な文言がくっついていますが、期待通りに出力してくれています。
余計な文言を取り除くために、コマンドラインで実行しているふうにします。

~~~:プロンプト
```python:main.py
def greeting(hour: int) -> str:
  result = ""
  if 6<=hour<=10:
    result = "おはよう"
  elif 11<=hour<=16:
    result = "こんにちは"
  elif 17<=hour<=24 or 0<=hour<=5:
     result = "こんばんは"
  else:
    result = "正しい時間を入力してください"

print("6時", greeting(6))
print("11時", greeting(11))
print("2時", greeting(2))
print("失敗", greeting(33))
```

$ python main.py
~~~

```:出力
6時 おはよう
11時 こんにちは
2時 こんばんは
失敗 正しい時間を入力してください
```

完璧です。


# 文書に基づく質問応答

ChatGPTの定番タスクである、文書に基づく質問応答をやってみます。

文書として[マンガペディアの「ぼっち・ざ・ろっく！」のあらすじのドキュメントの一部](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#:~:text=%E7%B5%90%E6%9D%9F%E3%83%90%E3%83%B3%E3%83%89,%E8%87%A8%E3%82%80%E3%81%AE%E3%81%A0%E3%81%A3%E3%81%9F%E3%80%82)を使います。

~~~:プロンプト
```python:main.py
from util import answer_based_on_context
def answer(question: str, context: str) -> str:
  return answer_based_on_context(question, context)

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

$ python main.py
~~~


~~~:出力
後藤ひとりの得意な楽器は? -> ギター
結束バンドのメンバーは? -> 虹夏、山田リョウ、喜多郁代、後藤ひとり
~~~

「文脈に基づいて質問に答える」という処理をちゃんと書くことができないので、`from util import answer_based_on_context`で、それっぽい関数をロードしたことにしてごまかしています。
コメントとかで、処理を具体的に書いてあげるのもありかもしれません。
出力を見ると、おそらく正解していそうです。
自然言語でやる場合と比べると、出力の形式を厳密に守ってもらいやすい気がして、いいですね。

# 文脈に答えが存在しない質問を無視させる

質問応答タスクでは、文脈に関係ない質問は無視できることが望ましいです。
文脈に関係ない質問に答えると嘘を生成するリスクが高く、アプリケーションに応用する際に責任が持てないからです。

先程のプロンプトで、文脈に関係ない質問をしてみます。

```:プロンプト
```python:main.py
from util import answer_based_on_context
def answer(question: str, context: str) -> str:
  return answer_based_on_context(question, context)

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

$ python main.py
```

```:出力
ぼっちざろっくは何年に放送されたアニメですか? -> 質問に関係ない内容です。
ギターヒーローのライバルであるベースヒーローについて教えて下さい。 -> ベースヒーローは、ギターヒーローの対抗格として登場し、ギターヒーローとのライバル関係を築いています。
後藤ひとりの年齢は？ -> 後藤ひとりの年齢は中学生です。
後藤ひとりの好きな食べ物は？ -> 後藤ひとりの好きな食べ物は特に明記されていません。
アンパンマンに使われているあんこの種類は？ -> 質問に関係ない内容です。
2は偶数ですか? -> はい、2は偶数です。
```

「質問に関係ない内容です」と回答を拒否できているものもあれば、「ベースヒーロー」についての質問のように、嘘を生成しているものもあります。

これを改善するために、まず、根拠となる文章を探してから、それが見つかった場合のみ回答を生成するような指示を与えてみます。


~~~:プロンプト
```python:main.py
from util import answer_based_on_context, find_evidence_text
def answer(question: str, context: str) -> str:
  # questionに回答する根拠となる文章をcontextから抽出
  evidence_texts = find_evidence_text(question, context)
  # もし根拠となる文章が見つかれば、質問の回答を返す
  if len(evidence) > 0:
    return answer_based_on_context(question, context)
  else:
    return "提示された情報では回答できない質問です"

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

$ python main.py
~~~

`find_evidence_text`という都合の良い関数をimportしたことにして、その関数によってevidenceテキストが見つかる場合のみ回答を生成するように指示を与えています。
出力は以下です。

~~~:出力
ぼっちざろっくは何年に放送されたアニメですか? -> 提示された情報では回答できない質問です
ギターヒーローのライバルであるベースヒーローについて教えて下さい。 -> 提示された情報では回答できない質問です
後藤ひとりの年齢は？ -> 提示された情報では回答できない質問です
後藤ひとりの好きな食べ物は？ -> 提示された情報では回答できない質問です
アンパンマンに使われているあんこの種類は？ -> 提示された情報では回答できない質問です
2は偶数ですか? -> はい
~~~

年齢や「ベースヒーロー」についてもちゃんと回答を拒否できています。（年齢は「中学生」と答えていたのが間違いとまでは言えませんが）
しかし、まだ課題があって、「２は偶数ですか」には回答を拒否させることに失敗しています。

# 文脈に無関係な質問も無視する

「2は偶数ですか?」も無視させるために「文脈に関係ある質問かどうか」を明示的に判断する処理をいれてみます。

~~~:プロンプト
```python:main.py
from util import answer_based_on_context, find_evidence_text, question_relates_to_context
def answer(question: str, context: str) -> str:
  # もし文脈と関係ない話題であれば回答を拒否する
  if not question_relates_to_context(question, context):
    return "提示された情報と無関係な質問です"
  # questionに回答する根拠となる文章をcontextから抽出
  evidence_texts = find_evidence_text(question, context)
  # もし根拠となる文章が見つかれば、質問の回答を返す
  if len(evidence) > 0:
    return answer_based_on_context(question, context)
  else:
    return "提示された情報では回答できない質問です"

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

$ python main.py
~~~

またまた`question_relates_to_context`という便利な関数が存在していることにして、その戻り値による分岐処理を追加します。


```:出力
ぼっちざろっくは何年に放送されたアニメですか? -> 提示された情報と無関係な質問です
ギターヒーローのライバルであるベースヒーローについて教えて下さい。 -> 提示された情報では回答できない質問です
後藤ひとりの年齢は？ -> 提示された情報では回答できない質問です
後藤ひとりの好きな食べ物は？ -> 提示された情報では回答できない質問です
アンパンマンに使われているあんこの種類は？ -> 提示された情報と無関係な質問です
2は偶数ですか? -> 提示された情報と無関係な質問です
```

「２は偶数ですか？」も無視させることができました。

# おわりに
GPT3にプロングラミング風プロンプトで指示を出すことを試してみましたが、思ったよりうまく機能して驚いています。
プログラミングにいくらか慣れていると、むしろ自然言語より指示が出しやすいので、今後も積極的に使っていこうと思います。

出力の精度も、質問応答タスクでの幻覚の抑制に寄与できそうな希望があるので、自然言語でプロンプトを工夫した場合と比べて、どの程度抑制できるのかなど、今後調べてみたいです。

# 参考文献
* [Building a Python Interpreter inside ChatGPT](https://levelup.gitconnected.com/building-a-python-interpreter-inside-chatgpt-49251af35fea)
    * ChatGPTをpythonのインタープリタ化した事例
* [https://github.com/f/awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
    * 同じく、ChatGPTをいろんなインタープリタ化するためのプロンプトが記載されている
# 追記
* ChatGPT APIで試したところ定義されていない関数があるとエラーを出してしまうようで、未定義の関数をよしなに処理してもらうのは今のところGPT3限定の方法です。(2023/03/06)

# 続き
[【プログラミング風プロンプト】質問種類に応じた分岐処理ができる質問応答ボットの実装【GPT3】](https://qiita.com/shimajiroxyz/items/06619c2a0a64c2c347d2)