ChatGPTを活用したAIで商品をさがす機能実現のための工夫

こんにちは、データサイエンティストの髙橋です。業務では企画/分析/機械学習モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。

この記事では 2023/11/01 に公開した AIでさがす機能の実現にあたり工夫した点について紹介します。本機能では OpenAI の Chat Completions API (Web 版の ChatGPTAPI 版のようなもの)を活用しているため、同 API 関連での工夫が中心となります。

この記事は Enigmo Advent Calendar 2023 の 9 日目の記事です。

AIでさがす機能の紹介

AIでさがす機能は BUYMA の Web サイト・アプリの検索機能の 1 つとしてボタンを追加しており、Web サイトの場合は以下のボタンを押下すると AIでさがす画面に遷移します。

ユーザーは事例から質問するか、アイテムのカテゴリを選択した上でフリーワードで質問するかのどちらかからチャットを始めることができます。事例は、アイテムのカテゴリとフリーワードの文章をあらかじめ弊社で作成したものです。
例えば、レディースアイテムカテゴリを選択した上で「30代におすすめのハイブランドレディースバッグ教えてください」と入力してメッセージを送信すると、以下のようにいくつかの商品が表示されます。

上記画面において、商品画像を押下すると該当の商品詳細画面が開き、「もっと見る」を押下すると表示されているおすすめ(シャネル チェーンバッグ など)を検索キーワードとした検索画面が開きます。

さらに、表示された商品群の末尾に以下のように「選んだ理由を聞く」ボタンが表示されており、押下すると選んだ理由が表示されます。

理由も表示できるようにすることでユーザーに納得感を得てもらったり、理由からこういう商品も良いかもと思い検索してもらったりすることを狙っています、また、弊社では FISS Project というファッションのプロが一緒に服を選んでくれるサービスも運営しており、より要望に特化した商品を選んでほしいと思われた方々の同サービスへの流入増加も狙いの1つです。そのため、以下のように理由表示後に同サービスの紹介も表示されます(カテゴリとしてメンズアイテムを選択した場合のみ表示されます)。

素早い機能公開とコスト削減の工夫にフォーカスした理由

目次を見ていただくと分かるように素早い機能公開とコスト削減に関する工夫が中心です。この2つにフォーカスした理由は以下のとおりです。

  • AIでさがす機能は他社の類似事例が少なく、どの程度の効果が見込めるか予測が難しかった
  • このような機能はどういったものであれば BUYMA の会員にとって有益であるのか予測が難しかった

そこで、可能な限り低コストで素早く公開し 分析→改善 のサイクルを回すことにより、どのようなものが有益か、どの程度効果が出そうなのかを予測しやすくしようと考えました。

結果として、本格的に開発を開始してから約6ヶ月で公開することが出来ました。なお、この期間にはソフトウェアエンジニアの方によるチャット画面周りのフロント/バッグエンド開発も含んでいます。 また、具体的な数値は出せませんがコストもかなり抑えた状態で公開できました。

素早い機能公開のため工夫

まずは素早い機能公開のための工夫を3つ紹介します。

おすすめ商品以外の質問には回答できなくする

企画の初期段階では商品のおすすめ以外の質問にもある程度対応できるようなチャットボットにすることを考えていました。例えば、BUYMA 利用時の QA でよくある「関税がかかるのか?」という質問に回答できるようにするなどです。しかし、それを実現しようとすると誤った情報を回答させないために BUYMA 公式の QA ガイドを embedding 化し、ユーザーの質問と類似した文書を抽出、その文書をプロンプトに組み込み回答させる、などが必要だろうと考えました。しかし、この作業自体に時間がかかる上に様々な質問に対して誤った情報を回答しないかを検証する時間もかかるため、思い切っておすすめの商品以外の質問には回答できないようにしました。

これを実現するために、ユーザーからのメッセージに対して Chat Completions API により回答を生成させる前に、メッセージがおすすめの商品に関するものかどうかを分類しそれ以外の質問の場合は以下のように固定したメッセージを返すようにしました。

なお、分類自体も Chat Completions API により行うこととしました。これは分類モデルを作成する手間を削減できると考え検証したところ、Chat Completions API による分類でも十分な精度が得られたためです。そのため、ユーザーのメッセージ 1 つに対して最大で 2 回 Chat Completions API を呼び出しています。

RAG は行わずシンプルに検索結果を返す仕組みとする

RAG (Retrieval-augmented Generation) とは、外部データを ChatGPT などの大規模言語モデルに与えることでそのデータに基づいた回答を生成させる手法です。

記事執筆時点(2023/12/09)で採用している方法はシンプルで、 ChatGPT におすすめ商品をいくつか生成してもらい、それぞれを検索キーワードとした検索結果内の複数個の商品 URL と検索結果の URL を表示するというものです。

企画初期段階では RAG のようなやり方(例えば商品情報を embedding 化し、回答の embedding と類似する商品を取得して画面に表示するなど)を検証していたのですが、以下の課題から RAG の方向性を突き詰めると時間がかかりそうなことを実感し、シンプルな方法を採用することとしました。embedding 化には OpenAI の text-embedding-ada-002 を利用しました。

  • 商品のメタデータ(商品名やカテゴリ、ブランド名など)を embedding 化したものと、ChatGPT の回答を embedding 化したものの類似度を取って上位の商品を見ると、回答とずれた商品がそれなりに出現する。
    • 例えば、ChatGPT の回答では「オックスフォードシャツ」と記載があるが、類似度上位にはTシャツが出現することがありました。
  • 上記のような問題があった場合に原因を特定することが難しい。
    • 商品名やカテゴリ、ブランド名などの複数の商品メタデータを embedding 化しているため、どのメタデータが原因なのか、特定のメタデータの組み合わせが原因なのかなどを特定することが難しく、また特定できたとしても今度は別のパターンで問題が発生することがありました。これを対応していると、この部分の開発のみでそれなりの時間を要してしまうと考えました。

また、仮に embedding 化がうまくいったとしても、それをシステム化することにも時間がかかるだろうと考えました。具体的には、 BUYMA には 600 万品以上の商品があり、この数の embedding と類似度を高速に計算するには Vertex Matching Engine などの利用が必要であり、弊社ではまだ利用実績もなかったため検証・構築に時間を要すると考えました。その上、コストもそれなりにかかりそうであったためシンプルな方法を採用するに至りました。

なお、記事執筆時点(2023/12/09)では RAG を利用した回答生成を高速に実現できそうな機能が OpenAI の Assistants APIGoogle Cloud の Grounding for PaLM API としてアナウンスされており、今後はユーザーの利用状況を見ながらこれらの活用も検討予定です。

API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする

これは弊社のソフトウェアエンジニアとの連携にかかる時間を削減するための取り組みです。Chat Completions API では過去のユーザーと AI のメッセージのやり取りを API の呼び出し側で組み立てる仕様になっています。具体的には、Open AI のドキュメントに記載のように API 呼び出し時の messages パラメータに過去のユーザーと AI のやり取りを渡す必要があります。

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Who won the world series in 2020?"
      },
      {
        "role": "assistant",
        "content": "The Los Angeles Dodgers won the World Series in 2020."
      },
      {
        "role": "user",
        "content": "Where was it played?"
      }
    ]
  }'

これを実現するための単純な方法は、アプリ側で画面に表示しているユーザーと AI のメッセージを取得し、 API 呼び出し時に渡すようソフトウェアエンジニアの方に依頼することです。しかし、そうすると過去のメッセージ群の一部を意図的に変更したい場合に、こちらの意図しないメッセージ群が渡されるるなどしてソフトウェアエンジニアの方との連携に時間がかかってしまうだろうと想定しました。
そこで、Chat Completions API をラップする独自 API を作成し、その API のレスポンスとして次の呼び出し時に渡すべきメッセージ群を返す仕様としました。具体的には、先程の Open AI のドキュメントの例を用いると Who won the world series in 2020? というメッセージで呼び出したときのレスポンスに、以下のように次の呼び出し時に渡すべきメッセージ群を含め、

{
    "past_messages": [
        {
            "role": "user",
            "content": "Who won the world series in 2020?"
        },
        {
            "role": "assistant",
            "content": "The Los Angeles Dodgers won the World Series in 2020."
        }
    ]
}

次の呼び出し時は以下のようにユーザーのメッセージと過去のメッセージ群をパラメータとして渡す仕様の独自 API を作成しました。

{
    "message": "Where was it played?",
    "past_messages": [
        {
            "role": "user",
            "content": "Who won the world series in 2020?"
        },
        {
            "role": "assistant",
            "content": "The Los Angeles Dodgers won the World Series in 2020."
        }
    ]
}

この独自 API を作成したことでこちらの意図しないメッセージ群が渡されてしまうことを防ぐことが出来ました。また、後述しますが過去のメッセージ群の一部を意図的に変更することを行っており、それを独自 API のレイヤーで行うことでソフトウェアエンジニアの方に意図的な変更の実装を依頼しこちらで確認するなどの連携が不要となり、開発期間の短縮に繋がりました。

コスト削減のための工夫

次にコスト削減のための工夫を 2 つ紹介します。ここで紹介するものは 1 回の Chat Completions API 呼び出し単位で見れば小さいものですが、BUYMA の会員数は 1000 万人以上であり 1 回の API 呼び出しが全体のコストに与える影響は大きいものとなります。どの程度効果が出るのか予測が難しく、また企画当初から全会員への公開を目指していたため少しのコスト削減でも検討することとしました。

一部のメッセージ群を API 呼び出し時は除外する

Chat Completions API の料金は input tokens による従量課金性であり、選んだ理由の回答が長文になりがちであるためそれに関するメッセージ群を API 呼び出し時は除外するようにしました。

例えば、ユーザーが「30代におすすめのハイブランドレディースバッグ教えてください」というメッセージを送り、「シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ」というメッセージが返された後に、「選んだ理由を教えて」とメッセージを送ると以下のようなメッセージが返されます。


シャネル チェーンバッグ
シャネルのチェーンバッグは、エレガントで上品なデザインが特徴です。高級感溢れる素材とクラシックなチェーンストラップが、大人の女性にふさわしい印象を与えます。また、シャネルのバッグはブランドの人気アイテムであり、長く愛用することができるため、コストパフォーマンスも高いです。

グッチ トートバッグ
グッチのトートバッグは、洗練されたデザインと高品質な素材が魅力です。ブランドのアイコンであるGG柄やロゴプリントが施されたデザインは、一目でグッチと分かる存在感を持っています。また、トートバッグは容量も大きく使い勝手が良いため、日常使いから旅行まで幅広く活躍します。

プラダ ショルダーバッグ
プラダのショルダーバッグは、モダンで洗練されたデザインが特徴です。シンプルながらも上品さを兼ね備えたデザインは、幅広いスタイルに合わせやすく、長く愛用することができます。また、プラダのバッグは高品質な素材と丁寧な作りが評価されており、耐久性も抜群です。


このメッセージの token 数は約 448 であり、以降の Chat Completions API 呼び出し毎に約 $0.00067 のコスト *1 がかかることとなります。先程述べたようにBUYMAの会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00067 のコスト増加でも大きな影響となります。そこで、過去の選んだ理由に関するメッセージ群を API 呼び出し時に除外することとしました。

具体例として、ユーザーが以下の順番でメッセージを送ったとし

  • 30代におすすめのハイブランドレディースバッグ教えてください
  • 選んだ理由を教えて
  • 小旅行で使えるおすすめレディースバッグは?

この時のメッセージ群が以下のようになったとします。

{
    "messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
        {
            "role": "user",
            "content": "選んだ理由を教えて"
        },
        {
            "role": "assistant",
            "content": "シャネルのチェーンバッグは、... / グッチのトートバッグは、... / プラダのショルダーバッグは、..."
        },
        {
            "role": "user",
            "content": "小旅行で使えるおすすめレディースバッグは?"
        },
        {
            "role": "assistant",
            "content": "トートバッグ / ショルダーバッグ / リュックサック"
        }
    ]
}'

この時、次の API 呼び出し時に渡す過去のメッセージ群を以下のように理由に関するやり取りを除外したものとしました。

{
    "messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
        {
            "role": "user",
            "content": "小旅行で使えるおすすめレディースバッグは?"
        },
        {
            "role": "assistant",
            "content": "トートバッグ / ショルダーバッグ / リュックサック"
        }
    ]
}

なお、上記の除外処理はアプリ側でも可能ですが、「API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする」にも記載したようにデータサイエンティストとソフトウェアエンジニア間の連携にかかる時間を削減するために、独自 API のレイヤーで行う仕様としました。具体的には、「選んだ理由を教えて」というメッセージで独自 API を呼び出した時のレスポンスは、以下のように理由に関するメッセージ群を除外したものになる仕様としました。

{
    "past_messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
    ]
}

そして、次に「小旅行で使えるおすすめレディースバッグは?」というメッセージで独自 API を呼び出す時は、上記の理由に関するメッセージ群が除外された過去のメッセージ群をそのまま渡す仕様としました。

{
    "message": "小旅行で使えるおすすめレディースバッグは?",
    "past_messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
    ]
}

プロンプトに質問と回答の例を含めないこととする

この機能ではユーザーからのメッセージに対して弊社システム側でプロンプトを追加し、Chat Completions API にリクエストを送信しています。そのプロンプトについて、開発初期は以下のようにユーザからのメッセージと AI からのメッセージの例をいくつか含めることでこちらの意図したフォーマットで回答を生成させ、複数のおすすめを分割していました(以下のプロンプトは実際に利用しているものとは異なります)。

「おすすめ1 / おすすめ2 / おすすめ3」というフォーマットで回答してください。
以下が質問と回答の例です。
質問:30代におすすめのハイブランドレディースバッグ教えてください
回答:シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ

質問:小旅行で使えるおすすめレディースバッグは?
回答:トートバッグ / ショルダーバッグ / リュックサック

しかし、ある程度開発が進んだ時点でコストを見積もったところ、予想以上にコストがかかる結果となりました。そこで、上記のプロンプトの 以下が質問と回答の例です。 以降を除外することを検討しました。

以下が質問と回答の例です。 以降の token 数は約 140 であり、 Chat Completions API 呼び出し毎に約 $0.00021 のコストがかかることとなります。先程述べた通り BUYMA の会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00021 のコスト削減でも見積もり上のコスト削減は大きなものでした。

一方で、実際に上記のプロンプトの 以下が質問と回答の例です。 以降を除外し、様々なユーザーのメッセージパターンで検証してみたところ、おすすめされるものにはほとんど変化がないもののフォーマットにブレが生じ、おすすめがうまく分割出来ないことがありました(半角の / が全角の になるなど)。 そこで、うまく抽出できなかったパターンに対し正規表現などの後処理を利用して対応しました。

結果として、公開から1ヶ月程度が経過した時点でフォーマットのブレによりおすすめが抽出出来ない問題は1件も発生しておらず、コストを削減しつつ安定して回答を生成することが出来ています。

まとめ

先日公開した AIでさがす機能実現のための工夫を紹介しました。特に素早い機能公開とコスト削減にフォーカスすることで安価に早く公開することが出来ました。今後は、利用状況を分析し RAG などにより BUYMA データとの連携を強化したり、Fine-tuning などによりさらにユーザーにとって有益な回答を生成させたりなどを検討予定です。


株式会社エニグモ すべての求人一覧

hrmos.co

*1:当機能の作成期間(2023/05 ~ 2023/11)では gpt-3.5-turbo の 1K tokens あたりの料金は $0.0015 であったためその数値を用いて計算しました。