一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

プロンプトエンジニアリングをしよう - 一休.comでの検索システム改善事例

はじめに

こんにちは。宿泊プロダクト開発部の宮崎です。

みなさん、生成 AI 使ってますか?

近年、AI の進歩はめざましく、文章生成や画像生成はもちろん、動画生成も実用的なレベルで出来るようになっています。

ChatGPT が話題になったのが 2022 年の 11 月なので、たった 2 年足らずでここまで来ているという事実に少し恐ろしくもありますね。AGI(汎用人工知能)の実現もそう遠くないのかもしれません。

一休でも AI 技術は注目していて今年の 6 月に、まさに生成 AI を使ってホテル検索システムの改善を行いました。

この記事では、その時に学んだプロンプトエンジニアリングの重要性について書いていこうと思います。

生成 AI を使ったホテル検索システム

今回我々が実装したのはフリーワード・文章でもホテルを検索できるシステムです。

以下のようなユーザーの自由な入力に対して、適切なホテル・旅館を返したいというのが目的でした。

  • 福井でおいしいご飯が食べられる宿
  • 沖縄 子供と楽しめる宿
  • 静岡で犬と泊まれる宿

今までは、このような入力の場合、形態素解析をして検索を行っていました。しかしその場合、「子供と楽しめる」だと「子供」「楽しめる」に分割して検索するので、本来の意味「子供と楽しめる」とは少し結果が違っていました。

よって文章の入力に対しては意味を考慮して検索ワードを切り出す必要があります。

私たちが行ったのは、AI にユーザーの入力を解析させ意味毎に区切り、検索条件 area, condition に分解させるというものです。以下に具体的な例を示します。

例:
ユーザーの入力:「銀座の夜景が楽しめる」

↓ これを AI で解析して分解する。

求める出力: { area:銀座, condition:[夜景が人気] }

従来は「銀座」「夜景」「楽しめる」で分解されていたのが、「銀座」「夜景が人気」に分解する。 このようにして json 形式で返してくれれば、既存のシステムで検索できます。この分解を生成 AI にやってもらおうと考えました。

実際の検索結果

具体的にどんな検索になったのか、実際にリリースしたものを使ってみましょう。

まず、行きたい施設について文章を入力し、検索ボタンを押す。

今回は「東京近郊でサウナがあって禁煙」と入力してみました。

以下の結果が表示されます。 東京近郊の都市で、サウナが人気、そして禁煙で検索されていますね。 このように、ユーザーの入力ワードをシステムで検索できるよう AI で分解するというのが今回行った改善です。

実装における課題

ただ、この仕組みを実装する上で、1 つ大きな課題を抱えていました。

それは単純な話で、コストの問題です。

当時、私たちはこの分解に GPT-4 を使っていました。 理由は他の AI と比較して GPT-4 が一番結果が良かったからです。(このプロジェクトは去年の秋ごろに始まったため、選択肢があまりなかったというのもあります。)

しかし、GPT-4 だとコストが高く、1 回の検索に 10 円以上かかることが分かりました。

どうしてそれほどコストがかかるのかというと、プロンプトが長いからです。

一休では、例えば近畿というエリアは「近畿(大阪以外)」と「大阪」という風に分けられています。 このような一休独自のエリアの分け方や、用語を AI が解釈できるようにプロンプトに詰め込んでいるので、どうしてもその分プロンプトが長くなってしまうのです。

プロンプト例:

扱うエリア名は以下です。
- 北海道
  - 札幌
  - 函館・湯の川・大沼
  - 旭川・富良野・稚内
  - ...
  - ...

<<以下、地名がずらりと後に続く…>>

解決案

GPT-4 ではコストがかかる。プロンプトを短くするのも難しい。 そこで考えたのは、GPT-3.5 Turbo を使うことでした。 これを使えば GPT-4 の 1/60 の値段になるので、コストの問題は簡単に解決できます。

ただし、これが最初からできていれば苦労はしません。

例えば「大阪でサウナと温泉が楽しめる」なら、GPT-4 は

{ area:大阪, condition:[サウナ,温泉] }

と出力されるのに対して、GPT-3.5 Turbo は以下の様に間違うことが多かったです。

  1. いくつかのキーワードが欠けている

    例:{area: 大阪,condition: [サウナ] }

  2. 入力がそのまま入ってしまっている

    例:{area: 大阪,condition: [サウナと温泉が楽しめる] }

こういった間違いに対して、最初はファインチューニングで精度を上げようとしました。 ファインチューニングは既存のモデルに追加でデータを学習させて、微調整するというものです。

参考:https://platform.openai.com/docs/guides/fine-tuning

しかし、これも上手くいきませんでした。

例えば condition の解釈精度をチューニングすると area 解釈精度が悪化したり、逆もまた然りであちらをたてればこちらがたたず状態に…。

結果的にプロンプトを 2 つに分けて area 用と condition 用、のようにしてみましたが、精度の割にはコストが 2 倍に増えてしまって断念しました。

※補足:精度の検証には新しくデータセットを作成しました。入力キーワードと、どのように分解して欲しいかの回答のセットです。これらを用いて生成された回答の一致率を測定しています。

プロンプトエンジニアリングをしよう

頼みの綱のファインチューニングもうまくいかず、頭を悩ませていた時のことです。

私は OpenAI 社のファインチューニングのドキュメントを読んでいました。チューニング精度を上げるための秘訣が書かれていればと思ったのです。

するとその時、以下の一文が目に留まりました。

Fine-tuning OpenAI text generation models can make them better for specific applications, but it requires a careful investment of time and effort. We recommend first attempting to get good results with prompt engineering, prompt chaining (breaking complex tasks into multiple prompts), and function calling,

引用:Fine-tuning (https://platform.openai.com/docs/guides/fine-tuning/fine-tuning)

簡単に言うと、ファインチューニングの前にまずはプロンプトエンジニアリングやプロンプトチェイニング(タスクを複数に分割する)をしましょう、ということです。

私はこの時まで、プロンプトエンジニアリングを軽視していました。プロンプトの修正で出力が改善されれば苦労しないだろうと。

ですが、このドキュメントを見ながら自分の今のプロンプトを見返すと、推奨されている書き方をほとんどしていないことが分かりました。

よって、半信半疑でありながらも、とりあえずドキュメントに従ってプロンプトエンジニアリングを行いました。

すると驚くべきことに、GPT-3.5 Turbo でも GPT-4 と精度を出すことができたのです。

難しいことは何もしていません。ただ、プロンプトを修正しただけです。

それだけでコストが 1/60 になり、今年の 6 月にこの機能をリリースすることができました。 まず最初にやるべきはプロンプトエンジニアリングだったというわけです。

※補足:現在は GPT-3.5 Turbo ではなく、 GPT-4o mini を使っています。

プロンプトエンジニアリングについて

ここからは具体的にどういったプロンプトをどう修正したのかについて書きます。

OpenAI のプロンプトエンジニアリングのガイドラインを参考に以下の修正を行いました。
参考:https://platform.openai.com/docs/guides/prompt-engineering

はっきりと簡潔な指示を出す


簡潔な言葉で何をしてほしいかを指示しました。

★ 修正前のプロンプト

現在、日本のホテルを検索するシステムを開発しています。システムは以下の機能を持っています。

<<システムの説明 ※ここでは省略。実際は 50 行くらい使ってホテルや宿泊条件など、機能の詳細な説明をしていた。>>

ここで、以下のユーザーのホテル検索クエリに対して、上記の機能を使って絞り込むのが適切だと判断したのなら、その条件を教えてください。

★ 修正後のプロンプト

あなたは優秀なクエリ分析システムです。
ユーザーのホテル検索クエリが与えられますので、それを分析して、どんな条件で検索すればいいのかをjson形式で回答してください。回答には、三重引用符で提供されたデータのみを使って答えてください。

このヒントは以下に書かれていました。
https://platform.openai.com/docs/guides/prompt-engineering/tactic-include-details-in-your-query-to-get-more-relevant-answers

ステップバイステップで考えさせる


以前は 1 つの指示+複数のルールで指示していましたが、これをステップバイステップの指示に切り替えました。

★ 修正前のプロンプト

ここで、以下のユーザーのホテル検索クエリに対して、上記の機能を使って絞り込むのが適切だと判断したのなら、その条件を教えてください。
その際、以下のルールを守ってください。

1. JSON形式で教えてください。
2. たとえば、ユーザーのホテル検索クエリ「ペットと泊まれる 新潟」の場合、
  {area: ["新潟"], condition: ["ペット"]}
  というJSONを期待します。
3. ...
4. ...

★ 修正後のプロンプト

以下の思考フローを用いて、ステップバイステップで絞り込んでいくことを想定してください。
ステップ1:ユーザーのクエリを確認し、エリアに該当するものを抽出してください。
ステップ2:ステップ1で抽出したワードを出力のareaのキーに追加してください。
ステップ3:...
ステップ4:...

このヒントは以下に書かれていました。
https://platform.openai.com/docs/guides/prompt-engineering/tactic-specify-the-steps-required-to-complete-a-task

セクションを区別する


エリア名称や、用語一覧を渡しているのですが、ここを引用符などで区切りました。

★ 修正前のプロンプト

以下にシステムで扱える用語一覧を示します。
* サウナ
* 禁煙
* 喫煙
* 室内プール
* ...
* ...

★ 修正後のプロンプト

以下にシステムで扱える用語一覧を示します。
"""
* サウナ
* 禁煙
* 喫煙
* 室内プール
* ...
* ...
"""

このヒントは以下に書かれていました。
https://platform.openai.com/docs/guides/prompt-engineering/tactic-use-delimiters-to-clearly-indicate-distinct-parts-of-the-input

例を付ける


出力の例を付けました。上述したように、以前はルールを使って回答を制御していて、1 つか 2 つしか例を付けていませんでした。

それを以下の様に例を増やしました。

★ 修正後のプロンプト

以下は、出力のjsonの例です。

例1:「絶景と温泉」というユーザーのクエリの場合、以下の条件を出力してください。
{ condition : ["絶景","温泉"] }

例2:「浜松でうなぎ料理を楽しめる旅館」というユーザーのクエリの場合、以下の条件を出力してください。
{ area:["浜松"] condition: ["うなぎ料理"] }

例3:...

例4:...

このヒントは以下に書かれていました。
https://platform.openai.com/docs/guides/prompt-engineering/tactic-provide-examples

以上がメインとなるプロンプトの修正です。これらは本当にガイドラインに従って足りていないものを書き換えただけです。

それだけで約 35% の精度改善となったので、同じような悩みを抱えている方はぜひ試していただきたいです。

おわりに

今回、プロンプトエンジニアリングの効果を十分に理解することができました。

ただ、考えてみれば、人が人に仕事を任せる時は当然相手の立場やスキルを考えて指示を出します。 これをどうして AI でやらないのか。 AI も AI の理解しやすい命令の構造があり、それをプロンプトエンジニアリングで最適化する。

人の気持ちを考えるように AI の気持ちを考える。

いい仕事をするためには人も、 AI も関係なく、相手を思い遣る心が大切なのだと思いました。