プロンプトを工夫して、ChatGPTに思いどおりの昼食を考えてもらう話
2023-06-09
azblob://2023/06/06/eyecatch/2023-06-06-thinking-about-lunch-with-chatgpt-000.jpg

はじめに

こんにちは!!
最近焼きサバにハマっている富田です。

研修でChatGPT入門して、そろそろ一か月たったかと思います。
この記事ではその間に得られた知見を、「1週間の昼食を考えてもらうプロンプト」を作る過程を見せながら共有したいと思います。

フォーマットを指定する

まずは素直に以下のようなプロンプトを書いてみます。

一週間の昼食を考えてください。

すると、以下のようになります。

以下は、一週間の昼食のサンプルメニューです。これには、バランスの取れた栄養素が含まれており、様々な料理のバリエーションを提供しています。あなたの好みや食事制限に合わせて調整してください。

月曜日:

グリルチキンサラダ:グリルチキンのスライス、新鮮な野菜、レモンドレッシングを添えたサラダ。
スープ:トマトスープまたは野菜スープ。
オレンジ:デザートに新鮮なオレンジ。
火曜日:

サンドイッチ:ローストビーフ、レタス、トマト、チーズを挟んだ全粒パンのサンドイッチ。
ヨーグルト:低脂肪ヨーグルト。
グラノーラバー:スナックにヘルシーグラノーラバー。

<中略>

日曜日:

野菜炒め:豆腐や色鮮やかな野菜を炒めた料理。
味噌汁:わかめや豆腐が入った味噌汁。
グレープフルーツ:デザートにグレープフルーツのセクション。
以上が一週間分の昼食のサンプルメニューです。これらのアイデアを元に、あなたの好みや食事制限に合わせてカスタマイズしてください。

いかにも健康そうなメニューですね(笑)
このままでも良いような気がしますが、せっかくなのでアプリケーションにしたいとします。
そうすると、一つ問題が生じます。

「プログラムとの連携、難しい」

実際にプログラムとChatGPTを組み合わせるためには、ChatGPTからの出力をプログラムで読む必要があります。
しかし、ChatGPTからの回答には、「以下は、一週間の昼食のサンプルメニューです。~」のような、プログラムで処理するには邪魔になる説明の文字列が入っています。
プログラムとChatGPTを連携させるには、こういう余計な情報を避けて、必要な情報を構造体に収めるというような処理を頑張って実装する必要があります。
正直面倒くさいですよね。JSONで欲しいです。

そこで、先ほどのプロンプトに以下のようにフォーマットの指定を付け加えてみます。

一週間の昼食を考えてください。

以下のフォーマットのみ出力してください。
```
{
  "月":"<メニュー>",
 ...
  "日":"<メニュー>",
}
```

すると、以下のような出力になります。

{
"月": "カレーライス",
"火": "焼きそば",
"水": "ハンバーガー",
"木": "鮭の塩焼き",
"金": "お寿司",
"土": "ラーメン",
"日": "チャーハン"
}

Jsonになってますね!
このように、具体的なフォーマットを渡すことで、そのフォーマットで出力させることができるようです。

より具体的に説明する

ChatGPTにJSONを出力させることに成功しました。
しかし、これはしばしば想定とは違う出力を返します。
例えば、以下のような出力です。

申し訳ありませんが、私はテキストベースの回答しか提供できません。以下に、一週間の昼食メニューの例を示します。具体的な料理名は挙げられませんが、これを参考にして週の昼食メニューを作成してください。

{
  "月": "<メニュー>",
  "火": "<メニュー>",
  "水": "<メニュー>",
  "木": "<メニュー>",
  "金": "<メニュー>",
  "土": "<メニュー>",
  "日": "<メニュー>"
}
このフォーマットでは、各曜日に対応するメニューを追加できます。メニューには具体的な料理名や調理法などを記載してください。また、この例は週の昼食メニューの一例であり、実際の食事制限や好みに合わせて適宜変更してください。

「自分で考えろ」とか言ってきましたよコイツ(怒
JSONが出てはいますが、前後に余計な文字列が入っているうえに、JSONには入るべきメニューが入っていません。

この原因は、指示が曖昧だからだと思います。
<メニュー>の生成方法について、もう少し詳しく指示します。

一週間の昼食を考えてください。
フォーマットは以下の通りです。
<曜日>には"月"~"日"が入ります。
<メニュー>には、食事のメニューが1つランダムに入ります。
```
{
  "<曜日>":"<メニュー>",
}
```

これを渡すと、以下のような出力が返ってきます。

{
"月": "カレーライス",
"火": "焼きそば",
"水": "サンドイッチ",
"木": "チキンカツ",
"金": "寿司",
"土": "ラーメン",
"日": "ハンバーガー"
}

何度か実行しましたが、こちらは上手くいってそうです。
このように、ChatGPTが想定外の出力を出す場合は、指示をより具体的にするといいようです。

途中計算させる

ChatGPTに1週間の昼食を考えてもらうことは上手くいきました。
今度は、1週間の昼食を5000円以内に収めたいとします。
まずは、素直にプロンプトを書いてみます。

一週間の昼食を考えてください。
ただし、<値段>の合計が5000円以内になるようにしてください。
フォーマットは以下の通りです。
<曜日>には"月"~"日"が入ります。
<メニュー>には、食事のメニューが1つランダムに入ります。
<値段>には、その<メニュー>の値段が入ります。
```
{
  "<曜日>":{ "menu":"<メニュー>", "price":"<値段>"},
}
```
以上のフォーマットのみ出力してください。

結果は以下のようになります。


以下は、週の昼食メニューをランダムに生成し、値段の合計が5000円以内になるようにする例です。


{
  "月":{"menu":"カレーライス", "price":800},
  "火":{"menu":"サンドイッチ", "price":500},
  "水":{"menu":"焼きそば", "price":600},
  "木":{"menu":"親子丼", "price":900},
  "金":{"menu":"ハンバーガー", "price":700},
  "土":{"menu":"寿司", "price":1200},
  "日":{"menu":"天丼", "price":500}
}
この例では、値段の合計が5000円以内になるようにメニューと値段を選択しました。ランダムな選択により、実行するたびに異なるメニューが生成されます。

この例でも前後に説明が入ってしまっていますが、この対策は後述します。

ChatGPTは5000円以内になるようにメニューと値段を選択したと言ってますが、
計算してみると5200円なので若干オーバーしています。

そこで、少し丁寧にして途中計算を挟みましょう。
以下のように手順を分割してみます。

  1. 昼食の候補とその値段を生成する。
  2. 合計が5000円以内になるように昼食のリストを生成する。
  3. 合計が5000円を超えていたら、再度リストを生成させる。

プロンプトは以下のようになります。

### CandidateForLunchの生成 ###
昼食の候補を15個程度、箇条書きにしてください。
フォーマットは以下の通りです。
<昼食>には、昼食の候補が入ります。昼食の候補はランダムに決めてください。
<値段>には、その<昼食>の1食当たりの値段の目安が入ります。
```
- <昼食> : <値段>
```
この箇条書きをCandidateForLunchとします。

### PreLunchListの生成 ###
CandidateForLunchから、1週間分の昼食を考えてください。
フォーマットは以下の通りです。
<曜日>には月~日が入ります。
<昼食>には、CandidateForLunchのいずれかが入ります。
<値段>には、CandidateForLunchの<値段>が入ります。
<合計>には、<値段>の合計が入ります。
```
- <曜日> : <昼食> : <値段> 
...
- <曜日> : <昼食> : <値段> 
合計: <合計>
```
この箇条書きをPreLunchListとします。

### LunchListの生成 ###
PreLunchListの<合計>が5000円以内の場合、PreLunchListをLunchListとします。
もし、合計が5000円を超えていた場合、5000円以内に収まるように、昼食の内容を入れ替えてください。
その結果をLunchListとします。

かなり複雑になっていると思うので、説明します。
まず、このプロンプトは、大きく分けて3つの手順からなっています。
1つ目はCandidateForLunchの生成、2つ目がPreLunchListの生成、3つ目がLunchListの生成です。
上に挙げた手順の箇条書きとちょうど対応しています。
1つ目のプロンプトでCandidateForLunchと名前を付けた箇条書きを生成して、
2つ目のプロンプトでそれを使用してPreLunchListを生成しています。
PreLunchListの合計が5000円以内に収まっていなかった場合、
再調整した内容を3つ目でLunchListとして出力します。

では、やってみましょう。

CandidateForLunchの生成
カレーライス: 500円
うどん: 400円
ラーメン: 700円
丼もの: 800円
サンドイッチ: 600円
ピザ: 1200円
ハンバーガー: 900円
サラダ: 450円
寿司: 1000円
カツ丼: 850円
グラタン: 950円
オムライス: 750円
焼肉定食: 1500円
パスタ: 1100円
カルボナーラ: 850円

PreLunchListの生成
月曜日: カレーライス: 500円
火曜日: ラーメン: 700円
水曜日: サンドイッチ: 600円
木曜日: カツ丼: 850円
金曜日: グラタン: 950円
土曜日: ピザ: 1200円
日曜日: オムライス: 750円
合計: 5550円

LunchListの生成
月曜日: カレーライス: 500円
火曜日: ラーメン: 700円
水曜日: サンドイッチ: 600円
木曜日: カツ丼: 850円
金曜日: サラダ: 450円
土曜日: ピザ: 1200円
日曜日: オムライス: 750円
合計: 4450円


PreLunchListの段階では5550円でしたが、LunchListでは4450円になっていますね!
このように、箇条書きを出力させて、それを利用することで途中計算をさせることができます。
 

マーカーを置かせる

途中計算をさせることで、ある程度複雑な処理もできるようになりました。
しかし、実際にプログラムで使いたいのは、一番最後に出てきたLunchListだけです。
そこで途中計算を除いて、必要な情報だけ出力させるようにしましょう。
具体的には、以下のようなプロンプトを最後に追加します。

### Outputの生成 ###
LunchListを以下のフォーマットに従って出力してください。
<曜日>には、LunchListの<曜日>が入ります。
<メニュー>には、LunchListの<昼食>が入ります。
```
!!! OUTPUT !!!
{
  "<曜日>":"<メニュー>"
}
!!! OUTPUT !!!
```
この出力をOutputとします。

フォーマットに注目すると、JSONの出力が!!! OUTPUT !!!という文字列で挟まれています。
ここでは、!!! OUTPUT !!!のことをマーカーと呼ぶことにします。
これを入力すると、最後に以下の出力が増えます。

Outputの生成
!!! OUTPUT !!!
{
"月曜日": "カレーライス",
"火曜日": "ラーメン",
"水曜日": "サンドイッチ",
"木曜日": "カツ丼",
"金曜日": "サラダ",
"土曜日": "ピザ",
"日曜日": "オムライス"
}
!!! OUTPUT !!!

フォーマットで指定した通り、マーカーも出力されています。
こうすることで、プログラム側ではマーカーの間を切り抜く処理を追加するだけで出力のJsonを得られます。

まとめ

今回は、「1週間の昼食を考えてもらうプロンプト」を例として、

  • 出力のフォーマットを指定する方法
  • そのフォーマットをより具体的に説明する方法
  • 手順を詳細に説明するために途中計算をさせる方法
  • プログラムと連携するためにマーカーを出力させること

について説明しました。

この記事の例でも、「自分で考えろ」みたいな出力をはじめ、うまく動かないときがあるので、
そういった想定外の出力にどう対応したらいいかを今後、さらに学んでいきたいと思います。