Cloud データベース、サーバーレス ランタイム、オープンソースの統合を使用したおもちゃ屋検索アプリ

1. 概要

バーチャルまたは実際におもちゃ屋に足を運び、ぴったりなギフトを簡単に見つけられるとしたらどうでしょう。欲しいものを説明したり、おもちゃの写真をアップロードしたり、自分でデザインしたりすると、ストアはすぐにニーズを理解し、カスタマイズされたエクスペリエンスを提供します。これは未来の空想ではなく、AI、クラウド テクノロジー、パーソナライズされた e コマースのビジョンを活用した現実です。

課題: 想像どおりの完璧な商品を見つけるのは難しい場合があります。一般的な検索語句、キーワード、ファジー検索では十分な結果が得られないことがよくあります。また、無限に続くページの閲覧は退屈になりがちで、期待していたものと実際に見つかったものとのギャップに不満を感じてしまうこともあります。

ソリューション: このデモ アプリケーションは、AI の力を活用してこの課題に正面から取り組み、コンテキスト検索と検索コンテキストに一致する商品のカスタム生成により、真にパーソナライズされたシームレスなエクスペリエンスを提供します。

作成するアプリの概要

このラボでは、次の作業を行います。

  1. AlloyDB インスタンスを作成して Toys データセットを読み込む
  2. AlloyDB で pgvector 拡張機能と生成 AI モデル拡張機能を有効にする
  3. 商品の説明からエンベディングを生成し、ユーザーの検索テキストに対してリアルタイムでコサイン類似度検索を実行する
  4. Gemini 2.0 Flash を呼び出して、ユーザーがアップロードした画像を記述し、コンテキストに基づくおもちゃの検索を行う
  5. Imagen 3 を呼び出して、ユーザーの関心に基づいておもちゃをカスタム作成する
  6. データベース向け生成 AI ツールボックスを使用して作成した価格予測ツールを呼び出し、カスタム作成されたおもちゃの価格の詳細を確認する
  7. サーバーレス Cloud Run Functions にソリューションをデプロイする

要件

  • ブラウザ(ChromeFirefox など)
  • 課金を有効にした Google Cloud プロジェクト

2. アーキテクチャ

データフローの詳細: データがシステム内をどのように移動するかを詳しく見てみましょう。

  1. AI を活用した RAG(検索拡張生成)によるコンテキスト検索

たとえば、「赤い車」を検索するのではなく、次のようなことを理解します。

「3 歳の男の子に適した小型車両」

基盤としての AlloyDB: Google Cloud の PostgreSQL 対応フルマネージド データベースである AlloyDB を使用して、説明、画像 URL、その他の関連属性などのおもちゃデータを保存します。

セマンティック検索用の pgvector: PostgreSQL 拡張機能の pgvector を使用すると、おもちゃの説明とユーザー検索クエリの両方のベクトル エンベディングを保存できます。これにより、セマンティック検索が可能になります。つまり、システムはキーワードだけでなく、単語の意味を理解します。

関連性のためのコサイン類似度: コサイン類似度を使用して、ユーザーの検索ベクトルとおもちゃの説明ベクトルの意味的類似度を測定し、最も関連性の高い結果を表示します。

高速かつ正確な結果を実現する ScaNN インデックス: おもちゃの在庫が増えるにつれて、迅速かつ正確な結果を実現するため、ScaNN(Scalable Nearest Neighbors)インデックスを統合しています。これにより、ベクトル検索の効率と再現率が大幅に向上します。

  1. Gemini 2.0 Flash による画像ベースの検索と理解

ユーザーが、検索に使用するおなじみのおもちゃの写真をアップロードしたいとします。ユーザーは、好きなおもちゃの画像をアップロードして、関連する特徴を取得できます。Google の Gemini 2.0 Flash モデル(LangChain4j を使用して呼び出されます)を活用して画像を分析し、おもちゃの色、素材、種類、対象年齢などの関連するコンテキストを抽出します。

  1. 生成 AI: Imagen 3 でカスタマイズした夢のおもちゃを構築する

ユーザーが独自のおもちゃを作成すると、本当の魔法が起こります。Imagen 3 を使用すると、簡単なテキスト プロンプトを使用して、夢のおもちゃを説明できます。「紫色の翼と優しい表情のぬいぐるみ ドラゴンが欲しい」と話しかけると、そのドラゴンが画面上で動き出すことを想像してみてください。Imagen 3 は、カスタム設計されたおもちゃの画像を生成し、ユーザーが作成したおもちゃを明確に可視化できるようにします。

  1. エージェントとデータベース向け生成 AI ツールボックスを活用した価格予測

カスタム設計されたおもちゃの製造費用を見積もる価格予測機能が実装されています。これは、高度な料金計算ツールを含むエージェントによって実現されています。

データベース向け生成 AI ツールボックス: このエージェントは、Google の新しいオープンソース ツールであるデータベース向け生成 AI ツールボックスを使用して、データベースとシームレスに統合されます。これにより、エージェントは材料費、製造プロセス、その他の関連する要素に関するリアルタイム データにアクセスして、正確な見積もりを提供できます。詳しくは、こちらをご覧ください。

  1. Java Spring Boot、Gemini Code Assist、Cloud Run による開発とサーバーレス デプロイの効率化

アプリケーション全体は、堅牢でスケーラブルなフレームワークである Java Spring Boot を使用して構築されています。開発プロセス全体で、特にフロントエンド開発で Gemini Code Assist を活用し、開発サイクルを大幅に短縮してコードの品質を向上させました。アプリケーション全体のデプロイには Cloud Run を使用し、データベースとエージェント機能を独立したエンドポイントとしてデプロイするために Cloud Run functions を使用しました。

3. 始める前に

プロジェクトを作成する

  1. Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。
  2. Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
  3. Cloud Shell(Google Cloud で動作するコマンドライン環境)を使用します。この環境には bq がプリロードされています。Google Cloud コンソールの上部にある [Cloud Shell をアクティブにする] をクリックします。

Cloud Shell を有効にするボタンの画像

  1. Cloud Shell に接続したら、次のコマンドを使用して、認証が完了していることと、プロジェクトがプロジェクト ID に設定されていることを確認します。
gcloud auth list
  1. Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
  1. プロジェクトが設定されていない場合は、次のコマンドを使用して設定します。
gcloud config set project <YOUR_PROJECT_ID>
  1. Cloud Shell ターミナルで次のコマンドを 1 つずつ実行して、必要な API を有効にします。

以下のコマンドを 1 つ実行することもできますが、トライアル アカウントをご利用の場合は、これらの機能を一括で有効にすると割り当ての問題が発生する可能性があります。そのため、コマンドは 1 行に 1 つずつ指定します。

gcloud services enable alloydb.googleapis.com
gcloud services enable compute.googleapis.com 
gcloud services enable cloudresourcemanager.googleapis.com 
gcloud services enable servicenetworking.googleapis.com 
gcloud services enable run.googleapis.com 
gcloud services enable cloudbuild.googleapis.com 
gcloud services enable cloudfunctions.googleapis.com 
gcloud services enable aiplatform.googleapis.com

gcloud コマンドの代わりに、コンソールで各プロダクトを検索するか、このリンクを使用します。

いずれかの API が不足している場合は、実装中にいつでも有効にできます。

gcloud コマンドとその使用方法については、ドキュメントをご覧ください。

4. データベースの設定

このラボでは、おもちゃ屋のデータを含むデータベースとして AlloyDB を使用します。クラスタを使用して、データベースやログなどのすべてのリソースを保持します。各クラスタには、データへのアクセス ポイントを提供するプライマリ インスタンスがあります。テーブルには実際のデータが保持されます。

e コマース データセットを読み込む AlloyDB クラスタ、インスタンス、テーブルを作成しましょう。

クラスタとインスタンスを作成する

  1. Cloud コンソールで AlloyDB ページに移動します。Cloud コンソールのほとんどのページは、コンソールの検索バーを使用して簡単に見つけることができます。
  2. そのページで [クラスタを作成] を選択します。

f76ff480c8c889aa.png

  1. 次のような画面が表示されます。次の値を使用してクラスタとインスタンスを作成します(リポジトリからアプリケーション コードをクローンする場合は、値が一致していることを確認してください)。
  • クラスタ ID: "vector-cluster"
  • password: "alloydb"
  • PostgreSQL 15 互換
  • リージョン: 「us-central1
  • Networking: 「default

538dba58908162fb.png

  1. デフォルトのネットワークを選択すると、次のような画面が表示されます。

[接続の設定] を選択します。
7939bbb6802a91bf.png

  1. [自動的に割り当てられた IP 範囲を使用する] を選択し、[続行] をクリックします。情報を確認したら、[CREATE CONNECTION] を選択します。768ff5210e79676f.png
  2. ネットワークが設定されたら、クラスタの作成を続行できます。[CREATE CLUSTER] をクリックして、クラスタの設定を完了します。

e06623e55195e16e.png

インスタンス ID を「

vector-instance"

クラスタの作成には 10 分ほどかかります。作成が完了すると、作成したクラスタの概要を示す画面が表示されます。

5. データの取り込み

次に、店舗に関するデータを含むテーブルを追加します。AlloyDB に移動し、プライマリ クラスタと AlloyDB Studio を選択します。

847e35f1bf8a8bd8.png

インスタンスの作成が完了するまで待つ必要があります。準備ができたら、クラスタの作成時に作成した認証情報を使用して AlloyDB にログインします。PostgreSQL の認証には、次のデータを使用します。

  • ユーザー名: 「postgres
  • データベース: 「postgres
  • パスワード: 「alloydb

AlloyDB Studio で認証が正常に完了すると、エディタに SQL コマンドが入力されます。最後のウィンドウの右側にあるプラス記号を使用して、複数のエディタ ウィンドウを追加できます。

91a86d9469d499c4.png

必要に応じて、[実行]、[フォーマット]、[クリア] の各オプションを使用して、エディタ ウィンドウに AlloyDB のコマンドを入力します。

拡張機能を有効にする

このアプリの作成では、拡張機能 pgvectorgoogle_ml_integration を使用します。pgvector 拡張機能を使用すると、ベクトル エンベディングを保存して検索できます。google_ml_integration 拡張機能には、Vertex AI 予測エンドポイントにアクセスして SQL で予測を取得するために使用する関数が用意されています。次の DDL を実行して、これらの拡張機能を有効にします。

CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;

データベースで有効になっている拡張機能を確認するには、次の SQL コマンドを実行します。

select extname, extversion from pg_extension;

テーブルを作成する

次の DDL ステートメントを使用してテーブルを作成します。

CREATE TABLE toys ( id VARCHAR(25), name VARCHAR(25), description VARCHAR(20000), quantity INT, price FLOAT, image_url VARCHAR(200), text_embeddings vector(768)) ;

上記のコマンドが正常に実行されると、データベース内のテーブルが表示されます。

データを取り込む

このラボでは、この SQL ファイルに約 72 件のレコードのテストデータがあります。id, name, description, quantity, price, image_url フィールドが含まれています。他のフィールドは、ラボの後半で入力します。

そこから行/挿入ステートメントをコピーし、空のエディタタブに貼り付けて、[実行] を選択します。

テーブルの内容を表示するには、[エクスプローラ] セクションを開き、apparels という名前のテーブルが表示されるまで展開します。省略記号(⋮)を選択すると、テーブルに対してクエリを実行するオプションが表示されます。新しいエディタタブで SELECT ステートメントが開きます。

cfaa52b717f9aaed.png

権限を付与

次のステートメントを実行して、embedding 関数の実行権限をユーザー postgres に付与します。

GRANT EXECUTE ON FUNCTION embedding TO postgres;

AlloyDB サービス アカウントに Vertex AI ユーザーロールを付与する

Cloud Shell ターミナルに移動し、次のコマンドを実行します。

PROJECT_ID=$(gcloud config get-value project)

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

6. コンテキストのエンベディングを作成する

コンピュータにとって、テキストを処理するよりも数値を処理するほうがはるかに簡単です。エンベディング システムは、テキストの表現方法や使用言語に関係なく、テキストをテキストを表す一連の浮動小数点数に変換します。

海辺の場所の説明について考えてみましょう。たとえば、「on the water」、「beachfront」、「walk from your room to the ocean」、「sur la mer」、「на берегу океана」などです。これらの用語はすべて見た目は異なりますが、意味的な意味や機械学習の用語では、埋め込みが非常に近いはずです。

データとコンテキストの準備ができたので、SQL を実行して商品説明のエンベディングをテーブルの embedding フィールドに追加します。使用できるエンベディング モデルはさまざまです。Vertex AI の text-embedding-005 を使用します。プロジェクト全体で同じエンベディング モデルを使用してください。

注: 以前に作成した既存の Google Cloud プロジェクトを使用している場合は、textembedding-gecko などの古いバージョンのテキスト エンベディング モデルを引き続き使用する必要があります。

[AlloyDB Studio] タブに戻り、次の DML を入力します。

UPDATE toys set text_embeddings = embedding( 'text-embedding-005', description);

toys テーブルをもう一度見て、エンベディングを確認します。変更を確認するには、SELECT ステートメントを再実行してください。

SELECT id, name, description, price, quantity, image_url, text_embeddings FROM toys;

次のように、おもちゃの説明のエンベディング ベクトルが返されます。これは浮動小数点の配列のように見えます。

7d32f7cd7204e1f3.png

注: 無料枠で新しく作成された Google Cloud プロジェクトでは、エンベディング モデルに対して 1 秒あたりに許可されるエンベディング リクエストの数に関して、割り当ての問題が発生する可能性があります。ID のフィルタクエリを使用して、エンベディングの生成時に 1 ~ 5 件のレコードを選択することをおすすめします。

7. ベクトル検索を実行する

テーブル、データ、エンベディングがすべて準備できたので、ユーザーの検索テキストのリアルタイム ベクトル検索を実行しましょう。

お客様から次のような質問を受けたとします。

I want a white plush teddy bear toy with a floral pattern

一致する値は、次のクエリを実行して見つけることができます。

select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

このクエリを詳しく見てみましょう。

このクエリでは、

  1. ユーザーの検索テキストは「I want a white plush teddy bear toy with a floral pattern.」です。
  2. モデル text-embedding-005 を使用して、embedding() メソッドでエンベディングに変換しています。このステップは、テーブル内のすべてのアイテムにエンベディング関数を適用した最後のステップとよく似ています。
  3. <=>」は、COSINE SIMILARITY 距離メソッドの使用を表します。使用可能なすべての類似性測定については、pgvector のドキュメントをご覧ください。
  4. エンベディング方法の結果をベクトル型に変換し、データベースに保存されているベクトルと互換性を持たせています。
  5. LIMIT 5 は、検索テキストの最近傍を 5 つ抽出することを表します。

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

fa7f0fc3a4c68804.png

検索結果を見ると、検索テキストにかなり近い一致が見つかります。テキストを変更して、結果がどのように変化するかを確認してみます。

注意事項:

ScaNN インデックスを使用して、このベクトル検索結果のパフォーマンス(クエリ時間)、効率、レコード数を増やしたいとします。インデックスありの場合とインデックスなしの場合の結果の違いを比較するには、こちらのブログの手順をご覧ください。

省略可: ScaNN インデックスを使用して効率と再現率を改善する

便宜上、インデックス作成手順を以下に示します。

  1. クラスタ、インスタンス、コンテキスト、エンベディングはすでに作成されているため、次のステートメントを使用して ScaNN 拡張機能をインストールするだけです。
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. 次に、インデックス(ScaNN)を作成します。
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

上記の DDL で、apparel_index はインデックスの名前です。

「toys」はテーブルです

「scann」はインデックス方法です。

「embedding」は、インデックスを付けるテーブルの列です。

「cosine」は、インデックスで使用する距離方法です。

「8」は、このインデックスに適用するパーティションの数です。1 ~ 1048576 の任意の値に設定します。この値を決定する方法の詳細については、ScaNN インデックスをチューニングするをご覧ください。

ScaNN リポジトリで推奨されているように、データポイント数の平方根を使用しました(パーティショニングする場合、num_leaves はデータポイント数の平方根に近い値にする必要があります)。

  1. 次のクエリを使用して、インデックスが作成されているかどうかを確認します。
SELECT * FROM pg_stat_ann_indexes;
  1. インデックスを使用しないときに使用したのと同じクエリを使用してベクトル検索を実行します。
select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

上記のクエリは、ラボの手順 8 で使用したものと同じです。これで、フィールドがインデックスに登録されました。

  1. インデックスありとインデックスなし(インデックスを削除)の両方で、単純な検索クエリを使用してテストします。

このユースケースには 72 件のレコードしかないため、インデックスは実際には機能しません。別のユースケースで実施したテストの結果は次のとおりです。

インデックスに登録されたエンベディング データに対して同じベクトル検索クエリを実行すると、高品質の検索結果と効率性が得られます。インデックスを使用すると、効率が大幅に向上します(実行時間: ScaNN なしの場合 10.37 ms、ScaNN ありの場合 0.87 ms)。このトピックについて詳しくは、こちらのブログをご覧ください。

8. LLM を使用した一致検証

次に進んで、アプリケーションに最適な一致を返すサービスを作成する前に、生成 AI モデルを使用して、これらの候補の回答が本当に関連性があり、ユーザーと共有しても安全かどうかを確認しましょう。

インスタンスが Gemini 用に設定されていることを確認する

まず、クラスタとインスタンスで Google ML 統合がすでに有効になっているかどうかを確認します。AlloyDB Studio で、次のコマンドを実行します。

show google_ml_integration.enable_model_support;

値が [オン] の場合は、次の 2 つの手順をスキップして、AlloyDB と Vertex AI モデルの統合の設定に直接進みます。

  1. AlloyDB クラスタのプライマリ インスタンスに移動し、[プライマリ インスタンスを編集] をクリックします。

cb76b934ba3735bd.png

  1. [詳細設定オプション] の [フラグ] セクションに移動します。次のように、google_ml_integration.enable_model_support flag が「on」に設定されていることを確認します。

6a59351fcd2a9d35.png

[オン] に設定されていない場合は、[オン] に設定して [インスタンスを更新] ボタンをクリックします。この処理には数分かかります。

AlloyDB と Vertex AI モデルの統合

これで、AlloyDB Studio に接続して次の DML ステートメントを実行し、AlloyDB から Gemini モデルへのアクセスを設定できます。必要に応じて、プロジェクト ID を使用してください。コマンドを実行する前に構文エラーの警告が表示されることがありますが、問題なく実行されます。

まず、以下に示すように Gemini 1.5 モデル接続を作成します。次のコマンドの $PROJECT_ID は、Google Cloud プロジェクト ID に置き換えてください。

CALL
 google_ml.create_model( model_id => 'gemini-1.5',
   model_request_url => 'https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/publishers/google/models/gemini-1.5-pro:streamGenerateContent',
   model_provider => 'google',
   model_auth_type => 'alloydb_service_agent_iam');

アクセス用に構成されたモデルは、AlloyDB Studio で次のコマンドを使用して確認できます。

select model_id,model_type from google_ml.model_info_view;        

最後に、データベース ユーザーに ml_predict_row 関数を実行して Google Vertex AI モデルを介して予測を実行する権限を付与する必要があります。次のコマンドを実行します。

GRANT EXECUTE ON FUNCTION ml_predict_row to postgres;

注: 既存の Google Cloud プロジェクトと、しばらく前に作成した AlloyDB の既存のクラスタまたはインスタンスを使用している場合、gemini-1.5 の今後の呼び出しで問題が発生する可能性があるため、gemini-1.5 モデルへの古い参照を削除し、上記の CALL ステートメントで再度作成し、関数 ml_predict_row で grant execute を再度実行する必要があります。

回答の評価

次のセクションでは、クエリからのレスポンスが妥当であることを確認するために、1 つの大きなクエリを使用しますが、このクエリは理解しにくい場合があります。各要素について確認し、どのように組み合わせるかを説明します。

  1. まず、データベースにリクエストを送信して、ユーザーのクエリに最も近い 10 件を取得します。
  2. レスポンスの有効性を判断するには、外部クエリを使用して、レスポンスを評価する方法を説明します。検索テキストである recommended_text フィールドと、内部テーブルの content(おもちゃの説明フィールド)をクエリの一部として使用します。
  3. これを基に、返された回答の「良さ」を審査します。
  4. predict_row は結果を JSON 形式で返します。コード「-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'"」は、その JSON から実際のテキストを抽出するために使用されます。返される実際の JSON を確認するには、このコードを削除します。
  5. 最後に、LLM レスポンスを得るために、REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') を使用してレスポンスを取り出します。
SELECT id,
       name,
       content,
       quantity,
       price,
       image_url,
       recommended_text,
       REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') AS gemini_validation
  FROM (SELECT id,
               name,
               content,
               quantity,
               price,
               image_url,
               recommended_text,
               CAST(ARRAY_AGG(LLM_RESPONSE) AS TEXT) AS gemini_validation
          FROM (SELECT id,
                       name,
                       content,
                       quantity,
                       price,
                       image_url,
                       recommended_text,
                       json_array_elements(google_ml.predict_row(model_id => 'gemini-1.5',
                                                                   request_body => CONCAT('{ "contents": [ { "role": "user", "parts": [ { "text": "User wants to buy a toy and this is the description of the toy they wish to buy: ',                                                                                              recommended_text,                                                                                              '. Check if the following product items from the inventory are close enough to really, contextually match the user description. Here are the items: ',                                                                                         content,                                                                                         '. Return a ONE-LINE response with 3 values: 1) MATCH: if the 2 contexts are reasonably matching in terms of any of the color or color family specified in the list, approximate style match with any of the styles mentioned in the user search text: This should be a simple YES or NO. Choose NO only if it is completely irrelevant to users search criteria. 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear one-line easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match. " } ] } ] }')::JSON)) -> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text' :: TEXT AS LLM_RESPONSE
                  FROM (SELECT id,
                               name,
                               description AS content,
                               quantity,
                               price,
                               image_url,
                               'Pink panther standing' AS recommended_text
                          FROM toys
                         ORDER BY text_embeddings <=> embedding('text-embedding-005',
                                                                'Pink panther standing')::VECTOR
                         LIMIT 10) AS xyz) AS X
         GROUP BY id,
                  name,
                  content,
                  quantity,
                  price,
                  image_url,
                  recommended_text) AS final_matches
 WHERE REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') LIKE '%MATCH%:%YES%';

それでもまだ難しいと思われるかもしれませんが、少しは理解していただけると思います。結果には、一致があるかどうか、一致率、評価の説明が表示されます。

Gemini モデルではストリーミングがデフォルトでオンになっているため、実際のレスポンスは複数行に分散されます。

c2b006aeb3f3a2fc.png

9. おもちゃの検索をサーバーレスでクラウドに移行する

このアプリをウェブに移行する準備はできていますか?Cloud Run Functions を使用してこのナレッジ エンジンをサーバーレスにするには、次の手順を行います。

  1. Google Cloud コンソールの Cloud Run Functions に移動して、新しい Cloud Run 関数を作成するか、リンク https://console.cloud.google.com/functions/add を使用します。
  2. 環境として [Cloud Run 関数] を選択します。関数名「get-toys-alloydb」を指定し、リージョンとして「us-central1」を選択します。[認証] を [未認証の呼び出しを許可] に設定し、[次へ] をクリックします。ランタイムとして [Java 17]、ソースコードとして [インライン エディタ] を選択します。
  3. デフォルトでは、エントリ ポイントは「gcfv2.HelloHttpFunction」に設定されます。Cloud Run 関数の HelloHttpFunction.javapom.xml のプレースホルダ コードを、それぞれ HelloHttpFunction.javapom.xml のコードに置き換えます。
  4. <<YOUR_PROJECT>> プレースホルダと AlloyDB 接続認証情報を、Java ファイルの値に変更してください。AlloyDB の認証情報は、この Codelab の開始時に使用したものと同じです。異なる値を使用している場合は、Java ファイルで同じ値を変更してください。
  5. [デプロイ] をクリックします。

デプロイしたら、Cloud Functions の関数が AlloyDB データベース インスタンスにアクセスできるように、VPC コネクタを作成します。

重要なステップ:

デプロイが完了すると、Google の Cloud Run Functions コンソールに関数が表示されます。新しく作成した関数(get-toys-alloydb)を検索してクリックし、[編集] をクリックして、次のように変更します。

  1. [ランタイム、ビルド、接続、セキュリティの設定] に移動します。
  2. タイムアウトを 180 秒に増やす
  3. [接続] タブに移動します。

4e83ec8a339cda08.png

  1. [上り(内向き)設定] で、[すべてのトラフィックを許可する] が選択されていることを確認します。
  2. [下り(外向き)設定] で、[ネットワーク] プルダウンをクリックし、[新しい VPC コネクタを追加] オプションを選択して、ポップアップ ダイアログに表示される手順に沿って操作します。

8126ec78c343f199.png

  1. VPC コネクタの名前を指定し、リージョンがインスタンスと同じであることを確認します。[ネットワーク] の値はデフォルトのままにして、[サブネット] を [カスタム IP 範囲] に設定し、IP 範囲を 10.8.0.0 または使用可能な類似の値に設定します。
  2. [スケーリング設定を表示] を開き、構成が次のように正確に設定されていることを確認します。

7baf980463a86a5c.png

  1. [作成] をクリックすると、このコネクタが下り(外向き)設定に表示されます。
  2. 新しく作成したコネクタを選択します。
  3. すべてのトラフィックをこの VPC コネクタ経由でルーティングするように選択します。
  4. [次へ]、[デプロイ] の順にクリックします。

10. Cloud Run 関数をテストする

更新された Cloud Functions の関数がデプロイされると、エンドポイントが次の形式で表示されます。

https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/get-toys-alloydb

または、次のように Cloud Run 関数をテストすることもできます。

PROJECT_ID=$(gcloud config get-value project)

curl -X POST https://us-central1-$PROJECT_ID.cloudfunctions.net/get-toys-alloydb \
  -H 'Content-Type: application/json' \
  -d '{"search":"I want a standing pink panther toy"}' \
  | jq .

結果は次のようになる

23861e9091565a64.png

これで、AlloyDB データでエンベディング モデルを使用して類似ベクトル検索を実行するのは、これほど簡単です。

11. ウェブ アプリケーション クライアントの構築

このパートでは、ユーザーがテキストや画像に基づいておもちゃを操作して見つけたり、ニーズに基づいて新しいおもちゃを作成したりできるウェブ アプリケーションを構築します。アプリケーションはすでにビルドされているため、以下の手順に沿って IDE にコピーし、アプリを稼働させることができます。

  1. Gemini 2.0 Flash を使用して、ユーザーがアップロードして対応するおもちゃを探す画像を記述するため、このアプリケーションの API KEY を取得する必要があります。これを行うには、https://aistudio.google.com/apikey にアクセスし、このアプリケーションを実装しているアクティブな Google Cloud プロジェクトの API キーを取得して、キーをどこかに保存します。

ae2db169e6a94e4a.png

  1. Cloud Shell ターミナルに移動します。
  2. 次のコマンドを使用してリポジトリのクローンを作成します。
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. リポジトリのクローンを作成すると、Cloud Shell エディタからプロジェクトにアクセスできるようになります。
  2. クローンを作成したプロジェクトから「get-toys-alloydb」フォルダと「toolbox-toys」フォルダを削除する必要があります。これらは、必要に応じてリポジトリから参照できる Cloud Run Functions のコードであるためです。
  3. アプリをビルドしてデプロイする前に、必要な環境変数がすべて設定されていることを確認します。Cloud Shell ターミナルに移動して、次のコマンドを実行します。
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID $PROJECT_ID

export GOOGLE_API_KEY <YOUR API KEY that you saved>
  1. アプリをローカルでビルドして実行します。

プロジェクト ディレクトリにいることを確認して、次のコマンドを実行します。

mvn package

mvn spring-boot:run 
  1. Cloud Run にデプロイする
gcloud run deploy --source .

12. 生成 AI の詳細について理解する

特に対応は必要ありません。なお、

アプリケーションをデプロイできたので、検索(テキストと画像)と生成をどのように実現したかについて理解を深めましょう

  1. ユーザーのテキストベースのベクトル検索:

これは、前述の「ベクトル検索アプリケーションのウェブサイトを利用する」セクションでデプロイした Cloud Run Functions ですでに対処されています。

  1. 画像アップロードベースのベクトル検索:

ユーザーが、検索に使用するおなじみのおもちゃの写真をアップロードしたいとします。ユーザーは、好きなおもちゃの画像をアップロードして、関連する特徴を取得できます。

Google の Gemini 2.0 Flash モデル(LangChain4j を使用して呼び出されます)を活用して画像を分析し、おもちゃの色、素材、種類、対象年齢などの関連するコンテキストを抽出します。

オープンソース フレームワークを使用して、ユーザーのマルチモーダル データ入力から、大規模言語モデルの呼び出しと一致する結果を取得するまで、わずか 5 ステップで完了しました。詳細:

package cloudcode.helloworld.web;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import java.util.Base64;
import java.util.Optional;

public class GeminiCall {
  public String imageToBase64String(byte[] imageBytes) {
    String base64Img = Base64.getEncoder().encodeToString(imageBytes);
    return base64Img;
  }

  public String callGemini(String base64ImgWithPrefix) throws Exception {
    String searchText = "";

    // 1. Remove the prefix
    String base64Img = base64ImgWithPrefix.replace("data:image/jpeg;base64,", "");

    // 2. Decode base64 to bytes
    byte[] imageBytes = Base64.getDecoder().decode(base64Img);
    String image = imageToBase64String(imageBytes);

    // 3. Get API key from environment variable
        String apiKey = Optional.ofNullable(System.getenv("GOOGLE_API_KEY"))
                .orElseThrow(() -> new IllegalArgumentException("GOOGLE_API_KEY environment variable not set"));

    // 4. Invoke Gemini 2.0
    ChatLanguageModel gemini = GoogleAiGeminiChatModel.builder()
        .apiKey(apiKey)
        .modelName("gemini-2.0-flash-001")
        .build();

    Response<AiMessage> response = gemini.generate(
        UserMessage.from(
            ImageContent.from(image, "image/jpeg"),
            TextContent.from(
                "The picture has a toy in it. Describe the toy in the image in one line. Do not add any prefix or title to your description. Just describe that toy that you see in the image in one line, do not describe the surroundings and other objects around the toy in the image. If you do not see any toy in the image, send  response stating that no toy is found in the input image.")));
   
    // 5. Get the text from the response and send it back to the controller
    searchText = response.content().text().trim();
    System.out.println("searchText inside Geminicall: " + searchText);
    return searchText;
  }
}
  1. Imagen 3 を使用して、生成 AI でユーザーのリクエストに基づいてカスタマイズされたおもちゃを作成する方法を学びます。

Imagen 3 は、カスタム設計されたおもちゃの画像を生成し、ユーザーが作成したおもちゃを明確に可視化できるようにします。5 つのステップで実現しました。

// Generate an image using a text prompt using an Imagen model
    public String generateImage(String projectId, String location, String prompt)
        throws ApiException, IOException {
      final String endpoint = String.format("%s-aiplatform.googleapis.com:443", location);
      PredictionServiceSettings predictionServiceSettings =
      PredictionServiceSettings.newBuilder().setEndpoint(endpoint).build();
     
      // 1. Set up the context and prompt
      String context = "Generate a photo-realistic image of a toy described in the following input text from the user. Make sure you adhere to all the little details and requirements mentioned in the prompt. Ensure that the user is only describing a toy. If it is anything unrelated to a toy, politely decline the request stating that the request is inappropriate for the current context. ";
      prompt = context + prompt;

      // 2. Initialize a client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      try (PredictionServiceClient predictionServiceClient =
          PredictionServiceClient.create(predictionServiceSettings)) {
 
      // 3. Invoke Imagen 3
        final EndpointName endpointName =
            EndpointName.ofProjectLocationPublisherModelName(
                projectId, location, "google", "imagen-3.0-generate-001"); //"imagegeneration@006"; imagen-3.0-generate-001
        Map<String, Object> instancesMap = new HashMap<>();
        instancesMap.put("prompt", prompt);
        Value instances = mapToValue(instancesMap);
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("sampleCount", 1);
        paramsMap.put("aspectRatio", "1:1");
        paramsMap.put("safetyFilterLevel", "block_few");
        paramsMap.put("personGeneration", "allow_adult");
        paramsMap.put("guidanceScale", 21);
        paramsMap.put("imagenControlScale", 0.95); //Setting imagenControlScale
        Value parameters = mapToValue(paramsMap);
       
      // 4. Get prediction response image
        PredictResponse predictResponse =
            predictionServiceClient.predict(
                endpointName, Collections.singletonList(instances), parameters);

      // 5. Return the Base64 Encoded String to the controller
        for (Value prediction : predictResponse.getPredictionsList()) {
          Map<String, Value> fieldsMap = prediction.getStructValue().getFieldsMap();
          if (fieldsMap.containsKey("bytesBase64Encoded")) {
            bytesBase64EncodedOuput = fieldsMap.get("bytesBase64Encoded").getStringValue();
        }
      }
      return bytesBase64EncodedOuput.toString();
    }
  }

価格予測

前のセクションでは、ユーザーが自分でデザインしたいおもちゃの画像を Imagen が生成する方法を説明しました。ユーザーが購入できるようにするには、アプリで価格を設定する必要があります。Google は、直感的なロジックを使用して、カスタム オーダー トイの価格を定義しています。ユーザーがデザインしたおもちゃに最も近い(説明の観点から)上位 5 つのおもちゃの平均価格が使用されます。

生成されたおもちゃの価格予測は、このアプリケーションの重要な部分です。この予測は、エージェント アプローチを使用して生成されます。データベース向け生成 AI ツールボックスをご紹介します。

13. データベース向け生成 AI ツールボックス

データベース向け生成 AI ツールボックスは、Google のオープンソース サーバーであり、データベースを操作するための生成 AI ツールを簡単に構築できます。これにより、接続プーリングや認証などの複雑な処理を処理することで、ツールをより簡単、迅速、安全に開発できます。エージェントがデータベース内のデータにアクセスできるようにする生成 AI ツールを構築できます。

ツールを準備してアプリをエージェント化するための設定手順は次のとおりです。ツールボックス コードラボへのリンク

これで、デプロイされた Cloud Run 関数エンドポイントを使用して、カスタム メイド トイの画像に対して生成された Imagen の結果とともに価格を入力できるようになりました。

14. ウェブ アプリケーションをテストする

アプリケーションのすべてのコンポーネントがビルドされ、デプロイされたので、クラウドで提供できる状態になりました。すべてのシナリオでアプリケーションをテストします。どのような内容かについては、こちらの動画をご覧ください。

https://www.youtube.com/shorts/ZMqUAWsghYQ

ランディング ページは次のようになります。

241db19e7176e93e.png

15. クリーンアップ

この投稿で使用したリソースについて、Google Cloud アカウントに課金されないようにするには、次の操作を行います。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。
  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

16. 完了

これで、オープンソース ライブラリを活用して堅牢な統合を構築しながら、AlloyDB、pgvector、Imagen、Gemini 2.0 を使用して、おもちゃ屋のコンテキスト検索と生成を正常に実行しました。AlloyDBVertex AIベクトル検索の機能を組み合わせることで、コンテキスト検索とベクトル検索をアクセスしやすく、効率的で、真に意味に基づくものにするための大きな一歩を踏み出しました。