これは「関連記事表示 by Vector Search 2.0」のデモサイトです。記事コンテンツはソリューションブログから引用しています。
Craft Vector Searchを使ってセマンティック検索を実現する

Craft Vector Searchを使ってセマンティック検索を実現する

こんにちは、Customer Engineerのgamiです!最近『Monument Valley』という超現実的なパズルゲームをやって感動しました。

さて今回は、Craft Vector Searchに格納されたベクトルデータに対して検索を実行する方法を紹介します。

先日、『KARTE Datahub上のデータでCraft Vector Searchのベクトルデータを更新する』の記事で、Craft Vector Searchにデータを投入する方法をご紹介しました。今回はその続きとして、実際に投入したデータに対してセマンティック検索を実行する方法を説明します。

Craft Vector Searchは、ベクトル検索技術を活用した検索機能を提供するサービスです。通常のキーワード検索では検索ワードと文字が一致しないコンテンツは検索にヒットしないですが、Craft Vector Searchを使うことで「検索ワードと意味的に類似したコンテンツ」を検索できるようになります。こうしたセマンティック検索によって、ユーザーの曖昧な意図を汲み取った検索機能を実現できます。

たとえば次のようなユースケースで活用できます。

Craft Vector Searchの想定ユースケース例:

  • 商品のセマンティック検索: 大量にある商品データに対してあいまいなワードで検索
  • AIレコメンド: ユーザーにヒアリングした内容とニュアンスが近いアイテムをAIがレコメンド
  • 類似記事の特定: 大量にある記事コンテンツについて、各記事と内容が近い記事を特定

今回紹介するCraft Functionsテンプレートを使うことで、Craft Functionsのエンドポイントを介してCraft Vector Searchに対する検索処理を実行し検索結果を取得できるようになります。構築したセマンティック検索用エンドポイントは、AIエージェントなどが動くサーバーから利用することも、Webフロントエンドから呼び出して検索UIを作ることもできます。

アウトプットイメージ

前述した『KARTE Datahub上のデータでCraft Vector Searchのベクトルデータを更新する』の記事と同じく、今回の記事でもアパレル商品のデータをサンプルとして使っていきます。

アウトプットのイメージは次の通りです。

今回の記事では、Craft Vector Search検索用のAPIエンドポイントをCraft Functionsで作成します。検索したい文字列を指定してそのAPIエンドポイントに対するリクエストを送ると、次の画像の通り、渡されたテキストに対して意味的に近いアイテムID(datapointId)の一覧が返されます。

このセマンティック検索用エンドポイントを利用することで、たとえば次の画像のようにセマンティック検索を活用したWebサイト上での施策を実現できます。

利用の仕方は自由なので、たとえば 「AIとの対話を通じてアイテムがレコメンドされる」 といった体験をエンドユーザーに提供することも可能です。

なお、Craft Vector Search側に用意されたデータ次第では、次のような高度な検索を実現することも可能です。

実現可能な検索パターン:

  • カテゴリやブランドなどを指定して検索対象を フィルタリング する
  • 検索精度を上げるために、テキストベクトルに対する検索結果と 画像ベクトルに対する検索結果 を混ぜて結果表示する
  • 検索精度を上げるために、セマンティック検索による検索結果と キーワード検索による検索結果 を混ぜて結果表示する

今回紹介するファンクションの構成図は次の通りです。

作成したファンクションは、エンドポイント経由でWebブラウザなどから送信されたHTTPリクエストを受け取ります。その後、リクエストに含まれる検索ワードをCraft AI Modulesでベクトル化した上で、Craft Vector Searchに対して検索を実行します。その結果得られた検索結果一覧は、HTTPレスポンスとして返却されます。

なお、現状のCraft Vector Searchの仕様上、検索結果の一覧にはdatapoint_idと類似度スコアだけが返されます。datapoint_idに対応する情報(たとえば商品IDに対応する商品名や商品画像URLなど)は、KARTEアクションテーブルやCraft KVSなど別のデータベースから取得する必要があります。

注意点

前提として、次の点に注意をしてください。

  • Craft Functions、Craft Vector Search、Craft AI Modulesを利用できるKARTEプロジェクトが必要です

設定手順

設定の手順は次の通りです。

  1. Craft Vector Searchのインデックスにデータを投入する
  2. 検索実行用のCraft Functionsを作成する

順番に見てみましょう。

1. Craft Vector Searchのインデックスにデータを投入する

事前準備として、Craft Vector Searchに検索対象となるベクトルデータを投入する必要があります。

詳しい手順については、先日公開した次の記事をご覧ください。

検索結果の精度を確認するためにも、十分な件数のダミーデータを投入することをおすすめします。

インデックスの用意ができたら、Craft Vector SearchのエンドポイントIDをコピーしておいてください。

2. 検索実行用のCraft Functionsを作成する

Craft Vector Searchに対して検索を実行するCraft Functionsを作成します。このファンクションのエンドポイントが、Web APIとして外部から利用できるようになります。

作成手順は次の通りです。

  • [すべてのメニュー > Craft > ファンクション] からファンクション一覧画面を開きます

  • [新規作成 > テンプレートから作成] を選択します

  • Craft Vector Searchのベクトルデータを検索する 」というテンプレートを検索し[取得]ボタンをクリックします

  • [反映] ボタンをクリックします

  • [設定 > ファンクションのタイプ] で「HTTPタイプ」を選択します

  • [変数] タブで次の変数の値を設定します

    • ALLOWED_ORIGINS
      • WebページからこのAPIエンドポイントを利用する場合には、アクセスを許可するサイトのURL(オリジン)をカンマ区切りで指定します
        • 例: https://example.com,https://app.example.com
      • 指定したオリジンに対してCORSリクエストが許可されます
      • 本番運用中は推奨されませんが、すべてのオリジンを許可する( * )設定もできます
    • ENDPOINT_ID
      • データ投入時に使用したCraft Vector Searchインデックスの「エンドポイントID」を指定します
      • 「インデックスID」と「エンドポイントID」は別物なので注意してください
    • DIMENSION_NUM
      • データ投入時に使用したCraft Vector Searchのインデックスの次元数を指定します
      • 例: 1408 (マルチモーダルの場合) または 768 (テキストのみの場合)
    • PARTITION_NAME
      • データ投入時に設定したパーティション名のベースとなる名前を指定します
      • 例: items
    • その他の変数は、ひとまずデフォルト値のままで問題ありません
  • 適当なファンクション名をつけて [デプロイ] します

  • [設定 > ファンクションのタイプ > エンドポイント] にあるURLをコピーして控えておきます

なお、テンプレートのソースコードはGitHubで公開されています。

craft-codes/craft-functions/search-for-vectorsearch-data at main · plaidev/craft-codes

実際に動かしてみる

実際に検索機能を使ってみましょう。

作成したファンクションのエンドポイントURLに対して、curlコマンドやPostmanを使ってPOSTリクエストを送信します。

curlコマンドの例:

curl -X POST https://xxxxxxxxxx.cev2.karte.io/functions/A1234567890B1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "query": "高貴な服が欲しい"
  }'

検索が成功すると、次のような形式のレスポンスが返却されます。

{
  "query": "高貴な服が欲しい",
  "results": [
    {
      "distance": 0.75,
      "datapoint": {
        "datapointId": "ITEM001"
      }
    },
    {
      "distance": 0.68,
      "datapoint": {
        "datapointId": "ITEM005"
      }
    },
    {
      "distance": 0.62,
      "datapoint": {
        "datapointId": "ITEM012"
      }
    }
  ]
}

レスポンスフィールドの説明は次の通りです。

レスポンスフィールド:

  • query: 検索に使用されたクエリ文字列
  • results: 検索結果の配列(類似度が高い順)
    • distance: 類似度スコア(大きいほど類似度が高い)
    • datapoint.datapointId: 検索結果のアイテムID(データ投入時のID_FIELDで指定したフィールドの値)

返却されるdatapointIdが商品ID等に対応しているので、その値を使って元のデータソース(商品マスタなど)から詳細情報(商品名、商品画像URLなど)を取得することで、ユーザーに対して検索結果を表示することができます。

補足

本記事で紹介したファンクションのテンプレートは、様々な要件に対応できるように汎用的に作られています。詳細を補足します。

APIリクエストパラメータについて

今回のテンプレートで提供されるAPIのリクエストパラメータは、次の通りです。

パラメータ名必須/オプション説明デフォルト値
query必須string検索クエリ文字列です。ユーザーが入力した検索キーワードを指定します。-
filtersオプションarrayフィルタリング条件の配列です。データ投入時にNAMESPACE_FIELDSで指定したフィールドを使って検索結果を絞り込むことができます。各要素はnamespace(フィールド名)、allow_list(含めたい値のリスト)、deny_list(除外したい値のリスト)を含みます。-
imageOutputWeightオプションnumber (0〜1)画像検索結果の重みです。0の場合はテキスト検索結果のみ使用、0.3の場合はテキスト検索結果70%・画像検索結果30%で混合します。0
sparseRrfオプションnumber (0〜1)セマンティック検索とキーワード検索の配分比率です。1の場合はセマンティック検索のみ、0.5の場合は半々で混合します。BM25_MODEL_URLが設定されていない場合は無視されます。1

各パラメータの意味も含めて、補足説明を続けます。

フィルタリング検索

リクエストパラメータに filters を含めることで、データ投入時にNAMESPACE_FIELDSで指定したフィールドを使って検索結果を絞り込むことができます。

フィルタリングの例:

たとえば次の条件による絞り込みを実現したい場合を考えます。

  • カテゴリが「トップス」または「アウター」のアイテムのみを検索対象とする
  • ブランドが「BrandX」のアイテムを除外する

このときは、次のようなパラメータを指定します。

{
  "query": "おしゃれなアイテム",
  "filters": [
    {
      "namespace": "category",
      "allow_list": ["トップス", "アウター"]
    },
    {
      "namespace": "brand",
      "deny_list": ["BrandX"]
    }
  ]
}

マルチモーダル検索の活用

データ投入時にテキストと画像の両方のベクトルデータを格納している場合、検索時にもマルチモーダル検索を活用できます。

  • imageOutputWeightパラメータで、テキストと画像の検索結果の配分を調整できます
  • 例えばimageOutputWeight: 0.3とすると、テキスト検索結果70%、画像検索結果30%で混合されます
  • これにより、ユーザーの検索クエリに対して、テキスト的にも視覚的にも類似したアイテムを推薦できます

セマンティック検索とキーワード検索のハイブリッド検索

セマンティック検索は意味的な類似性を考慮した検索ができる一方で、完全一致のキーワード検索が必要な場合もあります。

今回のファンクションテンプレートでは、BM25_MODEL_URLにモデルを取得できるURL(例: https://example.com/bm25-model.json)を設定することで、セマンティック検索とキーワード検索を組み合わせたハイブリッド検索を実現できます。

  • sparseRrfパラメータで検索手法の配分を調整できます
    • 1.0: セマンティック検索のみ
    • 0.5: セマンティック検索とキーワード検索を半々で混合
    • 0.0: キーワード検索のみ
  • BM25モデルは、データ投入時と同じモデルを使用してください
  • ハイブリッド検索により、意味的な類似性とキーワード一致の両方を考慮した検索結果が得られます

ただしキーワード検索を実現する具体的な方法については、長くなるので別の記事にて紹介します。

Webフロントエンドからの利用

今回作成したファンクションは、Webフロントエンドから直接呼び出すことができます。

JavaScriptでの実装例:

async function searchItems(query, filters = []) {
  const response = await fetch('https://xxxxxxxxxx.cev2.karte.io/functions/A1234567890B1234567890', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query,
      filters,
      sparseRrf: 0.5,
      imageOutputWeight: 0.2,
    }),
  });

  const data = await response.json();
  return data.results;
}

// 使用例
const results = await searchItems('エレガントなトップス', [
  {
    namespace: 'category',
    allow_list: ['トップス']
  }
]);

// 結果を使ってUIを更新
results.forEach(result => {
  const itemId = result.datapoint.datapointId;
  const similarity = result.distance;
  // itemIdを使って商品マスタから詳細情報を取得し、表示
});

こうした実装をすることで、前掲したようなセマンティック検索を利用したWebサイト上の施策も実現できます。

もちろん、KARTEの接客サービス機能やKARTE Blocksで配信したJavaScriptからセマンティック検索を利用することも可能です。

なお、エンドポイントを利用するWebサイトのオリジンについては、CORSの設定のためにファンクションのALLOWED_ORIGINS変数に追加する必要があります。

おわりに

今回はCraft Vector Searchに格納されたベクトルデータに対して検索を実行する方法について紹介しました。

先日の記事でデータ投入方法を、今回の記事で検索実行方法を紹介したことで、Craft Vector Searchを使ったセマンティック検索機能の実装の全体像が見えたかと思います。

セマンティック検索は、従来のキーワード検索では実現できなかった「意味的な類似性」に基づく検索を可能にします。ECサイトでの商品レコメンデーション、コンテンツの関連記事表示など、様々なユースケースに活用できます。

ぜひみなさんもCraft Vector Searchを使ったセマンティック検索機能を実装してみてください!