1. 概要
バーチャルまたは対面式のおもちゃ屋さんで、簡単にぴったりのプレゼントを見つけられることを想像してみてください。探しているものを説明したり、おもちゃの写真をアップロードしたり、独自の作品をデザインしたりすると、ストアはユーザーのニーズを即座に理解し、カスタマイズされたエクスペリエンスを提供します。これは未来のファンタジーではなく、AI、クラウド テクノロジー、パーソナライズされた e コマースのビジョンによって実現された現実です。
課題: 想像通りの完璧な商品を見つけるのは難しい場合があります。一般的な検索語句、キーワード、ファジー検索では十分な結果が得られないことが多く、延々とページを閲覧するのは面倒で、想像していたものと実際に利用できるものとの間にギャップがあると、不満につながる可能性があります。
ソリューション: このデモ アプリケーションは、この課題に正面から取り組み、AI の力を活用して、検索コンテキストに一致するプロダクトのコンテキスト検索とカスタム生成により、真にパーソナライズされたシームレスなエクスペリエンスを提供します。
作成するアプリの概要
このラボでは、次の作業を行います。
- AlloyDB インスタンスを作成して Toys データセットを読み込む
- AlloyDB で pgvector と生成 AI モデルの拡張機能を有効にする
- 商品説明からエンベディングを生成し、ユーザーの検索テキストに対してリアルタイムのコサイン類似度検索を実行する
- Gemini 2.0 Flash を呼び出して、ユーザーがアップロードした画像を説明し、コンテキストに沿ったおもちゃの検索を行う
- Imagen 3 を呼び出して、ユーザーの興味に基づいておもちゃをカスタム作成する
- データベース向け生成 AI ツールボックスを使用して作成した価格予測ツールを呼び出して、カスタム作成したおもちゃの価格の詳細を取得する
- サーバーレスの Cloud Run functions にソリューションをデプロイする
要件
2. アーキテクチャ
データフロー: データがシステム内をどのように移動するかを詳しく見てみましょう。
- AI を活用した RAG(検索拡張生成)によるコンテキスト検索
たとえば、「赤い車」を検索するだけでなく、以下のような内容を理解します。
「3 歳の男の子に適した小型車」
基盤としての AlloyDB: Google Cloud のフルマネージドの PostgreSQL 互換データベースである AlloyDB を使用して、説明、画像 URL、その他の関連する属性など、おもちゃのデータを保存します。
セマンティック検索用の pgvector: PostgreSQL 拡張機能である pgvector を使用すると、おもちゃの説明とユーザーの検索クエリの両方のベクトル エンベディングを保存できます。これにより、セマンティック検索が可能になります。つまり、システムはキーワードの完全一致だけでなく、単語の背後にある意味も理解します。
関連性のコサイン類似度: コサイン類似度を使用して、ユーザーの検索ベクトルとおもちゃの説明ベクトルの間のセマンティック類似度を測定し、最も関連性の高い結果を表示します。
速度と精度のための ScaNN インデックス: 特に玩具の在庫が増えるにつれて、迅速かつ正確な結果を保証するために、ScaNN(Scalable Nearest Neighbors)インデックスを統合します。これにより、ベクトル検索の効率と再現率が大幅に向上します。
- Gemini 2.0 Flash を使用した画像ベースの検索と理解
たとえば、ユーザーがテキストとしてコンテキストを入力する代わりに、検索に使用したいおもちゃの写真をアップロードしたいとします。ユーザーは、気に入ったおもちゃの画像をアップロードして、関連する機能を取得できます。LangChain4j を使用して呼び出される Google の Gemini 2.0 Flash モデルを活用して、画像を分析し、おもちゃの色、素材、種類、年齢層などの関連するコンテキストを抽出します。
- 生成 AI(Imagen 3)でカスタマイズされた夢のおもちゃを制作する
ユーザーが自分だけの玩具を作ろうと決めたときに、本当の魔法が起こります。Imagen 3 を使用することで、簡単なテキスト プロンプトで夢のおもちゃを説明できます。「紫色の翼と優しい顔をしたドラゴンのぬいぐるみを作って」とリクエストすると、そのドラゴンが画面に表示される様子を想像してみてください。Imagen 3 は、カスタム デザインのおもちゃの画像を生成し、ユーザーが作成したものを明確に視覚化します。
- エージェントとデータベース向け生成 AI ツールボックスによる価格予測
カスタムデザインのおもちゃの製造コストを推定する価格予測機能を実装しました。これは、高度な価格計算ツールを含むエージェントによって実現されます。
データベース向け生成 AI ツールボックス: このエージェントは、Google の新しいオープンソース ツールであるデータベース向け生成 AI ツールボックスを使用して、データベースとシームレスに統合されています。これにより、エージェントは材料費、製造プロセス、その他の関連要素に関するリアルタイム データにアクセスして、正確な価格見積もりを提供できます。詳しくは、こちらをご覧ください。
- Java Spring Boot、Gemini Code Assist、Cloud Run による開発の効率化とサーバーレス デプロイ
アプリケーション全体は、堅牢でスケーラブルなフレームワークである Java Spring Boot を使用して構築されています。開発プロセス全体、特にフロントエンド開発で Gemini Code Assist を活用したことで、開発サイクルが大幅に短縮され、コードの品質が向上しました。アプリケーション全体のデプロイには Cloud Run を使用し、データベースとエージェント機能のデプロイには Cloud Run functions を独立したエンドポイントとして使用しました。
3. 始める前に
プロジェクトを作成する
- Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。
- Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
- Cloud Shell(Google Cloud で動作するコマンドライン環境)を使用します。この環境には bq がプリロードされています。Google Cloud コンソールの上部にある [Cloud Shell をアクティブにする] をクリックします。
![[Cloud Shell をアクティブにする] ボタンの画像](https://codelabs.developers.google.com/static/toy-store-app/img/7875ca05ca6f7cab.png?hl=ja)
- Cloud Shell に接続したら、次のコマンドを使用して、すでに認証済みであることと、プロジェクトがプロジェクト ID に設定されていることを確認します。
gcloud auth list
- Cloud Shell で次のコマンドを実行して、gcloud コマンドがプロジェクトを認識していることを確認します。
gcloud config list project
- プロジェクトが設定されていない場合は、次のコマンドを使用して設定します。
gcloud config set project <YOUR_PROJECT_ID>
- 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 クラスタ、インスタンス、テーブルを作成しましょう。
クラスタとインスタンスを作成する
- Cloud コンソールの AlloyDB ページに移動します。Cloud コンソールでほとんどのページを簡単に見つけるには、コンソールの検索バーを使用して検索します。
- このページで [クラスタを作成] を選択します。

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

- デフォルト ネットワークを選択すると、次のような画面が表示されます。
[接続の設定] を選択します。
- [自動的に割り当てられた IP 範囲を使用する] を選択して、[続行] をクリックします。情報を確認したら、[接続を作成] を選択します。

- ネットワークを設定したら、クラスタの作成を続行できます。[CREATE CLUSTER] をクリックして、次のようにクラスタの設定を完了します。

インスタンス ID を必ず変更してください
vector-instance
変更できない場合は、以降のすべての参照でインスタンス ID を変更してください。
クラスタの作成には 10 分ほどかかります。成功すると、作成したクラスタの概要を示す画面が表示されます。
5. データの取り込み
次に、店舗に関するデータを含むテーブルを追加します。AlloyDB に移動し、プライマリ クラスタと AlloyDB Studio を選択します。

インスタンスの作成が完了するまで待つ必要がある場合があります。完了したら、クラスタの作成時に作成した認証情報を使用して AlloyDB にログインします。PostgreSQL の認証には次のデータを使用します。
- ユーザー名: 「
postgres」 - データベース: 「
postgres」 - パスワード: 「
alloydb」
AlloyDB Studio への認証が成功すると、エディタに SQL コマンドが入力されます。最後のウィンドウの右にあるプラス記号を使用して、複数のエディタ ウィンドウを追加できます。

必要に応じて [実行]、[形式]、[クリア] オプションを使用して、エディタ ウィンドウに AlloyDB のコマンドを入力します。
拡張機能を有効にする
このアプリのビルドには、拡張機能 pgvector と google_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 フィールドが含まれています。他のフィールドはラボで後ほど入力します。
そこから最初の 5 行/挿入ステートメントのみをコピーし、空白のエディタタブに貼り付けて、[実行] を選択します。トライアルの請求先アカウントを使用していない場合は、すべての挿入ステートメントをコピーして実行できる可能性があります。
テーブルの内容を確認するには、[エクスプローラ] セクションを開き、apparels というテーブルが表示されるまで展開します。三点リーダー(⋮)を選択して、テーブルのクエリ オプションを表示します。SELECT ステートメントが新しいエディタタブで開きます。

権限を付与
次のステートメントを実行して、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」、「на берегу океана」など、さまざまな表現がありますが、意味的意味、つまり ML 用語で言うとエンベディングは非常に近いものになります。
データとコンテキストの準備ができたので、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;
次のように、おもちゃの説明のエンベディング ベクトル(浮動小数点数の配列)が返されます。

注: 無料枠で新しく作成された Google Cloud プロジェクトでは、Embedding モデルに対して 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 2;
このクエリを詳しく見てみましょう。
このクエリでは、
- ユーザーの検索テキストは「
I want a white plush teddy bear toy with a floral pattern.」です。 - モデル
text-embedding-005を使用して、embedding()メソッドでエンベディングに変換しています。このステップは、テーブル内のすべての項目にエンベディング関数を適用した最後のステップと似ています。 - 「
<=>」は、コサイン類似度距離メソッドの使用を表します。使用可能な類似度指標については、pgvector のドキュメントをご覧ください。 - エンベディング メソッドの結果をベクトル型に変換して、データベースに保存されているベクトルと互換性を持たせています。
- LIMIT 5 は、検索テキストの最近傍を 5 つ抽出することを表します。
結果は次のようになります。

検索結果を見ると、検索テキストとかなり近い一致が見つかっています。テキストを変更して、結果がどのように変化するかを確認してみます。
注意事項:
ここで、ScaNN インデックスを使用して、このベクトル検索結果のパフォーマンス(クエリ時間)、効率、再現率を向上させるとします。インデックスがある場合とない場合の結果の違いを比較するには、こちらのブログの手順をご覧ください。
省略可: ScaNN インデックスを使用して効率と再現率を改善する
レコード数が 100 未満の場合は、この手順を無視してください。
便宜上、インデックス作成の手順を以下に示します。
- クラスタ、インスタンス、コンテキスト、エンベディングはすでに作成されているため、次のステートメントを使用して ScaNN 拡張機能をインストールするだけです。
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
- 次に、インデックス(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~1,048,576 の任意の値に設定します。この値を決定する方法の詳細については、ScaNN インデックスをチューニングするをご覧ください。
ScaNN リポジトリで推奨されているように、データポイント数の平方根を使用しました(パーティショニングを行う場合、num_leaves はデータポイント数の平方根とほぼ同じにする必要があります)。
- 次のクエリを使用して、インデックスが作成されているかどうかを確認します。
SELECT * FROM pg_stat_ann_indexes;
- インデックスなしで使用したのと同じクエリを使用して、ベクトル検索を実行します。
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 のラボで使用したクエリと同じです。ただし、フィールドのインデックスが作成されました。
- インデックスの有無にかかわらず(インデックスを削除して)、簡単な検索クエリでテストします。
このユースケースには 72 件のレコードしかないため、インデックスは実際には有効になりません。別のユースケースで実施したテストの結果は次のとおりです。
インデックス登録されたエンベディング データに対して同じベクトル検索クエリを実行すると、高品質の検索結果と効率が得られます。インデックスを使用すると、効率が大幅に向上します(実行時間: ScaNN なしで 10.37 ミリ秒、ScaNN ありで 0.87 ミリ秒)。このトピックについて詳しくは、こちらのブログをご覧ください。
8. LLM による一致の検証
次に進んで、最適な一致をアプリケーションに返すサービスを作成する前に、生成 AI モデルを使用して、これらの潜在的な回答がユーザーと共有するのに本当に適切で安全かどうかを検証しましょう。
インスタンスが Gemini 用に設定されていることを確認する
まず、クラスタとインスタンスで Google ML Integration がすでに有効になっているかどうかを確認します。AlloyDB Studio で、次のコマンドを実行します。
show google_ml_integration.enable_model_support;
値が 「on」と表示されている場合は、次の 2 つの手順をスキップして、AlloyDB と Vertex AI モデルの統合の設定に直接進むことができます。
- AlloyDB クラスタのプライマリ インスタンスに移動し、[プライマリ インスタンスを編集] をクリックします。

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

[オン] に設定されていない場合は、[オン] に設定して [インスタンスを更新] ボタンをクリックします。この手順には数分かかります。
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 モデルへの古い参照を削除し、上記の CALL ステートメントで再度作成する必要があります。また、gemini-1.5 の今後の呼び出しで問題が発生した場合は、関数 ml_predict_row で grant execute を再度実行する必要があります。
回答の評価
次のセクションでは、クエリからのレスポンスが妥当であることを確認するために、1 つの大きなクエリを使用しますが、このクエリは理解しにくい場合があります。ここでは、各要素を見て、それらがどのように連携するかを説明します。
- まず、データベースにリクエストを送信して、ユーザーのクエリに最も近い 10 件の候補を取得します。
- 有効なレスポンスを判断するために、レスポンスの評価方法を説明する外部クエリを使用します。クエリの一部として、検索テキストである
recommended_textフィールドと、内部テーブルのcontent(おもちゃの説明フィールド)を使用します。 - それを使用して、返されたレスポンスの「良さ」をレビューします。
predict_rowは結果を JSON 形式で返します。コード「-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'"」は、その JSON から実際のテキストを抽出するために使用されます。返される実際の JSON を確認するには、このコードを削除します。- 最後に、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 1) 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 モデルではデフォルトでストリーミングが有効になっているため、実際のレスポンスは複数行にわたって表示されます。

9. おもちゃ検索をサーバーレスでクラウドに移行する
このアプリをウェブに移行する準備はできましたか?次の手順に沿って、Cloud Run Functions を使用して Knowledge Engine Serverless を作成します。
- Google Cloud コンソールの Cloud Run 関数に移動して、新しい Cloud Run 関数を作成するか、リンク(https://console.cloud.google.com/functions/add)を使用します。
- [環境] で [Cloud Run functions] を選択します。関数名「get-toys-alloydb」を指定し、リージョンとして「us-central1」を選択します。[認証] を [未認証の呼び出しを許可] に設定し、[次へ] をクリックします。ランタイムとして [Java 17] を選択し、ソースコードとして [インライン エディタ] を選択します。
- デフォルトでは、エントリ ポイントは「
gcfv2.HelloHttpFunction」に設定されます。Cloud Run functions のHelloHttpFunction.javaとpom.xmlのプレースホルダ コードを、それぞれ HelloHttpFunction.java と pom.xml のコードに置き換えます。 - Java ファイルで、<<YOUR_PROJECT>> プレースホルダと AlloyDB 接続認証情報を実際の値に置き換えてください。AlloyDB の認証情報は、この Codelab の開始時に使用したものです。異なる値を使用している場合は、Java ファイルで同じ値を指定してください。
- [デプロイ] をクリックします。
デプロイが完了したら、Cloud Functions が AlloyDB データベース インスタンスにアクセスできるように、VPC コネクタを作成します。
重要な手順:
デプロイの準備が整ったら、Google Cloud Run Functions コンソールで関数を確認できます。新しく作成した関数(get-toys-alloydb)を検索してクリックし、[編集] をクリックして、次の項目を変更します。
- [ランタイム、ビルド、接続、セキュリティの設定] に移動する
- タイムアウトを 180 秒に増やします
- [接続] タブに移動します。

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

- VPC コネクタの名前を指定し、リージョンがインスタンスと同じであることを確認します。[ネットワーク] の値はデフォルトのままにし、[サブネット] を [カスタム IP 範囲] に設定して、使用可能な IP 範囲(10.8.0.0 など)を指定します。
- [SHOW SCALING SETTINGS] を展開し、構成が次のとおりに設定されていることを確認します。

- [作成] をクリックすると、このコネクタが下り(外向き)設定に表示されます。
- 新しく作成したコネクタを選択します。
- すべてのトラフィックがこの VPC コネクタ経由でルーティングされるようにします。
- [次へ]、[デプロイ] の順にクリックします。
10. Cloud Run 関数をテストする
更新された Cloud Functions の関数がデプロイされると、エンドポイントが生成されます。コピーして、次のコマンドで置き換えます。
または、次のように Cloud Run 関数をテストすることもできます。
PROJECT_ID=$(gcloud config get-value project)
curl -X POST <<YOUR_ENDPOINT>> \
-H 'Content-Type: application/json' \
-d '{"search":"I want a standing pink panther toy"}' \
| jq .
結果は次のようになる

これで、AlloyDB データでエンベディング モデルを使用して類似性ベクトル検索を実行するのは、これほど簡単です。
11. ウェブ アプリケーション クライアントの構築
このパートでは、ユーザーが操作して、テキストや画像に基づいて一致するおもちゃを見つけたり、ニーズに基づいて新しいおもちゃを作成したりできるウェブ アプリケーションを構築します。アプリケーションはすでにビルドされているため、以下の手順に沿って IDE にコピーし、アプリを起動して実行できます。
- Gemini 2.0 Flash を使用して、ユーザーがアップロードする可能性のある画像を説明し、一致するおもちゃを見つけるため、このアプリケーションの API キーを取得する必要があります。これを行うには、https://aistudio.google.com/apikey に移動し、このアプリケーションを実装するアクティブな Google Cloud プロジェクトの API キーを取得して、キーをどこかに保存します。

- Cloud Shell ターミナルに移動する
- 次のコマンドを使用して、リポジトリのクローンを作成します。
git clone https://github.com/AbiramiSukumaran/toysearch
cd toysearch
- リポジトリのクローンが作成されると、Cloud Shell エディタからプロジェクトにアクセスできるようになります。
- クローンされたプロジェクトから「get-toys-alloydb」フォルダと「toolbox-toys」フォルダを削除する必要があります。これら 2 つは Cloud Run Functions コードであり、必要に応じてリポジトリから参照できます。
- ウェブフォルダの GenerateToy.java に移動し、次の行を見つけて削除します。成人向けコンテンツを許可するには特別な権限が必要になる場合があり、一部のトライアル請求先アカウントでは利用できない可能性があるためです。
paramsMap.put("personGeneration", "allow_adult");
- アプリをビルドしてデプロイする前に、必要な環境変数がすべて設定されていることを確認してください。Cloud Shell ターミナルに移動して、次のコマンドを実行します。
PROJECT_ID=$(gcloud config get-value project)
export PROJECT_ID=$PROJECT_ID
export GOOGLE_API_KEY=<YOUR API KEY that you saved>
- アプリをローカルでビルドして実行します。
プロジェクト ディレクトリに移動し、次のコマンドを実行します。
mvn package
mvn spring-boot:run
- Cloud Run にデプロイする
gcloud run deploy --source .
12. 生成 AI の詳細を理解する
何もする必要はない。参考情報:
アプリケーションをデプロイしたので、検索(テキストと画像)と生成をどのように実現したかについて少し時間を取って理解しましょう。
- ユーザー テキストに基づくベクトル検索:
これは、「ベクトル検索アプリケーションのウェブを操作する」セクションでデプロイした Cloud Run functions で対応済みです。
- 画像アップロードに基づくベクトル検索:
たとえば、ユーザーがテキストとしてコンテキストを入力する代わりに、検索に使用したいおもちゃの写真をアップロードしたいとします。ユーザーは、気に入ったおもちゃの画像をアップロードして、関連する機能を取得できます。
LangChain4j を使用して呼び出される Google の Gemini 2.0 Flash モデルを活用して、画像を分析し、おもちゃの色、素材、種類、年齢層などの関連するコンテキストを抽出します。
わずか 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;
}
}
- 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 が生成する仕組みについて説明しました。購入できるようにするには、アプリで価格を設定する必要があります。弊社では、直感的なロジックを採用して、カスタムの受注生産のおもちゃの価格を定義しています。ロジックは、ユーザーがデザインしたおもちゃに最も近い(説明の点で)おもちゃの上位 5 個の平均価格を使用することです。
生成されたおもちゃの価格予測は、このアプリケーションの重要な部分であり、エージェント アプローチを使用して生成しました。データベース向け生成 AI ツールボックスの公開ベータ版を
13. データベース向け生成 AI ツールボックス
データベース向け生成 AI ツールボックスは、データベースとやり取りするための生成 AI ツールを簡単に構築できるようにする、Google のオープンソース サーバーです。これにより、接続プーリングや認証などの複雑な処理に対応して、ツールの開発をより簡単、迅速、セキュアに行うことができます。エージェントがデータベース内のデータにアクセスできるようにする生成 AI ツールの構築に役立ちます。
ツールを準備してアプリケーションをエージェント化するために必要な手順は、ツールボックスの Codelab へのリンクをご覧ください。
これで、デプロイされた Cloud Run 関数エンドポイントを使用して、カスタムの受注生産のおもちゃの画像に対して生成された Imagen の結果とともに価格を入力できるようになりました。
14. ウェブ アプリケーションをテストする
アプリケーションのすべてのコンポーネントがビルドされてデプロイされたので、クラウドで提供する準備が整いました。すべてのシナリオでアプリケーションをテストします。以下に、どのような結果になるかを示す動画リンクを示します。
https://www.youtube.com/shorts/ZMqUAWsghYQ
ランディング ページは次のようになります。

15. クリーンアップ
この投稿で使用したリソースについて、Google Cloud アカウントに課金されないようにするには、次の手順を行います。
- Google Cloud コンソールで、[リソースの管理] ページに移動します。
- プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
- ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。
16. 完了
おめでとうございます!AlloyDB、pgvector、Imagen、Gemini 2.0 を使用して Toystore コンテキスト検索と生成を正常に実行し、オープンソース ライブラリを活用して堅牢な統合を構築しました。AlloyDB、Vertex AI、ベクトル検索の機能を組み合わせることで、コンテキスト検索とベクトル検索をアクセス可能で効率的かつ真に意味主導型にするという点で、大きな進歩を遂げました。