LLM(大規模言語モデル)を使って商品検索用の属性フィルターを作った話
スイッチサイエンスではShopifyを使ってECサイトを運営しています。Shopifyの検索は便利ですが商品数が増え、検索語を含む商品が広くヒットしすぎて、目的の商品に絞り込みにくい場面が増えてきました。検索構文による絞り込みにも限界があります。
そこで、検索画面に「パーツを探す」「センサータイプ」のような絞り込み軸(以下、属性フィルター)を追加することにしました。ここでいう属性フィルターは検索画面に表示する絞り込みの軸で、属性は各商品に付与する値です。たとえば「パーツを探す」が属性フィルターで、その属性値として「電源・バッテリー」などを各商品に付与します。こうしたフィルター軸と属性値を設計し、各商品の属性値を LLM(大規模言語モデル)で判定する仕組みを組み込みました。分類軸は商品分類そのものよりも検索性改善を優先して設計しています。また継続運用できるようにECシステム開発チームと連携して差分更新の仕組みを組み込みました。
本記事では、課題設定、属性フィルターの設計、商品ごとの属性値の判定、運用への組み込みまで行った内容をまとめます。
Shopifyの検索だけではノイズが多い
Shopifyの検索は、検索語を含む商品を広く返します。これは関連商品を見つけるという点では有用ですが、目的の商品そのものに絞り込みたい場合にはノイズになりやすい挙動でもあります。
たとえば ACアダプタ で検索すると、ACアダプタ本体だけでなく、ACアダプタを含む商品や、ACアダプタを取り除いた商品などもまとめてヒットします。関連商品が見つかること自体は悪くありませんが、ACアダプタ本体だけを探したい場合には検索結果が広すぎます。
このようなケースでは、検索結果に対して パーツを探す -> 電源・バッテリー のような事実ベースのフィルターをかけられると、目的の商品に寄せやすくなります。
今回の目的は、検索をAIに置き換えることではなく、検索結果のノイズを減らすための補助線を用意することでした。
どんな属性フィルターを作ったか
以下は検索画面に表示する属性フィルターの軸です。各商品には、それぞれの軸に対して対応する属性値を付与します。
- パーツを探す
- コネクタ・接続方式
- エコシステム
- センサータイプ
- ディスプレイタイプ
- モーター・アクチュエータータイプ
- モータードライバタイプ
- 電源タイプ
- 通信・信号規格
- ワイヤレス規格
内部的には Category、Connection_System、Platform、Sensor_Type のような属性名で扱っていますが、画面上ではユーザーが意味を取りやすいフィルター名にしています。
商品分類ではなく検索改善
今回もっとも時間を使ったのは、「どんなフィルター軸を用意するか」と「各軸にどんな属性値を持たせるか」を決める部分でした。
属性フィルターの設計では、ユーザーが実際にどのような語で商品を探しているかを確認しました。その上で、検索時に絞り込み軸として使いやすいかどうかを基準に、フィルター軸とその属性値を決めていきました。
今回行ったのは百科事典のような商品分類ではなく、検索結果を整理し、目的の商品に到達しやすくするための設計です。
そのため、キーワード検索だけですぐ見つかるものは、あえて属性フィルターの軸としては出していません。逆に、キーワード検索ではノイズが増えやすいものを優先して属性化しました。
分類設計で難しかったこと
「パーツを探す」は境界が曖昧
「パーツを探す」は重要な属性フィルターですが、設計が難しい軸でもありました。
どの商品をどの属性値に割り当てるべきか迷うものが多く、属性値の名前に引っ張られて本質とは少し違う分類に入りやすい傾向がありました。たとえば商品名に キット と入っているだけで、なんでも 教材 に寄ってしまうようなことが起きます。
人間が見ても迷う境界は、当然LLMでも迷います。属性値の名前を見直したり、分離したり、正しい例(Few-shot)を追加したり試行錯誤を繰り返しました。
「センサータイプ」は細かく持つ方が扱いやすかった
「センサータイプ」も設計が難しい属性フィルターでした。種類が多く、しかも似たセンサーが多いためです。特に赤外線を使ったセンサーは分け方が難しく、どの粒度で属性値を切るかでかなり迷いました。
最初は表示上のわかりやすさを優先して大きめの分類に寄せようとしていましたが、後で表示側でまとめればよいと整理してからは、細かめに持つ方針にしました。その方が商品ごとの判定もしやすく、後からの再編もしやすくなります。
LLMの精度以前に「どの粒度で世界を切り分けるか」を決めることの方が重要でした。
プロンプト設計
最初は、商品の説明から判断材料を拾う処理と、設計済みの属性フィルターの属性値への当てはめを2段階に分ける構成も試しました。最終的には Few-shot の例を加えることでかなり安定したため、現在は1プロンプトで完結しています。
実際に問題になったのは、不明な属性値を大量に作ることや JSON が大きく崩れることではなく、表記ゆれや属性値の誤判定でした。たとえば ドットマトリクス が ドットマトリックス になったり、「通信・信号規格」に入るべき値が「ワイヤレス規格」に入ったり、その逆が起きたりします。
このあたりをプロンプトだけで抑え込もうとすると、指示が肥大化して保守しづらくなります。そこで、プロンプトは「商品ごとの属性値判定」に責務を絞り、正規化や軽微な修正はプログラム側で処理するようにしました。
この構成にすると、プロンプトを短く保ちつつ、後処理のロジックをコードとして管理できます。
初回全件判定の時間と費用
初回の全件判定では、販売中の全商品約7000点を対象にしました。1商品を1リクエストとして、50件並列で実行しました。
利用したのは Google Gen API です。全件判定にかかった時間は2〜3時間ほどで、費用は1000円ちょっとでした。ざっくり1リクエストあたり0.1円程度です。
このくらいのコスト感であれば、全件に対して一度実行し、結果を見ながら属性値や例(Few-shot)を見直す進め方が取りやすいです。実際には3〜4回ほど回しました。
運用への組み込み
商品ごとの属性判定は、一度やって終わりではありません。商品タイトルや説明文は更新されますし、新しい商品が日々追加されます。
運用システムへの組み込みでは、商品情報のハッシュ化と差分更新を行う構成にしました。商品タイトルと説明文をハッシュ化し、ハッシュが変わった商品だけを再判定対象にして、結果をShopify APIで更新する形です。
この継続運用の仕組みは、スイッチサイエンスのECシステム開発チームに実装してもらいました。自分は属性フィルターの設計と商品ごとの属性判定の設計を担当し、運用フローへの組み込みはチームで進めました。
まとめ
今回の取り組みでは、LLM 活用の成否はプロンプト設計やモデル選定だけでなく、属性フィルターの設計と責務分担に大きく左右されると分かりました。
分類軸は、商品をきれいに整理するためではなく、検索体験を改善するために決めた方がうまくいきました。プロンプトは万能ではないため、商品ごとの属性判定に集中させ、正規化や修正はコードで行う方が運用しやすくなります。さらに、本番運用を考えるなら差分更新の仕組みはほぼ必須です。
ECサイトの商品データ整備は地味ですが、検索体験にはかなり効きます。今回の取り組みによって、キーワード検索だけではノイズが多かった場面に対して、事実ベースのフィルターという補助線を引けるようになりました。
使ってみてください
今回追加したフィルターは、キーワード検索だけでは絞り込みにくい商品を探すときに役立つはずです。特に、対応プラットフォームや接続方式、センサー種別、電源まわりのように、商品名だけでは見つけにくい軸で探したいときに便利です。
検索画面のフィルターをぜひ試してみてください。使われ方を見ながら、分類軸や表示の仕方は引き続き改善していく予定です。